fixed UI and MFA
This commit is contained in:
parent
9d2ca7f592
commit
59d90ed807
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"}
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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 ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -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 |
@ -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
4
src/app/icon.svg
Normal 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 |
@ -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 = {
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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'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'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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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'll automatically switch to another store after deletion.
|
This is your active store. We'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>
|
||||||
|
|||||||
72
src/components/MobileAdminSidebar.tsx
Normal file
72
src/components/MobileAdminSidebar.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 */}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user