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: 온보딩 페이지 데이터 영속성 구현 #5

Merged
merged 6 commits into from
Feb 2, 2025
13 changes: 7 additions & 6 deletions src/components/AdmissionYearInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { useDropdown } from '../hooks/useDropDown';
const admissionYears = [25, 24, 23, 22, 21, 20, 19, 18];

interface AdmissionYearInputProps {
initialValue: number | undefined;
onNext: (admissionYear: number) => void;
}

const AdmissionYearInput = ({ onNext }: AdmissionYearInputProps) => {
const [admissionYear, setAdmissionYear] = useState<number>();
const [showLabel, setShowLabel] = useState(false);
const AdmissionYearInput = ({ onNext, initialValue }: AdmissionYearInputProps) => {
const [admissionYear, setAdmissionYear] = useState(initialValue);
const [showLabel, setShowLabel] = useState(admissionYear !== undefined);
const [showDropdown, setShowDropdown, dropDownRef] = useDropdown();

const handleAdmissionYearSelect = (year: number) => {
Expand Down Expand Up @@ -39,7 +40,7 @@ const AdmissionYearInput = ({ onNext }: AdmissionYearInputProps) => {
{showLabel && <label className="mb-1.5 block text-sm">입학년도(학번)</label>}
<div className="relative" ref={dropDownRef}>
<div
className="bg-basic-light focus-visible:ring-ring flex w-full items-center justify-between rounded-lg px-4 py-3 focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none"
className="bg-basic-light focus-visible:ring-ring flex w-full items-center justify-between rounded-xl px-4 py-3 focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none"
onClick={() => setShowDropdown(!showDropdown)}
>
<button
Expand All @@ -53,7 +54,7 @@ const AdmissionYearInput = ({ onNext }: AdmissionYearInputProps) => {
<AnimatePresence>
{showDropdown && (
<motion.ul
className="bg-basic-light absolute z-10 mt-2 max-h-55 w-full overflow-y-auto rounded-lg border border-gray-200 shadow-sm"
className="bg-basic-light absolute z-10 mt-2 max-h-55 w-full overflow-y-auto rounded-xl border border-gray-200 shadow-sm"
initial={{ opacity: 0, y: -10 }}
animate={{
opacity: 1,
Expand All @@ -71,7 +72,7 @@ const AdmissionYearInput = ({ onNext }: AdmissionYearInputProps) => {
<li key={year}>
<button
type="button"
className="text-list flex w-full items-center justify-between rounded-lg px-4 py-2 text-lg font-semibold hover:bg-gray-100"
className="text-list flex w-full items-center justify-between rounded-xl px-4 py-2 text-lg font-semibold hover:bg-gray-100"
onClick={() => {
handleAdmissionYearSelect(year);
}}
Expand Down
9 changes: 5 additions & 4 deletions src/components/ChapelInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { motion } from 'motion/react';
import { useState } from 'react';

interface ChapelInputProps {
initialValue: boolean | undefined;
onNext: (chapel: boolean) => void;
}

const ChapelInput = ({ onNext }: ChapelInputProps) => {
const [chapel, setChapel] = useState(true);
const ChapelInput = ({ onNext, initialValue }: ChapelInputProps) => {
const [chapel, setChapel] = useState(initialValue);

const handleClickChapel = () => {
setChapel(!chapel);
Expand All @@ -28,13 +29,13 @@ const ChapelInput = ({ onNext }: ChapelInputProps) => {
>
<label className="mb-1.5 block text-sm">채플 수강 여부</label>
<div
className="bg-basic-light focus-visible:ring-ring flex w-full items-center justify-between rounded-lg px-4 py-3 focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none"
className="bg-basic-light focus-visible:ring-ring flex w-full items-center justify-between rounded-xl px-4 py-3 focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none"
onClick={handleClickChapel}
>
<button type="button" className="text-primary text-lg font-semibold">
{chapel ? '채플 수강' : '채플 미수강'}
</button>
<CircleCheck className={`size-6 ${chapel ? 'text-primary' : 'text-placeholder'}`} />
<CircleCheck className={`size-6 ${chapel ? 'text-primary' : 'text-disabled'}`} />
</div>
</motion.div>
);
Expand Down
94 changes: 73 additions & 21 deletions src/components/DepartmentInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,72 @@ import { AnimatePresence, motion } from 'motion/react';
import { useState } from 'react';

const allDepartments = [
'컴퓨터학부',
'전자공학과',
'기계공학과',
'화학공학과',
'생명공학과',
'수학과',
'AI융합학부',
'건축학부 건축공학전공',
'건축학부 건축학부',
'건축학부 건축학전공',
'건축학부 실내건축전공',
'경영학부',
'국어국문학과',
'국제무역학과',
'국제법무학과',
'금융경제학과',
'금융학부',
'기계공학부',
'기독교학과',
'글로벌미디어학부',
'글로벌통상학과',
'독어독문학과',
'물리학과',
'화학과',
'경영학과',
'경제학과',
'심리학과',
'사회학과',
'미디어경영학과',
'법학과',
'벤처경영학과',
'벤처중소기업학과',
'복지경영학과',
'불어불문학과',
'사학과',
'사회복지학부',
'산업정보시스템공학과',
'소프트웨어학부',
'수학과',
'스포츠학부',
'신소재공학과',
'영어영문학과',
'국어국문학과',
'예술창작학부 문예창작전공',
'예술창작학부 영화예술전공',
'언론홍보학과',
'의생명시스템학부',
'자유전공학부',
'전기공학부',
'전자정보공학부 IT융합전공',
'전자정보공학부 전자공학전공',
'정보보호학과',
'정보사회학과',
'정보통계보험수리학과',
'정치외교학과',
'철학과',
];
'차세대반도체학과',
'컴퓨터학부',
'통상산업학과',
'평생교육학과',
'한문학과',
'화학과',
'화학공학과',
'혁신경영학과',
'행정학부',
'회계세무학과',
'회계학과',
] as const;

interface DepartmentInputProps {
initialValue: string | undefined;
onNext: (department: string) => void;
}

const DepartmentInput = ({ onNext }: DepartmentInputProps) => {
const [department, setDepartment] = useState('');
const DepartmentInput = ({ onNext, initialValue }: DepartmentInputProps) => {
const [department, setDepartment] = useState(initialValue);
const [matchingDepartments, setMatchingDepartments] = useState<string[]>([]);
const [showLabel, setShowLabel] = useState(false);
const [showLabel, setShowLabel] = useState(initialValue !== undefined);

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value.trim();
Expand All @@ -48,13 +89,24 @@ const DepartmentInput = ({ onNext }: DepartmentInputProps) => {
};

return (
<div className="relative">
<motion.div
className="relative"
initial={{
opacity: 0,
y: -20,
}}
animate={{ opacity: 1, y: 0 }}
transition={{
duration: 0.3,
ease: 'easeOut',
}}
>
{showLabel && <label className="mb-1.5 block text-sm">학과</label>}
<input
type="text"
value={department}
onChange={handleInputChange}
className="bg-basic-light text-primary focus-visible:ring-ring w-full rounded-lg px-4 py-3 text-lg font-semibold focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none"
className="bg-basic-light text-primary focus-visible:ring-ring w-full rounded-xl px-4 py-3 text-lg font-semibold focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none"
placeholder="학과"
/>
<AnimatePresence>
Expand All @@ -70,13 +122,13 @@ const DepartmentInput = ({ onNext }: DepartmentInputProps) => {
}}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
className="bg-basic-light absolute z-10 mt-2 w-full rounded-lg border border-gray-200 shadow-sm"
className="bg-basic-light absolute z-10 mt-2 max-h-44 w-full overflow-y-auto rounded-xl border border-gray-200 shadow-sm"
>
{matchingDepartments.map((dept, index) => (
<li key={index}>
<button
type="button"
className="text-list flex w-full items-center justify-between rounded-lg px-4 py-2 text-lg font-semibold hover:bg-gray-100"
className="text-list flex w-full items-center justify-between rounded-xl px-4 py-2 text-lg font-semibold hover:bg-gray-100"
onClick={() => handleDepartmentSelect(dept)}
>
{dept}
Expand All @@ -87,7 +139,7 @@ const DepartmentInput = ({ onNext }: DepartmentInputProps) => {
</motion.ul>
)}
</AnimatePresence>
</div>
</motion.div>
);
};

Expand Down
13 changes: 7 additions & 6 deletions src/components/GradeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import { Grade } from '../machines/studentMachine';
const grades = [1, 2, 3, 4, 5] as const;

interface GradeInputProps {
initialValue: Grade | undefined;
onNext: (grade: Grade) => void;
}

const GradeInput = ({ onNext }: GradeInputProps) => {
const [grade, setGrade] = useState<Grade>();
const [showLabel, setShowLabel] = useState(false);
const GradeInput = ({ onNext, initialValue }: GradeInputProps) => {
const [grade, setGrade] = useState(initialValue);
const [showLabel, setShowLabel] = useState(initialValue !== undefined);
const [showDropdown, setShowDropdown, dropDownRef] = useDropdown();

const handleGradeSelect = (grade: Grade) => {
Expand Down Expand Up @@ -40,7 +41,7 @@ const GradeInput = ({ onNext }: GradeInputProps) => {
{showLabel && <label className="mb-1.5 block text-sm">학년</label>}
<div className="relative" ref={dropDownRef}>
<div
className="bg-basic-light focus-visible:ring-ring flex w-full items-center justify-between rounded-lg px-4 py-3 focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none"
className="bg-basic-light focus-visible:ring-ring flex w-full items-center justify-between rounded-xl px-4 py-3 focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none"
onClick={() => setShowDropdown(!showDropdown)}
>
<button
Expand All @@ -54,7 +55,7 @@ const GradeInput = ({ onNext }: GradeInputProps) => {
<AnimatePresence>
{showDropdown && (
<motion.ul
className="bg-basic-light absolute z-10 mt-2 w-full rounded-lg border border-gray-200 shadow-sm"
className="bg-basic-light absolute z-10 mt-2 w-full rounded-xl border border-gray-200 shadow-sm"
initial={{ opacity: 0, y: -10 }}
animate={{
opacity: 1,
Expand All @@ -72,7 +73,7 @@ const GradeInput = ({ onNext }: GradeInputProps) => {
<li key={gradeOption}>
<button
type="button"
className="text-list flex w-full items-center justify-between rounded-lg px-4 py-2 text-lg font-semibold hover:bg-gray-100"
className="text-list flex w-full items-center justify-between rounded-xl px-4 py-2 text-lg font-semibold hover:bg-gray-100"
onClick={() => {
handleGradeSelect(gradeOption);
}}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const ProgressBar = ({ width }: ProgressBarProps) => {
return (
<div className="h-1 w-full bg-black">
<div
className="bg-progress-bar h-full transition-all duration-500"
className="bg-primary h-full transition-all duration-500"
style={{ width: `${width}%` }}
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

@theme {
--font-pretendard: 'Pretendard Variable', sans-serif;
--color-primary: #674fd0;
--color-progress-bar: #6b5cff;
--color-primary: #6b5cff;
--color-basic-light: #f7f8f8;
--color-list: #686868;
--color-ring: hsl(240 5% 64.9%);
--color-placeholder: #cfcfcf;
--color-hint: #b5b9c4;
--color-disabled: #e3e4e8;
}

@layer base {
Expand Down
28 changes: 25 additions & 3 deletions src/machines/studentMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { assign, setup } from 'xstate';

export type Grade = 1 | 2 | 3 | 4 | 5;

interface Context {
type Context = {
department: string; // 학과
admissionYear: number; // 입학년도
grade: Grade; // 학년
chapel: boolean; // 채플 수강 여부
}
};

type Event =
| { type: '학과입력완료'; payload: { department: string } }
Expand All @@ -27,7 +27,29 @@ const studentMachine = setup({
department: '',
admissionYear: 0,
grade: 1,
chapel: false,
chapel: true,
},
on: {
학과입력완료: {
actions: assign({
department: ({ event }) => event.payload.department,
}),
},
입학년도입력완료: {
actions: assign({
admissionYear: ({ event }) => event.payload.admissionYear,
}),
},
학년입력완료: {
actions: assign({
grade: ({ event }) => event.payload.grade,
}),
},
채플수강여부입력완료: {
actions: assign({
chapel: ({ event }) => event.payload.chapel,
}),
},
},
states: {
학과입력: {
Expand Down
Loading