Skip to content

Commit

Permalink
Merge pull request #25 from wafflestudio/develop
Browse files Browse the repository at this point in the history
Add Email Verification to main
  • Loading branch information
Scripter36 committed Aug 11, 2024
2 parents 2801bbd + d22b8b6 commit 0ecfb04
Show file tree
Hide file tree
Showing 24 changed files with 526 additions and 109 deletions.
17 changes: 17 additions & 0 deletions src/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export type editProfileRequest = {
password: string;
};

export type VerifyEmailRequest = {
email: string;
code: string;
};

export const EMAIL_REGEX =
"^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$";
export const PASSWORD_REGEX = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[\\W_]).{8,20}$";
Expand Down Expand Up @@ -92,6 +97,18 @@ export const isEmailConflict = async (
return res.status == HttpStatusCode.Conflict;
};

// GET /api/auth/email?email=”String”
export const sendVerificationEmail = async (email: string): Promise<void> => {
return await axios.get("/api/auth/email", {
params: { email },
});
};

// POST /api/auth/verification
export const verifyEmail = async (req: VerifyEmailRequest): Promise<void> => {
return await axios.post("/api/auth/verification", req);
};

export const useUser = () => {
return useQuery<User | null>({
queryKey: ["user"],
Expand Down
5 changes: 5 additions & 0 deletions src/assets/alert/circleCheckGreen.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
2 changes: 1 addition & 1 deletion src/components/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ function Alert({ type, children, size, ...props }: AlertProps) {
const AlertContainer = styled.div`
width: 100%;
padding: 1.6rem;
border-radius: 0.8rem;
display: flex;
flex-direction: row;
align-items: top;
background-color: ${({ theme }) => theme.colors.red["50"]};
`;
Expand Down
18 changes: 8 additions & 10 deletions src/components/FloatingSupportButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@ import { Link } from "react-router-dom";
import styled from "styled-components";

import bubbleTip from "../assets/support/bubbleTip.svg";
import contactSupport from "../assets/support/contactSupport.svg";
import feedback from "../assets/support/feedback.svg";
import questionMark from "../assets/support/questionMark.svg";
import terms from "../assets/support/terms.svg";

const FloatingSupportButton = () => {
const [showBubble, setShowBubble] = useState(false);
const [showMenu, setShowMenu] = useState(false);

console.log(showBubble, showMenu);

return (
<Container
clickable={showMenu}
Expand All @@ -27,14 +25,14 @@ const FloatingSupportButton = () => {
<BubbleTipImg src={bubbleTip} alt="Bubble Tip" />
</ReportBubble>
<ReportMenuContainer style={{ opacity: showMenu ? 1 : 0 }}>
<ReportMenuLink to="/support">
<img src={contactSupport} alt="Contact Support" />
Contact Support
<ReportMenuLink to="https://tally.so/r/w7WY6P" target="_blank">
<img src={feedback} alt="Contact Support" />
Contact & Feedback
</ReportMenuLink>
<Divider />
<ReportMenuLink to="/feedback">
<img src={feedback} alt="Feedback" />
Feedback
<ReportMenuLink to="/terms_of_use" target="_blank">
<img src={terms} alt="Terms of Use" />
Terms of Use
</ReportMenuLink>
</ReportMenuContainer>
</ContextContainer>
Expand All @@ -46,7 +44,7 @@ const FloatingSupportButton = () => {
setShowBubble(false);
}}
onClick={(e) => {
setShowMenu(true);
setShowMenu(!showMenu);
setShowBubble(false);
e.stopPropagation();
}}
Expand Down
69 changes: 69 additions & 0 deletions src/components/SnackBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import styled, { keyframes } from "styled-components";

import alertTriangleRed from "../assets/alert/alertTriangleRed.svg";
import circleCheckGreen from "../assets/alert/circleCheckGreen.svg";

interface SnackBarProps extends React.HTMLAttributes<HTMLDivElement> {
isSuccess: boolean;
message: string;
}

function SnackBar({ isSuccess, message, ...props }: SnackBarProps) {
return (
<Container isSuccess={isSuccess} {...props}>
<img src={isSuccess ? circleCheckGreen : alertTriangleRed} alt="icon" />
<h2>{message}</h2>
</Container>
);
}

const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;

const fadeOut = keyframes`
from {
opacity: 1;
}
to {
opacity: 0;
}
`;

const Container = styled.div<{ isSuccess: boolean }>`
display: flex;
flex-direction: row;
width: 85%;
height: 5.6rem;
padding: 1.6rem 2.4rem;
gap: 1rem;
border-radius: 0.8rem;
position: absolute;
bottom: 3rem;
left: 50%;
transform: translateX(-50%);
background-color: ${({ theme, isSuccess }) =>
isSuccess ? theme.colors.green[50] : theme.colors.red[50]};
animation:
${fadeIn} 0.2s ease-in,
${fadeOut} 0.2s ease-out 2s;
& h2 {
${({ theme }) => theme.typography.b2};
color: ${({ theme, isSuccess }) =>
isSuccess ? "#559764" : theme.colors.red[500]};
}
`;

export default SnackBar;
2 changes: 2 additions & 0 deletions src/components/class/ClassItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ const ContentLink = styled(Link)`
p.description {
flex: 1;
white-space: pre-wrap;
}
`;

Expand Down
15 changes: 8 additions & 7 deletions src/components/dashboard/CreateSessionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState } from "react";
import styled from "styled-components";

import useSubmitHandler from "../../hooks/submitHandler.tsx";
import { ModalStateType } from "../../type";
import { GreyButton, PrimaryButton } from "../Button";
import Modal from "../Modal";
Expand All @@ -10,19 +11,17 @@ interface CreateSessionModalProps {
state: ModalStateType;
onClose: () => void;
onSubmit?: (sessionName: string) => void;
setSessionState: React.Dispatch<
React.SetStateAction<"none" | "pending" | "activated">
>;
}

function CreateSessionModal({
state,
onClose,
onSubmit,
setSessionState,
}: CreateSessionModalProps) {
const [sessionName, setSessionName] = useState("");

const { isSubmitting, handleSubmit } = useSubmitHandler();

return (
<Modal state={state} onBackgroundClick={onClose}>
<Container>
Expand All @@ -37,10 +36,12 @@ function CreateSessionModal({
<GreyButton onClick={onClose}>Cancel</GreyButton>
<PrimaryButton
onClick={() => {
onSubmit?.(sessionName);
setSessionState("pending");
handleSubmit(() => {
onSubmit?.(sessionName);
setSessionName("");
});
}}
disabled={sessionName === ""}
disabled={sessionName === "" || isSubmitting}
>
Create a New Session
</PrimaryButton>
Expand Down
12 changes: 9 additions & 3 deletions src/components/dashboard/InfoCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ function InfoCards({
queryClient.invalidateQueries({
queryKey: ["previousSessions", classId],
});
const channel = new BroadcastChannel("sessionRefresh");
channel.postMessage("refresh");
channel.close();
},
});

Expand All @@ -69,6 +66,15 @@ function InfoCards({
}
}, [classId, isError, queryClient]);

useEffect(() => {
const channel = new BroadcastChannel("sessionDelete");
channel.onmessage = () => {
queryClient.invalidateQueries({
queryKey: ["currentSessionInfo", classId],
});
};
}, [classId, queryClient]);

return (
<InfoCardContainer {...props}>
{currentSessionInfo?.id != null ? (
Expand Down
27 changes: 27 additions & 0 deletions src/hooks/snackbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useState, useRef } from "react";

function useSnackbar() {
const [showSnackbar, setShowSnackbar] = useState(false);
const snackbarTimeoutRef = useRef<NodeJS.Timeout | null>(null);

const show = () => {
setShowSnackbar(true);
if (snackbarTimeoutRef.current) {
clearTimeout(snackbarTimeoutRef.current);
}
snackbarTimeoutRef.current = setTimeout(() => {
setShowSnackbar(false);
}, 2200);
};

const hide = () => {
setShowSnackbar(false);
if (snackbarTimeoutRef.current) {
clearTimeout(snackbarTimeoutRef.current);
}
};

return { showSnackbar, show, hide };
}

export default useSnackbar;
39 changes: 39 additions & 0 deletions src/hooks/submitHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useState, useRef, useCallback } from "react";

// Utility function to wrap synchronous functions in a Promise
function toAsync(fn: () => void | Promise<void>): () => Promise<void> {
return async () => {
if (fn.length === 0) {
return Promise.resolve(fn());
} else {
return fn();
}
};
}

function useSubmitHandler() {
const [isSubmitting, setIsSubmitting] = useState(false);
const isSubmittingRef = useRef(isSubmitting);

const handleSubmit = useCallback(
async (submitFunction: () => void | Promise<void>) => {
if (isSubmittingRef.current) return;

setIsSubmitting(true);
isSubmittingRef.current = true;

try {
const asyncSubmitFunction = toAsync(submitFunction);
await asyncSubmitFunction();
} finally {
setIsSubmitting(false);
isSubmittingRef.current = false;
}
},
[]
);

return { isSubmitting, handleSubmit };
}

export default useSubmitHandler;
11 changes: 9 additions & 2 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PrimaryButton } from "../components/Button.tsx";
import NoteSelectModal from "../components/home/NoteSelectModal.tsx";
import TextField from "../components/TextField";
import useModalState from "../hooks/modal.tsx";
import useSubmitHandler from "../hooks/submitHandler.tsx";

const ErrorMessages: Record<AttendanceErrorCode, string> = {
[AttendanceErrorCode.InvalidAuthCode]: `Could not find a session corresponding to passcode. Please check your credentials or contact the administrator for help.`,
Expand Down Expand Up @@ -56,6 +57,12 @@ function Home() {
const [noteSelectModalState, openNoteSelectModal, closeNoteSelectModal] =
useModalState();

const submit = () => {
performAttendance({ attendeeName: name, authCode: passcode });
};

const { isSubmitting, handleSubmit } = useSubmitHandler();

return (
<>
<NoteSelectModal
Expand Down Expand Up @@ -83,7 +90,7 @@ function Home() {
<InputContainer
onSubmit={(e) => {
e.preventDefault();
performAttendance({ attendeeName: name, authCode: passcode });
handleSubmit(submit);
}}
>
<TextField
Expand Down Expand Up @@ -113,7 +120,7 @@ function Home() {
width: "11.5rem",
marginLeft: "auto",
}}
disabled={name === "" || passcode === ""}
disabled={name === "" || passcode === "" || isSubmitting}
>
<img src={sendIcon} alt="Send" width={20} height={20} />
Send
Expand Down
Loading

0 comments on commit 0ecfb04

Please sign in to comment.