diff --git a/.env.example b/.env.example
index e3c2bcb..a5d0e60 100644
--- a/.env.example
+++ b/.env.example
@@ -1,4 +1,4 @@
-# Mode Live: required for Gemini Copilot and Gemini Vision extraction.
+# Optional: enables unverified Gemini Copilot responses.
# Keep server-side only. Never prefix with NEXT_PUBLIC_.
GEMINI_API_KEY=
@@ -6,8 +6,12 @@ GEMINI_API_KEY=
# Example: mysql://nexora_user:ENCODED_PASSWORD@127.0.0.1:3306/nexora
MYSQL_URL=
-# Optional Google Cloud configuration. The current scanner uses Gemini Vision;
-# these values declare a Document AI processor for a future/custom adapter.
+# Protects workspace and AI endpoints. Generate each value independently and
+# keep them server-side only. The browser session expires after 8 hours.
+NEXORA_ACCESS_TOKEN=
+NEXORA_SESSION_SECRET=
+
+# Reserved for future integrations. The current document scanner is simulation-only.
GOOGLE_CLOUD_PROJECT=
GOOGLE_CLOUD_LOCATION=us
DOCUMENT_AI_PROCESSOR_ID=
diff --git a/README.md b/README.md
index 6f2ac58..89c80b6 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-[](https://nexora-chi-cyan.vercel.app)
+[](https://nexora-chi-cyan.vercel.app)
[](https://cloud.google.com)
[](https://cloud.google.com)
[](https://github.com/fabrianivan/nexora/actions)
@@ -31,28 +31,28 @@
---
-Multi-country corporate ownership, document intelligence, fraud detection, and tax compliance workspace for Indonesia, Singapore, Malaysia, and the United States. Built for Google Cloud AI Builder Cup, Track 1: BFSI.
+Nexora is currently a demo interface for a multi-country corporate tax workspace. It is not a production tax, KYC, fraud, filing, or payments service.
Production:
-## Capabilities
+## Current behavior
-- Corporate entity CRUD with country-specific Zod validation, ownership graph, 100% ownership checks, and circular ownership detection.
-- PDF/JPG/PNG attachment workflow up to 10 MB. Gemini Vision extraction is available only in Live Mode; Manual Mode uses the identical editable schema.
-- Local fraud rules for TBTS indicators, IRAS Missing Trader risk, missing MyInvois UUID, split invoicing, non-deductible costs, and Z-score anomalies.
-- Deterministic corporate tax, indirect tax, withholding/treaty, and BEPS Pillar Two simulator. Calculations never depend on an LLM.
-- Editable fiscal reconciliation and validated draft exports for SPT 1771, Form C-S/Form C, and Form 1120 in JSON/XML plus a printable PDF summary.
-- MySQL persistence through server-only API routes and a repository abstraction, with full workspace JSON import/export.
+- The current UI loads synthetic company examples and marks them as demo data. UBO identity fields are redacted.
+- Corporate workspace state can be saved to MySQL through the authenticated workspace API. Configure `MYSQL_URL` and the access/session secrets to enable it.
+- The document scanner is a simulation. It does not upload or inspect a file, check a government registry, or verify compliance.
+- Copilot output is unverified, has no retrieved citations, and must not be used as tax advice. Tax and legal claims: **PERLU VERIFIKASI AHLI PAJAK.**
+- The persona switcher changes the demo view only. It is not per-user or per-entity authorization.
+- Billing, tax submission, external registry lookups, and payments are not connected.
-Nexora starts with an empty workspace. It contains no demo companies, fake scans, seeded invoices, or fabricated AI responses.
+The application displays a persistent warning that demo scores and sample company data are not production verification.
## Operating Modes
-**Manual Mode** is the default when `GEMINI_API_KEY` is absent. Every primary workflow remains available through manual forms. Copilot is disabled with a clear message, and the scanner never invents extracted values.
+**Development preview** is available without auth configuration, but workspace persistence and AI endpoints remain unavailable. It uses synthetic data only.
-**Live Mode** is enabled when `GEMINI_API_KEY` is present on the server. Gemini Vision can extract a user-uploaded attachment and Gemini can explain deterministic simulator results. API errors fail closed and return the user to manual entry.
+In production, configure `NEXORA_ACCESS_TOKEN` and `NEXORA_SESSION_SECRET`. The browser exchanges the access token for an eight-hour, HttpOnly, SameSite session cookie. Workspace, scanner, and Copilot APIs reject requests without a valid session. The access token is shared; configure an identity provider and per-user/tenant authorization before multi-user production use.
-The current implementation calls the Gemini API directly. `DOCUMENT_AI_PROCESSOR_ID`, BigQuery, and Vertex AI settings are optional configuration points and are not claimed as active integrations unless a deployment adds the corresponding adapter.
+When `GEMINI_API_KEY` is configured, Copilot can call Gemini. Its response is still unverified and no search or citation retrieval is connected. Scanner output remains simulated.
## Architecture
@@ -81,7 +81,7 @@ cp .env.example .env.local
npm run dev
```
-Set `MYSQL_URL` in `.env.local` to a MySQL database the server can reach. The app creates the `nexora_workspaces` table on first use; the equivalent DDL is in `sql/schema.mysql.sql`. Open . Leave `GEMINI_API_KEY` empty to verify the complete Manual Mode flow.
+Set `MYSQL_URL`, `NEXORA_ACCESS_TOKEN`, and `NEXORA_SESSION_SECRET` in `.env.local` to enable authenticated persistence. The server creates the `nexora_workspaces` table on first use. In development without auth configuration, the app opens in demo preview and does not save changes. Open .
```bash
npm run lint
@@ -109,10 +109,10 @@ e2e/ Playwright smoke test
## Security and Data
-- Input is validated by Zod; uploads accept only PDF/JPEG/PNG and are capped at 10 MB.
-- AI endpoints use an in-memory per-instance rate limit and request timeouts.
-- Uploaded bytes are sent to Gemini only after the user explicitly requests extraction. Attachments are not persisted; structured fields and attachment metadata are stored in MySQL.
-- The current build uses one shared workspace and has no sign-in, tenant isolation, or row-level ownership. Add authentication and tenant scoping before exposing a configured instance publicly.
+- Workspace, scan simulation, and Copilot endpoints require a signed server session; the access token is shared by all operators.
+- Workspace payloads are schema-validated and capped at 5 MB. AI/simulation routes have a basic per-process rate limit.
+- The scanner does not accept or inspect file bytes. Do not enter real personal or tax data into the demo.
+- There is no user directory, per-user role assignment, tenant isolation, audit log, or row-level ownership. Do not expose real customer data until these controls are implemented.
## Cloud Run
@@ -123,15 +123,11 @@ docker build -t nexora .
docker run --rm -p 8080:8080 -e PORT=8080 nexora
```
-Or deploy with `gcloud run deploy nexora --source . --region asia-southeast2 --allow-unauthenticated` and provide `MYSQL_URL` and (optionally) `GEMINI_API_KEY` through Secret Manager/environment configuration. The MySQL instance must be reachable from Cloud Run.
+Or deploy with `gcloud run deploy nexora --source . --region asia-southeast2 --allow-unauthenticated` and provide `MYSQL_URL`, `NEXORA_ACCESS_TOKEN`, and `NEXORA_SESSION_SECRET` through Secret Manager/environment configuration. The MySQL instance must be reachable from Cloud Run.
## Known Limitations
-- Workspace persistence requires `MYSQL_URL`; the workspace is stored as one JSON document in MySQL. The current build has no authentication or tenant isolation.
-- Gemini citations are displayed as official regulatory source links; no Vertex AI Search/RAG index is connected in this version.
-- Gemini's per-field confidence is model-reported and is not independently calibrated. Missing or invalid extraction output is sent to manual entry; no confidence score is fabricated.
-- Risk scores are a UI heuristic based on local findings, not a calibrated probability of fraud. TBTS tax comparisons use a configured standard-rate assumption and require verification against applicable exceptions.
-- Z-Score analysis uses e-invoices for each entity separately and requires at least five invoices for that entity.
-- The PDF output uses the browser print dialog, not an authority-issued filing form.
-- Payment channels are informational only. Nexora does not create billing codes, file returns, or move funds.
-- Tax rates are configurable assumptions and require professional review before real filing.
+- Workspace persistence requires MySQL and stores one shared JSON payload; multi-tenant storage is not implemented.
+- The demo contains tax rates, compliance scores, ownership, and transaction examples that have not been legally or operationally verified.
+- Copilot has no retrieval-backed citations. All tax and legal content: **PERLU VERIFIKASI AHLI PAJAK.**
+- No official billing, return submission, registry, KYC/PEP screening, or payment integration exists.
diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts
index 7f49adb..691e726 100644
--- a/e2e/smoke.spec.ts
+++ b/e2e/smoke.spec.ts
@@ -1,13 +1,36 @@
import { expect, test } from '@playwright/test';
-test('manual mode supports creating an entity and opening document flow', async ({ page }) => {
+async function authenticate(page: import('@playwright/test').Page) {
+ const response = await page.request.post('/api/session', { data: { token: 'test-access-token' } });
+ expect(response.ok()).toBeTruthy();
+}
+
+test('demo labels are visible and primary navigation switches workspace', async ({ page }) => {
+ await authenticate(page);
+ await page.goto('/');
+ await expect(page.getByRole('status')).toContainText('DATA DEMO / SIMULASI');
+ await page.getByRole('button', { name: 'Calculation', exact: true }).first().click();
+ await expect(page.getByRole('heading', { name: /Kertas Kerja Rekonsiliasi Fiskal/ })).toBeVisible();
+});
+
+test('workspace API rejects unauthenticated requests', async ({ request }) => {
+ const response = await request.get('/api/workspace');
+ expect(response.status()).toBe(401);
+ expect((await request.post('/api/copilot', { data: { prompt: 'test' } })).status()).toBe(401);
+ expect((await request.post('/api/scan-document', { data: { fileName: 'test.pdf' } })).status()).toBe(401);
+});
+
+test('mobile navigation remains available at narrow viewport', async ({ page }) => {
+ await authenticate(page);
+ await page.setViewportSize({ width: 390, height: 844 });
await page.goto('/');
- await expect(page.getByText('Mode Manual', { exact: true })).toBeVisible();
- await page.getByLabel('Nama legal').fill('PT Uji Kepatuhan');
- await page.getByLabel('NPWP / UEN / TIN / EIN').fill('1234567890123456');
- await page.getByLabel('Alamat').fill('Jalan Uji Nomor 1 Jakarta');
- await page.getByRole('button', { name: 'Simpan entitas' }).click();
- await expect(page.getByText('PT Uji Kepatuhan').first()).toBeVisible();
- await page.getByRole('button', { name: 'Dokumen' }).click();
- await expect(page.getByText('Smart Document & e-Invoice Scanner')).toBeVisible();
+ await expect(page.getByRole('navigation', { name: 'Navigasi utama' })).toBeVisible();
+ await page.getByRole('navigation', { name: 'Navigasi utama' }).getByRole('button', { name: 'Payment' }).click();
+ await expect(page.getByRole('heading', { name: /Tax Payment & Settlement/ })).toBeVisible();
+});
+
+test('security response headers are set', async ({ request }) => {
+ const response = await request.get('/');
+ expect(response.headers()['x-frame-options']).toBe('DENY');
+ expect(response.headers()['x-content-type-options']).toBe('nosniff');
});
diff --git a/next.config.mjs b/next.config.mjs
index 4de717a..3260995 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -2,6 +2,21 @@
const nextConfig = {
output: 'standalone',
reactStrictMode: true,
+ async headers() {
+ return [
+ {
+ source: '/:path*',
+ headers: [
+ { key: 'X-Content-Type-Options', value: 'nosniff' },
+ { key: 'X-Frame-Options', value: 'DENY' },
+ { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
+ { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
+ { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
+ ],
+ },
+ { source: '/api/:path*', headers: [{ key: 'Cache-Control', value: 'no-store' }, { key: 'X-Robots-Tag', value: 'noindex' }] },
+ ];
+ },
};
export default nextConfig;
diff --git a/src/app/api/copilot/route.ts b/src/app/api/copilot/route.ts
index 292617d..d33a390 100644
--- a/src/app/api/copilot/route.ts
+++ b/src/app/api/copilot/route.ts
@@ -1,187 +1,39 @@
import { NextRequest, NextResponse } from 'next/server';
import { MULTI_COUNTRY_DATA, CountryCode } from '@/data/multiCountryTaxData';
+import { isSessionValid } from '@/lib/server/session';
+import { rateLimit } from '@/lib/server/rateLimit';
function getSystemInstruction(country: CountryCode): string {
const cfg = MULTI_COUNTRY_DATA[country] || MULTI_COUNTRY_DATA.ID;
- return `Anda adalah Nexora Tax Global Tax Copilot, asisten konsultan pajak internasional, auditor forensik keuangan, dan spesialis Transfer Pricing / Tax Treaty (P3B/DTA) berbasis Google Cloud Vertex AI & Vertex AI Search.
-Saat ini pengguna sedang berada pada yurisdiksi: ${cfg.flag} ${cfg.name} (${cfg.authorityFull}).
-Entitas Aktif: ${cfg.company.name} (${cfg.company.taxIdLabel}: ${cfg.company.taxIdValue}).
-Sistem e-Invoice: ${cfg.eInvoiceSystem}.
-Pajak Tidak Langsung: ${cfg.indirectTaxName}.
-Tarif Pajak Korporasi Normal: ${(cfg.corporateTaxNormalRate * 100).toFixed(1)}% (${cfg.smeSpecialRateLabel}).
+ return `Anda adalah asisten umum untuk pertanyaan pajak ${cfg.name}. Informasi konteks ini hanya label yurisdiksi, bukan sumber hukum atau data resmi.
-Berikan jawaban yang terstruktur, akurat, mengutip dasar hukum perpajakan negara terkait (Indonesia: UU HPP & Coretax DJP; Singapura: Singapore Income Tax Act & GST Act IRAS; Malaysia: Income Tax Act 1967 & LHDN MyInvois; Amerika Serikat: Internal Revenue Code IRC & IRS Regulations; Lintas Negara: OECD BEPS 2.0 & Tax Treaty DTA/P3B).`;
+Berikan jawaban umum. Jangan mengarang tarif, pasal, tenggat, atau sitasi. Kutip sumber hanya jika sumber tersebut benar-benar tersedia dalam konteks; jika tidak, nyatakan keterbatasan dan tulis: PERLU VERIFIKASI AHLI PAJAK.`;
}
-function generateMultiCountryFallback(prompt: string, country: CountryCode) {
- const q = prompt.toLowerCase();
+function generateMultiCountryFallback(_prompt: string, country: CountryCode) {
const cfg = MULTI_COUNTRY_DATA[country] || MULTI_COUNTRY_DATA.ID;
-
- // Singapore specific queries
- if (country === 'SG' || q.includes('iras') || q.includes('singapura') || q.includes('pte') || q.includes('missing trader')) {
- if (q.includes('missing trader') || q.includes('mtf') || q.includes('lion city') || q.includes('gst')) {
- return {
- engine: 'Vertex AI Gemini 2.5 Flash + IRAS GST Forensics Engine',
- confidence: 99.3,
- citations: [
- 'Singapore Goods and Services Tax Act (Cap. 117A) Section 20(2A)',
- 'IRAS e-Tax Guide: GST — Missing Trader Fraud (Knowledge Principle)',
- ],
- reply: `### Protokol Mitigasi IRAS Missing Trader Fraud (MTF) — **Nusantara FinTech Pte. Ltd.**
-
-Sistem **Nexora Tax** menandai invoice \`LCHT/2026/08/0192\` dari **Lion City Hardware Trading LLP** (Nilai: **S$ 45,000**, Input GST: **S$ 4,410**) dengan **Skor Risiko 95/100 (CRITICAL)**.
-
----
-
-#### 1. Temuan Forensik Document AI & IRAS Registry:
-- **Status Registrasi GST**: Nomor \`M9-9991827-K\` berstatus *Cancelled/Suspended* di registri IRAS.
-- **Overcharge Tarif GST**: Vendor menagihkan GST sebesar **9,80% (S$ 4,410)**, melebihi tarif resmi **9% (S$ 4,050)**.
-- **Risiko Hukum Section 20(2A) GST Act**: Di bawah *Knowledge Principle*, IRAS berhak menolak klaim Input GST dan mengenakan penalti jika bisnis mengklaim pajak masukan dari rantai transaksi yang terindikasi *Missing Trader Fraud*.
-
-#### 2. Tindakan Mitigasi Segera:
-1. **Keluarkan dari GST Form F5 (Box 7)**: Jangan klaim Input GST sebesar **S$ 4,410** pada pelaporan kuartal ini.
-2. **Verifikasi UEN & PEPPOL ID via InvoiceNow**: Wajibkan seluruh vendor mengirimkan e-Invoice tervalidasi melalui jaringan **IMDA InvoiceNow**.`,
- };
- }
-
- return {
- engine: 'Vertex AI Gemini 2.5 Flash + Vertex AI Search (IRAS Tax Corpus)',
- confidence: 99.4,
- citations: [
- 'Singapore Income Tax Act 1947 Section 43(6) — Partial Tax Exemption (PTE)',
- 'IRAS Enterprise Innovation Scheme (EIS) — 400% Enhanced Tax Deduction',
- 'Singapore–Indonesia Avoidance of Double Taxation Agreement (DTA)',
- ],
- reply: `### Simulasi Pajak Korporasi Singapura (IRAS Form C-S) & Optimasi EIS
-
-Untuk **Nusantara FinTech Pte. Ltd.** (UEN: \`202319482K\`):
-- **Chargeable Income (Sebelum PTE)**: **S$ 341,500**
-- **Tarif Pajak Korporasi Standar (Headline Rate)**: **17%** (S$ 58,055 jika tanpa insentif)
-
----
-
-#### 1. Perhitungan Partial Tax Exemption (PTE) Singapura:
-- **75% Exemption** atas S$ 10,000 pertama = **S$ 7,500 bebas pajak**
-- **50% Exemption** atas S$ 190,000 berikutnya = **S$ 95,000 bebas pajak**
-- **Total Penghasilan Bebas Pajak (PTE)**: **S$ 102,500**
-- **Net Chargeable Income Setelah PTE**: \`S$ 341,500 - S$ 102,500\` = **S$ 239,000**
-- **Pajak Korporasi Terutang (17%)**: \`17% × S$ 239,000\` = **S$ 40,630** *(Effective Tax Rate hanya **11,9%** — hemat **S$ 17,425**!)*.
-
-#### 2. Tambahan Penghematan via Enterprise Innovation Scheme (EIS):
-Atas pengeluaran R&D & AI sebesar **S$ 85,000**, perusahaan berhak mengklaim **400% Tax Deduction** di bawah skema EIS IRAS, menghemat pajak tambahan hingga **S$ 27,425**.`,
- };
- }
-
- // Malaysia specific queries
- if (country === 'MY' || q.includes('lhdn') || q.includes('myinvois') || q.includes('malaysia')) {
- return {
- engine: 'Vertex AI Gemini 2.5 Flash + LHDN MyInvois Compliance Engine',
- confidence: 99.2,
- citations: [
- 'Income Tax Act 1967 (Malaysia) Section 82B & Section 120(1)(d)',
- 'LHDN MyInvois e-Invoice Guideline (Version 4.0) & Specific Guideline',
- 'Income Tax Act 1967 Schedule 1 Part I Para 2A (SME Tiered Tax Rate)',
- ],
- reply: `### Analisis Kepatuhan LHDN MyInvois & Pajak Korporasi SME Malaysia
-
-Untuk **Nusantara Digital Solutions Sdn. Bhd.** (TIN: \`C25849102090\`):
-
----
-
-#### 1. Risiko Invoice Tanpa Validasi UUID MyInvois (\`MY-TRX-089\` — RM 65,000)
-- Di bawah mandat **LHDN MyInvois (Continuous Transaction Control)**, setiap pengeluaran bisnis wajib didukung oleh e-Invoice yang telah divalidasi oleh LHDN (memiliki **Unique Identifier Number / UUID** dan kode QR terverifikasi).
-- Invoice manual dari **Klang Valley Enterprise Sdn. Bhd.** senilai **RM 65,000** tidak memiliki UUID MyInvois. Jika tetap diklaim sebagai pengurang pajak di **Form C**, LHDN akan melakukan *add-back* (koreksi positif) dan mengenakan penalti **Section 113(2) ITA 1967**.
-
-#### 2. Perhitungan Tarif Pajak Berjenjang SME Malaysia (Chargeable Income RM 709,000):
-- **RM 150,000 pertama @ 15%** = **RM 22,500**
-- **RM 450,000 berikutnya (RM 150k–RM 600k) @ 17%** = **RM 76,500**
-- **Sisa RM 109,000 (> RM 600k) @ 24%** = **RM 26,160**
-- **Total Pajak Terutang**: **RM 125,160** *(Hemat **RM 45,000** dibanding tarif korporasi standar 24% sebesar RM 170,160)*.`,
- };
- }
-
- // US specific queries
- if (country === 'US' || q.includes('irs') || q.includes('1099') || q.includes('w-9') || q.includes('schedule m-1')) {
- return {
- engine: 'Vertex AI Gemini 2.5 Flash + IRS IRC Tax Engine',
- confidence: 99.1,
- citations: [
- 'Internal Revenue Code (IRC) §274(a) & §274(n) — Disallowance of Entertainment & 50% Meal Limit',
- 'IRC §3406 — Mandatory 24% Backup Withholding on Missing/Invalid TIN',
- 'IRC §1441 & §1442 — 30% Foreign Withholding & Form W-8BEN-E Treaty Relief',
- ],
- reply: `### Audit Kepatuhan IRS Form 1120 (Schedule M-1) & Backup Withholding (IRC §3406)
-
-Untuk **Nusantara AI Technologies, Inc.** (Federal EIN: \`88-4920193\`):
-
----
-
-#### 1. Koreksi Fiskal Schedule M-1 (Meals & Entertainment — \`US-TRX-061\`)
-Berdasarkan **IRC §274** (pasca-TCJA):
-- **Tiket VIP / Hiburan Klien ($14,000)**: **100% Non-Deductible** (Koreksi Fiskal Positif **$14,000**).
-- **Jamuan Makan Bisnis dengan Klien ($10,000)**: Hanya **50% Deductible** (Koreksi Fiskal Positif **$5,000**).
-- **Total Schedule M-1 Book-to-Tax Add-Back**: **$19,000**.
-
-#### 2. Pelanggaran Backup Withholding 24% (\`US-TRX-089\` — $62,000)
-- Pembayaran jasa kontraktor **$62,000** kepada **Apex Growth Partners** dilakukan tanpa **Form W-9** yang lolos *IRS TIN Matching*.
-- Sesuai **IRC §3406**, pembayar wajib memotong **24% Backup Withholding ($14,880)** dan menyetorkannya melalui **IRS Form 945**. Untuk vendor luar negeri (misal anak usaha di Indonesia/Singapura), wajib mengumpulkan **Form W-8BEN-E** untuk menurunkan tarif potong dari **30%** menjadi **10%** (sesuai Tax Treaty US–Indonesia).`,
- };
- }
-
- // Cross-border / Tax Treaty queries
- if (q.includes('treaty') || q.includes('p3b') || q.includes('dta') || q.includes('lintas negara') || q.includes('multi negara') || q.includes('transfer pricing')) {
- return {
- engine: 'Vertex AI Gemini 2.5 Flash + Global OECD BEPS & DTA Treaty Engine',
- confidence: 99.5,
- citations: [
- 'P3B (Tax Treaty) Indonesia–Singapura, Indonesia–Malaysia, & Indonesia–Amerika Serikat',
- 'PER-25/PJ/2018 tentang Tata Cara Penerapan Persetujuan Penghindaran Pajak Berganda (Form DGT)',
- 'PMK 172 Tahun 2023 tentang Penerapan Prinsip Kewajaran dan Kelaziman Usaha (Transfer Pricing)',
- ],
- reply: `### Analisis Perpajakan Lintas Negara (Cross-Border Tax Treaty / P3B & Transfer Pricing)
-
-**Nexora Tax** memantau kewajiban pemotongan pajak lintas yurisdiksi (**Withholding Tax**) untuk transaksi grup antara **🇮🇩 Indonesia, 🇸🇬 Singapura, 🇲🇾 Malaysia, dan 🇺🇸 Amerika Serikat**:
-
-| Rute Transaksi Lintas Negara | Tarif Domestik Tanpa Dokumen | Tarif Tax Treaty (DTA / P3B) | Dokumen Wajib yang Divalidasi Document AI |
-| :--- | :---: | :---: | :--- |
-| **🇮🇩 ID $\\rightarrow$ 🇸🇬 SG** (Jasa Cloud / Royalti) | 20% (PPh Pasal 26) | **10% (Royalti) / 0% (Laba Usaha tanpa BUT)** | **Form DGT (e-SKD)** + *Certificate of Residence (COR) IRAS* |
-| **🇸🇬 SG $\\rightarrow$ 🇺🇸 US** (Lisensi AI / Royalti) | 10% (IRAS Section 45) | **10% / Bebas untuk Software Copyrighted Article** | **IRAS Section 45 Filing** + Kontrak Lisensi |
-| **🇮🇩 ID $\\rightarrow$ 🇺🇸 US** (Langganan SaaS / Royalti) | 20% (PPh Pasal 26) | **10% (Pasal 13 Tax Treaty RI–AS)** | **Form DGT** + **IRS Form 6166 (US Residency)** |
-| **🇺🇸 US $\\rightarrow$ 🇮🇩 ID** (Pembayaran Klien US) | 30% (IRC §1442 FDAP) | **10% / 0% (Business Profits)** | **IRS Form W-8BEN-E** |
-
-**Rekomendasi Kepatuhan**: Pastikan **e-SKD (Form DGT)** diterbitkan **sebelum** batas waktu penyampaian SPT Masa PPh Pasal 26 agar penghematan tarif dari 20% ke 10%/0% tidak dibatalkan saat pemeriksaan.`,
- };
- }
-
- // Default Indonesia / General response
return {
- engine: `Vertex AI Gemini 2.5 Flash + Vertex AI Search (${cfg.authorityShort})`,
- confidence: 99.2,
- citations: [
- `${cfg.authorityFull} — ${cfg.filingFormName}`,
- `Cross-Border Tax Treaty Relief: ${cfg.crossBorderTreatyNote.requiredForm}`,
- ],
- reply: `### Analisis Kepatuhan Pajak Multi-Negara — **${cfg.flag} ${cfg.name} (${cfg.company.name})**
-
-Berdasarkan buku besar aktif pada sistem **${cfg.eInvoiceSystem}**:
-- **Identitas Pajak**: \`${cfg.company.taxIdLabel}: ${cfg.company.taxIdValue}\`
-- **Tax Compliance Score**: **${cfg.company.taxHealthScore}/100** (Risiko ${cfg.riskNoticeName}: **${cfg.company.auditRiskProb}%**)
-- **Pajak Tidak Langsung**: **${cfg.indirectTaxName}** | **Tarif Pajak Badan**: **${(cfg.corporateTaxNormalRate * 100).toFixed(0)}%** (${cfg.smeSpecialRateLabel})
-- **Optimasi Lintas Negara (Tax Treaty)**: Gunakan **${cfg.crossBorderTreatyNote.requiredForm}** untuk memangkas tarif pemotongan dari **${cfg.crossBorderTreatyNote.standardWhtRate}** menjadi **${cfg.crossBorderTreatyNote.treatyWhtRate}**.
-
-Silakan klik salah satu tombol pertanyaan cepat di atas atau ubah bendera negara di bilah atas untuk mengaudit entitas di negara lain!`,
+ engine: 'Simulasi lokal Nexora (tanpa sumber eksternal)',
+ confidence: null,
+ citations: [],
+ verification: 'simulation',
+ reply: `Saya belum dapat memverifikasi jawaban pajak ${cfg.name} dari sumber resmi. Hubungkan model AI dan tinjau setiap klaim dengan penasihat pajak. PERLU VERIFIKASI AHLI PAJAK.`,
};
}
export async function POST(req: NextRequest) {
+ if (!isSessionValid(req)) return NextResponse.json({ error: 'Autentikasi diperlukan.' }, { status: 401 });
+ if (!rateLimit('copilot:session', 20)) return NextResponse.json({ error: 'Batas permintaan tercapai. Coba lagi sebentar.' }, { status: 429 });
try {
const body = await req.json();
- const { prompt, apiKey, country = 'ID' } = body as {
+ const { prompt, country = 'ID' } = body as {
prompt: string;
- apiKey?: string;
country?: CountryCode;
};
- const effectiveKey = process.env.GEMINI_API_KEY?.trim() || apiKey?.trim();
+ if (typeof prompt !== 'string' || prompt.trim().length === 0 || prompt.length > 4000 || !(country in MULTI_COUNTRY_DATA)) {
+ return NextResponse.json({ error: 'Prompt atau yurisdiksi tidak valid.' }, { status: 400 });
+ }
+ const effectiveKey = process.env.GEMINI_API_KEY?.trim();
if (effectiveKey) {
try {
@@ -214,13 +66,12 @@ export async function POST(req: NextRequest) {
data?.candidates?.[0]?.content?.parts?.[0]?.text ||
generateMultiCountryFallback(prompt, country).reply;
return NextResponse.json({
- reply: text,
- citations: [
- `${MULTI_COUNTRY_DATA[country].authorityFull}`,
- `OECD BEPS 2.0 & Tax Treaty (${MULTI_COUNTRY_DATA[country].crossBorderTreatyNote.requiredForm})`,
- ],
- engine: `Live Google Gemini 2.5 Flash (${MULTI_COUNTRY_DATA[country].name} Jurisdiction)`,
- confidence: 99.6,
+ reply: `⚠️ Keluaran AI belum diverifikasi oleh sumber resmi. PERLU VERIFIKASI AHLI PAJAK.\n\n${text}`,
+ citations: [],
+ engine: 'Gemini API (sumber hukum tidak diambil atau diverifikasi)',
+ confidence: null,
+ verification: 'unverified-ai-output',
+ disclaimer: 'Jawaban AI belum diverifikasi. Tarif, aturan, dan tenggat pajak: PERLU VERIFIKASI AHLI PAJAK.',
});
}
} catch {
@@ -228,7 +79,16 @@ export async function POST(req: NextRequest) {
}
}
- return NextResponse.json(generateMultiCountryFallback(prompt || '', country));
+ const simulation = generateMultiCountryFallback(prompt, country);
+ return NextResponse.json({
+ ...simulation,
+ engine: 'Simulasi lokal Nexora (tanpa sumber eksternal)',
+ confidence: null,
+ citations: [],
+ verification: 'simulation',
+ disclaimer: 'Ini simulasi dengan data contoh, bukan nasihat atau verifikasi pajak. Tarif, aturan, dan tenggat: PERLU VERIFIKASI AHLI PAJAK.',
+ reply: `⚠️ SIMULASI — bukan nasihat pajak dan tidak memeriksa sumber resmi. PERLU VERIFIKASI AHLI PAJAK.\n\n${simulation.reply}`,
+ });
} catch {
return NextResponse.json(
{ error: 'Gagal memproses permintaan AI Copilot Multi-Negara' },
diff --git a/src/app/api/scan-document/route.ts b/src/app/api/scan-document/route.ts
index 48871ea..450e426 100644
--- a/src/app/api/scan-document/route.ts
+++ b/src/app/api/scan-document/route.ts
@@ -1,7 +1,11 @@
import { NextRequest, NextResponse } from 'next/server';
import { MULTI_COUNTRY_DATA, CountryCode, formatCurrency } from '@/data/multiCountryTaxData';
+import { isSessionValid } from '@/lib/server/session';
+import { rateLimit } from '@/lib/server/rateLimit';
export async function POST(req: NextRequest) {
+ if (!isSessionValid(req)) return NextResponse.json({ error: 'Autentikasi diperlukan.' }, { status: 401 });
+ if (!rateLimit('document-simulation:session', 10)) return NextResponse.json({ error: 'Batas pemindaian tercapai. Coba lagi sebentar.' }, { status: 429 });
try {
const body = await req.json();
const {
@@ -18,6 +22,10 @@ export async function POST(req: NextRequest) {
country?: CountryCode;
};
+ if (typeof fileName !== 'string' || fileName.trim().length === 0 || fileName.length > 200 || !['ID', 'SG', 'MY', 'US'].includes(country)) {
+ return NextResponse.json({ error: 'Metadata dokumen atau yurisdiksi tidak valid.' }, { status: 400 });
+ }
+
const cfg = MULTI_COUNTRY_DATA[country] || MULTI_COUNTRY_DATA.ID;
const dpp = Number(customDpp) || (country === 'ID' ? 28500000 : 25000);
const expectedTax = Math.round(dpp * cfg.indirectTaxRate);
@@ -39,25 +47,26 @@ export async function POST(req: NextRequest) {
title: `Ekstraksi Document AI (${cfg.name}) — ${fileName}`,
docType: 'e-Faktur PPN',
fileName,
- uploadDate: `Baru saja (${cfg.authorityShort} Live OCR)`,
+ uploadDate: `Simulasi (${cfg.authorityShort})`,
+ simulation: true,
vendorName: vendor,
npwp16: isSuspicious ? `INVALID-${country}-TAXID` : cfg.company.taxIdValue,
nitku: isSuspicious ? 'UNVERIFIED-ENDPOINT' : cfg.company.subIdValue,
docNumber: docNum,
- docDate: '18 Oktober 2026',
+ docDate: 'Tanggal tidak diekstrak (simulasi)',
dpp,
taxType: `${cfg.indirectTaxName}`,
taxRate: `${((reportedTax / dpp) * 100).toFixed(2)}%`,
taxAmount: reportedTax,
totalAmount: dpp + reportedTax,
- status: isSuspicious ? 'RISIKO_FRAUD' : 'PATUH',
- confidenceScore: 99.3,
+ status: isSuspicious ? 'SIMULASI_BUTUH_TINJAUAN' : 'SIMULASI_TANPA_VERIFIKASI',
+ confidenceScore: 0,
extractedFields: [
{
label: `Sistem e-Invoice (${cfg.eInvoiceSystem})`,
value: fileName,
validation: 'valid',
- note: `Dipindai menggunakan Google Cloud Document AI (${cfg.name} Processor)`,
+ note: `Simulasi ekstraksi untuk ${cfg.name}; file belum diunggah dan tidak ada registry yang diakses.`,
},
{
label: `${cfg.company.taxIdLabel} Vendor`,
@@ -65,7 +74,7 @@ export async function POST(req: NextRequest) {
validation: isSuspicious ? 'error' : 'valid',
note: isSuspicious
? `PERINGATAN: Identitas pajak vendor gagal validasi di portal ${cfg.authorityShort}`
- : `Aktif dan patuh di registri ${cfg.authorityShort}`,
+ : `Belum diverifikasi pada registri ${cfg.authorityShort}; nilai ini hanya data simulasi.`,
},
{
label: 'Dasar Pengenaan Pajak (Taxable Base)',
@@ -83,7 +92,7 @@ export async function POST(req: NextRequest) {
expectedTax,
country
)} (Selisih ${formatCurrency(diff, country)})`
- : `Akurat 100% sesuai tarif resmi ${cfg.indirectTaxName}`,
+ : `Perhitungan aritmetika simulasi; tarif dan perlakuan pajak perlu verifikasi ahli.`,
},
],
aiAuditSummary: isSuspicious
@@ -94,14 +103,14 @@ export async function POST(req: NextRequest) {
expectedTax,
country
)}). Berisiko memicu ${cfg.riskNoticeName}.`
- : `DOKUMEN VALID & PATUH (${cfg.authorityShort}): Seluruh atribut ${cfg.eInvoiceSystem} pada ${fileName} telah sesuai regulasi ${cfg.name}. Nilai pajak ${formatCurrency(
+ : `SIMULASI — bukan verifikasi kepatuhan (${cfg.authorityShort}): ${fileName} tidak diunggah atau diperiksa terhadap ${cfg.eInvoiceSystem}. Nilai contoh ${formatCurrency(
reportedTax,
country
)} siap dimasukkan ke ${cfg.filingFormName}.`,
- regulationRef: `${cfg.authorityFull} — Standar Validasi ${cfg.eInvoiceSystem}`,
+ regulationRef: 'Tidak ada sumber resmi yang diakses; hasil hanya simulasi.',
recommendedAction: isSuspicious
- ? `Tahan klaim pajak masukan dan minta revisi dokumen melalui ${cfg.eInvoiceSystem}.`
- : `Setujui dan masukkan otomatis ke draf ${cfg.filingFormName}.`,
+ ? 'Tinjau dokumen dan lakukan validasi manual sebelum mengambil keputusan.'
+ : 'Jangan menyetujui dokumen dari hasil simulasi ini; lakukan validasi manual.',
});
} catch {
return NextResponse.json(
diff --git a/src/app/api/session/route.ts b/src/app/api/session/route.ts
new file mode 100644
index 0000000..347a708
--- /dev/null
+++ b/src/app/api/session/route.ts
@@ -0,0 +1,33 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { accessTokenMatches, createSession, isSessionValid, sessionCookieName, sessionIsConfigured } from '@/lib/server/session';
+import { rateLimit } from '@/lib/server/rateLimit';
+
+export async function GET(request: NextRequest) {
+ if (!sessionIsConfigured()) return NextResponse.json({ configured: false, authenticated: false }, { status: 503 });
+ return NextResponse.json({ configured: true, authenticated: isSessionValid(request) });
+}
+
+export async function POST(request: NextRequest) {
+ if (!sessionIsConfigured()) return NextResponse.json({ error: 'Akses belum dikonfigurasi. Atur NEXORA_ACCESS_TOKEN dan NEXORA_SESSION_SECRET.' }, { status: 503 });
+ if (!rateLimit('session-login', 10, 60_000)) return NextResponse.json({ error: 'Terlalu banyak percobaan. Coba lagi dalam satu menit.' }, { status: 429 });
+ let body: unknown;
+ try { body = await request.json(); } catch { return NextResponse.json({ error: 'Request harus berupa JSON.' }, { status: 400 }); }
+ const token = typeof body === 'object' && body !== null ? (body as { token?: unknown }).token : undefined;
+ if (!accessTokenMatches(token)) return NextResponse.json({ error: 'Token akses tidak valid.' }, { status: 401 });
+
+ const response = NextResponse.json({ authenticated: true });
+ response.cookies.set(sessionCookieName, createSession(), {
+ httpOnly: true,
+ secure: request.nextUrl.protocol === 'https:',
+ sameSite: 'strict',
+ path: '/',
+ maxAge: 8 * 60 * 60,
+ });
+ return response;
+}
+
+export async function DELETE() {
+ const response = NextResponse.json({ authenticated: false });
+ response.cookies.set(sessionCookieName, '', { httpOnly: true, sameSite: 'strict', path: '/', maxAge: 0 });
+ return response;
+}
diff --git a/src/app/api/status/route.ts b/src/app/api/status/route.ts
index 9661aab..39efa78 100644
--- a/src/app/api/status/route.ts
+++ b/src/app/api/status/route.ts
@@ -1,3 +1,3 @@
export async function GET() {
- return Response.json({ mode: process.env.GEMINI_API_KEY?.trim() ? 'live' : 'manual', gemini: Boolean(process.env.GEMINI_API_KEY?.trim()), documentAi: Boolean(process.env.GOOGLE_CLOUD_PROJECT && process.env.DOCUMENT_AI_PROCESSOR_ID), bigQuery: Boolean(process.env.BIGQUERY_DATASET), mysqlConfigured: Boolean(process.env.MYSQL_URL?.trim()) });
+ return Response.json({ mode: process.env.GEMINI_API_KEY?.trim() ? 'ai-unverified' : 'simulation', gemini: Boolean(process.env.GEMINI_API_KEY?.trim()), documentAi: false, documentScanner: 'simulation-only', bigQuery: Boolean(process.env.BIGQUERY_DATASET), mysqlConfigured: Boolean(process.env.MYSQL_URL?.trim()) });
}
diff --git a/src/app/api/workspace/route.ts b/src/app/api/workspace/route.ts
index 2cd2785..21c720e 100644
--- a/src/app/api/workspace/route.ts
+++ b/src/app/api/workspace/route.ts
@@ -1,10 +1,12 @@
import { NextRequest } from 'next/server';
import { workspaceSchema } from '@/lib/domain';
import { workspaceRepository } from '@/lib/mysql';
+import { isSessionValid } from '@/lib/server/session';
export const runtime = 'nodejs';
-export async function GET() {
+export async function GET(request: NextRequest) {
+ if (!isSessionValid(request)) return Response.json({ error: 'Autentikasi diperlukan.' }, { status: 401 });
if (!process.env.MYSQL_URL?.trim()) return Response.json({ error: 'Database belum dikonfigurasi. Tambahkan MYSQL_URL.' }, { status: 503 });
try {
const workspace = await workspaceRepository.load();
@@ -15,6 +17,7 @@ export async function GET() {
}
export async function PUT(request: NextRequest) {
+ if (!isSessionValid(request)) return Response.json({ error: 'Autentikasi diperlukan.' }, { status: 401 });
if (!process.env.MYSQL_URL?.trim()) return Response.json({ error: 'Database belum dikonfigurasi. Tambahkan MYSQL_URL.' }, { status: 503 });
const reader = request.body?.getReader();
if (!reader) return Response.json({ error: 'Request body kosong.' }, { status: 400 });
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 920da60..9fc9dd2 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,6 +1,6 @@
'use client';
-import React, { useState, useMemo, useEffect } from 'react';
+import React, { useState, useMemo, useEffect, useRef } from 'react';
import {
Building2,
Plus,
@@ -75,7 +75,7 @@ interface UserPersona {
const USER_PERSONAS: UserPersona[] = [
{
id: 'ceo_parent',
- name: 'Ir. Aditya Pratama, M.Sc.',
+ name: 'Demo Group Executive',
title: 'Group Chief Executive Officer (CEO)',
entityRole: 'PARENT',
organization: 'PT Nusantara Kreasi Digital Tbk (Induk Grup)',
@@ -89,7 +89,7 @@ const USER_PERSONAS: UserPersona[] = [
},
{
id: 'cfo_parent',
- name: 'Sri Mulyani Dewi, S.E., Ak., CA',
+ name: 'Demo Group Finance Lead',
title: 'Group Chief Financial Officer (CFO)',
entityRole: 'PARENT',
organization: 'PT Nusantara Kreasi Digital Tbk (Induk Grup)',
@@ -103,7 +103,7 @@ const USER_PERSONAS: UserPersona[] = [
},
{
id: 'md_sg',
- name: 'Marcus Tan, CFA',
+ name: 'Demo Singapore Director',
title: 'Managing Director — Singapore Subsidiary',
entityRole: 'SUBSIDIARY',
organization: 'Nusantara FinTech Pte. Ltd. (Singapura)',
@@ -117,7 +117,7 @@ const USER_PERSONAS: UserPersona[] = [
},
{
id: 'dir_my',
- name: 'Ahmad Razak, CA (M)',
+ name: 'Demo Malaysia Director',
title: 'Country Director — Malaysia Subsidiary',
entityRole: 'SUBSIDIARY',
organization: 'Nusantara Digital Solutions Sdn. Bhd. (Malaysia)',
@@ -131,7 +131,7 @@ const USER_PERSONAS: UserPersona[] = [
},
{
id: 'ctrl_us',
- name: 'Sarah Jenkins, CPA',
+ name: 'Demo US Controller',
title: 'VP Finance & Controller — US Subsidiary',
entityRole: 'SUBSIDIARY',
organization: 'Nusantara AI Technologies, Inc. (Amerika Serikat)',
@@ -158,8 +158,8 @@ interface IndividualShareholder {
effectiveSharePercent: number;
votingRightPercent: number;
dividendTaxRegime: string;
- pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)' | 'Enhanced Due Diligence (PEP)';
- kycDocStatus: 'Verified by Document AI' | 'Pending Review';
+ pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)' | 'Enhanced Due Diligence (PEP)' | 'Not assessed (demo)';
+ kycDocStatus: 'Verified by Document AI' | 'Pending Review' | 'Not verified (demo)';
status: 'Active' | 'Under Review';
address: string;
email: string;
@@ -253,7 +253,7 @@ interface CorporateEntity {
individualShareholders: IndividualShareholder[];
}
-const INITIAL_COMPANIES: CorporateEntity[] = [
+const INITIAL_COMPANIES: CorporateEntity[] = ([
{
id: 'ENT-ID-001',
legalName: 'PT Nusantara Kreasi Digital Tbk',
@@ -273,12 +273,12 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
functionalCurrency: 'IDR (Indonesian Rupiah)',
paidUpCapital: 'Rp 25.000.000.000',
taxOfficeName: 'KPP Madya Jakarta Selatan I (Coretax DJP)',
- address: 'Jl. Jend. Sudirman Kav. 52-53, District 8 SCBD Treasury Tower Lt. 28',
+ address: '[REDACTED DEMO]',
cityAndPostal: 'Jakarta Selatan, DKI Jakarta 12190, Indonesia',
authorizedTaxOfficer: 'Budi Santoso, S.E., Ak., CA., BKP',
officerPosition: 'VP of Global Tax & Fiscal Compliance (Kuasa Wajib Pajak)',
- contactEmail: 'tax.compliance@nusantaradigital.co.id',
- contactPhone: '+62 21 5088 9910',
+ contactEmail: 'demo@example.invalid',
+ contactPhone: '[REDACTED DEMO]',
businessClassification: 'KLU 62019 — Aktivitas Pemrograman Komputer, AI & Holding FinTech',
taxAuthority: 'Direktorat Jenderal Pajak (Coretax DJP)',
vatOrGstStatus: 'PKP Aktif (PPN 11% • e-Faktur 4.0 Coretax)',
@@ -454,8 +454,8 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
effectiveDate: '2020-06-15',
status: 'Active',
bepsRisk: 'Low (Substantial Activity)',
- address: '8 Marina View, #42-01 Asia Square Tower 1, Singapore 018960',
- contactEmail: 'corporate@archipelagoventures.sg',
+ address: '[REDACTED DEMO]',
+ contactEmail: 'demo@example.invalid',
},
{
id: 'INT-00002',
@@ -469,8 +469,8 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
effectiveDate: '2018-03-14',
status: 'Active',
bepsRisk: 'Low (Substantial Activity)',
- address: 'Pacific Century Place Lt. 35, SCBD Lot 10, Jakarta Selatan 12190',
- contactEmail: 'legal@merdekakapital.co.id',
+ address: '[REDACTED DEMO]',
+ contactEmail: 'demo@example.invalid',
},
{
id: 'INT-00003',
@@ -484,20 +484,20 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
effectiveDate: '2022-11-01',
status: 'Under Review',
bepsRisk: 'High (Conduit / Treaty Shopping Risk)',
- address: '200 Park Avenue, Suite 3900, New York, NY 10166, USA',
- contactEmail: 'tax@bluehorizonpartners.com',
+ address: '[REDACTED DEMO]',
+ contactEmail: 'demo@example.invalid',
},
],
individualShareholders: [
{
id: 'IND-00001',
type: 'Individual Shareholder (UBO)',
- fullName: 'Dr. Hendra Kusuma, S.Kom., M.B.A.',
+ fullName: 'Demo Shareholder',
roleTitle: 'Founder & President Commissioner (Ultimate Beneficial Owner)',
nationality: '🇮🇩 Indonesia',
taxResidency: '🇮🇩 Indonesia (SPDN — KPP Pratama Jakarta Kebayoran Baru Satu)',
- nationalIdOrPassport: 'NIK: 3174011403800001',
- personalTaxId: '3174011403800001 (NPWP 16 Digit NIK)',
+ nationalIdOrPassport: '[REDACTED DEMO]',
+ personalTaxId: '[REDACTED DEMO]',
directSharePercent: 25,
effectiveSharePercent: 48.5,
votingRightPercent: 48.5,
@@ -505,18 +505,18 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)',
kycDocStatus: 'Verified by Document AI',
status: 'Active',
- address: 'Jl. Senopati Dalam No. 18, Kebayoran Baru, Jakarta Selatan 12190',
- email: 'hendra.kusuma@nusantaradigital.co.id',
+ address: '[REDACTED DEMO]',
+ email: 'demo@example.invalid',
},
{
id: 'IND-00002',
type: 'Individual Founder / Director',
- fullName: 'Kartika Wijaya, S.E., Ak., CA.',
+ fullName: 'Demo Shareholder',
roleTitle: 'Co-Founder & Chief Executive Officer (Direktur Utama)',
nationality: '🇮🇩 Indonesia',
taxResidency: '🇮🇩 Indonesia (SPDN — KPP Pratama Jakarta Setiabudi)',
- nationalIdOrPassport: 'NIK: 3171045208850003',
- personalTaxId: '3171045208850003 (NPWP 16 Digit NIK)',
+ nationalIdOrPassport: '[REDACTED DEMO]',
+ personalTaxId: '[REDACTED DEMO]',
directSharePercent: 15,
effectiveSharePercent: 28.0,
votingRightPercent: 28.0,
@@ -524,18 +524,18 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)',
kycDocStatus: 'Verified by Document AI',
status: 'Active',
- address: 'Apartemen Pacific Place Residence Lt. 22, SCBD, Jakarta Selatan 12190',
- email: 'kartika.wijaya@nusantaradigital.co.id',
+ address: '[REDACTED DEMO]',
+ email: 'demo@example.invalid',
},
{
id: 'IND-00003',
type: 'Individual Minority Investor',
- fullName: 'Marcus Tan Wei Ming',
+ fullName: 'Demo Shareholder',
roleTitle: 'Non-Resident Individual Angel Investor (WPLN)',
nationality: '🇸🇬 Singapore',
taxResidency: '🇸🇬 Singapore (SPLN — IRAS Certificate of Residence Verified)',
- nationalIdOrPassport: 'Passport: K8192031Z / NRIC: S8419283D',
- personalTaxId: 'SG-NRIC-S8419283D',
+ nationalIdOrPassport: '[REDACTED DEMO]',
+ personalTaxId: '[REDACTED DEMO]',
directSharePercent: 5,
effectiveSharePercent: 19.0,
votingRightPercent: 19.0,
@@ -543,8 +543,8 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)',
kycDocStatus: 'Verified by Document AI',
status: 'Active',
- address: '15 Nassim Road, #04-02 Nassim Park Residences, Singapore 258382',
- email: 'marcus.tan@archipelagoventures.sg',
+ address: '[REDACTED DEMO]',
+ email: 'demo@example.invalid',
},
],
},
@@ -567,12 +567,12 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
functionalCurrency: 'SGD (Singapore Dollar)',
paidUpCapital: 'S$ 1,500,000',
taxOfficeName: 'Inland Revenue Authority of Singapore (IRAS Revenue House)',
- address: '10 Marina Boulevard, #39-01 Marina Bay Financial Centre Tower 2',
+ address: '[REDACTED DEMO]',
cityAndPostal: 'Singapore 018983',
authorizedTaxOfficer: 'Lim Wei Ling, SCTP (Accredited Tax Advisor)',
officerPosition: 'Regional Tax Director — APAC',
- contactEmail: 'sg.tax@nusantarafintech.sg',
- contactPhone: '+65 6812 4910',
+ contactEmail: 'demo@example.invalid',
+ contactPhone: '[REDACTED DEMO]',
businessClassification: 'SSIC 62019 — Software Publishing, FinTech IP & Treasury Center',
taxAuthority: 'Inland Revenue Authority of Singapore (IRAS)',
vatOrGstStatus: 'GST Registered (9% Prevailing Rate • IMDA InvoiceNow Active)',
@@ -725,8 +725,8 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
effectiveDate: '2021-05-20',
status: 'Active',
bepsRisk: 'Low (Substantial Activity)',
- address: 'SCBD Lot 11, Jakarta Selatan 12190, Indonesia',
- contactEmail: 'tax.compliance@nusantaradigital.co.id',
+ address: '[REDACTED DEMO]',
+ contactEmail: 'demo@example.invalid',
},
{
id: 'INT-00005',
@@ -740,20 +740,20 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
effectiveDate: '2022-01-10',
status: 'Active',
bepsRisk: 'Low (Substantial Activity)',
- address: '60B Orchard Road, #06-18 The Atrium@Orchard, Singapore 238891',
- contactEmail: 'investments@temasekseed.sg',
+ address: '[REDACTED DEMO]',
+ contactEmail: 'demo@example.invalid',
},
],
individualShareholders: [
{
id: 'IND-00004',
type: 'Individual Shareholder (UBO)',
- fullName: 'Dr. Hendra Kusuma, S.Kom., M.B.A.',
+ fullName: 'Demo Shareholder',
roleTitle: 'Registrable Controller (ACRA RORC — Ultimate Beneficial Owner)',
nationality: '🇮🇩 Indonesia',
taxResidency: '🇮🇩 Indonesia (SPDN)',
- nationalIdOrPassport: 'Passport: C9182731',
- personalTaxId: '3174011403800001',
+ nationalIdOrPassport: '[REDACTED DEMO]',
+ personalTaxId: '[REDACTED DEMO]',
directSharePercent: 0,
effectiveSharePercent: 41.2,
votingRightPercent: 41.2,
@@ -761,18 +761,18 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)',
kycDocStatus: 'Verified by Document AI',
status: 'Active',
- address: 'Jl. Senopati Dalam No. 18, Jakarta Selatan 12190, Indonesia',
- email: 'hendra.kusuma@nusantaradigital.co.id',
+ address: '[REDACTED DEMO]',
+ email: 'demo@example.invalid',
},
{
id: 'IND-00005',
type: 'Individual Founder / Director',
- fullName: 'Lim Wei Ling, SCTP',
+ fullName: 'Demo Shareholder',
roleTitle: 'Singapore Resident Director & ESOP Individual Shareholder',
nationality: '🇸🇬 Singapore',
taxResidency: '🇸🇬 Singapore (IRAS Tax Resident)',
- nationalIdOrPassport: 'NRIC: S8829104G',
- personalTaxId: 'SG-NRIC-S8829104G',
+ nationalIdOrPassport: '[REDACTED DEMO]',
+ personalTaxId: '[REDACTED DEMO]',
directSharePercent: 5,
effectiveSharePercent: 5.0,
votingRightPercent: 5.0,
@@ -780,8 +780,8 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)',
kycDocStatus: 'Verified by Document AI',
status: 'Active',
- address: '18 Marina Gardens Drive, #28-04 Marina One Residences, Singapore 018953',
- email: 'weiling.lim@nusantarafintech.sg',
+ address: '[REDACTED DEMO]',
+ email: 'demo@example.invalid',
},
],
},
@@ -804,12 +804,12 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
functionalCurrency: 'MYR (Malaysian Ringgit)',
paidUpCapital: 'RM 1,500,000',
taxOfficeName: 'LHDN Cawangan Kuala Lumpur Bandar (MyTax / MyInvois)',
- address: 'Level 25, Menara Maxis, Kuala Lumpur City Centre (KLCC)',
+ address: '[REDACTED DEMO]',
cityAndPostal: '50088 Kuala Lumpur, Wilayah Persekutuan, Malaysia',
authorizedTaxOfficer: 'Ahmad Faiz bin Razak, CTIM',
officerPosition: 'Head of Malaysia Tax & MyInvois Compliance',
- contactEmail: 'my.tax@nusantaradigital.my',
- contactPhone: '+60 3 2380 1190',
+ contactEmail: 'demo@example.invalid',
+ contactPhone: '[REDACTED DEMO]',
businessClassification: 'MSIC 62010 — Computer Programming & Enterprise SaaS Distribution',
taxAuthority: 'Lembaga Hasil Dalam Negeri Malaysia (LHDN MyInvois)',
vatOrGstStatus: 'RMCD SST Registered (8% Service Tax • Mandatory MyInvois e-Invoice)',
@@ -937,20 +937,20 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
effectiveDate: '2022-08-12',
status: 'Active',
bepsRisk: 'Low (Substantial Activity)',
- address: '10 Marina Boulevard, #39-01 MBFC Tower 2, Singapore 018983',
- contactEmail: 'sg.tax@nusantarafintech.sg',
+ address: '[REDACTED DEMO]',
+ contactEmail: 'demo@example.invalid',
},
],
individualShareholders: [
{
id: 'IND-00006',
type: 'Individual Founder / Director',
- fullName: 'Ahmad Faiz bin Razak, CTIM',
+ fullName: 'Demo Shareholder',
roleTitle: 'Malaysia Resident Director & Minority Individual Shareholder',
nationality: '🇲🇾 Malaysia',
taxResidency: '🇲🇾 Malaysia (LHDN Resident Individual)',
- nationalIdOrPassport: 'MyKad: 890412-14-5521',
- personalTaxId: 'IG-890412145521 (LHDN TIN)',
+ nationalIdOrPassport: '[REDACTED DEMO]',
+ personalTaxId: '[REDACTED DEMO]',
directSharePercent: 5,
effectiveSharePercent: 5.0,
votingRightPercent: 5.0,
@@ -958,18 +958,18 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)',
kycDocStatus: 'Verified by Document AI',
status: 'Active',
- address: 'Jalan Ampang Hilir No. 24, 55000 Kuala Lumpur, Malaysia',
- email: 'ahmad.faiz@nusantaradigital.my',
+ address: '[REDACTED DEMO]',
+ email: 'demo@example.invalid',
},
{
id: 'IND-00007',
type: 'Individual Shareholder (UBO)',
- fullName: 'Dr. Hendra Kusuma, S.Kom., M.B.A.',
+ fullName: 'Demo Shareholder',
roleTitle: 'Ultimate Beneficial Owner (SSM Section 56 BO Register)',
nationality: '🇮🇩 Indonesia',
taxResidency: '🇮🇩 Indonesia (SPDN)',
- nationalIdOrPassport: 'Passport: C9182731',
- personalTaxId: '3174011403800001',
+ nationalIdOrPassport: '[REDACTED DEMO]',
+ personalTaxId: '[REDACTED DEMO]',
directSharePercent: 0,
effectiveSharePercent: 41.2,
votingRightPercent: 41.2,
@@ -977,8 +977,8 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)',
kycDocStatus: 'Verified by Document AI',
status: 'Active',
- address: 'Jl. Senopati Dalam No. 18, Jakarta Selatan 12190, Indonesia',
- email: 'hendra.kusuma@nusantaradigital.co.id',
+ address: '[REDACTED DEMO]',
+ email: 'demo@example.invalid',
},
],
},
@@ -1001,12 +1001,12 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
functionalCurrency: 'USD (United States Dollar)',
paidUpCapital: 'US$ 2,000,000',
taxOfficeName: 'Internal Revenue Service (Ogden UT International & Corporate Center)',
- address: '548 Market St, Suite 49102, Financial District',
+ address: '[REDACTED DEMO]',
cityAndPostal: 'San Francisco, CA 94104, United States',
authorizedTaxOfficer: 'Michael Chen, CPA, MST',
officerPosition: 'Head of US Tax & IRS Compliance',
- contactEmail: 'us.tax@nusantara-ai.com',
- contactPhone: '+1 (415) 890-4412',
+ contactEmail: 'demo@example.invalid',
+ contactPhone: '[REDACTED DEMO]',
businessClassification: 'NAICS 541511 — Custom Computer Programming & AI Cloud Services',
taxAuthority: 'Internal Revenue Service (IRS MeF & State Nexus)',
vatOrGstStatus: 'Multi-State Economic Sales Tax Nexus (CA, NY, TX • Wayfair Compliant)',
@@ -1134,20 +1134,20 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
effectiveDate: '2023-02-01',
status: 'Active',
bepsRisk: 'Low (Substantial Activity)',
- address: 'SCBD Lot 11, Jakarta Selatan 12190, Indonesia',
- contactEmail: 'tax.compliance@nusantaradigital.co.id',
+ address: '[REDACTED DEMO]',
+ contactEmail: 'demo@example.invalid',
},
],
individualShareholders: [
{
id: 'IND-00008',
type: 'Individual Founder / Director',
- fullName: 'Michael Chen, CPA, MST',
+ fullName: 'Demo Shareholder',
roleTitle: 'US Resident Officer & Section 83(b) Restricted Stock Shareholder',
nationality: '🇺🇸 United States',
taxResidency: '🇺🇸 United States (IRS Form 1040 Resident)',
- nationalIdOrPassport: 'US Passport: 549182731',
- personalTaxId: 'SSN: ***-**-4918 (Form W-9 Verified)',
+ nationalIdOrPassport: '[REDACTED DEMO]',
+ personalTaxId: '[REDACTED DEMO]',
directSharePercent: 5,
effectiveSharePercent: 5.0,
votingRightPercent: 5.0,
@@ -1155,18 +1155,18 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)',
kycDocStatus: 'Verified by Document AI',
status: 'Active',
- address: '425 California St, Apt 18B, San Francisco, CA 94104, USA',
- email: 'm.chen@nusantara-ai.com',
+ address: '[REDACTED DEMO]',
+ email: 'demo@example.invalid',
},
{
id: 'IND-00009',
type: 'Individual Shareholder (UBO)',
- fullName: 'Dr. Hendra Kusuma, S.Kom., M.B.A.',
+ fullName: 'Demo Shareholder',
roleTitle: 'FinCEN BOI Registered Ultimate Beneficial Owner',
nationality: '🇮🇩 Indonesia',
taxResidency: '🇮🇩 Indonesia (Non-Resident Alien — Form W-8BEN)',
- nationalIdOrPassport: 'Passport: C9182731',
- personalTaxId: 'Foreign TIN: 3174011403800001 (Form W-8BEN)',
+ nationalIdOrPassport: '[REDACTED DEMO]',
+ personalTaxId: '[REDACTED DEMO]',
directSharePercent: 0,
effectiveSharePercent: 48.5,
votingRightPercent: 48.5,
@@ -1174,12 +1174,28 @@ const INITIAL_COMPANIES: CorporateEntity[] = [
pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)',
kycDocStatus: 'Verified by Document AI',
status: 'Active',
- address: 'Jl. Senopati Dalam No. 18, Jakarta Selatan 12190, Indonesia',
- email: 'hendra.kusuma@nusantaradigital.co.id',
+ address: '[REDACTED DEMO]',
+ email: 'demo@example.invalid',
},
],
},
-];
+] satisfies CorporateEntity[]).map((company) => ({
+ ...company,
+ address: '[REDACTED DEMO]',
+ authorizedTaxOfficer: 'Demo Contact (unverified)',
+ contactEmail: 'demo@example.invalid',
+ contactPhone: '[REDACTED DEMO]',
+ status: 'PENDING',
+ aiComplianceScore: 0,
+ documents: company.documents.map((document) => ({
+ ...document,
+ status: 'Pending Review',
+ confidenceScore: 0,
+ extractedNote: 'Data simulasi; dokumen tidak diunggah atau diverifikasi.',
+ })),
+ structureNodes: company.structureNodes.map((node) => ({ ...node, treatyFormStatus: 'Pending Renewal', complianceScore: 0 })),
+ individualShareholders: [],
+}));
// Pure AST markdown renderer for chat bubbles (**bold**, *italic*, `code`, ### headers, bullets, formulas, NPWP badges)
@@ -1277,6 +1293,12 @@ function renderChatMessage(text: string, isUserMessage = false): React.ReactNode
}
export default function NexoraApp() {
+ const [sessionStatus, setSessionStatus] = useState<'checking' | 'authenticated' | 'preview' | 'login_required' | 'configuration_missing'>('checking');
+ const [accessToken, setAccessToken] = useState('');
+ const [sessionError, setSessionError] = useState('');
+ const [workspaceStatus, setWorkspaceStatus] = useState<'loading' | 'connected' | 'unavailable' | 'saving' | 'save_failed'>('loading');
+ const workspaceReadyRef = useRef(false);
+ const workspaceCoreRef = useRef>({ version: 1, entities: [], ownerships: [], documents: [], reconciliation: [] });
// Primary & Sub Navigation matching Figma Prototype (`Companies | Calculation | Tax Return | Payment` + `Corporate | Individual | Studies | Tax Calendar | Reports`)
const [primaryNav, setPrimaryNav] = useState('companies');
const [subNav, setSubNav] = useState('corporate');
@@ -1288,6 +1310,92 @@ export default function NexoraApp() {
const [companySearch, setCompanySearch] = useState('');
const [entityRoleFilter, setEntityRoleFilter] = useState<'ALL' | 'PARENT' | 'SUBSIDIARY'>('ALL');
+ useEffect(() => {
+ let active = true;
+ fetch('/api/session').then(async (response) => {
+ if (response.ok) {
+ const result = await response.json();
+ if (active) setSessionStatus(result.authenticated ? 'authenticated' : 'login_required');
+ } else if (response.status === 503 && process.env.NODE_ENV === 'development') {
+ if (active) setSessionStatus('preview');
+ } else if (active) {
+ setSessionStatus(response.status === 503 ? 'configuration_missing' : 'login_required');
+ }
+ }).catch(() => { if (active) setSessionStatus(process.env.NODE_ENV === 'development' ? 'preview' : 'configuration_missing'); });
+ return () => { active = false; };
+ }, []);
+
+ useEffect(() => {
+ let active = true;
+ if (sessionStatus === 'preview') {
+ setWorkspaceStatus('unavailable');
+ return () => { active = false; };
+ }
+ if (sessionStatus !== 'authenticated') return () => { active = false; };
+ fetch('/api/workspace').then(async (response) => {
+ if (!response.ok) throw new Error('Workspace MySQL belum dapat dimuat.');
+ const { workspace } = await response.json();
+ workspaceCoreRef.current = {
+ version: 1,
+ entities: Array.isArray(workspace?.entities) ? workspace.entities : [],
+ ownerships: Array.isArray(workspace?.ownerships) ? workspace.ownerships : [],
+ documents: Array.isArray(workspace?.documents) ? workspace.documents : [],
+ reconciliation: Array.isArray(workspace?.reconciliation) ? workspace.reconciliation : [],
+ };
+ const storedCompanies = workspace?.corporateCompanies;
+ if (Array.isArray(storedCompanies) && storedCompanies.length > 0 && storedCompanies.every((company) =>
+ company && typeof company === 'object' && typeof company.id === 'string' && typeof company.legalName === 'string' && typeof company.countryCode === 'string'
+ )) {
+ setCompanies(storedCompanies as CorporateEntity[]);
+ const firstEntityId = (storedCompanies[0] as CorporateEntity).id;
+ setSelectedEntityId(firstEntityId);
+ }
+ if (active) {
+ workspaceReadyRef.current = true;
+ setWorkspaceStatus('connected');
+ }
+ }).catch(() => { if (active) setWorkspaceStatus('unavailable'); });
+ return () => { active = false; };
+ }, [sessionStatus]);
+
+ useEffect(() => {
+ if (sessionStatus !== 'authenticated' || !workspaceReadyRef.current) return;
+ const timer = window.setTimeout(() => {
+ setWorkspaceStatus('saving');
+ fetch('/api/workspace', {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ ...workspaceCoreRef.current, corporateCompanies: companies }),
+ }).then((response) => {
+ setWorkspaceStatus(response.ok ? 'connected' : 'save_failed');
+ }).catch(() => setWorkspaceStatus('save_failed'));
+ }, 700);
+ return () => window.clearTimeout(timer);
+ }, [companies, sessionStatus]);
+
+ async function handleSessionLogin(event: React.FormEvent) {
+ event.preventDefault();
+ setSessionError('');
+ try {
+ const response = await fetch('/api/session', {
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: accessToken }),
+ });
+ if (!response.ok) {
+ const result = await response.json();
+ setSessionError(result.error || 'Autentikasi gagal.');
+ return;
+ }
+ setAccessToken('');
+ setSessionStatus('authenticated');
+ } catch { setSessionError('Server tidak dapat dijangkau.'); }
+ }
+
+ async function handleLogout() {
+ await fetch('/api/session', { method: 'DELETE' }).catch(() => undefined);
+ workspaceReadyRef.current = false;
+ setSessionStatus('login_required');
+ }
+
// Figma 1 5-Screen Corporate Management State
const [inputMode, setInputMode] = useState<'search_existing' | 'manual'>('manual');
const [registryLookupQuery, setRegistryLookupQuery] = useState('');
@@ -1318,24 +1426,24 @@ export default function NexoraApp() {
const [newShJurisdiction, setNewShJurisdiction] = useState('🇸🇬 Singapore');
const [newShTaxId, setNewShTaxId] = useState('UEN-202499182K');
const [newShPercent, setNewShPercent] = useState('15');
- const [newShAddress, setNewShAddress] = useState('1 Raffles Place, #20-61 One Raffles Place, Singapore 048616');
+ const [newShAddress, setNewShAddress] = useState('[DATA DEMO DIHAPUS]');
const [newShEmail, setNewShEmail] = useState('legal@globalhorizon.sg');
// Add Individual Shareholder Form fields (Pemegang Saham Perorangan / UBO)
- const [newIndName, setNewIndName] = useState('Ir. Aditya Pratama, M.Sc.');
+ const [newIndName, setNewIndName] = useState('Contoh Pemegang Saham');
const [newIndType, setNewIndType] = useState('Individual Shareholder (UBO)');
const [newIndRole, setNewIndRole] = useState('Co-Founder & Chief Technology Officer (UBO)');
const [newIndNationality, setNewIndNationality] = useState('🇮🇩 Indonesia');
const [newIndResidency, setNewIndResidency] = useState('🇮🇩 Indonesia (SPDN — KPP Pratama Jakarta Selatan)');
- const [newIndNationalId, setNewIndNationalId] = useState('NIK: 3174051908840002');
- const [newIndTaxId, setNewIndTaxId] = useState('3174051908840002 (NPWP 16 Digit NIK)');
+ const [newIndNationalId, setNewIndNationalId] = useState('[DATA DEMO DIHAPUS]');
+ const [newIndTaxId, setNewIndTaxId] = useState('[DATA DEMO DIHAPUS]');
const [newIndDirectShare, setNewIndDirectShare] = useState('10');
const [newIndEffectiveShare, setNewIndEffectiveShare] = useState('18.5');
const [newIndDividendRegime, setNewIndDividendRegime] = useState(
'0% Bebas PPh Dividen (Diinvestasikan di NKRI min. 3 Tahun — UU HPP Pasal 4 ayat (3) huruf f & PMK 18/2021)'
);
- const [newIndAddress, setNewIndAddress] = useState('Jl. Wijaya II No. 44, Kebayoran Baru, Jakarta Selatan 12160');
- const [newIndEmail, setNewIndEmail] = useState('aditya.pratama@nusantaradigital.co.id');
+ const [newIndAddress, setNewIndAddress] = useState('[DATA DEMO DIHAPUS]');
+ const [newIndEmail, setNewIndEmail] = useState('demo@example.invalid');
// Multi-Entity & RBAC Access Control States (International Standard)
const [activePersonaId, setActivePersonaId] = useState('ceo_parent');
@@ -1374,13 +1482,8 @@ export default function NexoraApp() {
{
role: 'assistant',
entityLabel: '🇮🇩 PT Nusantara Kreasi Digital Tbk (Induk) & 3 Anak Perusahaan',
- text: 'Halo! Saya **Nexora AI Tax & Subsidiary Copilot** yang siap membantu Anda menganalisis pajak induk & seluruh anak perusahaan (**🇮🇩 Indonesia, 🇸🇬 Singapura, 🇲🇾 Malaysia, 🇺🇸 Amerika Serikat**). Klik tombol cepat di bawah atau pilih perusahaan di halaman untuk mengaudit dokumen, mendeteksi faktur fiktif, dan mengoptimalkan *Tax Treaty (P3B/DTA)*.',
- citations: [
- 'UU HPP & Coretax DJP (Induk ID)',
- 'IRAS InvoiceNow & ITA (Anak Usaha SG)',
- 'LHDN MyInvois & ITA 1967 (Anak Usaha MY)',
- 'IRS Form 1120 & Form 5472 (Anak Usaha US)',
- ],
+ text: 'Copilot memberikan keluaran AI yang belum diverifikasi dan tidak mengambil sumber resmi. Jangan masukkan data pribadi atau rahasia. Untuk klaim pajak: **PERLU VERIFIKASI AHLI PAJAK.**',
+ citations: [],
},
]);
@@ -1458,24 +1561,7 @@ export default function NexoraApp() {
setIsDocAiRunning(true);
setTimeout(() => {
setIsDocAiRunning(false);
- setCompanies((prev) =>
- prev.map((c) =>
- c.id === selectedEntity.id
- ? {
- ...c,
- status: 'ACTIVE',
- aiComplianceScore: Math.min(99, c.aiComplianceScore + 5),
- documents: c.documents.map((d) => ({
- ...d,
- status: 'Verified by Document AI',
- })),
- }
- : c
- )
- );
- triggerToast(
- `Google Cloud Document AI berhasil mengekstrak & memvalidasi ke-4 dokumen korporasi untuk ${selectedEntity.legalName}!`
- );
+ triggerToast('Simulasi saja: belum ada file yang diunggah dan Document AI/registry belum dijalankan.');
}, 750);
}
@@ -1575,7 +1661,7 @@ export default function NexoraApp() {
);
setShowShareholderModal(false);
triggerToast(
- `Intermediate Shareholder "${newSh.name}" (${newSh.id}) berhasil disimpan & lolos skrining OECD BEPS Action 6!`
+ `Data contoh "${newSh.name}" (${newSh.id}) tersimpan; skrining hukum dan kepatuhan belum dilakukan.`
);
}
@@ -1611,9 +1697,9 @@ export default function NexoraApp() {
effectiveSharePercent: Number(newIndEffectiveShare) || Number(newIndDirectShare) || 10,
votingRightPercent: Number(newIndEffectiveShare) || Number(newIndDirectShare) || 10,
dividendTaxRegime: newIndDividendRegime,
- pepAndSanctionStatus: 'Verified Clear (Non-PEP / FATF Safe)',
- kycDocStatus: 'Verified by Document AI',
- status: 'Active',
+ pepAndSanctionStatus: 'Not assessed (demo)',
+ kycDocStatus: 'Not verified (demo)',
+ status: 'Under Review',
address: newIndAddress,
email: newIndEmail,
};
@@ -1629,7 +1715,7 @@ export default function NexoraApp() {
);
setShowIndividualModal(false);
triggerToast(
- `Individual Shareholder "${newInd.fullName}" (${newInd.id}) berhasil ditambahkan & diverifikasi Document AI KYC!`
+ `Data demo pemegang saham "${newInd.fullName}" (${newInd.id}) ditambahkan; KYC dan PEP belum diverifikasi.`
);
}
@@ -1673,7 +1759,7 @@ export default function NexoraApp() {
)
);
triggerToast(
- `Anomali transaksi ${trxId} (${vendorName}) pada ${selectedEntity.legalName} berhasil dikoreksi secara fiskal!`
+ `Status demo untuk anomali ${trxId} (${vendorName}) diperbarui. Tidak ada catatan pajak atau transaksi yang dikoreksi.`
);
}
}
@@ -1722,8 +1808,37 @@ export default function NexoraApp() {
}
}
+ if (sessionStatus === 'checking') {
+ return Memeriksa sesi…;
+ }
+
+ if (sessionStatus === 'login_required' || sessionStatus === 'configuration_missing') {
+ return (
+
+
+
+ );
+ }
+
return (
-
+
+
+ DATA DEMO / SIMULASI. Persona, hasil AI, skor kepatuhan, dan data perusahaan bawaan bukan verifikasi produksi.
+ MySQL: {workspaceStatus === 'connected' ? 'terhubung' : workspaceStatus === 'saving' ? 'menyimpan…' : workspaceStatus === 'save_failed' ? 'gagal menyimpan' : workspaceStatus === 'loading' ? 'memuat…' : 'tidak tersedia (perubahan tidak tersimpan)'}
+
{/* =====================================================================
1. TOP WHITE HEADER BAR (EXACT FIGMA 2 LAYOUT — CLEAN, NO EXTRA BUTTONS)
===================================================================== */}
@@ -1747,7 +1862,7 @@ export default function NexoraApp() {
Nexora
- Fintech Tax
+ Demo
@@ -1808,6 +1923,11 @@ export default function NexoraApp() {
@@ -1891,11 +2024,11 @@ export default function NexoraApp() {
- OECD Pillar Two & ISO/IEC 27001 Access Governance
+ DEMO DATA • Persona switcher bukan kontrol akses
- Multi-Entity Consolidated Control
+ Tampilan grup simulasi
1 Induk + 3 Anak Usaha
@@ -1906,7 +2039,7 @@ export default function NexoraApp() {
GROUP ENTITIES (INDUK & ANAK PERUSAHAAN)
- Struktur Korporasi Grup: PT Nusantara Kreasi Digital Tbk (Induk) mengonsolidasi 3 Anak Perusahaan di 🇸🇬 Singapura, 🇲🇾 Malaysia, dan 🇺🇸 Amerika Serikat. Selaras standar tata kelola internasional, pemegang peran eksekutif induk memiliki visibilitas konsolidasian penuh.
+ Contoh struktur perusahaan untuk demonstrasi. Data dan hubungan kepemilikan ini belum diverifikasi dan tidak mewakili organisasi nyata.
@@ -1963,11 +2096,11 @@ export default function NexoraApp() {
{isGroupCeoOrParentExecutive ? (
<>
- Hak Akses Eksekutif Terverifikasi: Sebagai CEO/setara dari Perusahaan Induk (PT Nusantara Kreasi Digital Tbk), Anda memiliki wewenang penuh melihat seluruh entitas induk & anak perusahaan (100% Group Visibility sesuai standar enterprise internasional).
+ Persona demo aktif: Pemilihan ini hanya mengubah tampilan contoh. Akses database dan API dikontrol terpisah oleh sesi server.
>
) : (
<>
- Segregasi Hak Akses Aktif: Anda sedang masuk sebagai karyawan anak usaha ({activePersona.title}). Sesuai ISO 27001, entitas lain dikunci. Hanya CEO/C-Level dari Perusahaan Induk yang berhak melihat semua entitas.
+ Persona demo aktif: Filter ini hanya simulasi tampilan dan bukan pembatasan keamanan. Sesi saat ini memakai satu token operator bersama.
>
)}
@@ -1978,7 +2111,7 @@ export default function NexoraApp() {
type="button"
onClick={() => {
setActivePersonaId('ceo_parent');
- triggerToast('Beralih ke Hak Akses: Ir. Aditya Pratama (Group CEO - Induk)');
+ triggerToast('Persona demo diganti. Ini bukan perubahan hak akses server.');
}}
className="text-[11px] font-bold text-amber-300 hover:text-white underline cursor-pointer shrink-0"
>
@@ -2449,7 +2582,7 @@ export default function NexoraApp() {
type="button"
onClick={() =>
triggerToast(
- `Data registrasi pajak untuk "${registryLookupQuery || selectedEntity.legalName}" berhasil disinkronkan!`
+ `Simulasi saja: registry pajak eksternal belum diakses untuk "${registryLookupQuery || selectedEntity.legalName}".`
)
}
className="absolute right-1.5 top-1.5 bottom-1.5 px-4 rounded-lg bg-[#2563EB] hover:bg-blue-700 text-white text-xs font-bold transition shadow-xs cursor-pointer flex items-center justify-center"
@@ -2714,7 +2847,7 @@ export default function NexoraApp() {
{isDocAiRunning
? 'Scanning Document AI...'
- : 'Verify All Documents (Document AI)'}
+ : 'Simulasikan pemeriksaan (demo)'}
@@ -2725,13 +2858,13 @@ export default function NexoraApp() {
- Memindai Dokumen Statutory dengan Google Cloud Document AI OCR...
+ Simulasi antarmuka — tidak ada dokumen yang sedang dipindai
- 99.2%
+ DEMO
+ Keluaran AI tidak diverifikasi dan prompt dapat dikirim ke Google Gemini jika API key dikonfigurasi. Jangan kirim data pribadi/rahasia. PERLU VERIFIKASI AHLI PAJAK.
+
- Pilih dari daftar Pengurus / Founder / UBO terdaftar di Grup Nusantara:
+ Pilih contoh data demonstrasi; identitas dan hubungan UBO tidak diverifikasi:
@@ -5407,10 +5546,10 @@ export default function NexoraApp() {
- Hak Akses & Peran Karyawan Korporasi
+ Persona Demo (Bukan Kontrol Akses)
- Sesuai standar tata kelola perusahaan internasional, karyawan dengan posisi CEO/setara dari Perusahaan Induk (Holding) memiliki visibilitas penuh ke seluruh entitas.
+ Pilihan di bawah hanya mengubah tampilan simulasi. Peran pengguna individual dan pembatasan entitas belum dikonfigurasi.