This commit is contained in:
NotII
2025-03-14 17:03:54 +00:00
parent 87a58a6736
commit 0c64b5be79
5 changed files with 183 additions and 51 deletions

View File

@@ -27,12 +27,18 @@ export default function ShippingPage() {
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [modalOpen, setModalOpen] = useState<boolean>(false); const [modalOpen, setModalOpen] = useState<boolean>(false);
const [editing, setEditing] = useState<boolean>(false); const [editing, setEditing] = useState<boolean>(false);
const [refreshTrigger, setRefreshTrigger] = useState<number>(0);
const router = useRouter(); const router = useRouter();
const refreshShippingMethods = () => {
setRefreshTrigger(prev => prev + 1);
};
useEffect(() => { useEffect(() => {
const fetchShippingMethodsData = async () => { const fetchShippingMethodsData = async () => {
try { try {
setLoading(true);
const authToken = document.cookie const authToken = document.cookie
.split("; ") .split("; ")
.find((row) => row.startsWith("Authorization=")) .find((row) => row.startsWith("Authorization="))
@@ -65,23 +71,41 @@ export default function ShippingPage() {
}; };
fetchShippingMethodsData(); fetchShippingMethodsData();
}, []); }, [refreshTrigger]); // Add refreshTrigger as a dependency
const handleAddShipping = async () => { const handleAddShipping = async () => {
if (!newShipping.name || !newShipping.price) return; if (!newShipping.name || !newShipping.price) return;
try { try {
const authToken = document.cookie.split("Authorization=")[1]; setLoading(true);
const updatedMethods: ShippingMethod[] = await addShippingMethod( const authToken = document.cookie
.split("; ")
.find((row) => row.startsWith("Authorization="))
?.split("=")[1];
if (!authToken) {
console.error("No auth token found");
return;
}
await addShippingMethod(
authToken, authToken,
newShipping newShipping
); );
setShippingMethods(updatedMethods); // Close modal and reset form before refreshing to avoid UI delays
setNewShipping({ name: "", price: 0 }); // No `_id` needed for new entry
setModalOpen(false); setModalOpen(false);
setNewShipping({ name: "", price: 0 });
// Refresh the list after adding
refreshShippingMethods();
console.log("Shipping method added successfully");
} catch (error) { } catch (error) {
console.error("Error adding shipping method:", error); console.error("Error adding shipping method:", error);
alert("Failed to add shipping method. Please try again.");
} finally {
setLoading(false);
} }
}; };
@@ -89,23 +113,37 @@ export default function ShippingPage() {
if (!newShipping.name || !newShipping.price || !newShipping._id) return; // Ensure `_id` exists if (!newShipping.name || !newShipping.price || !newShipping._id) return; // Ensure `_id` exists
try { try {
const authToken = document.cookie.split("Authorization=")[1]; setLoading(true);
const updatedShipping: ShippingMethod = await updateShippingMethod( const authToken = document.cookie
.split("; ")
.find((row) => row.startsWith("Authorization="))
?.split("=")[1];
if (!authToken) {
console.error("No auth token found");
return;
}
await updateShippingMethod(
authToken, authToken,
newShipping._id, newShipping._id,
newShipping newShipping
); );
setShippingMethods((prevMethods) => // Close modal and reset form before refreshing to avoid UI delays
prevMethods.map((method) => setModalOpen(false);
method._id === updatedShipping._id ? updatedShipping : method
)
);
setNewShipping({ name: "", price: 0 }); setNewShipping({ name: "", price: 0 });
setEditing(false); setEditing(false);
setModalOpen(false);
// Refresh the list after updating
refreshShippingMethods();
console.log("Shipping method updated successfully");
} catch (error) { } catch (error) {
console.error("Error updating shipping method:", error); console.error("Error updating shipping method:", error);
alert("Failed to update shipping method. Please try again.");
} finally {
setLoading(false);
} }
}; };
@@ -114,9 +152,7 @@ export default function ShippingPage() {
const authToken = document.cookie.split("Authorization=")[1]; const authToken = document.cookie.split("Authorization=")[1];
const response = await deleteShippingMethod(authToken, _id); const response = await deleteShippingMethod(authToken, _id);
if (response.success) { if (response.success) {
setShippingMethods((prevMethods) => refreshShippingMethods(); // Refresh the list after deleting
prevMethods.filter((method) => method._id !== _id)
);
} else { } else {
console.error("Deletion was not successful."); console.error("Deletion was not successful.");
} }
@@ -141,7 +177,11 @@ export default function ShippingPage() {
<h1 className="text-2xl font-semibold text-gray-900 dark:text-white"> <h1 className="text-2xl font-semibold text-gray-900 dark:text-white">
Manage Shipping Options Manage Shipping Options
</h1> </h1>
<Button onClick={() => setModalOpen(true)}> <Button onClick={() => {
setNewShipping({ name: "", price: 0 });
setEditing(false);
setModalOpen(true);
}}>
<Plus className="mr-2 h-5 w-5" /> <Plus className="mr-2 h-5 w-5" />
Add Shipping Method Add Shipping Method
</Button> </Button>
@@ -159,7 +199,11 @@ export default function ShippingPage() {
{/* Shipping Modal */} {/* Shipping Modal */}
<ShippingModal <ShippingModal
open={modalOpen} open={modalOpen}
onClose={() => setModalOpen(false)} onClose={() => {
setNewShipping({ name: "", price: 0 });
setEditing(false);
setModalOpen(false);
}}
onSave={editing ? handleUpdateShipping : handleAddShipping} onSave={editing ? handleUpdateShipping : handleAddShipping}
shippingData={newShipping} shippingData={newShipping}
editing={editing} editing={editing}

View File

@@ -7,11 +7,28 @@ import { ShoppingCart, LogOut } from "lucide-react"
import { NavItem } from "./nav-item" import { NavItem } from "./nav-item"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { sidebarConfig } from "@/config/sidebar" import { sidebarConfig } from "@/config/sidebar"
import { logoutUser } from "@/lib/auth-utils"
import { toast } from "sonner"
const Sidebar: React.FC = () => { const Sidebar: React.FC = () => {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const router = useRouter() const router = useRouter()
const handleLogout = async () => {
try {
// Show toast notification for better user experience
toast.success("Logging out...");
// Perform the logout
await logoutUser();
// The logoutUser function will handle the redirect
} catch (error) {
console.error("Error during logout:", error);
toast.error("Failed to logout. Please try again.");
}
};
return ( return (
<> <>
<nav <nav
@@ -48,7 +65,7 @@ const Sidebar: React.FC = () => {
<div className="p-4 border-t border-border"> <div className="p-4 border-t border-border">
<Button <Button
onClick={() => {}} onClick={handleLogout}
variant="ghost" variant="ghost"
className="w-full justify-start text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-500 hover:bg-background" className="w-full justify-start text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-500 hover:bg-background"
> >

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { ChangeEvent } from "react"; import { ChangeEvent, FormEvent } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@@ -25,6 +25,12 @@ export const ShippingModal = ({
handleChange, handleChange,
setShippingData, setShippingData,
}: ShippingModalProps) => { }: ShippingModalProps) => {
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
onSave(shippingData);
};
return ( return (
<Dialog open={open} onOpenChange={onClose}> <Dialog open={open} onOpenChange={onClose}>
<DialogContent className="sm:max-w-[600px]"> <DialogContent className="sm:max-w-[600px]">
@@ -34,38 +40,42 @@ export const ShippingModal = ({
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<div className="grid gap-4 py-4"> <form onSubmit={handleSubmit}>
<div className="space-y-2"> <div className="grid gap-4 py-4">
<label className="text-sm font-medium">Method Name</label> <div className="space-y-2">
<Input <label className="text-sm font-medium">Method Name</label>
name="name" <Input
placeholder="Shipping Method Name" name="name"
value={shippingData.name} placeholder="Shipping Method Name"
onChange={handleChange} value={shippingData.name}
/> onChange={handleChange}
required
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Price</label>
<Input
name="price"
type="number"
step="0.01"
placeholder="Price (£)"
value={shippingData.price}
onChange={handleChange}
required
/>
</div>
</div> </div>
<div className="space-y-2"> <DialogFooter className="flex justify-end gap-2">
<label className="text-sm font-medium">Price</label> <Button type="button" variant="outline" onClick={onClose}>
<Input Cancel
name="price" </Button>
type="number" <Button type="submit">
step="0.01" {editing ? "Update Shipping Method" : "Create Shipping Method"}
placeholder="Price (£)" </Button>
value={shippingData.price} </DialogFooter>
onChange={handleChange} </form>
/>
</div>
</div>
<DialogFooter className="flex justify-end gap-2">
<Button variant="outline" onClick={onClose}>
Cancel
</Button>
<Button onClick={() => onSave(shippingData)}>
{editing ? "Update Shipping Method" : "Create Shipping Method"}
</Button>
</DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );

55
lib/auth-utils.ts Normal file
View File

@@ -0,0 +1,55 @@
/**
* Auth utilities for managing authentication state
*/
/**
* Get the authentication token from cookies or localStorage
*/
export function getAuthToken(): string | null {
return document.cookie
.split('; ')
.find(row => row.startsWith('Authorization='))
?.split('=')[1] || localStorage.getItem('Authorization');
}
/**
* Check if the user is logged in
*/
export function isLoggedIn(): boolean {
return !!getAuthToken();
}
/**
* Logout the user by removing auth tokens and redirecting
*/
export async function logoutUser(): Promise<void> {
const token = getAuthToken();
if (token) {
try {
// Try to logout on the server (if this fails, we still proceed with client logout)
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/logout`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}).catch(err => {
// Silently catch errors - we still want to proceed with local logout
console.warn('Server logout failed:', err);
});
} catch (error) {
console.warn('Error during server logout:', error);
// Continue with client-side logout regardless of server response
}
}
// Remove the auth token from cookies
document.cookie = 'Authorization=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; SameSite=Strict';
// Remove from localStorage as backup
localStorage.removeItem('Authorization');
// Redirect to login page
window.location.href = '/auth/login';
}

View File

@@ -47,6 +47,12 @@ export const addShippingMethod = async (
} }
); );
// If fetchData returns directly (not a Response object), just return it
if (!res.ok && !res.status) {
return res;
}
// Handle if it's a Response object
if (!res.ok) { if (!res.ok) {
const errorData = await res.json(); const errorData = await res.json();
throw new Error(errorData.message || "Failed to add shipping method"); throw new Error(errorData.message || "Failed to add shipping method");
@@ -102,7 +108,7 @@ export const updateShippingMethod = async (
); );
if (!res) throw new Error("Failed to update shipping method"); if (!res) throw new Error("Failed to update shipping method");
return res return res;
} catch (error) { } catch (error) {
console.error("Error updating shipping method:", error); console.error("Error updating shipping method:", error);
throw error; throw error;