diff --git a/hng_boilerplate_nextjs_project b/hng_boilerplate_nextjs_project deleted file mode 160000 index 8fc1b95c0..000000000 --- a/hng_boilerplate_nextjs_project +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8fc1b95c0e495b78b6b2d20e43b85695edcafec9 diff --git a/package.json b/package.json index 5735255e8..78985a100 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "test:ci": "vitest --coverage", "email:dev": "email dev --dir \"./src/email/templates\"", "email:build": "email build --dir \"./src/email/templates\"", - "email:start": "email start" + "email:start": "email start", + "typecheck": "tsc --project tsconfig.json --noEmit" }, "dependencies": { "@hookform/resolvers": "^3.9.0", diff --git a/src/actions/notifications/getAllNotifications.ts b/src/actions/notifications/getAllNotifications.ts index c7389fb9f..456b27580 100644 --- a/src/actions/notifications/getAllNotifications.ts +++ b/src/actions/notifications/getAllNotifications.ts @@ -3,10 +3,10 @@ import axios from "axios"; import { auth } from "~/lib/auth"; -import { getApiUrl } from "../getApiUrl"; + +const apiUrl = process.env.API_URL; export const getAllNotifications = async () => { - const apiUrl = await getApiUrl(); const session = await auth(); try { const response = await axios.get(`${apiUrl}/api/v1/notifications`, { diff --git a/src/actions/organization.ts b/src/actions/organization.ts index 36f321737..ac83db272 100644 --- a/src/actions/organization.ts +++ b/src/actions/organization.ts @@ -102,8 +102,13 @@ export const getAnalytics = async () => { }, }); + const formattedData = Object.keys(response.data.data).map((key) => ({ + month: key, + revenue: response.data.data[key], + })); + return { - data: response.data.data, + data: formattedData, }; } catch (error) { return axios.isAxiosError(error) && error.response diff --git a/src/actions/product.ts b/src/actions/product.ts index b527d36c9..e9fe607f6 100644 --- a/src/actions/product.ts +++ b/src/actions/product.ts @@ -70,7 +70,6 @@ export const getAllProduct = async (org_id: string) => { ); return { products: response.data.data, - response: response, }; } catch (error) { return axios.isAxiosError(error) && error.response diff --git a/src/actions/socialAuth.ts b/src/actions/socialAuth.ts index 0673e95b5..1324573ac 100644 --- a/src/actions/socialAuth.ts +++ b/src/actions/socialAuth.ts @@ -2,14 +2,14 @@ import axios from "axios"; -import { AuthResponse, ErrorResponse, Profile } from "~/types"; +import { AuthResponse, ErrorResponse } from "~/types"; const apiUrl = process.env.API_URL; -const googleAuth = async (profile: Profile): Promise => { +const googleAuth = async (idToken: string): Promise => { try { const response = await axios.post(`${apiUrl}/api/v1/auth/google`, { - id_token: profile.id_token, + id_token: idToken, }); return { diff --git a/src/app/(auth-routes)/login/page.tsx b/src/app/(auth-routes)/login/page.tsx index 269782fdb..5251e9fee 100644 --- a/src/app/(auth-routes)/login/page.tsx +++ b/src/app/(auth-routes)/login/page.tsx @@ -10,7 +10,6 @@ import { useEffect, useState, useTransition } from "react"; import { useForm } from "react-hook-form"; import * as z from "zod"; -import { loginUser } from "~/actions/login"; import CustomButton from "~/components/common/common-button/common-button"; import { Input } from "~/components/common/input"; import LoadingSpinner from "~/components/miscellaneous/loading-spinner"; @@ -24,28 +23,22 @@ import { FormMessage, } from "~/components/ui/form"; import { useToast } from "~/components/ui/use-toast"; -import { useLocalStorage } from "~/hooks/use-local-storage"; import { cn } from "~/lib/utils"; import { LoginSchema } from "~/schemas"; -import { Organisation } from "~/types"; const Login = () => { const t = useTranslations("login"); const router = useRouter(); const { toast } = useToast(); - const { status } = useSession(); const [isLoading, startTransition] = useTransition(); const [showPassword, setShowPassword] = useState(false); - const [, setUserOrg] = useLocalStorage("user_org", []); - - const [currentOrgId, setCurrentOrgId] = useLocalStorage( - "current_orgid", - "", - ); + const { status } = useSession(); - if (status === "authenticated") { - router.push("/dashboard"); - } + useEffect(() => { + if (status === "authenticated") { + router.push("/dashboard"); + } + }, [status, router]); const form = useForm>({ resolver: zodResolver(LoginSchema), @@ -55,34 +48,37 @@ const Login = () => { rememberMe: false, }, }); - const onSubmit = async (values: z.infer) => { - startTransition(async () => { - await loginUser(values).then(async (data) => { - const { email, password } = values; + const { email, password } = values; - if (data.status === 200) { - setUserOrg(data.organisations); - if (!currentOrgId && data.organisations.length > 0) { - setCurrentOrgId(data.organisations[0].organisation_id); - } - await signIn( - "credentials", - { - email, - password, - redirect: false, - }, - { callbackUrl: "/dashboard" }, - ); + try { + startTransition(async () => { + const result = await signIn("credentials", { + email, + password, + redirect: false, + }); + + if (result?.ok) { router.push("/dashboard"); + toast({ + title: "Login success", + description: "Redirecting", + }); + } else { + toast({ + title: "An error occurred", + description: result?.error || "Unknown error", + }); } - toast({ - title: data.status === 200 ? "Login success" : "An error occurred", - description: data.status === 200 ? "Redirecting" : data.error, - }); }); - }); + } catch (error) { + toast({ + title: "Login failed", + description: + (error as Error).message || "An error occurred during login", + }); + } }; const togglePasswordVisibility = () => { @@ -91,6 +87,7 @@ const Login = () => { useEffect(() => { document.title = "Login"; }, []); + return (
@@ -106,7 +103,7 @@ const Login = () => { signIn("google", { callbackUrl: "/dashboard" })} + onClick={() => signIn("google", { callbackUrl: "/" })} icon={ { last_name: "User", email: "user@example.com", image: "path/to/image", - role: "user", }, access_token: "some-token", expires: "1", diff --git a/src/app/(landing-routes)/blog/page.tsx b/src/app/(landing-routes)/blog/page.tsx index 3ec42db7f..646d72f53 100644 --- a/src/app/(landing-routes)/blog/page.tsx +++ b/src/app/(landing-routes)/blog/page.tsx @@ -1,14 +1,62 @@ "use client"; +import axios from "axios"; +import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import { getApiUrl } from "~/actions/getApiUrl"; import CustomButton from "~/components/common/common-button/common-button"; import HeroSection from "~/components/extDynamicPages/blogCollection/BlogPageHero"; import BlogCard from "~/components/layouts/BlogCards"; +import { useToast } from "~/components/ui/use-toast"; import { blogPosts } from "./data/mock"; const BlogHome = () => { const router = useRouter(); + const { toast } = useToast(); + const { data: session } = useSession(); + const [, setBlogPost] = useState(""); + + useEffect(() => { + async function fetchBlog() { + try { + const apiUrl = await getApiUrl(); + const token = session?.access_token; + const response = await axios.get(`${apiUrl}/api/v1/blogs`, { + headers: { + Authorization: `Bearer: ${token}`, + }, + }); + const data = response.data; + setBlogPost(data); + toast({ + title: + Array.isArray(data.data) && data.data.length === 0 + ? "No Blog Post Available" + : "Blog Posts Retrieved", + description: + Array.isArray(data.data) && data.data.length === 0 + ? "Blog posts are empty" + : "Blog posts retrieved successfully", + variant: + Array.isArray(data.data) && data.data.length === 0 + ? "destructive" + : "default", + }); + } catch (error) { + toast({ + title: "Error occured", + description: + error instanceof Error + ? error.message + : "An unknown error occurred", + variant: "destructive", + }); + } + } + fetchBlog(); + }, [session?.access_token, toast]); return (
diff --git a/src/app/(landing-routes)/contact-us/page.test.tsx b/src/app/(landing-routes)/contact-us/page.test.tsx index dea52ad56..5c4112890 100644 --- a/src/app/(landing-routes)/contact-us/page.test.tsx +++ b/src/app/(landing-routes)/contact-us/page.test.tsx @@ -1,11 +1,19 @@ +import { SessionProvider } from "next-auth/react"; +import { ReactNode } from "react"; + import { render } from "~/test/utils"; import Page from "./page"; describe("page tests", () => { + const renderWithSession = (component: ReactNode) => { + return render( + {component}, + ); + }; it("should render correctly", () => { expect.assertions(1); - render(); + renderWithSession(); expect(true).toBeTruthy(); }); diff --git a/src/app/dashboard/(admin)/admin/(settings)/settings/_components/layout/sidebar/index.tsx b/src/app/dashboard/(admin)/admin/(settings)/settings/_components/layout/sidebar/index.tsx index 48d8aabe8..f65b5e87f 100644 --- a/src/app/dashboard/(admin)/admin/(settings)/settings/_components/layout/sidebar/index.tsx +++ b/src/app/dashboard/(admin)/admin/(settings)/settings/_components/layout/sidebar/index.tsx @@ -12,12 +12,12 @@ import { UserRoundCog, UsersIcon, } from "lucide-react"; +import { useSession } from "next-auth/react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { FC, ForwardRefExoticComponent, RefAttributes } from "react"; import { useOrgContext } from "~/contexts/orgContext"; -import { useLocalStorage } from "~/hooks/use-local-storage"; const sideItems = [ { @@ -96,10 +96,10 @@ const SettingsSidebar: FC = ({ sideNavitems = sideItems }) => { pathname?.split("/").length == 2 ? "general" : pathname?.split("/")[3]; const organizationPath = pathname?.split("/")[4]; const { organizations } = useOrgContext(); - const [org_id] = useLocalStorage("current_orgid", ""); + const { data: session } = useSession(); const organization = organizations.find( - (org) => org.organisation_id === org_id, + (org) => org.organisation_id === session?.currentOrgId, ); return ( diff --git a/src/app/dashboard/(admin)/admin/(settings)/settings/data-and-privacy/page.tsx b/src/app/dashboard/(admin)/admin/(settings)/settings/data-and-privacy/page.tsx index dfd73041e..aa0e9bd79 100644 --- a/src/app/dashboard/(admin)/admin/(settings)/settings/data-and-privacy/page.tsx +++ b/src/app/dashboard/(admin)/admin/(settings)/settings/data-and-privacy/page.tsx @@ -2,6 +2,8 @@ import { useState } from "react"; +import CustomButton from "~/components/common/common-button/common-button"; + interface ToggleSwitchProperties { label: string; description: string; @@ -21,7 +23,7 @@ const ToggleSwitch: React.FC = ({ return (
- +
); }; diff --git a/src/app/dashboard/(admin)/admin/(settings)/settings/organization/members/page.tsx b/src/app/dashboard/(admin)/admin/(settings)/settings/organization/members/page.tsx index 9fa6ea504..85488d79b 100644 --- a/src/app/dashboard/(admin)/admin/(settings)/settings/organization/members/page.tsx +++ b/src/app/dashboard/(admin)/admin/(settings)/settings/organization/members/page.tsx @@ -8,7 +8,14 @@ import { useState } from "react"; import CustomButton from "~/components/common/common-button/common-button"; import { Input } from "~/components/common/input"; import InviteMemberModal from "~/components/common/modals/invite-member"; +import DeleteMember from "~/components/common/modals/invite-member/DeleteMembers"; import LoadingSpinner from "~/components/miscellaneous/loading-spinner"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "~/components/ui/dropdown-menu"; import { Select, SelectContent, @@ -71,8 +78,15 @@ const activeMembers: number = memberData.length; const Members = () => { const [isModalOpen, setIsModalOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [exporting, setExporting] = useState(false); const [text, setText] = useState("Export CSV"); + const [isVisible, setIsVisible] = useState(false); + + const toggleVisibility = () => { + setIsVisible(!isVisible); + }; const { toast } = useToast(); const handleCopy = () => { @@ -87,10 +101,18 @@ const Members = () => { setIsModalOpen(true); }; + const handleDeleteOpen = () => { + setIsDeleteModalOpen(true); + }; + const handleModalClose = () => { setIsModalOpen(false); }; + const handleDeleteClose = () => { + setIsDeleteModalOpen(false); + }; + const exportMembers = async () => { setExporting(true); setText("Exporting..."); @@ -160,25 +182,28 @@ const Members = () => { {}} + onChange={toggleVisibility} + checked={isVisible} />
-
- - https://www.figma.com/design/7hCSTNzQOJLl9aww6wEEd1/Managing-Users----Team-Learn-AI?node-i - - - Copy link - -
+ {isVisible && ( +
+ + https://www.figma.com/design/7hCSTNzQOJLl9aww6wEEd1/Managing-Users----Team-Learn-AI?node-i + + + Copy link + +
+ )}

Manage members

@@ -246,7 +271,19 @@ const Members = () => {
- {}} className="flex-shrink-0" /> + + + {}} + className="flex-shrink-0" + /> + + + + Delete Member + + + ); })} @@ -276,6 +313,7 @@ const Members = () => {
+ ); }; diff --git a/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/create-role/page.tsx b/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/create-role/page.tsx index 35330829d..a55dfa546 100644 --- a/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/create-role/page.tsx +++ b/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/create-role/page.tsx @@ -2,6 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { ChevronLeftIcon } from "lucide-react"; +import { useSession } from "next-auth/react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; @@ -19,7 +20,6 @@ import { SelectValue, } from "~/components/ui/select"; import { toast } from "~/components/ui/use-toast"; -import { useLocalStorage } from "~/hooks/use-local-storage"; import { roleSchema } from "~/schemas"; type UseFormInputs = z.infer; @@ -123,7 +123,7 @@ const transformPermissions = (apiResponse: APIPermissions[]) => { function CreateNewRolePage() { const router = useRouter(); - const [currentOrgId] = useLocalStorage("current_orgid", ""); + const { data: session } = useSession(); const [isSaving, setIsSaving] = useState(false); const [permissions, setPermissions] = useState< PermissionOption["permissions"] | [] @@ -175,7 +175,7 @@ function CreateNewRolePage() { const onValid = async (values: UseFormInputs) => { setIsSaving(true); try { - await createRole(values, currentOrgId) + await createRole(values, session?.currentOrgId ?? "") .then((data) => { if (!data.error) { toast({ diff --git a/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/page.tsx b/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/page.tsx index 11d6b6349..0ec979fa5 100644 --- a/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/page.tsx +++ b/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/page.tsx @@ -1,5 +1,6 @@ "use client"; +import { useSession } from "next-auth/react"; import Link from "next/link"; import { useEffect, useState } from "react"; @@ -13,7 +14,6 @@ import { import CustomButton from "~/components/common/common-button/common-button"; import LoadingSpinner from "~/components/miscellaneous/loading-spinner"; import { useToast } from "~/components/ui/use-toast"; -import { useLocalStorage } from "~/hooks/use-local-storage"; type Role = { id: string; @@ -39,18 +39,15 @@ const RolesAndPermission = () => { const [loadingRoles, setLoadingRoles] = useState(true); const [loadingPermissions, setLoadingPermissions] = useState(false); const [loadingRequest, setLoadingRequest] = useState(false); - const [currentOrgId] = useLocalStorage( - "current_orgid", - "", - ); + const { data: session } = useSession(); useEffect(() => { - if (!currentOrgId) return; + if (!session?.currentOrgId) return; const fetchData = async () => { try { const url = await getApiUrl(); setApiUrl(url); - const { data, error } = await getRoles(currentOrgId); + const { data, error } = await getRoles(session?.currentOrgId ?? ""); if (error) throw new Error("An error occurred!"); @@ -68,14 +65,14 @@ const RolesAndPermission = () => { }; setLoadingRoles(true); fetchData(); - }, [currentOrgId, toast]); + }, [session?.currentOrgId, toast]); useEffect(() => { const fetchPermissions = async () => { - if (selectedRoleId && currentOrgId) { + if (selectedRoleId && session?.currentOrgId) { setLoadingPermissions(true); try { - await getRolePermissions(currentOrgId, selectedRoleId).then( + await getRolePermissions(session?.currentOrgId, selectedRoleId).then( (data) => { const rolesData = data.data; if (rolesData.permissions.length > 0) { @@ -99,7 +96,7 @@ const RolesAndPermission = () => { } }; fetchPermissions(); - }, [selectedRoleId, apiUrl, currentOrgId, toast]); + }, [selectedRoleId, apiUrl, session?.currentOrgId, toast]); useEffect(() => { const fetchPermissions = async () => { @@ -132,7 +129,7 @@ const RolesAndPermission = () => { }; const handleSave = async () => { - if (!selectedRoleId || !currentOrgId) return; + if (!selectedRoleId || !session?.currentOrgId) return; const selectedRole = roles.some((role) => role.id === selectedRoleId) && roles.find((role) => role.id === selectedRoleId); @@ -141,7 +138,7 @@ const RolesAndPermission = () => { try { await updateRole( { ...selectedRole, permissions }, - currentOrgId, + session?.currentOrgId, selectedRoleId, ).then(() => { toast({ diff --git a/src/app/dashboard/(admin)/admin/(settings)/settings/payment-information/page.tsx b/src/app/dashboard/(admin)/admin/(settings)/settings/payment-information/page.tsx index bf10a7cec..fa909b5d2 100644 --- a/src/app/dashboard/(admin)/admin/(settings)/settings/payment-information/page.tsx +++ b/src/app/dashboard/(admin)/admin/(settings)/settings/payment-information/page.tsx @@ -213,10 +213,6 @@ const PaymentInformation = () => { - -
-

Compare all features

-
); }; diff --git a/src/app/dashboard/(user-dashboard)/(user-metrics)/layout.tsx b/src/app/dashboard/(user-dashboard)/(user-metrics)/layout.tsx index dbe5f72ec..41a7552da 100644 --- a/src/app/dashboard/(user-dashboard)/(user-metrics)/layout.tsx +++ b/src/app/dashboard/(user-dashboard)/(user-metrics)/layout.tsx @@ -22,7 +22,7 @@ const links = [ export default function UserMetricsLayout({ children }: PropsWithChildren) { return (
-
+

Dashboard

diff --git a/src/app/dashboard/(user-dashboard)/_components/layout/navbar/index.tsx b/src/app/dashboard/(user-dashboard)/_components/layout/navbar/index.tsx index d537b16e7..bf87dedb9 100644 --- a/src/app/dashboard/(user-dashboard)/_components/layout/navbar/index.tsx +++ b/src/app/dashboard/(user-dashboard)/_components/layout/navbar/index.tsx @@ -1,11 +1,7 @@ "use client"; import { BellIcon, HelpCircle } from "lucide-react"; -import { useSession } from "next-auth/react"; -import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; -import { getAllNotifications } from "~/actions/notifications/getAllNotifications"; import UnreadNotificationCard from "~/app/dashboard/(admin)/_components/unread-notification-card/UnreadNotificationCard"; import { MobileNavlinks } from "~/app/dashboard/(user-dashboard)/_components/layout/navbar/mobile-navlinks"; import { Navlinks } from "~/app/dashboard/(user-dashboard)/_components/layout/navbar/navlinks"; @@ -16,62 +12,23 @@ import { PopoverContent, PopoverTrigger, } from "~/components/ui/popover"; +import { useOrgContext } from "~/contexts/orgContext"; import { OrganisationSwitcher } from "./organisation-switcher"; -interface NotificationPreview { - message: string; - created_at: string; - is_read: boolean; - id: string; -} - -interface NotificationsData { - data: { - total_unread_notification_count: number; - total_notification_count: number; - notifications: NotificationPreview[]; - }; - message: string; -} - const UserNavbar = () => { - const [notifications, setNotifications] = - useState(); - const { status } = useSession(); - const router = useRouter(); - - useEffect(() => { - if (status === "unauthenticated") { - router.push("/login"); - } - }, [status, router]); - - useEffect(() => { - async function fetchNotifications() { - const result = await getAllNotifications(); - - if (result.error) { - return result.error; - } else { - setNotifications(result.data as NotificationsData); - } - } - - fetchNotifications(); - }, []); + const { notifications } = useOrgContext(); const totalUnreadNotificationCount = - notifications?.data.total_unread_notification_count || 0; + notifications?.data?.total_unread_notification_count || 0; const totalNotificationCount = - notifications?.data.total_notification_count || 0; - const notificationContent: NotificationPreview[] = - notifications?.data.notifications || []; + notifications?.data?.total_notification_count || 0; + const notificationContent = notifications?.data?.notifications || []; return (
); diff --git a/src/app/dashboard/(user-dashboard)/products/_components/new-product-modal.tsx b/src/app/dashboard/(user-dashboard)/products/_components/new-product-modal.tsx index 179d0c0f1..04064b833 100644 --- a/src/app/dashboard/(user-dashboard)/products/_components/new-product-modal.tsx +++ b/src/app/dashboard/(user-dashboard)/products/_components/new-product-modal.tsx @@ -3,6 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { AnimatePresence, motion } from "framer-motion"; import { Loader, X } from "lucide-react"; +import { useSession } from "next-auth/react"; import Image from "next/image"; import { useState, useTransition } from "react"; import { useForm } from "react-hook-form"; @@ -33,7 +34,6 @@ import { import { Textarea } from "~/components/ui/textarea"; import { useToast } from "~/components/ui/use-toast"; import { useOrgContext } from "~/contexts/orgContext"; -import { useLocalStorage } from "~/hooks/use-local-storage"; import { cn } from "~/lib/utils"; import { productSchema } from "~/schemas"; import { CloudinaryAsset } from "~/types"; @@ -44,7 +44,7 @@ const NewProductModal = () => { const { setIsNewModal, isNewModal } = useOrgContext(); const [image, setImage] = useState(); const [isLoading, startTransition] = useTransition(); - const [org_id] = useLocalStorage("current_orgid", ""); + const { data: session } = useSession(); const variantProperties = { left: "50%", @@ -83,7 +83,8 @@ const NewProductModal = () => { values.image_url = data.url; }); - await createProduct(values, org_id).then((data) => { + if (session?.currentOrgId === undefined) return; + await createProduct(values, session?.currentOrgId).then((data) => { if (data.status === 201) { newProductForm.reset(); setIsNewModal(false); diff --git a/src/app/dashboard/(user-dashboard)/products/_components/product-delete-modal.tsx b/src/app/dashboard/(user-dashboard)/products/_components/product-delete-modal.tsx index 99f2612a0..eb3904f82 100644 --- a/src/app/dashboard/(user-dashboard)/products/_components/product-delete-modal.tsx +++ b/src/app/dashboard/(user-dashboard)/products/_components/product-delete-modal.tsx @@ -1,13 +1,13 @@ "use client"; import { AnimatePresence, motion } from "framer-motion"; +import { useSession } from "next-auth/react"; import { useTransition } from "react"; import { deleteProduct } from "~/actions/product"; import { Button } from "~/components/ui/button"; import { toast } from "~/components/ui/use-toast"; import { useOrgContext } from "~/contexts/orgContext"; -import { useLocalStorage } from "~/hooks/use-local-storage"; import { cn } from "~/lib/utils"; const variantProperties = { @@ -18,7 +18,7 @@ const variantProperties = { }; const ProductDeleteModal = () => { - const [org_id] = useLocalStorage("current_orgid", ""); + const { data: session } = useSession(); const [isPending, startTransition] = useTransition(); const { selectedProduct, products, isDelete, setIsDelete } = useOrgContext(); @@ -32,21 +32,24 @@ const ProductDeleteModal = () => { }); startTransition(async () => { - await deleteProduct(org_id, selectedProduct).then((data) => { - if (data.status === 200) { - toast({ - title: "Product deleted", - description: "Product deleted successfully.", - }); - } else { - toast({ - title: "Error", - description: data.error || "An unexpected error occurred.", - variant: "destructive", - }); - } - setIsDelete(false); - }); + if (session?.currentOrgId === undefined) return; + await deleteProduct(session?.currentOrgId, selectedProduct).then( + (data) => { + if (data.status === 200) { + toast({ + title: "Product deleted", + description: "Product deleted successfully.", + }); + } else { + toast({ + title: "Error", + description: data.error || "An unexpected error occurred.", + variant: "destructive", + }); + } + setIsDelete(false); + }, + ); }); }; diff --git a/src/app/dashboard/(user-dashboard)/products/_components/product-detail-modal.tsx b/src/app/dashboard/(user-dashboard)/products/_components/product-detail-modal.tsx index 36f3da06d..a49fddded 100644 --- a/src/app/dashboard/(user-dashboard)/products/_components/product-detail-modal.tsx +++ b/src/app/dashboard/(user-dashboard)/products/_components/product-detail-modal.tsx @@ -2,6 +2,7 @@ import { AnimatePresence, motion } from "framer-motion"; import { Loader2, X } from "lucide-react"; +import { useSession } from "next-auth/react"; import { useRouter } from "next-nprogress-bar"; import { startTransition, useEffect, useState, useTransition } from "react"; @@ -10,7 +11,6 @@ import BlurImage from "~/components/miscellaneous/blur-image"; import { Button } from "~/components/ui/button"; import { toast } from "~/components/ui/use-toast"; import { useOrgContext } from "~/contexts/orgContext"; -import { useLocalStorage } from "~/hooks/use-local-storage"; import useWindowWidth from "~/hooks/use-window-width"; import { cn, formatPrice } from "~/lib/utils"; import { Product } from "~/types"; @@ -31,7 +31,7 @@ const ProductDetailModal = () => { useOrgContext(); const [isLoading, startLoading] = useTransition(); - const [org_id] = useLocalStorage("current_orgid", ""); + const { data: session } = useSession(); const { winWidth } = useWindowWidth(); useEffect(() => { @@ -53,23 +53,27 @@ const ProductDetailModal = () => { }); startLoading(() => { - deleteProduct(org_id, selectedProduct).then(async (data) => { - toast({ - title: data.status === 200 ? `Product deleted` : "an error occurred", - description: ( - - {product?.name}{" "} - {data.status === 200 ? " has been deleted." : data.error} - - ), - variant: "default", - className: "z-[99999]", - }); - if (data.status === 200) { - updateOpen(false); - setIsDelete(false); - } - }); + if (session?.currentOrgId === undefined) return; + deleteProduct(session?.currentOrgId, selectedProduct).then( + async (data) => { + toast({ + title: + data.status === 200 ? `Product deleted` : "an error occurred", + description: ( + + {product?.name}{" "} + {data.status === 200 ? " has been deleted." : data.error} + + ), + variant: "default", + className: "z-[99999]", + }); + if (data.status === 200) { + updateOpen(false); + setIsDelete(false); + } + }, + ); }); }; const handleEditAction = (id: string) => { 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 3c7b03b68..cfbf51e0f 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 @@ -2,6 +2,7 @@ import { AnimatePresence, motion } from "framer-motion"; import { X } from "lucide-react"; +import { useSession } from "next-auth/react"; import { useRouter } from "next-nprogress-bar"; import { useEffect, useTransition } from "react"; @@ -11,7 +12,6 @@ import LoadingSpinner from "~/components/miscellaneous/loading-spinner"; import { Button } from "~/components/ui/button"; import { toast } from "~/components/ui/use-toast"; import { useOrgContext } from "~/contexts/orgContext"; -import { useLocalStorage } from "~/hooks/use-local-storage"; import { cn, formatPrice } from "~/lib/utils"; const ProductDetailView = () => { @@ -28,7 +28,7 @@ const ProductDetailView = () => { const [isLoading, startTransition] = useTransition(); const product = products.find((p) => p.id === selectedProduct); - const [org_id] = useLocalStorage("current_orgid", ""); + const { data: session } = useSession(); const handleDelete = async () => { toast({ @@ -38,21 +38,24 @@ const ProductDetailView = () => { }); startTransition(async () => { - await deleteProduct(org_id, selectedProduct).then((data) => { - if (data.status === 200) { - toast({ - title: "Product deleted", - description: "Product deleted successfully.", - }); - } else { - toast({ - title: "Error", - description: data.error || "An unexpected error occurred.", - variant: "destructive", - }); - } - setIsDelete(false); - }); + if (session?.currentOrgId === undefined) return; + await deleteProduct(session?.currentOrgId, selectedProduct).then( + (data) => { + if (data.status === 200) { + toast({ + title: "Product deleted", + description: "Product deleted successfully.", + }); + } else { + toast({ + title: "Error", + description: data.error || "An unexpected error occurred.", + variant: "destructive", + }); + } + setIsDelete(false); + }, + ); }); }; const handleEditAction = (id: string) => { diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index 5c5cff5e4..84430821d 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -1,9 +1,41 @@ +import { getAllNotifications } from "~/actions/notifications/getAllNotifications"; +import { getAnalytics, getStatistics } from "~/actions/organization"; +import { getAllProduct } from "~/actions/product"; import OrgContextProvider from "~/contexts/orgContext"; +import { auth } from "~/lib/auth"; -export default function GeneralLayout({ - children, -}: { +interface GeneralLayoutProperties { children: React.ReactNode; -}) { - return {children}; +} + +export default async function GeneralLayout( + properties: GeneralLayoutProperties, +) { + const session = await auth(); + + if (!session || !session.currentOrgId) { + return; + } + + const { children } = properties; + + const [notifications, statistics, analytics, products] = await Promise.all([ + getAllNotifications(), + getStatistics(), + getAnalytics(), + getAllProduct(session?.currentOrgId), + ]); + + return ( + + {children} + + ); } diff --git a/src/app/invite/page.tsx b/src/app/invite/page.tsx index 61e1c1984..e03c03903 100644 --- a/src/app/invite/page.tsx +++ b/src/app/invite/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useRouter } from "next/navigation"; // Import from next/navigation -import { useEffect } from "react"; +import { useCallback, useEffect } from "react"; import { acceptInviteRequest } from "~/actions/inviteMembers"; @@ -12,12 +12,12 @@ const extractToken = () => { ? queryString : queryString.slice(0, Math.max(0, ampersandIndex)); }; + const AcceptInvitePage = () => { const router = useRouter(); - // Function to extract token from the query string - - const handleAcceptInvite = async () => { + // Memoized function to handle invite acceptance + const handleAcceptInvite = useCallback(async () => { // Extract token using the function const token = extractToken(); @@ -56,12 +56,12 @@ const AcceptInvitePage = () => { // Handle unexpected errors router.push("/error?message=An unexpected error occurred"); } - }; + }, [router]); // Process the invite on page load useEffect(() => { handleAcceptInvite(); - }, []); + }, [handleAcceptInvite]); return (
diff --git a/src/components/authproviders/AuthProvider.tsx b/src/components/authproviders/AuthProvider.tsx deleted file mode 100644 index dcaa74598..000000000 --- a/src/components/authproviders/AuthProvider.tsx +++ /dev/null @@ -1,11 +0,0 @@ -function AuthProvider({ title }: { title: string }) { - return ( - <> - - - ); -} - -export default AuthProvider; diff --git a/src/components/card/user-card.tsx b/src/components/card/user-card.tsx index 8f37be0ac..ed6fac9af 100644 --- a/src/components/card/user-card.tsx +++ b/src/components/card/user-card.tsx @@ -1,10 +1,7 @@ -import axios from "axios"; import { ChevronDown } from "lucide-react"; import { signOut, useSession } from "next-auth/react"; import Link from "next/link"; -import { useCallback, useEffect, useState } from "react"; -import { getApiUrl } from "~/actions/getApiUrl"; import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar"; import { DropdownMenu, @@ -16,7 +13,6 @@ import { DropdownMenuShortcut, DropdownMenuTrigger, } from "~/components/ui/dropdown-menu"; -import { toast } from "~/components/ui/use-toast"; import { cn } from "~/lib/utils"; const handleLogout = async () => { @@ -27,55 +23,6 @@ const handleLogout = async () => { const UserCard = () => { const { data: session, status } = useSession(); - const { user } = session ?? {}; - const [profilePicUrl, setProfilePicUrl] = useState(""); - - const fetchProfileData = useCallback(async () => { - if (status === "authenticated" && user?.id) { - try { - const baseUrl = await getApiUrl(); - const API_URL = `${baseUrl}/api/v1/profile/${user.id}`; - const response = await axios.get(API_URL, { - headers: { - Authorization: `Bearer ${session?.access_token}`, - }, - }); - if (response.data?.data) { - const { avatar_url, profile_pic_url } = response.data.data; - setProfilePicUrl(avatar_url || profile_pic_url); - } - } catch { - toast({ - title: "Error", - description: "Failed to fetch profile data.", - variant: "destructive", - }); - } - } - }, [status, user?.id, session?.access_token]); - - useEffect(() => { - fetchProfileData(); - const handleProfileUpdate = (event: CustomEvent) => { - if (event.detail && event.detail.profilePicUrl) { - setProfilePicUrl(event.detail.profilePicUrl); - } else { - fetchProfileData(); - } - }; - - window.addEventListener( - "userProfileUpdate", - handleProfileUpdate as EventListener, - ); - - return () => { - window.removeEventListener( - "userProfileUpdate", - handleProfileUpdate as EventListener, - ); - }; - }, [fetchProfileData]); return ( @@ -91,11 +38,11 @@ const UserCard = () => { {status === "authenticated" && ( - {user?.first_name?.charAt(0)} + {session.user?.first_name?.charAt(0)} )} @@ -110,10 +57,10 @@ const UserCard = () => { - {user?.first_name} {user?.last_name} + {session?.user?.first_name} {session?.user?.last_name} - {user?.email ?? "Signed In"} + {session?.user?.email ?? "Signed In"} diff --git a/src/components/common/contact-us-form/index.tsx b/src/components/common/contact-us-form/index.tsx index 7609b15a6..1d8f692bc 100644 --- a/src/components/common/contact-us-form/index.tsx +++ b/src/components/common/contact-us-form/index.tsx @@ -1,13 +1,13 @@ "use client"; import { Mail } from "lucide-react"; +import { useSession } from "next-auth/react"; import { useEffect, useState } from "react"; import { z, ZodError } from "zod"; import { getApiUrl } from "~/actions/getApiUrl"; import { Toaster } from "~/components/ui/toaster"; import { useToast } from "~/components/ui/use-toast"; -import { useLocalStorage } from "~/hooks/use-local-storage"; import CustomButton from "../common-button/common-button"; import InputField from "./inputfield"; @@ -44,7 +44,7 @@ const ContactForm: React.FC = () => { const [status, setStatus] = useState(); const [message, setMessage] = useState(); const [loading, setLoading] = useState(false); - const [org_id] = useLocalStorage("current_orgid", ""); + const { data: session } = useSession(); const { toast } = useToast(); useEffect(() => { @@ -72,22 +72,6 @@ const ContactForm: React.FC = () => { } }; - function transformFormData( - formData: FormData, - orgId: string, - ): TransformedData { - // Create a new object with the required structure - const transformedData = { - full_name: formData.name, - email: formData.email, - phone_number: formData.phone, - message: formData.message, - org_id: orgId, // Pass orgId as a parameter or obtain it from your application context - }; - - return transformedData; - } - const handleChange = ( event: React.ChangeEvent, ) => { @@ -104,7 +88,13 @@ const ContactForm: React.FC = () => { } try { const baseUrl = await getApiUrl(); - const apiData = transformFormData(formData, org_id); + const apiData: TransformedData = { + full_name: formData.name, + email: formData.email, + phone_number: formData.phone, + message: formData.message, + org_id: session?.currentOrgId ?? "", + }; setLoading(true); const response = await fetch(`${baseUrl}/api/v1/contact`, { method: "POST", diff --git a/src/components/common/modals/invite-member/DeleteMembers/index.tsx b/src/components/common/modals/invite-member/DeleteMembers/index.tsx new file mode 100644 index 000000000..eb5c7e6ec --- /dev/null +++ b/src/components/common/modals/invite-member/DeleteMembers/index.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { Button } from "~/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "~/components/ui/dialog"; + +interface ModalProperties { + show: boolean; + onClose: () => void; +} + +const DeleteMember: React.FC = ({ show, onClose }) => { + return ( + + + + + + {" "} +

+ Delete Member +

+
+
+

+ Are you sure you want to delete Chad Bosewick ? All data will be + permanently removed. This action cannot be undone. +

+ +
+
+ + + +
+
+
+
+
+
+
+ ); +}; + +export default DeleteMember; diff --git a/src/components/layouts/footer/index.tsx b/src/components/layouts/footer/index.tsx index 6e0c3b550..91c8a4b24 100644 --- a/src/components/layouts/footer/index.tsx +++ b/src/components/layouts/footer/index.tsx @@ -156,8 +156,6 @@ const Footer = () => { { route: "termsOfUse", link: "/terms-and-conditions" }, ]; - // - return (