diff --git a/.eslintrc.js b/.eslintrc.js index c8df6075..03ee7431 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,4 @@ module.exports = { root: true, - extends: ["custom"], + extends: ['custom'], }; diff --git "a/.github/ISSUE_TEMPLATE/\342\231\273\357\270\217-\353\246\254\355\214\251\355\206\240\353\247\201-\354\232\224\354\262\255.md" "b/.github/ISSUE_TEMPLATE/\342\231\273\357\270\217-\353\246\254\355\214\251\355\206\240\353\247\201-\354\232\224\354\262\255.md" index 49fc6fd4..936d4a86 100644 --- "a/.github/ISSUE_TEMPLATE/\342\231\273\357\270\217-\353\246\254\355\214\251\355\206\240\353\247\201-\354\232\224\354\262\255.md" +++ "b/.github/ISSUE_TEMPLATE/\342\231\273\357\270\217-\353\246\254\355\214\251\355\206\240\353\247\201-\354\232\224\354\262\255.md" @@ -1,23 +1,26 @@ --- -name: '♻️ 리팩토링 요청' +name: "♻️ 리팩토링 요청" about: 변경 혹은 개선해야 되는 문제를 작성해 주세요 title: 'refactor: ' labels: '' assignees: '' + --- -## 개선해야 되는 코드 혹은 기능에 대해서 적어주세요 +## 개선해야 되는 코드 혹은 기능에 대해서 적어주세요 + + -개선해야 될 코드에 대한 명확하고 간단한 설명 ## 원하는 개선 방향 + + -개선해야 되는 간단한 이유 혹은 개선 후 장점에 대해 적어주세요 ## 생각 중인 기능 추가 방안 + -해결책으로 간단하게 생각한 개선 방법에 대해 적어주세요 -## ETC -스크린샷이나 기능 등 추가 자료를 기술해 주세요 +## ETC + diff --git "a/.github/ISSUE_TEMPLATE/\342\234\250-\352\270\260\353\212\245-\354\266\224\352\260\200-\354\232\224\354\262\255.md" "b/.github/ISSUE_TEMPLATE/\342\234\250-\352\270\260\353\212\245-\354\266\224\352\260\200-\354\232\224\354\262\255.md" index d6783961..3e6aee3d 100644 --- "a/.github/ISSUE_TEMPLATE/\342\234\250-\352\270\260\353\212\245-\354\266\224\352\260\200-\354\232\224\354\262\255.md" +++ "b/.github/ISSUE_TEMPLATE/\342\234\250-\352\270\260\353\212\245-\354\266\224\352\260\200-\354\232\224\354\262\255.md" @@ -1,26 +1,27 @@ --- -name: '✨ 기능 추가 요청' +name: "✨ 기능 추가 요청" about: 구현하려는 새로운 기능을 요청 -title: 'feat: ' +title: 'feat: ' labels: '' assignees: '' + --- -## 추가하려는 기능이 어떠한 문제 혹은 기능과 연관되어 있나요? +## 추가하려는 기능이 어떠한 문제 혹은 기능과 연관되어 있나요? + -문제가 무엇인지에 대한 명확하고 간결한 설명을 적어주세요 -## 원하는 기능 추가 -추가하려는 기능을 명확하고 간결하게 설명해주세요 +## 원하는 기능 추가 + - [ ] todo - [ ] todo ## 생각 중인 기능 추가 방안 + -해결책으로 간단하게 생각한 기능의 방향 혹은 컴포넌트를 설명해주세요 -## ETC -스크린샷이나 기능 등 추가 자료를 기술해 주세요 +## ETC + diff --git "a/.github/ISSUE_TEMPLATE/\360\237\220\233-\353\262\204\352\267\270-\353\246\254\355\217\254\355\212\270.md" "b/.github/ISSUE_TEMPLATE/\360\237\220\233-\353\262\204\352\267\270-\353\246\254\355\217\254\355\212\270.md" index 561796c4..7a361c09 100644 --- "a/.github/ISSUE_TEMPLATE/\360\237\220\233-\353\262\204\352\267\270-\353\246\254\355\217\254\355\212\270.md" +++ "b/.github/ISSUE_TEMPLATE/\360\237\220\233-\353\262\204\352\267\270-\353\246\254\355\217\254\355\212\270.md" @@ -4,17 +4,20 @@ about: 버그를 고쳐주세요 title: 'bug: ' labels: '' assignees: '' + --- ## 버그 설명 + -발생되는 문제에 대해 간단하게 설명해 주세요 -## 버그 발생 단계 +## 버그 발생 단계 + diff --git a/apps/shelter/package.json b/apps/shelter/package.json index d75db056..059c5543 100644 --- a/apps/shelter/package.json +++ b/apps/shelter/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "tsc && vite build", "dev": "vite", - "lint": "eslint \"src/**/*.ts\"", + "lint": "eslint \"src/**/*.{ts,tsx}\"", "preview": "vite preview" }, "dependencies": { @@ -24,7 +24,7 @@ "react-error-boundary": "^4.0.11", "react-hook-form": "^7.47.0", "react-router-dom": "^6.17.0", - "ui": "workspace:*", + "shared": "workspace:*", "zod": "^3.22.4", "zustand": "^4.4.4" }, diff --git a/apps/shelter/src/App.tsx b/apps/shelter/src/App.tsx index 88653cfd..e222a5e1 100644 --- a/apps/shelter/src/App.tsx +++ b/apps/shelter/src/App.tsx @@ -1,12 +1,22 @@ import { ChakraProvider } from '@chakra-ui/react'; -import { CustomButton, Header } from 'ui'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { RouterProvider } from 'react-router-dom'; +import Fonts from 'shared/fonts'; +import theme from 'shared/theme'; + +import { router } from '@/routes'; + +const queryClient = new QueryClient(); export default function App() { return ( - -
- 보호소 어플리케이션 - - + + + + + + + ); } diff --git a/apps/shelter/src/apis/auth.ts b/apps/shelter/src/apis/auth.ts new file mode 100644 index 00000000..1e4fc97a --- /dev/null +++ b/apps/shelter/src/apis/auth.ts @@ -0,0 +1,26 @@ +import axiosInstance from 'shared/apis/axiosInstance'; +import type { + checkDuplicatedEmailRequestData, + checkDuplicatedEmailResponseData, + SigninRequestData, + SigninResponseData, +} from 'shared/types/apis/auth'; + +import { SignupRequestData } from '@/types/apis/auth'; + +export const signinShelter = async (data: SigninRequestData) => + await axiosInstance.post( + '/auth/shelters/login', + data, + ); + +export const signupShelter = async (data: SignupRequestData) => + await axiosInstance.post('/shelters', data); + +export const checkDuplicatedShelterEmail = async (email: string) => + await axiosInstance.post< + checkDuplicatedEmailResponseData, + checkDuplicatedEmailRequestData + >('/shelters/email', { + email, + }); diff --git a/apps/shelter/src/apis/recruitment.ts b/apps/shelter/src/apis/recruitment.ts new file mode 100644 index 00000000..a5983917 --- /dev/null +++ b/apps/shelter/src/apis/recruitment.ts @@ -0,0 +1,140 @@ +import axiosInstance from 'shared/apis/axiosInstance'; + +type PageInfo = { + totalElements: number; + hasNext: boolean; +}; + +type Recruitment = { + recruitmentId: number; + recruitmentTitle: string; + recruitmentStartTime: string; + recruitmentEndTime: string; + recruitmentDeadline: string; + recruitmentIsClosed: boolean; + recruitmentApplicantCount: number; + recruitmentCapacity: number; +}; + +type RecruitSearchParams = { + keyword: string; + startDate: string; + endDate: string; + isClosed: boolean; + content: boolean; + title: boolean; + pageSize: number; + pageNumber: number; +}; + +type PostRecruitmentParams = { + title: string; + startTime: string; + endTime: string; + deadline: string; + capacity: number; + content: string; + imageUrls: string[]; +}; + +type AttendanceStatus = { + applicantId: number; + attendance: boolean; +}; + +type Gender = 'MALE' | 'FEMALE'; +type RecruitementStatus = 'PENDING' | 'REFUSED' | 'APPROVED'; + +export const getShelterRecruitments = async ( + recruitSearchParams: Partial, +) => + axiosInstance.get< + { + pageInfo: PageInfo; + recruitments: Recruitment[]; + }, + RecruitSearchParams + >('/shelters/recruitments', { + params: recruitSearchParams, + }); + +export const createShelterRecruitment = ( + recruitmentParams: PostRecruitmentParams, +) => + axiosInstance.post( + `/shelters/recruitments`, + recruitmentParams, + ); + +export const updateShelterRecruitment = ( + recruitmentId: number, + recruitmentParams: PostRecruitmentParams, +) => + axiosInstance.patch( + `/shelters/recruitments/${recruitmentId}`, + recruitmentParams, + ); + +export const deleteShelterRecruitment = (recruitmentId: number) => + axiosInstance.delete( + `/shelters/recruitments/${recruitmentId}`, + ); + +export const closeShelterRecruitment = (recruitmentId: number) => + axiosInstance.patch( + `/shelters/recruitments/${recruitmentId}/close`, + ); + +export const getShelterRecruitmentApplicants = (recruitmentId: number) => + axiosInstance.get<{ + applicants: { + applicantId: number; + volunteerId: number; + volunteerName: string; + volunteerBirthDate: string; + volunteerGender: Gender; + completedVolunteerCount: number; + volunteerTemperature: number; + applicantStatus: RecruitementStatus; + }[]; + recruitmentCapacity: number; + }>(`/shelters/recruitments/${recruitmentId}/applicants`); + +export const updateShelterRecruitmentApplicant = ( + recruitmentId: number, + applicantId: number, +) => + axiosInstance.patch< + unknown, + { + status: RecruitementStatus; + } + >(`/shelters/recruitments/${recruitmentId}/applicants/${applicantId}`); + +export const getShelterApprovedRecruitmentApplicants = ( + recruitmentId: number, +) => + axiosInstance.get<{ + applicants: { + volunteerId: number; + applicantId: number; + volunteerName: string; + volunteerBirthDate: string; + volunteerGender: Gender; + volunteerPhoneNumber: string; + volunteerAttendance: boolean; + }[]; + }>(`/shelters/recruitments/${recruitmentId}/approval`); + +export const updatShelterApplicantsApproval = ( + recruitmentId: number, + applicants: AttendanceStatus[], +) => + axiosInstance.patch< + unknown, + { + applicants: AttendanceStatus[]; + } + >(`/shelters/recruitments/${recruitmentId}/approval`, { + applicants, + }); diff --git a/apps/shelter/src/apis/shelter.ts b/apps/shelter/src/apis/shelter.ts new file mode 100644 index 00000000..87faad9f --- /dev/null +++ b/apps/shelter/src/apis/shelter.ts @@ -0,0 +1,53 @@ +import axiosInstance from 'shared/apis/axiosInstance'; + +import { ShelterInfo } from '@/types/apis/shetler'; + +type PasswordUpdateParams = { + newPassword: string; + oldPassword: string; +}; + +type PageParams = { + pageSize: number; + pageNumber: number; +}; + +export const getShelterInfoAPI = () => + axiosInstance.get('/shelters/me'); + +export const updateShelterInfo = (shelterInfo: ShelterInfo) => + axiosInstance.patch('/shelters/me', shelterInfo); + +export const updatePassword = (passwordUpdateParams: PasswordUpdateParams) => + axiosInstance.patch( + '/shelters/me/password', + passwordUpdateParams, + ); + +export const updateAddressStatusAPI = (isOpenedAddress: boolean) => + axiosInstance.patch< + unknown, + { + isOpenedAddress: boolean; + } + >('/shelters/me/address/status', { isOpenedAddress }); + +export const getShelterReviewList = (pageParams: PageParams) => + axiosInstance.get<{ + pageInfo: { + totalElements: number; + hasNext: boolean; + }; + reviews: { + reviewId: number; + reviewCreatedAt: string; + reviewContent: string; + reviewImageUrls: string[]; + volunteerName: string; + volunteerTemperature: number; + volunteerReviewCount: number; + volunteerImageUrl: string; + }[]; + }>(`/shelters/me/reviews`, { + params: pageParams, + }); diff --git a/apps/shelter/src/constants/path.ts b/apps/shelter/src/constants/path.ts new file mode 100644 index 00000000..d6396874 --- /dev/null +++ b/apps/shelter/src/constants/path.ts @@ -0,0 +1,40 @@ +const PATH = { + VOLUNTEERS: { + INDEX: 'volunteers', + DETAIL: ':id', + PROFILE: 'profile', + SEARCH: 'search', + WRITE: 'write', + UPDATE: 'write/:id', + }, + ANIMALS: { + INDEX: 'animals', + DETAIL: ':id', + SEARCH: 'search', + WRITE: 'write', + UPDATE: 'write/:id', + }, + CHATTINGS: { + INDEX: 'chattings', + ROOM: 'chattings/:id', + }, + MYPAGE: { + INDEX: 'mypage', + REVIEWS: 'reviews', + }, + SETTINGS: { + INDEX: 'settings', + ACCOUNT: 'account', + PASSWORD: 'password', + }, + MANAGE: { + INDEX: 'manage', + ATTENDANCE: 'attendance/:id', + APPLY: 'apply/:id', + }, + NOTIFICATIONS: 'notifications', + SIGNUP: 'signup', + SIGNIN: 'signin', +}; + +export default PATH; diff --git a/apps/shelter/src/hooks/useIntersection.tsx b/apps/shelter/src/hooks/useIntersection.tsx new file mode 100644 index 00000000..be9c2f1a --- /dev/null +++ b/apps/shelter/src/hooks/useIntersection.tsx @@ -0,0 +1,36 @@ +import { useCallback, useEffect, useRef } from 'react'; + +type IntersectHandler = ( + entry: IntersectionObserverEntry, + observer: IntersectionObserver, +) => void; + +const useIntersect = ( + onIntersect: IntersectHandler, + options?: IntersectionObserverInit, +) => { + const ref = useRef(null); + const callback = useCallback( + (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + onIntersect(entry, observer); + } + }); + }, + [onIntersect], + ); + + useEffect(() => { + if (!ref.current) { + return; + } + const observer = new IntersectionObserver(callback, options); + observer.observe(ref.current); + return () => observer.disconnect(); + }, [ref, options, callback]); + + return ref; +}; + +export default useIntersect; diff --git a/apps/shelter/src/main.tsx b/apps/shelter/src/main.tsx index 23ceb8cf..f6444594 100644 --- a/apps/shelter/src/main.tsx +++ b/apps/shelter/src/main.tsx @@ -1,5 +1,3 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import React from 'react'; import ReactDOM from 'react-dom/client'; @@ -17,15 +15,10 @@ async function deferRender() { return worker.start(); } -const queryClient = new QueryClient(); - deferRender().then(() => { ReactDOM.createRoot(document.getElementById('root')!).render( - - - - + , ); }); diff --git a/apps/shelter/src/mocks/browser.ts b/apps/shelter/src/mocks/browser.ts index c95883bc..7f1ed5cd 100644 --- a/apps/shelter/src/mocks/browser.ts +++ b/apps/shelter/src/mocks/browser.ts @@ -1,5 +1,11 @@ import { setupWorker } from 'msw/browser'; -import { handlers } from './handlers'; +import { handlers as authHandlers } from './handlers/auth'; +import { handlers as recruitmentHandler } from './handlers/recruitment'; +import { handlers as shelterHandlers } from './handlers/shelter'; -export const worker = setupWorker(...handlers); +export const worker = setupWorker( + ...authHandlers, + ...shelterHandlers, + ...recruitmentHandler, +); diff --git a/apps/shelter/src/mocks/handlers.ts b/apps/shelter/src/mocks/handlers.ts deleted file mode 100644 index b091270d..00000000 --- a/apps/shelter/src/mocks/handlers.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { delay, http, HttpResponse } from 'msw'; - -const ALL_POST = [ - { - userId: 1, - id: 1, - title: '모킹 포스트 1', - body: '모킹된 포스트에요. MSW를 이용했어요.', - }, - { - userId: 1, - id: 2, - title: '모킹 포스트 2', - body: '모킹된 포스트에요. MSW를 이용했어요.', - }, - { - userId: 1, - id: 3, - title: '모킹 포스트 3', - body: '모킹된 포스트에요. MSW를 이용했어요.', - }, - { - userId: 1, - id: 4, - title: '모킹 포스트 4', - body: '모킹된 포스트에요. MSW를 이용했어요.', - }, - { - userId: 1, - id: 5, - title: '모킹 포스트 5', - body: '모킹된 포스트에요. MSW를 이용했어요.', - }, -]; - -export const handlers = [ - http.get('/example/posts', async () => { - await delay(200); - return HttpResponse.json(ALL_POST); - }), -]; diff --git a/apps/shelter/src/mocks/handlers/auth.ts b/apps/shelter/src/mocks/handlers/auth.ts new file mode 100644 index 00000000..f7a041d0 --- /dev/null +++ b/apps/shelter/src/mocks/handlers/auth.ts @@ -0,0 +1,78 @@ +import { delay, http, HttpResponse } from 'msw'; + +export const handlers = [ + http.post('/auth/shelters/login', async () => { + await delay(200); + return HttpResponse.json( + { + accessToken: 'accessToken', + userId: 1, + role: 'ROLE_SEHLTER', + }, + { status: 200 }, + ); + }), + http.post('/auth/shelters/login', async () => { + await delay(200); + return HttpResponse.json( + { + errorCode: 'AF002', + message: '이메일/비밀번호가 올바르지 않습니다', + }, + { status: 400 }, + ); + }), + http.post('/auth/shelters/login', async () => { + await delay(200); + return HttpResponse.json( + { + errorCode: 'AF001', + message: '잘못된 입력값입니다', + }, + { status: 400 }, + ); + }), + http.post('/shelters', async () => { + await delay(200); + return HttpResponse.json({}, { status: 400 }); + }), + http.post('/shelters', async () => { + await delay(200); + return HttpResponse.json( + { + errorCode: 'AF002', + message: '{입력값}은 1자 이상, 20자 이하여야 합니다.', + }, + { status: 400 }, + ); + }), + http.post('/shelters', async () => { + await delay(200); + return HttpResponse.json( + { + errorCode: 'AF001', + message: '요청값이 입력되지 않았습니다.. {}', + }, + { status: 400 }, + ); + }), + http.post('/shelters/email', async () => { + await delay(200); + return HttpResponse.json( + { + isDuplicated: false, + }, + { status: 200 }, + ); + }), + http.post('/shelters/email', async () => { + await delay(200); + return HttpResponse.json( + { + errorCode: 'AF001', + message: '잘못된 입력값입니다', + }, + { status: 400 }, + ); + }), +]; diff --git a/apps/shelter/src/mocks/handlers/recruitment.ts b/apps/shelter/src/mocks/handlers/recruitment.ts new file mode 100644 index 00000000..98219ebe --- /dev/null +++ b/apps/shelter/src/mocks/handlers/recruitment.ts @@ -0,0 +1,33 @@ +import { delay, http, HttpResponse } from 'msw'; + +const DUMMY_RECRUITMENT = { + recruitmentId: 1, + recruitmentTitle: '봉사자를 모집합니다', + recruitmentStartTime: '2021-11-08T11:44:30.327959', + recruitmentEndTime: '2021-11-08T11:44:30.327959', + recruitmentDeadline: '2023-11-20T11:44:30.327959', + recruitmentIsClosed: false, + recruitmentApplicantCount: 15, + recruitmentCapacity: 15, +}; + +const DUMMY_RECRUITMENT_LIST = Array.from( + { length: 4 }, + () => DUMMY_RECRUITMENT, +); + +export const handlers = [ + http.get('/shelters/recruitments', async () => { + await delay(1000); + return HttpResponse.json( + { + pageInfo: { + totalElements: 100, + hasNext: true, + }, + recruitments: DUMMY_RECRUITMENT_LIST, + }, + { status: 200 }, + ); + }), +]; diff --git a/apps/shelter/src/mocks/handlers/shelter.ts b/apps/shelter/src/mocks/handlers/shelter.ts new file mode 100644 index 00000000..d7ee40cf --- /dev/null +++ b/apps/shelter/src/mocks/handlers/shelter.ts @@ -0,0 +1,52 @@ +import { delay, http, HttpResponse } from 'msw'; + +const DUMMY_IMAGE = 'https://source.unsplash.com/random'; +const DUMMY_IMAGE_LIST = Array.from({ length: 4 }, () => DUMMY_IMAGE); +const DUMMY_REVIEW = { + reviewId: 32, + reviewCreatedAt: '2023-03-16T18:00', + reviewContent: '시설이 너무 깨끗하고 강아지도...', + reviewImageUrls: DUMMY_IMAGE_LIST, + volunteerName: '강혜린', + volunteerTemperature: 44, + volunteerReviewCount: 4, + volunteerImageUrl: DUMMY_IMAGE, +}; +const DUMMY_REVIEW_LIST = Array.from({ length: 4 }, () => DUMMY_REVIEW); + +export const handlers = [ + http.get('/shelters/me', async () => { + await delay(200); + return HttpResponse.json( + { + shelterId: 1, + shelterEmail: 'Shelter1234@gmail.com', + shelterName: '양천구 보호소', + imageUrl: null, + shelterAddress: '서울특별시 양천구', + shelterAddressDetail: '서울특별시 양천구 신월동 동자빌딩', + shelterPhoneNumber: '010-1234-5678', + shelterSparePhoneNumber: '02-345-6780', + shelterIsOpenedAddress: true, + }, + { status: 200 }, + ); + }), + http.patch('/shelters/me/address/status', async () => { + await delay(200); + return HttpResponse.json({ status: 200 }); + }), + http.get('/shelters/me/reviews', async () => { + await delay(200); + return HttpResponse.json( + { + pageInfo: { + totalElements: 100, + hasNext: true, + }, + reviews: DUMMY_REVIEW_LIST, + }, + { status: 200 }, + ); + }), +]; diff --git a/apps/shelter/src/pages/animals/detail/index.tsx b/apps/shelter/src/pages/animals/detail/index.tsx new file mode 100644 index 00000000..e36382be --- /dev/null +++ b/apps/shelter/src/pages/animals/detail/index.tsx @@ -0,0 +1,21 @@ +import { useEffect } from 'react'; +import useDetailHeaderStore from 'shared/store/detailHeaderStore'; + +const handleDeletePost = (postId: number) => { + // TODO: AnimalPost delete API 호출 + console.log('[Delete Animal] postId:', postId); +}; + +export default function AnimalsDetailPage() { + const setOnDelete = useDetailHeaderStore((state) => state.setOnDelete); + + useEffect(() => { + setOnDelete(handleDeletePost); + + return () => { + setOnDelete(() => {}); + }; + }, [setOnDelete]); + + return

AnimalsDetailPage

; +} diff --git a/apps/shelter/src/pages/animals/index.tsx b/apps/shelter/src/pages/animals/index.tsx new file mode 100644 index 00000000..c11ddc43 --- /dev/null +++ b/apps/shelter/src/pages/animals/index.tsx @@ -0,0 +1,3 @@ +export default function AnimalsPage() { + return

AnimalsPage

; +} diff --git a/apps/shelter/src/pages/animals/search/index.tsx b/apps/shelter/src/pages/animals/search/index.tsx new file mode 100644 index 00000000..5bbff8b6 --- /dev/null +++ b/apps/shelter/src/pages/animals/search/index.tsx @@ -0,0 +1,21 @@ +import { useEffect } from 'react'; +import useSearchHeaderStore from 'shared/store/searchHeaderStore'; + +const handleSearchkeyword = (keyword: string) => { + // TODO: AnimalList 검색 API 호출 + console.log('[Search Animal] - keyword:', keyword); +}; + +export default function AnimalsSearchPage() { + const setOnSearch = useSearchHeaderStore((state) => state.setOnSearch); + + useEffect(() => { + setOnSearch(handleSearchkeyword); + + return () => { + setOnSearch(() => {}); + }; + }, [setOnSearch]); + + return

AnimalsSearchPage

; +} diff --git a/apps/shelter/src/pages/animals/update/index.tsx b/apps/shelter/src/pages/animals/update/index.tsx new file mode 100644 index 00000000..3a9b93fc --- /dev/null +++ b/apps/shelter/src/pages/animals/update/index.tsx @@ -0,0 +1,3 @@ +export default function AnimalsUpdatePage() { + return

AnimalsUpdatePage

; +} diff --git a/apps/shelter/src/pages/animals/write/index.tsx b/apps/shelter/src/pages/animals/write/index.tsx new file mode 100644 index 00000000..a081d6a5 --- /dev/null +++ b/apps/shelter/src/pages/animals/write/index.tsx @@ -0,0 +1,3 @@ +export default function AnimalsWritePage() { + return

AnimalsWritePage

; +} diff --git a/apps/shelter/src/pages/chattings/index.tsx b/apps/shelter/src/pages/chattings/index.tsx new file mode 100644 index 00000000..a48ead0b --- /dev/null +++ b/apps/shelter/src/pages/chattings/index.tsx @@ -0,0 +1,3 @@ +export default function ChattingsPage() { + return

ChattingsPage

; +} diff --git a/apps/shelter/src/pages/chattings/room/index.tsx b/apps/shelter/src/pages/chattings/room/index.tsx new file mode 100644 index 00000000..55fc00e1 --- /dev/null +++ b/apps/shelter/src/pages/chattings/room/index.tsx @@ -0,0 +1,3 @@ +export default function ChattingsRoomPage() { + return

ChattingsRoomPage

; +} diff --git a/apps/shelter/src/pages/manage/apply/index.tsx b/apps/shelter/src/pages/manage/apply/index.tsx new file mode 100644 index 00000000..ad1f19d6 --- /dev/null +++ b/apps/shelter/src/pages/manage/apply/index.tsx @@ -0,0 +1,3 @@ +export default function ManageApplyPage() { + return

ManageApplyPage

; +} diff --git a/apps/shelter/src/pages/manage/attendance/index.tsx b/apps/shelter/src/pages/manage/attendance/index.tsx new file mode 100644 index 00000000..31edc9f3 --- /dev/null +++ b/apps/shelter/src/pages/manage/attendance/index.tsx @@ -0,0 +1,3 @@ +export default function ManageAttendancePage() { + return

ManageAttendancePage

; +} diff --git a/apps/shelter/src/pages/my/_hooks/useMyPage.ts b/apps/shelter/src/pages/my/_hooks/useMyPage.ts new file mode 100644 index 00000000..2d781c66 --- /dev/null +++ b/apps/shelter/src/pages/my/_hooks/useMyPage.ts @@ -0,0 +1,66 @@ +import { useQuery } from '@tanstack/react-query'; +import { useState } from 'react'; + +import { getShelterInfoAPI, updateAddressStatusAPI } from '@/apis/shelter'; +import { ShelterInfo } from '@/types/apis/shetler'; + +type ShelterProfile = { + shelterName: string; + email: string; + phoneNumber: string; + sparePhoneNumber: string; + shelterAddress: string; + isAddressPublic: boolean; +}; + +const createProfile = (response: ShelterInfo): ShelterProfile => { + const { + shelterName, + shelterEmail, + shelterPhoneNumber, + shelterSparePhoneNumber, + shelterAddressDetail, + shelterIsOpenedAddress, + } = response; + return { + shelterName: shelterName, + email: shelterEmail, + phoneNumber: shelterPhoneNumber, + sparePhoneNumber: shelterSparePhoneNumber, + shelterAddress: shelterAddressDetail, + isAddressPublic: shelterIsOpenedAddress, + }; +}; + +export const useMyPage = () => { + const [isAddressPublic, setIsAddressPublic] = useState(false); + + const updateAddressStatus = async () => { + try { + await updateAddressStatusAPI(!isAddressPublic); + setIsAddressPublic(!isAddressPublic); + } catch (error) { + console.error(error); + } + }; + + const { data } = useQuery({ + queryKey: ['shelterProfile'], + queryFn: async () => { + const response = (await getShelterInfoAPI()).data; + setIsAddressPublic(response.shelterIsOpenedAddress); + + return createProfile(response); + }, + initialData: { + shelterName: '', + email: '', + phoneNumber: '', + sparePhoneNumber: '', + shelterAddress: '', + isAddressPublic: false, + }, + }); + + return { shelterProfile: data, isAddressPublic, updateAddressStatus }; +}; diff --git a/apps/shelter/src/pages/my/index.tsx b/apps/shelter/src/pages/my/index.tsx new file mode 100644 index 00000000..7648b4d1 --- /dev/null +++ b/apps/shelter/src/pages/my/index.tsx @@ -0,0 +1,62 @@ +import { Box, Divider, Switch, VStack } from '@chakra-ui/react'; +import { useNavigate } from 'react-router-dom'; +import InfoItem from 'shared/components/InfoItem'; +import InfoList from 'shared/components/InfoList'; +import InfoTextItem from 'shared/components/InfoTextItem'; +import ProfileInfo from 'shared/components/ProfileInfo'; +import SettingGroup from 'shared/components/SettingGroup'; + +import { useMyPage } from '@/pages/my/_hooks/useMyPage'; + +export default function MyPage() { + const navigate = useNavigate(); + const { shelterProfile, isAddressPublic, updateAddressStatus } = useMyPage(); + + const { shelterName, email, phoneNumber, sparePhoneNumber, shelterAddress } = + shelterProfile; + + const goShelterReview = () => navigate('/mypage/reviews'); + const goSettingsAccount = () => navigate('/settings/account'); + const goSettingsPassword = () => navigate('/settings/password'); + const logout = () => { + // TODO: 로그아웃 + }; + + return ( + + + + + + + + + + + + + + + + + + ); +} diff --git a/apps/shelter/src/pages/my/reviews/VolunteerProfile.tsx b/apps/shelter/src/pages/my/reviews/VolunteerProfile.tsx new file mode 100644 index 00000000..9701bbcd --- /dev/null +++ b/apps/shelter/src/pages/my/reviews/VolunteerProfile.tsx @@ -0,0 +1,41 @@ +import { Avatar, Box, HStack, Image, Text } from '@chakra-ui/react'; +import NextIcon from 'shared/assets/icon_review_next.svg'; +import InfoSubtext from 'shared/components/InfoSubtext'; +import Label from 'shared/components/Label'; + +type VolunteerProfileprops = { + volunteerName: string; + volunteerTempature: number; + volunteerReviewCount: number; + volunteerImageUrl: string; + reviewCreatedAt: string; +}; + +export default function VolunteerProfile({ + volunteerName, + volunteerTempature, + volunteerReviewCount, + volunteerImageUrl, + reviewCreatedAt, +}: VolunteerProfileprops) { + return ( + + + + + + {volunteerName} + + + + + + + + + + + ); +} diff --git a/apps/shelter/src/pages/my/reviews/hooks/useFetchShelterReviews.tsx b/apps/shelter/src/pages/my/reviews/hooks/useFetchShelterReviews.tsx new file mode 100644 index 00000000..48bb56dd --- /dev/null +++ b/apps/shelter/src/pages/my/reviews/hooks/useFetchShelterReviews.tsx @@ -0,0 +1,14 @@ +import { useSuspenseInfiniteQuery } from '@tanstack/react-query'; + +import { getShelterReviewList } from '@/apis/shelter'; + +export default function useFetchShelterReviews(pageSize: number) { + return useSuspenseInfiniteQuery({ + queryKey: ['reviews'], + queryFn: ({ pageParam }) => + getShelterReviewList({ pageNumber: pageParam, pageSize }), + initialPageParam: 0, + getNextPageParam: ({ data: { pageInfo } }, _, lastPageParam) => + pageInfo.hasNext ? lastPageParam + 1 : null, + }); +} diff --git a/apps/shelter/src/pages/my/reviews/index.tsx b/apps/shelter/src/pages/my/reviews/index.tsx new file mode 100644 index 00000000..9f0f0167 --- /dev/null +++ b/apps/shelter/src/pages/my/reviews/index.tsx @@ -0,0 +1,77 @@ +import { Box, Heading, VStack } from '@chakra-ui/react'; +import { Suspense } from 'react'; +import ReviewItem from 'shared/components/ReviewItem'; +import { createFormattedTime } from 'shared/utils/date'; + +import useIntersect from '@/hooks/useIntersection'; + +import useFetchShelterReviews from './hooks/useFetchShelterReviews'; +import VolunteerProfile from './VolunteerProfile'; + +const PAGE_SIZE = 10; + +function Reviews() { + //TODO 봉사자 옆에 화살표 버튼 클릭시 봉사자 프로필 페이지로 가는 기능추가 + + const { + data: { pages }, + hasNextPage, + isFetchingNextPage, + fetchNextPage, + } = useFetchShelterReviews(PAGE_SIZE); + + const totalReviews = pages[0].data.pageInfo.totalElements; + const reviews = pages.flatMap(({ data }) => data.reviews); + const ref = useIntersect(async (entry, observer) => { + observer.unobserve(entry.target); + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }); + + return ( + + + 봉사자들이 작성한 봉사후기{` ${totalReviews}개`} + + + + {reviews.map((review) => ( + + + + ))} + + + + ); +} + +export default function MyReviewsPage() { + return ( + 글목록 로딩중...

}> + +
+ ); +} diff --git a/apps/shelter/src/pages/notfound/index.tsx b/apps/shelter/src/pages/notfound/index.tsx new file mode 100644 index 00000000..5ba55266 --- /dev/null +++ b/apps/shelter/src/pages/notfound/index.tsx @@ -0,0 +1,3 @@ +export default function NotFoundPage() { + return

NotFoundPage

; +} diff --git a/apps/shelter/src/pages/notifications/index.tsx b/apps/shelter/src/pages/notifications/index.tsx new file mode 100644 index 00000000..b7e24e5d --- /dev/null +++ b/apps/shelter/src/pages/notifications/index.tsx @@ -0,0 +1,3 @@ +export default function NotificationsPage() { + return

NotificationsPage

; +} diff --git a/apps/shelter/src/pages/settings/account/index.tsx b/apps/shelter/src/pages/settings/account/index.tsx new file mode 100644 index 00000000..92db107c --- /dev/null +++ b/apps/shelter/src/pages/settings/account/index.tsx @@ -0,0 +1,3 @@ +export default function SettingsAccountPage() { + return

SettingsAccountPage

; +} diff --git a/apps/shelter/src/pages/settings/password/index.tsx b/apps/shelter/src/pages/settings/password/index.tsx new file mode 100644 index 00000000..1d504fa2 --- /dev/null +++ b/apps/shelter/src/pages/settings/password/index.tsx @@ -0,0 +1,3 @@ +export default function SettingsPasswordPage() { + return

SettingsPasswordPage

; +} diff --git a/apps/shelter/src/pages/signin/index.tsx b/apps/shelter/src/pages/signin/index.tsx new file mode 100644 index 00000000..22355677 --- /dev/null +++ b/apps/shelter/src/pages/signin/index.tsx @@ -0,0 +1,175 @@ +import { + Box, + Button, + Center, + FormControl, + FormErrorMessage, + FormLabel, + Icon, + Image, + Input, + InputGroup, + InputRightElement, + useToast, + VStack, +} from '@chakra-ui/react'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useMutation } from '@tanstack/react-query'; +import type { AxiosResponse } from 'axios'; +import { AxiosError } from 'axios'; +import { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { useNavigate } from 'react-router-dom'; +import AnimalfriendsLogo from 'shared/assets/image-anifriends-logo.png'; +import IoEyeOff from 'shared/assets/IoEyeOff'; +import IoEyeSharp from 'shared/assets/IoEyeSharp'; +import useToggle from 'shared/hooks/useToggle'; +import type { + SigninRequestData, + SigninResponseData, +} from 'shared/types/apis/auth'; +import { ErrorResponseData } from 'shared/types/apis/error'; +import * as z from 'zod'; + +import { signinShelter } from '@/apis/auth'; +import PATH from '@/constants/path'; + +type Schema = z.infer; + +const schema = z.object({ + email: z + .string() + .min(1, '이메일이 입려되지 않았습니다') + .email('유효하지 않은 이메일입니다'), + password: z.string().min(1, '비밀번호가 입력되지 않았습니다'), +}); + +export default function SigninPage() { + const navigate = useNavigate(); + const toast = useToast(); + const [isShow, toggleInputShow] = useToggle(); + const { + register, + handleSubmit, + formState: { errors }, + setFocus, + } = useForm({ + resolver: zodResolver(schema), + }); + const { mutate } = useMutation< + AxiosResponse, + AxiosError, + SigninRequestData + >({ + mutationFn: (data) => signinShelter(data), + onSuccess: () => { + navigate(`/${PATH.VOLUNTEERS.INDEX}`); + }, + onError: (error) => { + toast({ + position: 'top', + description: error.response?.data.message, + status: 'error', + duration: 1500, + }); + + setFocus('email'); + }, + }); + + const goSignupPage = () => { + navigate(`/${PATH.SIGNUP}`); + }; + + const onSubmit = async (data: Schema) => { + mutate(data); + }; + + useEffect(() => setFocus('email'), [setFocus]); + + return ( + +
+ +
+
+ + 이메일 + + + {errors.email && errors.email.message} + + + + 비밀번호 + + + + + + + + {errors.password && errors.password.message} + + + + + + +
+
+ ); +} diff --git a/apps/shelter/src/pages/signup/index.tsx b/apps/shelter/src/pages/signup/index.tsx new file mode 100644 index 00000000..bf6be532 --- /dev/null +++ b/apps/shelter/src/pages/signup/index.tsx @@ -0,0 +1,300 @@ +import { + Box, + Button, + Center, + FormControl, + FormErrorMessage, + FormHelperText, + FormLabel, + HStack, + Icon, + Image, + Input, + InputGroup, + InputRightAddon, + InputRightElement, + Switch, + VStack, +} from '@chakra-ui/react'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Controller, useForm } from 'react-hook-form'; +import AnimalfriendsLogo from 'shared/assets/image-anifriends-logo.png'; +import IoEyeOff from 'shared/assets/IoEyeOff'; +import IoEyeSharp from 'shared/assets/IoEyeSharp'; +import useToggle from 'shared/hooks/useToggle'; +import * as z from 'zod'; + +type Schema = z.infer; + +const schema = z + .object({ + email: z + .string() + .min(1, '이메일은 필수 정보입니다') + .email('유효하지 않은 이메일입니다'), + password: z + .string() + .regex( + /^(?=.*[!@#$%^&*()\-_=+[\]\\|{};:'",<.>/?]+)(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/, + '비밀번호는 필수 정보입니다(8자 이상)', + ), + passwordConfirm: z.string().min(1, '비밀번호 확인 정보는 필수입니다'), + name: z.string().min(1, '보호소 이름 정보는 필수입니다'), + address: z.string().min(1, '보호소 주소 정보는 필수입니다'), + addressDetail: z.string().min(1, '보호소 상세주소 정보는 필수입니다'), + isOpendedAddress: z.boolean(), + phoneNumber: z + .string() + .min(1, '보호소 전화번호 정보는 필수입니다') + .refine( + (val) => !Number.isNaN(Number(val)), + '전화번호 형식은 숫자입니다', + ), + sparePhoneNumber: z + .string() + .refine( + (val) => !Number.isNaN(Number(val)) || val === '', + '전화번호 형식은 숫자입니다', + ), + }) + .refine(({ password, passwordConfirm }) => password === passwordConfirm, { + message: '비밀번호가 일치하지 않습니다', + path: ['passwordConfirm'], + }); + +export default function SignupPage() { + const [isPasswordShow, togglePasswordShow] = useToggle(); + const [isPasswordConfirmShow, togglePasswordConfirmShow] = useToggle(); + const { + register, + handleSubmit, + formState: { errors }, + control, + } = useForm({ + defaultValues: { + isOpendedAddress: false, + }, + resolver: zodResolver(schema), + }); + + const onSubmit = (data: Schema) => { + console.log(data); + }; + + return ( + +
+ +
+
+ + 이메일 + + + + 확인 + + + + {errors.email && errors.email.message} + + + + 비밀번호 + + + + + + + + 영대문자, 영소문자, 숫자, 특수문자 조합 8자리 이상 +
+ 특수문자: {`!@#$%^&*()-_=+[\\]{};:'",<.>/?`} +
+ + {errors.password && errors.password.message} + +
+ + 비밀번호 확인 + + + + + + + + {errors.passwordConfirm && errors.passwordConfirm.message} + + + + 보호소 이름 + + + {errors.name && errors.name.message} + + + + 보호소 주소 + + + {errors.address && errors.address.message} + + + + + + 보호소 상세주소 + + + + 상세주소 공개 + + ( + + )} + /> + + + + + {errors.isOpendedAddress && errors.isOpendedAddress.message} + + + {errors.addressDetail && errors.addressDetail.message} + + + + 보호소 전화번호 + + 형식: 01012345678 + + {errors.phoneNumber && errors.phoneNumber.message} + + + + 보호소 임시 전화번호 + + + {errors.sparePhoneNumber && errors.sparePhoneNumber.message} + + + + + + +
+
+ ); +} diff --git a/apps/shelter/src/pages/volunteers/_components/RecruitDateText.tsx b/apps/shelter/src/pages/volunteers/_components/RecruitDateText.tsx new file mode 100644 index 00000000..2a2f686a --- /dev/null +++ b/apps/shelter/src/pages/volunteers/_components/RecruitDateText.tsx @@ -0,0 +1,15 @@ +import { Text } from '@chakra-ui/react'; + +type RecruitDateTextProps = { + title: string; + date: string; + time: string; +}; + +export default function RecruitDateText({ + title, + date, + time, +}: RecruitDateTextProps) { + return {`${title} | ${date} • ${time}`}; +} diff --git a/apps/shelter/src/pages/volunteers/_components/RecruitItem.tsx b/apps/shelter/src/pages/volunteers/_components/RecruitItem.tsx new file mode 100644 index 00000000..e13fe287 --- /dev/null +++ b/apps/shelter/src/pages/volunteers/_components/RecruitItem.tsx @@ -0,0 +1,233 @@ +import { + Box, + Button, + Flex, + HStack, + Image, + Menu, + MenuButton, + MenuItem, + MenuList, + Spacer, + Text, + VStack, +} from '@chakra-ui/react'; +import MenuIcon from 'shared/assets/icon_menu.svg'; +import ApplicantStatus from 'shared/components/ApplicantStatus'; +import Label from 'shared/components/Label'; +import LabelText from 'shared/components/LabelText'; +import { createFormattedTime, getDDay } from 'shared/utils/date'; + +import RecruitDateText from './RecruitDateText'; + +type Recruitment = { + recruitmentId: number; + recruitmentTitle: string; + recruitmentStartTime: string; + recruitmentEndTime: string; + recruitmentDeadline: string; + recruitmentIsClosed: boolean; + recruitmentApplicantCount: number; + recruitmentCapacity: number; +}; + +type RecruitItemProps = { + showMenuButton?: boolean; + onUpdate?: VoidFunction; + onDelete?: VoidFunction; + onClickManageApplyButton: VoidFunction; + onClickManageAttendanceButton: VoidFunction; + onClickCloseRecruitButton: VoidFunction; +} & Recruitment; + +export default function RecruitItem({ + showMenuButton = true, + onUpdate = () => {}, + onDelete = () => {}, + recruitmentId, + recruitmentTitle, + recruitmentStartTime, + recruitmentEndTime, + recruitmentDeadline, + recruitmentIsClosed, + recruitmentApplicantCount, + recruitmentCapacity, + onClickCloseRecruitButton, + onClickManageApplyButton, + onClickManageAttendanceButton, +}: RecruitItemProps) { + return ( + + + + {recruitmentIsClosed ? ( + + {recruitmentIsClosed ? ( + + ) : ( + + )} + + {showMenuButton && } + + ); +} + +function RecruitingButtons({ + onClickManageApplyButton, + onClickCloseRecruitButton, +}: Pick< + RecruitItemProps, + 'onClickCloseRecruitButton' | 'onClickManageApplyButton' +>) { + return ( + + + + + ); +} + +function AttendanceManagementButton({ + onClickManageAttendanceButton, +}: Pick) { + return ( + + ); +} + +function CustomMenu({ + onUpdate, + onDelete, +}: { + onUpdate: VoidFunction; + onDelete: VoidFunction; +}) { + return ( + + + menu icon + + + 수정하기 + 삭제하기 + 닫기 + + + ); +} diff --git a/apps/shelter/src/pages/volunteers/detail/AlertModal.tsx b/apps/shelter/src/pages/volunteers/detail/AlertModal.tsx new file mode 100644 index 00000000..a28edc70 --- /dev/null +++ b/apps/shelter/src/pages/volunteers/detail/AlertModal.tsx @@ -0,0 +1,52 @@ +import { + Button, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + ModalProps, +} from '@chakra-ui/react'; + +type AlertModalProps = { + modalTitle: string; + modalContent: string; + onClick?: VoidFunction; +} & Omit; + +export default function AlertModal({ + modalTitle, + modalContent, + isOpen, + onClose, + onClick, +}: AlertModalProps) { + return ( + + + + + {modalTitle} + + + {modalContent} + + + + + + + + ); +} diff --git a/apps/shelter/src/pages/volunteers/detail/index.tsx b/apps/shelter/src/pages/volunteers/detail/index.tsx new file mode 100644 index 00000000..da7a2ced --- /dev/null +++ b/apps/shelter/src/pages/volunteers/detail/index.tsx @@ -0,0 +1,138 @@ +import { + Box, + Button, + Divider, + HStack, + Text, + useDisclosure, + VStack, +} from '@chakra-ui/react'; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import ImageCarousel from 'shared/components/ImageCarousel'; +import InfoTextList from 'shared/components/InfoTextList'; +import { LabelProps } from 'shared/components/Label'; +import LabelText from 'shared/components/LabelText'; +import useDetailHeaderStore from 'shared/store/detailHeaderStore'; + +import AlertModal from './AlertModal'; + +const handleDeletePost = (postId: number) => { + // TODO: VolunteerPost delete API 호출 + console.log('[Delete Volunteer] postId:', postId); +}; + +export default function VolunteersDetailPage() { + const setOnDelete = useDetailHeaderStore((state) => state.setOnDelete); + const postId = 5; + useEffect(() => { + setOnDelete(handleDeletePost); + + return () => { + setOnDelete(() => {}); + }; + }, [setOnDelete]); + + const navigate = useNavigate(); + + const { isOpen, onOpen, onClose } = useDisclosure(); + const [label, setLabel] = useState({ + labelTitle: '모집 중', + type: 'GREEN', + }); + const [isClosed, setIsClosed] = useState(false); + + const goManageApply = () => navigate(`/manage/apply/${postId}`); + const goManageAttendance = () => navigate(`/manage/attendance/${postId}`); + const onVolunteerDeadline = () => { + onClose(); + //TODO label type gray로 변경 + setLabel({ labelTitle: '모집 마감', type: 'ORANGE' }); + setIsClosed(!isClosed); + }; + + return ( + + + + + + 강아지 목욕 봉사자를 모집합니다! + + + 작성일 | 2023.10.23(수정됨) + + + + + + + + 강아지 봉사자를 모집합니다~~~강아지 봉사자를 모집합니다~~~강아지 + 봉사자를 모집합니다~~~강아지 봉사자를 모집합니다~~~강아지 봉사자를 + 모집합니다~~~ + + + {isClosed ? ( + + ) : ( + <> + + + + )} + + + + ); +} diff --git a/apps/shelter/src/pages/volunteers/hooks/useFetchVolunteers.tsx b/apps/shelter/src/pages/volunteers/hooks/useFetchVolunteers.tsx new file mode 100644 index 00000000..225641cd --- /dev/null +++ b/apps/shelter/src/pages/volunteers/hooks/useFetchVolunteers.tsx @@ -0,0 +1,14 @@ +import { useSuspenseInfiniteQuery } from '@tanstack/react-query'; + +import { getShelterRecruitments } from '@/apis/recruitment'; + +export default function useFetchVolunteers(pageSize: number) { + return useSuspenseInfiniteQuery({ + queryKey: ['recruitments'], + queryFn: ({ pageParam }) => + getShelterRecruitments({ pageNumber: pageParam, pageSize }), + initialPageParam: 0, + getNextPageParam: ({ data: { pageInfo } }, _, lastPageParam) => + pageInfo.hasNext ? lastPageParam + 1 : null, + }); +} diff --git a/apps/shelter/src/pages/volunteers/index.tsx b/apps/shelter/src/pages/volunteers/index.tsx new file mode 100644 index 00000000..c6fbe012 --- /dev/null +++ b/apps/shelter/src/pages/volunteers/index.tsx @@ -0,0 +1,72 @@ +import { Suspense } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import useIntersect from '@/hooks/useIntersection'; + +import RecruitItem from './_components/RecruitItem'; +import useFetchVolunteers from './hooks/useFetchVolunteers'; + +const PAGE_SIZE = 10; + +function Recruitments() { + const navigate = useNavigate(); + + const goToManageApplyPage = (postId: number) => { + navigate(`/manage/apply/${postId}`); + }; + const goToManageAttendancePage = (postId: number) => { + navigate(`/manage/attendance/${postId}`); + }; + const goToUpdatePage = (postId: number) => { + navigate(`/volunteers/write/${postId}`); + }; + + //TODO 삭제 버튼 눌렀을 때 기능 추가 + + //TODO recruit id 받아서 마감 + const closeRecruit = () => {}; + + const { + data: { pages }, + hasNextPage, + isFetchingNextPage, + fetchNextPage, + } = useFetchVolunteers(PAGE_SIZE); + + const recruitments = pages.flatMap(({ data }) => data.recruitments); + + const ref = useIntersect(async (entry, observer) => { + observer.unobserve(entry.target); + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }); + + return ( + <> + {recruitments.map((recruitment) => ( + + goToManageApplyPage(recruitment.recruitmentId) + } + onClickManageAttendanceButton={() => + goToManageAttendancePage(recruitment.recruitmentId) + } + onClickCloseRecruitButton={closeRecruit} + onUpdate={() => goToUpdatePage(recruitment.recruitmentId)} + /> + ))} +
+ + ); +} + +export default function VolunteersPage() { + return ( + 글목록 로딩중...

}> + +
+ ); +} diff --git a/apps/shelter/src/pages/volunteers/profile/index.tsx b/apps/shelter/src/pages/volunteers/profile/index.tsx new file mode 100644 index 00000000..dd8105c0 --- /dev/null +++ b/apps/shelter/src/pages/volunteers/profile/index.tsx @@ -0,0 +1,3 @@ +export default function VolunteersProfilePage() { + return

VolunteersProfilePage

; +} diff --git a/apps/shelter/src/pages/volunteers/search/_components/FilterGroup.tsx b/apps/shelter/src/pages/volunteers/search/_components/FilterGroup.tsx new file mode 100644 index 00000000..505db8ef --- /dev/null +++ b/apps/shelter/src/pages/volunteers/search/_components/FilterGroup.tsx @@ -0,0 +1,30 @@ +import { Flex } from '@chakra-ui/react'; +import { ReactNode } from 'react'; + +type FilterGroupProps = { + children: ReactNode; +}; + +export default function FilterGroup({ children }: FilterGroupProps) { + return ( + + + {children} + + + ); +} diff --git a/apps/shelter/src/pages/volunteers/search/_components/FilterSelect.tsx b/apps/shelter/src/pages/volunteers/search/_components/FilterSelect.tsx new file mode 100644 index 00000000..db497d10 --- /dev/null +++ b/apps/shelter/src/pages/volunteers/search/_components/FilterSelect.tsx @@ -0,0 +1,9 @@ +import { Select, SelectProps } from '@chakra-ui/react'; + +export default function FilterSelect({ children, ...props }: SelectProps) { + return ( + + ); +} diff --git a/apps/shelter/src/pages/volunteers/search/_constants/filter.ts b/apps/shelter/src/pages/volunteers/search/_constants/filter.ts new file mode 100644 index 00000000..be339dcf --- /dev/null +++ b/apps/shelter/src/pages/volunteers/search/_constants/filter.ts @@ -0,0 +1,16 @@ +export const PERIOD = { + WITHIN_ONE_DAY: 'previousDay', + WITHIN_ONE_WEEK: 'previousWeek', + WITHIN_ONE_MONTH: 'previousMonth', + CUSTOM_PERIOD: 'customPeriod', +} as const; + +export const RECRUITMENT_STATUS = { + IS_OPEN: 'isOpen', + IS_CLOSED: 'isClosed', +} as const; + +export const CATEGORY = { + TITLE: 'title', + CONTENT: 'content', +} as const; diff --git a/apps/shelter/src/pages/volunteers/search/_hooks/useSearchFilter.ts b/apps/shelter/src/pages/volunteers/search/_hooks/useSearchFilter.ts new file mode 100644 index 00000000..43658a7e --- /dev/null +++ b/apps/shelter/src/pages/volunteers/search/_hooks/useSearchFilter.ts @@ -0,0 +1,62 @@ +import { useEffect, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import useSearchHeaderStore from 'shared/store/searchHeaderStore'; + +const parseSearchParams = (searchParams: URLSearchParams) => { + const params: Record = {}; + for (const [key, value] of searchParams) { + params[key] = value; + } + return params; +}; + +export const useSearchFilter = ( + onSearch: (filter: SearchFilter) => void, +): [SearchFilter, (filter: SearchFilter) => void] => { + const setKeyword = useSearchHeaderStore((state) => state.setKeyword); + + const [filter, setFilter] = useState({} as SearchFilter); + const [searchParams, setSearchParams] = useSearchParams(); + + useEffect(() => { + if (Object.keys(filter).length === 0) { + return; + } + + onSearch(filter); + }, [filter, onSearch]); + + useEffect(() => { + if (searchParams.size === 0) { + return; + } + + const params = parseSearchParams(searchParams); + + if (params.keyword) { + setKeyword(params.keyword); + } + + setFilter(params as SearchFilter); + }, [searchParams, setKeyword]); + + const createSearchParams = (filter: SearchFilter) => { + const keys = Object.keys(filter); + + return keys.reduce( + (params, key) => { + return filter[key as keyof SearchFilter] + ? { ...params, [key]: String(filter[key as keyof SearchFilter]) } + : params; + }, + {} as Record, + ); + }; + + const setFilterValue = (filter: SearchFilter) => { + setSearchParams(createSearchParams(filter), { replace: true }); + setFilter(filter); + }; + + return [filter, setFilterValue]; +}; diff --git a/apps/shelter/src/pages/volunteers/search/_hooks/useVolunteerSearch.ts b/apps/shelter/src/pages/volunteers/search/_hooks/useVolunteerSearch.ts new file mode 100644 index 00000000..a36503f9 --- /dev/null +++ b/apps/shelter/src/pages/volunteers/search/_hooks/useVolunteerSearch.ts @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react'; +import useSearchHeaderStore from 'shared/store/searchHeaderStore'; + +import { useVolunteerSearchFilter } from '@/pages/volunteers/search/_hooks/useVolunteerSearchFilter'; + +const DUMMY_RECRUITMENT = { + recruitmentId: 1, + recruitmentTitle: '봉사자를 모집합니다', + recruitmentStartTime: '2021-11-08T11:44:30.327959', + recruitmentEndTime: '2021-11-08T11:44:30.327959', + recruitmentDeadline: '2021-11-08T11:44:30.327959', + recruitmentIsClosed: false, + recruitmentApplicantCount: 15, + recruitmentCapacity: 15, +}; + +export const useVolunteerSearch = () => { + const [recruitmentList, setRecruitmentList] = useState([]); + + useEffect(() => { + setRecruitmentList(Array.from({ length: 10 }, () => DUMMY_RECRUITMENT)); + }, []); + + const searchAPI = async () => { + // TODO: API call + }; + + const { + isSearched, + setKeywordFilter, + volunteerSearchFilter, + setVolunteerSearchFilter, + } = useVolunteerSearchFilter(searchAPI); + + const [setOnSearch, setKeyword] = useSearchHeaderStore((state) => [ + state.setOnSearch, + state.setKeyword, + ]); + + useEffect(() => { + setOnSearch(setKeywordFilter); + + return () => { + setKeyword(''); + setOnSearch(() => {}); + }; + }, []); + + return { + isSearched, + recruitmentList, + volunteerSearchFilter, + setVolunteerSearchFilter, + }; +}; diff --git a/apps/shelter/src/pages/volunteers/search/_hooks/useVolunteerSearchFilter.ts b/apps/shelter/src/pages/volunteers/search/_hooks/useVolunteerSearchFilter.ts new file mode 100644 index 00000000..0132c3b7 --- /dev/null +++ b/apps/shelter/src/pages/volunteers/search/_hooks/useVolunteerSearchFilter.ts @@ -0,0 +1,89 @@ +import { ChangeEvent, useEffect, useState } from 'react'; + +import { useSearchFilter } from '@/pages/volunteers/search/_hooks/useSearchFilter'; +import { + Category, + RecruitmentStatus, + SearchFilter, + VolunteerSearchFilter, +} from '@/pages/volunteers/search/_types/filter'; +import { + createCategorySearchFilter, + createPeriodSearchFilter, + createRecruitmentStatusSearchFilter, + createVolunteerSearchFilter, +} from '@/pages/volunteers/search/_utils/createFilter'; + +export const useVolunteerSearchFilter = (searchAPI: () => void) => { + const search = (filter: SearchFilter) => { + console.log('filter', filter); + // TODO: filter 를 통해 request 객체 가공하기 + // request = createSearchRequest(filter); + // searchAPI(request); + searchAPI(); + }; + + const [searchFilter, setSearchFilter] = useSearchFilter(search); + + const [volunteerSearchFilter, setVolunteerSearchFilter] = + useState({} as VolunteerSearchFilter); + + useEffect(() => { + const newVolunteerSearchFilter = createVolunteerSearchFilter(searchFilter); + + setVolunteerSearchFilter({ + ...volunteerSearchFilter, + ...newVolunteerSearchFilter, + }); + }, [searchFilter]); + + const setPeriod = (event: ChangeEvent) => { + const { value } = event.target; + + setVolunteerSearchFilter({ + ...volunteerSearchFilter, + period: value, + }); + + const newFilter = createPeriodSearchFilter(value); + + setSearchFilter({ ...searchFilter, ...newFilter }); + }; + + const setRecruitmentStatus = (event: ChangeEvent) => { + const { value } = event.target; + + setVolunteerSearchFilter({ + ...volunteerSearchFilter, + recruitmentStatus: value as RecruitmentStatus, + }); + + const newFilter = createRecruitmentStatusSearchFilter(value); + + setSearchFilter({ ...searchFilter, ...newFilter }); + }; + + const setCategory = (event: ChangeEvent) => { + const { value } = event.target; + + setVolunteerSearchFilter({ + ...volunteerSearchFilter, + category: value as Category, + }); + + const newFilter = createCategorySearchFilter(value); + + setSearchFilter({ ...searchFilter, ...newFilter }); + }; + + const setKeywordFilter = (keyword: string) => { + setSearchFilter({ ...searchFilter, keyword }); + }; + + return { + isSearched: Boolean(searchFilter.keyword), + setKeywordFilter, + volunteerSearchFilter, + setVolunteerSearchFilter: { setPeriod, setRecruitmentStatus, setCategory }, + }; +}; diff --git a/apps/shelter/src/pages/volunteers/search/_types/filter.ts b/apps/shelter/src/pages/volunteers/search/_types/filter.ts new file mode 100644 index 00000000..cb0d0896 --- /dev/null +++ b/apps/shelter/src/pages/volunteers/search/_types/filter.ts @@ -0,0 +1,24 @@ +import { + CATEGORY, + RECRUITMENT_STATUS, +} from '@/pages/volunteers/search/_constants/filter'; + +export type SearchFilter = Partial<{ + keyword: string; + startDate: string; + endDate: string; + isClosed: string; + content: string; + title: string; +}>; + +export type RecruitmentStatus = + (typeof RECRUITMENT_STATUS)[keyof typeof RECRUITMENT_STATUS]; + +export type Category = (typeof CATEGORY)[keyof typeof CATEGORY]; + +export type VolunteerSearchFilter = Partial<{ + period: string; + recruitmentStatus: RecruitmentStatus; + category: Category; +}>; diff --git a/apps/shelter/src/pages/volunteers/search/_utils/createFilter.ts b/apps/shelter/src/pages/volunteers/search/_utils/createFilter.ts new file mode 100644 index 00000000..0de29e3f --- /dev/null +++ b/apps/shelter/src/pages/volunteers/search/_utils/createFilter.ts @@ -0,0 +1,121 @@ +import { MILISECONDS } from 'shared/constants/date'; +import { createFormattedTime, isSameDay } from 'shared/utils/date'; + +import { + CATEGORY, + PERIOD, + RECRUITMENT_STATUS, +} from '@/pages/volunteers/search/_constants/filter'; +import { + SearchFilter, + VolunteerSearchFilter, +} from '@/pages/volunteers/search/_types/filter'; + +const createPeriod = (startDate?: string, endDate?: string) => { + if (!startDate) { + return endDate; + } + if (!endDate) { + return startDate; + } + + const start = new Date(startDate); + const end = new Date(endDate); + const today = new Date(); + + if (isSameDay(start, today)) { + const diff = (end.getTime() - start.getTime()) / 1000; + + if (diff === MILISECONDS.DAY) { + return PERIOD.WITHIN_ONE_DAY; + } + if (diff === MILISECONDS.DAY * 7) { + return PERIOD.WITHIN_ONE_WEEK; + } + if (diff === MILISECONDS.MONTH) { + return PERIOD.WITHIN_ONE_MONTH; + } + } + + return `${startDate}~${endDate}`; +}; + +export const createVolunteerSearchFilter = ( + filter: SearchFilter, +): VolunteerSearchFilter => { + const { startDate, endDate, isClosed, title, content } = filter; + + const volunteerSearchFilter: VolunteerSearchFilter = {}; + + if (startDate || endDate) { + const period = createPeriod(startDate, endDate); + volunteerSearchFilter.period = period; + } + + if (isClosed) { + volunteerSearchFilter.recruitmentStatus = + isClosed === String(true) ? 'isClosed' : 'isOpen'; + } + + if (title) { + volunteerSearchFilter.category = 'title'; + } + + if (content) { + volunteerSearchFilter.category = 'content'; + } + + return volunteerSearchFilter; +}; + +export const createPeriodSearchFilter = (value: string): SearchFilter => { + if (Object.values(PERIOD).every((period) => period !== value)) { + return { startDate: undefined, endDate: undefined }; + } + + const startDate = new Date(); + const endDate = new Date(); + + if (value === PERIOD.WITHIN_ONE_DAY) { + endDate.setDate(startDate.getDate() + 1); + } + + if (value === PERIOD.WITHIN_ONE_WEEK) { + endDate.setDate(startDate.getDate() + 7); + } + + if (value === PERIOD.WITHIN_ONE_MONTH) { + endDate.setMonth(startDate.getMonth() + 1); + } + + return { + startDate: createFormattedTime(startDate, 'YYYY-MM-DD'), + endDate: createFormattedTime(endDate, 'YYYY-MM-DD'), + }; +}; + +export const createRecruitmentStatusSearchFilter = ( + value: string, +): SearchFilter => { + if (value === RECRUITMENT_STATUS.IS_CLOSED) { + return { isClosed: String(true) }; + } + + if (value === RECRUITMENT_STATUS.IS_OPEN) { + return { isClosed: String(false) }; + } + + return { isClosed: undefined }; +}; + +export const createCategorySearchFilter = (value: string): SearchFilter => { + if (value === CATEGORY.TITLE) { + return { title: String(true), content: undefined }; + } + + if (value === CATEGORY.CONTENT) { + return { title: undefined, content: String(true) }; + } + + return { title: undefined, content: undefined }; +}; diff --git a/apps/shelter/src/pages/volunteers/search/index.tsx b/apps/shelter/src/pages/volunteers/search/index.tsx new file mode 100644 index 00000000..01b93c8c --- /dev/null +++ b/apps/shelter/src/pages/volunteers/search/index.tsx @@ -0,0 +1,67 @@ +import { Box } from '@chakra-ui/react'; + +import RecruitItem from '@/pages/volunteers/_components/RecruitItem'; +import FilterGroup from '@/pages/volunteers/search/_components/FilterGroup'; +import FilterSelect from '@/pages/volunteers/search/_components/FilterSelect'; +import { + CATEGORY, + PERIOD, + RECRUITMENT_STATUS, +} from '@/pages/volunteers/search/_constants/filter'; +import { useVolunteerSearch } from '@/pages/volunteers/search/_hooks/useVolunteerSearch'; + +export default function VolunteersSearchPage() { + const { + isSearched, + recruitmentList, + volunteerSearchFilter, + setVolunteerSearchFilter, + } = useVolunteerSearch(); + + const { period, recruitmentStatus, category } = volunteerSearchFilter; + const { setPeriod, setRecruitmentStatus, setCategory } = + setVolunteerSearchFilter; + + if (!isSearched) { + return null; + } + + return ( + + + + + + + + + + + + + + + + + + {recruitmentList?.map((recruitmentItem: any) => ( + + ))} + + ); +} diff --git a/apps/shelter/src/pages/volunteers/update/index.tsx b/apps/shelter/src/pages/volunteers/update/index.tsx new file mode 100644 index 00000000..d005acc1 --- /dev/null +++ b/apps/shelter/src/pages/volunteers/update/index.tsx @@ -0,0 +1,3 @@ +export default function VolunteersUpdatePage() { + return

VolunteersUpdatePage

; +} diff --git a/apps/shelter/src/pages/volunteers/write/index.tsx b/apps/shelter/src/pages/volunteers/write/index.tsx new file mode 100644 index 00000000..66925bc1 --- /dev/null +++ b/apps/shelter/src/pages/volunteers/write/index.tsx @@ -0,0 +1,3 @@ +export default function VolunteersWritePage() { + return

VolunteersWritePage

; +} diff --git a/apps/shelter/src/routes/index.tsx b/apps/shelter/src/routes/index.tsx new file mode 100644 index 00000000..1537e753 --- /dev/null +++ b/apps/shelter/src/routes/index.tsx @@ -0,0 +1,179 @@ +import { createBrowserRouter, RouterProviderProps } from 'react-router-dom'; +import APP_TYPE from 'shared/constants/appType'; +import PAGE_TYPE from 'shared/constants/pageType'; +import Layout from 'shared/layout'; + +import PATH from '@/constants/path'; +import AnimalsPage from '@/pages/animals'; +import AnimalsDetailPage from '@/pages/animals/detail'; +import AnimalsSearchPage from '@/pages/animals/search'; +import AnimalsUpdatePage from '@/pages/animals/update'; +import AnimalsWritePage from '@/pages/animals/write'; +import ChattingsPage from '@/pages/chattings'; +import ChattingsRoomPage from '@/pages/chattings/room'; +import ManageApplyPage from '@/pages/manage/apply'; +import ManageAttendancePage from '@/pages/manage/attendance'; +import MyPage from '@/pages/my'; +import MyReviewsPage from '@/pages/my/reviews'; +import NotFoundPage from '@/pages/notfound'; +import NotificationsPage from '@/pages/notifications'; +import SettingsAccountPage from '@/pages/settings/account'; +import SettingsPasswordPage from '@/pages/settings/password'; +import SigninPage from '@/pages/signin'; +import SignupPage from '@/pages/signup'; +import VolunteersPage from '@/pages/volunteers'; +import VolunteersDetailPage from '@/pages/volunteers/detail'; +import VolunteersProfilePage from '@/pages/volunteers/profile'; +import VolunteersSearchPage from '@/pages/volunteers/search'; +import VolunteersUpdatePage from '@/pages/volunteers/update'; +import VolunteersWritePage from '@/pages/volunteers/write'; + +export const router: RouterProviderProps['router'] = createBrowserRouter([ + { + path: '/', + element: , + errorElement: , + children: [ + { + path: PATH.VOLUNTEERS.INDEX, + children: [ + { + id: PAGE_TYPE.VOLUNTEERS, + index: true, + element: , + }, + { + id: PAGE_TYPE.VOLUNTEERS_DETAIL, + path: PATH.VOLUNTEERS.DETAIL, + element: , + }, + { + id: PAGE_TYPE.VOLUNTEERS_PROFILE, + path: PATH.VOLUNTEERS.PROFILE, + element: , + }, + { + id: PAGE_TYPE.VOLUNTEERS_SEARCH, + path: PATH.VOLUNTEERS.SEARCH, + element: , + }, + { + id: PAGE_TYPE.VOLUNTEERS_WRITE, + path: PATH.VOLUNTEERS.WRITE, + element: , + }, + { + id: PAGE_TYPE.VOLUNTEERS_UPDATE, + path: PATH.VOLUNTEERS.UPDATE, + element: , + }, + ], + }, + { + path: PATH.ANIMALS.INDEX, + children: [ + { + id: PAGE_TYPE.ANIMALS, + index: true, + element: , + }, + { + id: PAGE_TYPE.ANIMALS_DETAIL, + path: PATH.ANIMALS.DETAIL, + element: , + }, + { + id: PAGE_TYPE.ANIMALS_SEARCH, + path: PATH.ANIMALS.SEARCH, + element: , + }, + { + id: PAGE_TYPE.ANIMALS_WRITE, + path: PATH.ANIMALS.WRITE, + element: , + }, + { + id: PAGE_TYPE.ANIMALS_UPDATE, + path: PATH.ANIMALS.UPDATE, + element: , + }, + ], + }, + { + path: PATH.CHATTINGS.INDEX, + children: [ + { + id: PAGE_TYPE.CHATTINGS, + index: true, + element: , + }, + { + id: PAGE_TYPE.CHATTINGS_ROOM, + path: PATH.CHATTINGS.ROOM, + element: , + }, + ], + }, + { + path: PATH.MYPAGE.INDEX, + children: [ + { + id: PAGE_TYPE.MYPAGE, + index: true, + element: , + }, + { + id: PAGE_TYPE.MYPAGE_REVIEWS, + path: PATH.MYPAGE.REVIEWS, + element: , + }, + ], + }, + { + path: PATH.SETTINGS.INDEX, + children: [ + { + id: PAGE_TYPE.SETTINGS_ACCOUNT, + path: PATH.SETTINGS.ACCOUNT, + element: , + }, + { + id: PAGE_TYPE.SETTINGS_PASSWORD, + path: PATH.SETTINGS.PASSWORD, + element: , + }, + ], + }, + { + path: PATH.MANAGE.INDEX, + children: [ + { + id: PAGE_TYPE.MANAGE_ATTENDANCE, + path: PATH.MANAGE.ATTENDANCE, + element: , + }, + { + id: PAGE_TYPE.MANAGE_APPLY, + path: PATH.MANAGE.APPLY, + element: , + }, + ], + }, + { + id: PAGE_TYPE.NOTIFICATIONS, + path: PATH.NOTIFICATIONS, + element: , + }, + { + id: PAGE_TYPE.SIGNUP, + path: PATH.SIGNUP, + element: , + }, + { + id: PAGE_TYPE.SIGNIN, + path: PATH.SIGNIN, + element: , + }, + ], + }, +]); diff --git a/apps/shelter/src/types/apis/auth.ts b/apps/shelter/src/types/apis/auth.ts new file mode 100644 index 00000000..80d03996 --- /dev/null +++ b/apps/shelter/src/types/apis/auth.ts @@ -0,0 +1,10 @@ +import { SigninRequestData } from 'shared/types/apis/auth'; + +export type SignupRequestData = SigninRequestData & { + name: string; + address: string; + addressDetail: string; + phoneNumber: string; + sparePhoneNumber: string; + isOpenedAddress: boolean; +}; diff --git a/apps/shelter/src/types/apis/shetler.ts b/apps/shelter/src/types/apis/shetler.ts new file mode 100644 index 00000000..bf14ca0a --- /dev/null +++ b/apps/shelter/src/types/apis/shetler.ts @@ -0,0 +1,11 @@ +export type ShelterInfo = { + shelterId: number; + shelterEmail: string; + shelterName: string; + shelterImageUrl: string; + shelterAddress: string; + shelterAddressDetail: string; + shelterPhoneNumber: string; + shelterSparePhoneNumber: string; + shelterIsOpenedAddress: boolean; +}; diff --git a/apps/shelter/vercel.json b/apps/shelter/vercel.json new file mode 100644 index 00000000..3a48e56b --- /dev/null +++ b/apps/shelter/vercel.json @@ -0,0 +1,3 @@ +{ + "rewrites": [{ "source": "/(.*)", "destination": "/" }] +} diff --git a/apps/volunteer/package.json b/apps/volunteer/package.json index a13091cf..c506a9a6 100644 --- a/apps/volunteer/package.json +++ b/apps/volunteer/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "tsc && vite build", "dev": "vite", - "lint": "eslint \"src/**/*.ts\"", + "lint": "eslint \"src/**/*.{ts,tsx}\"", "preview": "vite preview" }, "dependencies": { @@ -24,7 +24,7 @@ "react-error-boundary": "^4.0.11", "react-hook-form": "^7.47.0", "react-router-dom": "^6.17.0", - "ui": "workspace:*", + "shared": "workspace:*", "zod": "^3.22.4", "zustand": "^4.4.4" }, @@ -35,8 +35,12 @@ "@vitejs/plugin-react-swc": "^3.4.0", "eslint": "^8.45.0", "eslint-config-custom": "workspace:*", + "msw": "^2.0.1", "tsconfig": "workspace:*", "typescript": "^5.0.2", "vite": "^4.4.5" + }, + "msw": { + "workerDirectory": "public" } -} +} \ No newline at end of file diff --git a/apps/volunteer/public/mockServiceWorker.js b/apps/volunteer/public/mockServiceWorker.js new file mode 100644 index 00000000..3c94cff7 --- /dev/null +++ b/apps/volunteer/public/mockServiceWorker.js @@ -0,0 +1,292 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (2.0.5). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '0877fcdc026242810f5bfde0d7178db4' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + // When performing original requests, response body will + // always be a ReadableStream, even for 204 responses. + // But when creating a new Response instance on the client, + // the body for a 204 response must be null. + const responseBody = response.status === 204 ? null : responseClone.body + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseBody, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseBody], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + const headers = Object.fromEntries(requestClone.headers.entries()) + + // Remove internal MSW request header so the passthrough request + // complies with any potential CORS preflight checks on the server. + // Some servers forbid unknown request headers. + delete headers['x-msw-intention'] + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Bypass requests with the explicit bypass header. + // Such requests can be issued by "ctx.fetch()". + const mswIntention = request.headers.get('x-msw-intention') + if (['bypass', 'passthrough'].includes(mswIntention)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'MOCK_NOT_FOUND': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} diff --git a/apps/volunteer/src/App.tsx b/apps/volunteer/src/App.tsx index 775be3fd..e1198d0a 100644 --- a/apps/volunteer/src/App.tsx +++ b/apps/volunteer/src/App.tsx @@ -1,12 +1,22 @@ import { ChakraProvider } from '@chakra-ui/react'; -import { CustomButton, Header } from 'ui'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { RouterProvider } from 'react-router-dom'; +import Fonts from 'shared/fonts'; +import theme from 'shared/theme'; + +import { router } from '@/routes'; + +const queryClient = new QueryClient(); export default function App() { return ( - -
- 봉사자 어플리케이션 - - + + + + + + + ); } diff --git a/apps/volunteer/src/apis/animal.ts b/apps/volunteer/src/apis/animal.ts new file mode 100644 index 00000000..218c55ad --- /dev/null +++ b/apps/volunteer/src/apis/animal.ts @@ -0,0 +1,55 @@ +import axiosInstance from 'shared/apis/axiosInstance'; + +type PageInfo = { + totalElements: number; + hasNext: boolean; +}; + +type Animal = { + animalId: number; + animalName: string; + shelterName: string; + shelterAddress: string; + animalImage: string; +}; + +export const searchVolunteerAnimals = () => + axiosInstance.get< + { + pageInfo: PageInfo; + animals: Animal[]; + }, + { + type: string; + gender: string; + isNeutered: boolean; + active: string; + size: string; + age: string; + pageNumber: number; + pageSize: number; + } + >('/volunteers/animals'); + +type Shelter = { + shelterId: number; + name: string; + imageUrl: string; + email: string; + address: string; +}; + +export const getVolunteerAnimalDetail = (animalId: number) => { + return axiosInstance.get<{ + name: string; + birthDate: string; + breed: string; + gender: string; + isNeutered: boolean; + active: string; + weight: number; + information: string; + animalImageUrls: string[]; + shelter: Shelter; + }>(`/volunteers/animals/${animalId}`); +}; diff --git a/apps/volunteer/src/apis/auth.ts b/apps/volunteer/src/apis/auth.ts new file mode 100644 index 00000000..d1c91e9f --- /dev/null +++ b/apps/volunteer/src/apis/auth.ts @@ -0,0 +1,26 @@ +import axiosInstance from 'shared/apis/axiosInstance'; +import type { + checkDuplicatedEmailRequestData, + checkDuplicatedEmailResponseData, + SigninRequestData, + SigninResponseData, +} from 'shared/types/apis/auth'; + +import { SignupRequestData } from '@/types/apis/auth'; + +export const signinVolunteer = async (data: SigninRequestData) => + await axiosInstance.post( + '/auth/volunteers/login', + data, + ); + +export const signupVolunteer = async (data: SignupRequestData) => + await axiosInstance.post('/volunteers', data); + +export const checkDuplicatedVolunteerEmail = async (email: string) => + await axiosInstance.post< + checkDuplicatedEmailResponseData, + checkDuplicatedEmailRequestData + >('/volunteers/email', { + email, + }); diff --git a/apps/volunteer/src/apis/recruitment.ts b/apps/volunteer/src/apis/recruitment.ts new file mode 100644 index 00000000..41afd77f --- /dev/null +++ b/apps/volunteer/src/apis/recruitment.ts @@ -0,0 +1,46 @@ +import axiosInstance from 'shared/apis/axiosInstance'; + +export const ApplyRecruitments = (recruitmentId: string) => + axiosInstance.post(`/recruitments/${recruitmentId}/apply`); + +type PageInfo = { + totalElements: number; + hasNext: boolean; +}; + +type Recruitment = { + recruitmentId: number; + recruitmentTitle: string; + recruitmentStartTime: string; + recruitmentEndTime: string; + recruitmentIsClosed: boolean; + recruitmentApplicantCount: number; + recruitmentCapacity: number; + shelterName: string; + shelterImageUrl: string; +}; + +type RecruitSearchParams = { + keyword: string; + startDate: string; + endDate: string; + isClosed: boolean; + content: boolean; + title: boolean; + shelterName: boolean; + pageNumber: number; + pageSize: number; +}; + +export const getRecruitments = ( + recruitSearchParams: Partial, +) => + axiosInstance.get< + { + pageInfo: PageInfo; + recruitments: Recruitment[]; + }, + RecruitSearchParams + >('/recruitments', { + params: recruitSearchParams, + }); diff --git a/apps/volunteer/src/apis/review.ts b/apps/volunteer/src/apis/review.ts new file mode 100644 index 00000000..5be72188 --- /dev/null +++ b/apps/volunteer/src/apis/review.ts @@ -0,0 +1,76 @@ +import axiosInstance from 'shared/apis/axiosInstance'; + +type NewReviewParams = { + applicantId: number; + content: string; + imageUrls: string[]; +}; + +type UpdatedReviewParams = { + content: string; + imageUrls: string[]; +}; + +export const getVolunteerReviewDetail = (reviewId: number) => + axiosInstance.get<{ + reviewId: number; + content: string; + imageUrls: string[]; + }>(`/reviews/${reviewId}`); + +export const createVolunteerNewReview = (newReviewParams: NewReviewParams) => + axiosInstance.post( + '/volunteers/reviews', + newReviewParams, + ); + +export const updateVolunteerReview = ( + reviewId: string, + updatedReviewParams: UpdatedReviewParams, +) => + axiosInstance.patch( + `/volunteers/reviews/${reviewId}`, + updatedReviewParams, + ); + +export const deleteVolunteerReview = (reviewId: string) => + axiosInstance.delete(`/volunteers/reviews/${reviewId}`); + +type ReviewOnShelterParams = { + pageNumber: number; + pageSize: number; +}; + +type Review = { + reviewId: number; + volunteerEmail: string; + volunteerTemperature: number; + reviewCreatedAt: string; + reviewComtent: string; + reviewImageUrls: string[]; +}; + +type PageInfo = { + totalElements: number; + hasNext: boolean; +}; + +type ReviewOnShelterResponse = { + pageInfo: PageInfo; + reviews: Review[]; +}; + +export const getVolunteerReviewsOnShelter = ( + shelterId: number, + pageNumber: number, + pageSize: number, +) => + axiosInstance.get( + `/shelters/${shelterId}/reviews`, + { + params: { + pageNumber, + pageSize, + }, + }, + ); diff --git a/apps/volunteer/src/apis/shelter.ts b/apps/volunteer/src/apis/shelter.ts new file mode 100644 index 00000000..7ca40791 --- /dev/null +++ b/apps/volunteer/src/apis/shelter.ts @@ -0,0 +1,21 @@ +import axiosInstance from 'shared/apis/axiosInstance'; + +export const getSimpleShelterProfile = (shelterId: number) => + axiosInstance.get<{ + shelterName: string; + shelterImageUrl: string; + shelterAddress: string; + shelterEmail: string; + }>(`/shelters/${shelterId}/profile/simple`); + +export const getShelterProfileDetail = (shelterId: number) => + axiosInstance.get<{ + shelterId: number; + shelterName: string; + shelterEmail: string; + shelterImageUrl: string; + shelterAddress: string; + shelterAddressDetail: string; + shelterPhoneNumber: string; + shelterSparePhoneNumber: string; + }>(`/shelters/${shelterId}/profile`); diff --git a/apps/volunteer/src/apis/volunteer.ts b/apps/volunteer/src/apis/volunteer.ts new file mode 100644 index 00000000..51f38d75 --- /dev/null +++ b/apps/volunteer/src/apis/volunteer.ts @@ -0,0 +1,66 @@ +import axiosInstance from 'shared/apis/axiosInstance'; + +type MyInfoResponse = { + volunteerId: string; + volunteerEmail: string; + volunteerName: string; + volunteerBirthDate: string; + volunteerPhoneNumber: string; + volunteerTemperture: number; + volunteerCount: number; + volunteerImageUrl: string; + volunteerGender: 'FEMAIL' | 'MALE'; +}; + +export const getMyVolunteerInfo = () => + axiosInstance.get('/volunteers/me'); + +type PasswordUpdateParams = { + newPassword: string; + oldPassword: string; +}; + +export const updateVolunteerPassword = ( + passwordUpdateParams: PasswordUpdateParams, +) => { + return axiosInstance.patch( + '/volunteers/me/password', + passwordUpdateParams, + ); +}; + +type UpdateUserInfoParams = { + name: string; + gender: string; + birthData: string; + phoneNumber: string; + imageUrl: string; +}; + +export const updateVolunteerUserInfo = ( + updateUserInfoParams: UpdateUserInfoParams, +) => + axiosInstance.patch( + '/volunteers/me', + updateUserInfoParams, + ); + +type Applicant = { + recruitmentId: number; + recruitmentTitle: string; + recruitmentStartTime: string; + shelterName: string; + applicantId: number; + applicantStatus: string; + applicantIsWritedReview: boolean; +}; + +type ApplicantsResponse = { + applicants: Applicant[]; +}; + +//봉사자가 신청한 봉사 리스트 조회 +export const getVolunteerApplicantList = () => + axiosInstance.get('/volunteers/applicants'); + +//TODO 봉사자가 작성한 후기 리스트 조회 diff --git a/apps/volunteer/src/constants/gender.ts b/apps/volunteer/src/constants/gender.ts new file mode 100644 index 00000000..bc768cfb --- /dev/null +++ b/apps/volunteer/src/constants/gender.ts @@ -0,0 +1,9 @@ +export const PERAON_GENDER_ENG = { + FEMALE: 'FEMALE', + MALE: 'MALE', +} as const; + +export const PERAON_GENDER_KOR = { + FEMALE: '여성', + MALE: '남성', +} as const; diff --git a/apps/volunteer/src/constants/path.ts b/apps/volunteer/src/constants/path.ts new file mode 100644 index 00000000..02c58b0d --- /dev/null +++ b/apps/volunteer/src/constants/path.ts @@ -0,0 +1,35 @@ +const PATH = { + VOLUNTEERS: { + INDEX: 'volunteers', + DETAIL: ':id', + SEARCH: 'search', + }, + ANIMALS: { + INDEX: 'animals', + DETAIL: ':id', + }, + CHATTINGS: { + INDEX: 'chattings', + ROOM: 'chattings/:id', + }, + MYPAGE: { + INDEX: 'mypage', + REVIEWS: 'mypage/reviews', + }, + SETTINGS: { + INDEX: 'settings', + ACCOUNT: 'account', + PASSWORD: 'password', + }, + SHELTERS: { + INDEX: 'shelters', + PROFILE: 'profile/:id', + REVIEWS_WRITE: 'reviews/write', + REVIEWS_UPDATE: 'reviews/write/:id', + }, + NOTIFICATIONS: 'notifications', + SIGNUP: 'signup', + SIGNIN: 'signin', +}; + +export default PATH; diff --git a/apps/volunteer/src/main.tsx b/apps/volunteer/src/main.tsx index b2677542..d7ba7b9a 100644 --- a/apps/volunteer/src/main.tsx +++ b/apps/volunteer/src/main.tsx @@ -3,8 +3,22 @@ import ReactDOM from 'react-dom/client'; import App from './App.tsx'; -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , -); +async function deferRender() { + if (import.meta.env.MODE !== 'development') { + return; + } + + const { worker } = await import('./mocks/browser'); + + // `worker.start()` returns a Promise that resolves + // once the Service Worker is up and ready to intercept requests. + return worker.start(); +} + +deferRender().then(() => { + ReactDOM.createRoot(document.getElementById('root')!).render( + + + , + ); +}); diff --git a/apps/volunteer/src/mocks/browser.ts b/apps/volunteer/src/mocks/browser.ts new file mode 100644 index 00000000..d69b3505 --- /dev/null +++ b/apps/volunteer/src/mocks/browser.ts @@ -0,0 +1,5 @@ +import { setupWorker } from 'msw/browser'; + +import { handlers } from './handlers/auth'; + +export const worker = setupWorker(...handlers); diff --git a/apps/volunteer/src/mocks/handlers/auth.ts b/apps/volunteer/src/mocks/handlers/auth.ts new file mode 100644 index 00000000..e04c4df5 --- /dev/null +++ b/apps/volunteer/src/mocks/handlers/auth.ts @@ -0,0 +1,68 @@ +import { delay, http, HttpResponse } from 'msw'; + +export const handlers = [ + http.post('/auth/volunteers/login', async () => { + await delay(200); + return HttpResponse.json( + { + accessToken: 'accessToken', + userId: 1, + role: 'ROLE_VOLUNTEER', + }, + { status: 200 }, + ); + }), + http.post('/auth/volunteers/login', async () => { + await delay(200); + return HttpResponse.json( + { + errorCode: 'AF002', + message: '이메일/비밀번호가 올바르지 않습니다', + }, + { status: 400 }, + ); + }), + http.post('/auth/volunteers/login', async () => { + await delay(200); + return HttpResponse.json( + { + errorCode: 'AF001', + message: '잘못된 입력값입니다', + }, + { status: 400 }, + ); + }), + http.post('/volunteers', async () => { + await delay(200); + return HttpResponse.json({}, { status: 200 }); + }), + http.post('/volunteers', async () => { + await delay(200); + return HttpResponse.json( + { + errorCode: 'AF001', + message: '{입력값}이 잘못되었습니다.', + }, + { status: 400 }, + ); + }), + http.post('/volunteers/email', async () => { + await delay(200); + return HttpResponse.json( + { + isDuplicated: false, + }, + { status: 200 }, + ); + }), + http.post('/volunteers/email', async () => { + await delay(200); + return HttpResponse.json( + { + errorCode: 'AF001', + message: '잘못된 입력값입니다', + }, + { status: 400 }, + ); + }), +]; diff --git a/apps/volunteer/src/pages/animals/detail/index.tsx b/apps/volunteer/src/pages/animals/detail/index.tsx new file mode 100644 index 00000000..49cf55b5 --- /dev/null +++ b/apps/volunteer/src/pages/animals/detail/index.tsx @@ -0,0 +1,3 @@ +export default function AnimalsDetailPage() { + return

AnimalsDetailPage

; +} diff --git a/apps/volunteer/src/pages/animals/index.tsx b/apps/volunteer/src/pages/animals/index.tsx new file mode 100644 index 00000000..c11ddc43 --- /dev/null +++ b/apps/volunteer/src/pages/animals/index.tsx @@ -0,0 +1,3 @@ +export default function AnimalsPage() { + return

AnimalsPage

; +} diff --git a/apps/volunteer/src/pages/chattings/index.tsx b/apps/volunteer/src/pages/chattings/index.tsx new file mode 100644 index 00000000..a48ead0b --- /dev/null +++ b/apps/volunteer/src/pages/chattings/index.tsx @@ -0,0 +1,3 @@ +export default function ChattingsPage() { + return

ChattingsPage

; +} diff --git a/apps/volunteer/src/pages/chattings/room/index.tsx b/apps/volunteer/src/pages/chattings/room/index.tsx new file mode 100644 index 00000000..55fc00e1 --- /dev/null +++ b/apps/volunteer/src/pages/chattings/room/index.tsx @@ -0,0 +1,3 @@ +export default function ChattingsRoomPage() { + return

ChattingsRoomPage

; +} diff --git a/apps/volunteer/src/pages/my/index.tsx b/apps/volunteer/src/pages/my/index.tsx new file mode 100644 index 00000000..1d167116 --- /dev/null +++ b/apps/volunteer/src/pages/my/index.tsx @@ -0,0 +1,3 @@ +export default function MyPage() { + return

MyPage

; +} diff --git a/apps/volunteer/src/pages/notfound/index.tsx b/apps/volunteer/src/pages/notfound/index.tsx new file mode 100644 index 00000000..5ba55266 --- /dev/null +++ b/apps/volunteer/src/pages/notfound/index.tsx @@ -0,0 +1,3 @@ +export default function NotFoundPage() { + return

NotFoundPage

; +} diff --git a/apps/volunteer/src/pages/notifications/index.tsx b/apps/volunteer/src/pages/notifications/index.tsx new file mode 100644 index 00000000..b7e24e5d --- /dev/null +++ b/apps/volunteer/src/pages/notifications/index.tsx @@ -0,0 +1,3 @@ +export default function NotificationsPage() { + return

NotificationsPage

; +} diff --git a/apps/volunteer/src/pages/settings/account/index.tsx b/apps/volunteer/src/pages/settings/account/index.tsx new file mode 100644 index 00000000..92db107c --- /dev/null +++ b/apps/volunteer/src/pages/settings/account/index.tsx @@ -0,0 +1,3 @@ +export default function SettingsAccountPage() { + return

SettingsAccountPage

; +} diff --git a/apps/volunteer/src/pages/settings/index.tsx b/apps/volunteer/src/pages/settings/index.tsx new file mode 100644 index 00000000..7dbf8d14 --- /dev/null +++ b/apps/volunteer/src/pages/settings/index.tsx @@ -0,0 +1,3 @@ +export default function SettingsPage() { + return

SettingsPage

; +} diff --git a/apps/volunteer/src/pages/settings/password/index.tsx b/apps/volunteer/src/pages/settings/password/index.tsx new file mode 100644 index 00000000..1d504fa2 --- /dev/null +++ b/apps/volunteer/src/pages/settings/password/index.tsx @@ -0,0 +1,3 @@ +export default function SettingsPasswordPage() { + return

SettingsPasswordPage

; +} diff --git a/apps/volunteer/src/pages/shelters/profile/index.tsx b/apps/volunteer/src/pages/shelters/profile/index.tsx new file mode 100644 index 00000000..81a181b4 --- /dev/null +++ b/apps/volunteer/src/pages/shelters/profile/index.tsx @@ -0,0 +1,3 @@ +export default function SheltersProfilePage() { + return

SheltersProfilePage

; +} diff --git a/apps/volunteer/src/pages/shelters/reviews/update/index.tsx b/apps/volunteer/src/pages/shelters/reviews/update/index.tsx new file mode 100644 index 00000000..8c8515e9 --- /dev/null +++ b/apps/volunteer/src/pages/shelters/reviews/update/index.tsx @@ -0,0 +1,3 @@ +export default function SheltersReviewsUpdatePage() { + return

SheltersReviewsUpdatePage

; +} diff --git a/apps/volunteer/src/pages/shelters/reviews/write/index.tsx b/apps/volunteer/src/pages/shelters/reviews/write/index.tsx new file mode 100644 index 00000000..f7e01d55 --- /dev/null +++ b/apps/volunteer/src/pages/shelters/reviews/write/index.tsx @@ -0,0 +1,3 @@ +export default function SheltersReviewsWritePage() { + return

SheltersReviewsWritePage

; +} diff --git a/apps/volunteer/src/pages/signin/index.tsx b/apps/volunteer/src/pages/signin/index.tsx new file mode 100644 index 00000000..62ee2682 --- /dev/null +++ b/apps/volunteer/src/pages/signin/index.tsx @@ -0,0 +1,193 @@ +import { + Box, + Button, + Center, + FormControl, + FormErrorMessage, + FormLabel, + Icon, + Image, + Input, + InputGroup, + InputRightElement, + useToast, + VStack, +} from '@chakra-ui/react'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useMutation } from '@tanstack/react-query'; +import type { AxiosResponse } from 'axios'; +import { AxiosError } from 'axios'; +import { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { useNavigate } from 'react-router-dom'; +import AnimalfriendsLogo from 'shared/assets/image-anifriends-logo.png'; +import IoEyeOff from 'shared/assets/IoEyeOff'; +import IoEyeSharp from 'shared/assets/IoEyeSharp'; +import useToggle from 'shared/hooks/useToggle'; +import type { + SigninRequestData, + SigninResponseData, +} from 'shared/types/apis/auth'; +import { ErrorResponseData } from 'shared/types/apis/error'; +import * as z from 'zod'; + +import { signinVolunteer } from '@/apis/auth'; +import PATH from '@/constants/path'; + +type Schema = z.infer; + +const schema = z.object({ + email: z + .string() + .min(1, '이메일이 입려되지 않았습니다') + .email('유효하지 않은 이메일입니다'), + password: z.string().min(1, '비밀번호가 입력되지 않았습니다'), +}); + +export default function SigninPage() { + const navigate = useNavigate(); + const toast = useToast(); + const [isShow, toggleInputShow] = useToggle(); + const { + register, + handleSubmit, + formState: { errors }, + setFocus, + } = useForm({ + resolver: zodResolver(schema), + }); + const { mutate } = useMutation< + AxiosResponse, + AxiosError, + SigninRequestData + >({ + mutationFn: (data) => signinVolunteer(data), + onSuccess: () => { + navigate(`/${PATH.VOLUNTEERS.INDEX}`); + }, + onError: (error) => { + toast({ + position: 'top', + description: error.response?.data.message, + status: 'error', + duration: 1500, + }); + + setFocus('email'); + }, + }); + + const goSignupPage = () => { + navigate(`/${PATH.SIGNUP}`); + }; + + const goVolunteersPage = () => { + navigate(`/${PATH.VOLUNTEERS.INDEX}`); + }; + + const onSubmit = async (data: Schema) => { + mutate(data); + }; + + useEffect(() => setFocus('email'), [setFocus]); + + return ( + +
+ +
+
+ + 이메일 + + + {errors.email && errors.email.message} + + + + 비밀번호 + + + + + + + + {errors.password && errors.password.message} + + + + + + + +
+
+ ); +} diff --git a/apps/volunteer/src/pages/signup/index.tsx b/apps/volunteer/src/pages/signup/index.tsx new file mode 100644 index 00000000..63058bbd --- /dev/null +++ b/apps/volunteer/src/pages/signup/index.tsx @@ -0,0 +1,263 @@ +import { + Box, + Button, + Center, + FormControl, + FormErrorMessage, + FormHelperText, + FormLabel, + Icon, + Image, + Input, + InputGroup, + InputRightAddon, + InputRightElement, + VStack, +} from '@chakra-ui/react'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Controller, useForm } from 'react-hook-form'; +import AnimalfriendsLogo from 'shared/assets/image-anifriends-logo.png'; +import IoEyeOff from 'shared/assets/IoEyeOff'; +import IoEyeSharp from 'shared/assets/IoEyeSharp'; +import RadioGroup from 'shared/components/RadioGroup'; +import useToggle from 'shared/hooks/useToggle'; +import * as z from 'zod'; + +import { PersonGenderEng, PersonGenderKor } from '@/types/gender'; + +type Schema = z.infer; + +const schema = z + .object({ + email: z + .string() + .min(1, '이메일은 필수 정보입니다') + .email('유효하지 않은 이메일입니다'), + password: z + .string() + .regex( + /^(?=.*[!@#$%^&*()\-_=+[\]\\|{};:'",<.>/?]+)(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/, + '비밀번호는 필수 정보입니다(8자 이상)', + ), + passwordConfirm: z.string().min(1, '비밀번호 확인 정보는 필수입니다'), + name: z.string().min(1, '이름 정보는 필수입니다'), + date: z + .string() + .min(1, '생년월일 정보는 필수입니다') + .refine( + (val) => new Date(val) < new Date(), + `${new Date() + .toLocaleDateString('ko-KR') + .split('') + .filter((v) => v !== ' ') + .join('')} 이전으로 선택해주세요`, + ), + phoneNumber: z + .string() + .min(1, '전화번호 정보는 필수입니다') + .refine( + (val) => !Number.isNaN(Number(val)), + '전화번호 형식은 숫자입니다', + ), + gender: z.enum(['FEMALE', 'MALE']), + }) + .refine(({ password, passwordConfirm }) => password === passwordConfirm, { + message: '비밀번호가 일치하지 않습니다', + path: ['passwordConfirm'], + }); + +export default function SignupPage() { + const [isPasswordShow, togglePasswordShow] = useToggle(); + const [isPasswordConfirmShow, togglePasswordConfirmShow] = useToggle(); + const { + register, + handleSubmit, + formState: { errors }, + control, + } = useForm({ + resolver: zodResolver(schema), + }); + + const onSubmit = (data: Schema) => { + console.log(data); + }; + + return ( + +
+ +
+
+ + 이메일 + + + + 확인 + + + + {errors.email && errors.email.message} + + + + 비밀번호 + + + + + + + + 영대문자, 영소문자, 숫자, 특수문자 조합 8자리 이상 +
+ 특수문자: {`!@#$%^&*()-_=+[\\]{};:'",<.>/?`} +
+ + {errors.password && errors.password.message} + +
+ + 비밀번호 확인 + + + + + + + + {errors.passwordConfirm && errors.passwordConfirm.message} + + + + 이름 + + + {errors.name && errors.name.message} + + + + 생년월일 + + + {errors.date && errors.date.message} + + + + 전화번호 + + 형식: 01012345678 + + {errors.phoneNumber && errors.phoneNumber.message} + + + + 성별 + ( + + value={value} + onChange={onChange} + radios={[ + { text: '남성', value: 'MALE' }, + { text: '여성', value: 'FEMALE' }, + ]} + /> + )} + /> + + + + + + +
+
+ ); +} diff --git a/apps/volunteer/src/pages/volunteers/VolunteerRecruitItem.tsx b/apps/volunteer/src/pages/volunteers/VolunteerRecruitItem.tsx new file mode 100644 index 00000000..caf34eed --- /dev/null +++ b/apps/volunteer/src/pages/volunteers/VolunteerRecruitItem.tsx @@ -0,0 +1,40 @@ +import { + AspectRatio, + Box, + HStack, + Image, + Text, + VStack, +} from '@chakra-ui/react'; +import ApplicantStatus from 'shared/components/ApplicantStatus'; +import InfoSubText from 'shared/components/InfoSubtext'; +import LabelText from 'shared/components/LabelText'; + +const DUMMY_IMAGE = 'https://source.unsplash.com/random'; + +export default function VolunteerRecruitItem() { + return ( + + + + + + + + + + 봉사자 모집합니다!! + + + 양천구 보건소 + + + + + + + + + + ); +} diff --git a/apps/volunteer/src/pages/volunteers/detail/index.tsx b/apps/volunteer/src/pages/volunteers/detail/index.tsx new file mode 100644 index 00000000..eba1c072 --- /dev/null +++ b/apps/volunteer/src/pages/volunteers/detail/index.tsx @@ -0,0 +1,3 @@ +export default function VolunteersDetailPage() { + return

VolunteersDetailPage

; +} diff --git a/apps/volunteer/src/pages/volunteers/index.tsx b/apps/volunteer/src/pages/volunteers/index.tsx new file mode 100644 index 00000000..c7293a18 --- /dev/null +++ b/apps/volunteer/src/pages/volunteers/index.tsx @@ -0,0 +1,3 @@ +export default function VolunteersPage() { + return

VolunteersPage

; +} diff --git a/apps/volunteer/src/pages/volunteers/search/index.tsx b/apps/volunteer/src/pages/volunteers/search/index.tsx new file mode 100644 index 00000000..b35ca75a --- /dev/null +++ b/apps/volunteer/src/pages/volunteers/search/index.tsx @@ -0,0 +1,21 @@ +import { useEffect } from 'react'; +import useSearchHeaderStore from 'shared/store/searchHeaderStore'; + +const handleSearchkeyword = (keyword: string) => { + // TODO: VolunteerList 검색 API 호출 + console.log('[Search Volunteer] keyword:', keyword); +}; + +export default function VolunteersSearchPage() { + const setOnSearch = useSearchHeaderStore((state) => state.setOnSearch); + + useEffect(() => { + setOnSearch(handleSearchkeyword); + + return () => { + setOnSearch(() => {}); + }; + }, [setOnSearch]); + + return

VolunteersSearchPage

; +} diff --git a/apps/volunteer/src/routes/index.tsx b/apps/volunteer/src/routes/index.tsx new file mode 100644 index 00000000..ed39944a --- /dev/null +++ b/apps/volunteer/src/routes/index.tsx @@ -0,0 +1,141 @@ +import { createBrowserRouter, RouterProviderProps } from 'react-router-dom'; +import APP_TYPE from 'shared/constants/appType'; +import PAGE_TYPE from 'shared/constants/pageType'; +import Layout from 'shared/layout'; + +import PATH from '@/constants/path'; +import AnimalsPage from '@/pages/animals'; +import AnimalsDetailPage from '@/pages/animals/detail'; +import ChattingsPage from '@/pages/chattings'; +import ChattingsRoomPage from '@/pages/chattings/room'; +import MyPage from '@/pages/my'; +import NotFoundPage from '@/pages/notfound'; +import NotificationsPage from '@/pages/notifications'; +import SettingsPage from '@/pages/settings'; +import SettingsAccountPage from '@/pages/settings/account'; +import SettingsPasswordPage from '@/pages/settings/password'; +import SheltersProfilePage from '@/pages/shelters/profile'; +import SheltersReviewsUpdatePage from '@/pages/shelters/reviews/update'; +import SheltersReviewsWritePage from '@/pages/shelters/reviews/write'; +import SigninPage from '@/pages/signin'; +import SignupPage from '@/pages/signup'; +import VolunteersPage from '@/pages/volunteers'; +import VolunteersDetailPage from '@/pages/volunteers/detail'; +import VolunteersSearchPage from '@/pages/volunteers/search'; + +export const router: RouterProviderProps['router'] = createBrowserRouter([ + { + path: '/', + element: , + errorElement: , + children: [ + { + path: PATH.VOLUNTEERS.INDEX, + children: [ + { + id: PAGE_TYPE.VOLUNTEERS, + index: true, + element: , + }, + { + id: PAGE_TYPE.VOLUNTEERS_DETAIL, + path: PATH.VOLUNTEERS.DETAIL, + element: , + }, + { + id: PAGE_TYPE.VOLUNTEERS_SEARCH, + path: PATH.VOLUNTEERS.SEARCH, + element: , + }, + ], + }, + { + path: PATH.ANIMALS.INDEX, + children: [ + { + id: PAGE_TYPE.ANIMALS, + index: true, + element: , + }, + { + id: PAGE_TYPE.ANIMALS_DETAIL, + path: PATH.ANIMALS.DETAIL, + element: , + }, + ], + }, + { + path: PATH.CHATTINGS.INDEX, + children: [ + { + id: PAGE_TYPE.CHATTINGS, + index: true, + element: , + }, + { + id: PAGE_TYPE.CHATTINGS_ROOM, + path: PATH.CHATTINGS.ROOM, + element: , + }, + ], + }, + { + id: PAGE_TYPE.MYPAGE, + path: PATH.MYPAGE.INDEX, + element: , + }, + { + id: PAGE_TYPE.SETTINGS, + path: PATH.SETTINGS.INDEX, + element: , + children: [ + { + id: PAGE_TYPE.SETTINGS_ACCOUNT, + path: PATH.SETTINGS.ACCOUNT, + element: , + }, + { + id: PAGE_TYPE.SETTINGS_PASSWORD, + path: PATH.SETTINGS.PASSWORD, + element: , + }, + ], + }, + { + path: PATH.SHELTERS.INDEX, + children: [ + { + id: PAGE_TYPE.SHELTERS_PROFILE, + path: PATH.SHELTERS.PROFILE, + element: , + }, + { + id: PAGE_TYPE.SHELTERS_REVIEWS_WRITE, + path: PATH.SHELTERS.REVIEWS_WRITE, + element: , + }, + { + id: PAGE_TYPE.SHELTERS_REVIEWS_UPDATE, + path: PATH.SHELTERS.REVIEWS_UPDATE, + element: , + }, + ], + }, + { + id: PAGE_TYPE.NOTIFICATIONS, + path: PATH.NOTIFICATIONS, + element: , + }, + { + id: PAGE_TYPE.SIGNUP, + path: PATH.SIGNUP, + element: , + }, + { + id: PAGE_TYPE.SIGNIN, + path: PATH.SIGNIN, + element: , + }, + ], + }, +]); diff --git a/apps/volunteer/src/types/apis/auth.ts b/apps/volunteer/src/types/apis/auth.ts new file mode 100644 index 00000000..a67d7e76 --- /dev/null +++ b/apps/volunteer/src/types/apis/auth.ts @@ -0,0 +1,10 @@ +import { SigninRequestData } from 'shared/types/apis/auth'; + +import { PersonGenderEng } from '../gender'; + +export type SignupRequestData = SigninRequestData & { + name: string; + birthDate: string; + phoneNumber: string; + gender: PersonGenderEng; +}; diff --git a/apps/volunteer/src/types/gender.ts b/apps/volunteer/src/types/gender.ts new file mode 100644 index 00000000..87f035c2 --- /dev/null +++ b/apps/volunteer/src/types/gender.ts @@ -0,0 +1,7 @@ +import { PERAON_GENDER_ENG, PERAON_GENDER_KOR } from '@/constants/gender'; + +export type PersonGenderEng = + (typeof PERAON_GENDER_ENG)[keyof typeof PERAON_GENDER_ENG]; + +export type PersonGenderKor = + (typeof PERAON_GENDER_KOR)[keyof typeof PERAON_GENDER_KOR]; diff --git a/apps/volunteer/vercel.json b/apps/volunteer/vercel.json new file mode 100644 index 00000000..3a48e56b --- /dev/null +++ b/apps/volunteer/vercel.json @@ -0,0 +1,3 @@ +{ + "rewrites": [{ "source": "/(.*)", "destination": "/" }] +} diff --git a/commitlint.config.js b/commitlint.config.js index b888378b..0a107507 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,6 +1,7 @@ module.exports = { extends: ['@commitlint/config-conventional'], rules: { + 'subject-case': [0], 'type-enum': [ 2, 'always', diff --git a/cz-config.js b/cz-config.js index 72fa801a..64b3c047 100644 --- a/cz-config.js +++ b/cz-config.js @@ -35,7 +35,7 @@ module.exports = { ], scopes: [ { name: 'common' }, - { name: 'ui' }, + { name: 'shared' }, { name: 'volunteer' }, { name: 'shelter' }, ], diff --git a/package.json b/package.json index 74a857c6..c89f9e53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "packageManager": "pnpm@8.6.10", + "packageManager": "pnpm@8.10.0", "scripts": { "build": "turbo run build", "build:shelter": "turbo run build --filter=shelter", @@ -27,12 +27,12 @@ "@commitlint/config-conventional": "^18.1.0", "commitizen": "^4.3.0", "cz-customizable": "^7.0.0", - "eslint": "^7.32.0", + "eslint": "^8.45.0", "eslint-config-custom": "workspace:*", "husky": "^8.0.0", "lint-staged": "^15.0.2", "packlint": "^0.2.4", - "prettier": "^2.5.1", + "prettier": "3.0.3", "turbo": "latest" } } diff --git a/packages/ui/.eslintrc.cjs b/packages/shared/.eslintrc.cjs similarity index 100% rename from packages/ui/.eslintrc.cjs rename to packages/shared/.eslintrc.cjs diff --git a/packages/ui/.gitignore b/packages/shared/.gitignore similarity index 100% rename from packages/ui/.gitignore rename to packages/shared/.gitignore diff --git a/packages/shared/apis/axiosInstance.ts b/packages/shared/apis/axiosInstance.ts new file mode 100644 index 00000000..bc770885 --- /dev/null +++ b/packages/shared/apis/axiosInstance.ts @@ -0,0 +1,71 @@ +import axios, { + AxiosInstance, + AxiosRequestConfig, + AxiosResponse, + InternalAxiosRequestConfig, +} from 'axios'; + +import { BASE_URL } from '../constants/baseURL'; +import { + onErrorRequest, + onErrorResponse, + onRequest, + onResponse, +} from './axiosInterceptor'; + +type interceptors = { + onRequest: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig; + onErrorRequest: (error: Error) => void | Promise; + onResponse: (response: AxiosResponse) => AxiosResponse; + onErrorResponse: (error: Error) => void | Promise; +}; + +class AxiosService { + private static instance: AxiosService; + + private constructor(private axiosInstance: AxiosInstance) {} + + public static getInstance({ + onRequest, + onErrorRequest, + onResponse, + onErrorResponse, + }: interceptors): AxiosService { + if (!AxiosService.instance) { + const axiosInstance = axios.create({ baseURL: BASE_URL }); + axiosInstance.interceptors.request.use(onRequest, onErrorRequest); + axiosInstance.interceptors.response.use(onResponse, onErrorResponse); + this.instance = new AxiosService(axiosInstance); + } + return this.instance; + } + + get( + url: string, + config?: AxiosRequestConfig, + ) { + return this.axiosInstance.get(url, config); + } + + post(url: string, data?: Request) { + return this.axiosInstance.post(url, data); + } + + delete( + url: string, + config?: AxiosRequestConfig, + ) { + return this.axiosInstance.delete(url, config); + } + + patch(url: string, data?: Request) { + return this.axiosInstance.patch(url, data); + } +} + +export default AxiosService.getInstance({ + onRequest, + onErrorRequest, + onResponse, + onErrorResponse, +}); diff --git a/packages/shared/apis/axiosInterceptor.ts b/packages/shared/apis/axiosInterceptor.ts new file mode 100644 index 00000000..ce4e3ef7 --- /dev/null +++ b/packages/shared/apis/axiosInterceptor.ts @@ -0,0 +1,18 @@ +import { AxiosResponse, InternalAxiosRequestConfig } from 'axios'; + +export const onRequest = (config: InternalAxiosRequestConfig) => config; + +export const onErrorRequest = (error: Error) => { + return Promise.reject(error); +}; +export const onResponse = (response: AxiosResponse) => response; +export const onErrorResponse = (error: Error) => { + return Promise.reject(error); +}; + +export default { + onRequest, + onErrorRequest, + onResponse, + onErrorResponse, +}; diff --git a/packages/shared/apis/common/Image.ts b/packages/shared/apis/common/Image.ts new file mode 100644 index 00000000..fdf8ff4f --- /dev/null +++ b/packages/shared/apis/common/Image.ts @@ -0,0 +1,11 @@ +import axiosInstance from 'apis/axiosInstance'; + +export const uploadImage = (images: File[]) => + axiosInstance.post< + { + imageUrls: string[]; + }, + { + images: File[]; + } + >('/images', { images }); diff --git a/packages/shared/apis/common/Recruitments.ts b/packages/shared/apis/common/Recruitments.ts new file mode 100644 index 00000000..7f8205da --- /dev/null +++ b/packages/shared/apis/common/Recruitments.ts @@ -0,0 +1,77 @@ +import axiosInstance from '../axiosInstance'; + +type RecruitmentDetailResponse = { + recruitmentTitle: string; + recruitmentApplicantCount: number; + recruitmentCapacity: number; + recruitmentContent: string; + recruitmentStartTime: string; + recruitmentEndTime: string; + recruitmentIsClosed: boolean; + recruitmentDeadline: string; + recruitmentCreatedAt: string; + recruitmentUpdatedAt: string; + recruitmentImageUrls: string[]; +}; + +export const getRecruitmentDetail = (recruitmentId: number) => + axiosInstance.get( + `/recruitments/${recruitmentId}`, + ); + +// 봉사자가 완료한 봉사 모집글 리스트 조회 +export const getCompletedVolunteers = ( + volunteerId: number, + pageNumber: number, + pageSize: number, +) => + axiosInstance.get< + { + pageInfo: { + totalElements: number; + hasNext: boolean; + }; + recruitments: { + recruitmentId: number; + recruitmentTitle: string; + recruitmentStartTime: string; + shelterName: string; + }[]; + }, + { pageNumber: number; pageSize: number } + >(`/volunteers/${volunteerId}/recruitments/completed`, { + params: { + pageNumber, + pageSize, + }, + }); + +//보호소가 생성한 봉사 모집글 리스트 조회 +export const getShelterRecruitment = ( + shelterId: number, + pageNumber: number, + pageSize: number, +) => + axiosInstance.get< + { + pageInfo: { + totalElements: number; + hasNext: boolean; + }; + recruitments: { + recruitmentId: number; + recruitmentTitle: string; + recruitmentStartTime: string; + shelterName: string; + recruitmentDeadline: string; + recruitmentCapacity: number; + recruitmentApplicantCount: number; + }[]; + }, + { pageNumber: number; pageSize: number } + >(`/shelters/${shelterId}/recruitments`, { + params: { + pageNumber, + pageSize, + }, + }); diff --git a/packages/shared/apis/common/Review.ts b/packages/shared/apis/common/Review.ts new file mode 100644 index 00000000..211b85a6 --- /dev/null +++ b/packages/shared/apis/common/Review.ts @@ -0,0 +1,31 @@ +import axiosInstance from 'apis/axiosInstance'; + +export const getVolunteerReviews = ( + volunteerId: number, + pageNumber: number, + pageSize: number, +) => + axiosInstance.get< + { + pageInfo: { + totalElements: number; + hasNext: boolean; + }; + reviews: { + reviewId: number; + shelterName: string; + reviewCreatedAt: string; + reviewContent: string; + reviewImageUrls: string[]; + }[]; + }, + { + pageNumber: number; + pageSize: number; + } + >(`/volunteers/${volunteerId}/reviews`, { + params: { + pageNumber, + pageSize, + }, + }); diff --git a/packages/shared/apis/common/Volunteer.ts b/packages/shared/apis/common/Volunteer.ts new file mode 100644 index 00000000..7fbc4510 --- /dev/null +++ b/packages/shared/apis/common/Volunteer.ts @@ -0,0 +1,10 @@ +import axiosInstance from 'apis/axiosInstance'; + +export const getVolunteerProfileInfo = (recruitmentId: number) => + axiosInstance.get<{ + volunteerEmail: string; + volunteerName: string; + volunteerTemperate: number; + volunteerImageUrl: string; + volunteerPhoneNumber: string; + }>(`/recruitments/${recruitmentId}`); diff --git a/packages/shared/assets/IoEyeOff.tsx b/packages/shared/assets/IoEyeOff.tsx new file mode 100644 index 00000000..7df1f095 --- /dev/null +++ b/packages/shared/assets/IoEyeOff.tsx @@ -0,0 +1,23 @@ +import { ComponentProps } from 'react'; + +export default function IoEyeOff({ ...props }: ComponentProps<'svg'>) { + return ( + + + + + ); +} diff --git a/packages/shared/assets/IoEyeSharp.tsx b/packages/shared/assets/IoEyeSharp.tsx new file mode 100644 index 00000000..132a5194 --- /dev/null +++ b/packages/shared/assets/IoEyeSharp.tsx @@ -0,0 +1,23 @@ +import { ComponentProps } from 'react'; + +export default function IoEyeSharp({ ...props }: ComponentProps<'svg'>) { + return ( + + + + + ); +} diff --git a/packages/shared/assets/bottomNavBar/icon_animals_selected.svg b/packages/shared/assets/bottomNavBar/icon_animals_selected.svg new file mode 100644 index 00000000..ea981bc8 --- /dev/null +++ b/packages/shared/assets/bottomNavBar/icon_animals_selected.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/shared/assets/bottomNavBar/icon_animals_unselected.svg b/packages/shared/assets/bottomNavBar/icon_animals_unselected.svg new file mode 100644 index 00000000..8d76a0e8 --- /dev/null +++ b/packages/shared/assets/bottomNavBar/icon_animals_unselected.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/bottomNavBar/icon_chattings_selected.svg b/packages/shared/assets/bottomNavBar/icon_chattings_selected.svg new file mode 100644 index 00000000..365ece97 --- /dev/null +++ b/packages/shared/assets/bottomNavBar/icon_chattings_selected.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/shared/assets/bottomNavBar/icon_chattings_unselected.svg b/packages/shared/assets/bottomNavBar/icon_chattings_unselected.svg new file mode 100644 index 00000000..3d4338b4 --- /dev/null +++ b/packages/shared/assets/bottomNavBar/icon_chattings_unselected.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/bottomNavBar/icon_mypage_selected.svg b/packages/shared/assets/bottomNavBar/icon_mypage_selected.svg new file mode 100644 index 00000000..bfca5350 --- /dev/null +++ b/packages/shared/assets/bottomNavBar/icon_mypage_selected.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/shared/assets/bottomNavBar/icon_mypage_unselected.svg b/packages/shared/assets/bottomNavBar/icon_mypage_unselected.svg new file mode 100644 index 00000000..3c98ae3a --- /dev/null +++ b/packages/shared/assets/bottomNavBar/icon_mypage_unselected.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/bottomNavBar/icon_volunteers_selected.svg b/packages/shared/assets/bottomNavBar/icon_volunteers_selected.svg new file mode 100644 index 00000000..e889f519 --- /dev/null +++ b/packages/shared/assets/bottomNavBar/icon_volunteers_selected.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/bottomNavBar/icon_volunteers_unselected.svg b/packages/shared/assets/bottomNavBar/icon_volunteers_unselected.svg new file mode 100644 index 00000000..a1681789 --- /dev/null +++ b/packages/shared/assets/bottomNavBar/icon_volunteers_unselected.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/icon-IoEyeOff.svg b/packages/shared/assets/icon-IoEyeOff.svg new file mode 100644 index 00000000..1df7a242 --- /dev/null +++ b/packages/shared/assets/icon-IoEyeOff.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/shared/assets/icon-IoEyeSharp.svg b/packages/shared/assets/icon-IoEyeSharp.svg new file mode 100644 index 00000000..62c791b7 --- /dev/null +++ b/packages/shared/assets/icon-IoEyeSharp.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/shared/assets/icon_BiX.svg b/packages/shared/assets/icon_BiX.svg new file mode 100644 index 00000000..a6595bb4 --- /dev/null +++ b/packages/shared/assets/icon_BiX.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/icon_IoCamera.svg b/packages/shared/assets/icon_IoCamera.svg new file mode 100644 index 00000000..8f7078e4 --- /dev/null +++ b/packages/shared/assets/icon_IoCamera.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/shared/assets/icon_applicant.svg b/packages/shared/assets/icon_applicant.svg new file mode 100644 index 00000000..52a832de --- /dev/null +++ b/packages/shared/assets/icon_applicant.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/shared/assets/icon_back.svg b/packages/shared/assets/icon_back.svg new file mode 100644 index 00000000..a03e8cc6 --- /dev/null +++ b/packages/shared/assets/icon_back.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/icon_menu.svg b/packages/shared/assets/icon_menu.svg new file mode 100644 index 00000000..429a0ff8 --- /dev/null +++ b/packages/shared/assets/icon_menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/icon_next.svg b/packages/shared/assets/icon_next.svg new file mode 100644 index 00000000..083d251d --- /dev/null +++ b/packages/shared/assets/icon_next.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/icon_notifications.svg b/packages/shared/assets/icon_notifications.svg new file mode 100644 index 00000000..20120955 --- /dev/null +++ b/packages/shared/assets/icon_notifications.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/icon_review_next.svg b/packages/shared/assets/icon_review_next.svg new file mode 100644 index 00000000..c0d965d9 --- /dev/null +++ b/packages/shared/assets/icon_review_next.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/icon_search.svg b/packages/shared/assets/icon_search.svg new file mode 100644 index 00000000..f42582ae --- /dev/null +++ b/packages/shared/assets/icon_search.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/icon_settings.svg b/packages/shared/assets/icon_settings.svg new file mode 100644 index 00000000..46033f3b --- /dev/null +++ b/packages/shared/assets/icon_settings.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/shared/assets/image-anifriends-logo.png b/packages/shared/assets/image-anifriends-logo.png new file mode 100644 index 00000000..a9c3932b Binary files /dev/null and b/packages/shared/assets/image-anifriends-logo.png differ diff --git a/packages/shared/components/ApplicantStatus.tsx b/packages/shared/components/ApplicantStatus.tsx new file mode 100644 index 00000000..12a82961 --- /dev/null +++ b/packages/shared/components/ApplicantStatus.tsx @@ -0,0 +1,24 @@ +import { Flex, Image, Text } from '@chakra-ui/react'; + +import ApplicantIcon from '../assets/icon_applicant.svg'; + +type ApplicantStatusProps = { + size?: number; + numerator: number; + denominator: number; +}; + +export default function ApplicantStatus({ + size = 5, + numerator, + denominator, +}: ApplicantStatusProps) { + return ( + + + + {`${numerator} / ${denominator}`} + + + ); +} diff --git a/packages/shared/components/EditPhotoItem.tsx b/packages/shared/components/EditPhotoItem.tsx new file mode 100644 index 00000000..f49c0829 --- /dev/null +++ b/packages/shared/components/EditPhotoItem.tsx @@ -0,0 +1,45 @@ +import type { ImageProps } from '@chakra-ui/react'; +import { Box, Image } from '@chakra-ui/react'; +import { MouseEvent } from 'react'; + +import BiX from '../assets/icon_BiX.svg'; + +type UploadedPhotoItemProps = { + photoId: ImageProps['id']; + photoSrc: ImageProps['src']; + onDeletePhoto: (event: MouseEvent) => void; +}; + +export default function EditPhotoItem({ + photoId, + photoSrc, + onDeletePhoto, +}: UploadedPhotoItemProps) { + return ( + + + + + + + ); +} diff --git a/packages/shared/components/EditPhotoList.tsx b/packages/shared/components/EditPhotoList.tsx new file mode 100644 index 00000000..ea6cfa87 --- /dev/null +++ b/packages/shared/components/EditPhotoList.tsx @@ -0,0 +1,140 @@ +import type { UseToastOptions } from '@chakra-ui/react'; +import { + Box, + Flex, + HStack, + Image, + Input, + Text, + useToast, +} from '@chakra-ui/react'; +import type { ChangeEvent, Dispatch, MouseEvent, SetStateAction } from 'react'; + +import IoIosCamera from '../assets/icon_IoCamera.svg'; +import EditPhotoItem from './EditPhotoItem'; + +type EditPhotoListProps = { + urls: string[]; + setUrls: Dispatch>; +}; + +type UploadPhotoItemProps = { + urlsCount: number; + onUploadPhoto: (event: ChangeEvent) => void; +}; + +export default function EditPhotoList({ urls, setUrls }: EditPhotoListProps) { + const toast = useToast(); + + const afterUploadToast = ( + description: string, + status: UseToastOptions['status'], + ) => { + toast({ + description, + position: 'top', + status, + duration: 1500, + isClosable: true, + }); + }; + + const deletePhoto = (event: MouseEvent) => { + const { id } = event.currentTarget; + const newUrls = urls.filter((url) => url !== id); + + setUrls(newUrls); + }; + + const uploadPhoto = async (event: ChangeEvent) => { + if (urls.length === 5) { + afterUploadToast('사진을 더이상 추가할 수 없습니다', 'error'); + + return; + } + + const formData = new FormData(); + const { files } = event.currentTarget; + const uploadPhotoCount = (files?.length ?? 0) + urls.length; + + if (uploadPhotoCount > 5) { + afterUploadToast(`${5 - urls.length}개 더 등록이 가능합니다`, 'error'); + + return; + } + + if (files) { + Array.from(files).forEach((file) => { + formData.append('images', file); + }); + } + + // 아래는 api 함수로 받아왔다는 이미지들을 url 형식들로 받아왔다는 가정하에 진행하는 로직입니다 + + if (files) { + const imageUrls = Array.from(files).map((file) => + URL.createObjectURL(file), + ); + + setUrls((prevUrls) => [...imageUrls, ...prevUrls]); + } + }; + + return ( + + + {urls.map((url, index) => ( + + ))} + + ); +} + +function UploadPhotoItem({ urlsCount, onUploadPhoto }: UploadPhotoItemProps) { + return ( + + + + + + + + {urlsCount} + + / + 5 + + + + + ); +} diff --git a/packages/shared/components/ImageCarousel.tsx b/packages/shared/components/ImageCarousel.tsx new file mode 100644 index 00000000..dc49ff8d --- /dev/null +++ b/packages/shared/components/ImageCarousel.tsx @@ -0,0 +1,135 @@ +import { Box, chakra, Flex, Image, shouldForwardProp } from '@chakra-ui/react'; +import type { PanInfo } from 'framer-motion'; +import { + AnimatePresence, + isValidMotionProp, + motion, + wrap, +} from 'framer-motion'; +import { useState } from 'react'; + +import BackIcon from '../assets/icon_back.svg'; + +type ImageCarouselProps = { + imageUrls: string[]; +}; + +const variants = { + enter: (direction: number) => { + return { + x: direction > 0 ? 500 : -500, + opacity: 0, + }; + }, + center: { + x: 0, + opacity: 1, + }, + exit: (direction: number) => { + return { + x: direction < 0 ? 500 : -500, + opacity: 0, + }; + }, +}; + +const swipeConfidenceThreshold = 10000; + +const swipePower = (offset: number, velocity: number) => + Math.abs(offset) * velocity; + +export default function ImageCarousel({ imageUrls }: ImageCarouselProps) { + const [[page, direction], setPage] = useState([0, 0]); + + const imageUrlIndex = wrap(0, imageUrls.length, page); + + const paginate = (newDirection: number) => + setPage([page + newDirection, newDirection]); + + const dragImage = ( + // eslint-disable-next-line + // @ts-ignore + event: DragEvent | TouchEvent | PointerEvent, + { offset, velocity }: PanInfo, + ) => { + const swipe = swipePower(offset.x, velocity.x); + + if (swipe < -swipeConfidenceThreshold) { + paginate(1); + } else if (swipe > swipeConfidenceThreshold) { + paginate(-1); + } + }; + + const goBackPage = () => paginate(-1); + + const goNextPage = () => paginate(1); + + return ( + + + + + + + + + + + + ); +} + +const MotionImage = chakra(motion.img, { + shouldForwardProp: (prop) => + isValidMotionProp(prop) || shouldForwardProp(prop), +}); diff --git a/packages/shared/components/InfoItem.tsx b/packages/shared/components/InfoItem.tsx new file mode 100644 index 00000000..68864206 --- /dev/null +++ b/packages/shared/components/InfoItem.tsx @@ -0,0 +1,33 @@ +import type { TextProps } from '@chakra-ui/react'; +import { Flex, Text } from '@chakra-ui/react'; +import { ReactElement } from 'react'; + +export type InfoItemStylesProps = { + titleTextStyles?: TextProps; +}; + +export type InfoItemProps = { + title: string; + children: ReactElement; +}; + +export default function InfoItem({ + title, + titleTextStyles, + children, +}: InfoItemProps & InfoItemStylesProps) { + return ( + + + {title} + + {children} + + ); +} diff --git a/packages/shared/components/InfoList.tsx b/packages/shared/components/InfoList.tsx new file mode 100644 index 00000000..07213df8 --- /dev/null +++ b/packages/shared/components/InfoList.tsx @@ -0,0 +1,17 @@ +import { Flex, FlexProps } from '@chakra-ui/react'; + +export default function InfoList({ children, ...props }: FlexProps) { + return ( + + {children} + + ); +} diff --git a/packages/shared/components/InfoSubtext.tsx b/packages/shared/components/InfoSubtext.tsx new file mode 100644 index 00000000..96f68888 --- /dev/null +++ b/packages/shared/components/InfoSubtext.tsx @@ -0,0 +1,19 @@ +import { HStack, Text } from '@chakra-ui/react'; + +type InfoSubtext = { + title: string; + content: string; +}; + +export default function InfoSubtext({ title, content }: InfoSubtext) { + return ( + + + {title} + + + {content} + + + ); +} diff --git a/packages/shared/components/InfoTextItem.tsx b/packages/shared/components/InfoTextItem.tsx new file mode 100644 index 00000000..5d8b32bb --- /dev/null +++ b/packages/shared/components/InfoTextItem.tsx @@ -0,0 +1,33 @@ +import type { TextProps } from '@chakra-ui/react'; +import { Text } from '@chakra-ui/react'; + +import InfoItem, { InfoItemStylesProps } from './InfoItem'; + +export type InfoTextItemStylesProps = { + contentTextStyles?: TextProps; +} & InfoItemStylesProps; + +export type InfoTextItemProps = { + title: string; + content: string; +}; + +export default function InfoTextItem({ + content, + contentTextStyles, + ...props +}: InfoTextItemProps & InfoTextItemStylesProps) { + return ( + + + {content} + + + ); +} diff --git a/packages/shared/components/InfoTextList.tsx b/packages/shared/components/InfoTextList.tsx new file mode 100644 index 00000000..1a658883 --- /dev/null +++ b/packages/shared/components/InfoTextList.tsx @@ -0,0 +1,22 @@ +import { FlexProps } from '@chakra-ui/react'; + +import InfoList from './InfoList'; +import type { InfoTextItemProps } from './InfoTextItem'; +import InfoTextItem from './InfoTextItem'; + +type InfoTextListProps = { + infoTextItems: InfoTextItemProps[]; +} & Omit; + +export default function InfoTextList({ + infoTextItems, + ...props +}: InfoTextListProps) { + return ( + + {infoTextItems.map((infoTextItemProps, index) => ( + + ))} + + ); +} diff --git a/packages/shared/components/Label.tsx b/packages/shared/components/Label.tsx new file mode 100644 index 00000000..d6ced9d3 --- /dev/null +++ b/packages/shared/components/Label.tsx @@ -0,0 +1,32 @@ +import { Badge } from '@chakra-ui/react'; + +const LABEL_BACKGROUND_COLOR = { + GREEN: 'green.300', + ORANGE: 'orange.400', + YELLOW: 'yellow.300', + RED: 'red.400', + GRAY: 'gray.400', +} as const; + +export type LabelProps = { + labelTitle: string; + type?: keyof typeof LABEL_BACKGROUND_COLOR; +}; + +export default function Label({ labelTitle, type = 'GREEN' }: LabelProps) { + return ( + + {labelTitle} + + ); +} diff --git a/packages/shared/components/LabelText.tsx b/packages/shared/components/LabelText.tsx new file mode 100644 index 00000000..764407e4 --- /dev/null +++ b/packages/shared/components/LabelText.tsx @@ -0,0 +1,17 @@ +import { HStack, Text } from '@chakra-ui/react'; + +import type { LabelProps } from './Label'; +import Label from './Label'; + +type LabelTextProps = { content: string } & LabelProps; + +export default function LabelText({ labelTitle, content }: LabelTextProps) { + return ( + + + ); +} diff --git a/packages/shared/components/OptionMenu.tsx b/packages/shared/components/OptionMenu.tsx new file mode 100644 index 00000000..6eb80748 --- /dev/null +++ b/packages/shared/components/OptionMenu.tsx @@ -0,0 +1,26 @@ +import { + Image, + Menu, + MenuButton, + MenuButtonProps, + MenuItem, + MenuList, +} from '@chakra-ui/react'; +import { ReactElement } from 'react'; + +import MenuIcon from '../assets/icon_menu.svg'; + +type OptionMenuProps = { + children: ReactElement | ReactElement[]; +} & Omit; + +export default function OptionMenu({ children, ...props }: OptionMenuProps) { + return ( + + + Menu Icon + + {children} + + ); +} diff --git a/packages/shared/components/ProfileInfo.tsx b/packages/shared/components/ProfileInfo.tsx new file mode 100644 index 00000000..0f692143 --- /dev/null +++ b/packages/shared/components/ProfileInfo.tsx @@ -0,0 +1,35 @@ +import { Avatar, HStack, Text, VStack } from '@chakra-ui/react'; + +type ProfileInfoProps = { + infoImage?: string; + infoTitle: string; + infoTexts?: string[]; + children?: React.ReactNode; +}; + +export default function ProfileInfo({ + infoImage, + infoTitle, + infoTexts, + children, +}: ProfileInfoProps) { + return ( + + + + + + {infoTitle} + + {children} + + {infoTexts && + infoTexts.map((infoText, index) => ( + + {infoText} + + ))} + + + ); +} diff --git a/packages/shared/components/RadioGroup.tsx b/packages/shared/components/RadioGroup.tsx new file mode 100644 index 00000000..30a132b0 --- /dev/null +++ b/packages/shared/components/RadioGroup.tsx @@ -0,0 +1,52 @@ +import type { + RadioGroupProps as ChakraRadioGroupProps, + RadioProps, + StackProps, +} from '@chakra-ui/react'; +import { + HStack, + Radio, + RadioGroup as ChakraRadioGroup, +} from '@chakra-ui/react'; + +type Radio = { + value: Value; + text: Text; +}; + +type RadioGroupProps = Omit & { + value: Value; + onChange: (nextValue: Value) => void; + defaultValue?: Value; + radios: Radio[]; + hStackProps?: StackProps; + radioProps?: RadioProps; +}; + +export default function RadioGroup({ + value, + onChange, + defaultValue, + radios, + hStackProps, + radioProps, + ...chakraRadioGropRestprops +}: RadioGroupProps) { + return ( + + + {radios.map(({ value, text }: Radio) => ( + + {text} + + ))} + + + ); +} diff --git a/packages/shared/components/ReviewItem.tsx b/packages/shared/components/ReviewItem.tsx new file mode 100644 index 00000000..3a661a82 --- /dev/null +++ b/packages/shared/components/ReviewItem.tsx @@ -0,0 +1,97 @@ +import { + Card, + HStack, + Image, + Menu, + MenuButton, + MenuItem, + MenuList, + Text, +} from '@chakra-ui/react'; +import React from 'react'; + +import MenuIcon from '../assets/icon_menu.svg'; + +type ReviewItemProps = { + children: React.ReactNode; + content: string; + images?: string[]; + showMenuButton?: boolean; + onUpdate?: VoidFunction; + onDelete?: VoidFunction; +}; + +function CustomMenu({ + onUpdate, + onDelete, +}: Pick) { + return ( + + + menu icon + + + 수정하기 + 삭제하기 + 닫기 + + + ); +} + +export default function ReviewItem({ + children, + content, + images, + showMenuButton = false, + onUpdate, + onDelete, +}: ReviewItemProps) { + return ( + + {children} + {showMenuButton && } + + {content} + + + {images?.map((src, index) => ( + + ))} + + + ); +} diff --git a/packages/shared/components/SettingGroup.tsx b/packages/shared/components/SettingGroup.tsx new file mode 100644 index 00000000..6ae14b16 --- /dev/null +++ b/packages/shared/components/SettingGroup.tsx @@ -0,0 +1,25 @@ +import { Box, Text } from '@chakra-ui/react'; + +import type { SettingItemProps } from './SettingItem'; +import SettingItem from './SettingItem'; + +type SettingProps = { + groupTitle: string; + settingItems: SettingItemProps[]; +}; + +export default function SettingGroup({ + groupTitle, + settingItems, +}: SettingProps) { + return ( + + + {groupTitle} + + {settingItems.map((item, index) => ( + + ))} + + ); +} diff --git a/packages/shared/components/SettingItem.tsx b/packages/shared/components/SettingItem.tsx new file mode 100644 index 00000000..908ae860 --- /dev/null +++ b/packages/shared/components/SettingItem.tsx @@ -0,0 +1,26 @@ +import { Box, Flex, Image, Text } from '@chakra-ui/react'; + +import Next from '../assets/icon_next.svg'; + +export type SettingItemProps = { + itemTitle: string; + onClick: VoidFunction; +}; +export default function SettingItem({ itemTitle, onClick }: SettingItemProps) { + return ( + + {itemTitle} + + + + + ); +} diff --git a/packages/shared/constants/appType.ts b/packages/shared/constants/appType.ts new file mode 100644 index 00000000..11710c6c --- /dev/null +++ b/packages/shared/constants/appType.ts @@ -0,0 +1,6 @@ +const APP_TYPE = { + SHELTER_APP: 'SHELTER_APP', + VOLUNTEER_APP: 'VOLUNTEER_APP', +} as const; + +export default APP_TYPE; diff --git a/packages/shared/constants/baseURL.ts b/packages/shared/constants/baseURL.ts new file mode 100644 index 00000000..16f714e9 --- /dev/null +++ b/packages/shared/constants/baseURL.ts @@ -0,0 +1 @@ +export const BASE_URL = '/'; diff --git a/packages/shared/constants/date.ts b/packages/shared/constants/date.ts new file mode 100644 index 00000000..c72baf0f --- /dev/null +++ b/packages/shared/constants/date.ts @@ -0,0 +1,7 @@ +export const MILISECONDS = { + YEAR: 60 * 60 * 24 * 365, + MONTH: 60 * 60 * 24 * 30, + DAY: 60 * 60 * 24, + HOUR: 60 * 60, + MINUITE: 60, +}; diff --git a/packages/shared/constants/headerTitle.ts b/packages/shared/constants/headerTitle.ts new file mode 100644 index 00000000..da89b7bf --- /dev/null +++ b/packages/shared/constants/headerTitle.ts @@ -0,0 +1,36 @@ +import { PageType } from '../types/page'; + +type HeaderTitle = { + [key in PageType]: string; +}; + +const headerTitle: HeaderTitle = { + VOLUNTEERS: '봉사자 모집', + VOLUNTEERS_DETAIL: '봉사자 모집 상세', + VOLUNTEERS_PROFILE: '봉사자 프로필', + VOLUNTEERS_SEARCH: '봉사자 모집글 검색', + VOLUNTEERS_WRITE: '봉사자 모집글 작성', + VOLUNTEERS_UPDATE: '봉사자 모집글 수정', + ANIMALS: '유기보호 동물', + ANIMALS_DETAIL: '유기보호 동물 상세', + ANIMALS_SEARCH: '유기보호 동물 검색', + ANIMALS_WRITE: '유기보호 동물 작성', + ANIMALS_UPDATE: '유기보호 동물 수정', + CHATTINGS: '채팅', + CHATTINGS_ROOM: '채팅방', + MYPAGE: '마이페이지', + MYPAGE_REVIEWS: '봉사 후기', + SETTINGS: '설정', + SETTINGS_ACCOUNT: '계정 정보 수정', + SETTINGS_PASSWORD: '비밀 번호 수정', + MANAGE_ATTENDANCE: '봉사자 출석 관리', + MANAGE_APPLY: '봉사자 신청 현황', + NOTIFICATIONS: '알림', + SHELTERS_PROFILE: '보호소 프로필', + SHELTERS_REVIEWS_WRITE: '봉사 후기 작성', + SHELTERS_REVIEWS_UPDATE: '봉사 후기 수정', + SIGNUP: '회원가입', + SIGNIN: '로그인', +} as const; + +export default headerTitle; diff --git a/packages/shared/constants/headerType.ts b/packages/shared/constants/headerType.ts new file mode 100644 index 00000000..11010d76 --- /dev/null +++ b/packages/shared/constants/headerType.ts @@ -0,0 +1,7 @@ +const HEADER_TYPE = { + DEFAULT: 'DEFAULT', + DETAIL: 'DETAIL', + SEARCH: 'SEARCH', +} as const; + +export default HEADER_TYPE; diff --git a/packages/shared/constants/pageType.ts b/packages/shared/constants/pageType.ts new file mode 100644 index 00000000..0bd02019 --- /dev/null +++ b/packages/shared/constants/pageType.ts @@ -0,0 +1,30 @@ +const PAGE_TYPE = { + VOLUNTEERS: 'VOLUNTEERS', + VOLUNTEERS_DETAIL: 'VOLUNTEERS_DETAIL', + VOLUNTEERS_PROFILE: 'VOLUNTEERS_PROFILE', + VOLUNTEERS_SEARCH: 'VOLUNTEERS_SEARCH', + VOLUNTEERS_WRITE: 'VOLUNTEERS_WRITE', + VOLUNTEERS_UPDATE: 'VOLUNTEERS_UPDATE', + ANIMALS: 'ANIMALS', + ANIMALS_DETAIL: 'ANIMALS_DETAIL', + ANIMALS_SEARCH: 'ANIMALS_SEARCH', + ANIMALS_WRITE: 'ANIMALS_WRITE', + ANIMALS_UPDATE: 'ANIMALS_UPDATE', + CHATTINGS: 'CHATTINGS', + CHATTINGS_ROOM: 'CHATTINGS_ROOM', + MYPAGE: 'MYPAGE', + MYPAGE_REVIEWS: 'MYPAGE_REVIEWS', + SETTINGS: 'SETTINGS', + SETTINGS_ACCOUNT: 'SETTINGS_ACCOUNT', + SETTINGS_PASSWORD: 'SETTINGS_PASSWORD', + MANAGE_ATTENDANCE: 'MANAGE_ATTENDANCE', + MANAGE_APPLY: 'MANAGE_APPLY', + NOTIFICATIONS: 'NOTIFICATIONS', + SHELTERS_PROFILE: 'SHELTERS_PROFILE', + SHELTERS_REVIEWS_WRITE: 'SHELTERS_REVIEWS_WRITE', + SHELTERS_REVIEWS_UPDATE: 'SHELTERS_REVIEWS_UPDATE', + SIGNUP: 'SIGNUP', + SIGNIN: 'SIGNIN', +} as const; + +export default PAGE_TYPE; diff --git a/packages/shared/fonts/IBMPlexSans-Regular.woff b/packages/shared/fonts/IBMPlexSans-Regular.woff new file mode 100644 index 00000000..dc4ae673 Binary files /dev/null and b/packages/shared/fonts/IBMPlexSans-Regular.woff differ diff --git a/packages/shared/fonts/index.tsx b/packages/shared/fonts/index.tsx new file mode 100644 index 00000000..db5c928e --- /dev/null +++ b/packages/shared/fonts/index.tsx @@ -0,0 +1,27 @@ +import { Global } from '@emotion/react'; + +export default function Fonts() { + return ( + + ); +} diff --git a/packages/shared/hooks/usePageType.ts b/packages/shared/hooks/usePageType.ts new file mode 100644 index 00000000..065b9ea3 --- /dev/null +++ b/packages/shared/hooks/usePageType.ts @@ -0,0 +1,18 @@ +import { useEffect, useState } from 'react'; +import { useMatches } from 'react-router-dom'; + +import { PageType } from '../types/page'; + +export const usePageType = () => { + const [pageType, setPageType] = useState(); + + const match = useMatches().at(-1); + + useEffect(() => { + const page = match?.id; + + setPageType(page as PageType); + }, [match]); + + return { pageType }; +}; diff --git a/packages/shared/hooks/useRadioGroup.ts b/packages/shared/hooks/useRadioGroup.ts new file mode 100644 index 00000000..3ef1cf2a --- /dev/null +++ b/packages/shared/hooks/useRadioGroup.ts @@ -0,0 +1,13 @@ +import { useState } from 'react'; + +const useRadioGroup = ( + initialValue: Value, +): [Value, (nextValue: Value) => void] => { + const [value, setValue] = useState(initialValue); + + const changeValue = (nextValue: Value) => setValue(nextValue); + + return [value, changeValue]; +}; + +export default useRadioGroup; diff --git a/packages/shared/hooks/useToggle.ts b/packages/shared/hooks/useToggle.ts new file mode 100644 index 00000000..87ac3b4c --- /dev/null +++ b/packages/shared/hooks/useToggle.ts @@ -0,0 +1,13 @@ +import { useBoolean } from '@chakra-ui/react'; + +const useToggle = (initialState = false): [boolean, () => void] => { + const [isTrue, setIsTrue] = useBoolean(initialState); + + const toggle = () => { + setIsTrue.toggle(); + }; + + return [isTrue, toggle]; +}; + +export default useToggle; diff --git a/packages/ui/index.tsx b/packages/shared/index.tsx similarity index 100% rename from packages/ui/index.tsx rename to packages/shared/index.tsx diff --git a/packages/shared/layout/BottomNavBar/NavBarButton.tsx b/packages/shared/layout/BottomNavBar/NavBarButton.tsx new file mode 100644 index 00000000..5cf0c77c --- /dev/null +++ b/packages/shared/layout/BottomNavBar/NavBarButton.tsx @@ -0,0 +1,29 @@ +import { Box, Image } from '@chakra-ui/react'; + +export type NavBarButtonProps = { + onClick: VoidFunction; + selected: boolean; + buttonImageSrc: [string, string]; + buttonText: string; +}; + +export default function NavBarButton({ + onClick, + selected, + buttonImageSrc, + buttonText, +}: NavBarButtonProps) { + const [unselectedButton, selectedButton] = buttonImageSrc; + + return ( + + + {buttonText} + + ); +} diff --git a/packages/shared/layout/BottomNavBar/index.tsx b/packages/shared/layout/BottomNavBar/index.tsx new file mode 100644 index 00000000..2e9b0f28 --- /dev/null +++ b/packages/shared/layout/BottomNavBar/index.tsx @@ -0,0 +1,71 @@ +import { Flex } from '@chakra-ui/react'; +import { useNavigate } from 'react-router-dom'; + +import AnimalsSelectedIcon from '../../assets/bottomNavBar/icon_animals_selected.svg'; +import AnimalsUnselectedIcon from '../../assets/bottomNavBar/icon_animals_unselected.svg'; +import ChattingsSelectedIcon from '../../assets/bottomNavBar/icon_chattings_selected.svg'; +import ChattingsUnselectedIcon from '../../assets/bottomNavBar/icon_chattings_unselected.svg'; +import MyPageSeletedIcon from '../../assets/bottomNavBar/icon_mypage_selected.svg'; +import MyPageUnselectedIcon from '../../assets/bottomNavBar/icon_mypage_unselected.svg'; +import VolunteersSelectedIcon from '../../assets/bottomNavBar/icon_volunteers_selected.svg'; +import VolunteersUnselectedIcon from '../../assets/bottomNavBar/icon_volunteers_unselected.svg'; +import PAGE_TYPE from '../../constants/pageType'; +import { usePageType } from '../../hooks/usePageType'; +import NavBarButton from './NavBarButton'; +import { useBottomNavBar } from './useBottomNavBar'; + +export default function BottomNavBar() { + const { pageType } = usePageType(); + const { isBottomNavBarVisible } = useBottomNavBar(pageType); + + const navigate = useNavigate(); + + const goVolunteers = () => navigate('/volunteers'); + const goAnimals = () => navigate('/animals'); + const goChattings = () => navigate('/chattings'); + const goMyPage = () => navigate('/mypage'); + + return ( + isBottomNavBarVisible && ( + + + + + + + ) + ); +} diff --git a/packages/shared/layout/BottomNavBar/useBottomNavBar.ts b/packages/shared/layout/BottomNavBar/useBottomNavBar.ts new file mode 100644 index 00000000..32c07ec6 --- /dev/null +++ b/packages/shared/layout/BottomNavBar/useBottomNavBar.ts @@ -0,0 +1,23 @@ +import { useEffect, useState } from 'react'; + +import PAGE_TYPE from '../../constants/pageType'; +import { PageType } from '../../types/page'; + +export const useBottomNavBar = (pageType?: PageType) => { + const [isBottomNavBarVisible, setIsBottomNavBarVisible] = useState(false); + + useEffect(() => { + if ( + pageType === PAGE_TYPE.VOLUNTEERS || + pageType === PAGE_TYPE.ANIMALS || + pageType === PAGE_TYPE.CHATTINGS || + pageType === PAGE_TYPE.MYPAGE + ) { + return setIsBottomNavBarVisible(true); + } + + return setIsBottomNavBarVisible(false); + }, [pageType]); + + return { isBottomNavBarVisible }; +}; diff --git a/packages/shared/layout/Header/DefaultHeader/headerIconState.ts b/packages/shared/layout/Header/DefaultHeader/headerIconState.ts new file mode 100644 index 00000000..7a296f32 --- /dev/null +++ b/packages/shared/layout/Header/DefaultHeader/headerIconState.ts @@ -0,0 +1,85 @@ +import { AppType } from '../../../types/app'; +import { DefaultHeaderIconVisibility } from './useDefaultHeader'; + +type DefaultHeaderIconState = { + [key: string]: { + [key in AppType]: DefaultHeaderIconVisibility; + }; +}; + +const defaultHeaderIconState: DefaultHeaderIconState = { + VOLUNTEERS: { + SHELTER_APP: { + searchIcon: true, + settingsIcon: false, + notificationsIcon: true, + }, + VOLUNTEER_APP: { + searchIcon: true, + settingsIcon: false, + notificationsIcon: true, + }, + }, + ANIMALS: { + SHELTER_APP: { + searchIcon: true, + settingsIcon: false, + notificationsIcon: true, + }, + VOLUNTEER_APP: { + searchIcon: false, + settingsIcon: false, + notificationsIcon: true, + }, + }, + CHATTINGS: { + SHELTER_APP: { + searchIcon: false, + settingsIcon: false, + notificationsIcon: true, + }, + VOLUNTEER_APP: { + searchIcon: false, + settingsIcon: false, + notificationsIcon: true, + }, + }, + MYPAGE: { + SHELTER_APP: { + searchIcon: false, + settingsIcon: false, + notificationsIcon: true, + }, + VOLUNTEER_APP: { + searchIcon: false, + settingsIcon: true, + notificationsIcon: true, + }, + }, + SIGNUP: { + SHELTER_APP: { + searchIcon: false, + settingsIcon: false, + notificationsIcon: false, + }, + VOLUNTEER_APP: { + searchIcon: false, + settingsIcon: false, + notificationsIcon: false, + }, + }, + SIGNIN: { + SHELTER_APP: { + searchIcon: false, + settingsIcon: false, + notificationsIcon: false, + }, + VOLUNTEER_APP: { + searchIcon: false, + settingsIcon: false, + notificationsIcon: false, + }, + }, +} as const; + +export default defaultHeaderIconState; diff --git a/packages/shared/layout/Header/DefaultHeader/index.tsx b/packages/shared/layout/Header/DefaultHeader/index.tsx new file mode 100644 index 00000000..b602115b --- /dev/null +++ b/packages/shared/layout/Header/DefaultHeader/index.tsx @@ -0,0 +1,57 @@ +import { Box, ButtonGroup, Flex, Image, Text } from '@chakra-ui/react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +import NotificationsIcon from '../../../assets/icon_notifications.svg'; +import SearchIcon from '../../../assets/icon_search.svg'; +import SettingsIcon from '../../../assets/icon_settings.svg'; +import { HeaderProps } from '../index'; +import { useDefaultHeader } from './useDefaultHeader'; + +export default function DefaultHeader({ appType }: HeaderProps) { + const navigate = useNavigate(); + const { pathname } = useLocation(); + const { title, iconVisibility } = useDefaultHeader(appType); + + const { searchIcon, settingsIcon, notificationsIcon } = iconVisibility; + + const goSearch = () => navigate(`${pathname}/search`); + const goSettings = () => navigate('/settings'); + const goNotifications = () => navigate('/notifications'); + + return ( + + + {title} + + + {searchIcon && ( + + Search Icon + + )} + {settingsIcon && ( + + Settings Icon + + )} + {notificationsIcon && ( + + Notifications Icon + + )} + + + ); +} diff --git a/packages/shared/layout/Header/DefaultHeader/useDefaultHeader.ts b/packages/shared/layout/Header/DefaultHeader/useDefaultHeader.ts new file mode 100644 index 00000000..de7a25d7 --- /dev/null +++ b/packages/shared/layout/Header/DefaultHeader/useDefaultHeader.ts @@ -0,0 +1,37 @@ +import { useEffect, useState } from 'react'; + +import { usePageType } from '../../../hooks/usePageType'; +import { AppType } from '../../../types/app'; +import { getHeaderTitle } from '../utils'; +import defaultHeaderState from './headerIconState'; + +export type DefaultHeaderIconVisibility = { + searchIcon: boolean; + settingsIcon: boolean; + notificationsIcon: boolean; +}; + +export const useDefaultHeader = (appType: AppType) => { + const { pageType } = usePageType(); + const [title, setTitle] = useState(''); + const [iconVisibility, setIconVisibility] = + useState({ + searchIcon: false, + settingsIcon: false, + notificationsIcon: false, + }); + + useEffect(() => { + if (pageType) { + setTitle(getHeaderTitle(pageType)); + + const iconState = defaultHeaderState[pageType]; + + if (iconState) { + setIconVisibility(iconState[appType]); + } + } + }, [appType, pageType]); + + return { title, iconVisibility }; +}; diff --git a/packages/shared/layout/Header/DetailHeader/index.tsx b/packages/shared/layout/Header/DetailHeader/index.tsx new file mode 100644 index 00000000..3f1a25b5 --- /dev/null +++ b/packages/shared/layout/Header/DetailHeader/index.tsx @@ -0,0 +1,61 @@ +import { Box, Flex, Image, MenuItem, Text } from '@chakra-ui/react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +import BackIcon from '../../../assets/icon_back.svg'; +import OptionMenu from '../../../components/OptionMenu'; +import useDetailHeaderStore from '../../../store/detailHeaderStore'; +import { HeaderProps } from '../index'; +import { useDetailHeader } from './useDetailHeader'; + +export default function DetailHeader({ appType }: HeaderProps) { + const navigate = useNavigate(); + const { pathname } = useLocation(); + const { title, iconVisibility } = useDetailHeader(appType); + const onDelete = useDetailHeaderStore((state) => state.onDelete); + + const { menuIcon } = iconVisibility; + + const goBack = () => navigate(-1); + + const handleUpdate = () => { + const [, path, id] = pathname.split('/'); + + navigate(`${path}/write/${id}`); + }; + + const handleDelete = () => { + const [, , id] = pathname.split('/'); + + onDelete(Number(id)); + }; + + return ( + + + Back Icon + + + {title} + + {menuIcon && ( + + 수정하기 + 삭제하기 + + )} + + ); +} diff --git a/packages/shared/layout/Header/DetailHeader/useDetailHeader.ts b/packages/shared/layout/Header/DetailHeader/useDetailHeader.ts new file mode 100644 index 00000000..64eff87b --- /dev/null +++ b/packages/shared/layout/Header/DetailHeader/useDetailHeader.ts @@ -0,0 +1,36 @@ +import { useEffect, useState } from 'react'; + +import APP_TYPE from '../../../constants/appType'; +import PAGE_TYPE from '../../../constants/pageType'; +import { usePageType } from '../../../hooks/usePageType'; +import { AppType } from '../../../types/app'; +import { getHeaderTitle } from '../utils'; + +export type DetailHeaderIconVisibility = { + menuIcon: boolean; +}; + +export const useDetailHeader = (appType: AppType) => { + const { pageType } = usePageType(); + const [title, setTitle] = useState(''); + const [iconVisibility, setIconVisibility] = + useState({ menuIcon: false }); + + useEffect(() => { + if (pageType) { + setTitle(getHeaderTitle(pageType)); + + if ( + appType === APP_TYPE.SHELTER_APP && + (pageType === PAGE_TYPE.VOLUNTEERS_DETAIL || + pageType === PAGE_TYPE.ANIMALS_DETAIL) + ) { + setIconVisibility({ menuIcon: true }); + } else { + setIconVisibility({ menuIcon: false }); + } + } + }, [appType, pageType]); + + return { title, iconVisibility }; +}; diff --git a/packages/shared/layout/Header/SearchHeader/index.tsx b/packages/shared/layout/Header/SearchHeader/index.tsx new file mode 100644 index 00000000..e91b1978 --- /dev/null +++ b/packages/shared/layout/Header/SearchHeader/index.tsx @@ -0,0 +1,86 @@ +import { Box, Flex, FormControl, Image, Input } from '@chakra-ui/react'; +import { ChangeEvent, FormEvent, useEffect, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import BackIcon from '../../../assets/icon_back.svg'; +import useSearchHeaderStore from '../../../store/searchHeaderStore'; + +export default function SearchHeader() { + const navigate = useNavigate(); + const [keyword, setKeyword, onSearch] = useSearchHeaderStore((state) => [ + state.keyword, + state.setKeyword, + state.onSearch, + ]); + + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + const goBack = () => navigate(-1); + + const handleChangeKeyword = (event: ChangeEvent) => { + const { value } = event.target; + setKeyword(value); + }; + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + if (!keyword) { + return; + } + + onSearch(keyword); + + if (inputRef.current) { + inputRef.current.blur(); + } + }; + + return ( + + + Back Icon + + + + + + ); +} diff --git a/packages/shared/layout/Header/index.tsx b/packages/shared/layout/Header/index.tsx new file mode 100644 index 00000000..5f4c9f5d --- /dev/null +++ b/packages/shared/layout/Header/index.tsx @@ -0,0 +1,27 @@ +import { usePageType } from '../../hooks/usePageType'; +import { AppType } from '../../types/app'; +import DefaultHeader from './DefaultHeader'; +import DetailHeader from './DetailHeader'; +import SearchHeader from './SearchHeader'; +import { useHeader } from './useHeader'; + +const Headers = { + DEFAULT: (props: HeaderProps) => , + DETAIL: (props: HeaderProps) => , + SEARCH: () => , +}; + +export type HeaderProps = { + appType: AppType; +}; + +export default function Header({ appType }: HeaderProps) { + const { pageType } = usePageType(); + const { headerType } = useHeader(); + + if (!pageType) { + return null; + } + + return Headers[headerType]({ appType }); +} diff --git a/packages/shared/layout/Header/useHeader.ts b/packages/shared/layout/Header/useHeader.ts new file mode 100644 index 00000000..6a283b0d --- /dev/null +++ b/packages/shared/layout/Header/useHeader.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react'; + +import HEADER_TYPE from '../../constants/headerType'; +import { usePageType } from '../../hooks/usePageType'; +import { HeaderType } from '../../types/header'; +import { getHeaderType } from './utils'; + +export const useHeader = () => { + const { pageType } = usePageType(); + const [headerType, setHeaderType] = useState(HEADER_TYPE.DEFAULT); + + useEffect(() => { + if (pageType) { + setHeaderType(getHeaderType(pageType)); + } + }, [pageType]); + + return { headerType }; +}; diff --git a/packages/shared/layout/Header/utils.ts b/packages/shared/layout/Header/utils.ts new file mode 100644 index 00000000..f599537e --- /dev/null +++ b/packages/shared/layout/Header/utils.ts @@ -0,0 +1,28 @@ +import headerTitle from '../../constants/headerTitle'; +import HEADER_TYPE from '../../constants/headerType'; +import PAGE_TYPE from '../../constants/pageType'; +import { HeaderType } from '../../types/header'; +import { PageType } from '../../types/page'; + +export const getHeaderType = (pageType: PageType): HeaderType => { + if ( + pageType === PAGE_TYPE.VOLUNTEERS || + pageType === PAGE_TYPE.ANIMALS || + pageType === PAGE_TYPE.CHATTINGS || + pageType === PAGE_TYPE.MYPAGE + ) { + return HEADER_TYPE.DEFAULT; + } + + if ( + pageType === PAGE_TYPE.VOLUNTEERS_SEARCH || + pageType === PAGE_TYPE.ANIMALS_SEARCH + ) { + return HEADER_TYPE.SEARCH; + } + + return HEADER_TYPE.DETAIL; +}; + +export const getHeaderTitle = (pageType: PageType): string => + headerTitle[pageType]; diff --git a/packages/shared/layout/index.tsx b/packages/shared/layout/index.tsx new file mode 100644 index 00000000..02d7a7f5 --- /dev/null +++ b/packages/shared/layout/index.tsx @@ -0,0 +1,22 @@ +import { Box, Container } from '@chakra-ui/react'; +import { Outlet } from 'react-router-dom'; + +import { AppType } from '../types/app'; +import BottomNavBar from './BottomNavBar'; +import Header from './Header'; + +type LayoutProps = { + appType: AppType; +}; + +export default function Layout({ appType }: LayoutProps) { + return ( + +
+ + + + + + ); +} diff --git a/packages/ui/package.json b/packages/shared/package.json similarity index 80% rename from packages/ui/package.json rename to packages/shared/package.json index 833d471d..488ccc79 100644 --- a/packages/ui/package.json +++ b/packages/shared/package.json @@ -1,5 +1,5 @@ { - "name": "ui", + "name": "shared", "version": "0.0.0", "license": "MIT", "main": "./index.tsx", @@ -11,14 +11,16 @@ "@chakra-ui/react": "^2.8.1", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "axios": "^1.6.0", "framer-motion": "^10.16.4", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.17.0", + "zustand": "^4.4.4" }, "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", - "@vitejs/plugin-react-swc": "^3.4.0", "eslint": "^8.45.0", "eslint-config-custom": "workspace:*", "tsconfig": "workspace:*", diff --git a/packages/shared/store/detailHeaderStore.ts b/packages/shared/store/detailHeaderStore.ts new file mode 100644 index 00000000..6caad5c9 --- /dev/null +++ b/packages/shared/store/detailHeaderStore.ts @@ -0,0 +1,19 @@ +import { create } from 'zustand'; + +type DeleteFunction = (id: number) => void; +interface DetailHeaderState { + onDelete: DeleteFunction; +} + +interface DetailHeaderActions { + setOnDelete: (onDelete: DeleteFunction) => void; +} + +const useDetailHeaderStore = create( + (set) => ({ + onDelete: () => {}, + setOnDelete: (onDelete: DeleteFunction) => set(() => ({ onDelete })), + }), +); + +export default useDetailHeaderStore; diff --git a/packages/shared/store/searchHeaderStore.ts b/packages/shared/store/searchHeaderStore.ts new file mode 100644 index 00000000..38e94ec4 --- /dev/null +++ b/packages/shared/store/searchHeaderStore.ts @@ -0,0 +1,24 @@ +import { create } from 'zustand'; + +type SearchFunction = (keyword: string) => void; + +interface SearchHeaderState { + keyword: string; + onSearch: SearchFunction; +} + +interface SearchHeaderActions { + setKeyword: (keyword: string) => void; + setOnSearch: (onSearch: SearchFunction) => void; +} + +const useSearchHeaderStore = create( + (set) => ({ + keyword: '', + onSearch: () => {}, + setKeyword: (keyword: string) => set(() => ({ keyword })), + setOnSearch: (onSearch: SearchFunction) => set(() => ({ onSearch })), + }), +); + +export default useSearchHeaderStore; diff --git a/packages/shared/svg.d.ts b/packages/shared/svg.d.ts new file mode 100644 index 00000000..cdb2b1a9 --- /dev/null +++ b/packages/shared/svg.d.ts @@ -0,0 +1,4 @@ +declare module '*.svg' { + const content: string; + export default content; +} diff --git a/packages/shared/theme/index.ts b/packages/shared/theme/index.ts new file mode 100644 index 00000000..7a98714f --- /dev/null +++ b/packages/shared/theme/index.ts @@ -0,0 +1,17 @@ +import { extendTheme } from '@chakra-ui/react'; + +const theme = extendTheme({ + fonts: { + heading: `'IBMPlexSans-Heading'`, + body: `'IBMPlexSans-Body'`, + }, + styles: { + global: { + body: { + 'overscroll-behavior': 'none', + }, + }, + }, +}); + +export default theme; diff --git a/packages/ui/tsconfig.json b/packages/shared/tsconfig.json similarity index 60% rename from packages/ui/tsconfig.json rename to packages/shared/tsconfig.json index ad816cf5..2719d08e 100644 --- a/packages/ui/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -1,11 +1,15 @@ { "extends": "tsconfig/base.json", - "include": ["."], + "include": [".", "svg.d.ts"], "exclude": ["dist", "build", "node_modules"], "compilerOptions": { "jsx": "react-jsx", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", - "target": "es6" + "target": "es6", + "baseUrl": ".", + "paths": { + "@/*": ["../../packages/shared/*"] + } } } diff --git a/packages/shared/types/apis/auth.ts b/packages/shared/types/apis/auth.ts new file mode 100644 index 00000000..71c60fc2 --- /dev/null +++ b/packages/shared/types/apis/auth.ts @@ -0,0 +1,17 @@ +export type checkDuplicatedEmailRequestData = { + email: string; +}; + +export type SigninRequestData = checkDuplicatedEmailRequestData & { + password: string; +}; + +export type SigninResponseData = { + accessToken: string; + useId: number; + role: string; +}; + +export type checkDuplicatedEmailResponseData = { + isDuplicated: boolean; +}; diff --git a/packages/shared/types/apis/error.ts b/packages/shared/types/apis/error.ts new file mode 100644 index 00000000..46ec5774 --- /dev/null +++ b/packages/shared/types/apis/error.ts @@ -0,0 +1,4 @@ +export type ErrorResponseData = { + errorCode: string; + message: string; +}; diff --git a/packages/shared/types/app.ts b/packages/shared/types/app.ts new file mode 100644 index 00000000..82b07294 --- /dev/null +++ b/packages/shared/types/app.ts @@ -0,0 +1,3 @@ +import APP_TYPE from '../constants/appType'; + +export type AppType = keyof typeof APP_TYPE; diff --git a/packages/shared/types/header.ts b/packages/shared/types/header.ts new file mode 100644 index 00000000..3ded0871 --- /dev/null +++ b/packages/shared/types/header.ts @@ -0,0 +1,3 @@ +import HEADER_TYPE from '../constants/headerType'; + +export type HeaderType = keyof typeof HEADER_TYPE; diff --git a/packages/shared/types/page.ts b/packages/shared/types/page.ts new file mode 100644 index 00000000..e5affb64 --- /dev/null +++ b/packages/shared/types/page.ts @@ -0,0 +1,3 @@ +import PAGE_TYPE from '../constants/pageType'; + +export type PageType = keyof typeof PAGE_TYPE; diff --git a/packages/shared/utils/date.ts b/packages/shared/utils/date.ts new file mode 100644 index 00000000..cd24fe5e --- /dev/null +++ b/packages/shared/utils/date.ts @@ -0,0 +1,36 @@ +import { MILISECONDS } from '../constants/date'; + +export const createFormattedTime = ( + date: Date, + format = 'YYYY.MM.DD', +): string => { + const formatReplacements: Record = { + YYYY: String(date.getFullYear()), + YY: String(date.getFullYear()).substring(2, 4), + MM: String(date.getMonth() + 1).padStart(2, '0'), + DD: String(date.getDate()).padStart(2, '0'), + hh: String(date.getHours()).padStart(2, '0'), + mm: String(date.getMinutes()).padStart(2, '0'), + }; + + const formattedTime = format.replace( + /YYYY|YY|MM|DD|hh|mm/g, + (match) => formatReplacements[match], + ); + + return formattedTime; +}; + +export const isSameDay = (a: Date, b: Date): boolean => { + const diff = (a.getTime() - b.getTime()) / 1000; + + return Math.trunc(diff / MILISECONDS.DAY) === 0; +}; + +export function getDDay(deadLine: string) { + const deadLineDate = new Date(deadLine).getTime(); + const currentDate = new Date().getTime(); + const diffDate = deadLineDate - currentDate; + + return Math.floor(diffDate / (1000 * MILISECONDS.DAY)).toString(); +} diff --git a/packages/ui/components/CustomButton.tsx b/packages/ui/components/CustomButton.tsx deleted file mode 100644 index 1748297e..00000000 --- a/packages/ui/components/CustomButton.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Button } from '@chakra-ui/react'; - -export function CustomButton() { - return ; -} diff --git a/packages/ui/components/Header.tsx b/packages/ui/components/Header.tsx deleted file mode 100644 index 80f3e150..00000000 --- a/packages/ui/components/Header.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function Header() { - return
커스텀 헤더
; -} diff --git a/packlint.config.mjs b/packlint.config.mjs index 03280a48..67202395 100644 --- a/packlint.config.mjs +++ b/packlint.config.mjs @@ -1,3 +1,7 @@ export default { - files: ['./package.json', './packages/*/package.json', './apps/*/package.json'], + files: [ + './package.json', + './packages/*/package.json', + './apps/*/package.json', + ], }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3342c43..4f1d6e2f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,19 +10,19 @@ importers: devDependencies: '@commitlint/cli': specifier: ^18.2.0 - version: 18.2.0(typescript@5.0.2) + version: 18.2.0(typescript@5.2.2) '@commitlint/config-conventional': specifier: ^18.1.0 version: 18.1.0 commitizen: specifier: ^4.3.0 - version: 4.3.0(typescript@5.0.2) + version: 4.3.0(typescript@5.2.2) cz-customizable: specifier: ^7.0.0 version: 7.0.0 eslint: - specifier: ^7.32.0 - version: 7.32.0 + specifier: ^8.45.0 + version: 8.53.0 eslint-config-custom: specifier: workspace:* version: link:packages/eslint-config-custom @@ -36,8 +36,8 @@ importers: specifier: ^0.2.4 version: 0.2.4(typanion@3.14.0) prettier: - specifier: ^2.5.1 - version: 2.8.0 + specifier: 3.0.3 + version: 3.0.3 turbo: specifier: latest version: 1.10.16 @@ -46,13 +46,13 @@ importers: dependencies: '@chakra-ui/react': specifier: ^2.8.1 - version: 2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.15)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0) + version: 2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0) '@emotion/react': specifier: ^11.11.1 - version: 11.11.1(@types/react@18.2.15)(react@18.2.0) + version: 11.11.1(@types/react@18.2.33)(react@18.2.0) '@emotion/styled': specifier: ^11.11.0 - version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.15)(react@18.2.0) + version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) '@hookform/resolvers': specifier: ^3.3.2 version: 3.3.2(react-hook-form@7.47.0) @@ -85,59 +85,59 @@ importers: version: 7.47.0(react@18.2.0) react-router-dom: specifier: ^6.17.0 - version: 6.17.0(react-dom@18.2.0)(react@18.2.0) - ui: + version: 6.18.0(react-dom@18.2.0)(react@18.2.0) + shared: specifier: workspace:* - version: link:../../packages/ui + version: link:../../packages/shared zod: specifier: ^3.22.4 version: 3.22.4 zustand: specifier: ^4.4.4 - version: 4.4.4(@types/react@18.2.15)(react@18.2.0) + version: 4.4.5(@types/react@18.2.33)(react@18.2.0) devDependencies: '@types/node': specifier: ^20.8.10 version: 20.8.10 '@types/react': specifier: ^18.2.15 - version: 18.2.15 + version: 18.2.33 '@types/react-dom': specifier: ^18.2.7 - version: 18.2.7 + version: 18.2.14 '@vitejs/plugin-react-swc': specifier: ^3.4.0 - version: 3.4.0(vite@4.4.5) + version: 3.4.1(vite@4.5.0) eslint: specifier: ^8.45.0 - version: 8.45.0 + version: 8.52.0 eslint-config-custom: specifier: workspace:* version: link:../../packages/eslint-config-custom msw: specifier: ^2.0.1 - version: 2.0.1(typescript@5.0.2) + version: 2.0.1(typescript@5.2.2) tsconfig: specifier: workspace:* version: link:../../packages/tsconfig typescript: specifier: ^5.0.2 - version: 5.0.2 + version: 5.2.2 vite: specifier: ^4.4.5 - version: 4.4.5(@types/node@20.8.10) + version: 4.5.0(@types/node@20.8.10) apps/volunteer: dependencies: '@chakra-ui/react': specifier: ^2.8.1 - version: 2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.15)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0) + version: 2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0) '@emotion/react': specifier: ^11.11.1 - version: 11.11.1(@types/react@18.2.15)(react@18.2.0) + version: 11.11.1(@types/react@18.2.33)(react@18.2.0) '@emotion/styled': specifier: ^11.11.0 - version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.15)(react@18.2.0) + version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) '@hookform/resolvers': specifier: ^3.3.2 version: 3.3.2(react-hook-form@7.47.0) @@ -170,99 +170,99 @@ importers: version: 7.47.0(react@18.2.0) react-router-dom: specifier: ^6.17.0 - version: 6.17.0(react-dom@18.2.0)(react@18.2.0) - ui: + version: 6.18.0(react-dom@18.2.0)(react@18.2.0) + shared: specifier: workspace:* - version: link:../../packages/ui + version: link:../../packages/shared zod: specifier: ^3.22.4 version: 3.22.4 zustand: specifier: ^4.4.4 - version: 4.4.4(@types/react@18.2.15)(react@18.2.0) + version: 4.4.5(@types/react@18.2.33)(react@18.2.0) devDependencies: '@types/node': specifier: ^20.8.10 version: 20.8.10 '@types/react': specifier: ^18.2.15 - version: 18.2.15 + version: 18.2.33 '@types/react-dom': specifier: ^18.2.7 - version: 18.2.7 + version: 18.2.14 '@vitejs/plugin-react-swc': specifier: ^3.4.0 - version: 3.4.0(vite@4.4.5) + version: 3.4.1(vite@4.5.0) eslint: specifier: ^8.45.0 - version: 8.45.0 + version: 8.52.0 eslint-config-custom: specifier: workspace:* version: link:../../packages/eslint-config-custom + msw: + specifier: ^2.0.1 + version: 2.0.1(typescript@5.2.2) tsconfig: specifier: workspace:* version: link:../../packages/tsconfig typescript: specifier: ^5.0.2 - version: 5.0.2 + version: 5.2.2 vite: specifier: ^4.4.5 - version: 4.4.5(@types/node@20.8.10) + version: 4.5.0(@types/node@20.8.10) packages/eslint-config-custom: dependencies: '@typescript-eslint/eslint-plugin': specifier: ^5.30.7 - version: 5.44.0(@typescript-eslint/parser@5.44.0)(eslint@8.45.0)(typescript@5.0.2) + version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/parser': specifier: ^5.30.7 - version: 5.44.0(eslint@8.45.0)(typescript@5.0.2) + version: 5.62.0(eslint@8.53.0)(typescript@5.2.2) eslint-config-prettier: specifier: ^8.5.0 - version: 8.5.0(eslint@8.45.0) + version: 8.10.0(eslint@8.53.0) eslint-plugin-import: specifier: ^2.29.0 - version: 2.29.0(@typescript-eslint/parser@5.44.0)(eslint@8.45.0) + version: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint@8.53.0) eslint-plugin-prettier: specifier: ^5.0.1 - version: 5.0.1(eslint-config-prettier@8.5.0)(eslint@8.45.0)(prettier@3.0.3) + version: 5.0.1(eslint-config-prettier@8.10.0)(eslint@8.53.0)(prettier@3.0.3) eslint-plugin-react: specifier: ^7.33.2 - version: 7.33.2(eslint@8.45.0) + version: 7.33.2(eslint@8.53.0) eslint-plugin-react-hooks: specifier: ^4.6.0 - version: 4.6.0(eslint@8.45.0) + version: 4.6.0(eslint@8.53.0) eslint-plugin-react-refresh: specifier: ^0.4.3 - version: 0.4.3(eslint@8.45.0) + version: 0.4.4(eslint@8.53.0) eslint-plugin-simple-import-sort: specifier: ^10.0.0 - version: 10.0.0(eslint@8.45.0) + version: 10.0.0(eslint@8.53.0) eslint-plugin-unused-imports: specifier: ^3.0.0 - version: 3.0.0(@typescript-eslint/eslint-plugin@5.44.0)(eslint@8.45.0) - devDependencies: - '@vitejs/plugin-react-swc': - specifier: ^3.4.0 - version: 3.4.0(vite@4.4.5) - - packages/tsconfig: + version: 3.0.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint@8.53.0) devDependencies: '@vitejs/plugin-react-swc': specifier: ^3.4.0 - version: 3.4.0(vite@4.4.5) + version: 3.4.1(vite@4.5.0) - packages/ui: + packages/shared: dependencies: '@chakra-ui/react': specifier: ^2.8.1 - version: 2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.15)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0) + version: 2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0) '@emotion/react': specifier: ^11.11.1 - version: 11.11.1(@types/react@18.2.15)(react@18.2.0) + version: 11.11.1(@types/react@18.2.33)(react@18.2.0) '@emotion/styled': specifier: ^11.11.0 - version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.15)(react@18.2.0) + version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) + axios: + specifier: ^1.6.0 + version: 1.6.0 framer-motion: specifier: ^10.16.4 version: 10.16.4(react-dom@18.2.0)(react@18.2.0) @@ -272,19 +272,22 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-router-dom: + specifier: ^6.17.0 + version: 6.18.0(react-dom@18.2.0)(react@18.2.0) + zustand: + specifier: ^4.4.4 + version: 4.4.5(@types/react@18.2.33)(react@18.2.0) devDependencies: '@types/react': specifier: ^18.2.15 - version: 18.2.15 + version: 18.2.33 '@types/react-dom': specifier: ^18.2.7 - version: 18.2.7 - '@vitejs/plugin-react-swc': - specifier: ^3.4.0 - version: 3.4.0(vite@4.4.5) + version: 18.2.14 eslint: specifier: ^8.45.0 - version: 8.45.0 + version: 8.53.0 eslint-config-custom: specifier: workspace:* version: link:../eslint-config-custom @@ -293,7 +296,13 @@ importers: version: link:../tsconfig typescript: specifier: ^5.0.2 - version: 5.0.2 + version: 5.2.2 + + packages/tsconfig: + devDependencies: + '@vitejs/plugin-react-swc': + specifier: ^3.4.0 + version: 3.4.1(vite@4.5.0) packages: @@ -301,12 +310,6 @@ packages: resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} engines: {node: '>=0.10.0'} - /@babel/code-frame@7.12.11: - resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==} - dependencies: - '@babel/highlight': 7.18.6 - dev: true - /@babel/code-frame@7.22.13: resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} engines: {node: '>=6.9.0'} @@ -330,15 +333,6 @@ packages: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} - /@babel/highlight@7.18.6: - resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.22.20 - chalk: 2.4.2 - js-tokens: 4.0.0 - dev: true - /@babel/highlight@7.22.20: resolution: {integrity: sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==} engines: {node: '>=6.9.0'} @@ -554,7 +548,7 @@ packages: '@emotion/react': '>=10.0.35' react: '>=18' dependencies: - '@emotion/react': 11.11.1(@types/react@18.2.15)(react@18.2.0) + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) react: 18.2.0 dev: false @@ -595,14 +589,14 @@ packages: resolution: {integrity: sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==} dev: false - /@chakra-ui/focus-lock@2.1.0(@types/react@18.2.15)(react@18.2.0): + /@chakra-ui/focus-lock@2.1.0(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==} peerDependencies: react: '>=18' dependencies: '@chakra-ui/dom-utils': 2.1.0 react: 18.2.0 - react-focus-lock: 2.9.6(@types/react@18.2.15)(react@18.2.0) + react-focus-lock: 2.9.6(@types/react@18.2.33)(react@18.2.0) transitivePeerDependencies: - '@types/react' dev: false @@ -740,7 +734,7 @@ packages: react: 18.2.0 dev: false - /@chakra-ui/modal@2.3.1(@chakra-ui/system@2.6.1)(@types/react@18.2.15)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0): + /@chakra-ui/modal@2.3.1(@chakra-ui/system@2.6.1)(@types/react@18.2.33)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==} peerDependencies: '@chakra-ui/system': '>=2.0.0' @@ -749,7 +743,7 @@ packages: react-dom: '>=18' dependencies: '@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0) - '@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.15)(react@18.2.0) + '@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.33)(react@18.2.0) '@chakra-ui/portal': 2.1.0(react-dom@18.2.0)(react@18.2.0) '@chakra-ui/react-context': 2.1.0(react@18.2.0) '@chakra-ui/react-types': 2.0.7(react@18.2.0) @@ -761,7 +755,7 @@ packages: framer-motion: 10.16.4(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.7(@types/react@18.2.15)(react@18.2.0) + react-remove-scroll: 2.5.7(@types/react@18.2.33)(react@18.2.0) transitivePeerDependencies: - '@types/react' dev: false @@ -882,8 +876,8 @@ packages: '@chakra-ui/react-env': 3.1.0(react@18.2.0) '@chakra-ui/system': 2.6.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) '@chakra-ui/utils': 2.0.15 - '@emotion/react': 11.11.1(@types/react@18.2.15)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.15)(react@18.2.0) + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -1099,7 +1093,7 @@ packages: react: 18.2.0 dev: false - /@chakra-ui/react@2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.15)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0): + /@chakra-ui/react@2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-UL9Rtj4DovP3+oVbI06gsdfyJJb+wmS2RYnGNXjW9tsjCyXxjlBw9TAUj0jyOfWe0+zd/4juL8+J+QCwmdhptg==} peerDependencies: '@emotion/react': ^11.0.0 @@ -1120,7 +1114,7 @@ packages: '@chakra-ui/counter': 2.1.0(react@18.2.0) '@chakra-ui/css-reset': 2.3.0(@emotion/react@11.11.1)(react@18.2.0) '@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.1)(react@18.2.0) - '@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.15)(react@18.2.0) + '@chakra-ui/focus-lock': 2.1.0(@types/react@18.2.33)(react@18.2.0) '@chakra-ui/form-control': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0) '@chakra-ui/hooks': 2.2.1(react@18.2.0) '@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.1)(react@18.2.0) @@ -1130,7 +1124,7 @@ packages: '@chakra-ui/live-region': 2.1.0(react@18.2.0) '@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.1)(react@18.2.0) '@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react@18.2.0) - '@chakra-ui/modal': 2.3.1(@chakra-ui/system@2.6.1)(@types/react@18.2.15)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0) + '@chakra-ui/modal': 2.3.1(@chakra-ui/system@2.6.1)(@types/react@18.2.33)(framer-motion@10.16.4)(react-dom@18.2.0)(react@18.2.0) '@chakra-ui/number-input': 2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0) '@chakra-ui/pin-input': 2.1.0(@chakra-ui/system@2.6.1)(react@18.2.0) '@chakra-ui/popover': 2.2.1(@chakra-ui/system@2.6.1)(framer-motion@10.16.4)(react@18.2.0) @@ -1161,8 +1155,8 @@ packages: '@chakra-ui/transition': 2.1.0(framer-motion@10.16.4)(react@18.2.0) '@chakra-ui/utils': 2.0.15 '@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.1)(react@18.2.0) - '@emotion/react': 11.11.1(@types/react@18.2.15)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.15)(react@18.2.0) + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) framer-motion: 10.16.4(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -1301,8 +1295,8 @@ packages: '@chakra-ui/styled-system': 2.9.1 '@chakra-ui/theme-utils': 2.0.20 '@chakra-ui/utils': 2.0.15 - '@emotion/react': 11.11.1(@types/react@18.2.15)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.15)(react@18.2.0) + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) react: 18.2.0 react-fast-compare: 3.2.2 dev: false @@ -1468,14 +1462,14 @@ packages: react: 18.2.0 dev: false - /@commitlint/cli@18.2.0(typescript@5.0.2): + /@commitlint/cli@18.2.0(typescript@5.2.2): resolution: {integrity: sha512-F/DCG791kMFmWg5eIdogakuGeg4OiI2kD430ed1a1Hh3epvrJdeIAgcGADAMIOmF+m0S1+VlIYUKG2dvQQ1Izw==} engines: {node: '>=v18'} hasBin: true dependencies: '@commitlint/format': 18.1.0 '@commitlint/lint': 18.1.0 - '@commitlint/load': 18.2.0(typescript@5.0.2) + '@commitlint/load': 18.2.0(typescript@5.2.2) '@commitlint/read': 18.1.0 '@commitlint/types': 18.1.0 execa: 5.1.1 @@ -1499,7 +1493,7 @@ packages: engines: {node: '>=v18'} dependencies: '@commitlint/types': 18.1.0 - ajv: 8.11.2 + ajv: 8.12.0 dev: true /@commitlint/ensure@18.1.0: @@ -1545,7 +1539,7 @@ packages: '@commitlint/types': 18.1.0 dev: true - /@commitlint/load@18.2.0(typescript@5.0.2): + /@commitlint/load@18.2.0(typescript@5.2.2): resolution: {integrity: sha512-xjX3d3CRlOALwImhOsmLYZh14/+gW/KxsY7+bPKrzmGuFailf9K7ckhB071oYZVJdACnpY4hDYiosFyOC+MpAA==} engines: {node: '>=v18'} dependencies: @@ -1553,10 +1547,10 @@ packages: '@commitlint/execute-rule': 18.1.0 '@commitlint/resolve-extends': 18.1.0 '@commitlint/types': 18.1.0 - '@types/node': 18.18.7 + '@types/node': 18.18.8 chalk: 4.1.2 - cosmiconfig: 8.3.6(typescript@5.0.2) - cosmiconfig-typescript-loader: 5.0.0(@types/node@18.18.7)(cosmiconfig@8.3.6)(typescript@5.0.2) + cosmiconfig: 8.3.6(typescript@5.2.2) + cosmiconfig-typescript-loader: 5.0.0(@types/node@18.18.8)(cosmiconfig@8.3.6)(typescript@5.2.2) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -1686,7 +1680,7 @@ packages: resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} dev: false - /@emotion/react@11.11.1(@types/react@18.2.15)(react@18.2.0): + /@emotion/react@11.11.1(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==} peerDependencies: '@types/react': '*' @@ -1702,7 +1696,7 @@ packages: '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) '@emotion/utils': 1.2.1 '@emotion/weak-memoize': 0.3.1 - '@types/react': 18.2.15 + '@types/react': 18.2.33 hoist-non-react-statics: 3.3.2 react: 18.2.0 dev: false @@ -1721,7 +1715,7 @@ packages: resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==} dev: false - /@emotion/styled@11.11.0(@emotion/react@11.11.1)(@types/react@18.2.15)(react@18.2.0): + /@emotion/styled@11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==} peerDependencies: '@emotion/react': ^11.0.0-rc.0 @@ -1734,11 +1728,11 @@ packages: '@babel/runtime': 7.23.2 '@emotion/babel-plugin': 11.11.0 '@emotion/is-prop-valid': 1.2.1 - '@emotion/react': 11.11.1(@types/react@18.2.15)(react@18.2.0) + '@emotion/react': 11.11.1(@types/react@18.2.33)(react@18.2.0) '@emotion/serialize': 1.1.2 '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) '@emotion/utils': 1.2.1 - '@types/react': 18.2.15 + '@types/react': 18.2.33 react: 18.2.0 dev: false @@ -1960,45 +1954,55 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.45.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.52.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.45.0 + eslint: 8.52.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@eslint-community/eslint-utils@4.4.0(eslint@8.53.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.53.0 eslint-visitor-keys: 3.4.3 /@eslint-community/regexpp@4.10.0: resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - /@eslint/eslintrc@0.4.3: - resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==} - engines: {node: ^10.12.0 || >=12.0.0} + /@eslint/eslintrc@2.1.2: + resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 7.3.1 + espree: 9.6.1 globals: 13.23.0 - ignore: 4.0.6 + ignore: 5.2.4 import-fresh: 3.3.0 - js-yaml: 3.14.1 + js-yaml: 4.1.0 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color dev: true - /@eslint/eslintrc@2.1.2: - resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} + /@eslint/eslintrc@2.1.3: + resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4 espree: 9.6.1 globals: 13.23.0 - ignore: 5.2.0 + ignore: 5.2.4 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -2006,8 +2010,13 @@ packages: transitivePeerDependencies: - supports-color - /@eslint/js@8.44.0: - resolution: {integrity: sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==} + /@eslint/js@8.52.0: + resolution: {integrity: sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@eslint/js@8.53.0: + resolution: {integrity: sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} /@hookform/resolvers@3.3.2(react-hook-form@7.47.0): @@ -2028,25 +2037,10 @@ packages: transitivePeerDependencies: - supports-color - /@humanwhocodes/config-array@0.5.0: - resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - /@humanwhocodes/module-importer@1.0.1: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - /@humanwhocodes/object-schema@1.2.1: - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - dev: true - /@humanwhocodes/object-schema@2.0.1: resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} @@ -2094,7 +2088,7 @@ packages: engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.13.0 + fastq: 1.15.0 /@open-draft/deferred-promise@2.2.0: resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} @@ -2162,8 +2156,8 @@ packages: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false - /@remix-run/router@1.10.0: - resolution: {integrity: sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==} + /@remix-run/router@1.11.0: + resolution: {integrity: sha512-BHdhcWgeiudl91HvVa2wxqZjSHbheSgIiDvxrF1VjFzBzpTtuDPkOdOi3Iqvc08kXtFkLjhbS+ML9aM8mJS+wQ==} engines: {node: '>=14.0.0'} dev: false @@ -2336,8 +2330,8 @@ packages: resolution: {integrity: sha512-/NCbMABw2uacuyE16Iwka1EzREDD50/W2ggRBad0y1WHBvAkvR9OEINxModVY7D428gXBe0igeVX7bUc9GaslQ==} dev: true - /@types/json-schema@7.0.11: - resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + /@types/json-schema@7.0.14: + resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==} dev: false /@types/json5@0.0.29: @@ -2358,8 +2352,8 @@ packages: resolution: {integrity: sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ==} dev: true - /@types/node@18.18.7: - resolution: {integrity: sha512-bw+lEsxis6eqJYW8Ql6+yTqkE6RuFtsQPSe5JxXbqYRFQEER5aJA9a5UH9igqDWm3X4iLHIKOHlnAXLM4mi7uQ==} + /@types/node@18.18.8: + resolution: {integrity: sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==} dependencies: undici-types: 5.26.5 dev: true @@ -2386,14 +2380,14 @@ packages: ts-toolbelt: 6.15.5 dev: true - /@types/react-dom@18.2.7: - resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} + /@types/react-dom@18.2.14: + resolution: {integrity: sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==} dependencies: - '@types/react': 18.2.15 + '@types/react': 18.2.33 dev: true - /@types/react@18.2.15: - resolution: {integrity: sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==} + /@types/react@18.2.33: + resolution: {integrity: sha512-v+I7S+hu3PIBoVkKGpSYYpiBT1ijqEzWpzQD62/jm4K74hPpSP7FF9BnKG6+fg2+62weJYkkBWDJlZt5JO/9hg==} dependencies: '@types/prop-types': 15.7.9 '@types/scheduler': 0.16.5 @@ -2402,16 +2396,16 @@ packages: /@types/scheduler@0.16.5: resolution: {integrity: sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==} - /@types/semver@7.3.13: - resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} + /@types/semver@7.5.4: + resolution: {integrity: sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==} dev: false /@types/statuses@2.0.3: resolution: {integrity: sha512-NwCYScf83RIwCyi5/9cXocrJB//xrqMh5PMw3mYTSFGaI3DuVjBLfO/PCk7QVAC3Da8b9NjxNmTO9Aj9T3rl/Q==} dev: true - /@typescript-eslint/eslint-plugin@5.44.0(@typescript-eslint/parser@5.44.0)(eslint@8.45.0)(typescript@5.0.2): - resolution: {integrity: sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==} + /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -2421,24 +2415,25 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.44.0(eslint@8.45.0)(typescript@5.0.2) - '@typescript-eslint/scope-manager': 5.44.0 - '@typescript-eslint/type-utils': 5.44.0(eslint@8.45.0)(typescript@5.0.2) - '@typescript-eslint/utils': 5.44.0(eslint@8.45.0)(typescript@5.0.2) + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 5.62.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/type-utils': 5.62.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/utils': 5.62.0(eslint@8.53.0)(typescript@5.2.2) debug: 4.3.4 - eslint: 8.45.0 - ignore: 5.2.0 + eslint: 8.53.0 + graphemer: 1.4.0 + ignore: 5.2.4 natural-compare-lite: 1.4.0 - regexpp: 3.2.0 - semver: 7.3.8 - tsutils: 3.21.0(typescript@5.0.2) - typescript: 5.0.2 + semver: 7.5.4 + tsutils: 3.21.0(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: false - /@typescript-eslint/parser@5.44.0(eslint@8.45.0)(typescript@5.0.2): - resolution: {integrity: sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==} + /@typescript-eslint/parser@5.62.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2447,26 +2442,26 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.44.0 - '@typescript-eslint/types': 5.44.0 - '@typescript-eslint/typescript-estree': 5.44.0(typescript@5.0.2) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) debug: 4.3.4 - eslint: 8.45.0 - typescript: 5.0.2 + eslint: 8.53.0 + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: false - /@typescript-eslint/scope-manager@5.44.0: - resolution: {integrity: sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==} + /@typescript-eslint/scope-manager@5.62.0: + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.44.0 - '@typescript-eslint/visitor-keys': 5.44.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 dev: false - /@typescript-eslint/type-utils@5.44.0(eslint@8.45.0)(typescript@5.0.2): - resolution: {integrity: sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==} + /@typescript-eslint/type-utils@5.62.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -2475,23 +2470,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.44.0(typescript@5.0.2) - '@typescript-eslint/utils': 5.44.0(eslint@8.45.0)(typescript@5.0.2) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) + '@typescript-eslint/utils': 5.62.0(eslint@8.53.0)(typescript@5.2.2) debug: 4.3.4 - eslint: 8.45.0 - tsutils: 3.21.0(typescript@5.0.2) - typescript: 5.0.2 + eslint: 8.53.0 + tsutils: 3.21.0(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: false - /@typescript-eslint/types@5.44.0: - resolution: {integrity: sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==} + /@typescript-eslint/types@5.62.0: + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: false - /@typescript-eslint/typescript-estree@5.44.0(typescript@5.0.2): - resolution: {integrity: sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==} + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.2.2): + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -2499,53 +2494,56 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.44.0 - '@typescript-eslint/visitor-keys': 5.44.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 - tsutils: 3.21.0(typescript@5.0.2) - typescript: 5.0.2 + tsutils: 3.21.0(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: false - /@typescript-eslint/utils@5.44.0(eslint@8.45.0)(typescript@5.0.2): - resolution: {integrity: sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==} + /@typescript-eslint/utils@5.62.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@types/json-schema': 7.0.11 - '@types/semver': 7.3.13 - '@typescript-eslint/scope-manager': 5.44.0 - '@typescript-eslint/types': 5.44.0 - '@typescript-eslint/typescript-estree': 5.44.0(typescript@5.0.2) - eslint: 8.45.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) + '@types/json-schema': 7.0.14 + '@types/semver': 7.5.4 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) + eslint: 8.53.0 eslint-scope: 5.1.1 - eslint-utils: 3.0.0(eslint@8.45.0) semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript dev: false - /@typescript-eslint/visitor-keys@5.44.0: - resolution: {integrity: sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==} + /@typescript-eslint/visitor-keys@5.62.0: + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.44.0 + '@typescript-eslint/types': 5.62.0 eslint-visitor-keys: 3.4.3 dev: false - /@vitejs/plugin-react-swc@3.4.0(vite@4.4.5): - resolution: {integrity: sha512-m7UaA4Uvz82N/0EOVpZL4XsFIakRqrFKeSNxa1FBLSXGvWrWRBwmZb4qxk+ZIVAZcW3c3dn5YosomDgx62XWcQ==} + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + + /@vitejs/plugin-react-swc@3.4.1(vite@4.5.0): + resolution: {integrity: sha512-7YQOQcVV5x1luD8nkbCDdyYygFvn1hjqJk68UvNAzY2QG4o4N5EwAhLLFNOcd1HrdMwDl0VElP8VutoWf9IvJg==} peerDependencies: vite: ^4 dependencies: '@swc/core': 1.3.95 - vite: 4.4.5(@types/node@20.8.10) + vite: 4.5.0(@types/node@20.8.10) transitivePeerDependencies: - '@swc/helpers' dev: true @@ -2572,29 +2570,15 @@ packages: through: 2.3.8 dev: true - /acorn-jsx@5.3.2(acorn@7.4.1): + /acorn-jsx@5.3.2(acorn@8.11.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 7.4.1 - dev: true + acorn: 8.11.2 - /acorn-jsx@5.3.2(acorn@8.10.0): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.10.0 - - /acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /acorn@8.10.0: - resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} + /acorn@8.11.2: + resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} engines: {node: '>=0.4.0'} hasBin: true @@ -2606,8 +2590,8 @@ packages: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - /ajv@8.11.2: - resolution: {integrity: sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==} + /ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} dependencies: fast-deep-equal: 3.1.3 json-schema-traverse: 1.0.0 @@ -2615,11 +2599,6 @@ packages: uri-js: 4.4.1 dev: true - /ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - dev: true - /ansi-escapes@3.2.0: resolution: {integrity: sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==} engines: {node: '>=4'} @@ -2683,12 +2662,6 @@ packages: picomatch: 2.3.1 dev: true - /argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - dependencies: - sprintf-js: 1.0.3 - dev: true - /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2785,11 +2758,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - dev: true - /asynciterator.prototype@1.0.0: resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} dependencies: @@ -2954,7 +2922,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /cli-cursor@2.1.0: @@ -3059,13 +3027,13 @@ packages: engines: {node: '>=16'} dev: true - /commitizen@4.3.0(typescript@5.0.2): + /commitizen@4.3.0(typescript@5.2.2): resolution: {integrity: sha512-H0iNtClNEhT0fotHvGV3E9tDejDeS04sN1veIebsKYGMuGscFaswRoYJKmT3eW85eIJAs0F28bG2+a/9wCOfPw==} engines: {node: '>= 12'} hasBin: true dependencies: cachedir: 2.3.0 - cz-conventional-changelog: 3.3.0(typescript@5.0.2) + cz-conventional-changelog: 3.3.0(typescript@5.2.2) dedent: 0.7.0 detect-indent: 6.1.0 find-node-modules: 2.1.3 @@ -3140,7 +3108,7 @@ packages: toggle-selection: 1.0.6 dev: false - /cosmiconfig-typescript-loader@5.0.0(@types/node@18.18.7)(cosmiconfig@8.3.6)(typescript@5.0.2): + /cosmiconfig-typescript-loader@5.0.0(@types/node@18.18.8)(cosmiconfig@8.3.6)(typescript@5.2.2): resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} engines: {node: '>=v16'} peerDependencies: @@ -3148,10 +3116,10 @@ packages: cosmiconfig: '>=8.2' typescript: '>=4' dependencies: - '@types/node': 18.18.7 - cosmiconfig: 8.3.6(typescript@5.0.2) - jiti: 1.20.0 - typescript: 5.0.2 + '@types/node': 18.18.8 + cosmiconfig: 8.3.6(typescript@5.2.2) + jiti: 1.21.0 + typescript: 5.2.2 dev: true /cosmiconfig@7.1.0: @@ -3164,7 +3132,7 @@ packages: path-type: 4.0.0 yaml: 1.10.2 - /cosmiconfig@8.3.6(typescript@5.0.2): + /cosmiconfig@8.3.6(typescript@5.2.2): resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} peerDependencies: @@ -3177,7 +3145,7 @@ packages: js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 - typescript: 5.0.2 + typescript: 5.2.2 dev: true /cross-spawn@7.0.3: @@ -3197,18 +3165,18 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - /cz-conventional-changelog@3.3.0(typescript@5.0.2): + /cz-conventional-changelog@3.3.0(typescript@5.2.2): resolution: {integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==} engines: {node: '>= 10'} dependencies: chalk: 2.4.2 - commitizen: 4.3.0(typescript@5.0.2) + commitizen: 4.3.0(typescript@5.2.2) conventional-commit-types: 3.0.0 lodash.map: 4.6.0 longest: 2.0.1 - word-wrap: 1.2.3 + word-wrap: 1.2.5 optionalDependencies: - '@commitlint/load': 18.2.0(typescript@5.0.2) + '@commitlint/load': 18.2.0(typescript@5.2.2) transitivePeerDependencies: - typescript dev: true @@ -3222,7 +3190,7 @@ packages: inquirer: 6.5.2 lodash: 4.17.21 temp: 0.9.4 - word-wrap: 1.2.3 + word-wrap: 1.2.5 dev: true /dargs@7.0.0: @@ -3238,7 +3206,7 @@ packages: supports-color: optional: true dependencies: - ms: 2.1.2 + ms: 2.1.3 dev: false /debug@4.3.4: @@ -3381,13 +3349,6 @@ packages: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: true - /enquirer@2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} - engines: {node: '>=8.6'} - dependencies: - ansi-colors: 4.1.3 - dev: true - /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -3446,7 +3407,7 @@ packages: define-properties: 1.2.1 es-abstract: 1.22.3 es-set-tostringtag: 2.0.2 - function-bind: 1.1.1 + function-bind: 1.1.2 get-intrinsic: 1.2.2 globalthis: 1.0.3 has-property-descriptors: 1.0.1 @@ -3524,13 +3485,13 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - /eslint-config-prettier@8.5.0(eslint@8.45.0): - resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} + /eslint-config-prettier@8.10.0(eslint@8.53.0): + resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.45.0 + eslint: 8.53.0 dev: false /eslint-import-resolver-node@0.3.9: @@ -3543,7 +3504,7 @@ packages: - supports-color dev: false - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.44.0)(eslint-import-resolver-node@0.3.9)(eslint@8.45.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@8.53.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -3564,15 +3525,15 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.44.0(eslint@8.45.0)(typescript@5.0.2) + '@typescript-eslint/parser': 5.62.0(eslint@8.53.0)(typescript@5.2.2) debug: 3.2.7 - eslint: 8.45.0 + eslint: 8.53.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color dev: false - /eslint-plugin-import@2.29.0(@typescript-eslint/parser@5.44.0)(eslint@8.45.0): + /eslint-plugin-import@2.29.0(@typescript-eslint/parser@5.62.0)(eslint@8.53.0): resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==} engines: {node: '>=4'} peerDependencies: @@ -3582,16 +3543,16 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.44.0(eslint@8.45.0)(typescript@5.0.2) + '@typescript-eslint/parser': 5.62.0(eslint@8.53.0)(typescript@5.2.2) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.45.0 + eslint: 8.53.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.44.0)(eslint-import-resolver-node@0.3.9)(eslint@8.45.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@8.53.0) hasown: 2.0.0 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -3607,7 +3568,7 @@ packages: - supports-color dev: false - /eslint-plugin-prettier@5.0.1(eslint-config-prettier@8.5.0)(eslint@8.45.0)(prettier@3.0.3): + /eslint-plugin-prettier@5.0.1(eslint-config-prettier@8.10.0)(eslint@8.53.0)(prettier@3.0.3): resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -3621,31 +3582,31 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 8.45.0 - eslint-config-prettier: 8.5.0(eslint@8.45.0) + eslint: 8.53.0 + eslint-config-prettier: 8.10.0(eslint@8.53.0) prettier: 3.0.3 prettier-linter-helpers: 1.0.0 synckit: 0.8.5 dev: false - /eslint-plugin-react-hooks@4.6.0(eslint@8.45.0): + /eslint-plugin-react-hooks@4.6.0(eslint@8.53.0): resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: - eslint: 8.45.0 + eslint: 8.53.0 dev: false - /eslint-plugin-react-refresh@0.4.3(eslint@8.45.0): - resolution: {integrity: sha512-Hh0wv8bUNY877+sI0BlCUlsS0TYYQqvzEwJsJJPM2WF4RnTStSnSR3zdJYa2nPOJgg3UghXi54lVyMSmpCalzA==} + /eslint-plugin-react-refresh@0.4.4(eslint@8.53.0): + resolution: {integrity: sha512-eD83+65e8YPVg6603Om2iCIwcQJf/y7++MWm4tACtEswFLYMwxwVWAfwN+e19f5Ad/FOyyNg9Dfi5lXhH3Y3rA==} peerDependencies: eslint: '>=7' dependencies: - eslint: 8.45.0 + eslint: 8.53.0 dev: false - /eslint-plugin-react@7.33.2(eslint@8.45.0): + /eslint-plugin-react@7.33.2(eslint@8.53.0): resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} engines: {node: '>=4'} peerDependencies: @@ -3656,7 +3617,7 @@ packages: array.prototype.tosorted: 1.1.2 doctrine: 2.1.0 es-iterator-helpers: 1.0.15 - eslint: 8.45.0 + eslint: 8.53.0 estraverse: 5.3.0 jsx-ast-utils: 3.3.5 minimatch: 3.1.2 @@ -3670,15 +3631,15 @@ packages: string.prototype.matchall: 4.0.10 dev: false - /eslint-plugin-simple-import-sort@10.0.0(eslint@8.45.0): + /eslint-plugin-simple-import-sort@10.0.0(eslint@8.53.0): resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==} peerDependencies: eslint: '>=5.0.0' dependencies: - eslint: 8.45.0 + eslint: 8.53.0 dev: false - /eslint-plugin-unused-imports@3.0.0(@typescript-eslint/eslint-plugin@5.44.0)(eslint@8.45.0): + /eslint-plugin-unused-imports@3.0.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint@8.53.0): resolution: {integrity: sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -3688,8 +3649,8 @@ packages: '@typescript-eslint/eslint-plugin': optional: true dependencies: - '@typescript-eslint/eslint-plugin': 5.44.0(@typescript-eslint/parser@5.44.0)(eslint@8.45.0)(typescript@5.0.2) - eslint: 8.45.0 + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.53.0)(typescript@5.2.2) + eslint: 8.53.0 eslint-rule-composer: 0.3.0 dev: false @@ -3704,6 +3665,7 @@ packages: dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 + dev: false /eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} @@ -3712,96 +3674,70 @@ packages: esrecurse: 4.3.0 estraverse: 5.3.0 - /eslint-utils@2.1.0: - resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} - engines: {node: '>=6'} - dependencies: - eslint-visitor-keys: 1.3.0 - dev: true - - /eslint-utils@3.0.0(eslint@8.45.0): - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} - peerDependencies: - eslint: '>=5' - dependencies: - eslint: 8.45.0 - eslint-visitor-keys: 2.1.0 - dev: false - - /eslint-visitor-keys@1.3.0: - resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} - engines: {node: '>=4'} - dev: true - - /eslint-visitor-keys@2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - /eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /eslint@7.32.0: - resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==} - engines: {node: ^10.12.0 || >=12.0.0} + /eslint@8.52.0: + resolution: {integrity: sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true dependencies: - '@babel/code-frame': 7.12.11 - '@eslint/eslintrc': 0.4.3 - '@humanwhocodes/config-array': 0.5.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0) + '@eslint-community/regexpp': 4.10.0 + '@eslint/eslintrc': 2.1.2 + '@eslint/js': 8.52.0 + '@humanwhocodes/config-array': 0.11.13 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 debug: 4.3.4 doctrine: 3.0.0 - enquirer: 2.3.6 escape-string-regexp: 4.0.0 - eslint-scope: 5.1.1 - eslint-utils: 2.1.0 - eslint-visitor-keys: 2.1.0 - espree: 7.3.1 - esquery: 1.4.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 - functional-red-black-tree: 1.0.1 - glob-parent: 5.1.2 - globals: 13.18.0 - ignore: 4.0.6 - import-fresh: 3.3.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.23.0 + graphemer: 1.4.0 + ignore: 5.2.4 imurmurhash: 0.1.4 is-glob: 4.0.3 - js-yaml: 3.14.1 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 - optionator: 0.9.1 - progress: 2.0.3 - regexpp: 3.2.0 - semver: 7.3.8 + optionator: 0.9.3 strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 - table: 6.8.1 text-table: 0.2.0 - v8-compile-cache: 2.3.0 transitivePeerDependencies: - supports-color dev: true - /eslint@8.45.0: - resolution: {integrity: sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==} + /eslint@8.53.0: + resolution: {integrity: sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.45.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) '@eslint-community/regexpp': 4.10.0 - '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.44.0 + '@eslint/eslintrc': 2.1.3 + '@eslint/js': 8.53.0 '@humanwhocodes/config-array': 0.11.13 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 @@ -3819,7 +3755,7 @@ packages: glob-parent: 6.0.2 globals: 13.23.0 graphemer: 1.4.0 - ignore: 5.2.0 + ignore: 5.2.4 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -3835,35 +3771,14 @@ packages: transitivePeerDependencies: - supports-color - /espree@7.3.1: - resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - acorn: 7.4.1 - acorn-jsx: 5.3.2(acorn@7.4.1) - eslint-visitor-keys: 1.3.0 - dev: true - /espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.10.0 - acorn-jsx: 5.3.2(acorn@8.10.0) + acorn: 8.11.2 + acorn-jsx: 5.3.2(acorn@8.11.2) eslint-visitor-keys: 3.4.3 - /esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - dev: true - - /esquery@1.4.0: - resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: true - /esquery@1.5.0: resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} @@ -3879,6 +3794,7 @@ packages: /estraverse@4.3.0: resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} engines: {node: '>=4.0'} + dev: false /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} @@ -3975,8 +3891,8 @@ packages: /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - /fastq@1.13.0: - resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: reusify: 1.0.4 @@ -3998,7 +3914,7 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: - flat-cache: 3.0.4 + flat-cache: 3.1.1 /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} @@ -4056,15 +3972,16 @@ packages: resolve-dir: 1.0.1 dev: true - /flat-cache@3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} + /flat-cache@3.1.1: + resolution: {integrity: sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==} + engines: {node: '>=12.0.0'} dependencies: - flatted: 3.2.7 + flatted: 3.2.9 + keyv: 4.5.4 rimraf: 3.0.2 - /flatted@3.2.7: - resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + /flatted@3.2.9: + resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} /focus-lock@1.0.0: resolution: {integrity: sha512-a8Ge6cdKh9za/GZR/qtigTAk7SrGore56EFcoMshClsh7FLk1zwszc/ltuMfKhx56qeuyL/jWQ4J4axou0iJ9w==} @@ -4161,18 +4078,14 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true dev: true optional: true - /function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: false - /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -4186,10 +4099,6 @@ packages: functions-have-names: 1.2.3 dev: false - /functional-red-black-tree@1.0.1: - resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} - dev: true - /functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: false @@ -4291,13 +4200,6 @@ packages: which: 1.3.1 dev: true - /globals@13.18.0: - resolution: {integrity: sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: true - /globals@13.23.0: resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} engines: {node: '>=8'} @@ -4318,7 +4220,7 @@ packages: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.1 - ignore: 5.2.0 + ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 dev: false @@ -4446,13 +4348,8 @@ packages: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true - /ignore@4.0.6: - resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} - engines: {node: '>= 4'} - dev: true - - /ignore@5.2.0: - resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} /import-fresh@3.3.0: @@ -4524,6 +4421,27 @@ packages: wrap-ansi: 7.0.0 dev: true + /inquirer@8.2.6: + resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} + engines: {node: '>=12.0.0'} + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.1 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 6.2.0 + dev: true + /internal-slot@1.0.6: resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} engines: {node: '>= 0.4'} @@ -4805,8 +4723,8 @@ packages: set-function-name: 2.0.1 dev: false - /jiti@1.20.0: - resolution: {integrity: sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==} + /jiti@1.21.0: + resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true dev: true @@ -4818,19 +4736,15 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - /js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - dev: true - /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true dependencies: argparse: 2.0.1 + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} @@ -4874,6 +4788,11 @@ packages: object.values: 1.1.7 dev: false + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + dependencies: + json-buffer: 3.0.1 + /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -4979,10 +4898,6 @@ packages: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} dev: true - /lodash.truncate@4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - dev: true - /lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} dev: true @@ -5143,7 +5058,11 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - /msw@2.0.1(typescript@5.0.2): + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + + /msw@2.0.1(typescript@5.2.2): resolution: {integrity: sha512-eGcoo5anJmorEkGMQ48dLFx53EJlDL00GjeJtl2dRGm6WwPpzt2fYDZ9ueJ53zMNTtrNtjH9RqbDa+nz7e9Dew==} engines: {node: '>=18'} hasBin: true @@ -5168,7 +5087,7 @@ packages: formdata-node: 4.4.1 graphql: 16.8.1 headers-polyfill: 4.0.2 - inquirer: 8.2.5 + inquirer: 8.2.6 is-node-process: 1.2.0 js-levenshtein: 1.1.6 node-fetch: 2.7.0 @@ -5176,7 +5095,7 @@ packages: path-to-regexp: 6.2.1 strict-event-emitter: 0.5.1 type-fest: 2.19.0 - typescript: 5.0.2 + typescript: 5.2.2 yargs: 17.7.2 transitivePeerDependencies: - encoding @@ -5357,18 +5276,6 @@ packages: is-wsl: 2.2.0 dev: false - /optionator@0.9.1: - resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} - engines: {node: '>= 0.8.0'} - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.3 - dev: true - /optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} @@ -5552,21 +5459,10 @@ packages: fast-diff: 1.3.0 dev: false - /prettier@2.8.0: - resolution: {integrity: sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==} - engines: {node: '>=10.13.0'} - dev: true - /prettier@3.0.3: resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} engines: {node: '>=14'} hasBin: true - dev: false - - /progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - dev: true /prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -5580,8 +5476,8 @@ packages: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false - /punycode@2.1.1: - resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} /queue-microtask@1.2.3: @@ -5628,7 +5524,7 @@ packages: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} dev: false - /react-focus-lock@2.9.6(@types/react@18.2.15)(react@18.2.0): + /react-focus-lock@2.9.6(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-B7gYnCjHNrNYwY2juS71dHbf0+UpXXojt02svxybj8N5bxceAkzPChKEncHuratjUHkIFNCn06k2qj1DRlzTug==} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5638,13 +5534,13 @@ packages: optional: true dependencies: '@babel/runtime': 7.23.2 - '@types/react': 18.2.15 + '@types/react': 18.2.33 focus-lock: 1.0.0 prop-types: 15.8.1 react: 18.2.0 react-clientside-effect: 1.2.6(react@18.2.0) - use-callback-ref: 1.3.0(@types/react@18.2.15)(react@18.2.0) - use-sidecar: 1.1.2(@types/react@18.2.15)(react@18.2.0) + use-callback-ref: 1.3.0(@types/react@18.2.33)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.33)(react@18.2.0) dev: false /react-hook-form@7.47.0(react@18.2.0): @@ -5660,7 +5556,7 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: false - /react-remove-scroll-bar@2.3.4(@types/react@18.2.15)(react@18.2.0): + /react-remove-scroll-bar@2.3.4(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==} engines: {node: '>=10'} peerDependencies: @@ -5670,13 +5566,13 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.15 + '@types/react': 18.2.33 react: 18.2.0 - react-style-singleton: 2.2.1(@types/react@18.2.15)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.33)(react@18.2.0) tslib: 2.6.2 dev: false - /react-remove-scroll@2.5.7(@types/react@18.2.15)(react@18.2.0): + /react-remove-scroll@2.5.7(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==} engines: {node: '>=10'} peerDependencies: @@ -5686,39 +5582,39 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.15 + '@types/react': 18.2.33 react: 18.2.0 - react-remove-scroll-bar: 2.3.4(@types/react@18.2.15)(react@18.2.0) - react-style-singleton: 2.2.1(@types/react@18.2.15)(react@18.2.0) + react-remove-scroll-bar: 2.3.4(@types/react@18.2.33)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.33)(react@18.2.0) tslib: 2.6.2 - use-callback-ref: 1.3.0(@types/react@18.2.15)(react@18.2.0) - use-sidecar: 1.1.2(@types/react@18.2.15)(react@18.2.0) + use-callback-ref: 1.3.0(@types/react@18.2.33)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.33)(react@18.2.0) dev: false - /react-router-dom@6.17.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==} + /react-router-dom@6.18.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Ubrue4+Ercc/BoDkFQfc6og5zRQ4A8YxSO3Knsne+eRbZ+IepAsK249XBH/XaFuOYOYr3L3r13CXTLvYt5JDjw==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' dependencies: - '@remix-run/router': 1.10.0 + '@remix-run/router': 1.11.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-router: 6.17.0(react@18.2.0) + react-router: 6.18.0(react@18.2.0) dev: false - /react-router@6.17.0(react@18.2.0): - resolution: {integrity: sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==} + /react-router@6.18.0(react@18.2.0): + resolution: {integrity: sha512-vk2y7Dsy8wI02eRRaRmOs9g2o+aE72YCx5q9VasT1N9v+lrdB79tIqrjMfByHiY5+6aYkH2rUa5X839nwWGPDg==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' dependencies: - '@remix-run/router': 1.10.0 + '@remix-run/router': 1.11.0 react: 18.2.0 dev: false - /react-style-singleton@2.2.1(@types/react@18.2.15)(react@18.2.0): + /react-style-singleton@2.2.1(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} peerDependencies: @@ -5728,7 +5624,7 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.15 + '@types/react': 18.2.33 get-nonce: 1.0.1 invariant: 2.2.4 react: 18.2.0 @@ -5810,10 +5706,6 @@ packages: set-function-name: 2.0.1 dev: false - /regexpp@3.2.0: - resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} - engines: {node: '>=8'} - /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -5915,7 +5807,7 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /run-applescript@5.0.0: @@ -5990,12 +5882,6 @@ packages: hasBin: true dev: false - /semver@7.3.8: - resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} - engines: {node: '>=10'} - dependencies: - lru-cache: 6.0.0 - /semver@7.5.4: resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} @@ -6053,15 +5939,6 @@ packages: engines: {node: '>=8'} dev: false - /slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - /slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} @@ -6113,10 +5990,6 @@ packages: engines: {node: '>= 10.x'} dev: true - /sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - dev: true - /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -6286,17 +6159,6 @@ packages: tslib: 2.6.2 dev: false - /table@6.8.1: - resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==} - engines: {node: '>=10.0.0'} - dependencies: - ajv: 8.11.2 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - /temp@0.9.4: resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==} engines: {node: '>=6.0.0'} @@ -6386,14 +6248,14 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - /tsutils@3.21.0(typescript@5.0.2): + /tsutils@3.21.0(typescript@5.2.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.0.2 + typescript: 5.2.2 dev: false /turbo-darwin-64@1.10.16: @@ -6538,9 +6400,9 @@ packages: is-typed-array: 1.1.12 dev: false - /typescript@5.0.2: - resolution: {integrity: sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==} - engines: {node: '>=12.20'} + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} + engines: {node: '>=14.17'} hasBin: true /unbox-primitive@1.0.2: @@ -6569,9 +6431,9 @@ packages: /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: 2.1.1 + punycode: 2.3.1 - /use-callback-ref@1.3.0(@types/react@18.2.15)(react@18.2.0): + /use-callback-ref@1.3.0(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==} engines: {node: '>=10'} peerDependencies: @@ -6581,12 +6443,12 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.15 + '@types/react': 18.2.33 react: 18.2.0 tslib: 2.6.2 dev: false - /use-sidecar@1.1.2(@types/react@18.2.15)(react@18.2.0): + /use-sidecar@1.1.2(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} peerDependencies: @@ -6596,7 +6458,7 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.15 + '@types/react': 18.2.33 detect-node-es: 1.1.0 react: 18.2.0 tslib: 2.6.2 @@ -6621,10 +6483,6 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true - /v8-compile-cache@2.3.0: - resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} - dev: true - /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: @@ -6632,8 +6490,8 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /vite@4.4.5(@types/node@20.8.10): - resolution: {integrity: sha512-4m5kEtAWHYr0O1Fu7rZp64CfO1PsRGZlD3TAB32UmQlpd7qg15VF7ROqGN5CyqN7HFuwr7ICNM2+fDWRqFEKaA==} + /vite@4.5.0(@types/node@20.8.10): + resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -6665,7 +6523,7 @@ packages: postcss: 8.4.31 rollup: 3.29.4 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /wcwidth@1.0.1: @@ -6752,11 +6610,20 @@ packages: dependencies: isexe: 2.0.0 - /word-wrap@1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} dev: true + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -6834,8 +6701,8 @@ packages: /zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} - /zustand@4.4.4(@types/react@18.2.15)(react@18.2.0): - resolution: {integrity: sha512-5UTUIAiHMNf5+mFp7/AnzJXS7+XxktULFN0+D1sCiZWyX7ZG+AQpqs2qpYrynRij4QvoDdCD+U+bmg/cG3Ucxw==} + /zustand@4.4.5(@types/react@18.2.33)(react@18.2.0): + resolution: {integrity: sha512-jgIrBBLKncQW74PA2Lclct3gwD4aPughwGE4FqlXrv3rDqQox7JCn8rwUiFK6ygJcbBAvfTf8fF+ICg7HLD2FQ==} engines: {node: '>=12.7.0'} peerDependencies: '@types/react': '>=16.8' @@ -6849,7 +6716,7 @@ packages: react: optional: true dependencies: - '@types/react': 18.2.15 + '@types/react': 18.2.33 react: 18.2.0 use-sync-external-store: 1.2.0(react@18.2.0) dev: false