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

[7주차] Team 페달지니 윤영준 & 박지수 미션 제출합니다. #2

Open
wants to merge 36 commits into
base: master
Choose a base branch
from

Conversation

yyj0917
Copy link

@yyj0917 yyj0917 commented Jan 4, 2025

배포링크


영준 `s Comment


지금까지 통신하는 것 없이 과제를 진행해오다 보니 통신과 api를 어떻게 했는지 기억이 잘 안나서 초반에 많이 헤맸던 것 같습니다. 프로젝트 개발에 앞서 이번 과제를 통해 통신을 미리 접하고, 공부하고 갈 수 있어 의미있었습니다. 특히 백엔드 팀이 배포를 빨리 해주고, 수정사항이 생길 때마다 바로바로 해결해주었기에 프론트 입장에서 정말 수월하게 개발을 진행할 수 있었습니다. 마지막 과제를 마치면서 CEOS에서 했던 과제들을 돌이켜보니 지금의 저에게 많은 도움이 되었고, 이를 기반으로 프로젝트까지 잘 해보도록 노력해보겠습니다. 프론트 운영진분들, 프론트 부원분들 한 학기동안 정말 고생많으셨습니다!


지수`s Comment


백엔드와 협업해서 한 이번 주차! 파트장분들께서 기간도 여유롭게 주셔서 부담 없이 잘 진행할 수 있었습니다🥹 이번 구현은 영준님이 로그인, 투표 둘 다 맡아보고 싶다 해서 백•프론트 파트장 투표하기 부분을 맡았어용 폴더 구조도 너무 잘 잡아줘서 고민 없이 바로 개발 할 수 있었어서 고마울 따름.. 개발하면서 실제 뮤사이 프로젝트에서도 쓸 것 같은 기능들 미리 예행 연습을 한 것 같고, 직접 백에서 데이터 받아오고 전송하는 과정에서 에러 하나 없이 잘 진행돼서 앞으로의 프로젝트가 차질 없이 잘 진행 될 것 같다는 기대감이 생겼습니다🤭 프론트 협업, 디자이너 협업, 이어서 백엔드 API 협업까지 경험해서 할 수 있는 것들 많이 해본 것 같습니다! 이제 뮤사이 개발만 남았는데, 꾸준히 해온 스터디 경험 살려서 멋진 결과 내고 싶습니다 ceos 20기 프론트엔드 다들 고생많으셨어요🤍🤍

yyj0917 and others added 30 commits December 22, 2024 00:38
…마 생성, zustand + middleware.ts로 추후 유저관련 로직 세팅
…, Fixes : login api access token 헤더에 안 담겨오는 거 수정중
[12월 26일 개발 내용 전체 푸시]
… 변수 수정 , Fixes : vote page 500 error 수정중
Feat : login, register, demoday 모든 api 연결완료., Refactor : member api관련…
Feat: 파트장 투표 페이지
[Refactor] : code refactoring
Copy link

@Programming-Seungwan Programming-Seungwan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

과제 진행하느라 너무 수고 많으셨습니다!
react-hook-form 라이브러리와 zod를 이용하여 런타임의 유효성 검사를 진행하시는 모습이 참 인상에 깊네요. 남은 시간 더 파이팅입니다 💪🏼

Comment on lines +1 to +16
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
baseDirectory: __dirname,
});

const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];

export default eslintConfig;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 프로젝트 전반적으로 lint가 적용되고 있는지 한번 확인해주세요! npm run lint 명령어를 이용하여 린팅을 하는데, 해당 mjs 확장자의 파일이 적용되지 않고 eslintrc.json이 생성되는 것 같습니다.

Comment on lines +1 to +38
import type { Metadata } from "next";
import "./globals.css";
import { Toaster } from "@/components/ui/toaster";



export const metadata: Metadata = {
title: "MUSAI CEOS VOTE",
description: "MUSAI Team CEOS Election Page",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<head>
{/* Pretendard 웹폰트 CDN 설정 */}
<link
rel="stylesheet"
as="style"
href="https://cdn.jsdelivr.net/gh/orioncactus/[email protected]/dist/web/static/pretendard-dynamic-subset.css"
crossOrigin="anonymous"
/>

</head>
{/* 데스크탑 & 태블릿에서는 375px 고정, 모바일에서는 폰 화면에 따라 조정 */}
<body className="h-[100vh] flex justify-center items-center bg-white">
<main className="min-w-[375px] max-w-[415px] max-h-[812px] lg:max-w-[375px] h-full bg-grey1000 rounded-lg">
{children}
</main>
<Toaster/>
</body>
</html>
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

본 컴포넌트와는 다를 수도 있는 내용이지만, error.tsxnot-found.tsx 파일을 만들어주어 네트워크 에러가 발생했을 경우와 존재하지 않는 페이지에 사용자가 접근할 경우 볼 수 있는 UI 역시 만들어 주시면 좋을 것 같습니다. 지금은 그냥 nextJS 기본 UI로 나타나는 것 같아요

Comment on lines +1 to +31
import { Button } from '@/components/button';
import { Vote } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';

// 로그인 전 홈화면

export default function Home() {
return (
<div className="px-10 w-full h-full flex flex-col justify-around items-center">
<span className='w-full flex flex-col justify-center items-center text-grey550'>
<Image src={'/image/logo-main.png'} alt={'musai-logo'} width={200} height={200} priority/>
<h1 className='w-full text-big text-center'>
<span>CEOS Election</span>
</h1>
</span>
<section className='w-full flex flex-col gap-4'>
<Link href='/login' className='w-full'>
<Button variant={'primary'} className='w-full text-grey350 hover:bg-grey850'>
Join us
</Button>
</Link>
<Link href='/register' className='w-full'>
<Button variant={'link'} className='w-full mt-2 text-grey350 underline underline-offset-4'>
Sign up as a member
</Button>
</Link>
</section>
</div>
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그인을 한 다음에 인위적으로 해당 경로로 접근해도 다시 로그인 창으로 가는 것이 안 막혀 있는 것 같습니다.
accessToken 등의 유무를 검사해서 다른 페이지로 redirect 하는 로직을 넣어주시면 좋겠습니다.

Comment on lines +1 to +107

import { zodResolver } from "@hookform/resolvers/zod"

import { LoginAPI } from "@/lib/api/auth"
import { LoginSchema, loginSchema } from "@/lib/zod/schema"
import { useAuthStore } from "@/lib/zustand/useAuthStore"

import { useRouter } from "next/navigation"
import { toast } from "@/hooks/use-toast"
import { ToastAction } from "@radix-ui/react-toast"


export default function LoginForm() {
const inputType = [
{
type: "text",
name: "loginId",
placeholder: "Enter your ID",
title: "ID",
},
{
type: "password",
name: "password",
placeholder: "Enter your password",
title: "Password",
},
];
const { setAuth } = useAuthStore();
const [error, setError] = useState<string | null>(null);
const router = useRouter();

// React Hook Form - zodResolver
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm<LoginSchema>({
resolver: zodResolver(loginSchema),
mode: "onSubmit",
});

// 폼 제출 시 호출
const onSubmit: SubmitHandler<LoginSchema> = async (data) => {
try {
await LoginAPI(data.loginId, data.password);
setError(null);
// FormValue init
reset();
alert("로그인 성공");
setAuth(localStorage.getItem("accessToken") as string);
router.push("/main");
} catch (err) {
setError("에러가 발생했습니다. 다시 시도해주세요.");
toast({
variant: "destructive",
title: "오류 발생",
description: error,
action: <ToastAction altText="다시 시도">Try again</ToastAction>,
});
}
};

return (
<div className="w-full h-full flex flex-col gap-40">
{/* Header */}
<Header title="Login" />
{/* Form */}
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-20">
<div className="space-y-4">
{inputType.map((input, index) => (
<div key={index} className="flex flex-col gap-2">
<label className="text-body1 text-grey450">{input.title}</label>
<input
{...register(input.name as keyof LoginSchema)}
type={input.type}
placeholder={input.placeholder}
autoComplete="off"
className="px-1 py-2 w-full text-grey450 border-b-2 border-grey550 bg-inherit focus:outline-none focus:ring-0 focus:border-white focus:placeholder-transparent "
/>
{/* 에러 메시지 */}
{errors[input.name as keyof LoginSchema] && (
<p className="text-newRed text-sm">
{errors[input.name as keyof LoginSchema]?.message as string}
</p>
)}
</div>
))}
</div>
<Button
type="submit"
variant={"primary"}
className="w-full p-2 text-white hover:bg-grey750"
>
Sign In
</Button>
</form>
</div>
)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

login 디렉터리 내에 언더바를 사용하여 가깝게 컴포넌트를 정의하고 라우팅에는 영향 못 미치도록 만들어주는 코드 좋습니다 👍

alert("로그인 성공");
setAuth(localStorage.getItem("accessToken") as string);
router.push("/main");
} catch (err) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 라인에서 err 부분에 unused-var 에러가 발생하네요! 이 부분 관련 lint 규칙을 관련 설정 파일에 만들어주면 좋을 것 같아요

Comment on lines +16 to +26
const handleVoteResult = async () => {
try {
// api 요청 로직
const res = await getTeamVoteResult();
setTeamData(res.data);
setWinnerTeam(res.data[0].name);
return;
}
catch (err) {
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 try ~ catch 문을 무책임하게 작성하는 것은 개선해주시면 좋을 것 같아요. 사실 매번 모종의 이유로 api call이 성공하는 것은 아닐겁니다.
따라서 teamData, winnerTeam 상태를 실패를 알리는 메시지를 적거나 아니면 isApiCallSuccess 라는 boolean 타입을 지정해서 catch 문에서 setIsApiCallSuccess(false) 값을 주거나 해서 분기 처리를 하는 것은 어떨까요?
아니면 루트 디렉터리에 error.tsx 컴포넌트를 만들어주어 오류 화면을 띄워주는 것도 괜찮구요

Comment on lines +23 to +35
const getBack = async () => {
try{
const res = await getPartList("BACK");
if (Array.isArray(res.data)){
setBackList(res.data);
console.log(res.data);
} else {
throw new Error("Invalid data format")
}
}
catch(err){
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불필요한 console 문 삭제, 캐치 절에서 상황에 맞는 대응 코드 작성이 되면 좋을 것 같아요

const router = useRouter();

// 백 리스트 가져오기
const getBack = async () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

함수의 네이밍을 바꾸는 것은 어떨까요? 저는 처음에 보고 페이지 라우팅을 뒤로 하나? 왜 컴포넌트 마운트 시에 바로 뒤로 가지? 라는 생각이 들었어요.
나중에 musai 팀이 코드를 살펴볼 때 헷갈릴 수도 있으니 getBackendPartData 정도의 이름은 어떨까요?

set(() => ({
token: null,
isAuthenticated: false,
memberInfo: null,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그인 됐을 때나, 안 됐을 때나 둘다 memberInfo는 null이고 이 값이 딱히 쓰이는 코드가 없는 것 같은데 빼도 되지 않을까요?

Comment on lines +1 to +30
// middleware.ts

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
// 일단 가정. 추후 백엔드로직보고 변경 'userToken'이라는 이름의 쿠키로 로그인 여부 판별
const accessToken = request.headers.get("access");
// 사용자가 "/" 경로로 진입했을 때
if (request.nextUrl.pathname === "/") {
// 로그인되어 있는지(토큰 존재) 확인
if (accessToken) {
// 로그인 상태라면 "/main"으로 이동
const url = request.nextUrl.clone();
url.pathname = "/main";
return NextResponse.redirect(url);
} else {
// 로그인되어 있지 않다면 "/" 페이지 그대로
return NextResponse.next();
}
}

// 위 조건 외 경로는 계속 진행
return NextResponse.next();
}

// 미들웨어가 적용될 경로 설정
export const config = {
matcher: ["/"],
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accessToken 여부를 까보고 있으면 main으로 보내고, 아니면 그냥 진행하는 로직이네요.
제가 다 파악한 것은 아니지만, 해당 로직이 현재 동작하나요? 일단 로그인 된 상태(localStorage에 accessToken과 인증 정보가 있습니다)에서 "/" 로 접근해도 main으로 가지는 않는 것 같아요.
그리고 middleware.ts는 nextJS 서버의 로직으로, request를 까보는 로직을 잘 고려하셔야 할 것 같아요.
이러한 코드 구성은 차라리 토큰을 헤더에 넣고 특정 도메인으로의 요청마다 포함시킬 때 어울려요. 근데 이 경우에도 서브 도메인 등의 구성으로 백엔드와의 협의가 필요한 부분이라고 생각합니다.

Copy link

@hae2ni hae2ni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하나하나 고심하신 게 보여요!

setWinnerTeam(res.data[0].name);
return;
}
catch (err) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error 가 생겼을 때 바로 알 수 있는 메세지를 띄워주는 것도 좋을 것 같아요

useEffect(() => {
handleVoteResult();
}, []);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleVoteResult 자체가 teamData, winnerTeam에 관한 거라서 갠적으로 그 두개의 dependency를 추가하는 게 좋지 않을까용

requestAnimationFrame(frame); // 15초 동안 반복
}
})();
}, []);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아니 왜 디테일한건데,,,

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋㅋㅋㅋ 멋지네요



export default function Vote() {
const voteArray = [
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

상수화는 VOTE_ARRAY 같은 naming을 추천합니다

handleVoteResult();
}, []);

useEffect(() => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것도 혹시 폭죽일까요? 반복되는 것 같은데 컴포넌트를 분리하는 건 어떨까용?

@mimizae
Copy link

mimizae commented Jan 6, 2025

마지막 과제 정말 정말 수고하셨습니다 🖤❤️

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉 혹시 이 이미지 파일도 사용하시나용?! 아니라면 사용하지 않는 이미지 파일은 제거해 주시면 좋을 것 같습니다!!

@@ -0,0 +1,38 @@
import type { Metadata } from "next";
import "./globals.css";
import { Toaster } from "@/components/ui/toaster";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 토스터!! 저는 모달 중독자라서 모달만 만들어봤는데 토스터로 알림 처리를 하셨군요 이번 제 프로젝트에서는 모달이 아니라 토스터로 알림 표시를 해서 좀 배워가야겠습니다👍🏻

return (
<html lang="en">
<head>
{/* Pretendard 웹폰트 CDN 설정 */}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉 보안까지 고려하셨네요 전 그저 pretendard 폰트를 쓸 때 link로 가져오기만 했었는데 배워갑니다 👍🏻

import { zodResolver } from "@hookform/resolvers/zod"

import { LoginAPI } from "@/lib/api/auth"
import { LoginSchema, loginSchema } from "@/lib/zod/schema"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우오 저도 로그인, 회원가입 페이지를 맡아 구현했는데 저는 lib/ 폴더에 유효성 검사 함수를 따로 제작해 검사에 대한 로직을 직접 구현했는데 zod를 사용하면 선언적으로 사용해 훨씬 편리하네용... 이번 프로젝트에서 마이페이지 부분을 맡았는데 이때 쓰이는 게 맞는지 모르겠지만 바이오 글자 수 등 제한할 때 사용해 보아야겠습니다 👍🏻

@@ -0,0 +1,41 @@
import {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기부터 아래 3개의 컴포넌트가 버튼 텍스트, 다이얼로그 내용, 그리고 핸들러 함수 정도의 차이 제외 거의 구조적으로 동일한 것 같은데

이를 고려해, 세 컴포넌트를 하나의 재사용 가능한 컴포넌트로 통합해 보는 것은 어떨까용!!!???

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 동의합니다..! 코드를 더 간결하게 줄일 수 있을 것 같아요~ 지금은 비슷한 import문이 너무 많은 듯한 느낌이 듭니다.

@@ -0,0 +1,47 @@
"use client";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석을 친절히 달아주셔서 이해가 쉬워요 👍🏻👍🏻

const router = useRouter();

// 팀 리스트 가져오기
const getTeam = async () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정말 디테일한 부분이긴 한데 getTeamList 등으로 함수명을 변경하시는 것은 어떨까용? 아래의 handleVote 또한 handleVoteTeam 등등...으로용...

사실 제가 이번 과제를 하며 느낀 것인데 저와 제 팀원의 함수 명명 방식이나 변수 명명 방식이 약간씩은 달라서 프로젝트 전체적으로 보면 일관성이 좀 없어보이더라고요... 그래서 이번 프로젝트에서 사소하게라도 함수 명명 규칙을 정의하고자 마음 먹었습니답 이미 무사이 팀은 규칙을 정의하셨을 것 같긴 하고 이미 함수명도 적절한 듯 싶은데 혹시나 하는 마음에... 추천 드립니다 👍🏻

}, [isLoading]);

return (
<div className="w-full h-full flex flex-col justify-center items-center gap-10">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 보니 테일윈드의 장점을 확실히 알 수 있겠네요 따로 저~~ 아래에 스타일 코드를 작성하지 않고 한 눈에 파악할 수 있는 게 좋은 것 같습니다... 가독성은 조금 포기하더라도?! 스타일드 컴포넌트를 사용할 때 한 파일에 쓰는 것은 좋은데 너무 코드의 길이가 길어져 고민이었는데 해결방안이 여기 있었네용

Copy link

@ryu-won ryu-won left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

과제하시느라 고생많으셨습니다! 특히 회원가입 로직에서 react-hook-form, zod 라이브러리 사용하셔서 저희도 사용해봐야겠습니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데이터검증 zod라이브러리가 있군요!

Comment on lines +34 to +36
catch (err) {
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 에러처리 코드를 비워두신 이유가 있으실까용?


import axios from "axios";

const baseURL = "http://43.201.23.26:80";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 baseurl을 하드코딩 되어있는데 const baseURL = process.env.NEXT_PUBLIC_API_URL; 환경 변수로 따로 빼두어서 재사용성 좋게 하는건 어떻까요?

Copy link

@jiwonnchoi jiwonnchoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요! 페달지니 팀 코드리뷰를 맡은 커피딜 최지원입니다:)
마지막 주차 과제까지 정말 수고하셨습니다. 배포 링크 보면서 다양한 효과들과 디테일에 재밌게 보았습니다ㅎㅎ 앞선 리뷰들 보면서 저도 많이 배워갑니다!
고생 많으셨습니다~~! 🎉

Comment on lines +23 to +33
<AlertDialogDescription>
MUSAI : 15표
<br />
CakeWay : 10표
<br />
CoffeeDeal : 5표
<br />
PhotoGround : 3표
<br />
AngelBridge : 2표
</AlertDialogDescription>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트해보았을 때 '파트장 투표 결과 확인'에서 해당 데모데이 투표 결과가 뜨는데 파트장 투표 결과를 띄워주셔야 할 것 같아요! 표 수도 api 호출 받아온 결과로 반영해주셔야할 것 같습니당

Comment on lines +71 to +90
// 409 재투표 에러처리 - priority
if (status === 409) {
toast({
variant: "destructive",
title: "중복된 정보",
description: err.response?.data.message,
action: <ToastAction altText="다시 시도">Try again</ToastAction>,
});
return;
}
// 400 자기 팀 투표 에러처리
if (status === 400) {
toast({
variant: "destructive",
title: "팀 선택 오류",
description: err.response?.data.message,
action: <ToastAction altText="다시 시도">Try again</ToastAction>,
});
return;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

409 로 받는 에러는 처음 봐서 특이하네요..!
409 400 에러의 toast 로직이 중복되니 toast 처리 로직을 함수로 추출해서 중복을 줄여도 좋을 것 같습니다!

const showToast = (title: string, description: string) => {
    toast({
        variant: "destructive",
        title,
        description,
        action: <ToastAction altText="다시 시도">Try again</ToastAction>,
    });
};

if (status === 409) {
    showToast("중복된 정보", err.response?.data.message);
    return;
}

if (status === 400) {
    showToast("팀 선택 오류", err.response?.data.message);
    return;
}

Comment on lines +43 to +49
const handleVote = (teamName: string) => {
if (teamName === selectedTeam) {
setSelectedTeam("");
} else {
setSelectedTeam(teamName);
}
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정말 사소하긴 하지만 이렇게 간략히 쓰는거슨.. 어떨까요

Suggested change
const handleVote = (teamName: string) => {
if (teamName === selectedTeam) {
setSelectedTeam("");
} else {
setSelectedTeam(teamName);
}
};
const handleVote = (teamName: string) => {
setSelectedTeam(prev => (prev === teamName ? "" : teamName));
};

const end = Date.now() + 1.5 * 1000; // 15초 동안 실행
const colors = ["#bb0000", "#ffffff"]; // 축제 색상

(function frame() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉🎉

Copy link

@s-uxun s-uxun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요! 마지막 주차 코드리뷰를 맡은 송유선입니다.
한 학기동안의 스터디, 그리고 마지막 과제까지 고생 많으셨습니다 ✨
페달지니 영준 오빠, 지수의 코드를 보면서 저도 한 학기 동안 많이 배울 수 있었어요. 특히 이번 과제에서 독창적인 UI, 세심한 에러 처리, zod를 활용한 유효성 검사 등 많이 고민하신 흔적을 느낄 수 있었습니다. 앞으로 데오데이까지 남은 기간동안 무사이 팀 프로젝트도 파이팅하시길 바랄게요! 🔥

Comment on lines +13 to +19
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

절대 경로 쓰신 거 좋아요~~ 이제 마지막 과제라서 그런가 다들 절대경로를 잘 활용하고 계시더라구요!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

무사이 팀의 아이덴티티가 돋보이는 UI가 멋있었습니다!

@@ -0,0 +1,41 @@
import {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 동의합니다..! 코드를 더 간결하게 줄일 수 있을 것 같아요~ 지금은 비슷한 import문이 너무 많은 듯한 느낌이 듭니다.

</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>데모데이 투표 결과</AlertDialogTitle>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분을 왜 하드코딩하셨는지 알 수 있을까요? 만약 백 연결 전, 초반 퍼블리싱할 때 넣어두셨던 거라면 없애고 실제 결과를 받아오도록 리팩토링하셔야 할 듯 합니다! 현재 파트장 투표 결과를 클릭하면 해당 부분이 나오고 있기 때문에 사용자 입장에서 의아하게 느껴질 것 같아요..

<AlertDialogFooter>
<AlertDialogCancel>아니요</AlertDialogCancel>
<AlertDialogAction onClick={handleSubmit}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋㅋㅋㅋ 귀여워요

Comment on lines +37 to +40
{/* Bar */}
<div className="w-full border-2 border-grey450 mb-10"></div>
{/* 진행 바 */}
<Progress value={progress} className="w-[90%] bg-grey450" />
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유저가 투표하는 과정에서 그 단계에 따라 진행바를 구현하신 점이 인상 깊었어요! 다만 코드를 보면 배경 바가 있고, 그 위에 진행 바가 겹쳐져서 보이는 구조같은데, 일부 환경에서는 두 요소가 완전히 겹쳐지지 않아 배경과 진행바가 살짝 따로 노는 듯한 느낌을 받았습니다...! 차라리, div안에 또다른 div를 넣는 형태로 해서 진짜 진행바가 "채워지는" 형태로 구현해보면 어떨까요?

Suggested change
{/* Bar */}
<div className="w-full border-2 border-grey450 mb-10"></div>
{/* 진행 바 */}
<Progress value={progress} className="w-[90%] bg-grey450" />
<div className="w-[90%] h-4 bg-grey450 rounded-full overflow-hidden">
<div
className="h-full bg-blue-500 transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>

requestAnimationFrame(frame); // 15초 동안 반복
}
})();
}, []);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋㅋㅋㅋ 멋지네요

Comment on lines +68 to +77
const {
control,
register,
handleSubmit,
reset,
formState: { errors },
} = useForm<RegisterSchema>({
resolver: zodResolver(registerSchema),
mode: "onSubmit",
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 새롭게 배우고 갑니다..!!

const status = err.response?.status;

// 409 중복된 이메일 & 아이디 에러 처리
if (status === 409) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 포함해서 코드 전반적으로 에러 처리를 세심하게 하신 게 느껴집니다 👍🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants