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

Develop #29

Merged
merged 13 commits into from
Oct 4, 2024
20 changes: 16 additions & 4 deletions src/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ export type VerifyEmailRequest = {
};

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}$";
export const NAME_REGEX = "^(?=.*[a-zA-Z0-9가-힣])[a-zA-Z0-9가-힣]{2,16}$";
/^[\w!#$%&'*+/=?`{|}~^.-]+(?:\.[\w!#$%&'*+/=?`{|}~^.-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}$/;
export const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[\W_]).{8,20}$/;
export const NAME_REGEX = /^(?=.*[a-zA-Z0-9가-힣])[a-zA-Z0-9가-힣]{2,16}$/;

export const getUser = async (): Promise<User | null> => {
const res = await axios.get<User>("/api/auth/me", {
Expand Down Expand Up @@ -98,17 +98,29 @@ export const isEmailConflict = async (
};

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

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

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

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

export const useUser = () => {
return useQuery<User | null>({
queryKey: ["user"],
Expand Down
4 changes: 4 additions & 0 deletions src/assets/success.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/FloatingSupportButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const FloatingSupportButton = () => {
Contact & Feedback
</ReportMenuLink>
<Divider />
<ReportMenuLink to="/terms_of_use" target="_blank">
<ReportMenuLink to="/terms-of-use" target="_blank">
<img src={terms} alt="Terms of Use" />
Terms of Use
</ReportMenuLink>
Expand Down
2 changes: 1 addition & 1 deletion src/components/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function SideBar() {
mutationKey: ["logout"],
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user"] });
navigate("/admin/signin");
navigate("/host/signin");
},
});

Expand Down
1 change: 1 addition & 0 deletions src/components/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ function TextField({
}

// create class, setting, account setting에서만 쓰임
// label이 왼쪽에 있음
function SingleLineTextField({
label,
style,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function AuthRequiredRoute() {
}

if (!user) {
return <Navigate to="/admin/signin" replace />;
return <Navigate to="/host/signin" replace />;
}

if (classId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
// Admin과 Attendees를 전환하기 위한 상단 배너입니다.
// Host와 Attendees를 전환하기 위한 상단 배너입니다.

import { Link } from "react-router-dom";
import styled from "styled-components";

import { useUser } from "../../api/user.ts";

interface TransferBannerProps {
from: "admin" | "attendees";
from: "host" | "attendees";
}

function TransferBanner(props: TransferBannerProps) {
const { data: user } = useUser();

return (
<>
{props.from === "admin" ? (
<BannerLink to="/">If you are Attendees</BannerLink>
{props.from === "host" ? (
<BannerLink to="/">If you are an Attendee, click here!</BannerLink>
) : (
<BannerLink to="/class">If you are Admin</BannerLink>
<BannerLink to={user ? "/class" : "/host/signin"}>
If you are the Host, click here!
</BannerLink>
)}
</>
);
Expand Down
53 changes: 53 additions & 0 deletions src/hooks/password.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useState, useEffect } from "react";

import { PASSWORD_REGEX } from "../api/user.ts";

export function usePasswordValidation() {
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");

const [isPasswordFocused, setIsPasswordFocused] = useState(false);
const [isConfirmPasswordFocused, setIsConfirmPasswordFocused] =
useState(false);

const [isPasswordError, setIsPasswordError] = useState(false);
const [isConfirmPasswordError, setIsConfirmPasswordError] = useState(false);
const [isButtonDisabled, setIsButtonDisabled] = useState(true);

useEffect(() => {
if (password === "") {
setIsPasswordError(false);
} else {
setIsPasswordError(!PASSWORD_REGEX.test(password));
}

if (confirmPassword === "") {
setIsConfirmPasswordError(false);
} else {
setIsConfirmPasswordError(password !== confirmPassword);
}
}, [isPasswordFocused, isConfirmPasswordFocused]);

useEffect(() => {
setIsButtonDisabled(
password === "" ||
confirmPassword === "" ||
!PASSWORD_REGEX.test(password) ||
password !== confirmPassword
);
}, [password, confirmPassword]);

return {
password,
setPassword,
confirmPassword,
setConfirmPassword,
isPasswordFocused,
setIsPasswordFocused,
isConfirmPasswordFocused,
setIsConfirmPasswordFocused,
isPasswordError,
isConfirmPasswordError,
isButtonDisabled,
};
}
7 changes: 3 additions & 4 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import styled from "styled-components";

import { AttendanceErrorCode, postAttend } from "../api/attendance.ts";
import sendIcon from "../assets/send.svg";
import TransferBanner from "../components/admin/TransferBanner.tsx";
import Alert from "../components/Alert.tsx";
import { PrimaryButton } from "../components/Button.tsx";
import NoteSelectModal from "../components/home/NoteSelectModal.tsx";
import TransferBanner from "../components/host/TransferBanner.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.`,
[AttendanceErrorCode.InvalidName]: `Could not find a session corresponding to your name. Please check your credentials or contact the administrator for help.`,
[AttendanceErrorCode.InvalidAuthCode]: `Could not find a session corresponding to passcode. Please check your credentials or contact the host for help.`,
[AttendanceErrorCode.InvalidName]: `Could not find a session corresponding to your name. Please check your credentials or contact the host for help.`,
[AttendanceErrorCode.AlreadyAttended]: `You have already attended the session.`,
[AttendanceErrorCode.DifferentName]: `You have already attended the session with a different name.`,
[AttendanceErrorCode.FailedToAttend]: `Failed to attend the session. Please try again later.`,
Expand Down Expand Up @@ -49,7 +49,6 @@ function Home() {
}
},
onError: (error) => {
console.error(error);
setErrorMessage(ErrorMessages[error.message as AttendanceErrorCode]);
},
});
Expand Down
20 changes: 10 additions & 10 deletions src/pages/admin/SignIn.tsx → src/pages/host/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { Link, useNavigate } from "react-router-dom";
import styled from "styled-components";

import { EMAIL_REGEX, signIn, useUser } from "../../api/user";
import Alert from "../../components/Alert";
import { PrimaryButton } from "../../components/Button";
import {
OptionalActionLabel,
OptionalActionLink,
} from "../../components/admin/OptionalAction";
import TransferBanner from "../../components/admin/TransferBanner";
import Alert from "../../components/Alert";
import { PrimaryButton } from "../../components/Button";
} from "../../components/host/OptionalAction";
import TransferBanner from "../../components/host/TransferBanner";
import TextField from "../../components/TextField";

function SignIn() {
Expand All @@ -20,7 +20,7 @@ function SignIn() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");

const emailError = email.match(EMAIL_REGEX) == null;
const emailError = !EMAIL_REGEX.test(email);
const [showError, setShowError] = useState(false);

const [resultError, setResultError] = useState<string | undefined>();
Expand Down Expand Up @@ -48,7 +48,7 @@ function SignIn() {

return (
<Container>
<TransferBanner from="admin" />
<TransferBanner from="host" />
<LoginContainer>
<DisplayMessage>
Simply manage attendance
Expand All @@ -71,7 +71,7 @@ function SignIn() {
type="text"
name="email"
autoComplete="username"
label="Email address"
label="Email Address"
onChange={(e) => setEmail(e.target.value)}
supportingText={
showError && emailError ? "Invalid email address" : undefined
Expand All @@ -86,7 +86,7 @@ function SignIn() {
onChange={(e) => setPassword(e.target.value)}
/>
<ForgotPasswordLink
to="/admin/forgotpassword"
to="/host/forgot-password/email"
style={{ marginTop: "1.4rem" }}
>
Forgot Password
Expand All @@ -105,8 +105,8 @@ function SignIn() {
</PrimaryButton>
<OptionalActionLabel style={{ marginTop: "5.0rem" }}>
Don't have an account?{" "}
<OptionalActionLink to="/admin/signup">
Sign up now
<OptionalActionLink to="/host/signup">
Sign Up Now
</OptionalActionLink>
</OptionalActionLabel>
</InputContainer>
Expand Down
Loading