Enhance onboarding experience with new page and data; update global styles and configuration. Added onboarding page with multi-step form, integrated business types and plans from JSON data, and improved global CSS with new color variables and scrollbar styles. Updated Next.js configuration for remote image patterns.
This commit is contained in:
parent
d19d30efd2
commit
518103561e
@ -1,7 +1,14 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "images.unsplash.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@ -1,26 +1,163 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--destructive-foreground: #fafafa;
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--radius: var(--radius);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-geist-mono), ui-monospace, monospace;
|
||||
--font-heading: var(--font-sans);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--radius-sm: calc(var(--radius) * 0.6);
|
||||
--radius-md: calc(var(--radius) * 0.8);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) * 1.4);
|
||||
--radius-2xl: calc(var(--radius) * 1.8);
|
||||
--radius-3xl: calc(var(--radius) * 2.2);
|
||||
--radius-4xl: calc(var(--radius) * 2.6);
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: var(--font-poppins), var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border);
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--muted-foreground);
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border) transparent;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
html {
|
||||
@apply font-sans;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,9 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Geist, Geist_Mono, Poppins, Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const inter = Inter({ subsets: ['latin'], variable: '--font-sans' });
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
@ -12,9 +15,15 @@ const geistMono = Geist_Mono({
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const poppins = Poppins({
|
||||
variable: "--font-poppins",
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "500", "600", "700"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "Profitra - Kelola Bisnis Anda",
|
||||
description: "Platform manajemen bisnis dengan POS, Inventory, Laporan Penjualan, dan lainnya.",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@ -24,8 +33,8 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||
lang="id"
|
||||
className={cn("h-full", "antialiased", geistSans.variable, geistMono.variable, poppins.variable, "font-sans", inter.variable)}
|
||||
>
|
||||
<body className="min-h-full flex flex-col">{children}</body>
|
||||
</html>
|
||||
|
||||
439
src/app/onboarding/page.tsx
Normal file
439
src/app/onboarding/page.tsx
Normal file
@ -0,0 +1,439 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { ArrowRightIcon, CheckCircle, ChevronRightIcon } from "lucide-react";
|
||||
import onboardingData from "@/data/onboarding.json";
|
||||
|
||||
export default function OnboardingPage() {
|
||||
const [step, setStep] = useState(1);
|
||||
const [selectedBusiness, setSelectedBusiness] = useState<string | null>(null);
|
||||
const [selectedPlan, setSelectedPlan] = useState<string | null>(null);
|
||||
const [form, setForm] = useState({
|
||||
businessName: "",
|
||||
ownerName: "",
|
||||
email: "",
|
||||
whatsapp: "",
|
||||
address: "",
|
||||
currency: "IDR",
|
||||
timezone: "Asia/Jakarta",
|
||||
});
|
||||
|
||||
const handleNext = () => {
|
||||
if (step < 3) setStep(step + 1);
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
if (step > 1) setStep(step - 1);
|
||||
};
|
||||
|
||||
const handleFormChange = (name: string, value: string) => {
|
||||
setForm({ ...form, [name]: value });
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log("Onboarding complete:", { selectedBusiness, form, selectedPlan });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen lg:h-screen bg-background flex flex-col lg:flex-row lg:overflow-hidden">
|
||||
{/* Left Panel */}
|
||||
<div className="w-full lg:w-[60%] flex flex-col px-5 py-6 sm:px-6 sm:py-10 lg:px-20 lg:py-14 lg:h-full lg:overflow-hidden">
|
||||
{/* Logo */}
|
||||
<div className="mb-6 lg:mb-10 shrink-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center">
|
||||
<span className="text-primary-foreground font-bold text-sm">P</span>
|
||||
</div>
|
||||
<span className="text-xl font-semibold tracking-tight text-foreground">
|
||||
Profitra
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step Indicator */}
|
||||
<div className="mb-6 lg:mb-10 shrink-0">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{[1, 2, 3].map((s) => (
|
||||
<div key={s} className="flex items-center gap-1.5">
|
||||
<div
|
||||
className={`h-1.5 rounded-full transition-all ${step === s ? "w-6 bg-primary" : "w-2 bg-muted-foreground/30"
|
||||
}`}
|
||||
/>
|
||||
{step === s && (
|
||||
<span className="text-[11px] font-medium text-foreground whitespace-nowrap">
|
||||
{onboardingData.stepLabels[String(s) as keyof typeof onboardingData.stepLabels]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step Content */}
|
||||
<div className="flex-1 lg:overflow-y-auto lg:min-h-0 pb-4 lg:pb-0 px-0.5">
|
||||
{step === 1 && (
|
||||
<StepOne
|
||||
selected={selectedBusiness}
|
||||
onSelect={setSelectedBusiness}
|
||||
businessTypes={onboardingData.businessTypes}
|
||||
/>
|
||||
)}
|
||||
{step === 2 && (
|
||||
<StepTwo
|
||||
form={form}
|
||||
onChange={handleFormChange}
|
||||
currencies={onboardingData.currencies}
|
||||
timezones={onboardingData.timezones}
|
||||
/>
|
||||
)}
|
||||
{step === 3 && (
|
||||
<StepThree
|
||||
selected={selectedPlan}
|
||||
onSelect={setSelectedPlan}
|
||||
plans={onboardingData.plans}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 mt-6 lg:mt-10 pt-6 border-t border-border shrink-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onClick={handleBack}
|
||||
disabled={step <= 1}
|
||||
className="order-2 sm:order-1"
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
{step < 3 ? (
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={handleNext}
|
||||
disabled={step === 1 && !selectedBusiness}
|
||||
className="order-1 sm:order-2"
|
||||
>
|
||||
Selanjutnya
|
||||
<ChevronRightIcon className="ml-1 size-4" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={handleSubmit}
|
||||
className="order-1 sm:order-2"
|
||||
>
|
||||
Mulai Menggunakan Profitra
|
||||
<ArrowRightIcon className="ml-1 size-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{step === 1 && !selectedBusiness && (
|
||||
<p className="text-sm text-muted-foreground mt-3 shrink-0">
|
||||
Silakan pilih jenis bisnis terlebih dahulu.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Panel */}
|
||||
<div className="hidden lg:flex w-[40%] relative border-l border-border overflow-hidden p-3">
|
||||
<div className="relative w-full h-full rounded-xl overflow-hidden">
|
||||
<Image
|
||||
src={onboardingData.rightImages[String(step) as keyof typeof onboardingData.rightImages]}
|
||||
alt="Onboarding illustration"
|
||||
fill
|
||||
className="object-cover transition-all duration-500"
|
||||
sizes="40vw"
|
||||
priority
|
||||
/>
|
||||
<div className="absolute inset-0 bg-linear-to-t from-black/70 via-black/20 to-transparent" />
|
||||
<div className="absolute bottom-0 left-0 p-8 w-full">
|
||||
<h2 className="text-xl font-semibold text-white mb-2 leading-tight text-left">
|
||||
{onboardingData.rightPanelContent[String(step) as keyof typeof onboardingData.rightPanelContent].title}
|
||||
</h2>
|
||||
<p className="text-white/70 text-sm leading-relaxed max-w-xs text-left">
|
||||
{onboardingData.rightPanelContent[String(step) as keyof typeof onboardingData.rightPanelContent].description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StepOne({
|
||||
selected,
|
||||
onSelect,
|
||||
businessTypes,
|
||||
}: {
|
||||
selected: string | null;
|
||||
onSelect: (val: string) => void;
|
||||
businessTypes: { label: string; image: string }[];
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-foreground mb-2">
|
||||
Pilih Jenis Bisnis Anda
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm mb-8 leading-relaxed">
|
||||
Profitra akan menyesuaikan fitur dan pengaturan awal berdasarkan jenis
|
||||
usaha yang Anda jalankan.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2.5 pb-2 px-0.5 py-1">
|
||||
{businessTypes.map((type) => (
|
||||
<button
|
||||
key={type.label}
|
||||
onClick={() => onSelect(type.label)}
|
||||
className={`group relative aspect-4/3 rounded-lg overflow-hidden text-left transition-all duration-150 ${selected === type.label
|
||||
? "ring-2 ring-primary ring-offset-2 shadow-md"
|
||||
: "ring-1 ring-border hover:ring-foreground/30 hover:shadow-sm"
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={type.image}
|
||||
alt={type.label}
|
||||
fill
|
||||
className="object-cover transition-transform duration-300 group-hover:scale-105"
|
||||
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-linear-to-t from-black/60 via-black/20 to-transparent" />
|
||||
<div className="relative h-full flex flex-col justify-end p-3">
|
||||
<span className="text-sm font-medium text-white drop-shadow-sm">
|
||||
{type.label}
|
||||
</span>
|
||||
</div>
|
||||
{selected === type.label && (
|
||||
<div className="absolute top-2 right-2 w-5 h-5 rounded-full bg-primary flex items-center justify-center">
|
||||
<CheckCircle className="size-3 text-primary-foreground" strokeWidth={3} />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StepTwo({
|
||||
form,
|
||||
onChange,
|
||||
currencies,
|
||||
timezones,
|
||||
}: {
|
||||
form: {
|
||||
businessName: string;
|
||||
ownerName: string;
|
||||
email: string;
|
||||
whatsapp: string;
|
||||
address: string;
|
||||
currency: string;
|
||||
timezone: string;
|
||||
};
|
||||
onChange: (name: string, value: string) => void;
|
||||
currencies: { value: string; label: string }[];
|
||||
timezones: { value: string; label: string }[];
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-foreground mb-2">
|
||||
Lengkapi Informasi Bisnis
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm mb-8 leading-relaxed">
|
||||
Masukkan informasi dasar mengenai usaha Anda.
|
||||
</p>
|
||||
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Label>Nama Usaha</Label>
|
||||
<Input
|
||||
value={form.businessName}
|
||||
onChange={(e) => onChange("businessName", e.target.value)}
|
||||
placeholder="Contoh: Warung Berkah"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Nama Pemilik</Label>
|
||||
<Input
|
||||
value={form.ownerName}
|
||||
onChange={(e) => onChange("ownerName", e.target.value)}
|
||||
placeholder="Yoga Pangestu"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Email Bisnis</Label>
|
||||
<Input
|
||||
type="email"
|
||||
value={form.email}
|
||||
onChange={(e) => onChange("email", e.target.value)}
|
||||
placeholder="owner@email.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Nomor WhatsApp</Label>
|
||||
<Input
|
||||
type="tel"
|
||||
value={form.whatsapp}
|
||||
onChange={(e) => onChange("whatsapp", e.target.value)}
|
||||
placeholder="0812xxxxxxxx"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Alamat</Label>
|
||||
<Textarea
|
||||
value={form.address}
|
||||
onChange={(e) => onChange("address", e.target.value)}
|
||||
rows={3}
|
||||
placeholder="Alamat lengkap usaha Anda"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Mata Uang</Label>
|
||||
<Select value={form.currency} onValueChange={(val) => val && onChange("currency", val)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{currencies.map((currency) => (
|
||||
<SelectItem key={currency.value} value={currency.value}>{currency.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Zona Waktu</Label>
|
||||
<Select value={form.timezone} onValueChange={(val) => val && onChange("timezone", val)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{timezones.map((tz) => (
|
||||
<SelectItem key={tz.value} value={tz.value}>{tz.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StepThree({
|
||||
selected,
|
||||
onSelect,
|
||||
plans,
|
||||
}: {
|
||||
selected: string | null;
|
||||
onSelect: (val: string) => void;
|
||||
plans: {
|
||||
name: string;
|
||||
price: string;
|
||||
priceSub: string;
|
||||
description: string;
|
||||
features: string[];
|
||||
buttonLabel: string;
|
||||
popular: boolean;
|
||||
}[];
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-foreground mb-2">
|
||||
Pilih Paket Profitra
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm mb-8 leading-relaxed">
|
||||
Mulai gratis dan upgrade kapan saja sesuai perkembangan bisnis Anda.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 px-0.5 py-1">
|
||||
{plans.map((plan) => (
|
||||
<Card
|
||||
key={plan.name}
|
||||
onClick={() => onSelect(plan.name)}
|
||||
className={`relative cursor-pointer transition-all duration-150 ${selected === plan.name
|
||||
? "border-primary bg-primary/5 shadow-sm ring-1 ring-primary"
|
||||
: "hover:border-foreground/20 hover:bg-accent/50"
|
||||
}`}
|
||||
>
|
||||
{plan.popular && (
|
||||
<Badge className="absolute -top-2.5 left-4">
|
||||
Paling Populer
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<CardHeader>
|
||||
<CardTitle>{plan.name}</CardTitle>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="text-xl font-bold text-foreground">
|
||||
{plan.price}
|
||||
</span>
|
||||
{plan.priceSub && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{plan.priceSub}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{plan.description && (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{plan.description}
|
||||
</p>
|
||||
)}
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<ul className="space-y-2">
|
||||
{plan.features.map((feature) => (
|
||||
<li
|
||||
key={feature}
|
||||
className="flex items-center gap-2 text-sm text-muted-foreground"
|
||||
>
|
||||
<CheckCircle
|
||||
className="size-3.5 text-primary shrink-0"
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter>
|
||||
<Button
|
||||
variant={selected === plan.name ? "default" : "secondary"}
|
||||
className="w-full"
|
||||
size="lg"
|
||||
>
|
||||
{plan.buttonLabel}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
127
src/data/onboarding.json
Normal file
127
src/data/onboarding.json
Normal file
@ -0,0 +1,127 @@
|
||||
{
|
||||
"businessTypes": [
|
||||
{
|
||||
"label": "Restoran",
|
||||
"image": "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=400&h=300&fit=crop"
|
||||
},
|
||||
{
|
||||
"label": "Kafe",
|
||||
"image": "https://images.unsplash.com/photo-1501339847302-ac426a4a7cbb?w=400&h=300&fit=crop"
|
||||
},
|
||||
{
|
||||
"label": "Warung",
|
||||
"image": "https://images.unsplash.com/photo-1604719312566-8912e9227c6a?w=400&h=300&fit=crop"
|
||||
},
|
||||
{
|
||||
"label": "Minimarket",
|
||||
"image": "https://images.unsplash.com/photo-1578916171728-46686eac8d58?w=400&h=300&fit=crop"
|
||||
},
|
||||
{
|
||||
"label": "Toko Retail",
|
||||
"image": "https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=400&h=300&fit=crop"
|
||||
},
|
||||
{
|
||||
"label": "Jasa",
|
||||
"image": "https://images.unsplash.com/photo-1497366216548-37526070297c?w=400&h=300&fit=crop"
|
||||
},
|
||||
{
|
||||
"label": "Digital Product",
|
||||
"image": "https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=400&h=300&fit=crop"
|
||||
},
|
||||
{
|
||||
"label": "Distributor",
|
||||
"image": "https://images.unsplash.com/photo-1553413077-190dd305871c?w=400&h=300&fit=crop"
|
||||
},
|
||||
{
|
||||
"label": "Grosir",
|
||||
"image": "https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?w=400&h=300&fit=crop"
|
||||
},
|
||||
{
|
||||
"label": "Lainnya",
|
||||
"image": "https://images.unsplash.com/photo-1556761175-4b46a572b786?w=400&h=300&fit=crop"
|
||||
}
|
||||
],
|
||||
"plans": [
|
||||
{
|
||||
"name": "Starter",
|
||||
"price": "Gratis",
|
||||
"priceSub": "",
|
||||
"description": "Cocok untuk bisnis baru.",
|
||||
"features": [
|
||||
"POS",
|
||||
"Produk",
|
||||
"Pelanggan",
|
||||
"Laporan Dasar",
|
||||
"Maksimal 100 Produk"
|
||||
],
|
||||
"buttonLabel": "Mulai Gratis",
|
||||
"popular": false
|
||||
},
|
||||
{
|
||||
"name": "Business",
|
||||
"price": "Rp99.000",
|
||||
"priceSub": "/bulan",
|
||||
"description": "",
|
||||
"features": [
|
||||
"Semua Starter",
|
||||
"Inventory",
|
||||
"Multi Kasir",
|
||||
"Diskon",
|
||||
"Printer Struk",
|
||||
"Laporan Lengkap",
|
||||
"Export Excel"
|
||||
],
|
||||
"buttonLabel": "Pilih Paket",
|
||||
"popular": true
|
||||
},
|
||||
{
|
||||
"name": "Enterprise",
|
||||
"price": "Hubungi Kami",
|
||||
"priceSub": "",
|
||||
"description": "",
|
||||
"features": [
|
||||
"Semua Business",
|
||||
"Multi Cabang",
|
||||
"Multi Gudang",
|
||||
"Role Management",
|
||||
"API Integration",
|
||||
"Priority Support"
|
||||
],
|
||||
"buttonLabel": "Hubungi Sales",
|
||||
"popular": false
|
||||
}
|
||||
],
|
||||
"rightImages": {
|
||||
"1": "https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=800&h=1200&fit=crop",
|
||||
"2": "https://images.unsplash.com/photo-1556742111-a301076d9d18?w=800&h=1200&fit=crop",
|
||||
"3": "https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=800&h=1200&fit=crop"
|
||||
},
|
||||
"stepLabels": {
|
||||
"1": "Bisnis",
|
||||
"2": "Info",
|
||||
"3": "Paket"
|
||||
},
|
||||
"rightPanelContent": {
|
||||
"1": {
|
||||
"title": "Kelola bisnis lebih mudah bersama Profitra",
|
||||
"description": "Pilih jenis usaha Anda dan mulai kelola bisnis dengan cara yang lebih modern."
|
||||
},
|
||||
"2": {
|
||||
"title": "Semua data bisnis tersimpan dengan aman",
|
||||
"description": "Informasi bisnis Anda dilengkapi dengan enkripsi dan perlindungan data terbaik."
|
||||
},
|
||||
"3": {
|
||||
"title": "Siap meningkatkan bisnis Anda?",
|
||||
"description": "Dengan paket yang tepat, bisnis Anda akan berkembang lebih cepat."
|
||||
}
|
||||
},
|
||||
"currencies": [
|
||||
{ "value": "IDR", "label": "Rupiah (IDR)" },
|
||||
{ "value": "USD", "label": "Dollar (USD)" }
|
||||
],
|
||||
"timezones": [
|
||||
{ "value": "Asia/Jakarta", "label": "Asia/Jakarta" },
|
||||
{ "value": "Asia/Makassar", "label": "Asia/Makassar" },
|
||||
{ "value": "Asia/Jayapura", "label": "Asia/Jayapura" }
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user