Autosave: 20260619-124833

This commit is contained in:
Flatlogic Bot 2026-06-19 12:48:30 +00:00
parent f954e3c153
commit e12c93f715
20 changed files with 1303 additions and 123 deletions

359
PROJECT_LOG.md Normal file
View File

@ -0,0 +1,359 @@
# Project Log Version — VORTA Universe
Tanggal pembaruan: **19 Juni 2026**
Status: **Development / VORTA Feed MVP Active**
## Ringkasan Project
**VORTA Universe** adalah konsep ekosistem digital dengan visi:
> **Trust • Connect • Grow**
Project ini diarahkan menjadi platform super-app yang menggabungkan workspace bisnis, koneksi sosial, marketplace, dompet digital, modul AI, dan sistem skor kepercayaan dalam satu pengalaman aplikasi.
Stack project aktif saat ini:
- **Frontend:** Next.js + Tailwind CSS
- **Backend:** Node.js / Express
- **Database:** PostgreSQL + Sequelize
- **Auth:** JWT + role/permission system
- **Mode saat ini:** Development environment dengan hot reload
> Catatan: kode Express + SQLite standalone yang pernah dikirim sebelumnya diperlakukan sebagai referensi konsep. Untuk project aktif ini, integrasi sebaiknya mengikuti struktur Next.js + PostgreSQL yang sudah berjalan.
---
## Versi Saat Ini
### v0.4.0-dev — VORTA Feed MVP
Tanggal: **19 Juni 2026**
#### Fitur yang Sudah Aktif
##### 1. Halaman Khusus VORTA Feed
Halaman authenticated baru tersedia di:
- `/vorta-feed`
Halaman ini memakai endpoint native project, bukan server SQLite standalone.
Fungsi aktif:
- Memuat feed dari PostgreSQL.
- Menampilkan statistik jumlah post.
- Menampilkan statistik jumlah komentar dan reply.
- Membuat post baru.
- Membuat komentar pada post.
- Membuat reply dengan `parent_id`.
- Refresh feed.
- Reset feed demo.
- Membersihkan draft UI tanpa menghapus data database.
##### 2. Storage Posts & Comments
MVP feed memakai tabel PostgreSQL yang sudah terintegrasi dengan Sequelize:
- `vorta_social_posts`
- `vorta_social_comments`
Relasi aktif:
- Post memiliki banyak comment.
- Comment menyimpan `post_id`.
- Reply comment memakai `parent_id`.
- Data terscope ke user/organization sesuai helper VORTA Universe.
##### 3. API Feed Aktif
Endpoint yang dipakai halaman VORTA Feed:
- `GET /vorta-universe/social-feed`
- `POST /vorta-universe/posts`
- `POST /vorta-universe/posts/:postId/comments`
- `POST /vorta-universe/social-feed/reset`
Semua request frontend memakai axios relative path agar tetap lewat base `/api` project.
##### 4. Sidebar Navigation
Sidebar grup **VORTA Universe** sekarang punya menu:
- **VORTA Feed**`/vorta-feed`
- **Feed Bisnis Dashboard**`/vorta-universe#vorta-social`
##### 5. Verifikasi v0.4.0-dev
Pemeriksaan yang sudah dilakukan:
- Frontend lint: **0 error**.
- Browser public domain: halaman `/vorta-feed` berhasil dibuka.
- Load feed PostgreSQL: berhasil.
- Create post: berhasil, statistik post naik.
- Create comment: berhasil, statistik komentar naik.
- Runtime error log frontend/backend: bersih setelah pengecekan.
---
## Riwayat Versi
### v0.3.0-dev — Front Page Interaction Release
Tanggal: **19 Juni 2026**
#### Fitur yang Sudah Aktif
##### 1. Halaman Muka VORTA Universe
Halaman muka sudah berfungsi sebagai landing page interaktif untuk memperkenalkan ekosistem VORTA.
Menu utama yang aktif:
- **Beranda**
- **Workspace**
- **Super-App**
- **Skor**
- **Laporan**
Setiap menu dapat membawa pengguna ke section yang sesuai dan memperbarui status aksi di halaman.
##### 2. Hero Action Buttons
Tombol utama di hero sudah memiliki fungsi:
- **Lihat Alur Super-App**
Mengarahkan pengguna ke section pilar / super-app.
- **Jalankan Demo Skor**
Mengisi simulator skor secara otomatis dan membawa pengguna ke form skor.
- **Masuk ke Admin**
Mengarahkan pengguna ke halaman login/admin.
##### 3. Workspace Modules
Kartu workspace sudah dapat diklik dan memperbarui modul aktif:
- **Chat bisnis**
- **Feed sosial**
- **Marketplace**
- **Dompet digital**
Setiap kartu mengubah status modul aktif dan mengarahkan pengguna ke section yang relevan.
##### 4. Super-App Pillars
Pilar super-app sudah memiliki interaksi preview dan link modul.
Pilar yang tersedia:
- **Mega Super-App**
- **Vorta Nexus**
- **Facta.AI / Commerce**
- **Vorta Synapse**
Fungsi aktif:
- Tombol **Preview di halaman ini**
- Highlight **Pilar aktif**
- Link ke halaman modul:
- `/mega-super-app`
- `/vorta-universe`
- `/vorta-commerce`
- `/vorta-synapse`
##### 5. Trust Score Simulator
Form skor kepercayaan sudah berjalan dengan fungsi:
- Validasi nama kosong
- Isi contoh cepat
- Reset formulir
- Submit laporan valid
- Laporan baru langsung muncul
- Detail laporan langsung terbuka setelah submit
##### 6. Reports Section
Bagian laporan sudah memiliki aksi:
- **Jalankan Contoh**
- **Bersihkan Laporan**
- Empty state dengan tombol **Buat Contoh Sekarang**
##### 7. Verifikasi Terakhir
Pemeriksaan terakhir yang sudah dilakukan:
- Frontend lint: **0 error**
- Browser public page: berhasil dibuka dan diuji
- Tombol demo skor: berjalan
- Kartu workspace: berjalan
- Preview pilar: berjalan
- Submit form skor: berjalan
- Link modul Synapse: berhasil dibuka
- Runtime error log frontend/backend: bersih setelah pengecekan terakhir
### v0.2.0-dev — Interactive Landing Foundation
Status: selesai
Perubahan utama:
- Landing page VORTA Universe dibuat sebagai pusat pengenalan ekosistem.
- Section workspace, super-app, skor, dan laporan mulai disusun.
- Struktur konten diarahkan ke konsep super-app digital.
- Tombol dan menu mulai disiapkan untuk interaksi pengguna.
### v0.1.0-dev — Initial Concept
Status: selesai
Perubahan utama:
- Konsep awal **VORTA Universe** dibuat.
- Visi utama ditetapkan: **Trust • Connect • Grow**.
- Ide fitur awal:
- Website utama
- Produk digital
- Lokasi / informasi perusahaan
- Kontak
- Login / admin
- Feed/posting sosial
- Komentar bertingkat
Catatan teknis:
- Konsep awal sempat ditulis dalam bentuk server Express + SQLite standalone.
- Untuk project aktif, konsep tersebut akan diadaptasi ke arsitektur yang sudah tersedia: Next.js + Express + PostgreSQL.
---
## Backlog / Roadmap Berikutnya
### Prioritas 1 — Penyempurnaan VORTA Feed
Status: MVP selesai pada `v0.4.0-dev`.
Lanjutan yang disarankan:
- Tambahkan edit/delete post.
- Tambahkan edit/delete komentar.
- Tambahkan like/reaction.
- Tambahkan upload gambar/video post.
- Tambahkan filter/pencarian feed.
- Tambahkan pagination atau infinite scroll.
- Tambahkan moderasi/admin review.
### Prioritas 2 — Public Website Content
Tujuan:
Memperkuat website utama agar siap menjadi halaman publik VORTA Universe.
Rencana fitur:
- Section Tentang Kami
- Section Produk
- Section Lokasi
- Section Hubungi Kami
- CTA menuju login/admin
- CTA menuju demo skor
- Konten SEO dasar
### Prioritas 3 — Super-App Module Pages
Tujuan:
Menyempurnakan halaman detail untuk setiap pilar.
Halaman target:
- Mega Super-App
- Vorta Nexus
- Vorta Commerce / Facta.AI
- Vorta Synapse
Rencana fitur:
- Deskripsi modul
- Use case
- Status fitur
- Tombol demo / simulasi
- Link antar modul
### Prioritas 4 — Trust Score Data Persistence
Tujuan:
Menyimpan laporan skor kepercayaan ke database, bukan hanya state halaman.
Rencana teknis:
- Buat entity Trust Reports
- Simpan nama, kategori, skor, catatan, dan status
- Tambahkan list laporan dari backend
- Tambahkan detail laporan
- Tambahkan filter dan pencarian
### Prioritas 5 — Admin Dashboard VORTA
Tujuan:
Membuat admin dashboard untuk mengelola data ekosistem.
Rencana fitur:
- Ringkasan statistik
- Jumlah laporan skor
- Jumlah post/feed
- Jumlah komentar
- Aktivitas terbaru
- Manajemen user jika dibutuhkan
---
## Catatan Integrasi Kode Express + SQLite
Kode standalone yang menggunakan:
```js
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('vorta.db');
```
Tidak akan ditempel langsung ke project aktif karena project ini sudah menggunakan PostgreSQL dan Sequelize.
Adaptasi yang benar:
| Konsep Lama | Adaptasi Project Aktif |
| --- | --- |
| Express standalone `/` | Next.js page di `frontend/src/pages` |
| SQLite `vorta.db` | PostgreSQL via Sequelize |
| `CREATE TABLE posts` manual | Sequelize migration + model |
| `CREATE TABLE comments` manual | Sequelize migration + model |
| HTML string di `res.send` | React component + Tailwind |
| Manual CSS dalam `<style>` | Tailwind + existing theme system |
---
## Definition of Done Versi Berikutnya
Versi berikutnya dapat dianggap selesai jika:
- Posts & comments sudah tersimpan di PostgreSQL
- Feed page bisa menampilkan daftar post
- Pengguna bisa membuat post baru
- Pengguna bisa memberi komentar
- Reply bertingkat berjalan minimal 1 level
- Halaman tetap lolos lint tanpa error
- Runtime log backend/frontend bersih setelah pengujian
---
## Catatan Pemilik Project
Nama project: **VORTA Universe**
Tagline: **Trust • Connect • Grow**
Arah produk: **Super-app ecosystem with trust, commerce, social, workspace, and AI modules**

View File

@ -19,7 +19,7 @@ export default function AsideMenu({
<>
<AsideMenuLayer
menu={props.menu}
className={`${isAsideMobileExpanded ? 'left-0' : '-left-60 lg:left-0'} ${
className={`${isAsideMobileExpanded ? 'left-0' : '-left-80 lg:left-0'} ${
!isAsideLgActive ? 'lg:hidden xl:flex' : ''
}`}
onAsideLgCloseClick={props.onAsideLgClose}

View File

@ -46,7 +46,7 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
<BaseIcon path={item.icon} className={`flex-none mx-3 ${activeClassAddon}`} size="18" />
)}
<span
className={`grow text-ellipsis line-clamp-1 ${
className={`grow min-w-0 whitespace-normal break-words leading-snug text-black ${
item.menu ? '' : 'pr-12'
} ${activeClassAddon}`}
>
@ -63,13 +63,13 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
)
const componentClass = [
'flex cursor-pointer py-1.5 ',
'flex cursor-pointer items-start py-2 text-black ',
isDropdownList ? 'px-6 text-sm' : '',
item.color
? getButtonColor(item.color, false, true)
: `${asideMenuItemStyle}`,
isLinkActive
? `text-black ${activeLinkColor} dark:text-white dark:bg-dark-800`
? `text-black ${activeLinkColor}`
: '',
].join(' ');

View File

@ -57,17 +57,17 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props
return (
<aside
id='asideMenu'
className={`${className} zzz lg:py-2 lg:pl-2 w-60 fixed flex z-40 top-0 h-screen transition-position overflow-hidden`}
className={`${className} zzz lg:py-2 lg:pl-2 w-80 fixed flex z-40 top-0 h-screen transition-position overflow-hidden`}
>
<div
className={`flex-1 flex flex-col overflow-hidden dark:bg-dark-900 ${asideStyle} ${corners}`}
className={`flex-1 flex flex-col overflow-hidden bg-white text-black ${asideStyle} ${corners}`}
>
<div
className={`flex flex-row h-14 items-center justify-between ${asideBrandStyle}`}
className={`flex flex-row h-14 items-center justify-between bg-white text-black ${asideBrandStyle}`}
>
<div className="text-center flex-1 lg:text-left lg:pl-6 xl:text-center xl:pl-0">
<b className="font-black">VORTA-COMMERCE</b>
<b className="font-black">VORTA UNIVERSE</b>
{organizationName && <p>{organizationName}</p>}

View File

@ -85,18 +85,18 @@ export default function LayoutAuthenticated({
}, [router.events, dispatch])
const layoutAsidePadding = 'xl:pl-60'
const layoutAsidePadding = 'xl:pl-80'
return (
<div className={`${darkMode ? 'dark' : ''} overflow-hidden lg:overflow-visible`}>
<div
className={`${layoutAsidePadding} ${
isAsideMobileExpanded ? 'ml-60 lg:ml-0' : ''
isAsideMobileExpanded ? 'ml-80 lg:ml-0' : ''
} pt-14 min-h-screen w-screen transition-position lg:w-auto ${bgColor} dark:bg-dark-800 dark:text-slate-100`}
>
<NavBar
menu={menuNavBar}
className={`${layoutAsidePadding} ${isAsideMobileExpanded ? 'ml-60 lg:ml-0' : ''}`}
className={`${layoutAsidePadding} ${isAsideMobileExpanded ? 'ml-80 lg:ml-0' : ''}`}
>
<NavBarItemPlain
display="flex lg:hidden"

View File

@ -25,10 +25,15 @@ const menuAside: MenuAsideItem[] = [
icon: resolveMenuIcon('mdiApps'),
label: 'Modules',
},
{
href: '/vorta-feed',
icon: resolveMenuIcon('mdiPostOutline'),
label: 'VORTA Feed',
},
{
href: '/vorta-universe#vorta-social',
icon: resolveMenuIcon('mdiPostOutline'),
label: 'Feed Bisnis',
label: 'Feed Bisnis Dashboard',
},
{
href: '/vorta-universe#vorta-marketplace',
@ -63,30 +68,29 @@ const menuAside: MenuAsideItem[] = [
{
href: '/vorta-commerce',
icon:
'mdiStorefrontOutline' in icon
? (icon['mdiStorefrontOutline' as keyof typeof icon] as string)
: icon.mdiViewDashboardOutline,
label: 'VORTA-Commerce',
icon: resolveMenuIcon('mdiStorefrontOutline'),
label: '1. VORTA Commerce',
},
{
href: '/mega-super-app',
label: 'Mega Super-App',
icon:
'mdiApps' in icon
? (icon['mdiApps' as keyof typeof icon] as string)
: icon.mdiViewDashboardOutline,
},
{
href: '/vorta-synapse',
label: '04 Vorta Synapse',
icon:
'mdiTransitConnectionVariant' in icon
? (icon['mdiTransitConnectionVariant' as keyof typeof icon] as string)
: icon.mdiViewDashboardOutline,
label: '2. Vorta Synapse',
icon: resolveMenuIcon('mdiTransitConnectionVariant'),
},
{
href: '/mega-super-app',
label: '3. Mega Super-App',
icon: resolveMenuIcon('mdiApps'),
},
{
href: '/vorta-universe#vorta-overview',
label: '4. Vorta Nexus',
icon: resolveMenuIcon('mdiShieldCheck'),
},
{
href: '/facta_queries/facta_queries-list',
label: '5. Facta.Ai',
icon: resolveMenuIcon('mdiShieldSearchOutline'),
permissions: 'READ_FACTA_QUERIES',
},
{
@ -475,7 +479,7 @@ const menuAside: MenuAsideItem[] = [
},
{
href: '/facta_queries/facta_queries-list',
label: 'Kueri Facta',
label: 'Facta.Ai Kueri',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon:
@ -486,7 +490,7 @@ const menuAside: MenuAsideItem[] = [
},
{
href: '/facta_answers/facta_answers-list',
label: 'Jawaban Facta',
label: 'Facta.Ai Jawaban',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon:

View File

@ -149,8 +149,8 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
setStepsEnabled(false);
};
const title = 'VORTA-COMMERCE'
const description = "Commerce operating system for products, inventory, orders, payments, shipping, marketing, reports, and settings."
const title = 'VORTA UNIVERSE'
const description = "VORTA Universe menyatukan VORTA Commerce, Vorta Synapse, Mega Super-App, Vorta Nexus, dan Facta.Ai."
const url = "https://flatlogic.com/"
const image = "https://project-screens.s3.amazonaws.com/screenshots/40285/app-hero-20260618-210659.png"
const imageWidth = '1920'

View File

@ -90,10 +90,10 @@ const Facta_queriesTablesPage = () => {
return (
<>
<Head>
<title>{getPageTitle('Facta_queries')}</title>
<title>{getPageTitle('5. Facta.Ai')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Facta_queries" main>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="5. Facta.Ai" main>
{''}
</SectionTitleLineWithButton>
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>

View File

@ -90,10 +90,10 @@ const Facta_queriesTablesPage = () => {
return (
<>
<Head>
<title>{getPageTitle('Facta_queries')}</title>
<title>{getPageTitle('5. Facta.Ai')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Facta_queries" main>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="5. Facta.Ai" main>
{''}
</SectionTitleLineWithButton>
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>

View File

@ -74,7 +74,7 @@ const profileTypeLabels: Record<TrustForm['profileType'], string> = {
const sectionLinks: SectionLink[] = [
{ id: 'home', label: 'Beranda' },
{ id: 'workspace-preview', label: 'Workspace' },
{ id: 'pillars', label: 'Super-App' },
{ id: 'pillars', label: 'VORTA Universe' },
{ id: 'trust-flow', label: 'Skor' },
{ id: 'reports', label: 'Laporan' },
];
@ -108,33 +108,102 @@ const workspaceModules: WorkspaceModule[] = [
const pillars: Pillar[] = [
{
title: 'Mega Super-App',
title: 'VORTA Commerce',
description:
'Chat bisnis, sosial, marketplace, dan dompet dalam satu ruang kerja ringan.',
route: '/mega-super-app',
actionLabel: 'Buka Super-App',
},
{
title: 'Vorta Nexus',
description:
'Skor kepercayaan 0-100 untuk identitas, bisnis, ulasan, dan akses komunitas.',
route: '/vorta-universe',
actionLabel: 'Buka Nexus',
},
{
title: 'Facta.AI',
description:
'Riset bebas SEO spam dengan Truth Score dan peta rujukan langsung.',
'VORTA Commerce adalah platform utama dalam ekosistem VORTA Universe yang menghubungkan perdagangan, distribusi, komunitas, logistik, afiliasi, dan pembayaran berbasis kepercayaan.',
route: '/vorta-commerce',
actionLabel: 'Buka Commerce',
},
{
title: 'Vorta Synapse',
description:
'Protokol distribusi, afiliasi, dan logistik pintar untuk ekonomi kreator.',
'Protokol Distribusi, Afiliasi, Dan Logistik Pintar Untuk Ekonomi Kreator.',
route: '/vorta-synapse',
actionLabel: 'Buka Synapse',
},
{
title: 'Mega Super-App',
description:
'Chat Bisnis, Sosial, Marketplace, Dan Dompet Dalam Satu Ruang Kerja Ringan.',
route: '/mega-super-app',
actionLabel: 'Buka Super-App',
},
{
title: 'Vorta Nexus',
description:
'Skor Kepercayaan 0-100 Untuk Identitas, Bisnis, Ulasan, Dan Akses Komunitas.',
route: '/vorta-universe#vorta-overview',
actionLabel: 'Buka Nexus',
},
{
title: 'Facta.Ai',
description:
'Riset Bebas Seo Spam Dengan Truth Score Dan Peta Rujukan Langsung.',
route: '/facta_queries/facta_queries-list',
actionLabel: 'Buka Facta.Ai',
},
];
const commerceVision =
'Membangun ekosistem perdagangan digital berbasis kepercayaan terbesar yang menghubungkan seluruh rantai ekonomi dalam satu platform.';
const commerceMission = [
'Membantu UMKM berkembang secara digital.',
'Menghubungkan pembeli dan penjual secara aman.',
'Mempercepat distribusi produk dan jasa.',
'Mendukung ekonomi kreator dan afiliasi.',
'Membangun komunitas bisnis yang aktif.',
'Menyederhanakan komunikasi dan pembayaran.',
];
const commercePillars = [
{
title: 'Commerce',
description: 'Pusat perdagangan digital.',
features: [
'Marketplace',
'Toko Online',
'Produk',
'Keranjang Belanja',
'Checkout',
'Pesanan',
'Pengiriman',
'Retur',
'Review',
],
},
{
title: 'Distribution & Affiliate',
description: 'Mesin pertumbuhan bisnis.',
features: [
'Affiliate Program',
'Referral Program',
'Komisi Otomatis',
'Creator Partner',
'Reseller Network',
'Distributor Network',
'Tracking Penjualan',
'Dashboard Komisi',
],
},
{
title: 'Smart Logistics',
description: 'Sistem logistik terintegrasi.',
features: [
'Tracking Pengiriman',
'Multi Kurir',
'Pengiriman Lokal',
'Pengiriman Nasional',
'Estimasi Ongkir',
'Smart Routing',
'Manajemen Gudang',
],
},
{
title: 'Social Commerce',
description: 'Komunitas dan promosi dalam satu tempat.',
features: ['Feed Sosial', 'Like', 'Komentar'],
},
];
const getTier = (score: number) => {
@ -377,7 +446,7 @@ export default function VortaLanding() {
};
const stats = [
{ label: 'kanal utama', value: '4 mode' },
{ label: 'produk utama', value: '5 modul' },
{ label: 'ruang kerja', value: '1 app' },
{ label: 'akses ringan', value: '<3 detik' },
];
@ -385,10 +454,10 @@ export default function VortaLanding() {
return (
<>
<Head>
<title>{getPageTitle('01 Mega Super-App')}</title>
<title>{getPageTitle('VORTA Universe')}</title>
<meta
name='description'
content='01 Mega Super-App VORTA menyatukan chat bisnis, sosial, marketplace, dan dompet dalam satu ruang kerja ringan.'
content='VORTA Universe mengurutkan VORTA Commerce, Vorta Synapse, Mega Super-App, Vorta Nexus, dan Facta.Ai sebagai ekosistem produk utama.'
/>
</Head>
@ -447,30 +516,27 @@ export default function VortaLanding() {
<div className='mx-auto grid max-w-7xl gap-10 py-20 lg:grid-cols-[1.05fr_0.95fr] lg:items-center lg:py-28'>
<div>
<div className='mb-6 inline-flex rounded-full border border-emerald-300/30 bg-emerald-300/10 px-4 py-2 text-sm font-medium text-emerald-100'>
Fokus pertama: 01 Mega Super-App
VORTA UNIVERSE 1. VORTA Commerce
</div>
<h1 className='max-w-4xl text-5xl font-black leading-tight tracking-tight text-white md:text-7xl'>
Chat bisnis, sosial, marketplace, dan dompet dalam satu ruang
kerja ringan.
Ekosistem perdagangan, distribusi, super-app, kepercayaan, dan riset berbasis kebenaran.
</h1>
<p className='mt-6 max-w-2xl text-lg leading-8 text-slate-300'>
Mulai dari percakapan tim, feed komunitas, etalase produk,
pembayaran, sampai riwayat transaksisemuanya berjalan di satu
workspace cepat dengan identitas dan skor kepercayaan bawaan.
VORTA Commerce adalah platform utama dalam ekosistem VORTA Universe yang menghubungkan pembeli, penjual, UMKM, kreator, perusahaan, komunitas, afiliator, dan mitra logistik melalui sistem perdagangan berbasis kepercayaan (Trust-Based Commerce). VORTA Commerce tidak hanya berfungsi sebagai marketplace, tetapi juga sebagai pusat aktivitas bisnis digital yang menggabungkan perdagangan, distribusi, afiliasi, logistik, komunikasi, komunitas, dan pembayaran dalam satu platform terpadu. Dengan dukungan VORTA Nexus Trust Score, setiap identitas, toko, produk, ulasan, transaksi, dan aktivitas komunitas memiliki tingkat kepercayaan yang dapat diukur dan diverifikasi.
</p>
<div className='mt-9 flex flex-col gap-3 sm:flex-row'>
<BaseButton
onClick={() =>
handleSectionNavigation('pillars', 'Alur Super-App')
}
label='Lihat Alur Super-App'
label='Lihat Urutan VORTA Universe'
color='success'
roundedFull
className='border-emerald-300 bg-[#1DE9B6] px-7 py-3 text-slate-950 hover:bg-white'
/>
<BaseButton
onClick={handleStartDemo}
label='Jalankan Demo Skor'
label='Jalankan Demo Nexus'
color='white'
outline
roundedFull
@ -518,7 +584,7 @@ export default function VortaLanding() {
Pratinjau ruang kerja langsung
</p>
<h2 className='text-xl font-bold'>
Mega Super-App Workspace
VORTA Universe Workspace
</h2>
</div>
<span className='rounded-full bg-emerald-300/15 px-3 py-1 text-xs font-semibold text-emerald-200'>
@ -587,18 +653,17 @@ export default function VortaLanding() {
>
<div className='mx-auto max-w-7xl'>
<div className='mb-8 max-w-2xl'>
<p className='text-sm font-semibold uppercase tracking-[0.28em] text-[#1DE9B6]'>
Fokus pertama: 01 Mega Super-App
<p className='text-sm font-semibold tracking-[0.28em] text-[#1DE9B6]'>
VORTA UNIVERSE 1. VORTA Commerce
</p>
<h2 className='mt-3 text-3xl font-black md:text-4xl'>
Satu ruang kerja ringan yang menghubungkan percakapan,
komunitas, perdagangan, pembayaran, dan reputasi pengguna.
Semua produk, menu, dan tombol utama mengikuti urutan 1 sampai 5 berikut.
</h2>
<p className='mt-4 rounded-3xl border border-white/10 bg-white/[0.08] px-5 py-3 text-sm font-semibold text-cyan-100'>
Pilar aktif: {activePillarTitle}
</p>
</div>
<div className='grid gap-4 md:grid-cols-2 xl:grid-cols-4'>
<div className='grid gap-4 md:grid-cols-2 xl:grid-cols-5'>
{pillars.map((pillar, index) => (
<article
key={pillar.title}
@ -614,7 +679,7 @@ export default function VortaLanding() {
className='block w-full text-left focus:outline-none focus:ring-4 focus:ring-cyan-300/30'
>
<div className='mb-8 flex h-12 w-12 items-center justify-center rounded-2xl bg-gradient-to-br from-[#1DE9B6] to-[#6C63FF] text-lg font-black text-slate-950'>
0{index + 1}
{index + 1}
</div>
<h3 className='text-xl font-bold'>{pillar.title}</h3>
<p className='mt-3 text-sm leading-6 text-slate-300'>
@ -651,10 +716,78 @@ export default function VortaLanding() {
</div>
</section>
<section id='commerce-detail' className='px-5 py-20 sm:px-8 lg:px-12'>
<div className='mx-auto max-w-7xl'>
<div className='grid gap-8 lg:grid-cols-[0.95fr_1.05fr] lg:items-start'>
<div className='rounded-[2rem] border border-emerald-300/20 bg-emerald-300/10 p-6 shadow-2xl shadow-emerald-950/20'>
<p className='text-sm font-semibold tracking-[0.28em] text-[#1DE9B6]'>
1. VORTA Commerce
</p>
<h2 className='mt-3 text-3xl font-black md:text-5xl'>
Platform Perdagangan, Distribusi, Komunitas, dan Pembayaran Berbasis Kepercayaan
</h2>
<p className='mt-5 text-sm leading-7 text-slate-200 md:text-base'>
VORTA Commerce adalah platform utama dalam ekosistem VORTA Universe yang menghubungkan pembeli, penjual, UMKM, kreator, perusahaan, komunitas, afiliator, dan mitra logistik melalui sistem perdagangan berbasis kepercayaan (Trust-Based Commerce). VORTA Commerce tidak hanya berfungsi sebagai marketplace, tetapi juga sebagai pusat aktivitas bisnis digital yang menggabungkan perdagangan, distribusi, afiliasi, logistik, komunikasi, komunitas, dan pembayaran dalam satu platform terpadu. Dengan dukungan VORTA Nexus Trust Score, setiap identitas, toko, produk, ulasan, transaksi, dan aktivitas komunitas memiliki tingkat kepercayaan yang dapat diukur dan diverifikasi.
</p>
<div className='mt-6 rounded-3xl border border-white/10 bg-white/[0.08] p-5'>
<p className='text-xs font-black uppercase tracking-[0.22em] text-emerald-200'>
Visi
</p>
<p className='mt-3 text-sm leading-7 text-slate-200'>{commerceVision}</p>
</div>
</div>
<div className='space-y-5'>
<div className='rounded-[2rem] border border-white/10 bg-slate-950/50 p-6'>
<p className='text-xs font-black uppercase tracking-[0.22em] text-cyan-200'>
Misi
</p>
<div className='mt-4 grid gap-3 sm:grid-cols-2'>
{commerceMission.map((mission) => (
<div
key={mission}
className='rounded-2xl border border-white/10 bg-white/[0.06] px-4 py-3 text-sm leading-6 text-slate-200'
>
{mission}
</div>
))}
</div>
</div>
<div className='grid gap-4 md:grid-cols-2'>
{commercePillars.map((pillar, index) => (
<article
key={pillar.title}
className='rounded-[1.5rem] border border-white/10 bg-white/[0.06] p-5'
>
<div className='mb-4 inline-flex h-10 w-10 items-center justify-center rounded-2xl bg-gradient-to-br from-[#1DE9B6] to-[#6C63FF] text-sm font-black text-slate-950'>
{index + 1}
</div>
<h3 className='text-lg font-black'>{pillar.title}</h3>
<p className='mt-2 text-sm text-slate-300'>{pillar.description}</p>
<div className='mt-4 flex flex-wrap gap-2'>
{pillar.features.map((feature) => (
<span
key={feature}
className='rounded-full border border-white/10 bg-white/[0.08] px-3 py-1 text-xs font-semibold text-cyan-100'
>
{feature}
</span>
))}
</div>
</article>
))}
</div>
</div>
</div>
</div>
</section>
<section id='trust-flow' className='px-5 py-20 sm:px-8 lg:px-12'>
<div className='mx-auto grid max-w-7xl gap-8 lg:grid-cols-[0.9fr_1.1fr]'>
<div className='lg:sticky lg:top-8 lg:self-start'>
<p className='text-sm font-semibold uppercase tracking-[0.28em] text-[#1DE9B6]'>
<p className='text-sm font-semibold tracking-[0.28em] text-[#1DE9B6]'>
Mesin kepercayaan bawaan
</p>
<h2 className='mt-3 text-4xl font-black'>
@ -999,8 +1132,7 @@ export default function VortaLanding() {
<footer className='border-t border-white/10 px-5 py-8 text-sm text-slate-400 sm:px-8 lg:px-12'>
<div className='mx-auto flex max-w-7xl flex-col gap-4 md:flex-row md:items-center md:justify-between'>
<p>
© 2026 VORTA Universe MVP. Dimulai dari 01 Mega Super-App untuk
chat, sosial, marketplace, dan dompet ringan.
© 2026 VORTA Universe MVP. Urutan utama dimulai dari 1. VORTA Commerce, lalu Synapse, Super-App, Nexus, dan Facta.Ai.
</p>
<div className='flex gap-5'>
<Link href='/privacy-policy/' className='hover:text-white'>

View File

@ -44,7 +44,7 @@ export default function Login() {
password: '9654d2ec',
remember: true })
const title = 'VORTA-COMMERCE'
const title = 'VORTA UNIVERSE'
// Fetch Pexels image/video
useEffect( () => {

View File

@ -329,12 +329,12 @@ const MegaSuperAppPage = () => {
return (
<>
<Head>
<title>{getPageTitle('Mega Super-App')}</title>
<title>{getPageTitle('3. Mega Super-App')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton
icon={resolveIcon('mdiApps')}
title="Mega Super-App"
title="3. Mega Super-App"
main
>
<BaseButton
@ -350,7 +350,7 @@ const MegaSuperAppPage = () => {
<div className="p-6 lg:col-span-3">
<div className="mb-3 inline-flex items-center rounded-full bg-blue-50 px-3 py-1 text-sm font-semibold text-blue-700 dark:bg-blue-900/30 dark:text-blue-200">
<BaseIcon path={resolveIcon('mdiFlashOutline')} className="mr-2" size={18} />
Workspace ringan untuk bisnis, sosial, marketplace, dan dompet
3. Mega Super-App · Chat Bisnis, Sosial, Marketplace, Dan Dompet Dalam Satu Ruang Kerja Ringan
</div>
<h2 className="mb-3 text-3xl font-bold leading-tight md:text-4xl">
Halo {userName}, kelola semua aktivitas dari satu ruang kerja.

View File

@ -5,7 +5,7 @@ import LayoutGuest from '../layouts/Guest';
import { getPageTitle } from '../config';
export default function PrivacyPolicy() {
const title = 'VORTA-COMMERCE'
const title = 'VORTA UNIVERSE'
const [projectUrl, setProjectUrl] = useState('');
useEffect(() => {

View File

@ -5,7 +5,7 @@ import LayoutGuest from '../layouts/Guest';
import { getPageTitle } from '../config';
export default function PrivacyPolicy() {
const title = 'VORTA-COMMERCE';
const title = 'VORTA UNIVERSE';
const [projectUrl, setProjectUrl] = useState('');
useEffect(() => {

View File

@ -359,13 +359,114 @@ const initialLedger: LedgerItem[] = [
];
const initialSettings: Settings = {
storeName: 'VORTA-COMMERCE HQ',
storeName: 'VORTA Commerce HQ',
currency: 'IDR',
fulfillmentMode: 'Otomatis',
lowStockAlert: true,
paymentAutoCapture: true,
};
const vortaUniverseOrder = [
{
number: '1',
title: 'VORTA Commerce',
description:
'Platform Perdagangan, Distribusi, Komunitas, dan Pembayaran Berbasis Kepercayaan.',
href: '/vorta-commerce',
iconName: 'mdiStorefrontOutline',
},
{
number: '2',
title: 'Vorta Synapse',
description: 'Protokol Distribusi, Afiliasi, Dan Logistik Pintar Untuk Ekonomi Kreator.',
href: '/vorta-synapse',
iconName: 'mdiTransitConnectionVariant',
},
{
number: '3',
title: 'Mega Super-App',
description: 'Chat Bisnis, Sosial, Marketplace, Dan Dompet Dalam Satu Ruang Kerja Ringan.',
href: '/mega-super-app',
iconName: 'mdiApps',
},
{
number: '4',
title: 'Vorta Nexus',
description: 'Skor Kepercayaan 0-100 Untuk Identitas, Bisnis, Ulasan, Dan Akses Komunitas.',
href: '/vorta-universe#vorta-overview',
iconName: 'mdiShieldCheck',
},
{
number: '5',
title: 'Facta.Ai',
description: 'Riset Bebas Seo Spam Dengan Truth Score Dan Peta Rujukan Langsung.',
href: '/facta_queries/facta_queries-list',
iconName: 'mdiShieldSearchOutline',
},
];
const commerceVision =
'Membangun ekosistem perdagangan digital berbasis kepercayaan terbesar yang menghubungkan seluruh rantai ekonomi dalam satu platform.';
const commerceMission = [
'Membantu UMKM berkembang secara digital.',
'Menghubungkan pembeli dan penjual secara aman.',
'Mempercepat distribusi produk dan jasa.',
'Mendukung ekonomi kreator dan afiliasi.',
'Membangun komunitas bisnis yang aktif.',
'Menyederhanakan komunikasi dan pembayaran.',
];
const commercePillars = [
{
title: 'Commerce',
description: 'Pusat perdagangan digital.',
features: [
'Marketplace',
'Toko Online',
'Produk',
'Keranjang Belanja',
'Checkout',
'Pesanan',
'Pengiriman',
'Retur',
'Review',
],
},
{
title: 'Distribution & Affiliate',
description: 'Mesin pertumbuhan bisnis.',
features: [
'Affiliate Program',
'Referral Program',
'Komisi Otomatis',
'Creator Partner',
'Reseller Network',
'Distributor Network',
'Tracking Penjualan',
'Dashboard Komisi',
],
},
{
title: 'Smart Logistics',
description: 'Sistem logistik terintegrasi.',
features: [
'Tracking Pengiriman',
'Multi Kurir',
'Pengiriman Lokal',
'Pengiriman Nasional',
'Estimasi Ongkir',
'Smart Routing',
'Manajemen Gudang',
],
},
{
title: 'Social Commerce',
description: 'Komunitas dan promosi dalam satu tempat.',
features: ['Feed Sosial', 'Like', 'Komentar'],
},
];
const tabs: { id: TabId; label: string; iconName: string }[] = [
{ id: 'dashboard', label: 'Dashboard', iconName: 'mdiViewDashboardOutline' },
{ id: 'products', label: 'Produk', iconName: 'mdiPackageVariantClosed' },
@ -422,6 +523,7 @@ const getToneClass = (tone: LedgerItem['tone']) => {
const VortaCommercePage = () => {
const { currentUser } = useAppSelector((state) => state.auth);
const iconsColor = useAppSelector((state) => state.style.iconsColor);
const [activeTab, setActiveTab] = useState<TabId>('dashboard');
const [products, setProducts] = useState<Product[]>(initialProducts);
const [customers, setCustomers] = useState<Customer[]>(initialCustomers);
@ -456,7 +558,7 @@ const VortaCommercePage = () => {
const response = await axios.get<CommerceState>('/vorta-commerce/state');
applyCommerceState(response.data);
} catch (error) {
console.error('Failed to load VORTA-COMMERCE state:', error);
console.error('Failed to load VORTA Commerce state:', error);
setCommerceError('Data database belum bisa dimuat. Fallback demo lokal tetap ditampilkan.');
} finally {
setIsLoadingCommerce(false);
@ -482,6 +584,10 @@ const VortaCommercePage = () => {
}
}, [applyCommerceState]);
const displayStoreName = settings.storeName
.replace('VORTA-COMMERCE', 'VORTA Commerce')
.replace('Vorta-Commerce', 'VORTA Commerce');
const metrics = useMemo(() => {
const paidRevenue = payments
.filter((payment) => payment.status === 'Berhasil')
@ -691,7 +797,7 @@ const VortaCommercePage = () => {
<div className="mt-6 rounded-2xl border border-blue-100 bg-gradient-to-r from-blue-50 to-emerald-50 p-5 dark:border-blue-900/30 dark:from-blue-950/40 dark:to-emerald-950/20">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<p className="text-sm font-semibold uppercase tracking-wide text-blue-700 dark:text-blue-200">
<p className="text-sm font-semibold tracking-wide text-blue-700 dark:text-blue-200">
Produk terlaris
</p>
<h3 className="mt-1 text-2xl font-bold">{bestSeller?.name}</h3>
@ -1117,7 +1223,7 @@ const VortaCommercePage = () => {
<div className="mt-5 space-y-4">
<div className="rounded-2xl bg-slate-50 p-4 dark:bg-dark-800">
<p className="text-sm text-gray-500 dark:text-gray-400">Nama toko</p>
<p className="font-semibold">{settings.storeName}</p>
<p className="font-semibold">{displayStoreName}</p>
</div>
<div className="rounded-2xl bg-slate-50 p-4 dark:bg-dark-800">
<p className="text-sm text-gray-500 dark:text-gray-400">Mata uang</p>
@ -1191,12 +1297,12 @@ const VortaCommercePage = () => {
return (
<>
<Head>
<title>{getPageTitle('VORTA-COMMERCE')}</title>
<title>{getPageTitle('1. VORTA Commerce')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton
icon={resolveIcon('mdiStorefrontOutline')}
title="VORTA-COMMERCE"
title="1. VORTA Commerce"
main
>
<BaseButton
@ -1216,17 +1322,17 @@ const VortaCommercePage = () => {
{(isLoadingCommerce || isSavingCommerce) ? (
<div className="mb-4 rounded-2xl border border-blue-200 bg-blue-50 p-4 text-sm font-medium text-blue-700 dark:border-blue-900/40 dark:bg-blue-900/20 dark:text-blue-200">
{isLoadingCommerce ? 'Memuat data VORTA-COMMERCE dari database...' : 'Menyimpan perubahan ke database...'}
{isLoadingCommerce ? 'Memuat data VORTA Commerce dari database...' : 'Menyimpan perubahan ke database...'}
</div>
) : null}
<div className="mb-6 overflow-hidden rounded-3xl bg-gradient-to-r from-slate-950 via-blue-950 to-emerald-900 p-6 text-white shadow-xl">
<div className="grid grid-cols-1 gap-6 xl:grid-cols-3">
<div className="xl:col-span-2">
<p className="text-sm font-semibold uppercase tracking-[0.35em] text-emerald-200">Commerce Operating System</p>
<h1 className="mt-3 text-3xl font-black md:text-5xl">Dashboard utama untuk produk, order, pembayaran, dan fulfillment.</h1>
<p className="text-sm font-semibold tracking-[0.35em] text-emerald-200">VORTA UNIVERSE 1. VORTA Commerce</p>
<h1 className="mt-3 text-3xl font-black md:text-5xl">Platform Perdagangan, Distribusi, Komunitas, dan Pembayaran Berbasis Kepercayaan.</h1>
<p className="mt-4 max-w-3xl text-sm text-slate-200 md:text-base">
MVP ini menyatukan frontend, API module, dan rancangan database VORTA-COMMERCE dalam satu command center yang langsung bisa diuji.
VORTA Commerce adalah platform utama dalam ekosistem VORTA Universe yang menghubungkan pembeli, penjual, UMKM, kreator, perusahaan, komunitas, afiliator, dan mitra logistik melalui sistem perdagangan berbasis kepercayaan (Trust-Based Commerce). VORTA Commerce tidak hanya berfungsi sebagai marketplace, tetapi juga sebagai pusat aktivitas bisnis digital yang menggabungkan perdagangan, distribusi, afiliasi, logistik, komunikasi, komunitas, dan pembayaran dalam satu platform terpadu. Dengan dukungan VORTA Nexus Trust Score, setiap identitas, toko, produk, ulasan, transaksi, dan aktivitas komunitas memiliki tingkat kepercayaan yang dapat diukur dan diverifikasi.
</p>
<div className="mt-6 flex flex-wrap gap-2">
<BaseButton
@ -1257,7 +1363,7 @@ const VortaCommercePage = () => {
{currentUser?.firstName || currentUser?.email || 'Admin VORTA'}
</p>
<div className="mt-5 space-y-3 text-sm">
<div className="flex justify-between"><span>Store</span><strong>{settings.storeName}</strong></div>
<div className="flex justify-between"><span>Store</span><strong>{displayStoreName}</strong></div>
<div className="flex justify-between"><span>Mode</span><strong>{settings.fulfillmentMode}</strong></div>
<div className="flex justify-between"><span>Open orders</span><strong>{metrics.openOrders}</strong></div>
<div className="flex justify-between"><span>Low stock</span><strong>{metrics.lowStockCount}</strong></div>
@ -1267,6 +1373,107 @@ const VortaCommercePage = () => {
</div>
</div>
<CardBox className="mb-6 border border-slate-100/80 dark:border-dark-700">
<div className="mb-5 flex flex-col gap-2 lg:flex-row lg:items-end lg:justify-between">
<div>
<p className="text-sm font-semibold tracking-[0.28em] text-blue-600 dark:text-blue-300">VORTA UNIVERSE</p>
<h2 className="mt-2 text-2xl font-black text-slate-900 dark:text-white">Urutan menu utama dashboard</h2>
<p className="mt-2 max-w-3xl text-sm text-gray-500 dark:text-gray-400">
Semua tombol produk VORTA di dashboard mengikuti urutan 1 sampai 5 sesuai struktur ekosistem.
</p>
</div>
</div>
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-5">
{vortaUniverseOrder.map((item) => (
<div
key={item.number}
className="flex h-full flex-col rounded-2xl border border-slate-100 bg-slate-50 p-4 dark:border-dark-700 dark:bg-dark-800"
>
<div className="mb-4 flex items-center justify-between gap-3">
<span className="grid h-10 w-10 place-items-center rounded-xl bg-blue-600 text-sm font-black text-white">
{item.number}
</span>
<BaseIcon path={resolveIcon(item.iconName)} className={iconsColor} size={24} />
</div>
<h3 className="text-lg font-black text-slate-900 dark:text-white">{item.title}</h3>
<p className="mt-2 flex-1 text-sm leading-6 text-gray-500 dark:text-gray-400">{item.description}</p>
<BaseButton
href={item.href}
label={`Buka ${item.title}`}
color={item.number === '1' ? 'info' : 'white'}
outline={item.number !== '1'}
small
className="mt-4 w-full"
/>
</div>
))}
</div>
</CardBox>
<CardBox className="mb-6 border border-emerald-100/80 dark:border-emerald-900/30">
<div className="grid gap-6 xl:grid-cols-[0.9fr_1.1fr]">
<div>
<p className="text-sm font-semibold tracking-[0.28em] text-emerald-600 dark:text-emerald-300">1. VORTA Commerce</p>
<h2 className="mt-2 text-2xl font-black text-slate-900 dark:text-white">
Platform Perdagangan, Distribusi, Komunitas, dan Pembayaran Berbasis Kepercayaan
</h2>
<p className="mt-4 text-sm leading-7 text-gray-600 dark:text-gray-300">
VORTA Commerce adalah platform utama dalam ekosistem VORTA Universe yang menghubungkan pembeli, penjual, UMKM, kreator, perusahaan, komunitas, afiliator, dan mitra logistik melalui sistem perdagangan berbasis kepercayaan (Trust-Based Commerce). VORTA Commerce tidak hanya berfungsi sebagai marketplace, tetapi juga sebagai pusat aktivitas bisnis digital yang menggabungkan perdagangan, distribusi, afiliasi, logistik, komunikasi, komunitas, dan pembayaran dalam satu platform terpadu. Dengan dukungan VORTA Nexus Trust Score, setiap identitas, toko, produk, ulasan, transaksi, dan aktivitas komunitas memiliki tingkat kepercayaan yang dapat diukur dan diverifikasi.
</p>
<div className="mt-5 rounded-2xl border border-emerald-100 bg-emerald-50 p-4 dark:border-emerald-900/40 dark:bg-emerald-900/20">
<p className="text-xs font-black uppercase tracking-[0.22em] text-emerald-700 dark:text-emerald-200">Visi</p>
<p className="mt-2 text-sm leading-7 text-emerald-900 dark:text-emerald-100">{commerceVision}</p>
</div>
</div>
<div className="space-y-4">
<div className="rounded-2xl border border-slate-100 bg-slate-50 p-4 dark:border-dark-700 dark:bg-dark-800">
<p className="text-xs font-black uppercase tracking-[0.22em] text-blue-600 dark:text-blue-300">Misi</p>
<div className="mt-3 grid gap-2 sm:grid-cols-2">
{commerceMission.map((mission) => (
<div
key={mission}
className="rounded-xl border border-slate-100 bg-white px-3 py-2 text-sm text-slate-600 dark:border-dark-700 dark:bg-dark-900 dark:text-slate-300"
>
{mission}
</div>
))}
</div>
</div>
<div className="grid gap-3 md:grid-cols-2">
{commercePillars.map((pillar, index) => (
<div
key={pillar.title}
className="rounded-2xl border border-slate-100 bg-white p-4 dark:border-dark-700 dark:bg-dark-900"
>
<div className="mb-3 flex items-start gap-3">
<span className="grid h-9 w-9 flex-none place-items-center rounded-xl bg-emerald-500 text-sm font-black text-white">
{index + 1}
</span>
<div>
<h3 className="font-black text-slate-900 dark:text-white">{pillar.title}</h3>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">{pillar.description}</p>
</div>
</div>
<div className="flex flex-wrap gap-2">
{pillar.features.map((feature) => (
<span
key={feature}
className="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600 dark:bg-dark-800 dark:text-slate-300"
>
{feature}
</span>
))}
</div>
</div>
))}
</div>
</div>
</div>
</CardBox>
<div className="mb-6 flex gap-2 overflow-x-auto pb-2">
{tabs.map((tab) => (
<button

View File

@ -0,0 +1,479 @@
import * as icon from '@mdi/js';
import axios from 'axios';
import Head from 'next/head';
import React, { ReactElement, useEffect, useMemo, useState } from 'react';
import BaseButton from '../components/BaseButton';
import BaseIcon from '../components/BaseIcon';
import CardBox from '../components/CardBox';
import FormField from '../components/FormField';
import SectionMain from '../components/SectionMain';
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
import LayoutAuthenticated from '../layouts/Authenticated';
import { getPageTitle } from '../config';
import { useAppSelector } from '../stores/hooks';
type SocialComment = {
id: string;
post_id: string;
user: string;
comment: string;
parent_id: string;
created_at: string;
};
type SocialPost = {
id: string;
user: string;
content: string;
created_at: string;
comments: SocialComment[];
};
type SocialFeed = {
posts: SocialPost[];
stats: {
postsCount: number;
commentsCount: number;
};
syncedAt: string;
};
type SocialMutationResponse = {
feed: SocialFeed;
};
type NoticeTone = 'info' | 'success' | 'warning' | 'danger';
const resolveIcon = (name: string) => (
name in icon ? (icon[name as keyof typeof icon] as string) : icon.mdiViewDashboardOutline
);
const formatDateTime = (value?: string) => {
if (!value) {
return '-';
}
try {
return new Intl.DateTimeFormat('id-ID', {
dateStyle: 'medium',
timeStyle: 'short',
}).format(new Date(value));
} catch (error) {
console.error('Failed to format VORTA feed date:', error);
return value;
}
};
const VortaFeedPage = () => {
const { currentUser } = useAppSelector((state) => state.auth);
const [feed, setFeed] = useState<SocialFeed | null>(null);
const [postContent, setPostContent] = useState('');
const [commentDrafts, setCommentDrafts] = useState<Record<string, string>>({});
const [replyDrafts, setReplyDrafts] = useState<Record<string, string>>({});
const [activeReplyId, setActiveReplyId] = useState<string | null>(null);
const [notice, setNotice] = useState('');
const [noticeTone, setNoticeTone] = useState<NoticeTone>('info');
const [errorMessage, setErrorMessage] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const displayName = useMemo(() => {
const name = [currentUser?.firstName, currentUser?.lastName].filter(Boolean).join(' ').trim();
return name || currentUser?.email || 'VORTA Member';
}, [currentUser]);
const loadFeed = async () => {
setIsLoading(true);
setErrorMessage('');
try {
const response = await axios.get<SocialFeed>('/vorta-universe/social-feed');
setFeed(response.data);
setNotice(`Feed tersinkron dari PostgreSQL: ${formatDateTime(response.data.syncedAt)}`);
setNoticeTone('success');
} catch (error) {
console.error('Failed to load VORTA Feed:', error);
setErrorMessage('Gagal memuat VORTA Feed. Cek koneksi dan backend log untuk detail.');
} finally {
setIsLoading(false);
}
};
useEffect(() => {
loadFeed();
}, []);
const runMutation = async (
action: () => Promise<{ data: SocialMutationResponse }>,
successMessage: string,
) => {
setIsSaving(true);
setErrorMessage('');
setNotice('');
try {
const response = await action();
setFeed(response.data.feed);
setNotice(successMessage);
setNoticeTone('success');
return true;
} catch (error) {
console.error('Failed to mutate VORTA Feed:', error);
setErrorMessage('Aksi feed gagal disimpan. Cek input dan backend log untuk detail.');
return false;
} finally {
setIsSaving(false);
}
};
const createPost = async () => {
if (!postContent.trim()) {
setErrorMessage('Isi post terlebih dahulu sebelum menerbitkan.');
return;
}
const saved = await runMutation(
() => axios.post<SocialMutationResponse>('/vorta-universe/posts', {
data: {
user: displayName,
content: postContent,
},
}),
'Post baru berhasil tersimpan di tabel vorta_social_posts.',
);
if (saved) {
setPostContent('');
}
};
const createComment = async (postId: string, parentId = '0') => {
const draftKey = parentId === '0' ? postId : parentId;
const draft = parentId === '0' ? commentDrafts[postId] : replyDrafts[parentId];
if (!draft?.trim()) {
setErrorMessage(parentId === '0' ? 'Isi komentar terlebih dahulu.' : 'Isi balasan terlebih dahulu.');
return;
}
const saved = await runMutation(
() => axios.post<SocialMutationResponse>(`/vorta-universe/posts/${postId}/comments`, {
data: {
user: displayName,
comment: draft,
parent_id: parentId,
},
}),
parentId === '0'
? 'Komentar baru berhasil tersimpan di tabel vorta_social_comments.'
: 'Balasan komentar berhasil tersimpan dengan parent_id.',
);
if (saved && parentId === '0') {
setCommentDrafts((drafts) => ({ ...drafts, [draftKey]: '' }));
}
if (saved && parentId !== '0') {
setReplyDrafts((drafts) => ({ ...drafts, [draftKey]: '' }));
setActiveReplyId(null);
}
};
const resetFeed = async () => {
const saved = await runMutation(
() => axios.post<SocialMutationResponse>('/vorta-universe/social-feed/reset'),
'Feed berhasil di-reset ke contoh awal VORTA.',
);
if (saved) {
setPostContent('');
setCommentDrafts({});
setReplyDrafts({});
setActiveReplyId(null);
}
};
const clearDrafts = () => {
setPostContent('');
setCommentDrafts({});
setReplyDrafts({});
setActiveReplyId(null);
setNotice('Semua draft di layar sudah dibersihkan. Data tersimpan tidak dihapus.');
setNoticeTone('info');
setErrorMessage('');
};
const posts = feed?.posts || [];
const commentsCount = feed?.stats.commentsCount || 0;
return (
<>
<Head>
<title>{getPageTitle('VORTA Feed')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton
icon={resolveIcon('mdiPostOutline')}
title="VORTA Feed"
main
>
<BaseButton
label={isLoading ? 'Memuat...' : 'Refresh'}
icon={resolveIcon('mdiRefresh')}
color="info"
onClick={loadFeed}
disabled={isLoading || isSaving}
/>
</SectionTitleLineWithButton>
<div className="mb-6 grid grid-cols-1 gap-4 lg:grid-cols-3">
<CardBox className="border border-emerald-500/30 bg-emerald-950/10">
<div className="flex items-center gap-3">
<BaseIcon path={resolveIcon('mdiDatabaseCheckOutline')} size={32} className="text-emerald-500" />
<div>
<p className="text-sm text-gray-500 dark:text-gray-400">Storage</p>
<h2 className="text-xl font-bold">PostgreSQL Native</h2>
</div>
</div>
</CardBox>
<CardBox>
<div className="flex items-center gap-3">
<BaseIcon path={resolveIcon('mdiPostOutline')} size={32} className="text-sky-500" />
<div>
<p className="text-sm text-gray-500 dark:text-gray-400">Posts</p>
<h2 className="text-xl font-bold">{posts.length}</h2>
</div>
</div>
</CardBox>
<CardBox>
<div className="flex items-center gap-3">
<BaseIcon path={resolveIcon('mdiCommentMultipleOutline')} size={32} className="text-amber-500" />
<div>
<p className="text-sm text-gray-500 dark:text-gray-400">Comments & Replies</p>
<h2 className="text-xl font-bold">{commentsCount}</h2>
</div>
</div>
</CardBox>
</div>
{(notice || errorMessage) && (
<div
className={`mb-6 rounded-xl border px-4 py-3 text-sm ${
errorMessage
? 'border-red-500/40 bg-red-500/10 text-red-700 dark:text-red-200'
: noticeTone === 'success'
? 'border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-200'
: noticeTone === 'warning'
? 'border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-200'
: 'border-sky-500/40 bg-sky-500/10 text-sky-700 dark:text-sky-200'
}`}
>
{errorMessage || notice}
</div>
)}
<div className="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,420px)_1fr]">
<div className="space-y-6">
<CardBox>
<div className="mb-4">
<p className="text-sm font-semibold uppercase tracking-wide text-emerald-600 dark:text-emerald-300">
Create Post
</p>
<h2 className="text-2xl font-bold">Tulis update komunitas</h2>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
Post tersimpan sebagai row baru di <strong>vorta_social_posts</strong> dan langsung muncul di feed.
</p>
</div>
<FormField label="Nama tampil" help="Diambil dari akun login, bisa ikut tersimpan sebagai user display feed.">
<input value={displayName} disabled />
</FormField>
<FormField label="Konten Post" hasTextareaHeight>
<textarea
value={postContent}
onChange={(event) => setPostContent(event.target.value)}
placeholder="Contoh: Update bisnis hari ini, promo komunitas, atau ide kolaborasi VORTA..."
/>
</FormField>
<div className="flex flex-wrap gap-2">
<BaseButton
label={isSaving ? 'Menyimpan...' : 'Terbitkan Post'}
icon={resolveIcon('mdiSend')}
color="success"
onClick={createPost}
disabled={isSaving || isLoading}
/>
<BaseButton
label="Bersihkan Draft"
icon={resolveIcon('mdiBroom')}
color="warning"
outline
onClick={clearDrafts}
disabled={isSaving || isLoading}
/>
<BaseButton
label="Reset Demo"
icon={resolveIcon('mdiRestore')}
color="danger"
outline
onClick={resetFeed}
disabled={isSaving || isLoading}
/>
</div>
</CardBox>
<CardBox>
<p className="text-sm font-semibold uppercase tracking-wide text-sky-600 dark:text-sky-300">
API aktif
</p>
<div className="mt-3 space-y-3 text-sm text-gray-600 dark:text-gray-300">
<p><strong>GET</strong> /vorta-universe/social-feed</p>
<p><strong>POST</strong> /vorta-universe/posts</p>
<p><strong>POST</strong> /vorta-universe/posts/:postId/comments</p>
<p><strong>POST</strong> /vorta-universe/social-feed/reset</p>
</div>
</CardBox>
</div>
<div className="space-y-5">
{isLoading && !feed && (
<CardBox>
<div className="py-10 text-center text-gray-500 dark:text-gray-400">Memuat VORTA Feed...</div>
</CardBox>
)}
{!isLoading && posts.length === 0 && (
<CardBox>
<div className="py-10 text-center">
<BaseIcon path={resolveIcon('mdiPostOutline')} size={48} className="mx-auto mb-3 text-gray-400" />
<h3 className="text-xl font-bold">Belum ada post</h3>
<p className="mt-2 text-gray-600 dark:text-gray-400">Buat post pertama untuk memulai feed komunitas.</p>
</div>
</CardBox>
)}
{posts.map((post) => {
const rootComments = post.comments.filter((comment) => comment.parent_id === '0');
const repliesByParent = post.comments.reduce<Record<string, SocialComment[]>>((acc, comment) => {
if (comment.parent_id !== '0') {
acc[comment.parent_id] = acc[comment.parent_id] || [];
acc[comment.parent_id].push(comment);
}
return acc;
}, {});
return (
<CardBox key={post.id} className="overflow-hidden">
<div className="mb-4 flex flex-col gap-3 border-b border-gray-200 pb-4 dark:border-dark-700 md:flex-row md:items-start md:justify-between">
<div>
<div className="flex items-center gap-3">
<div className="flex h-11 w-11 items-center justify-center rounded-full bg-emerald-500/15 text-lg font-bold text-emerald-600 dark:text-emerald-300">
{post.user.slice(0, 1).toUpperCase()}
</div>
<div>
<h3 className="font-bold">{post.user}</h3>
<p className="text-xs text-gray-500 dark:text-gray-400">{formatDateTime(post.created_at)}</p>
</div>
</div>
<p className="mt-4 whitespace-pre-wrap text-gray-800 dark:text-gray-100">{post.content}</p>
</div>
<div className="rounded-full bg-sky-500/10 px-3 py-1 text-xs font-semibold text-sky-700 dark:text-sky-200">
{post.comments.length} komentar
</div>
</div>
<div className="space-y-4">
{rootComments.map((comment) => (
<div key={comment.id} className="rounded-xl border border-gray-200 p-3 dark:border-dark-700">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div className="font-semibold">{comment.user}</div>
<div className="text-xs text-gray-500 dark:text-gray-400">{formatDateTime(comment.created_at)}</div>
</div>
<p className="mt-2 whitespace-pre-wrap text-sm text-gray-700 dark:text-gray-200">{comment.comment}</p>
{(repliesByParent[comment.id] || []).map((reply) => (
<div key={reply.id} className="mt-3 border-l-4 border-emerald-400 pl-3">
<div className="flex flex-col gap-1 text-xs text-gray-500 dark:text-gray-400 sm:flex-row sm:items-center sm:justify-between">
<span className="font-semibold text-gray-700 dark:text-gray-200">{reply.user}</span>
<span>{formatDateTime(reply.created_at)}</span>
</div>
<p className="mt-1 whitespace-pre-wrap text-sm text-gray-700 dark:text-gray-200">{reply.comment}</p>
</div>
))}
{activeReplyId === comment.id ? (
<div className="mt-3 flex flex-col gap-2 md:flex-row">
<input
className="min-h-10 flex-1 rounded border border-gray-300 px-3 py-2 dark:border-dark-700 dark:bg-dark-800"
value={replyDrafts[comment.id] || ''}
onChange={(event) => setReplyDrafts((drafts) => ({
...drafts,
[comment.id]: event.target.value,
}))}
placeholder="Tulis balasan..."
/>
<BaseButton
label="Kirim Reply"
color="success"
small
onClick={() => createComment(post.id, comment.id)}
disabled={isSaving}
/>
<BaseButton
label="Batal"
color="warning"
small
outline
onClick={() => setActiveReplyId(null)}
disabled={isSaving}
/>
</div>
) : (
<button
type="button"
className="mt-3 text-sm font-semibold text-emerald-600 hover:underline dark:text-emerald-300"
onClick={() => setActiveReplyId(comment.id)}
>
Balas komentar
</button>
)}
</div>
))}
<div className="flex flex-col gap-2 border-t border-gray-200 pt-4 dark:border-dark-700 md:flex-row">
<input
className="min-h-11 flex-1 rounded border border-gray-300 px-3 py-2 dark:border-dark-700 dark:bg-dark-800"
value={commentDrafts[post.id] || ''}
onChange={(event) => setCommentDrafts((drafts) => ({
...drafts,
[post.id]: event.target.value,
}))}
placeholder="Tulis komentar untuk post ini..."
/>
<BaseButton
label="Komentar"
icon={resolveIcon('mdiCommentPlusOutline')}
color="info"
onClick={() => createComment(post.id)}
disabled={isSaving || isLoading}
/>
</div>
</div>
</CardBox>
);
})}
</div>
</div>
</SectionMain>
</>
);
};
VortaFeedPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
};
export default VortaFeedPage;

View File

@ -395,12 +395,12 @@ const VortaSynapsePage = () => {
return (
<>
<Head>
<title>{getPageTitle('04 Vorta Synapse')}</title>
<title>{getPageTitle('2. Vorta Synapse')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton
icon={resolveIcon('mdiTransitConnectionVariant')}
title="04 Vorta Synapse"
title="2. Vorta Synapse"
main
>
<BaseButton
@ -419,7 +419,7 @@ const VortaSynapsePage = () => {
<div className="relative">
<div className="mb-3 inline-flex items-center rounded-full bg-blue-50 px-3 py-1 text-sm font-semibold text-blue-700 dark:bg-blue-900/30 dark:text-blue-200">
<BaseIcon path={resolveIcon('mdiNetworkOutline')} className="mr-2" size={18} />
Protokol distribusi, afiliasi, dan logistik pintar untuk ekonomi kreator
2. Vorta Synapse · Protokol Distribusi, Afiliasi, Dan Logistik Pintar Untuk Ekonomi Kreator
</div>
<h2 className="mb-3 text-3xl font-bold leading-tight md:text-4xl">
Halo {userName}, sinkronkan drop kreator dari kampanye sampai paket terkirim.

View File

@ -705,12 +705,12 @@ const VortaUniversePage = () => {
return (
<>
<Head>
<title>{getPageTitle('VORTA Universe')}</title>
<title>{getPageTitle('4. Vorta Nexus')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton
icon={resolveIcon('mdiOrbitVariant')}
title="VORTA Universe"
title="4. Vorta Nexus"
main
>
<BaseButton
@ -743,15 +743,14 @@ const VortaUniversePage = () => {
<CardBox id="vorta-overview" className="mb-6 overflow-hidden border border-slate-100/80 bg-slate-950 text-white dark:border-dark-700">
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
<div className="lg:col-span-2">
<div className="inline-flex rounded-full bg-white/10 px-3 py-1 text-xs font-semibold uppercase tracking-widest text-emerald-200">
Sync Persisten · Flatlogic Native · PostgreSQL
<div className="inline-flex rounded-full bg-white/10 px-3 py-1 text-xs font-semibold tracking-widest text-emerald-200">
VORTA UNIVERSE 4. Vorta Nexus
</div>
<h1 className="mt-5 text-4xl font-black tracking-tight md:text-5xl">
🌐 VORTA UNIVERSE
4. Vorta Nexus
</h1>
<p className="mt-3 max-w-3xl text-base text-slate-300 md:text-lg">
Halo {greetingName}, prototype Express/SQLite Anda sekarang berjalan sebagai modul app utama:
auth bawaan, API existing, Sequelize, dan database PostgreSQL.
Halo {greetingName}, Skor Kepercayaan 0-100 Untuk Identitas, Bisnis, Ulasan, Dan Akses Komunitas.
</p>
<div className="mt-6 flex flex-wrap gap-3">
<BaseButton
@ -814,7 +813,7 @@ const VortaUniversePage = () => {
<div>
<h2 className="text-xl font-bold">Quick Menu VORTA</h2>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
Menu kiri prototype Express/SQLite Anda dibuat aktif sebagai anchor dan shortcut app utama.
Shortcut operasional Nexus tetap aktif, sementara menu utama VORTA Universe mengikuti urutan 1 sampai 5.
</p>
</div>
<div className="flex flex-wrap gap-2">

View File

@ -25,11 +25,11 @@ interface StyleObject {
}
export const white: StyleObject = {
aside: 'bg-white dark:text-white',
aside: 'bg-white text-black',
asideScrollbars: 'aside-scrollbars-light',
asideBrand: '',
asideMenuItem: 'text-gray-700 hover:bg-gray-100/70 dark:text-dark-500 dark:hover:text-white dark:hover:bg-dark-800',
asideMenuItemActive: 'font-bold text-black dark:text-white',
asideBrand: 'bg-white text-black',
asideMenuItem: 'text-black hover:bg-gray-100',
asideMenuItemActive: 'font-bold text-black',
asideMenuDropdown: 'bg-gray-100/75',
navBarItemLabel: 'text-blue-600',
navBarItemLabelHover: 'hover:text-black',
@ -53,14 +53,14 @@ export const white: StyleObject = {
export const midnightBlueTheme: StyleObject = {
aside: 'bg-midnightBlueTheme-800 text-midnightBlueTheme-text dark:text-white lg:rounded-lg',
aside: 'bg-white text-black lg:rounded-lg',
asideScrollbars: 'aside-scrollbars-blue',
asideBrand: 'text-blue-500 bg-white',
asideBrand: 'bg-white text-black',
asideMenuItem:
'text-midnightBlueTheme-text hover:text-white dark:text-dark-500 dark:hover:text-white dark:hover:bg-dark-800 dark:text-white',
asideMenuItemActive: 'font-bold text-white dark:text-white',
activeLinkColor: 'bg-midnightBlueTheme-buttonColor rounded-lg',
asideMenuDropdown: 'bg-blue-700/50',
'text-black hover:bg-gray-100',
asideMenuItemActive: 'font-bold text-black',
activeLinkColor: 'bg-gray-100 rounded-lg',
asideMenuDropdown: 'bg-gray-100/75',
navBarItemLabel: 'text-primaryText',
iconsColor: 'text-midnightBlueTheme-iconsColor dark:text-blue-500',
navBarItemLabelHover: 'hover:text-stone-400',
@ -109,12 +109,12 @@ export const dataGridStyles = {
};
export const basic: StyleObject = {
aside: 'bg-gray-800',
aside: 'bg-white text-black',
asideScrollbars: 'aside-scrollbars-gray',
asideBrand: 'bg-gray-900 text-white',
asideMenuItem: 'text-gray-300 hover:text-white',
asideMenuItemActive: 'font-bold text-white',
asideMenuDropdown: 'bg-gray-700/50',
asideBrand: 'bg-white text-black',
asideMenuItem: 'text-black hover:bg-gray-100',
asideMenuItemActive: 'font-bold text-black',
asideMenuDropdown: 'bg-gray-100/75',
navBarItemLabel: 'text-black',
navBarItemLabelHover: 'hover:text-blue-500',
navBarItemLabelActiveColor: 'text-blue-600',

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB