fixed UI and MFA

This commit is contained in:
root 2026-02-14 16:02:43 +05:00
parent 9d2ca7f592
commit 59d90ed807
22 changed files with 1309 additions and 1092 deletions

View File

@ -1,5 +1,5 @@
import { AdminSidebar } from "@/components/AdminSidebar" import { AdminSidebar } from "@/components/AdminSidebar"
import { MobileSidebar } from "@/components/MobileSidebar" import { MobileAdminSidebar } from "@/components/MobileAdminSidebar"
import UserAccountMenu from "@/components/UserAccountMenu" import UserAccountMenu from "@/components/UserAccountMenu"
import NotificationBell from "@/components/NotificationBell" import NotificationBell from "@/components/NotificationBell"
import PageTransition from "@/components/PageTransition" import PageTransition from "@/components/PageTransition"
@ -41,7 +41,7 @@ export default async function AdminLayout({
<header className="h-16 px-4 md:px-8 border-b border-white/10 bg-slate-900/50 backdrop-blur-xl flex items-center justify-between flex-shrink-0 z-50"> <header className="h-16 px-4 md:px-8 border-b border-white/10 bg-slate-900/50 backdrop-blur-xl flex items-center justify-between flex-shrink-0 z-50">
<div className="flex items-center gap-2 md:gap-4 flex-1 min-w-0"> <div className="flex items-center gap-2 md:gap-4 flex-1 min-w-0">
{/* Reuse vendor mobile sidebar trigger for consistency */} {/* Reuse vendor mobile sidebar trigger for consistency */}
<MobileSidebar /> <MobileAdminSidebar />
<div className="flex flex-col"> <div className="flex flex-col">
<h2 className="text-xs md:text-sm font-semibold text-slate-300 uppercase tracking-wider"> <h2 className="text-xs md:text-sm font-semibold text-slate-300 uppercase tracking-wider">
Admin Portal Admin Portal

View File

@ -1,6 +1,8 @@
"use client" "use client"
import { useEffect, useMemo, useState } from "react" import { useState, useEffect, useMemo } from "react"
import { createPortal } from "react-dom"
import { motion, AnimatePresence } from "framer-motion"
import { FileText, Search, Plus, Loader2, Edit, Trash2 } from "lucide-react" import { FileText, Search, Plus, Loader2, Edit, Trash2 } from "lucide-react"
import { renderTemplate } from "@/lib/templates/simpleRenderer" import { renderTemplate } from "@/lib/templates/simpleRenderer"
import { useTheme } from "@/components/ThemeProvider" import { useTheme } from "@/components/ThemeProvider"
@ -52,9 +54,14 @@ export default function EmailTemplatesPage() {
const [editing, setEditing] = useState<Template | null>(null) const [editing, setEditing] = useState<Template | null>(null)
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [mounted, setMounted] = useState(false)
const { theme } = useTheme() const { theme } = useTheme()
const isLight = theme === "light" const isLight = theme === "light"
useEffect(() => {
setMounted(true)
}, [])
const loadTemplates = async () => { const loadTemplates = async () => {
try { try {
setLoading(true) setLoading(true)
@ -186,8 +193,10 @@ export default function EmailTemplatesPage() {
return ( return (
<div className="h-full flex flex-col space-y-4 md:space-y-6 mt-4 md:mt-6"> <div className="h-full flex flex-col space-y-4 md:space-y-6 mt-4 md:mt-6">
<div className="bg-white/5 backdrop-blur-xl rounded-xl md:rounded-2xl border border-white/10 overflow-hidden flex flex-col flex-1 min-h-0"> <div className={`backdrop-blur-xl rounded-xl md:rounded-2xl border overflow-hidden flex flex-col flex-1 min-h-0 ${isLight ? "bg-white border-slate-200 shadow-sm" : "bg-white/5 border-white/10"
<div className="p-4 md:p-6 border-b border-white/10 flex flex-col sm:flex-row gap-3 md:gap-4 justify-between bg-white/5 flex-shrink-0"> }`}>
<div className={`p-4 md:p-6 border-b flex flex-col sm:flex-row gap-3 md:gap-4 justify-between flex-shrink-0 ${isLight ? "border-slate-100 bg-slate-50/50" : "border-white/10 bg-white/5"
}`}>
<div className="relative flex-1 min-w-0"> <div className="relative flex-1 min-w-0">
<Search className="absolute left-3 md:left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" /> <Search className="absolute left-3 md:left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
<input <input
@ -195,7 +204,10 @@ export default function EmailTemplatesPage() {
placeholder="Search templates by name, intent, or mood..." placeholder="Search templates by name, intent, or mood..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="w-full h-10 md:h-11 pl-10 md:pl-11 pr-4 bg-white/5 border border-white/10 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50 text-white placeholder:text-slate-500 text-sm font-medium transition-all" className={`w-full h-10 md:h-11 pl-10 md:pl-11 pr-4 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500/30 transition-all text-sm font-medium ${isLight
? "bg-white border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500"
: "bg-white/5 border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500/50"
}`}
/> />
</div> </div>
<button <button
@ -232,26 +244,31 @@ export default function EmailTemplatesPage() {
key={template.id} key={template.id}
type="button" type="button"
onClick={() => startEditTemplate(template)} onClick={() => startEditTemplate(template)}
className="bg-white/5 backdrop-blur-xl p-4 md:p-6 rounded-xl md:rounded-2xl border border-white/10 hover:bg-white/[0.07] hover:border-indigo-500/30 transition-all group text-left" className={`backdrop-blur-xl p-4 md:p-6 rounded-xl md:rounded-2xl border transition-all group text-left ${isLight
? "bg-white border-slate-200 hover:bg-slate-50 hover:border-indigo-300 shadow-sm"
: "bg-white/5 border-white/10 hover:bg-white/[0.07] hover:border-indigo-500/30"
}`}
> >
<div className="flex items-start justify-between mb-4"> <div className="flex items-start justify-between mb-4">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h3 className="font-bold text-base md:text-lg text-white truncate group-hover:text-indigo-400 transition-colors"> <h3 className={`font-bold text-base md:text-lg truncate group-hover:text-indigo-500 transition-colors ${isLight ? "text-slate-900" : "text-white"
}`}>
{template.name} {template.name}
</h3> </h3>
<p className="text-xs text-slate-400 mt-1 truncate"> <p className={`text-xs mt-1 truncate ${isLight ? "text-slate-500" : "text-slate-400"}`}>
Intent: {template.intent} • Mood:{" "} Intent: {template.intent} • Mood:{" "}
{template.mood} {template.mood}
</p> </p>
</div> </div>
</div> </div>
<p className="text-xs text-slate-300 line-clamp-3"> <p className={`text-xs line-clamp-3 ${isLight ? "text-slate-600" : "text-slate-300"}`}>
{template.description || {template.description ||
template.body.split("\n").slice(0, 3).join(" ")} template.body.split("\n").slice(0, 3).join(" ")}
</p> </p>
<div className="flex items-center justify-between pt-4 border-t border-white/10 mt-4"> <div className={`flex items-center justify-between pt-4 border-t mt-4 ${isLight ? "border-slate-100" : "border-white/10"
}`}>
<span className="text-[10px] text-slate-500 font-medium"> <span className="text-[10px] text-slate-500 font-medium">
Updated{" "} Updated{" "}
{new Date( {new Date(
@ -283,230 +300,272 @@ export default function EmailTemplatesPage() {
</div> </div>
</div> </div>
{editing && ( <AnimatePresence>
<div {editing && mounted && createPortal(
className={`fixed inset-0 z-40 flex items-start justify-center px-4 pt-20 md:pt-24 ${ <div className="fixed inset-0 z-[100] flex items-center justify-center pointer-events-none p-4">
isLight ? "bg-slate-900/10" : "bg-slate-950/60" {/* Backdrop */}
}`} <motion.div
> initial={{ opacity: 0 }}
<div animate={{ opacity: 1 }}
className={`flex w-full max-h-[90vh] max-w-3xl flex-col rounded-2xl border shadow-2xl ${ exit={{ opacity: 0 }}
isLight onClick={() => !saving && setEditing(null)}
? "bg-white text-slate-900 border-slate-200" className={`absolute inset-0 pointer-events-auto backdrop-blur-sm ${isLight ? "bg-black/40" : "bg-black/60"
: "bg-slate-950 text-slate-50 border-white/10"
}`}
>
<div
className={`flex items-center justify-between px-5 py-4 border-b ${
isLight ? "border-slate-200" : "border-white/10"
}`}
>
<div>
<h2 className="text-lg font-bold">
{editing.id ? "Edit template" : "New template"}
</h2>
<p
className={`mt-0.5 text-xs ${
isLight ? "text-slate-500" : "text-slate-400"
}`}
>
Use {"{{variable}}"} placeholders to personalize replies.
</p>
</div>
<button
type="button"
onClick={() => setEditing(null)}
className={`text-sm ${
isLight
? "text-slate-500 hover:text-slate-900"
: "text-slate-400 hover:text-white"
}`} }`}
/>
{/* Modal */}
<motion.div
initial={{ scale: 0.95, opacity: 0, y: 20 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
exit={{ scale: 0.95, opacity: 0, y: 20 }}
className={`relative flex w-full max-h-[90vh] max-w-3xl flex-col rounded-2xl border shadow-2xl pointer-events-auto ${isLight
? "bg-white text-slate-900 border-slate-200"
: "bg-slate-900 text-slate-50 border-white/10"
}`}
>
<div
className={`flex items-center justify-between px-6 py-5 border-b ${isLight ? "border-slate-100" : "border-white/10"
}`}
> >
Close <div>
</button> <h2 className="text-xl font-bold">
</div> {editing.id ? "Edit template" : "New template"}
</h2>
<div className="flex-1 overflow-y-auto px-5 py-4 space-y-4"> <p
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> className={`mt-1 text-sm ${isLight ? "text-slate-500" : "text-slate-400"
<div className="space-y-3"> }`}
<div> >
<label className="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1"> Use {"{{variable}}"} placeholders to personalize replies.
Name
</label>
<input
type="text"
value={editing.name}
onChange={(e) =>
setEditing((prev) =>
prev
? { ...prev, name: e.target.value }
: prev,
)
}
className="w-full h-9 px-3 rounded-lg bg-slate-50 dark:bg-slate-900/60 border border-slate-200 dark:border-white/10 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
/>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label className="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">
Intent
</label>
<select
value={editing.intent}
onChange={(e) =>
setEditing((prev) =>
prev
? {
...prev,
intent: e.target.value,
}
: prev,
)
}
className="w-full h-9 px-3 rounded-lg bg-slate-50 dark:bg-slate-900/60 border border-slate-200 dark:border-white/10 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
>
{INTENT_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">
Mood
</label>
<select
value={editing.mood}
onChange={(e) =>
setEditing((prev) =>
prev
? {
...prev,
mood: e.target.value,
}
: prev,
)
}
className="w-full h-9 px-3 rounded-lg bg-slate-50 dark:bg-slate-900/60 border border-slate-200 dark:border-white/10 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
>
{MOOD_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">
Description (optional)
</label>
<input
type="text"
value={editing.description || ""}
onChange={(e) =>
setEditing((prev) =>
prev
? {
...prev,
description: e.target.value,
}
: prev,
)
}
className="w-full h-9 px-3 rounded-lg bg-slate-50 dark:bg-slate-900/60 border border-slate-200 dark:border-white/10 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
placeholder="Short summary for your team"
/>
</div>
</div>
<div className="space-y-2 text-xs text-slate-600 dark:text-slate-400">
<p className="font-semibold text-slate-800 dark:text-slate-200">
Available variables
</p> </p>
<ul className="space-y-1">
<li>
<code className="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-900/80 rounded border border-slate-200 dark:border-white/10 text-[11px]">
{"{{customer_name}}"}
</code>{" "}
– Customer’s name
</li>
<li>
<code className="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-900/80 rounded border border-slate-200 dark:border-white/10 text-[11px]">
{"{{customer_email}}"}
</code>{" "}
– Customer’s email
</li>
<li>
<code className="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-900/80 rounded border border-slate-200 dark:border-white/10 text-[11px]">
{"{{store_name}}"}
</code>{" "}
– Store name
</li>
<li>
<code className="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-900/80 rounded border border-slate-200 dark:border-white/10 text-[11px]">
{"{{ticket_summary}}"}
</code>{" "}
– Short summary of the customer’s email
</li>
</ul>
</div> </div>
</div>
<div>
<label className="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">
Template body
</label>
<textarea
value={editing.body}
onChange={(e) =>
setEditing((prev) =>
prev ? { ...prev, body: e.target.value } : prev,
)
}
rows={8}
className="w-full rounded-lg bg-slate-50 dark:bg-slate-900/60 border border-slate-200 dark:border-white/10 text-xs text-slate-900 dark:text-slate-100 font-mono p-3 focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
/>
</div>
<div>
<p className="text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">
Preview with example data
</p>
<div className="rounded-lg border border-slate-200 dark:border-white/10 bg-slate-50 dark:bg-slate-900/80 p-4 text-xs text-slate-900 dark:text-slate-100 whitespace-pre-wrap">
{currentPreview || "Preview will appear here."}
</div>
</div>
</div>
<div className="px-5 py-3 border-t border-slate-200 dark:border-white/10 flex items-center justify-between">
<p className="text-[11px] text-slate-500 dark:text-slate-500">
Templates use simple {"{{variable}}"} placeholders only.
</p>
<div className="flex items-center gap-2">
<button <button
type="button" type="button"
onClick={() => setEditing(null)} onClick={() => setEditing(null)}
className="h-8 px-3 rounded-lg border border-slate-200 dark:border-white/10 text-xs text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-white/10" className={`w-10 h-10 rounded-xl flex items-center justify-center transition-colors ${isLight
? "bg-slate-100 hover:bg-slate-200 text-slate-600"
: "bg-white/10 hover:bg-white/15 text-slate-300"
}`}
> >
Cancel <Plus className="w-5 h-5 rotate-45" />
</button>
<button
type="button"
disabled={saving}
onClick={() => void handleSave()}
className="h-8 px-3 rounded-lg bg-indigo-500 text-xs font-semibold text-white hover:bg-indigo-600 border border-indigo-400/30 disabled:opacity-60 disabled:cursor-not-allowed"
>
{saving ? "Saving..." : "Save template"}
</button> </button>
</div> </div>
</div>
</div> <div className="flex-1 overflow-y-auto px-6 py-6 space-y-6">
</div> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
)} <div className="space-y-4">
<div>
<label className={`block text-xs font-bold uppercase tracking-wider mb-2 ${isLight ? "text-slate-500" : "text-slate-400"
}`}>
Template Name
</label>
<input
type="text"
value={editing.name}
onChange={(e) =>
setEditing((prev) =>
prev
? { ...prev, name: e.target.value }
: prev,
)
}
placeholder="e.g. Shipping Update"
className={`w-full h-11 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight
? "bg-slate-50 border border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500"
: "bg-white/5 border border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500"
}`}
/>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className={`block text-xs font-bold uppercase tracking-wider mb-2 ${isLight ? "text-slate-500" : "text-slate-400"
}`}>
Intent
</label>
<select
value={editing.intent}
onChange={(e) =>
setEditing((prev) =>
prev
? {
...prev,
intent: e.target.value,
}
: prev,
)
}
className={`w-full h-11 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight
? "bg-slate-50 border border-slate-200 text-slate-900 focus:border-indigo-500"
: "bg-white/5 border border-white/10 text-white focus:border-indigo-500"
}`}
>
{INTENT_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
<div>
<label className={`block text-xs font-bold uppercase tracking-wider mb-2 ${isLight ? "text-slate-500" : "text-slate-400"
}`}>
Mood
</label>
<select
value={editing.mood}
onChange={(e) =>
setEditing((prev) =>
prev
? {
...prev,
mood: e.target.value,
}
: prev,
)
}
className={`w-full h-11 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight
? "bg-slate-50 border border-slate-200 text-slate-900 focus:border-indigo-500"
: "bg-white/5 border border-white/10 text-white focus:border-indigo-500"
}`}
>
{MOOD_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
</div>
<div>
<label className={`block text-xs font-bold uppercase tracking-wider mb-2 ${isLight ? "text-slate-500" : "text-slate-400"
}`}>
Description (optional)
</label>
<input
type="text"
value={editing.description || ""}
onChange={(e) =>
setEditing((prev) =>
prev
? {
...prev,
description: e.target.value,
}
: prev,
)
}
placeholder="Short summary for your team"
className={`w-full h-11 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight
? "bg-slate-50 border border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500"
: "bg-white/5 border border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500"
}`}
/>
</div>
</div>
<div className={`p-4 rounded-2xl border ${isLight ? "bg-indigo-50/50 border-indigo-100" : "bg-indigo-500/5 border-indigo-500/10"
}`}>
<p className={`text-xs font-bold uppercase tracking-wider mb-3 ${isLight ? "text-indigo-600" : "text-indigo-400"
}`}>
Available variables
</p>
<div className="grid grid-cols-1 gap-2">
{[
{ key: "{{customer_name}}", label: "Customer's name" },
{ key: "{{customer_email}}", label: "Customer's email" },
{ key: "{{store_name}}", label: "Store name" },
{ key: "{{ticket_summary}}", label: "Email summary" }
].map((v) => (
<div key={v.key} className="flex items-center justify-between text-[11px]">
<code className={`px-2 py-0.5 rounded font-mono font-bold ${isLight ? "bg-white border border-indigo-200 text-indigo-700" : "bg-white/10 text-indigo-300"
}`}>
{v.key}
</code>
<span className={isLight ? "text-slate-500" : "text-slate-400"}>{v.label}</span>
</div>
))}
</div>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between">
<label className={`block text-xs font-bold uppercase tracking-wider ${isLight ? "text-slate-500" : "text-slate-400"
}`}>
Template body
</label>
<span className={`text-[10px] ${isLight ? "text-slate-400" : "text-slate-500"}`}>
Markdown & HTML supported
</span>
</div>
<textarea
value={editing.body}
onChange={(e) =>
setEditing((prev) =>
prev ? { ...prev, body: e.target.value } : prev,
)
}
rows={8}
className={`w-full rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 p-4 font-mono text-xs leading-relaxed ${isLight
? "bg-slate-50 border border-slate-200 text-slate-900 focus:border-indigo-500"
: "bg-white/5 border border-white/10 text-white focus:border-indigo-500"
}`}
/>
</div>
<div className="space-y-4">
<label className={`block text-xs font-bold uppercase tracking-wider ${isLight ? "text-slate-500" : "text-slate-400"
}`}>
Preview with example data
</label>
<div className={`rounded-xl border p-5 text-sm leading-relaxed whitespace-pre-wrap ${isLight
? "bg-slate-50 border-slate-200 text-slate-800"
: "bg-slate-950 border-white/5 text-slate-300 shadow-inner"
}`}>
{currentPreview || "Preview will appear here..."}
</div>
</div>
</div>
<div className={`px-6 py-4 border-t flex items-center justify-between ${isLight ? "border-slate-100 bg-slate-50/50" : "border-white/10 bg-white/[0.02]"
}`}>
<p className={`text-[11px] font-medium ${isLight ? "text-slate-500" : "text-slate-500"}`}>
Autosaved to your draft templates.
</p>
<div className="flex items-center gap-3">
<button
type="button"
onClick={() => setEditing(null)}
className={`h-10 px-6 rounded-xl font-bold transition-all border ${isLight
? "bg-white text-slate-700 hover:bg-slate-50 border-slate-200"
: "bg-white/10 text-slate-300 hover:bg-white/20 border-white/10"
}`}
>
Cancel
</button>
<button
type="button"
disabled={saving}
onClick={() => void handleSave()}
className="h-10 px-6 bg-indigo-500 text-sm font-bold text-white hover:bg-indigo-600 border border-indigo-400/20 shadow-lg shadow-indigo-500/20 transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2 rounded-xl"
>
{saving ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Saving...
</>
) : (
"Save template"
)}
</button>
</div>
</div>
</motion.div>
</div>,
document.body
)}
</AnimatePresence>
</div> </div>
) )
} }

View File

@ -47,16 +47,18 @@ export default async function DashboardLayout({
<main className="md:pl-72 flex flex-col flex-1 h-full min-w-0"> <main className="md:pl-72 flex flex-col flex-1 h-full min-w-0">
{/* Fixed Header */} {/* Fixed Header */}
<header className="h-16 px-4 md:px-8 border-b border-white/10 bg-slate-900/50 backdrop-blur-xl flex items-center justify-between flex-shrink-0 z-50"> <header className="h-16 px-4 md:px-8 border-b border-white/10 bg-slate-900/50 backdrop-blur-xl flex items-center justify-between flex-shrink-0 z-50">
<div className="flex items-center gap-2 md:gap-4 flex-1 min-w-0"> <div className="flex items-center gap-1.5 md:gap-4 flex-1 min-w-0">
<MobileSidebar /> <MobileSidebar />
<h2 className="hidden sm:block text-xs md:text-sm font-semibold text-slate-400 uppercase tracking-wider">Vendor Portal</h2> <h2 className="hidden lg:block text-xs md:text-sm font-semibold text-slate-400 uppercase tracking-wider shrink-0">Vendor Portal</h2>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0 max-w-[200px] md:max-w-none">
<StoreSwitcher /> <StoreSwitcher />
</div> </div>
</div> </div>
<div className="flex items-center gap-2 md:gap-4 shrink-0"> <div className="flex items-center gap-1.5 md:gap-4 shrink-0">
<ThemeToggle /> <ThemeToggle />
<NotificationBell /> <div className="hidden xs:block">
<NotificationBell />
</div>
<div className="hidden sm:flex flex-col text-right"> <div className="hidden sm:flex flex-col text-right">
<span className="text-xs font-bold text-white truncate max-w-[120px]"> <span className="text-xs font-bold text-white truncate max-w-[120px]">
{session.user?.name || "Vendor User"} {session.user?.name || "Vendor User"}

View File

@ -235,120 +235,119 @@ export default function OrdersPage() {
{/* Orders Grid */} {/* Orders Grid */}
<div className="flex-1 overflow-y-auto overflow-x-hidden min-h-0"> <div className="flex-1 overflow-y-auto overflow-x-hidden min-h-0">
{loading ? ( {loading ? (
<div className="h-full flex flex-col items-center justify-center text-slate-400 gap-3"> <div className="h-full flex flex-col items-center justify-center text-slate-400 gap-3">
<Loader2 className="w-8 h-8 animate-spin text-indigo-400" /> <Loader2 className="w-8 h-8 animate-spin text-indigo-400" />
<p className="font-medium animate-pulse">Fetching latest orders...</p> <p className="font-medium animate-pulse">Fetching latest orders...</p>
</div>
) : error ? (
<div className="h-full flex flex-col items-center justify-center text-red-400 gap-4 p-8 text-center">
<div className="w-12 h-12 bg-red-500/10 border border-red-500/20 rounded-2xl flex items-center justify-center">
<AlertCircle className="w-6 h-6" />
</div> </div>
) : error ? ( <p className="font-bold">{error}</p>
<div className="h-full flex flex-col items-center justify-center text-red-400 gap-4 p-8 text-center"> </div>
<div className="w-12 h-12 bg-red-500/10 border border-red-500/20 rounded-2xl flex items-center justify-center"> ) : filteredOrders.length === 0 ? (
<AlertCircle className="w-6 h-6" /> <div className="h-full flex flex-col items-center justify-center text-slate-400 gap-3">
</div> <ShoppingCart className="w-10 h-10 opacity-20" />
<p className="font-bold">{error}</p> <p className="font-medium">No orders found.</p>
</div> </div>
) : filteredOrders.length === 0 ? ( ) : (
<div className="h-full flex flex-col items-center justify-center text-slate-400 gap-3"> <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 md:gap-6 h-full content-start">
<ShoppingCart className="w-10 h-10 opacity-20" /> {filteredOrders.map((order) => (
<p className="font-medium">No orders found.</p> <div
</div> key={order.id}
) : ( className="bg-white/5 rounded-xl border border-white/10 p-5 hover:bg-white/10 hover:border-indigo-500/30 transition-all cursor-pointer group relative"
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 md:gap-6 h-full content-start"> onClick={() => handleViewDetails(order)}
{filteredOrders.map((order) => ( >
<div {/* Order Header */}
key={order.id} <div className="flex items-start justify-between mb-4">
className="bg-white/5 rounded-xl border border-white/10 p-5 hover:bg-white/10 hover:border-indigo-500/30 transition-all cursor-pointer group relative" <div className="flex items-center gap-3 flex-1 min-w-0">
onClick={() => handleViewDetails(order)} <div className="w-10 h-10 rounded-lg bg-indigo-500/20 border border-indigo-400/20 flex items-center justify-center text-indigo-400 group-hover:bg-indigo-500/30 transition-all shrink-0">
> <ShoppingBag className="w-5 h-5" />
{/* Order Header */}
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-3 flex-1 min-w-0">
<div className="w-10 h-10 rounded-lg bg-indigo-500/20 border border-indigo-400/20 flex items-center justify-center text-indigo-400 group-hover:bg-indigo-500/30 transition-all shrink-0">
<ShoppingBag className="w-5 h-5" />
</div>
<div className="flex-1 min-w-0">
<h3 className="font-black text-base text-white truncate">{order.name}</h3>
<p className="text-xs text-slate-400 mt-0.5">
{new Date(order.created_at).toLocaleDateString()} • {new Date(order.created_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
</p>
</div>
</div> </div>
<div className="relative shrink-0" onClick={(e) => e.stopPropagation()} ref={(el) => { menuRefs.current[order.id] = el }}> <div className="flex-1 min-w-0">
<button <h3 className="font-black text-base text-white truncate">{order.name}</h3>
onClick={() => handleMenuToggle(order.id)} <p className="text-xs text-slate-400 mt-0.5">
className="p-2 hover:bg-white/10 rounded-lg text-slate-400 hover:text-indigo-400 transition-all" {new Date(order.created_at).toLocaleDateString()} • {new Date(order.created_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
> </p>
<MoreHorizontal className="w-4 h-4" />
</button>
{openMenuId === order.id && (
<div className="absolute right-0 top-full mt-1 bg-white/95 backdrop-blur-xl border border-white/20 rounded-xl shadow-xl overflow-hidden z-[200] min-w-[180px] whitespace-nowrap">
<button
onClick={() => handleViewDetails(order)}
className="w-full px-4 py-2.5 text-left text-sm font-medium text-slate-700 hover:bg-indigo-500 hover:text-white transition-colors flex items-center gap-2"
>
<Eye className="w-4 h-4" />
Details
</button>
<button
onClick={() => handleViewInShopify(order.id)}
className="w-full px-4 py-2.5 text-left text-sm font-medium text-slate-700 hover:bg-indigo-500 hover:text-white transition-colors flex items-center gap-2"
>
<ExternalLink className="w-4 h-4" />
Show on Shopify
</button>
<button
onClick={() => handleCopyOrderId(order.id)}
className="w-full px-4 py-2.5 text-left text-sm font-medium text-slate-700 hover:bg-indigo-500 hover:text-white transition-colors flex items-center gap-2"
>
<Copy className="w-4 h-4" />
Copy Order ID
</button>
</div>
)}
</div> </div>
</div> </div>
<div className="relative shrink-0" onClick={(e) => e.stopPropagation()} ref={(el) => { menuRefs.current[order.id] = el }}>
{/* Customer Info */} <button
{order.customer && ( onClick={() => handleMenuToggle(order.id)}
<div className="flex items-center gap-2 mb-4"> className="p-2 hover:bg-white/10 rounded-lg text-slate-400 hover:text-indigo-400 transition-all"
<div className="w-7 h-7 rounded-full bg-white/10 flex items-center justify-center shrink-0"> >
<User className="w-3.5 h-3.5 text-slate-400" /> <MoreHorizontal className="w-4 h-4" />
</button>
{openMenuId === order.id && (
<div className="absolute right-0 top-full mt-1 bg-slate-800/95 backdrop-blur-xl border border-white/10 rounded-xl shadow-xl overflow-hidden z-[200] min-w-[180px] whitespace-nowrap">
<button
onClick={() => handleViewDetails(order)}
className="w-full px-4 py-2.5 text-left text-sm font-medium text-slate-200 hover:bg-indigo-500 hover:text-white transition-colors flex items-center gap-2"
>
<Eye className="w-4 h-4" />
Details
</button>
<button
onClick={() => handleViewInShopify(order.id)}
className="w-full px-4 py-2.5 text-left text-sm font-medium text-slate-200 hover:bg-indigo-500 hover:text-white transition-colors flex items-center gap-2"
>
<ExternalLink className="w-4 h-4" />
Show on Shopify
</button>
<button
onClick={() => handleCopyOrderId(order.id)}
className="w-full px-4 py-2.5 text-left text-sm font-medium text-slate-200 hover:bg-indigo-500 hover:text-white transition-colors flex items-center gap-2"
>
<Copy className="w-4 h-4" />
Copy Order ID
</button>
</div> </div>
<div className="flex-1 min-w-0"> )}
<p className="text-sm font-bold text-white truncate">
{order.customer.first_name} {order.customer.last_name}
</p>
<p className="text-xs text-slate-400 truncate">{order.email}</p>
</div>
</div>
)}
{/* Order Details */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-slate-400 uppercase tracking-wider">Total</span>
<span className="text-lg font-black text-white">{order.total_price} {order.currency}</span>
</div>
<div className="flex items-center gap-2 flex-wrap">
<span className={`px-2.5 py-1 rounded-full text-[10px] font-black uppercase tracking-wider inline-flex items-center gap-1.5 ${
order.financial_status === "paid"
? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30"
: "bg-red-500/20 text-red-400 border border-red-500/30"
}`}>
{order.financial_status === "paid" && <CheckCircle2 className="w-3 h-3" />}
Payment: {order.financial_status}
</span>
<span className={`px-2.5 py-1 rounded-full text-[10px] font-black uppercase tracking-wider inline-flex items-center gap-1.5 ${getStatusStyles(order.fulfillment_status)}`}>
{order.fulfillment_status === "fulfilled" ? <CheckCircle2 className="w-3 h-3" /> : <Clock className="w-3 h-3" />}
{order.fulfillment_status || "Unfulfilled"}
</span>
</div>
</div> </div>
</div> </div>
))}
</div> {/* Customer Info */}
)} {order.customer && (
<div className="flex items-center gap-2 mb-4">
<div className="w-7 h-7 rounded-full bg-white/10 flex items-center justify-center shrink-0">
<User className="w-3.5 h-3.5 text-slate-400" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-bold text-white truncate">
{order.customer.first_name} {order.customer.last_name}
</p>
<p className="text-xs text-slate-400 truncate">{order.email}</p>
</div>
</div>
)}
{/* Order Details */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-slate-400 uppercase tracking-wider">Total</span>
<span className="text-lg font-black text-white">{order.total_price} {order.currency}</span>
</div>
<div className="flex items-center gap-2 flex-wrap">
<span className={`px-2.5 py-1 rounded-full text-[10px] font-black uppercase tracking-wider inline-flex items-center gap-1.5 ${order.financial_status === "paid"
? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30"
: "bg-red-500/20 text-red-400 border border-red-500/30"
}`}>
{order.financial_status === "paid" && <CheckCircle2 className="w-3 h-3" />}
Payment: {order.financial_status}
</span>
<span className={`px-2.5 py-1 rounded-full text-[10px] font-black uppercase tracking-wider inline-flex items-center gap-1.5 ${getStatusStyles(order.fulfillment_status)}`}>
{order.fulfillment_status === "fulfilled" ? <CheckCircle2 className="w-3 h-3" /> : <Clock className="w-3 h-3" />}
{order.fulfillment_status || "Unfulfilled"}
</span>
</div>
</div>
</div>
))}
</div>
)}
</div> </div>
</div> </div>
) )

View File

@ -218,7 +218,7 @@ export default function SecuritySettingsPage() {
{/* MFA Section */} {/* MFA Section */}
<div className="bg-white/5 rounded-xl p-6 border border-white/10"> <div className="bg-white/5 rounded-xl p-6 border border-white/10">
<div className="flex items-start justify-between mb-4"> <div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4 mb-4">
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-3 mb-2"> <div className="flex items-center gap-3 mb-2">
<Shield className="w-5 h-5 text-indigo-400" /> <Shield className="w-5 h-5 text-indigo-400" />
@ -232,7 +232,7 @@ export default function SecuritySettingsPage() {
<button <button
onClick={handleDisableMFA} onClick={handleDisableMFA}
disabled={mfaLoading} disabled={mfaLoading}
className="px-4 py-2 rounded-lg font-bold text-sm transition-colors flex items-center gap-2 bg-red-500/20 text-red-400 border border-red-500/30 hover:bg-red-500/30 disabled:opacity-50 disabled:cursor-not-allowed" className="w-full sm:w-auto px-4 py-2 rounded-lg font-bold text-sm transition-colors flex items-center justify-center gap-2 bg-red-500/20 text-red-400 border border-red-500/30 hover:bg-red-500/30 disabled:opacity-50 disabled:cursor-not-allowed"
> >
{mfaLoading ? ( {mfaLoading ? (
<> <>
@ -247,7 +247,7 @@ export default function SecuritySettingsPage() {
<button <button
onClick={handleSetupMFA} onClick={handleSetupMFA}
disabled={setupLoading || showSetup} disabled={setupLoading || showSetup}
className="px-4 py-2 rounded-lg font-bold text-sm transition-colors flex items-center gap-2 bg-indigo-500 text-white hover:bg-indigo-600 disabled:opacity-50 disabled:cursor-not-allowed" className="w-full sm:w-auto px-4 py-2 rounded-lg font-bold text-sm transition-colors flex items-center justify-center gap-2 bg-indigo-500 text-white hover:bg-indigo-600 disabled:opacity-50 disabled:cursor-not-allowed"
> >
{setupLoading ? ( {setupLoading ? (
<> <>

View File

@ -20,6 +20,12 @@ export default function StoresPage() {
const [error, setError] = useState("") const [error, setError] = useState("")
const [switching, setSwitching] = useState<string | null>(null) const [switching, setSwitching] = useState<string | null>(null)
const [isAddModalOpen, setIsAddModalOpen] = useState(false) const [isAddModalOpen, setIsAddModalOpen] = useState(false)
/*
* Alignment Fix Details:
* - Use React `createPortal` to render the modal backdrop and content at the end of `document.body`.
* - This ensures `fixed inset-0` is always relative to the viewport, bypassing any parent `transform` styles (like in `PageTransition`) that create new containing blocks.
* - Verify that the `fixed inset-0` flex container correctly centers its child vertically and horizontally.
*/
const [deleteModal, setDeleteModal] = useState<{ isOpen: boolean; storeId: string | null; storeName: string; isActive: boolean }>({ const [deleteModal, setDeleteModal] = useState<{ isOpen: boolean; storeId: string | null; storeName: string; isActive: boolean }>({
isOpen: false, isOpen: false,
storeId: null, storeId: null,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -179,13 +179,19 @@ html {
will-change: auto; will-change: auto;
} }
/* Smooth transitions for all interactive elements */ /* Smooth transitions for interactive elements */
* { a, button, input, select, textarea, [role="button"], [tabindex] {
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform; transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms; transition-duration: 150ms;
} }
/* Fix select dropdown option readability across themes */
select option {
background-color: var(--background);
color: var(--foreground);
}
/* Optimize performance for animations */ /* Optimize performance for animations */
button, a, input, select, textarea { button, a, input, select, textarea {
will-change: transform; will-change: transform;

4
src/app/icon.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="8" fill="#4F46E5"/>
<path d="M16 6L10 18H14L12 26L22 14H18L20 6H16Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 224 B

View File

@ -15,10 +15,6 @@ const geistMono = Geist_Mono({
export const metadata: Metadata = { export const metadata: Metadata = {
title: "JUNOHUB", title: "JUNOHUB",
description: "JUNOHUB - Your dropshipping and e-commerce management platform", description: "JUNOHUB - Your dropshipping and e-commerce management platform",
icons: {
icon: "/icon.svg",
apple: "/icon.svg",
},
}; };
export const viewport = { export const viewport = {

View File

@ -205,25 +205,26 @@ export default function SignupPage() {
Log in here Log in here
</Link> </Link>
</p> </p>
<div className="mt-8 pt-6 border-t border-white/10 flex flex-col items-center gap-1 text-[9px] text-slate-500 font-semibold uppercase tracking-widest">
<div className="flex items-center gap-4">
<Link
href="/privacy-policy"
className="hover:text-indigo-300 underline-offset-4 hover:underline normal-case"
>
Privacy Policy
</Link>
<span className="text-slate-600">•</span>
<Link
href="/terms-of-service"
className="hover:text-indigo-300 underline-offset-4 hover:underline normal-case"
>
Terms of Service
</Link>
</div>
</div>
</div> </div>
</motion.div> </motion.div>
<div className="mt-6 flex flex-col items-center gap-1 text-[9px] text-slate-500 font-semibold uppercase tracking-widest">
<div className="flex items-center gap-4">
<Link
href="/privacy-policy"
className="hover:text-indigo-300 underline-offset-4 hover:underline normal-case"
>
Privacy Policy
</Link>
<span className="text-slate-600">•</span>
<Link
href="/terms-of-service"
className="hover:text-indigo-300 underline-offset-4 hover:underline normal-case"
>
Terms of Service
</Link>
</div>
</div>
</div> </div>
) )
} }

View File

@ -1,6 +1,7 @@
"use client" "use client"
import { useState } from "react" import { useState, useEffect } from "react"
import { createPortal } from "react-dom"
import { motion, AnimatePresence } from "framer-motion" import { motion, AnimatePresence } from "framer-motion"
import { import {
X, X,
@ -11,6 +12,7 @@ import {
Cloud, Cloud,
CheckCircle2 CheckCircle2
} from "lucide-react" } from "lucide-react"
import { useTheme } from "@/components/ThemeProvider"
interface AddStoreModalProps { interface AddStoreModalProps {
isOpen: boolean isOpen: boolean
@ -23,6 +25,13 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState("") const [error, setError] = useState("")
const [success, setSuccess] = useState(false) const [success, setSuccess] = useState(false)
const [mounted, setMounted] = useState(false)
const { theme } = useTheme()
const isLight = theme === "light"
useEffect(() => {
setMounted(true)
}, [])
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
businessName: "", businessName: "",
@ -133,271 +142,315 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
} }
} }
return ( if (!mounted) return null
return createPortal(
<AnimatePresence> <AnimatePresence>
{isOpen && ( {isOpen && (
<> <div className="fixed inset-0 z-[100] flex items-center justify-center pointer-events-none">
{/* Backdrop */} {/* Backdrop */}
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
onClick={handleClose} onClick={handleClose}
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50" className="absolute inset-0 bg-black/60 backdrop-blur-sm pointer-events-auto"
/> />
{/* Modal */} {/* Modal */}
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 pointer-events-none"> <motion.div
<motion.div initial={{ scale: 0.95, opacity: 0, y: 20 }}
initial={{ scale: 0.95, opacity: 0 }} animate={{ scale: 1, opacity: 1, y: 0 }}
animate={{ scale: 1, opacity: 1 }} exit={{ scale: 0.95, opacity: 0, y: 20 }}
exit={{ scale: 0.95, opacity: 0 }} className={`relative rounded-2xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden pointer-events-auto flex flex-col mx-4 ${isLight
className="bg-slate-900/95 backdrop-blur-xl rounded-2xl border border-white/10 shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden pointer-events-auto flex flex-col" ? "bg-white border border-slate-200"
> : "bg-slate-900 border border-white/10"
{/* Header */} }`}
<div className="flex items-center justify-between p-6 border-b border-white/10"> >
<div> {/* Header */}
<h2 className="text-2xl font-bold text-white">Add New Store</h2> <div className={`flex items-center justify-between p-6 border-b ${isLight ? "border-slate-100" : "border-white/10"
<p className="text-sm text-slate-400 mt-1">Connect a Shopify store to your account</p> }`}>
</div> <div>
<button <h2 className={`text-2xl font-bold ${isLight ? "text-slate-900" : "text-white"}`}>Add New Store</h2>
onClick={handleClose} <p className={`text-sm mt-1 ${isLight ? "text-slate-500" : "text-slate-400"}`}>Connect a Shopify store to your account</p>
disabled={loading || success}
className="w-10 h-10 rounded-xl bg-white/10 hover:bg-white/15 flex items-center justify-center transition-colors disabled:opacity-50 text-slate-300"
>
<X className="w-5 h-5" />
</button>
</div> </div>
<button
onClick={handleClose}
disabled={loading || success}
className={`w-10 h-10 rounded-xl flex items-center justify-center transition-colors disabled:opacity-50 ${isLight
? "bg-slate-100 hover:bg-slate-200 text-slate-600"
: "bg-white/10 hover:bg-white/15 text-slate-300"
}`}
>
<X className="w-5 h-5" />
</button>
</div>
{/* Progress Bar */} {/* Progress Bar */}
<div className="px-6 pt-6"> <div className="px-6 pt-6">
<div className="flex justify-between text-[10px] font-bold text-slate-500 mb-2 uppercase tracking-wider"> <div className="flex justify-between text-[10px] font-bold text-slate-500 mb-2 uppercase tracking-wider">
<span className={step >= 1 ? "text-indigo-400" : ""}>Store Details</span> <span className={step >= 1 ? "text-indigo-500" : ""}>Store Details</span>
<span className={step >= 2 ? "text-indigo-400" : ""}>API Credentials</span> <span className={step >= 2 ? "text-indigo-500" : ""}>API Credentials</span>
<span className={step >= 3 ? "text-indigo-400" : ""}>Complete</span> <span className={step >= 3 ? "text-indigo-500" : ""}>Complete</span>
</div>
<div className="h-1.5 bg-white/10 rounded-full overflow-hidden">
<motion.div
initial={{ width: "0%" }}
animate={{ width: `${(step / 3) * 100}%` }}
className="h-full bg-indigo-500 transition-all duration-500"
/>
</div>
</div> </div>
<div className={`h-1.5 rounded-full overflow-hidden ${isLight ? "bg-slate-100" : "bg-white/10"
}`}>
<motion.div
initial={{ width: "0%" }}
animate={{ width: `${(step / 3) * 100}%` }}
className="h-full bg-indigo-500 transition-all duration-500"
/>
</div>
</div>
{/* Content */} {/* Content */}
<div className="flex-1 overflow-y-auto p-6"> <div className="flex-1 overflow-y-auto p-6">
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
{success ? ( {success ? (
<motion.div
key="success"
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
className="flex flex-col items-center justify-center py-12 text-center"
>
<div className="w-16 h-16 rounded-full bg-emerald-500/20 border border-emerald-400/30 flex items-center justify-center mb-4">
<CheckCircle2 className="w-8 h-8 text-emerald-400" />
</div>
<h3 className="text-xl font-bold text-white mb-2">Store Added Successfully!</h3>
<p className="text-slate-400">Your store has been connected and is ready to use.</p>
</motion.div>
) : step === 1 ? (
<motion.div
key="step1"
initial={{ x: 20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -20, opacity: 0 }}
className="space-y-6"
>
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl bg-indigo-500/20 border border-indigo-400/20 flex items-center justify-center">
<Globe className="w-6 h-6 text-indigo-400" />
</div>
<div>
<h3 className="text-lg font-bold text-white">Store Information</h3>
<p className="text-sm text-slate-400">Enter your store details</p>
</div>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-bold text-slate-300 mb-2">
Business Name <span className="text-red-400">*</span>
</label>
<input
name="businessName"
value={formData.businessName}
onChange={handleInputChange}
placeholder="e.g. My Store"
className="w-full h-12 px-4 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30 transition-all"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-bold text-slate-300 mb-2">
Shopify Store Name <span className="text-red-400">*</span>
</label>
<input
name="storeName"
value={formData.storeName}
onChange={handleInputChange}
placeholder="my-store"
className="w-full h-12 px-4 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30 transition-all"
/>
</div>
<div>
<label className="block text-sm font-bold text-slate-300 mb-2">
Domain <span className="text-red-400">*</span>
</label>
<input
name="domain"
value={formData.domain}
onChange={handleInputChange}
placeholder="my-store.myshopify.com"
className="w-full h-12 px-4 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30 transition-all"
/>
</div>
</div>
</div>
</motion.div>
) : step === 2 ? (
<motion.div
key="step2"
initial={{ x: 20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -20, opacity: 0 }}
className="space-y-6"
>
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl bg-indigo-500/20 border border-indigo-400/20 flex items-center justify-center">
<Key className="w-6 h-6 text-indigo-400" />
</div>
<div>
<h3 className="text-lg font-bold text-white">API Credentials</h3>
<p className="text-sm text-slate-400">Provide your Shopify Admin API credentials</p>
</div>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-bold text-slate-300 mb-2">
Admin API Key <span className="text-red-400">*</span>
</label>
<input
name="apiKey"
type="password"
value={formData.apiKey}
onChange={handleInputChange}
placeholder="Enter API Key"
className="w-full h-12 px-4 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30 transition-all"
/>
</div>
<div>
<label className="block text-sm font-bold text-slate-300 mb-2">
Admin API Secret <span className="text-red-400">*</span>
</label>
<input
name="apiSecret"
type="password"
value={formData.apiSecret}
onChange={handleInputChange}
placeholder="Enter API Secret"
className="w-full h-12 px-4 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30 transition-all"
/>
</div>
<div>
<label className="block text-sm font-bold text-slate-300 mb-2">
Access Token <span className="text-red-400">*</span>
</label>
<input
name="accessToken"
type="password"
value={formData.accessToken}
onChange={handleInputChange}
placeholder="shpat_..."
className="w-full h-12 px-4 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30 transition-all"
/>
</div>
</div>
</motion.div>
) : (
<motion.div
key="step3"
initial={{ x: 20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -20, opacity: 0 }}
className="flex flex-col items-center justify-center py-8 text-center"
>
<div className="w-16 h-16 rounded-full bg-indigo-500/20 border border-indigo-400/20 flex items-center justify-center mb-4 relative">
<Cloud className={`w-8 h-8 ${loading ? "text-indigo-400 animate-pulse" : "text-indigo-400"}`} />
{loading && (
<motion.div
animate={{ rotate: 360 }}
transition={{ repeat: Infinity, duration: 2, ease: "linear" }}
className="absolute inset-0 border-2 border-indigo-500 border-t-transparent rounded-full"
/>
)}
</div>
<h3 className="text-lg font-bold text-white mb-2">Connecting Store</h3>
<p className="text-sm text-slate-400 max-w-sm">
We&apos;re connecting your store and setting up the integration...
</p>
</motion.div>
)}
</AnimatePresence>
{error && (
<motion.div <motion.div
initial={{ opacity: 0, y: -10 }} key="success"
animate={{ opacity: 1, y: 0 }} initial={{ scale: 0.9, opacity: 0 }}
className="mt-4 p-4 rounded-xl bg-red-500/10 border border-red-500/20 flex items-start gap-3" animate={{ scale: 1, opacity: 1 }}
className="flex flex-col items-center justify-center py-12 text-center"
> >
<AlertCircle className="w-5 h-5 text-red-400 shrink-0 mt-0.5" /> <div className={`w-16 h-16 rounded-full flex items-center justify-center mb-4 ${isLight
<p className="text-sm text-red-400">{error}</p> ? "bg-emerald-100 border border-emerald-200"
: "bg-emerald-500/20 border border-emerald-400/30"
}`}>
<CheckCircle2 className={`w-8 h-8 ${isLight ? "text-emerald-600" : "text-emerald-400"}`} />
</div>
<h3 className={`text-xl font-bold mb-2 ${isLight ? "text-slate-900" : "text-white"}`}>Store Added Successfully!</h3>
<p className={`${isLight ? "text-slate-600" : "text-slate-400"}`}>Your store has been connected and is ready to use.</p>
</motion.div>
) : step === 1 ? (
<motion.div
key="step1"
initial={{ x: 20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -20, opacity: 0 }}
className="space-y-6"
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isLight
? "bg-indigo-50 border border-indigo-100"
: "bg-indigo-500/20 border border-indigo-400/20"
}`}>
<Globe className="w-6 h-6 text-indigo-500" />
</div>
<div>
<h3 className={`text-lg font-bold ${isLight ? "text-slate-900" : "text-white"}`}>Store Information</h3>
<p className={`text-sm ${isLight ? "text-slate-500" : "text-slate-400"}`}>Enter your store details</p>
</div>
</div>
<div className="space-y-4">
<div>
<label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Business Name <span className="text-red-500">*</span>
</label>
<input
name="businessName"
value={formData.businessName}
onChange={handleInputChange}
placeholder="e.g. My Store"
className={`w-full h-12 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight
? "bg-slate-50 border border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500"
: "bg-white/5 border border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500"
}`}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Shopify Store Name <span className="text-red-500">*</span>
</label>
<input
name="storeName"
value={formData.storeName}
onChange={handleInputChange}
placeholder="my-store"
className={`w-full h-12 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight
? "bg-slate-50 border border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500"
: "bg-white/5 border border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500"
}`}
/>
</div>
<div>
<label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Domain <span className="text-red-500">*</span>
</label>
<input
name="domain"
value={formData.domain}
onChange={handleInputChange}
placeholder="my-store.myshopify.com"
className={`w-full h-12 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight
? "bg-slate-50 border border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500"
: "bg-white/5 border border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500"
}`}
/>
</div>
</div>
</div>
</motion.div>
) : step === 2 ? (
<motion.div
key="step2"
initial={{ x: 20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -20, opacity: 0 }}
className="space-y-6"
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isLight
? "bg-indigo-50 border border-indigo-100"
: "bg-indigo-500/20 border border-indigo-400/20"
}`}>
<Key className="w-6 h-6 text-indigo-500" />
</div>
<div>
<h3 className={`text-lg font-bold ${isLight ? "text-slate-900" : "text-white"}`}>API Credentials</h3>
<p className={`text-sm ${isLight ? "text-slate-500" : "text-slate-400"}`}>Provide your Shopify Admin API credentials</p>
</div>
</div>
<div className="space-y-4">
<div>
<label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Admin API Key <span className="text-red-500">*</span>
</label>
<input
name="apiKey"
type="password"
value={formData.apiKey}
onChange={handleInputChange}
placeholder="Enter API Key"
className={`w-full h-12 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight
? "bg-slate-50 border border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500"
: "bg-white/5 border border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500"
}`}
/>
</div>
<div>
<label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Admin API Secret <span className="text-red-500">*</span>
</label>
<input
name="apiSecret"
type="password"
value={formData.apiSecret}
onChange={handleInputChange}
placeholder="Enter API Secret"
className={`w-full h-12 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight
? "bg-slate-50 border border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500"
: "bg-white/5 border border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500"
}`}
/>
</div>
<div>
<label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Access Token <span className="text-red-500">*</span>
</label>
<input
name="accessToken"
type="password"
value={formData.accessToken}
onChange={handleInputChange}
placeholder="shpat_..."
className={`w-full h-12 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight
? "bg-slate-50 border border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500"
: "bg-white/5 border border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500"
}`}
/>
</div>
</div>
</motion.div>
) : (
<motion.div
key="step3"
initial={{ x: 20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -20, opacity: 0 }}
className="flex flex-col items-center justify-center py-8 text-center"
>
<div className={`w-16 h-16 rounded-full flex items-center justify-center mb-4 relative ${isLight ? "bg-indigo-50 border border-indigo-100" : "bg-indigo-500/20 border border-indigo-400/20"
}`}>
<Cloud className={`w-8 h-8 ${loading ? "text-indigo-500 animate-pulse" : "text-indigo-500"}`} />
{loading && (
<motion.div
animate={{ rotate: 360 }}
transition={{ repeat: Infinity, duration: 2, ease: "linear" }}
className="absolute inset-0 border-2 border-indigo-500 border-t-transparent rounded-full"
/>
)}
</div>
<h3 className={`text-lg font-bold mb-2 ${isLight ? "text-slate-900" : "text-white"}`}>Connecting Store</h3>
<p className={`text-sm max-w-sm ${isLight ? "text-slate-500" : "text-slate-400"}`}>
We&apos;re connecting your store and setting up the integration...
</p>
</motion.div> </motion.div>
)} )}
</div> </AnimatePresence>
{/* Footer */} {error && (
{!success && ( <motion.div
<div className="p-6 border-t border-white/10 flex items-center justify-between gap-4"> initial={{ opacity: 0, y: -10 }}
<button animate={{ opacity: 1, y: 0 }}
onClick={prevStep} className={`mt-4 p-4 rounded-xl flex items-start gap-3 ${isLight
disabled={step === 1 || loading} ? "bg-red-50 border border-red-100"
className="h-11 px-6 bg-white/10 text-slate-300 rounded-xl font-bold hover:bg-white/15 border border-white/10 transition-all disabled:opacity-50 disabled:cursor-not-allowed" : "bg-red-500/10 border border-red-500/20"
> }`}
Back >
</button> <AlertCircle className="w-5 h-5 text-red-500 shrink-0 mt-0.5" />
{step < 3 ? ( <p className={`text-sm ${isLight ? "text-red-700" : "text-red-400"}`}>{error}</p>
<button </motion.div>
onClick={nextStep}
disabled={loading}
className="h-11 px-6 bg-indigo-500 text-white rounded-xl font-bold hover:bg-indigo-600 border border-indigo-400/20 transition-all disabled:opacity-50 flex items-center gap-2 ml-auto"
>
Continue
</button>
) : (
<button
onClick={handleSubmit}
disabled={loading}
className="h-11 px-6 bg-indigo-500 text-white rounded-xl font-bold hover:bg-indigo-600 border border-indigo-400/20 transition-all disabled:opacity-50 flex items-center gap-2 ml-auto"
>
{loading ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Connecting...
</>
) : (
"Connect Store"
)}
</button>
)}
</div>
)} )}
</motion.div> </div>
</div>
</> {/* Footer */}
{!success && (
<div className={`p-6 border-t flex items-center justify-between gap-4 ${isLight ? "border-slate-100" : "border-white/10"
}`}>
<button
onClick={prevStep}
disabled={step === 1 || loading}
className={`h-11 px-6 rounded-xl font-bold transition-all disabled:opacity-50 disabled:cursor-not-allowed border ${isLight
? "bg-white text-slate-700 hover:bg-slate-50 border-slate-200"
: "bg-white/10 text-slate-300 hover:bg-white/15 border-white/10"
}`}
>
Back
</button>
{step < 3 ? (
<button
onClick={nextStep}
disabled={loading}
className="h-11 px-6 bg-indigo-500 text-white rounded-xl font-bold hover:bg-indigo-600 border border-indigo-400/20 transition-all disabled:opacity-50 flex items-center gap-2 ml-auto"
>
Continue
</button>
) : (
<button
onClick={handleSubmit}
disabled={loading}
className="h-11 px-6 bg-indigo-500 text-white rounded-xl font-bold hover:bg-indigo-600 border border-indigo-400/20 transition-all disabled:opacity-50 flex items-center gap-2 ml-auto"
>
{loading ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Connecting...
</>
) : (
"Connect Store"
)}
</button>
)}
</div>
)}
</motion.div>
</div>
)} )}
</AnimatePresence> </AnimatePresence>,
document.body
) )
} }

View File

@ -3,6 +3,7 @@
import { useState } from "react" import { useState } from "react"
import { motion, AnimatePresence } from "framer-motion" import { motion, AnimatePresence } from "framer-motion"
import { X, AlertTriangle, Loader2, Trash2 } from "lucide-react" import { X, AlertTriangle, Loader2, Trash2 } from "lucide-react"
import { useTheme } from "@/components/ThemeProvider"
interface DeleteStoreModalProps { interface DeleteStoreModalProps {
isOpen: boolean isOpen: boolean
@ -21,6 +22,8 @@ export default function DeleteStoreModal({
}: DeleteStoreModalProps) { }: DeleteStoreModalProps) {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState("") const [error, setError] = useState("")
const { theme } = useTheme()
const isLight = theme === "light"
const handleDelete = async () => { const handleDelete = async () => {
setLoading(true) setLoading(true)
@ -55,26 +58,33 @@ export default function DeleteStoreModal({
initial={{ scale: 0.95, opacity: 0 }} initial={{ scale: 0.95, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.95, opacity: 0 }} exit={{ scale: 0.95, opacity: 0 }}
className="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden pointer-events-auto" className={`rounded-2xl shadow-2xl w-full max-w-md overflow-hidden pointer-events-auto ${isLight
? "bg-white"
: "bg-slate-900/95 backdrop-blur-xl border border-white/10"
}`}
> >
{/* Header */} {/* Header */}
<div className="p-6 border-b border-slate-100"> <div className={`p-6 border-b ${isLight ? "border-slate-100" : "border-white/10"}`}>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-red-100 flex items-center justify-center"> <div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isLight ? "bg-red-100" : "bg-red-500/20 border border-red-500/30"
<AlertTriangle className="w-6 h-6 text-red-600" /> }`}>
<AlertTriangle className={`w-6 h-6 ${isLight ? "text-red-600" : "text-red-400"}`} />
</div> </div>
<div> <div>
<h2 className="text-xl font-bold text-slate-900">Delete Store</h2> <h2 className={`text-xl font-bold ${isLight ? "text-slate-900" : "text-white"}`}>Delete Store</h2>
<p className="text-sm text-slate-500 mt-0.5">This action cannot be undone</p> <p className={`text-sm mt-0.5 ${isLight ? "text-slate-500" : "text-slate-400"}`}>This action cannot be undone</p>
</div> </div>
</div> </div>
<button <button
onClick={onClose} onClick={onClose}
disabled={loading} disabled={loading}
className="w-8 h-8 rounded-lg bg-slate-100 hover:bg-slate-200 flex items-center justify-center transition-colors disabled:opacity-50" className={`w-8 h-8 rounded-lg flex items-center justify-center transition-colors disabled:opacity-50 ${isLight
? "bg-slate-100 hover:bg-slate-200 text-slate-600"
: "bg-white/10 hover:bg-white/15 text-slate-400"
}`}
> >
<X className="w-4 h-4 text-slate-600" /> <X className="w-4 h-4" />
</button> </button>
</div> </div>
</div> </div>
@ -82,27 +92,33 @@ export default function DeleteStoreModal({
{/* Content */} {/* Content */}
<div className="p-6"> <div className="p-6">
<div className="mb-6"> <div className="mb-6">
<p className="text-slate-700 mb-4"> <p className={`mb-4 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Are you sure you want to delete <span className="font-bold text-slate-900">{storeName}</span>? Are you sure you want to delete <span className={`font-bold ${isLight ? "text-slate-900" : "text-white"}`}>{storeName}</span>?
</p> </p>
{isActive && ( {isActive && (
<div className="p-4 rounded-xl bg-amber-50 border border-amber-200 mb-4"> <div className={`p-4 rounded-xl border mb-4 ${isLight
? "bg-amber-50 border-amber-200"
: "bg-amber-500/10 border-amber-500/20"
}`}>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<AlertTriangle className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" /> <AlertTriangle className={`w-5 h-5 shrink-0 mt-0.5 ${isLight ? "text-amber-600" : "text-amber-400"}`} />
<div> <div>
<p className="text-sm font-bold text-amber-900 mb-1">Active Store</p> <p className={`text-sm font-bold mb-1 ${isLight ? "text-amber-900" : "text-amber-300"}`}>Active Store</p>
<p className="text-sm text-amber-700"> <p className={`text-sm ${isLight ? "text-amber-700" : "text-amber-400/80"}`}>
This is your active store. We&apos;ll automatically switch to another store after deletion. This is your active store. We&apos;ll automatically switch to another store after deletion.
</p> </p>
</div> </div>
</div> </div>
</div> </div>
)} )}
<div className="p-4 rounded-xl bg-slate-50 border border-slate-200"> <div className={`p-4 rounded-xl border ${isLight
<p className="text-sm text-slate-600"> ? "bg-slate-50 border-slate-200"
: "bg-white/5 border-white/10"
}`}>
<p className={`text-sm ${isLight ? "text-slate-600" : "text-slate-400"}`}>
This will permanently delete: This will permanently delete:
</p> </p>
<ul className="mt-2 space-y-1 text-sm text-slate-600 ml-4 list-disc"> <ul className={`mt-2 space-y-1 text-sm ml-4 list-disc ${isLight ? "text-slate-600" : "text-slate-400"}`}>
<li>Store configuration and credentials</li> <li>Store configuration and credentials</li>
<li>All cached products, orders, and customers</li> <li>All cached products, orders, and customers</li>
<li>Sync metadata and webhook connections</li> <li>Sync metadata and webhook connections</li>
@ -114,20 +130,27 @@ export default function DeleteStoreModal({
<motion.div <motion.div
initial={{ opacity: 0, y: -10 }} initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
className="mb-4 p-4 rounded-xl bg-red-50 border border-red-200 flex items-start gap-3" className={`mb-4 p-4 rounded-xl border flex items-start gap-3 ${isLight
? "bg-red-50 border-red-200"
: "bg-red-500/10 border-red-500/20"
}`}
> >
<AlertTriangle className="w-5 h-5 text-red-600 shrink-0 mt-0.5" /> <AlertTriangle className={`w-5 h-5 shrink-0 mt-0.5 ${isLight ? "text-red-600" : "text-red-400"}`} />
<p className="text-sm text-red-700">{error}</p> <p className={`text-sm ${isLight ? "text-red-700" : "text-red-400"}`}>{error}</p>
</motion.div> </motion.div>
)} )}
</div> </div>
{/* Footer */} {/* Footer */}
<div className="p-6 border-t border-slate-100 flex items-center justify-end gap-3"> <div className={`p-6 border-t flex items-center justify-end gap-3 ${isLight ? "border-slate-100" : "border-white/10"
}`}>
<button <button
onClick={onClose} onClick={onClose}
disabled={loading} disabled={loading}
className="h-11 px-6 bg-slate-100 text-slate-700 rounded-xl font-bold hover:bg-slate-200 transition-all disabled:opacity-50" className={`h-11 px-6 rounded-xl font-bold transition-all disabled:opacity-50 ${isLight
? "bg-slate-100 text-slate-700 hover:bg-slate-200"
: "bg-white/10 text-slate-300 hover:bg-white/15"
}`}
> >
Cancel Cancel
</button> </button>

View File

@ -0,0 +1,72 @@
"use client"
import { useState, useEffect } from "react"
import { Menu, X } from "lucide-react"
import { motion, AnimatePresence } from "framer-motion"
import { AdminSidebar } from "@/components/AdminSidebar"
export function MobileAdminSidebar() {
const [isOpen, setIsOpen] = useState(false)
useEffect(() => {
if (isOpen) {
document.body.style.overflow = "hidden"
} else {
document.body.style.overflow = ""
}
return () => {
document.body.style.overflow = ""
}
}, [isOpen])
return (
<>
<button
onClick={() => setIsOpen(true)}
className="md:hidden p-2 hover:bg-white/10 rounded-lg transition-colors"
aria-label="Open admin menu"
>
<Menu className="w-5 h-5 text-white" />
</button>
<AnimatePresence>
{isOpen && (
<>
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[90] md:hidden"
onClick={() => setIsOpen(false)}
/>
{/* Sidebar Panel */}
<motion.div
initial={{ x: -288 }}
animate={{ x: 0 }}
exit={{ x: -288 }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
className="fixed inset-y-0 left-0 w-72 bg-slate-900/95 backdrop-blur-xl border-r border-white/10 z-[100] md:hidden flex flex-col"
>
<div className="flex items-center justify-between p-4 border-b border-white/10">
<span className="text-sm font-bold text-white tracking-wide">Admin Panel</span>
<button
onClick={() => setIsOpen(false)}
className="p-1.5 hover:bg-white/10 rounded-lg transition-colors"
aria-label="Close admin menu"
>
<X className="w-4 h-4 text-slate-400" />
</button>
</div>
<div className="flex-1 overflow-y-auto">
<AdminSidebar />
</div>
</motion.div>
</>
)}
</AnimatePresence>
</>
)
}

View File

@ -1,6 +1,7 @@
"use client" "use client"
import { useState, useEffect } from "react" import { useState, useEffect } from "react"
import { createPortal } from "react-dom"
import { usePathname } from "next/navigation" import { usePathname } from "next/navigation"
import { motion, AnimatePresence } from "framer-motion" import { motion, AnimatePresence } from "framer-motion"
import { X, Menu, Zap } from "lucide-react" import { X, Menu, Zap } from "lucide-react"
@ -8,8 +9,13 @@ import { Sidebar } from "./Sidebar"
export function MobileSidebar() { export function MobileSidebar() {
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const [mounted, setMounted] = useState(false)
const pathname = usePathname() const pathname = usePathname()
useEffect(() => {
setMounted(true)
}, [])
// Close sidebar when route changes // Close sidebar when route changes
useEffect(() => { useEffect(() => {
setIsOpen(false) setIsOpen(false)
@ -40,14 +46,14 @@ export function MobileSidebar() {
{/* Mobile Sidebar Overlay */} {/* Mobile Sidebar Overlay */}
<AnimatePresence> <AnimatePresence>
{isOpen && ( {isOpen && mounted && createPortal(
<> <>
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[90] md:hidden" className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[999] md:hidden pointer-events-auto"
onClick={() => setIsOpen(false)} onClick={() => setIsOpen(false)}
/> />
<motion.div <motion.div
@ -55,30 +61,31 @@ export function MobileSidebar() {
animate={{ x: 0 }} animate={{ x: 0 }}
exit={{ x: -288 }} exit={{ x: -288 }}
transition={{ type: "spring", damping: 25, stiffness: 200 }} transition={{ type: "spring", damping: 25, stiffness: 200 }}
className="fixed inset-y-0 left-0 w-72 bg-slate-900/95 backdrop-blur-xl border-r border-white/10 z-[100] md:hidden flex flex-col" className="fixed inset-y-0 left-0 w-72 bg-slate-900/95 backdrop-blur-xl border-r border-white/10 z-[1000] md:hidden flex flex-col pointer-events-auto"
> >
{/* Header with close button */} {/* Header with close button */}
<div className="flex items-center justify-between p-4 border-b border-white/10"> <div className="flex items-center justify-between p-4 border-b border-white/10">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-indigo-500/40 border border-white/20 flex items-center justify-center backdrop-blur-sm"> <div className="w-8 h-8 rounded-lg bg-indigo-500/40 border border-white/20 flex items-center justify-center backdrop-blur-sm">
<Zap className="w-5 h-5 text-white" /> <Zap className="w-5 h-5 text-white" />
</div>
<h1 className="text-xl font-bold tracking-tight text-white">JUNOHUB</h1>
</div> </div>
<h1 className="text-xl font-bold tracking-tight text-white">JUNOHUB</h1> <button
onClick={() => setIsOpen(false)}
className="p-2 rounded-lg bg-white/10 border border-white/10 text-white hover:bg-white/15 transition-all"
aria-label="Close menu"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Sidebar Content */}
<div className="flex-1 overflow-y-auto">
<Sidebar />
</div> </div>
<button
onClick={() => setIsOpen(false)}
className="p-2 rounded-lg bg-white/10 border border-white/10 text-white hover:bg-white/15 transition-all"
aria-label="Close menu"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Sidebar Content */}
<div className="flex-1 overflow-y-auto">
<Sidebar />
</div>
</motion.div> </motion.div>
</> </>,
document.body
)} )}
</AnimatePresence> </AnimatePresence>
</> </>

View File

@ -50,9 +50,9 @@ export function MobileSupplierSidebar() {
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[90] md:hidden" className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[90] md:hidden"
/> />
<motion.div <motion.div
initial={{ x: -300 }} initial={{ x: -288 }}
animate={{ x: 0 }} animate={{ x: 0 }}
exit={{ x: -300 }} exit={{ x: -288 }}
transition={{ type: "spring", damping: 25, stiffness: 200 }} transition={{ type: "spring", damping: 25, stiffness: 200 }}
className="fixed left-0 top-0 bottom-0 w-72 bg-slate-900/95 backdrop-blur-xl border-r border-white/10 z-[100] md:hidden overflow-y-auto" className="fixed left-0 top-0 bottom-0 w-72 bg-slate-900/95 backdrop-blur-xl border-r border-white/10 z-[100] md:hidden overflow-y-auto"
> >

View File

@ -3,6 +3,7 @@
import { useEffect, useState, useRef } from "react" import { useEffect, useState, useRef } from "react"
import { Bell, Loader2, CheckCircle2, Clock, XCircle, Mail, Package, ShoppingCart, X } from "lucide-react" import { Bell, Loader2, CheckCircle2, Clock, XCircle, Mail, Package, ShoppingCart, X } from "lucide-react"
import { formatDistanceToNow } from "date-fns" import { formatDistanceToNow } from "date-fns"
import { motion, AnimatePresence } from "framer-motion"
import { useTheme } from "@/components/ThemeProvider" import { useTheme } from "@/components/ThemeProvider"
interface Notification { interface Notification {
@ -125,14 +126,12 @@ export default function NotificationBell() {
<div className="relative" ref={dropdownRef}> <div className="relative" ref={dropdownRef}>
<button <button
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
className={`relative p-2 rounded-lg transition-colors ${ className={`relative p-2 rounded-lg transition-colors ${isLight ? "hover:bg-slate-100" : "hover:bg-white/10"
isLight ? "hover:bg-slate-100" : "hover:bg-white/10" }`}
}`}
> >
<Bell <Bell
className={`w-5 h-5 ${ className={`w-5 h-5 ${isLight ? "text-slate-600" : "text-slate-400"
isLight ? "text-slate-600" : "text-slate-400" }`}
}`}
/> />
{unreadCount > 0 && ( {unreadCount > 0 && (
<span className="absolute top-1 right-1 flex h-4 w-4 items-center justify-center rounded-full bg-red-500 text-[10px] font-bold text-white"> <span className="absolute top-1 right-1 flex h-4 w-4 items-center justify-center rounded-full bg-red-500 text-[10px] font-bold text-white">
@ -141,178 +140,167 @@ export default function NotificationBell() {
)} )}
</button> </button>
{isOpen && ( <AnimatePresence>
<div {isOpen && (
className={`absolute right-0 top-full z-50 mt-2 flex max-h-[600px] w-80 sm:w-96 max-w-[calc(100vw-2rem)] flex-col rounded-xl border shadow-xl ${ <motion.div
isLight initial={{ opacity: 0, scale: 0.95, y: -4 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: -4 }}
transition={{ duration: 0.15, ease: "easeOut" }}
className={`absolute right-0 top-full z-50 mt-2 flex max-h-[600px] w-80 sm:w-96 max-w-[calc(100vw-2rem)] flex-col rounded-xl border shadow-xl ${isLight
? "bg-white border-slate-200" ? "bg-white border-slate-200"
: "bg-slate-800 border-white/10" : "bg-slate-800 border-white/10"
}`}
>
<div
className={`flex items-center justify-between border-b p-4 ${
isLight ? "border-slate-200" : "border-white/10"
}`}
>
<h3
className={`font-bold ${
isLight ? "text-slate-900" : "text-white"
}`} }`}
>
<div
className={`flex items-center justify-between border-b p-4 ${isLight ? "border-slate-200" : "border-white/10"
}`}
> >
Notifications <h3
</h3> className={`font-bold ${isLight ? "text-slate-900" : "text-white"
{unreadCount > 0 && ( }`}
<button >
onClick={markAllAsRead} Notifications
className={`text-xs font-medium ${ </h3>
isLight {unreadCount > 0 && (
<button
onClick={markAllAsRead}
className={`text-xs font-medium ${isLight
? "text-indigo-600 hover:text-indigo-500" ? "text-indigo-600 hover:text-indigo-500"
: "text-indigo-400 hover:text-indigo-300" : "text-indigo-400 hover:text-indigo-300"
}`} }`}
> >
Mark all as read Mark all as read
</button> </button>
)} )}
</div> </div>
<div className="overflow-y-auto flex-1"> <div className="overflow-y-auto flex-1">
{loading ? ( {loading ? (
<div className="flex items-center justify-center py-8"> <div className="flex items-center justify-center py-8">
<Loader2 <Loader2
className={`h-6 w-6 animate-spin ${ className={`h-6 w-6 animate-spin ${isLight
isLight
? "text-indigo-600" ? "text-indigo-600"
: "text-indigo-400" : "text-indigo-400"
}`} }`}
/> />
</div> </div>
) : notifications.length === 0 ? ( ) : notifications.length === 0 ? (
<div className="p-8 text-center"> <div className="p-8 text-center">
<Bell <Bell
className={`mx-auto mb-3 h-12 w-12 opacity-50 ${ className={`mx-auto mb-3 h-12 w-12 opacity-50 ${isLight
isLight
? "text-slate-400" ? "text-slate-400"
: "text-slate-400" : "text-slate-400"
}`} }`}
/> />
<p <p
className={`text-sm ${ className={`text-sm ${isLight
isLight
? "text-slate-500" ? "text-slate-500"
: "text-slate-400" : "text-slate-400"
}`} }`}
> >
No notifications yet No notifications yet
</p> </p>
</div> </div>
) : ( ) : (
<div <div
className={`divide-y ${ className={`divide-y ${isLight
isLight
? "divide-slate-100" ? "divide-slate-100"
: "divide-white/10" : "divide-white/10"
}`} }`}
> >
{notifications.map((notification) => ( {notifications.map((notification) => (
<div <div
key={notification.id} key={notification.id}
className={`p-4 transition-colors ${ className={`p-4 transition-colors ${isLight
isLight
? "hover:bg-slate-50" ? "hover:bg-slate-50"
: "hover:bg-white/5" : "hover:bg-white/5"
} ${ } ${!notification.read
!notification.read ? isLight
? isLight ? "bg-indigo-50"
? "bg-indigo-50" : "bg-indigo-500/10"
: "bg-indigo-500/10" : ""
: "" }`}
}`} >
> <div className="flex items-start gap-3">
<div className="flex items-start gap-3"> <div className="mt-0.5 flex-shrink-0">
<div className="mt-0.5 flex-shrink-0"> {getNotificationIcon(notification.type)}
{getNotificationIcon(notification.type)} </div>
</div> <div className="flex-1 min-w-0">
<div className="flex-1 min-w-0"> <div className="flex items-start justify-between gap-2">
<div className="flex items-start justify-between gap-2"> <div className="flex-1 min-w-0">
<div className="flex-1 min-w-0"> <h4
<h4 className={`mb-1 text-sm font-semibold ${isLight
className={`mb-1 text-sm font-semibold ${
isLight
? "text-slate-900" ? "text-slate-900"
: "text-white" : "text-white"
}`} }`}
> >
{notification.title} {notification.title}
</h4> </h4>
<p <p
className={`mb-2 text-xs ${ className={`mb-2 text-xs ${isLight
isLight
? "text-slate-600" ? "text-slate-600"
: "text-slate-300" : "text-slate-300"
}`} }`}
> >
{notification.message} {notification.message}
</p> </p>
<p <p
className={`text-[10px] ${ className={`text-[10px] ${isLight
isLight
? "text-slate-400" ? "text-slate-400"
: "text-slate-500" : "text-slate-500"
}`} }`}
> >
{formatDistanceToNow(new Date(notification.createdAt), { {formatDistanceToNow(new Date(notification.createdAt), {
addSuffix: true addSuffix: true
})} })}
</p> </p>
</div> </div>
<div className="flex items-center gap-1 flex-shrink-0"> <div className="flex items-center gap-1 flex-shrink-0">
{!notification.read && ( {!notification.read && (
<button <button
onClick={() => markAsRead(notification.id)} onClick={() => markAsRead(notification.id)}
className={`rounded p-1 transition-colors ${ className={`rounded p-1 transition-colors ${isLight
isLight
? "hover:bg-slate-100" ? "hover:bg-slate-100"
: "hover:bg-white/10" : "hover:bg-white/10"
}`} }`}
title="Mark as read" title="Mark as read"
> >
<CheckCircle2 <CheckCircle2
className={`h-3 w-3 ${ className={`h-3 w-3 ${isLight
isLight
? "text-slate-400" ? "text-slate-400"
: "text-slate-400" : "text-slate-400"
}`} }`}
/> />
</button> </button>
)} )}
<button <button
onClick={() => deleteNotification(notification.id)} onClick={() => deleteNotification(notification.id)}
className={`rounded p-1 transition-colors ${ className={`rounded p-1 transition-colors ${isLight
isLight
? "hover:bg-slate-100" ? "hover:bg-slate-100"
: "hover:bg-white/10" : "hover:bg-white/10"
}`} }`}
title="Delete" title="Delete"
> >
<X <X
className={`h-3 w-3 ${ className={`h-3 w-3 ${isLight
isLight
? "text-slate-400" ? "text-slate-400"
: "text-slate-400" : "text-slate-400"
}`} }`}
/> />
</button> </button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> ))}
))} </div>
</div> )}
)} </div>
</div> </motion.div>
</div> )}
)} </AnimatePresence>
</div> </div>
) )
} }

View File

@ -109,16 +109,7 @@ export default function OrderDetailsModal({ isOpen, onClose, orderId, orderName
return ( return (
<div <div
className="fixed inset-0 z-[9999] bg-black/70 backdrop-blur-md" className="fixed inset-0 z-[9999] bg-black/70 backdrop-blur-md overflow-y-auto overflow-x-hidden"
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
overflowY: 'auto',
overflowX: 'hidden'
}}
onClick={(e) => { onClick={(e) => {
if (e.target === e.currentTarget) { if (e.target === e.currentTarget) {
onClose() onClose()
@ -128,10 +119,6 @@ export default function OrderDetailsModal({ isOpen, onClose, orderId, orderName
<div className="min-h-screen flex items-center justify-center p-4 py-8"> <div className="min-h-screen flex items-center justify-center p-4 py-8">
<div <div
className="bg-slate-900 rounded-2xl border border-white/10 w-full max-w-5xl max-h-[90vh] overflow-hidden flex flex-col shadow-2xl mx-auto" className="bg-slate-900 rounded-2xl border border-white/10 w-full max-w-5xl max-h-[90vh] overflow-hidden flex flex-col shadow-2xl mx-auto"
style={{
maxWidth: 'min(90vw, 80rem)',
width: '100%'
}}
> >
{/* Header */} {/* Header */}
<div className="flex items-center justify-between p-4 md:p-6 border-b border-white/10 flex-shrink-0 bg-slate-900/95 backdrop-blur-sm"> <div className="flex items-center justify-between p-4 md:p-6 border-b border-white/10 flex-shrink-0 bg-slate-900/95 backdrop-blur-sm">
@ -154,201 +141,199 @@ export default function OrderDetailsModal({ isOpen, onClose, orderId, orderName
{/* Content */} {/* Content */}
<div className="flex-1 overflow-y-auto overflow-x-hidden p-4 md:p-6 min-h-0 bg-slate-900 max-w-full" style={{ maxHeight: 'calc(90vh - 140px)' }}> <div className="flex-1 overflow-y-auto overflow-x-hidden p-4 md:p-6 min-h-0 bg-slate-900 max-w-full" style={{ maxHeight: 'calc(90vh - 140px)' }}>
{loading ? ( {loading ? (
<div className="flex items-center justify-center h-64"> <div className="flex items-center justify-center h-64">
<div className="text-slate-400">Loading order details...</div> <div className="text-slate-400">Loading order details...</div>
</div>
) : error ? (
<div className="flex items-center justify-center h-64">
<div className="text-red-400">{error}</div>
</div>
) : order ? (
<div className="space-y-6 max-w-full overflow-hidden">
{/* Order Summary - Top Section */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-white/5 rounded-xl p-5 border border-white/10">
<div className="flex items-center gap-2 text-slate-400 mb-3">
<Calendar className="w-4 h-4" />
<span className="text-xs font-bold uppercase tracking-wider">Order Date</span>
</div>
<p className="text-white font-bold text-lg">{new Date(order.created_at).toLocaleString()}</p>
</div>
<div className="bg-white/5 rounded-xl p-5 border border-white/10">
<div className="flex items-center gap-2 text-slate-400 mb-3">
<DollarSign className="w-4 h-4" />
<span className="text-xs font-bold uppercase tracking-wider">Total Amount</span>
</div>
<p className="text-white font-bold text-2xl">{order.total_price} {order.currency}</p>
</div>
</div> </div>
) : error ? (
{/* Status Cards */} <div className="flex items-center justify-center h-64">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="text-red-400">{error}</div>
<div className="bg-white/5 rounded-xl p-5 border border-white/10">
<span className="text-xs font-bold uppercase text-slate-400 tracking-wider block mb-3">Payment Status</span>
<div>
<span className={`px-3 py-1.5 rounded-full text-xs font-black uppercase tracking-wider inline-flex items-center gap-1.5 ${
order.financial_status === "paid"
? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30"
: "bg-red-500/20 text-red-400 border border-red-500/30"
}`}>
{order.financial_status === "paid" && <CheckCircle2 className="w-3 h-3" />}
{order.financial_status}
</span>
</div>
</div>
<div className="bg-white/5 rounded-xl p-5 border border-white/10">
<span className="text-xs font-bold uppercase text-slate-400 tracking-wider block mb-3">Fulfillment Status</span>
<div>
<span className={`px-3 py-1.5 rounded-full text-xs font-black uppercase tracking-wider inline-flex items-center gap-1.5 ${
order.fulfillment_status === "fulfilled"
? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30"
: "bg-blue-500/20 text-blue-400 border border-blue-500/30"
}`}>
{order.fulfillment_status === "fulfilled" ? <CheckCircle2 className="w-3 h-3" /> : <Clock className="w-3 h-3" />}
{order.fulfillment_status || "Unfulfilled"}
</span>
</div>
</div>
</div> </div>
) : order ? (
{/* Main Content Grid */} <div className="space-y-6 max-w-full overflow-hidden">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 min-w-0 max-w-full"> {/* Order Summary - Top Section */}
{/* Left Column - Customer & Shipping */} <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="lg:col-span-1 space-y-6 min-w-0 max-w-full"> <div className="bg-white/5 rounded-xl p-5 border border-white/10">
{/* Customer Info */} <div className="flex items-center gap-2 text-slate-400 mb-3">
{order.customer && ( <Calendar className="w-4 h-4" />
<div className="bg-white/5 rounded-xl p-5 border border-white/10"> <span className="text-xs font-bold uppercase tracking-wider">Order Date</span>
<div className="flex items-center gap-2 text-slate-400 mb-4">
<User className="w-4 h-4" />
<span className="text-xs font-bold uppercase tracking-wider">Customer</span>
</div>
<div className="space-y-3">
<p className="text-white font-bold text-base">
{order.customer.first_name} {order.customer.last_name}
</p>
<div className="flex items-center gap-2 text-slate-300 text-sm min-w-0">
<Mail className="w-3.5 h-3.5 text-slate-500 flex-shrink-0" />
<span className="break-all min-w-0">{order.customer.email}</span>
</div>
{order.customer.phone && (
<div className="flex items-center gap-2 text-slate-300 text-sm">
<Phone className="w-3.5 h-3.5 text-slate-500" />
<span>{order.customer.phone}</span>
</div>
)}
</div>
</div> </div>
)} <p className="text-white font-bold text-lg">{new Date(order.created_at).toLocaleString()}</p>
</div>
<div className="bg-white/5 rounded-xl p-5 border border-white/10">
<div className="flex items-center gap-2 text-slate-400 mb-3">
<DollarSign className="w-4 h-4" />
<span className="text-xs font-bold uppercase tracking-wider">Total Amount</span>
</div>
<p className="text-white font-bold text-2xl">{order.total_price} {order.currency}</p>
</div>
</div>
{/* Shipping Address */} {/* Status Cards */}
{order.shipping_address && ( <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-white/5 rounded-xl p-5 border border-white/10"> <div className="bg-white/5 rounded-xl p-5 border border-white/10">
<div className="flex items-center gap-2 text-slate-400 mb-4"> <span className="text-xs font-bold uppercase text-slate-400 tracking-wider block mb-3">Payment Status</span>
<MapPin className="w-4 h-4" /> <div>
<span className="text-xs font-bold uppercase tracking-wider">Shipping Address</span> <span className={`px-3 py-1.5 rounded-full text-xs font-black uppercase tracking-wider inline-flex items-center gap-1.5 ${order.financial_status === "paid"
</div> ? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30"
<div className="text-slate-300 text-sm space-y-1.5 break-words"> : "bg-red-500/20 text-red-400 border border-red-500/30"
<p className="font-medium text-white break-words"> }`}>
{order.shipping_address.first_name} {order.shipping_address.last_name} {order.financial_status === "paid" && <CheckCircle2 className="w-3 h-3" />}
</p> {order.financial_status}
<p className="break-words">{order.shipping_address.address1}</p> </span>
{order.shipping_address.address2 && <p className="break-words">{order.shipping_address.address2}</p>} </div>
<p className="break-words"> </div>
{order.shipping_address.city}, {order.shipping_address.province} {order.shipping_address.zip} <div className="bg-white/5 rounded-xl p-5 border border-white/10">
</p> <span className="text-xs font-bold uppercase text-slate-400 tracking-wider block mb-3">Fulfillment Status</span>
<p className="break-words">{order.shipping_address.country}</p> <div>
{order.shipping_address.phone && ( <span className={`px-3 py-1.5 rounded-full text-xs font-black uppercase tracking-wider inline-flex items-center gap-1.5 ${order.fulfillment_status === "fulfilled"
<p className="mt-2 flex items-center gap-2 pt-2 border-t border-white/5"> ? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30"
<Phone className="w-3.5 h-3.5 text-slate-500" /> : "bg-blue-500/20 text-blue-400 border border-blue-500/30"
{order.shipping_address.phone} }`}>
{order.fulfillment_status === "fulfilled" ? <CheckCircle2 className="w-3 h-3" /> : <Clock className="w-3 h-3" />}
{order.fulfillment_status || "Unfulfilled"}
</span>
</div>
</div>
</div>
{/* Main Content Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 min-w-0 max-w-full">
{/* Left Column - Customer & Shipping */}
<div className="lg:col-span-1 space-y-6 min-w-0 max-w-full">
{/* Customer Info */}
{order.customer && (
<div className="bg-white/5 rounded-xl p-5 border border-white/10">
<div className="flex items-center gap-2 text-slate-400 mb-4">
<User className="w-4 h-4" />
<span className="text-xs font-bold uppercase tracking-wider">Customer</span>
</div>
<div className="space-y-3">
<p className="text-white font-bold text-base">
{order.customer.first_name} {order.customer.last_name}
</p> </p>
)} <div className="flex items-center gap-2 text-slate-300 text-sm min-w-0">
</div> <Mail className="w-3.5 h-3.5 text-slate-500 flex-shrink-0" />
</div> <span className="break-all min-w-0">{order.customer.email}</span>
)}
</div>
{/* Right Column - Order Items */}
<div className="lg:col-span-2 min-w-0 max-w-full overflow-hidden">
<div className="bg-white/5 rounded-xl p-5 border border-white/10 min-w-0 max-w-full overflow-hidden">
<div className="flex items-center gap-2 text-slate-400 mb-5">
<Package className="w-4 h-4" />
<span className="text-xs font-bold uppercase tracking-wider">Order Items</span>
</div>
<div className="space-y-4 overflow-x-auto">
{order.line_items.map((item) => (
<div key={item.id} className="bg-white/5 rounded-lg p-4 border border-white/5 min-w-0">
<div className="flex items-start justify-between gap-4 min-w-0">
<div className="flex-1 min-w-0 overflow-hidden">
<p className="text-white font-bold text-base mb-1 break-words">{item.title}</p>
{item.variant_title && (
<p className="text-sm text-slate-400 mb-2 break-words">{item.variant_title}</p>
)}
<div className="flex items-center gap-4 flex-wrap">
<span className="text-sm text-slate-400">
<span className="font-medium text-slate-300">Qty:</span> {item.quantity}
</span>
{item.sku && (
<span className="text-xs text-slate-500 break-all">
<span className="font-medium">SKU:</span> {item.sku}
</span>
)}
</div>
</div>
<div className="text-right flex-shrink-0">
<p className="text-white font-bold text-lg whitespace-nowrap">
{(parseFloat(item.price) * item.quantity).toFixed(2)} {order.currency}
</p>
<p className="text-xs text-slate-400 mt-1 whitespace-nowrap">
{parseFloat(item.price).toFixed(2)} {order.currency} each
</p>
</div>
</div> </div>
{order.customer.phone && (
<div className="flex items-center gap-2 text-slate-300 text-sm">
<Phone className="w-3.5 h-3.5 text-slate-500" />
<span>{order.customer.phone}</span>
</div>
)}
</div> </div>
))} </div>
</div> )}
{/* Order Summary */} {/* Shipping Address */}
{(order.subtotal_price || order.total_tax || (order.total_discounts && parseFloat(order.total_discounts) > 0)) && ( {order.shipping_address && (
<div className="mt-6 pt-6 border-t border-white/10 space-y-3"> <div className="bg-white/5 rounded-xl p-5 border border-white/10">
{order.subtotal_price && ( <div className="flex items-center gap-2 text-slate-400 mb-4">
<div className="flex justify-between text-sm"> <MapPin className="w-4 h-4" />
<span className="text-slate-400">Subtotal</span> <span className="text-xs font-bold uppercase tracking-wider">Shipping Address</span>
<span className="text-white font-medium">{order.subtotal_price} {order.currency}</span> </div>
</div> <div className="text-slate-300 text-sm space-y-1.5 break-words">
)} <p className="font-medium text-white break-words">
{order.total_tax && ( {order.shipping_address.first_name} {order.shipping_address.last_name}
<div className="flex justify-between text-sm"> </p>
<span className="text-slate-400">Tax</span> <p className="break-words">{order.shipping_address.address1}</p>
<span className="text-white font-medium">{order.total_tax} {order.currency}</span> {order.shipping_address.address2 && <p className="break-words">{order.shipping_address.address2}</p>}
</div> <p className="break-words">
)} {order.shipping_address.city}, {order.shipping_address.province} {order.shipping_address.zip}
{order.total_discounts && parseFloat(order.total_discounts) > 0 && ( </p>
<div className="flex justify-between text-sm"> <p className="break-words">{order.shipping_address.country}</p>
<span className="text-slate-400">Discount</span> {order.shipping_address.phone && (
<span className="text-emerald-400 font-medium">-{order.total_discounts} {order.currency}</span> <p className="mt-2 flex items-center gap-2 pt-2 border-t border-white/5">
</div> <Phone className="w-3.5 h-3.5 text-slate-500" />
)} {order.shipping_address.phone}
<div className="flex justify-between text-lg font-bold pt-3 border-t border-white/10"> </p>
<span className="text-white">Total</span> )}
<span className="text-white">{order.total_price} {order.currency}</span>
</div> </div>
</div> </div>
)} )}
</div> </div>
{/* Order Notes */} {/* Right Column - Order Items */}
{order.note && ( <div className="lg:col-span-2 min-w-0 max-w-full overflow-hidden">
<div className="bg-white/5 rounded-xl p-5 border border-white/10 mt-6"> <div className="bg-white/5 rounded-xl p-5 border border-white/10 min-w-0 max-w-full overflow-hidden">
<span className="text-xs font-bold uppercase text-slate-400 tracking-wider block mb-3">Order Notes</span> <div className="flex items-center gap-2 text-slate-400 mb-5">
<p className="text-slate-300 text-sm leading-relaxed">{order.note}</p> <Package className="w-4 h-4" />
<span className="text-xs font-bold uppercase tracking-wider">Order Items</span>
</div>
<div className="space-y-4 overflow-x-auto">
{order.line_items.map((item) => (
<div key={item.id} className="bg-white/5 rounded-lg p-4 border border-white/5 min-w-0">
<div className="flex items-start justify-between gap-4 min-w-0">
<div className="flex-1 min-w-0 overflow-hidden">
<p className="text-white font-bold text-base mb-1 break-words">{item.title}</p>
{item.variant_title && (
<p className="text-sm text-slate-400 mb-2 break-words">{item.variant_title}</p>
)}
<div className="flex items-center gap-4 flex-wrap">
<span className="text-sm text-slate-400">
<span className="font-medium text-slate-300">Qty:</span> {item.quantity}
</span>
{item.sku && (
<span className="text-xs text-slate-500 break-all">
<span className="font-medium">SKU:</span> {item.sku}
</span>
)}
</div>
</div>
<div className="text-right flex-shrink-0">
<p className="text-white font-bold text-lg whitespace-nowrap">
{(parseFloat(item.price) * item.quantity).toFixed(2)} {order.currency}
</p>
<p className="text-xs text-slate-400 mt-1 whitespace-nowrap">
{parseFloat(item.price).toFixed(2)} {order.currency} each
</p>
</div>
</div>
</div>
))}
</div>
{/* Order Summary */}
{(order.subtotal_price || order.total_tax || (order.total_discounts && parseFloat(order.total_discounts) > 0)) && (
<div className="mt-6 pt-6 border-t border-white/10 space-y-3">
{order.subtotal_price && (
<div className="flex justify-between text-sm">
<span className="text-slate-400">Subtotal</span>
<span className="text-white font-medium">{order.subtotal_price} {order.currency}</span>
</div>
)}
{order.total_tax && (
<div className="flex justify-between text-sm">
<span className="text-slate-400">Tax</span>
<span className="text-white font-medium">{order.total_tax} {order.currency}</span>
</div>
)}
{order.total_discounts && parseFloat(order.total_discounts) > 0 && (
<div className="flex justify-between text-sm">
<span className="text-slate-400">Discount</span>
<span className="text-emerald-400 font-medium">-{order.total_discounts} {order.currency}</span>
</div>
)}
<div className="flex justify-between text-lg font-bold pt-3 border-t border-white/10">
<span className="text-white">Total</span>
<span className="text-white">{order.total_price} {order.currency}</span>
</div>
</div>
)}
</div> </div>
)}
{/* Order Notes */}
{order.note && (
<div className="bg-white/5 rounded-xl p-5 border border-white/10 mt-6">
<span className="text-xs font-bold uppercase text-slate-400 tracking-wider block mb-3">Order Notes</span>
<p className="text-slate-300 text-sm leading-relaxed">{order.note}</p>
</div>
)}
</div>
</div> </div>
</div> </div>
</div> ) : null}
) : null}
</div> </div>
{/* Footer */} {/* Footer */}

View File

@ -95,37 +95,32 @@ export default function StoreSwitcher() {
<div className="relative" ref={dropdownRef}> <div className="relative" ref={dropdownRef}>
<button <button
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
className={`flex max-w-full items-center gap-1.5 rounded-xl border px-2 py-2 text-left backdrop-blur-sm transition-all duration-200 ease-out md:gap-2 md:px-4 ${ className={`flex max-w-full items-center gap-1.5 rounded-xl border px-2 py-2 text-left backdrop-blur-sm transition-all duration-200 ease-out md:gap-2 md:px-4 ${isLight
isLight
? "bg-white border-slate-200 hover:bg-slate-50" ? "bg-white border-slate-200 hover:bg-slate-50"
: "bg-white/10 border-white/10 hover:bg-white/15" : "bg-white/10 border-white/10 hover:bg-white/15"
}`} }`}
> >
<Store <Store
className={`h-3.5 w-3.5 shrink-0 md:h-4 md:w-4 ${ className={`h-3.5 w-3.5 shrink-0 md:h-4 md:w-4 ${isLight ? "text-slate-600" : "text-slate-300"
isLight ? "text-slate-600" : "text-slate-300" }`}
}`}
/> />
<div className="flex min-w-0 flex-1 flex-col items-start"> <div className="flex min-w-0 flex-1 flex-col items-start">
<span <span
className={`hidden text-xs font-medium md:block ${ className={`hidden text-xs font-medium md:block ${isLight ? "text-slate-500" : "text-slate-400"
isLight ? "text-slate-500" : "text-slate-400" }`}
}`}
> >
Store Store
</span> </span>
<span <span
className={`truncate text-xs font-bold md:text-sm md:max-w-none max-w-[100px] ${ className={`truncate text-xs font-bold md:text-sm md:max-w-none max-w-[80px] xs:max-w-[120px] ${isLight ? "text-slate-900" : "text-white"
isLight ? "text-slate-900" : "text-white" }`}
}`}
> >
{activeStore?.businessName || "Select Store"} {activeStore?.businessName || "Select Store"}
</span> </span>
</div> </div>
<ChevronDown <ChevronDown
className={`h-3.5 w-3.5 shrink-0 transition-transform duration-200 ease-out md:h-4 md:w-4 ${ className={`h-3.5 w-3.5 shrink-0 transition-transform duration-200 ease-out md:h-4 md:w-4 ${isLight ? "text-slate-500" : "text-slate-400"
isLight ? "text-slate-500" : "text-slate-400" } ${isOpen ? "rotate-180" : ""}`}
} ${isOpen ? "rotate-180" : ""}`}
/> />
</button> </button>
@ -145,21 +140,18 @@ export default function StoreSwitcher() {
animate={{ opacity: 1, y: 0, scale: 1 }} animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -8, scale: 0.95 }} exit={{ opacity: 0, y: -8, scale: 0.95 }}
transition={{ duration: 0.2, ease: [0.4, 0, 0.2, 1] }} transition={{ duration: 0.2, ease: [0.4, 0, 0.2, 1] }}
className={`absolute left-0 z-[110] mt-2 max-w-[320px] overflow-hidden rounded-2xl border shadow-2xl backdrop-blur-xl md:left-auto md:right-0 w-[calc(100vw-2rem)] md:w-72 ${ className={`absolute left-0 z-[110] mt-2 max-w-[320px] overflow-hidden rounded-2xl border shadow-2xl backdrop-blur-xl md:left-auto md:right-0 w-[calc(100vw-2rem)] md:w-72 ${isLight
isLight
? "bg-white border-slate-200" ? "bg-white border-slate-200"
: "bg-slate-900/90 border-white/10" : "bg-slate-900/90 border-white/10"
}`} }`}
> >
<div <div
className={`p-3 border-b ${ className={`p-3 border-b ${isLight ? "bg-slate-50 border-slate-200" : "bg-white/5 border-white/10"
isLight ? "bg-slate-50 border-slate-200" : "bg-white/5 border-white/10" }`}
}`}
> >
<p <p
className={`text-xs font-bold uppercase tracking-wider ${ className={`text-xs font-bold uppercase tracking-wider ${isLight ? "text-slate-500" : "text-slate-400"
isLight ? "text-slate-500" : "text-slate-400" }`}
}`}
> >
Your Stores Your Stores
</p> </p>
@ -175,58 +167,52 @@ export default function StoreSwitcher() {
setIsOpen(false) setIsOpen(false)
}} }}
disabled={loading} disabled={loading}
className={`flex w-full items-center justify-between px-4 py-3 transition-all ${ className={`flex w-full items-center justify-between px-4 py-3 transition-all ${isLight
isLight
? "hover:bg-slate-50" ? "hover:bg-slate-50"
: "hover:bg-white/10" : "hover:bg-white/10"
} ${ } ${store.isActive
store.isActive
? isLight ? isLight
? "bg-indigo-50" ? "bg-indigo-50"
: "bg-indigo-500/20" : "bg-indigo-500/20"
: "" : ""
}`} }`}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div <div
className={`flex h-8 w-8 items-center justify-center rounded-lg ${ className={`flex h-8 w-8 items-center justify-center rounded-lg ${store.isActive
store.isActive
? isLight ? isLight
? "bg-indigo-100 border border-indigo-300" ? "bg-indigo-100 border border-indigo-300"
: "bg-indigo-500/50 border border-indigo-400/30" : "bg-indigo-500/50 border border-indigo-400/30"
: isLight : isLight
? "bg-slate-100" ? "bg-slate-100"
: "bg-white/10" : "bg-white/10"
}`} }`}
> >
<Store <Store
className={`h-4 w-4 ${ className={`h-4 w-4 ${store.isActive
store.isActive
? isLight ? isLight
? "text-indigo-600" ? "text-indigo-600"
: "text-indigo-300" : "text-indigo-300"
: isLight : isLight
? "text-slate-500" ? "text-slate-500"
: "text-slate-400" : "text-slate-400"
}`} }`}
/> />
</div> </div>
<div className="flex flex-col items-start"> <div className="flex flex-col items-start">
<span <span
className={`text-sm font-bold ${ className={`text-sm font-bold ${isLight
isLight
? "text-slate-900" ? "text-slate-900"
: "text-white" : "text-white"
}`} }`}
> >
{store.businessName} {store.businessName}
</span> </span>
<span <span
className={`text-xs ${ className={`text-xs ${isLight
isLight
? "text-slate-500" ? "text-slate-500"
: "text-slate-400" : "text-slate-400"
}`} }`}
> >
{store.shopifyDomain} {store.shopifyDomain}
</span> </span>
@ -234,31 +220,28 @@ export default function StoreSwitcher() {
</div> </div>
{store.isActive && ( {store.isActive && (
<Check <Check
className={`h-4 w-4 ${ className={`h-4 w-4 ${isLight
isLight
? "text-indigo-600" ? "text-indigo-600"
: "text-indigo-400" : "text-indigo-400"
}`} }`}
/> />
)} )}
</button> </button>
))} ))}
</div> </div>
<div <div
className={`p-3 border-t ${ className={`p-3 border-t ${isLight ? "bg-slate-50 border-slate-200" : "bg-white/5 border-white/10"
isLight ? "bg-slate-50 border-slate-200" : "bg-white/5 border-white/10" }`}
}`}
> >
<button <button
onClick={() => { onClick={() => {
setIsOpen(false) setIsOpen(false)
router.push("/dashboard/stores") router.push("/dashboard/stores")
}} }}
className={`flex h-9 w-full items-center justify-center gap-2 rounded-lg text-sm font-bold transition-all border ${ className={`flex h-9 w-full items-center justify-center gap-2 rounded-lg text-sm font-bold transition-all border ${isLight
isLight
? "bg-indigo-600 text-white border-indigo-500 hover:bg-indigo-500" ? "bg-indigo-600 text-white border-indigo-500 hover:bg-indigo-500"
: "bg-white/10 text-white border-white/10 hover:bg-white/15" : "bg-white/10 text-white border-white/10 hover:bg-white/15"
}`} }`}
> >
<Settings className="h-4 w-4" /> <Settings className="h-4 w-4" />
Manage Stores Manage Stores

View File

@ -58,7 +58,7 @@ export function SupplierSidebar() {
</div> </div>
<h1 className="text-xl font-bold tracking-tight">JUNOHUB</h1> <h1 className="text-xl font-bold tracking-tight">JUNOHUB</h1>
</div> </div>
<div className="flex-1 px-3 space-y-1"> <div className="flex-1 px-3 space-y-1 overflow-y-auto">
{supplierRoutes.map((route) => ( {supplierRoutes.map((route) => (
<Link <Link
key={route.href} key={route.href}

View File

@ -13,37 +13,49 @@ export default function ThemeToggle() {
<button <button
type="button" type="button"
onClick={toggleTheme} onClick={toggleTheme}
className="inline-flex items-center gap-1.5 rounded-full border border-white/15 bg-slate-900/40 px-2.5 py-1 text-[11px] font-medium text-slate-200 hover:bg-white/10" className={`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-medium ${isLight
? "border-slate-300 bg-white/80 text-slate-700 hover:bg-slate-100"
: "border-white/15 bg-slate-900/40 text-slate-200 hover:bg-white/10"
}`}
aria-label="Toggle theme" aria-label="Toggle theme"
> >
{isLight ? ( {isLight ? (
<Sun className="h-3.5 w-3.5 text-amber-300" /> <Sun className="h-3.5 w-3.5 text-amber-500" />
) : ( ) : (
<Moon className="h-3.5 w-3.5 text-sky-300" /> <Moon className="h-3.5 w-3.5 text-sky-300" />
)} )}
<span>{isLight ? "Light mode" : "Default mode"}</span> <span className="hidden xs:inline">{isLight ? "Light" : "Default"}</span>
</button> </button>
<div className="hidden md:flex rounded-full border border-white/10 bg-slate-900/50 text-[11px] font-semibold text-slate-300 overflow-hidden"> <div className={`hidden md:flex rounded-full border text-[11px] font-semibold overflow-hidden ${isLight
? "border-slate-300 bg-slate-100 text-slate-600"
: "border-white/10 bg-slate-900/50 text-slate-300"
}`}>
<button <button
type="button" type="button"
onClick={() => setTheme("default")} onClick={() => setTheme("default")}
className={`px-2.5 py-1 ${ className={`px-2.5 py-1 ${theme === "default"
theme === "default" ? isLight
? "bg-white text-slate-900" ? "bg-slate-700 text-white"
: "bg-white text-slate-900"
: isLight
? "bg-transparent hover:bg-slate-200"
: "bg-transparent hover:bg-white/5" : "bg-transparent hover:bg-white/5"
}`} }`}
> >
Default Default
</button> </button>
<button <button
type="button" type="button"
onClick={() => setTheme("light")} onClick={() => setTheme("light")}
className={`px-2.5 py-1 ${ className={`px-2.5 py-1 ${theme === "light"
theme === "light" ? isLight
? "bg-white text-slate-900" ? "bg-slate-700 text-white"
: "bg-white text-slate-900"
: isLight
? "bg-transparent hover:bg-slate-200"
: "bg-transparent hover:bg-white/5" : "bg-transparent hover:bg-white/5"
}`} }`}
> >
Light Light
</button> </button>

View File

@ -4,12 +4,16 @@ import { useState, useEffect, useRef } from "react"
import { useRouter } from "next/navigation" import { useRouter } from "next/navigation"
import { signOut, useSession } from "next-auth/react" import { signOut, useSession } from "next-auth/react"
import { Settings, LogOut, ChevronDown } from "lucide-react" import { Settings, LogOut, ChevronDown } from "lucide-react"
import { motion, AnimatePresence } from "framer-motion"
import { useTheme } from "@/components/ThemeProvider"
export default function UserAccountMenu() { export default function UserAccountMenu() {
const { data: session } = useSession() const { data: session } = useSession()
const router = useRouter() const router = useRouter()
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const menuRef = useRef<HTMLDivElement>(null) const menuRef = useRef<HTMLDivElement>(null)
const { theme } = useTheme()
const isLight = theme === "light"
// Close menu when clicking outside // Close menu when clicking outside
useEffect(() => { useEffect(() => {
@ -49,25 +53,42 @@ export default function UserAccountMenu() {
<ChevronDown className={`w-4 h-4 text-slate-400 transition-transform ${isOpen ? "rotate-180" : ""}`} /> <ChevronDown className={`w-4 h-4 text-slate-400 transition-transform ${isOpen ? "rotate-180" : ""}`} />
</button> </button>
{isOpen && ( <AnimatePresence>
<div className="absolute right-0 top-full mt-2 bg-white/95 backdrop-blur-xl border border-white/20 rounded-xl shadow-xl overflow-hidden z-50 min-w-[180px]"> {isOpen && (
<button <motion.div
onClick={handleSettings} initial={{ opacity: 0, scale: 0.95, y: -4 }}
className="w-full px-4 py-2.5 text-left text-sm font-medium text-slate-700 hover:bg-indigo-500 hover:text-white transition-colors flex items-center gap-2" animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: -4 }}
transition={{ duration: 0.15, ease: "easeOut" }}
className={`absolute right-0 top-full mt-2 backdrop-blur-xl border rounded-xl shadow-xl overflow-hidden z-50 min-w-[180px] ${isLight
? "bg-white/95 border-slate-200"
: "bg-slate-800/95 border-white/10"
}`}
> >
<Settings className="w-4 h-4" /> <button
Settings onClick={handleSettings}
</button> className={`w-full px-4 py-2.5 text-left text-sm font-medium transition-colors flex items-center gap-2 ${isLight
<div className="border-t border-white/10"></div> ? "text-slate-700 hover:bg-indigo-500 hover:text-white"
<button : "text-slate-200 hover:bg-indigo-500 hover:text-white"
onClick={handleLogout} }`}
className="w-full px-4 py-2.5 text-left text-sm font-medium text-red-600 hover:bg-red-500 hover:text-white transition-colors flex items-center gap-2" >
> <Settings className="w-4 h-4" />
<LogOut className="w-4 h-4" /> Settings
Logout </button>
</button> <div className={`border-t ${isLight ? "border-slate-200" : "border-white/10"}`}></div>
</div> <button
)} onClick={handleLogout}
className={`w-full px-4 py-2.5 text-left text-sm font-medium transition-colors flex items-center gap-2 ${isLight
? "text-red-600 hover:bg-red-500 hover:text-white"
: "text-red-400 hover:bg-red-500 hover:text-white"
}`}
>
<LogOut className="w-4 h-4" />
Logout
</button>
</motion.div>
)}
</AnimatePresence>
</div> </div>
) )
} }