diff --git a/package-lock.json b/package-lock.json
index b500ee7..9e47204 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3725,15 +3725,15 @@
}
},
"node_modules/vite": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz",
- "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==",
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz",
+ "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==",
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
"picomatch": "^4.0.5",
- "postcss": "^8.5.23",
- "rolldown": "~1.2.0",
+ "postcss": "^8.5.25",
+ "rolldown": "~1.2.1",
"tinyglobby": "^0.2.17"
},
"bin": {
diff --git a/src/assets/Archive.png b/src/assets/Archive.png
new file mode 100644
index 0000000..9a97c55
Binary files /dev/null and b/src/assets/Archive.png differ
diff --git a/src/assets/Arrow.png b/src/assets/Arrow.png
new file mode 100644
index 0000000..96d26e9
Binary files /dev/null and b/src/assets/Arrow.png differ
diff --git a/src/assets/Exclamation.png b/src/assets/Exclamation.png
new file mode 100644
index 0000000..7b2f036
Binary files /dev/null and b/src/assets/Exclamation.png differ
diff --git a/src/assets/LayerLogo.png b/src/assets/LayerLogo.png
new file mode 100644
index 0000000..3b077c9
Binary files /dev/null and b/src/assets/LayerLogo.png differ
diff --git a/src/components/auth/AlertBox.tsx b/src/components/auth/AlertBox.tsx
new file mode 100644
index 0000000..86d1b44
--- /dev/null
+++ b/src/components/auth/AlertBox.tsx
@@ -0,0 +1,16 @@
+export default function AlertBox(props: {isVisible : boolean}){
+ if(props.isVisible){
+ return (
+
+
+
+
+
모든 항목을 입력해주세요.
+
+
+
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/components/common/input/TextArea.tsx b/src/components/common/input/TextArea.tsx
index 21262f5..532cfa4 100644
--- a/src/components/common/input/TextArea.tsx
+++ b/src/components/common/input/TextArea.tsx
@@ -33,7 +33,7 @@ function TextArea({
/>
- {value.length} / {maxLength}
+ {value?.length} / {maxLength}
)
diff --git a/src/components/onboarding/OnboardingComponentChange.tsx b/src/components/onboarding/OnboardingComponentChange.tsx
new file mode 100644
index 0000000..673ef11
--- /dev/null
+++ b/src/components/onboarding/OnboardingComponentChange.tsx
@@ -0,0 +1,24 @@
+import { onboardingComponentProps } from '@/pages/Onboarding'
+import OnboardingPage1Component from '@/components/onboarding/OnboardingPage1Component'
+import OnboardingPage2Component from '@/components/onboarding/OnboardingPage2Component'
+import OnboardingPage3Component from '@/components/onboarding/OnboardingPage3Component'
+
+export default function OnboardingComponentChange ({pageNumber, topic, setTopic, decision, setDecision, reason, setReason, selectedValue, setSelectedValue, selectedCategory, setSelectedCategory} : onboardingComponentProps) {
+ switch(pageNumber){
+ case 1: return
+ case 2: return
+ case 3: return
+ }
+}
\ No newline at end of file
diff --git a/src/components/onboarding/OnboardingHeader.tsx b/src/components/onboarding/OnboardingHeader.tsx
new file mode 100644
index 0000000..830acaf
--- /dev/null
+++ b/src/components/onboarding/OnboardingHeader.tsx
@@ -0,0 +1,14 @@
+export default function OnboardingHeader(props: { pageNumber:number; }) {
+ return (
+
+ {[1, 2, 3].map((step) => (
+
+ ))}
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/onboarding/OnboardingPage1Component.tsx b/src/components/onboarding/OnboardingPage1Component.tsx
new file mode 100644
index 0000000..a60b78c
--- /dev/null
+++ b/src/components/onboarding/OnboardingPage1Component.tsx
@@ -0,0 +1,30 @@
+export default function OnboardingPage1Component() {
+
+ return (
+
+ {/* nine blocks */}
+
+
+ {/* content */}
+ 가치관을 묻지 않아요
+ 대신 지금 고민 중인 선택 하나만 물어볼게요.
'당신의 가치'는 무엇인가는 묻지 않지만,
구체적인 갈림길 앞에서는 누구나 이유를 둘 수 있으니까요.
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/onboarding/OnboardingPage2Component.tsx b/src/components/onboarding/OnboardingPage2Component.tsx
new file mode 100644
index 0000000..fbf75f6
--- /dev/null
+++ b/src/components/onboarding/OnboardingPage2Component.tsx
@@ -0,0 +1,50 @@
+export default function OnboardingPage2Component() {
+ return (
+
+
고르고, 이유를 남기면
+
+
+
+
+
고민
+
지금 하는 갈림길 하나를 이름 붙인다
+
+
+
+
+
+
오늘의 판단
+
오늘, 어느 쪽으로 마음이 갔는지
+
+
+
+
+
+
+
가치
+
가장 가까운 것 하나만 고른다
+
+
+
+
쌓일수록, 당신의 선택이 보여요.
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/onboarding/OnboardingPage3Component.tsx b/src/components/onboarding/OnboardingPage3Component.tsx
new file mode 100644
index 0000000..d3a6097
--- /dev/null
+++ b/src/components/onboarding/OnboardingPage3Component.tsx
@@ -0,0 +1,101 @@
+import { useState, type ChangeEvent } from 'react'
+
+import { onboardingComponentProps } from '@/pages/Onboarding'
+import RecordValueButton from '@/components/common/button/RecordValueButton'
+import TextArea from '@/components/common/input/TextArea'
+import CategoryButton from '@/components/common/button/CategoryButton'
+
+import { VALUE_LABELS, type ValueKey } from '@/constants/insights'
+import { VALUE_DESCRIPTIONS } from '@/constants/values'
+
+
+
+const VALUE_KEYS = Object.keys(VALUE_LABELS) as ValueKey[]
+
+const CATEGORY_OPTIONS = [
+ { value: 'work', title: '일·진로', description: '취업, 이직, 업무 판단' },
+ {
+ value: 'relationship',
+ title: '관계',
+ description: '친구, 연인, 가족, 동료',
+ },
+ { value: 'money', title: '돈', description: '소비, 저축, 투자' },
+ { value: 'health', title: '건강·몸', description: '운동, 수면, 컨디션' },
+ { value: 'self', title: '나 자신', description: '성격, 원칙, 새로운 시도' },
+ { value: 'etc', title: '기타', description: '위 어디에도 해당 없음' },
+] as const
+
+type CategoryValue = (typeof CATEGORY_OPTIONS)[number]['value']
+
+export default function OnboardingPage3Component({pageNumber, topic, setTopic, decision, setDecision, reason, setReason, selectedValue, setSelectedValue, selectedCategory, setSelectedCategory} : onboardingComponentProps) {
+
+
+ return (
+ <>
+
+
지금 고민,
하나씩 적어볼까요?
+
아직 연습이예요. 편하게 가볍게 한 번 써볼까요?
+
고민은 취업, 관계, 돈, 건강처럼 영역으로 나눠서 적어볼 수 있어요. 아래에서 골라보세요.
+
+
+
+ 이 고민은 어떤 영역인가요?
+
+ {CATEGORY_OPTIONS.map((option) => (
+ setSelectedCategory(option.value)}
+ />
+ ))}
+
+
+
+ 이유는 '성장', '안정', '재미'처럼 9가지 가치 중 가장 가까운 하나로 정리돼요.
+
+
+
+ 방금 그 이유, 가장 가까운 하나는?
+
+ {VALUE_KEYS.map((key) => (
+ setSelectedValue(key)}
+ />
+ ))}
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/components/settings/DeleteAccount.tsx b/src/components/settings/DeleteAccount.tsx
new file mode 100644
index 0000000..d1cec7a
--- /dev/null
+++ b/src/components/settings/DeleteAccount.tsx
@@ -0,0 +1,51 @@
+import { Dispatch, SetStateAction } from 'react';
+
+export default function DeleteAccount(props: {
+ isVisible : boolean;
+ deleteAccountShow: boolean,
+ setDeleteAccountShow:Dispatch>,
+ deleteAccountAlertShow : boolean,
+ setDeleteAccountAlertShow : Dispatch>,
+ }){
+
+ const DeleteAccountShow = () =>{
+ if(props.deleteAccountShow == false){
+ props.setDeleteAccountShow(true);
+ }
+ else{
+ props.setDeleteAccountShow(false);
+ }
+ }
+
+ const DeleteAccountAlertShow = () =>{
+ if(props.deleteAccountAlertShow == false){
+ props.setDeleteAccountShow(false);
+ props.setDeleteAccountAlertShow(true);
+ }
+ else{
+ props.setDeleteAccountShow(true);
+ props.setDeleteAccountAlertShow(false);
+ }
+ }
+
+ if(props.isVisible){
+ return (
+
+
+
+
+
정말 탈퇴하시겠어요?
+
회원 탈퇴 시 아래 데이터가 모두 삭제됩니다.
+
• 고민 및 기록
• 가치 분포 및 인사이트
• 계정 정보
+
삭제된 데이터는 복구할 수 없습니다.
+
+
+
+
+
+
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/components/settings/DeleteAccountAlert.tsx b/src/components/settings/DeleteAccountAlert.tsx
new file mode 100644
index 0000000..d353553
--- /dev/null
+++ b/src/components/settings/DeleteAccountAlert.tsx
@@ -0,0 +1,21 @@
+import { Dispatch, SetStateAction } from 'react';
+
+export default function DeleteAccountAlert(props: {
+ isVisible : boolean,
+ }){
+ if(props.isVisible){
+ return (
+
+
+
+
+
회원 탈퇴가 완료되었습니다.
+
그동안 Layer를 이용해주셔서 감사합니다.
+
+
+
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
new file mode 100644
index 0000000..d1881be
--- /dev/null
+++ b/src/pages/Login.tsx
@@ -0,0 +1,55 @@
+import { useState, type ChangeEvent } from 'react'
+
+import LayerLogo from '@/assets/LayerLogo.png'
+import Exclamation from '@/assets/Exclamation.png'
+import Input from '@/components/common/input/Input'
+import Button from '@/components/common/button/Button'
+
+import {
+ validateNickname,
+ validatePassword,
+ validatePasswordConfirm,
+ validateUserId,
+} from '@/utils/validation'
+
+export default function Login () {
+
+ const [id] = useState('')
+ const [password, setPassword] = useState('')
+
+ return (
+
+
+
+ 당신이 실제로 골라온 것을 봅니다.
+
+
+
+
+
+
+
+
+ {/* alert tab */}
+
+

+
모든 항목을 입력해주세요.
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/Onboarding.tsx b/src/pages/Onboarding.tsx
new file mode 100644
index 0000000..f74ccb6
--- /dev/null
+++ b/src/pages/Onboarding.tsx
@@ -0,0 +1,84 @@
+import React, { Dispatch, SetStateAction, useState } from 'react'
+import OnboardingComponentChange from '@/components/onboarding/OnboardingComponentChange'
+import OnboardingHeader from '@/components/onboarding/OnboardingHeader'
+
+
+const VALUE_KEYS = Object.keys(VALUE_LABELS) as ValueKey[]
+import { VALUE_LABELS, type ValueKey } from '@/constants/insights'
+
+const CATEGORY_OPTIONS = [
+ { value: 'work', title: '일·진로', description: '취업, 이직, 업무 판단' },
+ {
+ value: 'relationship',
+ title: '관계',
+ description: '친구, 연인, 가족, 동료',
+ },
+ { value: 'money', title: '돈', description: '소비, 저축, 투자' },
+ { value: 'health', title: '건강·몸', description: '운동, 수면, 컨디션' },
+ { value: 'self', title: '나 자신', description: '성격, 원칙, 새로운 시도' },
+ { value: 'etc', title: '기타', description: '위 어디에도 해당 없음' },
+] as const
+
+type CategoryValue = (typeof CATEGORY_OPTIONS)[number]['value']
+
+export interface onboardingComponentProps {
+ pageNumber : number,
+ topic : string,
+ setTopic : React.Dispatch>,
+ decision : string
+ setDecision : React.Dispatch>,
+ reason : string,
+ setReason : React.Dispatch>,
+ selectedValue : string | null,
+ setSelectedValue : React.Dispatch>,
+ selectedCategory : string | null,
+ setSelectedCategory : Dispatch>,
+}
+
+export default function Onboarding () {
+
+ const [topic, setTopic] = useState('');
+ const [decision, setDecision] = useState('');
+ const [reason, setReason] = useState('');
+ const [selectedValue, setSelectedValue] = useState(null)
+ const [selectedCategory, setSelectedCategory] =
+ useState(null)
+
+ {/*onboarding page number*/}
+ const [pageNumber, setPageNumber] = useState(1);
+
+ {/*page move function*/}
+ const pageMove = () => {
+ if(pageNumber <= 2)
+ setPageNumber(pageNumber => (pageNumber + 1));
+ }
+
+ return (
+
+ {/* onboarding header*/}
+
+
+ {/* onboarding contents, change by pageNumber*/}
+
+
+ {/*bottom buttons*/}
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/RecordGuide.tsx b/src/pages/RecordGuide.tsx
new file mode 100644
index 0000000..84480f2
--- /dev/null
+++ b/src/pages/RecordGuide.tsx
@@ -0,0 +1,92 @@
+import { useState, type ChangeEvent } from 'react'
+import Header from '@/components/common/header/Header'
+import BottomNav, {
+ type NavValue,
+} from '@/components/common/navigation/BottomNav'
+import Tab from '@/components/common/tab/Tab'
+import CategoryButton from '@/components/common/button/CategoryButton'
+import RecordValueButton from '@/components/common/button/RecordValueButton'
+
+import { VALUE_LABELS, type ValueKey } from '@/constants/insights'
+import { VALUE_DESCRIPTIONS } from '@/constants/values'
+
+
+const CATEGORY_OPTIONS = [
+ { value: 'work', title: '일·진로', description: '취업, 이직, 업무 판단' },
+ {
+ value: 'relationship',
+ title: '관계',
+ description: '친구, 연인, 가족, 동료',
+ },
+ { value: 'money', title: '돈', description: '소비, 저축, 투자' },
+ { value: 'health', title: '건강·몸', description: '운동, 수면, 컨디션' },
+ { value: 'self', title: '나 자신', description: '성격, 원칙, 새로운 시도' },
+ { value: 'etc', title: '기타', description: '위 어디에도 해당 없음' },
+] as const
+
+const TAB_ITEMS = [
+ { value: 'topic', label: '주제' },
+ { value: 'value', label: '가치' },
+] as const
+
+type CategoryValue = (typeof CATEGORY_OPTIONS)[number]['value']
+
+type TabValue = (typeof TAB_ITEMS)[number]['value']
+
+const VALUE_KEYS = Object.keys(VALUE_LABELS) as ValueKey[]
+
+export default function RecordGuide () {
+
+ const [activeTab, setActiveTab] = useState('topic')
+ const [selectedValue, setSelectedValue] = useState(null)
+
+ const [selectedCategory, setSelectedCategory] =
+ useState(null)
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx
new file mode 100644
index 0000000..83f5e17
--- /dev/null
+++ b/src/pages/Register.tsx
@@ -0,0 +1,107 @@
+import { useState, type ChangeEvent } from 'react'
+
+import LayerLogo from '../assets/LayerLogo.png'
+import Input from '@/components/common/input/Input'
+import IdInput from '@/components/auth/IdInput'
+import Header from '@/components/common/header/Header'
+import AlertBox from '@/components/auth/AlertBox'
+
+import {
+ validateNickname,
+ validatePassword,
+ validatePasswordConfirm,
+ validateUserId,
+} from '@/utils/validation'
+
+const TAKEN_IDS = ['admin', 'test']
+
+type DuplicateCheckResult = 'available' | 'unavailable' | null
+
+
+export default function Register () {
+
+ const [id] = useState('')
+ const [password, setPassword] = useState('')
+
+ const [userId, setUserId] = useState('')
+ const [checkResult, setCheckResult] = useState(null)
+
+ const [passwordConfirm, setPasswordConfirm] = useState('')
+
+ const handleUserIdChange = (e: ChangeEvent) => {
+ setUserId(e.target.value)
+ setCheckResult(null) // 입력이 바뀌면 이전 확인 결과 무효화
+ }
+
+ const idFormat = validateUserId(userId)
+ const idStatus = checkResult ?? idFormat.status
+ const idMessage =
+ checkResult === 'available'
+ ? '사용 가능한 아이디예요.'
+ : checkResult === 'unavailable'
+ ? '이미 사용 중인 아이디예요. 다시 입력해주세요.'
+ : idFormat.message
+
+ const handleCheckDuplicate = () => {
+ setCheckResult(TAKEN_IDS.includes(userId) ? 'unavailable' : 'available')
+ }
+
+ return (
+
+
+
console.log('뒤로가기')}
+ />
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/Setting.tsx b/src/pages/Setting.tsx
new file mode 100644
index 0000000..7b5130a
--- /dev/null
+++ b/src/pages/Setting.tsx
@@ -0,0 +1,77 @@
+import { useState, type ChangeEvent } from 'react'
+import Header from '@/components/common/header/Header'
+import BottomNav, {
+ type NavValue,
+} from '@/components/common/navigation/BottomNav'
+import DeleteAccount from '@/components/settings/DeleteAccount'
+import DeleteAccountAlert from '@/components/settings/DeleteAccountAlert'
+
+import Archive from '@/assets/Archive.png'
+import Arrow from '@/assets/Arrow.png'
+
+export default function Setting () {
+
+ const [activeNav, setActiveNav] = useState('setting');
+ const [deleteAccountShow, setDeleteAccountShow] = useState(false);
+ const [deleteAccountAlertShow, setDeleteAccountAlertShow] = useState(false);
+
+ const DeleteAccountShow = () =>{
+ if(deleteAccountShow == false){
+ setDeleteAccountShow(true);
+ }
+ else{
+ setDeleteAccountShow(false);
+ }
+ }
+
+ const DeleteAccountAlertShow = () =>{
+ if(deleteAccountAlertShow == false){
+ setDeleteAccountShow(false);
+ setDeleteAccountAlertShow(true);
+ }
+ else{
+ setDeleteAccountShow(true);
+ setDeleteAccountAlertShow(false);
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
기록 가이드
+
주제와 가치 설명 보기
+
+
+
+
+
+
+
+
+
+
Layer v1.0.0
+
+
+ {/* Account Delete tab */}
+
+ {/* Account Delete Alert tab */}
+
+
+ );
+}
\ No newline at end of file