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,31 +300,39 @@ 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 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => !saving && setEditing(null)}
className={`absolute inset-0 pointer-events-auto backdrop-blur-sm ${isLight ? "bg-black/40" : "bg-black/60"
}`} }`}
> />
<div
className={`flex w-full max-h-[90vh] max-w-3xl flex-col rounded-2xl border shadow-2xl ${ {/* Modal */}
isLight <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-white text-slate-900 border-slate-200"
: "bg-slate-950 text-slate-50 border-white/10" : "bg-slate-900 text-slate-50 border-white/10"
}`} }`}
> >
<div <div
className={`flex items-center justify-between px-5 py-4 border-b ${ className={`flex items-center justify-between px-6 py-5 border-b ${isLight ? "border-slate-100" : "border-white/10"
isLight ? "border-slate-200" : "border-white/10"
}`} }`}
> >
<div> <div>
<h2 className="text-lg font-bold"> <h2 className="text-xl font-bold">
{editing.id ? "Edit template" : "New template"} {editing.id ? "Edit template" : "New template"}
</h2> </h2>
<p <p
className={`mt-0.5 text-xs ${ className={`mt-1 text-sm ${isLight ? "text-slate-500" : "text-slate-400"
isLight ? "text-slate-500" : "text-slate-400"
}`} }`}
> >
Use {"{{variable}}"} placeholders to personalize replies. Use {"{{variable}}"} placeholders to personalize replies.
@ -316,22 +341,22 @@ export default function EmailTemplatesPage() {
<button <button
type="button" type="button"
onClick={() => setEditing(null)} onClick={() => setEditing(null)}
className={`text-sm ${ className={`w-10 h-10 rounded-xl flex items-center justify-center transition-colors ${isLight
isLight ? "bg-slate-100 hover:bg-slate-200 text-slate-600"
? "text-slate-500 hover:text-slate-900" : "bg-white/10 hover:bg-white/15 text-slate-300"
: "text-slate-400 hover:text-white"
}`} }`}
> >
Close <Plus className="w-5 h-5 rotate-45" />
</button> </button>
</div> </div>
<div className="flex-1 overflow-y-auto px-5 py-4 space-y-4"> <div className="flex-1 overflow-y-auto px-6 py-6 space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-3"> <div className="space-y-4">
<div> <div>
<label className="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1"> <label className={`block text-xs font-bold uppercase tracking-wider mb-2 ${isLight ? "text-slate-500" : "text-slate-400"
Name }`}>
Template Name
</label> </label>
<input <input
type="text" type="text"
@ -343,13 +368,18 @@ export default function EmailTemplatesPage() {
: prev, : 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="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>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div> <div>
<label className="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1"> <label className={`block text-xs font-bold uppercase tracking-wider mb-2 ${isLight ? "text-slate-500" : "text-slate-400"
}`}>
Intent Intent
</label> </label>
<select <select
@ -364,7 +394,10 @@ export default function EmailTemplatesPage() {
: prev, : 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" 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) => ( {INTENT_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}> <option key={opt.value} value={opt.value}>
@ -374,7 +407,8 @@ export default function EmailTemplatesPage() {
</select> </select>
</div> </div>
<div> <div>
<label className="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1"> <label className={`block text-xs font-bold uppercase tracking-wider mb-2 ${isLight ? "text-slate-500" : "text-slate-400"
}`}>
Mood Mood
</label> </label>
<select <select
@ -389,7 +423,10 @@ export default function EmailTemplatesPage() {
: prev, : 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" 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) => ( {MOOD_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}> <option key={opt.value} value={opt.value}>
@ -401,7 +438,8 @@ export default function EmailTemplatesPage() {
</div> </div>
<div> <div>
<label className="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1"> <label className={`block text-xs font-bold uppercase tracking-wider mb-2 ${isLight ? "text-slate-500" : "text-slate-400"
}`}>
Description (optional) Description (optional)
</label> </label>
<input <input
@ -417,49 +455,50 @@ export default function EmailTemplatesPage() {
: prev, : 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" 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> </div>
<div className="space-y-2 text-xs text-slate-600 dark:text-slate-400"> <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="font-semibold text-slate-800 dark:text-slate-200"> }`}>
<p className={`text-xs font-bold uppercase tracking-wider mb-3 ${isLight ? "text-indigo-600" : "text-indigo-400"
}`}>
Available variables Available variables
</p> </p>
<ul className="space-y-1"> <div className="grid grid-cols-1 gap-2">
<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]"> { key: "{{customer_name}}", label: "Customer's name" },
{"{{customer_name}}"} { key: "{{customer_email}}", label: "Customer's email" },
</code>{" "} { key: "{{store_name}}", label: "Store name" },
– Customer’s name { key: "{{ticket_summary}}", label: "Email summary" }
</li> ].map((v) => (
<li> <div key={v.key} className="flex items-center justify-between text-[11px]">
<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]"> <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"
{"{{customer_email}}"} }`}>
</code>{" "} {v.key}
– Customer’s email </code>
</li> <span className={isLight ? "text-slate-500" : "text-slate-400"}>{v.label}</span>
<li> </div>
<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}}"} </div>
</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>
<div> <div className="space-y-4">
<label className="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1"> <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 Template body
</label> </label>
<span className={`text-[10px] ${isLight ? "text-slate-400" : "text-slate-500"}`}>
Markdown & HTML supported
</span>
</div>
<textarea <textarea
value={editing.body} value={editing.body}
onChange={(e) => onChange={(e) =>
@ -468,29 +507,40 @@ export default function EmailTemplatesPage() {
) )
} }
rows={8} 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" 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>
<div> <div className="space-y-4">
<p className="text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1"> <label className={`block text-xs font-bold uppercase tracking-wider ${isLight ? "text-slate-500" : "text-slate-400"
}`}>
Preview with example data Preview with example data
</p> </label>
<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"> <div className={`rounded-xl border p-5 text-sm leading-relaxed whitespace-pre-wrap ${isLight
{currentPreview || "Preview will appear here."} ? "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>
</div> </div>
<div className="px-5 py-3 border-t border-slate-200 dark:border-white/10 flex items-center justify-between"> <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] text-slate-500 dark:text-slate-500"> }`}>
Templates use simple {"{{variable}}"} placeholders only. <p className={`text-[11px] font-medium ${isLight ? "text-slate-500" : "text-slate-500"}`}>
Autosaved to your draft templates.
</p> </p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-3">
<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={`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 Cancel
</button> </button>
@ -498,15 +548,24 @@ export default function EmailTemplatesPage() {
type="button" type="button"
disabled={saving} disabled={saving}
onClick={() => void handleSave()} 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" 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 ? "Saving..." : "Save template"} {saving ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Saving...
</>
) : (
"Save template"
)}
</button> </button>
</div> </div>
</div> </div>
</div> </motion.div>
</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 />
<div className="hidden xs:block">
<NotificationBell /> <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

@ -281,24 +281,24 @@ export default function OrdersPage() {
<MoreHorizontal className="w-4 h-4" /> <MoreHorizontal className="w-4 h-4" />
</button> </button>
{openMenuId === order.id && ( {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"> <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 <button
onClick={() => handleViewDetails(order)} 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" 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" /> <Eye className="w-4 h-4" />
Details Details
</button> </button>
<button <button
onClick={() => handleViewInShopify(order.id)} 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" 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" /> <ExternalLink className="w-4 h-4" />
Show on Shopify Show on Shopify
</button> </button>
<button <button
onClick={() => handleCopyOrderId(order.id)} 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" 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 className="w-4 h-4" />
Copy Order ID Copy Order ID
@ -331,8 +331,7 @@ export default function OrdersPage() {
</div> </div>
<div className="flex items-center gap-2 flex-wrap"> <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 ${ <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"
order.financial_status === "paid"
? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30" ? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30"
: "bg-red-500/20 text-red-400 border border-red-500/30" : "bg-red-500/20 text-red-400 border border-red-500/30"
}`}> }`}>

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,9 +205,8 @@ export default function SignupPage() {
Log in here Log in here
</Link> </Link>
</p> </p>
</div>
</motion.div> <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="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"> <div className="flex items-center gap-4">
<Link <Link
href="/privacy-policy" href="/privacy-policy"
@ -225,5 +224,7 @@ export default function SignupPage() {
</div> </div>
</div> </div>
</div> </div>
</motion.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,37 +142,45 @@ 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 }} initial={{ scale: 0.95, opacity: 0, y: 20 }}
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1, y: 0 }}
exit={{ scale: 0.95, opacity: 0 }} exit={{ scale: 0.95, opacity: 0, y: 20 }}
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" 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
? "bg-white border border-slate-200"
: "bg-slate-900 border border-white/10"
}`}
> >
{/* Header */} {/* Header */}
<div className="flex items-center justify-between p-6 border-b border-white/10"> <div className={`flex items-center justify-between p-6 border-b ${isLight ? "border-slate-100" : "border-white/10"
}`}>
<div> <div>
<h2 className="text-2xl font-bold text-white">Add New Store</h2> <h2 className={`text-2xl font-bold ${isLight ? "text-slate-900" : "text-white"}`}>Add New Store</h2>
<p className="text-sm text-slate-400 mt-1">Connect a Shopify store to your account</p> <p className={`text-sm mt-1 ${isLight ? "text-slate-500" : "text-slate-400"}`}>Connect a Shopify store to your account</p>
</div> </div>
<button <button
onClick={handleClose} onClick={handleClose}
disabled={loading || success} 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" 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" /> <X className="w-5 h-5" />
</button> </button>
@ -172,11 +189,12 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
{/* 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>
<div className="h-1.5 bg-white/10 rounded-full overflow-hidden"> <div className={`h-1.5 rounded-full overflow-hidden ${isLight ? "bg-slate-100" : "bg-white/10"
}`}>
<motion.div <motion.div
initial={{ width: "0%" }} initial={{ width: "0%" }}
animate={{ width: `${(step / 3) * 100}%` }} animate={{ width: `${(step / 3) * 100}%` }}
@ -195,11 +213,14 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1 }}
className="flex flex-col items-center justify-center py-12 text-center" 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"> <div className={`w-16 h-16 rounded-full flex items-center justify-center mb-4 ${isLight
<CheckCircle2 className="w-8 h-8 text-emerald-400" /> ? "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> </div>
<h3 className="text-xl font-bold text-white mb-2">Store Added Successfully!</h3> <h3 className={`text-xl font-bold mb-2 ${isLight ? "text-slate-900" : "text-white"}`}>Store Added Successfully!</h3>
<p className="text-slate-400">Your store has been connected and is ready to use.</p> <p className={`${isLight ? "text-slate-600" : "text-slate-400"}`}>Your store has been connected and is ready to use.</p>
</motion.div> </motion.div>
) : step === 1 ? ( ) : step === 1 ? (
<motion.div <motion.div
@ -210,51 +231,63 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
className="space-y-6" className="space-y-6"
> >
<div className="flex items-center gap-4"> <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"> <div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isLight
<Globe className="w-6 h-6 text-indigo-400" /> ? "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>
<div> <div>
<h3 className="text-lg font-bold text-white">Store Information</h3> <h3 className={`text-lg font-bold ${isLight ? "text-slate-900" : "text-white"}`}>Store Information</h3>
<p className="text-sm text-slate-400">Enter your store details</p> <p className={`text-sm ${isLight ? "text-slate-500" : "text-slate-400"}`}>Enter your store details</p>
</div> </div>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="block text-sm font-bold text-slate-300 mb-2"> <label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Business Name <span className="text-red-400">*</span> Business Name <span className="text-red-500">*</span>
</label> </label>
<input <input
name="businessName" name="businessName"
value={formData.businessName} value={formData.businessName}
onChange={handleInputChange} onChange={handleInputChange}
placeholder="e.g. My Store" 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" 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 className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<label className="block text-sm font-bold text-slate-300 mb-2"> <label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Shopify Store Name <span className="text-red-400">*</span> Shopify Store Name <span className="text-red-500">*</span>
</label> </label>
<input <input
name="storeName" name="storeName"
value={formData.storeName} value={formData.storeName}
onChange={handleInputChange} onChange={handleInputChange}
placeholder="my-store" 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" 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> <div>
<label className="block text-sm font-bold text-slate-300 mb-2"> <label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Domain <span className="text-red-400">*</span> Domain <span className="text-red-500">*</span>
</label> </label>
<input <input
name="domain" name="domain"
value={formData.domain} value={formData.domain}
onChange={handleInputChange} onChange={handleInputChange}
placeholder="my-store.myshopify.com" 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" 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> </div>
@ -269,19 +302,22 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
className="space-y-6" className="space-y-6"
> >
<div className="flex items-center gap-4"> <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"> <div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isLight
<Key className="w-6 h-6 text-indigo-400" /> ? "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>
<div> <div>
<h3 className="text-lg font-bold text-white">API Credentials</h3> <h3 className={`text-lg font-bold ${isLight ? "text-slate-900" : "text-white"}`}>API Credentials</h3>
<p className="text-sm text-slate-400">Provide your Shopify Admin API credentials</p> <p className={`text-sm ${isLight ? "text-slate-500" : "text-slate-400"}`}>Provide your Shopify Admin API credentials</p>
</div> </div>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="block text-sm font-bold text-slate-300 mb-2"> <label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Admin API Key <span className="text-red-400">*</span> Admin API Key <span className="text-red-500">*</span>
</label> </label>
<input <input
name="apiKey" name="apiKey"
@ -289,12 +325,15 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
value={formData.apiKey} value={formData.apiKey}
onChange={handleInputChange} onChange={handleInputChange}
placeholder="Enter API Key" 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" 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> <div>
<label className="block text-sm font-bold text-slate-300 mb-2"> <label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Admin API Secret <span className="text-red-400">*</span> Admin API Secret <span className="text-red-500">*</span>
</label> </label>
<input <input
name="apiSecret" name="apiSecret"
@ -302,12 +341,15 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
value={formData.apiSecret} value={formData.apiSecret}
onChange={handleInputChange} onChange={handleInputChange}
placeholder="Enter API Secret" 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" 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> <div>
<label className="block text-sm font-bold text-slate-300 mb-2"> <label className={`block text-sm font-bold mb-2 ${isLight ? "text-slate-700" : "text-slate-300"}`}>
Access Token <span className="text-red-400">*</span> Access Token <span className="text-red-500">*</span>
</label> </label>
<input <input
name="accessToken" name="accessToken"
@ -315,7 +357,10 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
value={formData.accessToken} value={formData.accessToken}
onChange={handleInputChange} onChange={handleInputChange}
placeholder="shpat_..." 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" 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> </div>
@ -328,8 +373,9 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
exit={{ x: -20, opacity: 0 }} exit={{ x: -20, opacity: 0 }}
className="flex flex-col items-center justify-center py-8 text-center" 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"> <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-400 animate-pulse" : "text-indigo-400"}`} /> }`}>
<Cloud className={`w-8 h-8 ${loading ? "text-indigo-500 animate-pulse" : "text-indigo-500"}`} />
{loading && ( {loading && (
<motion.div <motion.div
animate={{ rotate: 360 }} animate={{ rotate: 360 }}
@ -338,8 +384,8 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
/> />
)} )}
</div> </div>
<h3 className="text-lg font-bold text-white mb-2">Connecting Store</h3> <h3 className={`text-lg font-bold mb-2 ${isLight ? "text-slate-900" : "text-white"}`}>Connecting Store</h3>
<p className="text-sm text-slate-400 max-w-sm"> <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... We&apos;re connecting your store and setting up the integration...
</p> </p>
</motion.div> </motion.div>
@ -350,21 +396,28 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
<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="mt-4 p-4 rounded-xl bg-red-500/10 border border-red-500/20 flex items-start gap-3" className={`mt-4 p-4 rounded-xl flex items-start gap-3 ${isLight
? "bg-red-50 border border-red-100"
: "bg-red-500/10 border border-red-500/20"
}`}
> >
<AlertCircle className="w-5 h-5 text-red-400 shrink-0 mt-0.5" /> <AlertCircle className="w-5 h-5 text-red-500 shrink-0 mt-0.5" />
<p className="text-sm text-red-400">{error}</p> <p className={`text-sm ${isLight ? "text-red-700" : "text-red-400"}`}>{error}</p>
</motion.div> </motion.div>
)} )}
</div> </div>
{/* Footer */} {/* Footer */}
{!success && ( {!success && (
<div className="p-6 border-t border-white/10 flex items-center justify-between gap-4"> <div className={`p-6 border-t flex items-center justify-between gap-4 ${isLight ? "border-slate-100" : "border-white/10"
}`}>
<button <button
onClick={prevStep} onClick={prevStep}
disabled={step === 1 || loading} disabled={step === 1 || loading}
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" 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 Back
</button> </button>
@ -396,8 +449,8 @@ export default function AddStoreModal({ isOpen, onClose, onSuccess }: AddStoreMo
)} )}
</motion.div> </motion.div>
</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,7 +61,7 @@ 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">
@ -78,7 +84,8 @@ export function MobileSidebar() {
<Sidebar /> <Sidebar />
</div> </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,13 +126,11 @@ 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 && (
@ -141,22 +140,24 @@ export default function NotificationBell() {
)} )}
</button> </button>
<AnimatePresence>
{isOpen && ( {isOpen && (
<div <motion.div
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 ${ initial={{ opacity: 0, scale: 0.95, y: -4 }}
isLight 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 <div
className={`flex items-center justify-between border-b p-4 ${ className={`flex items-center justify-between border-b p-4 ${isLight ? "border-slate-200" : "border-white/10"
isLight ? "border-slate-200" : "border-white/10"
}`} }`}
> >
<h3 <h3
className={`font-bold ${ className={`font-bold ${isLight ? "text-slate-900" : "text-white"
isLight ? "text-slate-900" : "text-white"
}`} }`}
> >
Notifications Notifications
@ -164,8 +165,7 @@ export default function NotificationBell() {
{unreadCount > 0 && ( {unreadCount > 0 && (
<button <button
onClick={markAllAsRead} onClick={markAllAsRead}
className={`text-xs font-medium ${ className={`text-xs font-medium ${isLight
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"
}`} }`}
@ -179,8 +179,7 @@ export default function NotificationBell() {
{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"
}`} }`}
@ -189,15 +188,13 @@ export default function NotificationBell() {
) : 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"
}`} }`}
@ -207,8 +204,7 @@ export default function NotificationBell() {
</div> </div>
) : ( ) : (
<div <div
className={`divide-y ${ className={`divide-y ${isLight
isLight
? "divide-slate-100" ? "divide-slate-100"
: "divide-white/10" : "divide-white/10"
}`} }`}
@ -216,12 +212,10 @@ export default function NotificationBell() {
{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"
@ -236,8 +230,7 @@ export default function NotificationBell() {
<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 ${ className={`mb-1 text-sm font-semibold ${isLight
isLight
? "text-slate-900" ? "text-slate-900"
: "text-white" : "text-white"
}`} }`}
@ -245,8 +238,7 @@ export default function NotificationBell() {
{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"
}`} }`}
@ -254,8 +246,7 @@ export default function NotificationBell() {
{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"
}`} }`}
@ -269,16 +260,14 @@ export default function NotificationBell() {
{!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"
}`} }`}
@ -287,16 +276,14 @@ export default function NotificationBell() {
)} )}
<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"
}`} }`}
@ -311,8 +298,9 @@ export default function NotificationBell() {
</div> </div>
)} )}
</div> </div>
</div> </motion.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">
@ -187,8 +174,7 @@ export default function OrderDetailsModal({ isOpen, onClose, orderId, orderName
<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">
<span className="text-xs font-bold uppercase text-slate-400 tracking-wider block mb-3">Payment Status</span> <span className="text-xs font-bold uppercase text-slate-400 tracking-wider block mb-3">Payment Status</span>
<div> <div>
<span className={`px-3 py-1.5 rounded-full text-xs font-black uppercase tracking-wider inline-flex items-center gap-1.5 ${ <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"
order.financial_status === "paid"
? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30" ? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30"
: "bg-red-500/20 text-red-400 border border-red-500/30" : "bg-red-500/20 text-red-400 border border-red-500/30"
}`}> }`}>
@ -200,8 +186,7 @@ export default function OrderDetailsModal({ isOpen, onClose, orderId, orderName
<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">
<span className="text-xs font-bold uppercase text-slate-400 tracking-wider block mb-3">Fulfillment Status</span> <span className="text-xs font-bold uppercase text-slate-400 tracking-wider block mb-3">Fulfillment Status</span>
<div> <div>
<span className={`px-3 py-1.5 rounded-full text-xs font-black uppercase tracking-wider inline-flex items-center gap-1.5 ${ <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"
order.fulfillment_status === "fulfilled"
? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30" ? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30"
: "bg-blue-500/20 text-blue-400 border border-blue-500/30" : "bg-blue-500/20 text-blue-400 border border-blue-500/30"
}`}> }`}>

View File

@ -95,36 +95,31 @@ 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,20 +140,17 @@ 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
@ -175,12 +167,10 @@ 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"
@ -189,8 +179,7 @@ export default function StoreSwitcher() {
> >
<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"
@ -200,8 +189,7 @@ export default function StoreSwitcher() {
}`} }`}
> >
<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"
@ -213,8 +201,7 @@ export default function StoreSwitcher() {
</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"
}`} }`}
@ -222,8 +209,7 @@ export default function StoreSwitcher() {
{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"
}`} }`}
@ -234,8 +220,7 @@ 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"
}`} }`}
@ -245,8 +230,7 @@ export default function StoreSwitcher() {
))} ))}
</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
@ -254,8 +238,7 @@ export default function StoreSwitcher() {
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"
}`} }`}

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,24 +13,33 @@ 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"
}`} }`}
> >
@ -39,9 +48,12 @@ export default function ThemeToggle() {
<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"
}`} }`}
> >

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>
<AnimatePresence>
{isOpen && ( {isOpen && (
<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]"> <motion.div
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 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"
}`}
>
<button <button
onClick={handleSettings} onClick={handleSettings}
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" className={`w-full px-4 py-2.5 text-left text-sm font-medium transition-colors flex items-center gap-2 ${isLight
? "text-slate-700 hover:bg-indigo-500 hover:text-white"
: "text-slate-200 hover:bg-indigo-500 hover:text-white"
}`}
> >
<Settings className="w-4 h-4" /> <Settings className="w-4 h-4" />
Settings Settings
</button> </button>
<div className="border-t border-white/10"></div> <div className={`border-t ${isLight ? "border-slate-200" : "border-white/10"}`}></div>
<button <button
onClick={handleLogout} 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" 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 className="w-4 h-4" />
Logout Logout
</button> </button>
</div> </motion.div>
)} )}
</AnimatePresence>
</div> </div>
) )
} }