Autosave: 20260619-124833
This commit is contained in:
parent
f954e3c153
commit
e12c93f715
359
PROJECT_LOG.md
Normal file
359
PROJECT_LOG.md
Normal 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**
|
||||
@ -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}
|
||||
|
||||
@ -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(' ');
|
||||
|
||||
|
||||
@ -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>}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'>
|
||||
|
||||
@ -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'>
|
||||
|
||||
@ -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 transaksi—semuanya 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'>
|
||||
|
||||
@ -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( () => {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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
|
||||
|
||||
479
frontend/src/pages/vorta-feed.tsx
Normal file
479
frontend/src/pages/vorta-feed.tsx
Normal 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;
|
||||
@ -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.
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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',
|
||||
|
||||
BIN
sidebar-white-black-menu.png
Normal file
BIN
sidebar-white-black-menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 558 KiB |
Loading…
x
Reference in New Issue
Block a user