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

frontend: add sign in confirmation modal + reword show review limit alert #77

Merged
merged 4 commits into from
May 17, 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
9 changes: 2 additions & 7 deletions apps/web/app/courses/[courseCode]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,6 @@ export default function CourseReview({ params }: { params: { courseCode: string
setReviewsData(reviews);
} catch (err) {
console.error(err);
notifications.show({
title: "Error!",
message: "Something went wrong, please try again later",
color: "red"
});
} finally {
setIsLoading(false);
}
Expand Down Expand Up @@ -148,8 +143,8 @@ export default function CourseReview({ params }: { params: { courseCode: string
<Stack gap="sm">
{showReviewLimitAlert ? (
<Alert color="yellow" icon={<IconLock />}>
Explore up to 2 reviews per course. Share your thoughts by writing your first review to discover
more reviews and insights!
Explore up to 2 reviews per course. Unlock this by submitting your first review in any courses
you have taken.
</Alert>
) : null}
{reviewsData?.data && reviewsData.data.length > 0
Expand Down
39 changes: 10 additions & 29 deletions apps/web/components/core/application-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { useAuth } from "hooks/use-auth";
import Link from "next/link";
import { useEffect, useState } from "react";
import { navItems } from "./application-navbar";

import { useDisclosure } from "@mantine/hooks";
import type { Session } from "@supabase/supabase-js";
import SigninConfirmationModal from "@components/ui/signin-confirmation";

interface ApplicationHeaderProps {
opened: boolean;
Expand All @@ -14,11 +15,10 @@ interface ApplicationHeaderProps {

// Assuming proper typing for SupabaseClient is completed outside this snippet
export default function ApplicationHeader({ opened, toggle }: ApplicationHeaderProps): JSX.Element {
const { signIn, signOut, getSession } = useAuth();
const { signOut, getSession, working } = useAuth();
const { supabase } = useSupabaseStore();
const [sessionData, setSessionData] = useState<Session>();

const [working, setWorking] = useState(false);
const [openedSignInConfirmation, { open: openConfirmation, close: closeConfirmation }] = useDisclosure(false);

useEffect(() => {
getSession()
Expand All @@ -45,29 +45,6 @@ export default function ApplicationHeader({ opened, toggle }: ApplicationHeaderP

const isLoggedIn = sessionData?.user !== undefined;

const handleSignOut = (): void => {
if (working) return;
setWorking(true);

signOut()
.catch(console.error)
.finally(() => {
setWorking(false);
});
};

// TODO: better anti-spam functionality. signInWithOAuth resolves very quickly and can be spammed.. for some reason.
const handleSignInWithAzure = (): void => {
if (working) return;
setWorking(true);

signIn()
.catch(console.error)
.finally(() => {
setWorking(false);
});
};

return (
<AppShell.Header>
<Group h="100%" px="md">
Expand Down Expand Up @@ -104,7 +81,7 @@ export default function ApplicationHeader({ opened, toggle }: ApplicationHeaderP
{sessionData.user.email ? sessionData.user.email.split("@")[0] : "No email"}
</Menu.Item>
<Menu.Label>Actions</Menu.Label>
<Button disabled={working} className="w-full" color="red" onClick={handleSignOut}>
<Button disabled={working} className="w-full" color="red" onClick={signOut}>
Sign Out
</Button>
</Menu.Dropdown>
Expand All @@ -113,12 +90,16 @@ export default function ApplicationHeader({ opened, toggle }: ApplicationHeaderP
<Button
disabled={working}
variant="default"
onClick={handleSignInWithAzure}
onClick={openConfirmation}
className="select-none text-lg font-bold uppercase"
>
Sign In
</Button>
)}
<SigninConfirmationModal
opened={openedSignInConfirmation}
close={closeConfirmation}
/>
</div>
</Group>
</AppShell.Header>
Expand Down
36 changes: 8 additions & 28 deletions apps/web/components/core/application-navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { AppShell, Button, Flex, NavLink, Stack } from "@mantine/core";
import { useAuth } from "hooks/use-auth";
import Link from "next/link";
import { useEffect, useState } from "react";

import { useDisclosure } from "@mantine/hooks";
import { IconBooks, IconHistory, IconHome } from "@tabler/icons-react";
import SigninConfirmationModal from "@components/ui/signin-confirmation";

export const navItems = [
{
label: "Home",
Expand All @@ -25,9 +27,9 @@ export const navItems = [
];

export default function ApplicationNavbar({ opened, toggle }: { opened: boolean; toggle: () => void }): JSX.Element {
const { signIn, signOut, getSession } = useAuth();
const [working, setWorking] = useState(false);
const { signOut, working, getSession } = useAuth();
const [signedIn, setSignedIn] = useState(false);
const [openedSignInConfirmation, { open: openConfirmation, close: closeConfirmation }] = useDisclosure(false);

useEffect(() => {
getSession()
Expand All @@ -39,29 +41,6 @@ export default function ApplicationNavbar({ opened, toggle }: { opened: boolean;
.catch(console.error);
}, [getSession]);

// TODO: This is code duplicated with application-header.tsx, should be refactored into a hook
const handleSignInWithAzure = (): void => {
if (working) return;
setWorking(true);

signIn()
.catch(console.error)
.finally(() => {
setWorking(false);
});
};

const handleSignOut = (): void => {
if (working) return;
setWorking(true);

signOut()
.catch(console.error)
.finally(() => {
setWorking(false);
});
};

return (
<AppShell.Navbar p="md" hidden={!opened}>
<Stack h="100%" gap="xs">
Expand All @@ -83,19 +62,20 @@ export default function ApplicationNavbar({ opened, toggle }: { opened: boolean;

<Flex className="mt-auto">
{signedIn ? (
<Button color="red" className="w-full" onClick={handleSignOut}>
<Button color="red" className="w-full" onClick={signOut}>
Sign Out
</Button>
) : (
<Button
disabled={working}
variant="default"
onClick={handleSignInWithAzure}
onClick={openConfirmation}
className="w-full select-none text-lg font-bold uppercase"
>
Sign In
</Button>
)}
<SigninConfirmationModal opened={openedSignInConfirmation} close={closeConfirmation} />
</Flex>
</Stack>
</AppShell.Navbar>
Expand Down
Empty file.
38 changes: 38 additions & 0 deletions apps/web/components/ui/signin-confirmation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Alert, Button, Divider, Flex, Group, Modal, Text, Title } from "@mantine/core";
import { IconAlertCircle } from "@tabler/icons-react";
import { useAuth } from "hooks/use-auth";

export default function SigninConfirmationModal({ opened, close }: { opened: boolean; close: () => void }) {
const { signIn, working } = useAuth();

return (
<Modal
opened={opened}
onClose={close}
title={<Title order={3}>Signing in to Course Compose</Title>}
size="auto"
>
<Flex direction="column" gap="lg" align="center" maw={500}>
<Alert icon={<IconAlertCircle />}>
Please read the following to if you have any concerns regarding the use of your Stamford accounts
</Alert>

<Text ta="center">
By signing in, you will be redirected to the Microsoft login Page which <b>automatically</b>{" "}
retrieves your recent sessions. This process is solely to confirm your status as a Stamford student.
</Text>
<Text ta="center" fs="italic">
We, Stamford Syntax Club, <b>do not have</b> access to your credentials
</Text>
<Group>
<Button disabled={working} onClick={signIn}>
Sign In
</Button>
<Button variant="transparent" onClick={close}>
No, take me back
</Button>
</Group>
</Flex>
</Modal>
);
}
16 changes: 14 additions & 2 deletions apps/web/hooks/use-auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useSupabaseStore } from "@stores/supabase-store";
import type { Session, SupabaseClient } from "@supabase/supabase-js";
import { useCallback } from "react";
import { useCallback, useState } from "react";

async function signInWithAzure(supabase: SupabaseClient): Promise<void> {
try {
Expand All @@ -26,14 +26,18 @@ async function emailPasswordSignIn(supabase: SupabaseClient, email: string, pass
interface UseAuthReturnType {
signIn: () => Promise<void>;
signOut: () => Promise<void>;
working: boolean;
getSession: () => Promise<Session | null>;
}

export const useAuth = (): UseAuthReturnType => {
const { supabase } = useSupabaseStore();

const [working, setWorking] = useState(false);

const signIn = useCallback(async (): Promise<void> => {
if (!supabase) return;
if (working) return;

try {
// TODO: Implement a proper way to use email/password in beta
Expand All @@ -51,19 +55,27 @@ export const useAuth = (): UseAuthReturnType => {
// await emailPasswordSignIn(supabase, email, password);
// }

setWorking(true);
await signInWithAzure(supabase);
} catch (error) {
console.error("Error during the sign-in process:", error);
} finally {
setWorking(false);
}
}, [supabase]);

const signOut = useCallback(async (): Promise<void> => {
if (!supabase) return;
if (working) return;

try {
setWorking(true);
await supabase.auth.signOut();
} catch (error) {
console.error("Error during the sign-out process:", error);
} finally {
setWorking(false);
window.location.reload();
}
}, [supabase]);

Expand All @@ -89,5 +101,5 @@ export const useAuth = (): UseAuthReturnType => {
}, [supabase]);

// Returning the signIn function and any error that might have occurred
return { signIn, signOut, getSession };
return { signIn, signOut, working, getSession };
};
30 changes: 26 additions & 4 deletions apps/web/lib/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
ERR_USER_NOT_EXIST,
ERR_MISSING_TOKEN,
ERR_USER_NOT_OWNER,
COURSE_API_ENDPOINT
COURSE_API_ENDPOINT,
ERR_USER_NOT_STUDENT
} from "@utils/constants";
import type { NotificationData } from "@mantine/notifications";
import { notifications, type NotificationData } from "@mantine/notifications";

export default class CourseComposeAPIClient {
private courseEndpoint: string;
Expand Down Expand Up @@ -38,8 +39,7 @@ export default class CourseComposeAPIClient {

return data.json() as Promise<PaginatedResponse<Course>>;
}



async fetchCourseDetails(): Promise<Course> {
const data = await fetch(this.courseEndpoint);

Expand All @@ -60,6 +60,21 @@ export default class CourseComposeAPIClient {

if (!data.ok) {
const err = (await data.json()) as ErrorResponse;
if (err.message === ERR_USER_NOT_STUDENT) {
notifications.show({
title: err.message,
message: "Please sign out as guests to continue using the site",
color: "red",
autoClose: 5000
});
} else {
notifications.show({
title: "Something is wrong on our end",
message: "We could not retrieve reviews for this course at the moment, please try again later",
color: "red",
autoClose: 5000
});
}
throw new Error(err.message);
}

Expand Down Expand Up @@ -109,6 +124,13 @@ export default class CourseComposeAPIClient {
color: "red",
autoClose: 5000
};
case ERR_USER_NOT_STUDENT:
return {
title: errMsg,
message: "Please sign out as guests to continue using the site",
color: "red",
autoClose: 5000
};
default:
return {
title: "Something is wrong on our end",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "1.0.0",
"version": "1.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbo",
Expand Down
1 change: 1 addition & 0 deletions apps/web/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const ERR_EXPIRED_TOKEN = "token has invalid claims: token is expired";
export const ERR_REVIEW_EXIST = "You have already written a review for this course";
export const ERR_USER_NOT_EXIST = "User does not exist";
export const ERR_USER_NOT_OWNER = "User is not the owner of this review";
export const ERR_USER_NOT_STUDENT = "Only Stamford students can access this service";
export const ERR_INTERNAL_SERVER = "Internal Server Error";

if (!process.env.NEXT_PUBLIC_BACKEND_URL) {
Expand Down
Loading