diff --git a/src/app/dashboard/(user-dashboard)/layout.tsx b/src/app/dashboard/(user-dashboard)/layout.tsx index 5c4b1925c..472a3d467 100644 --- a/src/app/dashboard/(user-dashboard)/layout.tsx +++ b/src/app/dashboard/(user-dashboard)/layout.tsx @@ -8,7 +8,7 @@ export default function AdminLayout({ children: React.ReactNode; }) { return ( -
+
{children} diff --git a/src/app/dashboard/(user-dashboard)/products/[productId]/form-images/project-logo.tsx b/src/app/dashboard/(user-dashboard)/products/[productId]/form-images/project-logo.tsx index c8cffcf8b..ce822e4d4 100644 --- a/src/app/dashboard/(user-dashboard)/products/[productId]/form-images/project-logo.tsx +++ b/src/app/dashboard/(user-dashboard)/products/[productId]/form-images/project-logo.tsx @@ -17,7 +17,7 @@ const handleImageChange = (event: React.ChangeEvent) => { const ProjectLogo = ({ form, name }: Properties) => { const [isDragging, setIsDragging] = useState(false); - const projectLogo = form.getValues("media"); + const productImage = form.getValues("media"); const handleDrop = (event: React.DragEvent) => { event.preventDefault(); @@ -55,18 +55,19 @@ const ProjectLogo = ({ form, name }: Properties) => { {isDragging && (
-

Drop to upload

+

Drop to upload

)} - {projectLogo.url && typeof projectLogo.url === "string" ? ( -
+ {productImage.url && typeof productImage.url === "string" ? ( +
- + - + {product.name} - {product.status === "in_stock" && "In Stock"} - {product.status === "low_on_stock" && "Low on Stock"} - {product.status === "out_of_stock" && "Out of Stock"} + + {" "} + {product.status === "in_stock" && "In Stock"} + {product.status === "low_on_stock" && "Low on Stock"} + {product.status === "out_of_stock" && "Out of Stock"} +
-
+
-
diff --git a/src/app/dashboard/(user-dashboard)/products/_components/product-detail-view.tsx b/src/app/dashboard/(user-dashboard)/products/_components/product-detail-view.tsx index 7e13ae354..b5933bfea 100644 --- a/src/app/dashboard/(user-dashboard)/products/_components/product-detail-view.tsx +++ b/src/app/dashboard/(user-dashboard)/products/_components/product-detail-view.tsx @@ -1,5 +1,6 @@ import { AnimatePresence, motion } from "framer-motion"; import { X } from "lucide-react"; +import { useRouter } from "next-nprogress-bar"; import { useEffect, useTransition } from "react"; import BlurImage from "~/components/miscellaneous/blur-image"; @@ -11,6 +12,7 @@ import { useProducts } from "~/hooks/admin-product/use-products.persistence"; import { cn, formatPrice, simulateDelay } from "~/lib/utils"; const ProductDetailView = () => { + const router = useRouter(); const { products, deleteProduct } = useProducts(); const [isLoading, startTransition] = useTransition(); const { @@ -46,6 +48,12 @@ const ProductDetailView = () => { setIsDelete(false); }); }; + const handleEditAction = (id: string) => { + updateOpen(false); + router.push(`/dashboard/products/${id}`); + updateProductId("null"); + }; + useEffect(() => { document.title = isOpen ? `Product - ${product?.name}` @@ -59,8 +67,8 @@ const ProductDetailView = () => { initial={{ opacity: 0, x: 100 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: 100 }} - transition={{ duration: 0.5 }} - className="sticky top-0 hidden w-full min-w-[350px] flex-col gap-y-5 rounded-[6px] border border-gray-300 bg-white px-2 py-4 shadow-[0px_1px_18px_0px_rgba(10,_57,_176,_0.12)] lg:flex lg:max-w-[370px] xl:w-[403px] xl:px-4" + transition={{ duration: 0.3 }} + className="sticky top-0 hidden w-full min-w-[340px] flex-col gap-y-5 rounded-[6px] border border-gray-300 bg-white px-2 py-4 shadow-[0px_1px_18px_0px_rgba(10,_57,_176,_0.12)] lg:flex lg:max-w-[370px] xl:w-[403px] xl:px-4" >
{ Delete )} -
diff --git a/src/app/dashboard/(user-dashboard)/products/page.test.tsx b/src/app/dashboard/(user-dashboard)/products/page.test.tsx deleted file mode 100644 index dea52ad56..000000000 --- a/src/app/dashboard/(user-dashboard)/products/page.test.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { render } from "~/test/utils"; -import Page from "./page"; - -describe("page tests", () => { - it("should render correctly", () => { - expect.assertions(1); - - render(); - - expect(true).toBeTruthy(); - }); -}); diff --git a/src/app/dashboard/(user-dashboard)/settings/organization/roles-and-permissions/page.tsx b/src/app/dashboard/(user-dashboard)/settings/organization/roles-and-permissions/page.tsx index 2873b0226..df7b0c3ca 100644 --- a/src/app/dashboard/(user-dashboard)/settings/organization/roles-and-permissions/page.tsx +++ b/src/app/dashboard/(user-dashboard)/settings/organization/roles-and-permissions/page.tsx @@ -1,9 +1,12 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import CustomButton from "~/components/common/common-button/common-button"; import RoleCreationModal from "~/components/common/modals/role-creation"; +import LoadingSpinner from "~/components/miscellaneous/loading-spinner"; +import { useToast } from "~/components/ui/use-toast"; +import { getApiUrl } from "~/utils/getApiUrl"; type Role = { id: number; @@ -12,75 +15,116 @@ type Role = { }; type Permission = { - name: string; - enabled: boolean; -}; - -const rolesData: Role[] = [ - { id: 1, name: "Administrator", description: "Full access, control" }, - { id: 2, name: "Guest", description: "Read-only access" }, - { id: 3, name: "User", description: "Read, write, update" }, - { id: 4, name: "Manager", description: "Read, write, approve" }, - { id: 5, name: "Project Lead", description: "Manage, coordinate, oversee" }, -]; - -const permissionsData: { [key: number]: Permission[] } = { - 1: [ - { name: "Can view transactions", enabled: true }, - { name: "Can view refunds", enabled: false }, - { name: "Can log refunds", enabled: true }, - ], - 2: [ - { name: "Can view users", enabled: true }, - { name: "Can create users", enabled: false }, - { name: "Can edit users", enabled: true }, - { name: "Can blacklist/whitelist users", enabled: true }, - ], - 3: [ - { name: "Can view users", enabled: true }, - { name: "Can create users", enabled: true }, - { name: "Can edit users", enabled: false }, - { name: "Can blacklist/whitelist users", enabled: true }, - ], - 4: [ - { name: "Can view transactions", enabled: true }, - { name: "Can view refunds", enabled: true }, - { name: "Can log refunds", enabled: true }, - { name: "Can view users", enabled: true }, - { name: "Can create users", enabled: true }, - { name: "Can edit users", enabled: true }, - { name: "Can blacklist/whitelist users", enabled: true }, - ], - 5: [ - { name: "Can view transactions", enabled: true }, - { name: "Can view refunds", enabled: true }, - { name: "Can log refunds", enabled: true }, - { name: "Can view users", enabled: true }, - { name: "Can create users", enabled: true }, - { name: "Can edit users", enabled: true }, - { name: "Can blacklist/whitelist users", enabled: true }, - ], + [key: string]: boolean; }; const RolesAndPermission = () => { const [selectedRoleId, setSelectedRoleId] = useState(); - const [permissions, setPermissions] = useState([]); + const [permissions, setPermissions] = useState({}); const [isModalOpen, setIsModalOpen] = useState(false); + const [roles, setRoles] = useState([]); + const [apiUrl, setApiUrl] = useState(""); + const { toast } = useToast(); + const [loadingRoles, setLoadingRoles] = useState(true); + const [loadingPermissions, setLoadingPermissions] = useState(false); + const [loadingRequest, setLoadingRequest] = useState(false); + const org_id = "9ca4512c-f665-4901-ba63-0b6492e47d32"; + + useEffect(() => { + setLoadingRoles(true); + const fetchData = async () => { + try { + const url = await getApiUrl(); + setApiUrl(url); + const response = await fetch(`${url}/organisations/${org_id}/roles`); + const data = await response.json(); + setRoles(data); + setLoadingRoles(false); + } catch (error: unknown) { + toast({ + title: "Error occurred", + description: + error instanceof Error ? error.message : "Error fetching data", + variant: "destructive", + }); + setLoadingRoles(false); + } + }; + fetchData(); + }, [toast]); + + useEffect(() => { + const fetchPermissions = async () => { + if (selectedRoleId) { + setLoadingPermissions(true); + try { + const response = await fetch( + `${apiUrl}/organisations/${org_id}/roles/${selectedRoleId}`, + ); + const permissionsData = await response.json(); + setPermissions(permissionsData.permission_list); + setLoadingPermissions(false); + } catch { + toast({ + title: "An error occurred", + description: "Error fetching permissions", + variant: "destructive", + }); + setLoadingPermissions(false); + } + } + }; + fetchPermissions(); + }, [selectedRoleId, apiUrl, org_id, toast]); const handleRoleClick = (roleId: number) => { setSelectedRoleId(roleId); - setPermissions([...permissionsData[roleId]]); }; - const handleToggle = (index: number) => { - setPermissions((previous) => { - const newPermissions = [...previous]; - newPermissions[index].enabled = !newPermissions[index].enabled; - permissionsData[selectedRoleId!] = newPermissions; - return newPermissions; + const handleToggle = (permission: string, value: boolean) => { + setPermissions({ + ...permissions, + [permission]: value, }); }; + const handleSave = async () => { + setLoadingRequest(true); + try { + const response = await fetch( + `${apiUrl}/organisations/${org_id}/${selectedRoleId}/permissions`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ permission_list: permissions }), + }, + ); + + if (response.ok) { + toast({ + title: "Success", + description: "Permissions updated successfully", + variant: "default", + }); + } else { + toast({ + title: "Error", + description: "Failed to update permissions", + variant: "destructive", + }); + } + } catch { + toast({ + title: "Error", + description: "Failed to update permissions", + variant: "destructive", + }); + } + setLoadingRequest(false); + }; + const handleModalOpen = () => { setIsModalOpen(true); }; @@ -95,7 +139,12 @@ const RolesAndPermission = () => {

Roles

    - {rolesData.map((role) => ( + {loadingRoles && ( +
    + +
    + )} + {roles.map((role) => (
  • { Click on a role to view permissions

+ ) : loadingPermissions ? ( +
+ +
) : (
- {permissions.map((permission, index) => ( + {Object.keys(permissions).map((permission) => (
- {permission.name} + {permission + .replaceAll("_", " ") + .replaceAll(/\b\w/g, (l) => l.toUpperCase())}
))}
-
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 38fe4626a..3c252bb27 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,8 @@ import { Inter } from "next/font/google"; import "./globals.css"; +import { SessionProvider } from "next-auth/react"; + import Providers from "~/components/providers"; import { Toaster } from "~/components/ui/toaster"; @@ -22,7 +24,7 @@ export default function RootLayout({
- {children} + {children}
diff --git a/src/components/card/user-card.tsx b/src/components/card/user-card.tsx index d75b65cc1..1d46b02c8 100644 --- a/src/components/card/user-card.tsx +++ b/src/components/card/user-card.tsx @@ -1,4 +1,5 @@ import { AnimatePresence, motion } from "framer-motion"; +import { signOut } from "next-auth/react"; import { useEffect, useState } from "react"; import { useUser } from "~/hooks/user/use-user"; @@ -12,6 +13,7 @@ const UserCard = ({ email }: { email: string }) => { const handleLogout = () => { updateUser({ email: "", name: "" }); setIsLogout(false); + signOut({ callbackUrl: "/" }); }; const handleEscapeClick = (event: KeyboardEvent) => { diff --git a/src/components/layouts/navbar/index.tsx b/src/components/layouts/navbar/index.tsx index 91b9c3fdb..a78994c54 100644 --- a/src/components/layouts/navbar/index.tsx +++ b/src/components/layouts/navbar/index.tsx @@ -1,18 +1,18 @@ "use client"; +import { useSession } from "next-auth/react"; import Link from "next/link"; import { useEffect, useState } from "react"; import UserCard from "~/components/card/user-card"; import Logo from "~/components/common/logo"; -import { useUser } from "~/hooks/user/use-user"; import { cn } from "~/lib/utils"; import { NAV_LINKS } from "./links"; import MobileNav from "./mobile-navbar"; const Navbar = () => { const [scrolling, setIsScrolling] = useState(false); - const { user } = useUser(); + const { data: session } = useSession(); const handleScrollEvent = () => { if (window.scrollY > 1) { @@ -36,7 +36,7 @@ const Navbar = () => { className={cn( `relative mx-auto flex w-full max-w-[1200px] items-center gap-x-4 transition-all duration-500 md:justify-between`, scrolling ? "py-2" : "py-4 md:py-9", - user.email && "justify-between", + session?.user?.email && "justify-between", )} > @@ -55,7 +55,9 @@ const Navbar = () => { ); })}
- {!user.email && ( + {session?.user?.email ? ( + + ) : (
{
)} - {user.email && }
);