diff --git a/package.json b/package.json
index b1bec53..9e8beda 100644
--- a/package.json
+++ b/package.json
@@ -17,10 +17,12 @@
"bcryptjs": "^3.0.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "cn": "^0.2.6",
"date-fns": "^4.4.0",
"jose": "^6.2.10",
"lucide-react": "^1.34.0",
"next": "16.3.3",
+ "next-themes": "^0.4.6",
"prisma": "^5.22.0",
"react": "19.2.8",
"react-day-picker": "^10.0.1",
@@ -28,6 +30,7 @@
"react-hook-form": "^7.86.0",
"recharts": "^3.10.1",
"shadcn": "^4.19.0",
+ "sonner": "^2.0.8",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0",
"uuid": "^14.0.2",
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index fc469bb..2148637 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -1,6 +1,3 @@
-// This is your Prisma schema file,
-// learn more about it in the docs: https://pris.ly/d/prisma-schema
-
generator client {
provider = "prisma-client-js"
}
@@ -25,28 +22,28 @@ model User {
}
model Order {
- id String @id @default(uuid())
- title String
- description String?
- date DateTime
- allow_custom Boolean @default(false)
- status String @default("DRAFT")
- creator_id String
- creator User @relation("CreatedOrders", fields: [creator_id], references: [id])
+ id String @id @default(uuid())
+ title String
+ date DateTime
+ allow_custom Boolean @default(false)
+ status String @default("DRAFT")
+ creator_id String
+ created_at DateTime @default(now())
+ updated_at DateTime @updatedAt
+ description String?
available_items AvailableItem[]
- submissions Submission[]
- created_at DateTime @default(now())
- updated_at DateTime @updatedAt
+ creator User @relation("CreatedOrders", fields: [creator_id], references: [id])
+ submissions Submission[]
}
model AvailableItem {
id String @id @default(uuid())
order_id String
name String
+ created_at DateTime @default(now())
+ updated_at DateTime @updatedAt
is_sold_out Boolean @default(false)
order Order @relation(fields: [order_id], references: [id], onDelete: Cascade)
- created_at DateTime @default(now())
- updated_at DateTime @updatedAt
}
model Submission {
@@ -54,13 +51,13 @@ model Submission {
order_id String
user_id String
bill Int?
- paid_amount Int?
payment_status String @default("BELUM_BAYAR")
+ created_at DateTime @default(now())
+ updated_at DateTime @updatedAt
+ paid_amount Int?
order Order @relation(fields: [order_id], references: [id], onDelete: Cascade)
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
items SubmissionItem[]
- created_at DateTime @default(now())
- updated_at DateTime @updatedAt
}
model SubmissionItem {
@@ -69,7 +66,13 @@ model SubmissionItem {
name String
qty Int @default(1)
is_custom Boolean @default(false)
- submission Submission @relation(fields: [submission_id], references: [id], onDelete: Cascade)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
+ submission Submission @relation(fields: [submission_id], references: [id], onDelete: Cascade)
+}
+
+model Setting {
+ key String @id
+ value String
+ updated_at DateTime @updatedAt
}
diff --git a/src/app/(app)/my-orders/[id]/page.tsx b/src/app/(app)/my-orders/[id]/page.tsx
index f31c1b9..767dfec 100644
--- a/src/app/(app)/my-orders/[id]/page.tsx
+++ b/src/app/(app)/my-orders/[id]/page.tsx
@@ -283,6 +283,7 @@ export default function OrderDetailPage() {
{order.status === 'OPEN' && (
diff --git a/src/app/(app)/settings/integrations/page.tsx b/src/app/(app)/settings/integrations/page.tsx
new file mode 100644
index 0000000..2b80152
--- /dev/null
+++ b/src/app/(app)/settings/integrations/page.tsx
@@ -0,0 +1,132 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+import { getSettings, updateSettings } from '@/app/actions'
+import { toast } from 'sonner'
+import { Loader2, Save, MessageSquare, MessageCircle, AlertCircle } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import { Textarea } from '@/components/ui/textarea'
+
+export default function IntegrationsPage() {
+ const [loading, setLoading] = useState(true)
+ const [saving, setSaving] = useState(false)
+ const [settings, setSettings] = useState({
+ MATTERMOST_WEBHOOK_URL: '',
+ MATTERMOST_TEMPLATE: '',
+ WHATSAPP_TEMPLATE: ''
+ })
+
+ useEffect(() => {
+ const fetchSettings = async () => {
+ const data = await getSettings()
+ if (data) {
+ setSettings({
+ MATTERMOST_WEBHOOK_URL: data.MATTERMOST_WEBHOOK_URL || '',
+ MATTERMOST_TEMPLATE: data.MATTERMOST_TEMPLATE || 'Halo tim! Lagi ada orderan open nih untuk {title}. Mumpung belum di-checkout, yang mau ikutan nitip bisa langsung cek ke sini ya: {url}',
+ WHATSAPP_TEMPLATE: data.WHATSAPP_TEMPLATE || 'Halo tim! Lagi ada orderan open nih untuk {title}. Mumpung belum di-checkout, yang mau ikutan nitip bisa langsung cek ke sini ya: {url}'
+ })
+ }
+ setLoading(false)
+ }
+ fetchSettings()
+ }, [])
+
+ const handleChange = (key: string, value: string) => {
+ setSettings(prev => ({ ...prev, [key]: value }))
+ }
+
+ const handleSave = async () => {
+ setSaving(true)
+ const res = await updateSettings(settings)
+ if (res.success) {
+ toast.success('Berhasil Disimpan', {
+ description: 'Pengaturan integrasi berhasil disimpan!',
+ duration: 3000
+ })
+ } else {
+ toast.error('Gagal', {
+ description: 'Gagal menyimpan pengaturan',
+ duration: 3000
+ })
+ }
+ setSaving(false)
+ }
+
+ if (loading) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+
Integrasi & Sharing
+
Konfigurasi endpoint API dan template pesan otomatis.
+
+
+
+ {/* Mattermost Section */}
+
+
+
+
Mattermost Webhook
+
+
Gunakan URL Incoming Webhook dari Mattermost untuk mengirim broadcast PO baru secara otomatis.
+
+
+
+ handleChange('MATTERMOST_WEBHOOK_URL', e.target.value)}
+ className="h-11 rounded-xl bg-white dark:bg-slate-950"
+ placeholder="https://mattermost.yourdomain.com/hooks/xxx"
+ />
+
+
+
+
+
+ {/* WhatsApp Section */}
+
+
+
+
WhatsApp Share Template
+
+
Atur template teks default saat user menekan tombol share ke WhatsApp.
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/app/(app)/settings/layout.tsx b/src/app/(app)/settings/layout.tsx
new file mode 100644
index 0000000..ec6ccbb
--- /dev/null
+++ b/src/app/(app)/settings/layout.tsx
@@ -0,0 +1,49 @@
+'use client'
+
+import Link from 'next/link'
+import { usePathname } from 'next/navigation'
+import { cn } from '@/lib/utils'
+import { User, Share2 } from 'lucide-react'
+
+export default function SettingsLayout({ children }: { children: React.ReactNode }) {
+ const pathname = usePathname()
+
+ const tabs = [
+ { name: 'User Management', href: '/settings/users', icon: User },
+ { name: 'Integrations & Share', href: '/settings/integrations', icon: Share2 },
+ ]
+
+ return (
+
+ {/* Settings Navigation Tabs */}
+
+
+ {tabs.map((tab) => {
+ const isActive = pathname === tab.href
+ const Icon = tab.icon
+ return (
+
+
+ {tab.name}
+
+ )
+ })}
+
+
+
+ {/* Settings Content */}
+
+ {children}
+
+
+ )
+}
diff --git a/src/app/(app)/users/page.tsx b/src/app/(app)/settings/users/page.tsx
similarity index 96%
rename from src/app/(app)/users/page.tsx
rename to src/app/(app)/settings/users/page.tsx
index d41036e..de3bcd6 100644
--- a/src/app/(app)/users/page.tsx
+++ b/src/app/(app)/settings/users/page.tsx
@@ -100,19 +100,18 @@ export default function UsersPage() {
}
return (
-
-
+
+
-
Manajemen Pengguna
-
Kelola data seluruh pengguna TitipIn.
+
Manajemen Pengguna
+
Kelola data seluruh pengguna TitipIn.
-
-
-
+
@@ -232,7 +231,6 @@ export default function UsersPage() {
)}
-
{/* CREATE MODAL */}