feat: add Ina Trading portal flows and API integration

This commit is contained in:
Wira Basalamah
2026-04-24 05:19:05 +07:00
parent d98b4769f0
commit e08f2f9286
97 changed files with 18889 additions and 110 deletions

View File

@ -0,0 +1,295 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useLanguage } from "@/lib/i18n-context";
function getToken() {
if (typeof window === "undefined") return "";
return sessionStorage.getItem("token") || localStorage.getItem("token") || "";
}
function getPasswordStrength(password: string): number {
if (!password) return 0;
let score = 0;
if (password.length >= 8) score++;
if (password.length >= 12) score++;
if (/[A-Z]/.test(password) && /[a-z]/.test(password)) score++;
if (/\d/.test(password)) score++;
if (/[^A-Za-z0-9]/.test(password)) score++;
return Math.min(score, 4);
}
function getStrengthColor(score: number): string {
if (score === 1) return "#ba1a1a";
if (score === 2) return "#b7131a";
if (score === 3) return "#2d9648";
return "#1b7a3c";
}
// ─── Password Field ───────────────────────────────────────────────────────────
function PasswordField({
label,
value,
onChange,
}: {
label: string;
value: string;
onChange: (v: string) => void;
}) {
const [show, setShow] = useState(false);
return (
<div className="space-y-1.5">
<label className="block text-[10px] font-black uppercase tracking-[0.18em] text-on-surface-variant">
{label}
</label>
<div className="relative">
<input
type={show ? "text" : "password"}
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="••••••••••••"
className="w-full bg-transparent border-b-2 border-outline-variant/40 focus:border-primary pb-2 pt-1 pr-10 text-sm font-semibold text-on-surface placeholder:text-on-surface-variant/30 focus:outline-none transition-colors"
/>
<button
type="button"
tabIndex={-1}
onClick={() => setShow((s) => !s)}
className="absolute right-0 top-0 text-on-surface-variant/60 hover:text-on-surface transition-colors"
>
<span className="material-symbols-outlined text-[20px]">
{show ? "visibility_off" : "visibility"}
</span>
</button>
</div>
</div>
);
}
// ─── Strength Bar ─────────────────────────────────────────────────────────────
function StrengthBar({
password,
prefix,
labels,
}: {
password: string;
prefix: string;
labels: [string, string, string, string];
}) {
const score = getPasswordStrength(password);
const color = getStrengthColor(score);
const label = score > 0 ? labels[score - 1] : "";
if (!password) return null;
return (
<div className="space-y-1.5 mt-1">
<div className="flex gap-1.5">
{[1, 2, 3, 4].map((i) => (
<div
key={i}
className="h-1 flex-1 rounded-full transition-all duration-300"
style={{ backgroundColor: i <= score ? color : "#e4beb9" }}
/>
))}
</div>
<p className="text-[10px] font-black tracking-[0.15em]" style={{ color }}>
{prefix} {label}
</p>
</div>
);
}
// ─── Main Page ────────────────────────────────────────────────────────────────
export default function ChangePasswordPage() {
const router = useRouter();
const { t } = useLanguage();
const cp = t.dashboard.changePassword;
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [saving, setSaving] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
const strengthLabels: [string, string, string, string] = [
cp.strengthWeak,
cp.strengthModerate,
cp.strengthStrong,
cp.strengthVeryStrong,
];
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError("");
setSuccess(false);
if (!currentPassword || !newPassword || !confirmPassword) {
setError(cp.errorRequired);
return;
}
if (newPassword !== confirmPassword) {
setError(cp.errorMismatch);
return;
}
if (getPasswordStrength(newPassword) < 2) {
setError(cp.errorWeak);
return;
}
setSaving(true);
try {
const res = await fetch("/api/profile/change-password", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-auth-token": getToken(),
},
body: JSON.stringify({ oldPassword: currentPassword, newPassword }),
});
const data = await res.json();
if (!res.ok) {
throw new Error(data?.responseDesc || data?.message || cp.errorGeneric);
}
setSuccess(true);
setCurrentPassword("");
setNewPassword("");
setConfirmPassword("");
} catch (err) {
setError(err instanceof Error ? err.message : cp.errorGeneric);
} finally {
setSaving(false);
}
}
return (
<div className="w-full px-6 md:px-10 py-8 pb-10">
{/* Page Header */}
<div className="mb-8">
<h1 className="font-headline font-black text-4xl text-on-surface tracking-tight mb-1">
{cp.title}
</h1>
<nav className="flex items-center gap-2 text-[10px] uppercase tracking-widest font-bold text-outline">
<span>{cp.settings}</span>
<span>/</span>
<span className="text-primary">{cp.security}</span>
</nav>
</div>
{/* Card */}
<div className="flex rounded-2xl overflow-hidden shadow-lg border border-outline-variant/10 max-w-4xl">
{/* ── Left: Form ──────────────────────────────────────────────── */}
<div className="flex-1 bg-surface-container-lowest p-8 md:p-10 space-y-6">
{success && (
<div className="rounded-xl border border-tertiary/20 bg-tertiary/5 px-4 py-3 text-sm font-semibold text-tertiary flex items-center gap-2">
<span className="material-symbols-outlined text-[18px]" style={{ fontVariationSettings: "'FILL' 1" }}>
check_circle
</span>
{cp.successMessage}
</div>
)}
{error && (
<div className="rounded-xl border border-error/20 bg-error-container px-4 py-3 text-sm font-semibold text-on-error-container flex items-center gap-2">
<span className="material-symbols-outlined text-error text-[18px]">error</span>
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-7">
<PasswordField label={cp.currentPassword} value={currentPassword} onChange={setCurrentPassword} />
<div className="space-y-2">
<PasswordField label={cp.newPassword} value={newPassword} onChange={setNewPassword} />
<StrengthBar password={newPassword} prefix={cp.strengthPrefix} labels={strengthLabels} />
</div>
<PasswordField label={cp.confirmNewPassword} value={confirmPassword} onChange={setConfirmPassword} />
{confirmPassword && newPassword !== confirmPassword && (
<p className="text-[11px] font-semibold text-error -mt-4">{cp.mismatch}</p>
)}
<div className="flex items-center gap-5 pt-2">
<button
type="submit"
disabled={saving}
className="editorial-gradient text-white px-8 py-3 rounded-xl font-black text-sm uppercase tracking-[0.15em] shadow-md shadow-primary/20 hover:-translate-y-0.5 transition-all disabled:opacity-60 disabled:hover:translate-y-0 flex items-center gap-2"
>
{saving ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
{cp.saving}
</>
) : (
<>
<span className="material-symbols-outlined text-[18px]">lock_reset</span>
{cp.updatePassword}
</>
)}
</button>
<button
type="button"
onClick={() => router.back()}
disabled={saving}
className="text-sm font-black uppercase tracking-[0.15em] text-on-surface-variant hover:text-primary transition-colors disabled:opacity-40"
>
{cp.cancel}
</button>
</div>
</form>
</div>
{/* ── Right: Security Guidelines ──────────────────────────── */}
<div
className="w-72 shrink-0 flex flex-col justify-between p-8"
style={{ background: "linear-gradient(160deg, #1a1f2e 0%, #2e3132 100%)" }}
>
<div className="space-y-6">
<h2 className="font-headline font-black text-base uppercase tracking-[0.2em] text-white whitespace-pre-line">
{cp.guidelines.title}
</h2>
<div className="space-y-5">
{[
{ title: cp.guidelines.length, desc: cp.guidelines.lengthDesc },
{ title: cp.guidelines.mix, desc: cp.guidelines.mixDesc },
{ title: cp.guidelines.entropy, desc: cp.guidelines.entropyDesc },
].map((item) => (
<div key={item.title} className="flex gap-3">
<div className="mt-0.5 w-5 h-5 rounded-full bg-primary/80 flex items-center justify-center shrink-0">
<span
className="material-symbols-outlined text-white text-[13px]"
style={{ fontVariationSettings: "'FILL' 1" }}
>
location_on
</span>
</div>
<div>
<p className="text-[10px] font-black uppercase tracking-[0.15em] text-white mb-0.5">
{item.title}
</p>
<p className="text-[11px] text-white/60 font-medium leading-relaxed">
{item.desc}
</p>
</div>
</div>
))}
</div>
</div>
<div className="flex items-center gap-2 pt-6 border-t border-white/10 mt-6">
<span className="material-symbols-outlined text-white/40 text-[16px]">lock</span>
<p className="text-[10px] font-black uppercase tracking-[0.12em] text-white/40">
{cp.guidelines.lastChanged}
</p>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,56 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useLanguage } from "@/lib/i18n-context";
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const { t } = useLanguage();
const s = t.dashboard.settings;
const settingsNav = [
{ href: "/settings", icon: "account_circle", label: s.profile },
{ href: "/settings/change-password", icon: "shield", label: s.changePassword },
];
return (
<div className="flex min-h-screen">
{/* Settings sub-sidebar */}
<aside className="w-56 shrink-0 bg-surface-container-lowest border-r border-surface-container py-8 px-3 pt-12">
<p className="text-[9px] font-black uppercase tracking-[0.2em] text-outline px-3 mb-3">
{s.account}
</p>
<nav className="space-y-0.5">
{settingsNav.map((item) => {
const isActive = pathname === item.href;
return (
<Link
key={item.href}
href={item.href}
className={`flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-semibold transition-all ${
isActive
? "bg-primary/8 text-primary border-l-4 border-primary rounded-l-none"
: "text-on-surface-variant hover:bg-surface-container hover:translate-x-1 transition-transform"
}`}
>
<span
className="material-symbols-outlined text-[20px]"
style={isActive ? { fontVariationSettings: "'FILL' 1" } : {}}
>
{item.icon}
</span>
<span>{item.label}</span>
</Link>
);
})}
</nav>
</aside>
{/* Page content */}
<div className="flex-1 min-w-0">
{children}
</div>
</div>
);
}

View File

@ -0,0 +1,571 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { useLanguage } from "@/lib/i18n-context";
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "";
function getToken() {
if (typeof window === "undefined") return "";
return sessionStorage.getItem("token") || localStorage.getItem("token") || "";
}
interface SellerProfile {
biography: string;
sellerId: string;
sellerImageUrl: string | null;
storeImageUrl: string | null;
storeName: string;
}
// ─── Avatar Upload ─────────────────────────────────────────────────────────────
function AvatarUpload({
currentUrl,
previewUrl,
onUploaded,
editMode,
}: {
currentUrl: string | null;
previewUrl: string;
onUploaded: (fileId: string, objectUrl: string) => void;
editMode: boolean;
}) {
const inputRef = useRef<HTMLInputElement | null>(null);
const [uploading, setUploading] = useState(false);
const [error, setError] = useState("");
async function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
const objectUrl = URL.createObjectURL(file);
setUploading(true);
setError("");
try {
const fd = new FormData();
fd.append("file", file);
const res = await fetch("/api/upload", {
method: "POST",
headers: { "x-auth-token": getToken() },
body: fd,
});
const data = await res.json();
if (!res.ok) throw new Error(data?.responseDesc || data?.error || "Upload gagal");
const id = data?.data?.id || data?.data?.fileId || data?.fileId || "";
if (!id) throw new Error("File id tidak ditemukan");
onUploaded(id, objectUrl);
} catch (err) {
setError(err instanceof Error ? err.message : "Upload gagal");
} finally {
setUploading(false);
if (inputRef.current) inputRef.current.value = "";
}
}
const displayUrl = previewUrl || currentUrl;
return (
<div className="relative group">
<div className="w-48 h-48 rounded-full border-4 border-primary/10 mb-6 overflow-hidden bg-surface-container-low flex items-center justify-center">
{displayUrl ? (
<img
src={displayUrl}
alt="Seller Profile"
className="w-full h-full object-cover"
onError={(e) => {
(e.target as HTMLImageElement).style.display = "none";
}}
/>
) : (
<span
className="material-symbols-outlined text-6xl text-outline/30"
style={{ fontVariationSettings: "'FILL' 1" }}
>
person
</span>
)}
</div>
{editMode && (
<>
<button
type="button"
onClick={() => inputRef.current?.click()}
disabled={uploading}
className="absolute bottom-6 right-4 bg-primary text-white p-3 rounded-full shadow-xl hover:scale-110 transition-transform disabled:opacity-60"
>
{uploading ? (
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
) : (
<span className="material-symbols-outlined text-lg">photo_camera</span>
)}
</button>
{error && (
<p className="text-[10px] text-error text-center mt-1">{error}</p>
)}
<input
ref={inputRef}
type="file"
accept="image/*"
onChange={handleChange}
className="hidden"
/>
</>
)}
</div>
);
}
// ─── Store Photo Upload ────────────────────────────────────────────────────────
function StorePhotoUpload({
currentUrl,
previewUrl,
onUploaded,
onRemove,
editMode,
}: {
currentUrl: string | null;
previewUrl: string;
onUploaded: (fileId: string, objectUrl: string) => void;
onRemove: () => void;
editMode: boolean;
}) {
const inputRef = useRef<HTMLInputElement | null>(null);
const [uploading, setUploading] = useState(false);
const [error, setError] = useState("");
async function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
const objectUrl = URL.createObjectURL(file);
setUploading(true);
setError("");
try {
const fd = new FormData();
fd.append("file", file);
const res = await fetch("/api/upload", {
method: "POST",
headers: { "x-auth-token": getToken() },
body: fd,
});
const data = await res.json();
if (!res.ok) throw new Error(data?.responseDesc || data?.error || "Upload gagal");
const id = data?.data?.id || data?.data?.fileId || data?.fileId || "";
if (!id) throw new Error("File id tidak ditemukan");
onUploaded(id, objectUrl);
} catch (err) {
setError(err instanceof Error ? err.message : "Upload gagal");
} finally {
setUploading(false);
if (inputRef.current) inputRef.current.value = "";
}
}
const displayUrl = previewUrl || currentUrl;
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Preview */}
<div className="relative aspect-video bg-surface-container-lowest overflow-hidden group rounded-xl">
{displayUrl ? (
<>
<img
src={displayUrl}
alt="Store Photo"
className="w-full h-full object-cover grayscale hover:grayscale-0 transition-all duration-500"
onError={(e) => {
(e.target as HTMLImageElement).style.display = "none";
}}
/>
{editMode && (
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-4">
<button
type="button"
onClick={onRemove}
className="bg-primary/80 backdrop-blur-md p-3 rounded-xl text-white hover:bg-primary transition-colors"
>
<span className="material-symbols-outlined">delete</span>
</button>
</div>
)}
</>
) : (
<div className="w-full h-full flex flex-col items-center justify-center text-outline/30">
<span className="material-symbols-outlined text-5xl mb-2">image</span>
<p className="text-xs font-semibold">No store photo</p>
</div>
)}
</div>
{/* Upload Area */}
{editMode ? (
<button
type="button"
onClick={() => inputRef.current?.click()}
disabled={uploading}
className="border-2 border-dashed border-outline-variant bg-surface-container-lowest/50 flex flex-col items-center justify-center p-8 text-center hover:bg-white transition-all rounded-xl disabled:opacity-60"
>
{uploading ? (
<>
<div className="w-10 h-10 border-2 border-primary border-t-transparent rounded-full animate-spin mb-4" />
<p className="text-xs font-bold text-outline uppercase tracking-wider">Uploading...</p>
</>
) : (
<>
<span className="material-symbols-outlined text-4xl text-outline/30 mb-4">cloud_upload</span>
<p className="text-xs font-bold text-on-surface uppercase tracking-wider mb-2">Upload New Asset</p>
<p className="text-[10px] text-outline px-4">JPG, PNG or WEBP. Max 5MB. Recommended 1600×900px.</p>
</>
)}
{error && <p className="text-[10px] text-error mt-2">{error}</p>}
<input
ref={inputRef}
type="file"
accept="image/*"
onChange={handleChange}
className="hidden"
/>
</button>
) : (
<div className="border-2 border-dashed border-outline-variant/30 rounded-xl flex flex-col items-center justify-center p-8 text-center bg-surface-container-lowest/30">
<span className="material-symbols-outlined text-3xl text-outline/20 mb-2">cloud_upload</span>
<p className="text-[10px] text-outline/40 uppercase tracking-wider font-bold">Enable edit to upload</p>
</div>
)}
</div>
);
}
// ─── Main Page ─────────────────────────────────────────────────────────────────
export default function SettingsPage() {
const { t } = useLanguage();
const s = t.dashboard.settings;
const [profile, setProfile] = useState<SellerProfile | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const [editMode, setEditMode] = useState(false);
const [saving, setSaving] = useState(false);
const [saveError, setSaveError] = useState("");
const [saveSuccess, setSaveSuccess] = useState(false);
// Form state
const [storeName, setStoreName] = useState("");
const [biography, setBiography] = useState("");
const [sellerImageId, setSellerImageId] = useState("");
const [sellerImagePreview, setSellerImagePreview] = useState("");
const [storeImageId, setStoreImageId] = useState("");
const [storeImagePreview, setStoreImagePreview] = useState("");
useEffect(() => {
fetch("/api/seller/profile", {
headers: { "x-auth-token": getToken() },
})
.then((r) => r.json())
.then((j) => {
const d: SellerProfile = j?.data || j;
setProfile(d);
setStoreName(d.storeName || "");
setBiography(d.biography || "");
})
.catch(() => setError("Gagal memuat profil"))
.finally(() => setLoading(false));
}, []);
function handleEdit() {
setEditMode(true);
setSaveError("");
setSaveSuccess(false);
}
function handleCancel() {
if (!profile) return;
setStoreName(profile.storeName || "");
setBiography(profile.biography || "");
setSellerImageId("");
setSellerImagePreview("");
setStoreImageId("");
setStoreImagePreview("");
setEditMode(false);
setSaveError("");
}
async function handleSave() {
setSaving(true);
setSaveError("");
setSaveSuccess(false);
try {
const body: Record<string, unknown> = {
storeName,
storeBiography: biography,
};
if (sellerImageId) body.imageId = sellerImageId;
if (storeImageId) body.storeImageId = storeImageId;
const res = await fetch("/api/seller/profile", {
method: "PUT",
headers: {
"Content-Type": "application/json",
"x-auth-token": getToken(),
},
body: JSON.stringify(body),
});
const result = await res.json();
if (!res.ok) throw new Error(result?.responseDesc || "Gagal menyimpan profil");
// Update local profile state
setProfile((prev) => prev ? {
...prev,
storeName,
biography,
sellerImageUrl: sellerImagePreview || prev.sellerImageUrl,
storeImageUrl: storeImagePreview || prev.storeImageUrl,
} : prev);
setSaveSuccess(true);
setEditMode(false);
setSellerImageId("");
setSellerImagePreview("");
setStoreImageId("");
setStoreImagePreview("");
} catch (err) {
setSaveError(err instanceof Error ? err.message : s.errorSave);
} finally {
setSaving(false);
}
}
if (loading) {
return (
<div className="w-full px-6 md:px-10 py-8 flex items-center justify-center min-h-[60vh]">
<div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
<p className="text-sm font-semibold text-on-surface-variant">{s.loading}</p>
</div>
</div>
);
}
if (error || !profile) {
return (
<div className="w-full px-6 md:px-10 py-8">
<div className="rounded-xl border border-error/20 bg-error-container p-6 text-sm font-semibold text-on-error-container">
{error || s.profileNotFound}
</div>
</div>
);
}
return (
<div className="w-full px-6 md:px-10 py-8 pb-10 space-y-10">
{/* Page Header */}
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
<div>
<h1 className="font-headline font-black text-4xl md:text-5xl text-on-surface tracking-tight mb-2">
{s.sellerProfile}
</h1>
<nav className="flex items-center gap-2 text-[10px] uppercase tracking-widest font-bold text-outline">
<span>{s.management}</span>
<span>/</span>
<span className="text-primary">{s.profileConfiguration}</span>
</nav>
</div>
<div className="flex items-center gap-3">
{editMode ? (
<>
<button
type="button"
onClick={handleCancel}
disabled={saving}
className="px-6 py-3 text-sm font-black text-on-surface-variant border-2 border-outline-variant/30 rounded-xl hover:bg-surface-container-low transition-all disabled:opacity-40"
>
{t.common.cancel}
</button>
<button
type="button"
onClick={handleSave}
disabled={saving}
className="px-8 py-3 editorial-gradient text-white font-bold rounded-xl shadow-lg shadow-primary/20 hover:-translate-y-0.5 active:translate-y-0 transition-all disabled:opacity-60 flex items-center gap-2"
>
{saving ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
{s.saving}
</>
) : (
<>
<span className="material-symbols-outlined text-[18px]">save</span>
{s.saveChanges}
</>
)}
</button>
</>
) : (
<button
type="button"
onClick={handleEdit}
className="px-8 py-3 editorial-gradient text-white font-bold rounded-xl shadow-lg shadow-primary/20 hover:-translate-y-0.5 active:translate-y-0 transition-all flex items-center gap-2"
>
<span className="material-symbols-outlined text-[18px]">edit</span>
{s.editProfile}
</button>
)}
</div>
</div>
{/* Alerts */}
{saveSuccess && (
<div className="rounded-xl border border-tertiary/20 bg-tertiary/5 px-5 py-4 text-sm font-semibold text-tertiary flex items-center gap-3">
<span className="material-symbols-outlined text-[20px]" style={{ fontVariationSettings: "'FILL' 1" }}>check_circle</span>
{s.successUpdate}
</div>
)}
{saveError && (
<div className="rounded-xl border border-error/20 bg-error-container px-5 py-4 text-sm font-semibold text-on-error-container flex items-center gap-3">
<span className="material-symbols-outlined text-error text-[20px]">error</span>
{saveError}
</div>
)}
{/* Bento Grid */}
<div className="grid grid-cols-12 gap-6">
{/* ── Left: Profile Identity ─────────────────────────────────────── */}
<section className="col-span-12 lg:col-span-4 bg-surface-container-low p-8 rounded-2xl border border-outline-variant/10 shadow-sm flex flex-col items-center text-center">
<AvatarUpload
currentUrl={profile.sellerImageUrl}
previewUrl={sellerImagePreview}
onUploaded={(id, url) => {
setSellerImageId(id);
setSellerImagePreview(url);
}}
editMode={editMode}
/>
<h3 className="font-headline font-extrabold text-2xl text-on-surface mb-1">
{storeName || profile.storeName || "—"}
</h3>
<p className="text-sm text-on-surface-variant font-medium mt-2 leading-relaxed max-w-xs">
{biography || profile.biography || ""}
</p>
{/* Seller ID badge */}
<div className="mt-6 w-full p-4 rounded-xl bg-surface-container border border-outline-variant/10">
<p className="text-[9px] font-black uppercase tracking-[0.2em] text-outline mb-1">{s.sellerId}</p>
<p className="text-xs font-mono font-bold text-on-surface break-all">{profile.sellerId}</p>
</div>
</section>
{/* ── Right: Forms ───────────────────────────────────────────────── */}
<section className="col-span-12 lg:col-span-8 space-y-6">
{/* Store Information */}
<div className="bg-surface-container-low p-8 md:p-10 rounded-2xl border border-outline-variant/10 shadow-sm relative overflow-hidden">
<div className="absolute top-0 right-0 w-40 h-40 bg-primary/5 rounded-bl-full pointer-events-none" />
<div className="relative z-10 space-y-8">
<div className="flex items-center gap-4">
<div className="w-10 h-10 bg-primary text-white flex items-center justify-center rounded-xl shadow-md shadow-primary/20">
<span className="material-symbols-outlined">edit_note</span>
</div>
<h2 className="font-headline font-extrabold text-2xl tracking-tight text-on-surface">
{s.storeInfo}
</h2>
</div>
<div className="space-y-6">
<div>
<label className="block text-[10px] font-black text-outline uppercase tracking-[0.2em] mb-3">
{s.storeName}
</label>
{editMode ? (
<input
value={storeName}
onChange={(e) => setStoreName(e.target.value)}
placeholder="Enter formal store name"
className="w-full bg-surface-container-lowest border-none rounded-xl px-6 py-4 text-on-surface font-bold text-lg shadow-sm focus:ring-2 focus:ring-primary/20 focus:outline-none transition-all"
/>
) : (
<p className="w-full bg-surface-container-lowest rounded-xl px-6 py-4 text-on-surface font-bold text-lg shadow-sm">
{profile.storeName || <span className="text-outline font-medium"></span>}
</p>
)}
</div>
<div>
<label className="block text-[10px] font-black text-outline uppercase tracking-[0.2em] mb-3">
{s.storeBiography}
</label>
{editMode ? (
<textarea
value={biography}
onChange={(e) => setBiography(e.target.value)}
rows={4}
placeholder="Describe your store's mission and value proposition"
className="w-full bg-surface-container-lowest border-none rounded-xl px-6 py-4 text-on-surface font-medium text-sm leading-relaxed shadow-sm focus:ring-2 focus:ring-primary/20 focus:outline-none resize-none transition-all"
/>
) : (
<p className="w-full bg-surface-container-lowest rounded-xl px-6 py-4 text-on-surface font-medium text-sm leading-relaxed shadow-sm min-h-[7rem] whitespace-pre-line">
{profile.biography || <span className="text-outline"></span>}
</p>
)}
</div>
</div>
</div>
</div>
{/* Store Photo */}
<div className="bg-surface-container-low p-8 md:p-10 rounded-2xl border border-outline-variant/10 shadow-sm">
<div className="flex items-center gap-4 mb-8">
<div className="w-10 h-10 bg-primary text-white flex items-center justify-center rounded-xl shadow-md shadow-primary/20">
<span className="material-symbols-outlined">add_a_photo</span>
</div>
<h2 className="font-headline font-extrabold text-2xl tracking-tight text-on-surface">
{s.storePhoto}
</h2>
</div>
<StorePhotoUpload
currentUrl={profile.storeImageUrl}
previewUrl={storeImagePreview}
onUploaded={(id, url) => {
setStoreImageId(id);
setStoreImagePreview(url);
}}
onRemove={() => {
setStoreImageId("");
setStoreImagePreview("");
setProfile((prev) => prev ? { ...prev, storeImageUrl: null } : prev);
}}
editMode={editMode}
/>
</div>
{/* Action Footer */}
<div className="flex items-center justify-between px-8 py-6 bg-surface-container-highest/20 border-l-4 border-primary rounded-r-2xl">
<div className="flex items-center gap-4">
<span
className="material-symbols-outlined text-primary text-[22px] shrink-0"
style={{ fontVariationSettings: "'FILL' 1" }}
>
info
</span>
<p className="text-[11px] font-medium text-on-surface-variant max-w-sm leading-relaxed">
{s.complianceNote}
</p>
</div>
<button
type="button"
className="flex items-center gap-2 text-primary font-black uppercase text-[10px] tracking-[0.2em] hover:translate-x-1 transition-transform flex-shrink-0 ml-4"
>
{s.viewStorefront}
<span className="material-symbols-outlined text-sm">arrow_forward</span>
</button>
</div>
</section>
</div>
</div>
);
}