WOOHOO
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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
55
lib/auth-utils.ts
Normal 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';
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user