Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add onboarding #77

Merged
merged 6 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions frontend/app/(authenticated)/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import clsx from "clsx";
import { ArrowRight } from "lucide-react";

import { Button } from "@/components/ui/button";
import { LoadingSpinner } from "@/components/ui/loading-spinner";
import { getCategories } from "@/queries/category";
import { useUpdateProfile } from "@/queries/user";
import { useUserStore } from "@/store/user/user-store-provider";
import { getCategoryFor } from "@/types/categories";

export default function Onboarding() {
const { data: categories, isLoading } = useQuery(getCategories());
const [categoryIds, setCategoryIds] = useState<number[]>([]);
const user = useUserStore((state) => state.user);

const toggleCategory = (id: number) => {
if (!categoryIds.includes(id)) {
setCategoryIds([...categoryIds, id]);
} else {
setCategoryIds(categoryIds.filter((item) => item !== id));
}
};

const updateProfileMutation = useUpdateProfile();
const router = useRouter();

const handleSubmit = () => {
updateProfileMutation.mutate(
{ categoryIds },
{
onSuccess: () => {
router.push("/");
},
},
);
};

if (user?.categories.length) {
router.push("/");
}

return (
<div className="flex justify-center items-center h-[calc(100vh_-_72px)]">
<div className="flex flex-col items-center gap-16">
{isLoading && <LoadingSpinner className="w-24 h-24" />}
{!isLoading && (
<>
<h1 className="text-3xl font-bold text-center leading-[3rem]">
Let&apos;s get started!
<br />
What are your preferred GP categories?
</h1>
<div className="flex flex-wrap gap-4 justify-center max-w-2xl">
{categories!.map((category) => {
const isActive = categoryIds.includes(category.id);
return (
<div
className={clsx(
"font-medium p-3 px-4 rounded-3xl cursor-pointer shadow-md",
{
"bg-emerald-600 text-white": isActive,
"bg-slate-100": !isActive,
},
)}
key={category.id}
onClick={() => toggleCategory(category.id)}
>
{getCategoryFor(category.name)}
</div>
);
})}
</div>
<Button className="flex items-center gap-2" onClick={handleSubmit}>
Save and Continue
<ArrowRight className="w-4 h-4" />
</Button>
</>
)}
</div>
</div>
);
}
5 changes: 5 additions & 0 deletions frontend/app/(authenticated)/template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ export default function RedirectIfNotAuthenticated({
const { status } = useQuery(getUserProfile());

const isLoggedIn = useUserStore((state) => state.isLoggedIn);
const user = useUserStore((state) => state.user);
console.log({ isLoggedIn, status });

if (!isLoggedIn && !status) {
router.push("/login");
}
if (isLoggedIn && !user!.categories.length) {
router.push("/onboarding");
}

return <Suspense>{children}</Suspense>;
}
2 changes: 1 addition & 1 deletion frontend/app/(unauthenticated)/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function RegisterPage() {
} else {
setIsError(false);
setLoggedIn(response.data.user);
router.push("/login");
router.push("/onboarding");
}
};

Expand Down
6 changes: 6 additions & 0 deletions frontend/app/home.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";

import { getEventsEventsGet, MiniEventDTO } from "@/client";
import ArticleLoading from "@/components/news/article-loading";
Expand All @@ -14,6 +15,11 @@ const Home = () => {
const [isLoaded, setIsLoaded] = useState<boolean>(false);
const user = useUserStore((state) => state.user);

const router = useRouter();
if (!user!.categories.length) {
router.push("/onboarding");
}

useEffect(() => {
const fetchTopEvents = async () => {
const dateNow = new Date();
Expand Down
58 changes: 33 additions & 25 deletions frontend/components/layout/app-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { ComponentProps, ReactNode, useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import { useQuery } from "@tanstack/react-query";

import Navbar from "@/components/navigation/navbar";
Expand Down Expand Up @@ -81,35 +82,42 @@ const AppLayout = ({ children }: { children: ReactNode }) => {
}
}, [userProfile, isUserProfileSuccess, setLoggedIn, setNotLoggedIn]);

const pathname = usePathname();
const isOnboarding = pathname === "/onboarding";

return (
<div className="relative flex min-h-screen flex-col bg-background">
<Navbar />
<main>
<ResizablePanelGroup
className="flex flex-1 w-full h-[calc(100vh_-_72px)]"
direction="horizontal"
>
{isLoggedIn && (
<>
<ResizablePanel
className="flex w-full"
id="sidebar"
onCollapse={() => setIsCollapsed(true)}
onExpand={() => setIsCollapsed(false)}
order={1}
{...breakpointConfigMap[mediaBreakpoint]}
>
<Sidebar />
</ResizablePanel>
<ResizableHandle withHandle={isCollapsed} />
</>
)}
<ResizablePanel defaultSize={75} order={2}>
<div className="flex flex-1 w-full h-[calc(100vh_-_72px)] min-h-[calc(100vh_-_72px)] overflow-y-scroll">
{children}
</div>
</ResizablePanel>
</ResizablePanelGroup>
{isOnboarding ? (
children
) : (
<ResizablePanelGroup
className="flex flex-1 w-full h-[calc(100vh_-_72px)]"
direction="horizontal"
>
{isLoggedIn && (
<>
<ResizablePanel
className="flex w-full"
id="sidebar"
onCollapse={() => setIsCollapsed(true)}
onExpand={() => setIsCollapsed(false)}
order={1}
{...breakpointConfigMap[mediaBreakpoint]}
>
<Sidebar />
</ResizablePanel>
<ResizableHandle withHandle={isCollapsed} />
</>
)}
<ResizablePanel defaultSize={75} order={2}>
<div className="flex flex-1 w-full h-[calc(100vh_-_72px)] min-h-[calc(100vh_-_72px)] overflow-y-scroll">
{children}
</div>
</ResizablePanel>
</ResizablePanelGroup>
)}
</main>
<Toaster />
</div>
Expand Down
27 changes: 25 additions & 2 deletions frontend/queries/user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { queryOptions } from "@tanstack/react-query";
import {
queryOptions,
useMutation,
useQueryClient,
} from "@tanstack/react-query";

import { getUserAuthSessionGet } from "@/client/services.gen";
import {
getUserAuthSessionGet,
updateProfileProfilePut,
} from "@/client/services.gen";

import { QueryKeys } from "./utils/query-keys";

Expand All @@ -12,3 +19,19 @@ export const getUserProfile = () =>
(data) => data.data,
),
});

export const useUpdateProfile = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ categoryIds }: { categoryIds: number[] }) => {
return updateProfileProfilePut({
body: {
category_ids: categoryIds,
},
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QueryKeys.UserProfile] });
},
});
};
5 changes: 5 additions & 0 deletions frontend/types/categories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Microscope,
Palette,
Scale,
School,
UsersRound,
} from "lucide-react";

Expand All @@ -19,6 +20,7 @@ export enum Category {
Politics = "Politics",
Media = "Media",
Environment = "Environment",
Education = "Education",
Economics = "Economics",
Sports = "Sports",
GenderEquality = "Gender & equality",
Expand All @@ -39,6 +41,7 @@ export const getCategoryFor = (categoryName: string) => {
"gender & equality": Category.GenderEquality,
religion: Category.Religion,
"society & culture": Category.SocietyCulture,
education: Category.Education,
};
const formattedName = categoryName.toLowerCase();
if (formattedName in mappings) {
Expand All @@ -58,6 +61,7 @@ export const categoriesToDisplayName: Record<Category, string> = {
[Category.GenderEquality]: "Gender & equality",
[Category.Religion]: "Religion",
[Category.SocietyCulture]: "Society & culture",
[Category.Education]: "Education",
[Category.Others]: "Others",
};

Expand All @@ -72,5 +76,6 @@ export const categoriesToIconsMap: Record<Category, LucideIcon> = {
[Category.GenderEquality]: Scale,
[Category.Religion]: HeartHandshake,
[Category.SocietyCulture]: UsersRound,
[Category.Education]: School,
[Category.Others]: CircleHelp,
};
Loading