"use client" import { useState, useEffect, useCallback } from "react" import { useSession } from "next-auth/react" import { User, Mail, Building, Save, Loader2, CheckCircle2, AlertCircle, Bell, BellOff, KeyRound, ChevronDown, } from "lucide-react" import { usePushNotifications } from "@/hooks/usePushNotifications" import { accountUpdateSchema, isStrongPassword } from "@/lib/validation" import { cn } from "@/lib/utils" type ActiveStoreMeta = { id: string businessName: string shopifyDomain: string } const SHOPIFY_TOKEN_MAX = 4000 export default function AccountSettingsPage() { const { data: session, update } = useSession() const { permission, isSubscribed, loading: pushLoading, subscribe, unsubscribe } = usePushNotifications() const [pushActionLoading, setPushActionLoading] = useState(false) const [loading, setLoading] = useState(false) const [success, setSuccess] = useState("") const [error, setError] = useState("") const [formData, setFormData] = useState({ businessName: "", currentPassword: "", newPassword: "", confirmPassword: "" }) const [activeStoreMeta, setActiveStoreMeta] = useState(null) const [shopifyTokenOpen, setShopifyTokenOpen] = useState(false) const [newShopifyToken, setNewShopifyToken] = useState("") const [tokenLoading, setTokenLoading] = useState(false) const [tokenSuccess, setTokenSuccess] = useState("") const [tokenError, setTokenError] = useState("") const refreshActiveStore = useCallback(async () => { try { const res = await fetch("/api/stores") if (!res.ok) return const data = await res.json() const activeStore = data.stores?.find((s: { isActive: boolean }) => s.isActive) as | { id?: string businessName?: string shopifyDomain?: string onboardingComplete?: boolean } | undefined if (activeStore?.businessName) { setFormData((prev) => ({ ...prev, businessName: activeStore.businessName || "" })) } else if (session?.user?.name) { setFormData((prev) => ({ ...prev, businessName: session.user.name || "" })) } if ( activeStore?.id && activeStore.shopifyDomain && activeStore.onboardingComplete ) { setActiveStoreMeta({ id: activeStore.id, businessName: activeStore.businessName || "Store", shopifyDomain: activeStore.shopifyDomain, }) } else { setActiveStoreMeta(null) } } catch { // Silent fail } }, [session?.user?.name]) useEffect(() => { void refreshActiveStore() }, [refreshActiveStore]) const toggleShopifyTokenPanel = () => { const next = !shopifyTokenOpen setShopifyTokenOpen(next) setTokenError("") setTokenSuccess("") if (next) void refreshActiveStore() } const saveShopifyAccessToken = async () => { const trimmed = newShopifyToken.trim() if (!trimmed) { setTokenError("Paste your new Admin API access token.") return } if (!activeStoreMeta) { setTokenError("No connected Shopify store found for your active store.") return } setTokenLoading(true) setTokenError("") setTokenSuccess("") try { const res = await fetch( `/api/stores/${encodeURIComponent(activeStoreMeta.id)}/shopify-access-token`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ accessToken: trimmed }), }, ) const data = await res.json().catch(() => ({})) if (!res.ok) { setTokenError( typeof data?.message === "string" ? data.message : "Could not update token. Try again.", ) return } setTokenSuccess("Token saved and verified with Shopify.") setNewShopifyToken("") } catch { setTokenError("Something went wrong. Try again.") } finally { setTokenLoading(false) } } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setLoading(true) setError("") setSuccess("") try { // Validate password change if provided if (formData.newPassword || formData.currentPassword) { if (!formData.currentPassword) { setError("Current password is required to change password") setLoading(false) return } if (formData.newPassword !== formData.confirmPassword) { setError("New passwords do not match") setLoading(false) return } if (!isStrongPassword(formData.newPassword)) { setError("New password must be at least 8 characters and include upper, lower, number and special character") setLoading(false) return } } const updateData: { businessName?: string; currentPassword?: string; newPassword?: string } = {} if (formData.businessName) { updateData.businessName = formData.businessName } if (formData.newPassword) { updateData.currentPassword = formData.currentPassword updateData.newPassword = formData.newPassword } const bodyCheck = accountUpdateSchema.safeParse(updateData) if (!bodyCheck.success) { setError(bodyCheck.error.issues[0]?.message ?? "Invalid input") setLoading(false) return } const res = await fetch("/api/settings/account", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(bodyCheck.data), }) if (!res.ok) { const data = await res.json() setError(data.message || "Failed to update account") return } setSuccess("Account updated successfully") setFormData(prev => ({ ...prev, currentPassword: "", newPassword: "", confirmPassword: "" })) // Update session await update() } catch { setError("An error occurred. Please try again.") } finally { setLoading(false) } } return (

Account Information

Update your business name and password.

{success && (
{success}
)} {error && (
{error}
)}
{/* Email (Read-only) */}

Email cannot be changed

{/* Business Name */}
setFormData(prev => ({ ...prev, businessName: e.target.value }))} placeholder="Enter your business name" maxLength={200} className="w-full px-4 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder:text-slate-500 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50" />
{/* Password Change Section */}
setFormData(prev => ({ ...prev, currentPassword: e.target.value }))} placeholder="Enter current password" maxLength={128} className="w-full px-4 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder:text-slate-500 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50" />
setFormData(prev => ({ ...prev, newPassword: e.target.value }))} placeholder="Enter new password" maxLength={128} className="w-full px-4 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder:text-slate-500 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50" />
setFormData(prev => ({ ...prev, confirmPassword: e.target.value }))} placeholder="Confirm new password" maxLength={128} className="w-full px-4 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder:text-slate-500 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50" />
{/* Shopify access token (collapsed by default — reinstall / new scopes) */} {activeStoreMeta ? (
{shopifyTokenOpen ? (

Reinstalled your app in Shopify or added API permissions? Shopify issues a new Admin API access token. Paste it here—your store domain stays the same; we verify the token with Shopify before saving. Supplier fulfillment sync needs fulfillment scopes; write_orders is required for order tags and the hold metafield. Native{" "} On hold in Shopify admin also needs{" "} write_merchant_managed_fulfillment_orders {" "} (or write_third_party_fulfillment_orders{" "} if applicable).

{tokenSuccess ? (
{tokenSuccess}
) : null} {tokenError ? (
{tokenError}
) : null}
setNewShopifyToken( e.target.value.slice(0, SHOPIFY_TOKEN_MAX), ) } placeholder="shpat_…" className="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-2.5 font-mono text-sm text-white placeholder:text-slate-600 focus:border-indigo-500/50 focus:outline-none focus:ring-2 focus:ring-indigo-500/20" />
) : null}
) : null} {/* Push notifications */}

Push notifications

Get notified in your browser when you receive new messages and updates. When you click Enable, your browser will show a permission prompt—choose Allow.

{pushLoading ? (
Checking notification status...
) : !("Notification" in window) || !("serviceWorker" in navigator) ? (

Push notifications are not supported in this browser.

) : permission === "denied" ? (

Notifications are blocked. Enable them in your browser settings for this site, then refresh the page.

) : isSubscribed ? (
Notifications enabled
) : ( )}
) }