diff --git a/apps/admin/package.json b/apps/admin/package.json index 6e1a7b43c..6b84d2659 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -1,46 +1,53 @@ { - "name": "admin", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint", - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" - }, - "devDependencies": { - "@maru/eslint": "workspace:*", - "@maru/tsconfig": "workspace:*", - "@storybook/addon-essentials": "^7.3.1", - "@storybook/addon-interactions": "^7.3.1", - "@storybook/addon-links": "^7.3.1", - "@storybook/addon-onboarding": "^1.0.8", - "@storybook/addon-styling": "^1.3.6", - "@storybook/blocks": "^7.3.1", - "@storybook/nextjs": "^7.3.1", - "@storybook/react": "^7.3.1", - "@storybook/testing-library": "^0.2.0", - "@types/node": "20.3.1", - "@types/react": "18.2.13", - "@types/react-dom": "18.2.6", - "@types/styled-components": "^5.1.26", - "babel-plugin-styled-components": "^2.1.4", - "eslint-plugin-storybook": "^0.6.13", - "storybook": "^7.3.1", - "styled-components": "^6.0.3", - "tsconfig-paths-webpack-plugin": "^4.1.0", - "typescript": "5.1.6" - }, - "dependencies": { - "@maru/hooks": "workspace:*", - "@maru/icon": "workspace:*", - "@maru/theme": "workspace:*", - "@maru/ui": "workspace:*", - "@maru/utils": "workspace:*", - "next": "13.4.6", - "react": "18.2.0", - "react-dom": "18.2.0" - } -} + "name": "admin", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" + }, + "devDependencies": { + "@maru/eslint": "workspace:*", + "@maru/tsconfig": "workspace:*", + "@storybook/addon-essentials": "^7.3.1", + "@storybook/addon-interactions": "^7.3.1", + "@storybook/addon-links": "^7.3.1", + "@storybook/addon-onboarding": "^1.0.8", + "@storybook/addon-styling": "^1.3.6", + "@storybook/blocks": "^7.3.1", + "@storybook/nextjs": "^7.3.1", + "@storybook/react": "^7.3.1", + "@storybook/testing-library": "^0.2.0", + "@types/node": "20.3.1", + "@types/react": "18.2.13", + "@types/react-dom": "18.2.6", + "@types/styled-components": "^5.1.26", + "babel-plugin-styled-components": "^2.1.4", + "eslint-plugin-storybook": "^0.6.13", + "msw": "^1.2.2", + "storybook": "^7.3.1", + "styled-components": "^6.0.3", + "tsconfig-paths-webpack-plugin": "^4.1.0", + "typescript": "5.1.6" + }, + "dependencies": { + "@maru/hooks": "workspace:*", + "@maru/icon": "workspace:*", + "@maru/theme": "workspace:*", + "@maru/ui": "workspace:*", + "@maru/utils": "workspace:*", + "@tanstack/react-query": "^4.32.6", + "@tanstack/react-query-devtools": "^4.32.6", + "axios": "^1.4.0", + "next": "13.4.6", + "react": "18.2.0", + "react-dom": "18.2.0" + }, + "msw": { + "workerDirectory": "public" + } +} \ No newline at end of file diff --git a/apps/admin/public/mockServiceWorker.js b/apps/admin/public/mockServiceWorker.js new file mode 100644 index 000000000..8ee70b3e4 --- /dev/null +++ b/apps/admin/public/mockServiceWorker.js @@ -0,0 +1,303 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (1.2.2). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70' +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 + const accept = request.headers.get('accept') || '' + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return + } + + // 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 = Math.random().toString(16).slice(2) + + event.respondWith( + handleRequest(event, requestId).catch((error) => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url, + ) + return + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}`, + ) + }), + ) +}) + +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 clonedResponse = response.clone() + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: + clonedResponse.body === null ? null : await clonedResponse.text(), + headers: Object.fromEntries(clonedResponse.headers.entries()), + redirected: clonedResponse.redirected, + }, + }) + })() + } + + 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 + const clonedRequest = request.clone() + + function passthrough() { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const headers = Object.fromEntries(clonedRequest.headers.entries()) + + // Remove MSW-specific request headers so the bypassed requests + // comply with the server's CORS preflight check. + // Operate with the headers as an object because request "Headers" + // are immutable. + delete headers['x-msw-bypass'] + + return fetch(clonedRequest, { 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()". + if (request.headers.get('x-msw-bypass') === 'true') { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.text(), + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'MOCK_NOT_FOUND': { + return passthrough() + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.data + const networkError = new Error(message) + networkError.name = name + + // Rejecting a "respondWith" promise emulates a network error. + throw networkError + } + } + + return passthrough() +} + +function sendToClient(client, message) { + 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]) + }) +} + +function sleep(timeMs) { + return new Promise((resolve) => { + setTimeout(resolve, timeMs) + }) +} + +async function respondWithMock(response) { + await sleep(response.delay) + return new Response(response.body, response) +} diff --git a/apps/admin/src/apis/instance/instance.ts b/apps/admin/src/apis/instance/instance.ts new file mode 100644 index 000000000..0635ff118 --- /dev/null +++ b/apps/admin/src/apis/instance/instance.ts @@ -0,0 +1,27 @@ +import axios from 'axios'; + +export const maru = axios.create({ + baseURL: process.env.NEXT_PUBLIC_BASE_URL, + timeout: 15000, + headers: { + 'Content-Type': 'application/json', + }, +}); + +maru.interceptors.request.use( + (config) => { + return config; + }, + (error) => { + return Promise.reject(error); + }, +); + +maru.interceptors.response.use( + (response) => { + return response; + }, + async (error) => { + return Promise.reject(error); + }, +); diff --git a/apps/admin/src/app/layout.tsx b/apps/admin/src/app/layout.tsx index 27e5f111b..da12b1d29 100644 --- a/apps/admin/src/app/layout.tsx +++ b/apps/admin/src/app/layout.tsx @@ -1,4 +1,5 @@ import Provider from '@/components/Provider'; +import QueryClientProvider from '@/services/QueryClientProvider'; import { ReactNode } from 'react'; export const metadata = { @@ -14,7 +15,9 @@ const RootLayout = ({ children }: Props) => { return ( - {children} + + {children} + ); diff --git a/apps/admin/src/app/notice/page.tsx b/apps/admin/src/app/notice/page.tsx index 97e5f1459..229783027 100644 --- a/apps/admin/src/app/notice/page.tsx +++ b/apps/admin/src/app/notice/page.tsx @@ -34,6 +34,6 @@ const StyledNoticePage = styled.div` ${flex({ flexDirection: 'column' })} gap: 40px; width: 100%; - height: 100%; + min-height: 100vh; padding: 64px 75px; `; diff --git a/apps/admin/src/app/page.tsx b/apps/admin/src/app/page.tsx index edeb1e7aa..c9de1ed35 100644 --- a/apps/admin/src/app/page.tsx +++ b/apps/admin/src/app/page.tsx @@ -1,7 +1,46 @@ 'use client'; +import FormList from '@/components/main/FormList/FormList'; import AppLayout from '@/layouts/AppLayout'; +import initMockAPI from '@/mocks'; +import { Button, Column, Row, SearchInput, Text } from '@maru/ui'; +import { flex } from '@maru/utils'; +import { styled } from 'styled-components'; -export default function Home() { - return asdf; +if (process.env.NODE_ENV === 'development') { + initMockAPI(); } + +const Home = () => { + return ( + + + 원서 관리 + + + + + + + + + + + + + ); +}; + +export default Home; + +const StyledHome = styled.div` + ${flex({ flexDirection: 'column' })} + gap: 40px; + width: 100%; + height: 100%; + padding: 64px 75px; + + overflow: auto; +`; diff --git a/apps/admin/src/components/common/SideBar/SideBar.tsx b/apps/admin/src/components/common/SideBar/SideBar.tsx index a0b3e0cf9..9e1fd78f0 100644 --- a/apps/admin/src/components/common/SideBar/SideBar.tsx +++ b/apps/admin/src/components/common/SideBar/SideBar.tsx @@ -8,9 +8,13 @@ import { color, font } from '../../../../../../packages/maru-theme'; const NAVIGATION_DATA = [ { - name: '홈', + name: '원서조회', route: ROUTES.MAIN, }, + { + name: '원서검토', + route: ROUTES.REVIEW, + }, { name: '공지사항', route: ROUTES.NOTICE, @@ -77,6 +81,7 @@ const SideNavigationBar = styled.div` `; const StyledLink = styled(Link)<{ $active: boolean }>` + ${font.H5} position: relative; ${flex({ alignItems: 'center' })} width: 100%; @@ -106,7 +111,7 @@ const StyledLink = styled(Link)<{ $active: boolean }>` `; const LogoutButton = styled.button` - ${font.btn1} + ${font.H5} ${flex({ alignItems: 'center' })} height: 56px; padding: 0px 36px; diff --git a/apps/admin/src/components/common/ListHeader/ListHeader.stories.tsx b/apps/admin/src/components/common/TableHeader/TableHeader.stories.tsx similarity index 94% rename from apps/admin/src/components/common/ListHeader/ListHeader.stories.tsx rename to apps/admin/src/components/common/TableHeader/TableHeader.stories.tsx index 576847c59..7e71df1ae 100644 --- a/apps/admin/src/components/common/ListHeader/ListHeader.stories.tsx +++ b/apps/admin/src/components/common/TableHeader/TableHeader.stories.tsx @@ -1,6 +1,6 @@ import { Row, Text } from "@maru/ui"; import type { Meta, StoryObj } from "@storybook/react"; -import ListHeader from "./ListHeader"; +import ListHeader from "./TableHeader"; export default { component: ListHeader, diff --git a/apps/admin/src/components/common/ListHeader/ListHeader.tsx b/apps/admin/src/components/common/TableHeader/TableHeader.tsx similarity index 88% rename from apps/admin/src/components/common/ListHeader/ListHeader.tsx rename to apps/admin/src/components/common/TableHeader/TableHeader.tsx index 321161d53..85b7dfd84 100644 --- a/apps/admin/src/components/common/ListHeader/ListHeader.tsx +++ b/apps/admin/src/components/common/TableHeader/TableHeader.tsx @@ -7,11 +7,11 @@ interface Props { children: ReactNode; } -const ListHeader = ({ children }: Props) => { +const TableHeader = ({ children }: Props) => { return {children}; }; -export default ListHeader; +export default TableHeader; const StyledListHeader = styled.div` ${flex({ alignItems: 'center', justifyContent: 'space-between' })} diff --git a/apps/admin/src/components/common/ListItem/ListItem.stories.tsx b/apps/admin/src/components/common/TableItem/TableItem.stories.tsx similarity index 95% rename from apps/admin/src/components/common/ListItem/ListItem.stories.tsx rename to apps/admin/src/components/common/TableItem/TableItem.stories.tsx index b404ff61b..1dbca9939 100644 --- a/apps/admin/src/components/common/ListItem/ListItem.stories.tsx +++ b/apps/admin/src/components/common/TableItem/TableItem.stories.tsx @@ -1,6 +1,6 @@ import { Row, Text } from "@maru/ui"; import type { Meta, StoryObj } from "@storybook/react"; -import ListItem from "./ListItem"; +import ListItem from "./TableItem"; export default { component: ListItem, diff --git a/apps/admin/src/components/common/ListItem/ListItem.tsx b/apps/admin/src/components/common/TableItem/TableItem.tsx similarity index 88% rename from apps/admin/src/components/common/ListItem/ListItem.tsx rename to apps/admin/src/components/common/TableItem/TableItem.tsx index 6dcf32898..6a5b022d1 100644 --- a/apps/admin/src/components/common/ListItem/ListItem.tsx +++ b/apps/admin/src/components/common/TableItem/TableItem.tsx @@ -7,11 +7,11 @@ interface Props { children: ReactNode; } -const ListItem = ({ children }: Props) => { +const TableItem = ({ children }: Props) => { return {children}; }; -export default ListItem; +export default TableItem; const StyledListItem = styled.div` ${flex({ alignItems: 'center', justifyContent: 'space-between' })} diff --git a/apps/admin/src/components/faq/FaqTable/FaqTableHeader/FaqTableHeader.tsx b/apps/admin/src/components/faq/FaqTable/FaqTableHeader/FaqTableHeader.tsx index 7cce24b74..d901e088e 100644 --- a/apps/admin/src/components/faq/FaqTable/FaqTableHeader/FaqTableHeader.tsx +++ b/apps/admin/src/components/faq/FaqTable/FaqTableHeader/FaqTableHeader.tsx @@ -1,9 +1,9 @@ -import ListHeader from '@/components/common/ListHeader/ListHeader'; +import TableHeader from '@/components/common/TableHeader/TableHeader'; import { Row, Text } from '@maru/ui'; const FaqTableHeader = () => { return ( - + 번호 @@ -20,7 +20,7 @@ const FaqTableHeader = () => { 게시일 - + ); }; diff --git a/apps/admin/src/components/faq/FaqTable/FaqTableItem/FaqTableItem.tsx b/apps/admin/src/components/faq/FaqTable/FaqTableItem/FaqTableItem.tsx index 255dd5678..d87485a55 100644 --- a/apps/admin/src/components/faq/FaqTable/FaqTableItem/FaqTableItem.tsx +++ b/apps/admin/src/components/faq/FaqTable/FaqTableItem/FaqTableItem.tsx @@ -1,5 +1,5 @@ -import ListItem from '@/components/common/ListItem/ListItem'; -import { Text, Row } from '@maru/ui'; +import TableItem from '@/components/common/TableItem/TableItem'; +import { Row, Text } from '@maru/ui'; import { formatCreatedAt } from '@maru/utils'; interface Props { @@ -11,7 +11,7 @@ interface Props { const FaqTableItem = ({ id, title, category, createdAt }: Props) => { return ( - + {id} @@ -28,7 +28,7 @@ const FaqTableItem = ({ id, title, category, createdAt }: Props) => { {formatCreatedAt(createdAt)} - + ); }; diff --git a/apps/admin/src/components/main/FormList/FormList.tsx b/apps/admin/src/components/main/FormList/FormList.tsx new file mode 100644 index 000000000..1c5220dc4 --- /dev/null +++ b/apps/admin/src/components/main/FormList/FormList.tsx @@ -0,0 +1,32 @@ +import { Column } from '@maru/ui'; +import { styled } from 'styled-components'; + +import { useFormListQuery } from '@/services/form/queries'; +import FormListHeader from '../FormListHeader/FormListHeader'; +import FormListItem from '../FormListItem/FormListItem'; + +const FormList = () => { + const { data: formList } = useFormListQuery(); + + return ( + + + {formList && + formList.map((item) => ( + + ))} + + ); +}; + +export default FormList; + +const StyledFormList = styled.div``; diff --git a/apps/admin/src/components/main/FormListHeader/FormListHeader.tsx b/apps/admin/src/components/main/FormListHeader/FormListHeader.tsx new file mode 100644 index 000000000..2516cf070 --- /dev/null +++ b/apps/admin/src/components/main/FormListHeader/FormListHeader.tsx @@ -0,0 +1,31 @@ +import TableHeader from '@/components/common/TableHeader/TableHeader'; +import { Row, Text } from '@maru/ui'; + +const FormListHeader = () => { + return ( + + + + 접수번호 + + + 이름 + + + 생년월일 + + + 학교 + + + 전형 + + + + 상태 + + + ); +}; + +export default FormListHeader; diff --git a/apps/admin/src/components/main/FormListItem/FormListItem.tsx b/apps/admin/src/components/main/FormListItem/FormListItem.tsx new file mode 100644 index 000000000..6f8d067cc --- /dev/null +++ b/apps/admin/src/components/main/FormListItem/FormListItem.tsx @@ -0,0 +1,43 @@ +import TableItem from '@/components/common/TableItem/TableItem'; +import { KoreanFormType } from '@/constants/main/constants'; +import { FormStatus, FormType, GraduationType } from '@/types/main/client'; +import { Row, Text } from '@maru/ui'; + +interface Props { + id: number; + name: string; + birthday: string; + graduationType: GraduationType; + school: string; + status: FormStatus; + type: FormType; +} + +const FormListItem = ({ id, name, birthday, graduationType, school, type, status }: Props) => { + return ( + + + + {id} + + + {name} + + + {birthday.replaceAll('-', '').slice(2)} + + + {graduationType === 'QUALIFICATION_EXAMINATION' ? '검정고시' : school} + + + {KoreanFormType[type as FormType]} + + + + {status} + + + ); +}; + +export default FormListItem; diff --git a/apps/admin/src/components/notice/NoticeList/NoticeList.tsx b/apps/admin/src/components/notice/NoticeList/NoticeList.tsx index 4c839e756..835c0bc58 100644 --- a/apps/admin/src/components/notice/NoticeList/NoticeList.tsx +++ b/apps/admin/src/components/notice/NoticeList/NoticeList.tsx @@ -1,57 +1,18 @@ -import ListHeader from '@/components/common/ListHeader/ListHeader'; -import ListItem from '@/components/common/ListItem/ListItem'; -import { Column, Row, Text } from '@maru/ui'; -import { formatPostedAt } from '@maru/utils'; - -const NOTICE_DATA = [ - { - id: 0, - title: '입학전형 사용 방법에 대한 공지사항', - date: '2022-05-07T10:35:57', - }, - { - id: 1, - title: '테스트입니다', - date: '2022-05-07T10:35:57', - }, - { - id: 2, - title: '테스트입니다', - date: '2022-05-07T10:35:57', - }, -] as const; +import { useNoticeListQuery } from '@/services/notice/queries'; +import { Column } from '@maru/ui'; +import NoticeListHeader from '../NoticeListHeader/NoticeListHeader'; +import NoticeListItem from '../NoticeListItem/NoticeListItem'; const NoticeList = () => { + const { data: noticeList } = useNoticeListQuery(); + return ( - - - - 번호 - - - 제목 - - - - 게시일 - - - {NOTICE_DATA.map((item) => ( - - - - {item.id} - - - {item.title} - - - - {formatPostedAt(item.date)} - - - ))} + + {noticeList && + noticeList.map(({ id, title, createdAt }) => ( + + ))} ); }; diff --git a/apps/admin/src/components/notice/NoticeListHeader/NoticeListHeader.tsx b/apps/admin/src/components/notice/NoticeListHeader/NoticeListHeader.tsx new file mode 100644 index 000000000..ec8dc8ff7 --- /dev/null +++ b/apps/admin/src/components/notice/NoticeListHeader/NoticeListHeader.tsx @@ -0,0 +1,22 @@ +import TableHeader from '@/components/common/TableHeader/TableHeader'; +import { Row, Text } from '@maru/ui'; + +const NoticeListHeader = () => { + return ( + + + + 번호 + + + 제목 + + + + 게시일 + + + ); +}; + +export default NoticeListHeader; diff --git a/apps/admin/src/components/notice/NoticeListItem/NoticeListItem.tsx b/apps/admin/src/components/notice/NoticeListItem/NoticeListItem.tsx new file mode 100644 index 000000000..a78e868a1 --- /dev/null +++ b/apps/admin/src/components/notice/NoticeListItem/NoticeListItem.tsx @@ -0,0 +1,29 @@ +import TableItem from '@/components/common/TableItem/TableItem'; +import { Row, Text } from '@maru/ui'; +import { formatPostedAt } from '@maru/utils'; + +interface Props { + id: number; + title: string; + createdAt: string; +} + +const NoticeListItem = ({ id, title, createdAt }: Props) => { + return ( + + + + {id} + + + {title} + + + + {formatPostedAt(createdAt)} + + + ); +}; + +export default NoticeListItem; diff --git a/apps/admin/src/constants/common/constants.ts b/apps/admin/src/constants/common/constants.ts index 024b80eba..d19b45f44 100644 --- a/apps/admin/src/constants/common/constants.ts +++ b/apps/admin/src/constants/common/constants.ts @@ -1,6 +1,12 @@ +export const KEY = { + NOTICE_LIST: 'useNoticeList', + FORM_LIST: 'useFormList', +}; + export const ROUTES = { MAIN: '/', + REVIEW: '/review', NOTICE: '/notice', FAQ: '/faq', - ANALYSIS: 'analysis', + ANALYSIS: '/analysis', } as const; diff --git a/apps/admin/src/constants/main/constants.ts b/apps/admin/src/constants/main/constants.ts new file mode 100644 index 000000000..4e2254ba8 --- /dev/null +++ b/apps/admin/src/constants/main/constants.ts @@ -0,0 +1,16 @@ +import { FormType } from '@/types/main/client'; + +export const KoreanFormType: Record = { + REGULAR: '일반', + MEISTER_TALENT: '마이스터인재', + NATIONAL_BASIC_LIVING: '국민기초생활수급자', + NEAR_POVERTY: '차상위계층', + NATIONAL_VETERANS: '국가보훈대상자 (국가유공자)', + ONE_PARENT: '한부모가정', + FROM_NORTH_KOREA: '북한이탈주민', + MULTICULTURAL: '다문화가정', + TEEN_HOUSEHOLDER: '소년소녀가장', + MULTI_CHILDREN: '다자녀가정자녀', + FARMING_AND_FISHING: '농어촌지역출신자', + SPECIAL_ADMISSION: '특례입학대상자전형', +}; diff --git a/apps/admin/src/layouts/AppLayout.tsx b/apps/admin/src/layouts/AppLayout.tsx index 129e9ccde..d3dacc46e 100644 --- a/apps/admin/src/layouts/AppLayout.tsx +++ b/apps/admin/src/layouts/AppLayout.tsx @@ -26,4 +26,6 @@ const StyledAppLayout = styled.div` const Section = styled.section` flex: 1; + min-width: fit-content; + overflow: auto; `; diff --git a/apps/admin/src/mocks/browser.ts b/apps/admin/src/mocks/browser.ts new file mode 100644 index 000000000..74df1dbec --- /dev/null +++ b/apps/admin/src/mocks/browser.ts @@ -0,0 +1,4 @@ +import { setupWorker } from "msw"; +import { handlers } from "./handlers"; + +export const worker = setupWorker(...handlers); diff --git a/apps/admin/src/mocks/handlers/index.ts b/apps/admin/src/mocks/handlers/index.ts new file mode 100644 index 000000000..8535a3bd8 --- /dev/null +++ b/apps/admin/src/mocks/handlers/index.ts @@ -0,0 +1,4 @@ +import { mainHandlers } from './main'; +import { noticeHandlers } from './notice'; + +export const handlers = [...noticeHandlers, ...mainHandlers]; diff --git a/apps/admin/src/mocks/handlers/main.ts b/apps/admin/src/mocks/handlers/main.ts new file mode 100644 index 000000000..3a5094819 --- /dev/null +++ b/apps/admin/src/mocks/handlers/main.ts @@ -0,0 +1,38 @@ +import { Form } from '@/types/main/client'; +import { rest } from 'msw'; + +const MAIN_FORM_DATA: Form[] = [ + { + id: 0, + name: '김밤돌', + birthday: '2005-04-15', + graduationType: 'EXPECTED', + school: '비전중학교', + status: '최종 제출됨', + type: 'REGULAR', + }, + { + id: 1, + name: '김밤돌', + birthday: '2005-04-15', + graduationType: 'QUALIFICATION_EXAMINATION', + school: '비전중학교', + status: '반려됨', + type: 'REGULAR', + }, + { + id: 2, + name: '김밤돌', + birthday: '2005-04-15', + graduationType: 'EXPECTED', + school: '비전중학교', + status: '최종 제출됨', + type: 'MULTICULTURAL', + }, +]; + +export const mainHandlers = [ + rest.get('/form/review', (_, res, ctx) => { + return res(ctx.status(200), ctx.json({ dataList: MAIN_FORM_DATA })); + }), +]; diff --git a/apps/admin/src/mocks/handlers/notice.ts b/apps/admin/src/mocks/handlers/notice.ts new file mode 100644 index 000000000..68700c417 --- /dev/null +++ b/apps/admin/src/mocks/handlers/notice.ts @@ -0,0 +1,26 @@ +import { Notice } from '@/types/notice/client'; +import { rest } from 'msw'; + +const NOTICE_DATA: Notice[] = [ + { + id: 0, + title: '테스트입니다', + createdAt: '2022-05-07T10:35:57', + }, + { + id: 1, + title: '테스트입니다', + createdAt: '2022-05-07T10:35:57', + }, + { + id: 2, + title: '테스트입니다', + createdAt: '2022-05-07T10:35:57', + }, +]; + +export const noticeHandlers = [ + rest.get('/notice', (_, res, ctx) => { + return res(ctx.status(200), ctx.json({ dataList: NOTICE_DATA })); + }), +]; diff --git a/apps/admin/src/mocks/index.ts b/apps/admin/src/mocks/index.ts new file mode 100644 index 000000000..9b307565e --- /dev/null +++ b/apps/admin/src/mocks/index.ts @@ -0,0 +1,11 @@ +const initMockAPI = async (): Promise => { + if (typeof window === 'undefined') { + const { server } = await import('@/mocks/server'); + server.listen(); + } else { + const { worker } = await import('@/mocks/browser'); + worker.start(); + } +}; + +export default initMockAPI; diff --git a/apps/admin/src/mocks/server.ts b/apps/admin/src/mocks/server.ts new file mode 100644 index 000000000..7b37f2acf --- /dev/null +++ b/apps/admin/src/mocks/server.ts @@ -0,0 +1,4 @@ +import { setupServer } from "msw/node"; +import { handlers } from "./handlers"; + +export const server = setupServer(...handlers); diff --git a/apps/admin/src/services/QueryClientProvider.tsx b/apps/admin/src/services/QueryClientProvider.tsx new file mode 100644 index 000000000..9f238d39b --- /dev/null +++ b/apps/admin/src/services/QueryClientProvider.tsx @@ -0,0 +1,32 @@ +'use client'; + +import { QueryClientProvider as MaruQueryClientProvider, QueryClient } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { ReactNode, useState } from 'react'; + +interface PropsType { + children: ReactNode; +} + +const QueryClientProvider = ({ children }: PropsType) => { + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + suspense: true, + }, + }, + }), + ); + + return ( + + {children} + + + ); +}; + +export default QueryClientProvider; diff --git a/apps/admin/src/services/form/api.ts b/apps/admin/src/services/form/api.ts new file mode 100644 index 000000000..e7054e18b --- /dev/null +++ b/apps/admin/src/services/form/api.ts @@ -0,0 +1,8 @@ +import { maru } from '@/apis/instance/instance'; +import { GetFormReviewListRes } from '@/types/main/remote'; + +export const getFormReviewList = async () => { + const { data } = await maru.get('/form/review'); + + return data; +}; diff --git a/apps/admin/src/services/form/queries.ts b/apps/admin/src/services/form/queries.ts new file mode 100644 index 000000000..0354428aa --- /dev/null +++ b/apps/admin/src/services/form/queries.ts @@ -0,0 +1,13 @@ +import { KEY } from '@/constants/common/constants'; +import { useQuery } from '@tanstack/react-query'; +import { getFormReviewList } from './api'; + +export const useFormListQuery = () => { + const { data, ...restQuery } = useQuery({ + queryKey: [KEY.FORM_LIST], + queryFn: getFormReviewList, + suspense: false, + }); + + return { data: data?.dataList, ...restQuery }; +}; diff --git a/apps/admin/src/services/notice/api.ts b/apps/admin/src/services/notice/api.ts new file mode 100644 index 000000000..c686fcd74 --- /dev/null +++ b/apps/admin/src/services/notice/api.ts @@ -0,0 +1,7 @@ +import { maru } from '@/apis/instance/instance'; +import { GetNoticeListRes } from '@/types/notice/remote'; + +export const getNoticeList = async () => { + const { data } = await maru.get('/notice'); + return data; +}; diff --git a/apps/admin/src/services/notice/queries.ts b/apps/admin/src/services/notice/queries.ts new file mode 100644 index 000000000..1af4d8c80 --- /dev/null +++ b/apps/admin/src/services/notice/queries.ts @@ -0,0 +1,13 @@ +import { KEY } from '@/constants/common/constants'; +import { useQuery } from '@tanstack/react-query'; +import { getNoticeList } from './api'; + +export const useNoticeListQuery = () => { + const { data, ...restQuery } = useQuery({ + queryKey: [KEY.NOTICE_LIST], + queryFn: getNoticeList, + suspense: false, + }); + + return { data: data?.dataList, ...restQuery }; +}; diff --git a/apps/admin/src/types/main/client.ts b/apps/admin/src/types/main/client.ts new file mode 100644 index 000000000..2b6fc1850 --- /dev/null +++ b/apps/admin/src/types/main/client.ts @@ -0,0 +1,27 @@ +export type FormType = + | 'REGULAR' + | 'MEISTER_TALENT' + | 'NATIONAL_BASIC_LIVING' + | 'NEAR_POVERTY' + | 'NATIONAL_VETERANS' + | 'ONE_PARENT' + | 'FROM_NORTH_KOREA' + | 'MULTICULTURAL' + | 'TEEN_HOUSEHOLDER' + | 'MULTI_CHILDREN' + | 'FARMING_AND_FISHING' + | 'SPECIAL_ADMISSION'; + +export type GraduationType = 'EXPECTED' | 'GRADUATED' | 'QUALIFICATION_EXAMINATION'; + +export type FormStatus = '최종 제출됨' | '반려됨'; + +export interface Form { + id: number; + name: string; + birthday: string; + graduationType: GraduationType; + school: string; + status: FormStatus; + type: FormType; +} diff --git a/apps/admin/src/types/main/remote.ts b/apps/admin/src/types/main/remote.ts new file mode 100644 index 000000000..edc153de0 --- /dev/null +++ b/apps/admin/src/types/main/remote.ts @@ -0,0 +1,5 @@ +import { Form } from './client'; + +export interface GetFormReviewListRes { + dataList: Form[]; +} diff --git a/apps/admin/src/types/notice/client.ts b/apps/admin/src/types/notice/client.ts new file mode 100644 index 000000000..26ab03a2c --- /dev/null +++ b/apps/admin/src/types/notice/client.ts @@ -0,0 +1,5 @@ +export interface Notice { + id: number; + title: string; + createdAt: string; +} diff --git a/apps/admin/src/types/notice/remote.ts b/apps/admin/src/types/notice/remote.ts new file mode 100644 index 000000000..8b575d968 --- /dev/null +++ b/apps/admin/src/types/notice/remote.ts @@ -0,0 +1,5 @@ +import { Notice } from './client'; + +export interface GetNoticeListRes { + dataList: Notice[]; +} diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json index 0c7555fa7..9557b813f 100644 --- a/apps/admin/tsconfig.json +++ b/apps/admin/tsconfig.json @@ -1,28 +1,28 @@ { - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/apps/user/src/app/error.tsx b/apps/user/src/app/error.tsx index e7f94c9cf..7c61b5251 100644 --- a/apps/user/src/app/error.tsx +++ b/apps/user/src/app/error.tsx @@ -13,7 +13,7 @@ const Error = () => { return ( - + error @@ -38,14 +38,14 @@ const Error = () => { 새로고침하기 - + ); }; export default Error; -const StyledError = styled.div` +const StyledErrorPage = styled.div` ${flex({ flexDirection: 'column', alignItems: 'center' })}; gap: 56px; margin: 82px auto 0; diff --git a/apps/user/src/app/faq/page.tsx b/apps/user/src/app/faq/page.tsx index d7cac7f8f..e100b6924 100644 --- a/apps/user/src/app/faq/page.tsx +++ b/apps/user/src/app/faq/page.tsx @@ -12,7 +12,7 @@ const FaqPage = () => { const [category, setCategory] = useState('TOP_QUESTION'); return ( - + 자주 묻는 질문 @@ -31,6 +31,8 @@ export default FaqPage; const StyledFaqPage = styled.div` position: relative; width: 100%; + max-width: 1240px; height: 100%; - padding-bottom: 227px; + margin: 0 auto; + padding: 82px 204px 240px; `; diff --git "a/apps/user/src/app/form/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264.hooks.ts" "b/apps/user/src/app/form/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264.hooks.ts" index 0043834c8..6996e3cfe 100644 --- "a/apps/user/src/app/form/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264.hooks.ts" +++ "b/apps/user/src/app/form/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264.hooks.ts" @@ -1,6 +1,6 @@ -import { ChangeEventHandler } from 'react'; import { useSaveFormMutation } from '@/services/form/mutations'; -import { useSetFormStore, useSetFormStepStore, useFormValueStore } from '@/store'; +import { useFormValueStore, useSetFormStepStore, useSetFormStore } from '@/store'; +import { ChangeEventHandler } from 'react'; export const useCTAButton = () => { const form = useFormValueStore(); @@ -24,6 +24,7 @@ export const useInput = () => { const handle보호자정보DataChange: ChangeEventHandler = (e) => { const { name, value } = e.target; + setForm((prev) => ({ ...prev, parent: { ...prev.parent, [name]: value } })); }; diff --git "a/apps/user/src/app/form/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264.tsx" "b/apps/user/src/app/form/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264.tsx" index 0467bb74e..b733432fb 100644 --- "a/apps/user/src/app/form/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264.tsx" +++ "b/apps/user/src/app/form/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264/\353\263\264\355\230\270\354\236\220\354\240\225\353\263\264.tsx" @@ -1,7 +1,7 @@ import { FindAddressModal, FormController } from '@/components/form'; import { FormLayout } from '@/layouts'; import { useFormValueStore } from '@/store'; -import { ButtonInput, Column, Input, RadioGroup, Row } from '@maru/ui'; +import { ButtonInput, Column, Input, Row } from '@maru/ui'; import { useOverlay } from '@toss/use-overlay'; import styled from 'styled-components'; import { useCTAButton, useInput } from './보호자정보.hooks'; @@ -53,32 +53,14 @@ const 보호자정보 = () => { isError={form.parent.address.length > 100} errorMessage="100자 이하여야 합니다." /> - - - - {form.parent.relation !== '아빠' && form.parent.relation !== '엄마' && ( - - )} + { - const [fieldStep, setFieldStep] = useState('성적 입력'); + const [scoreStep, setScoreStep] = useState('성적 입력'); const { handleNextButtonClick, handlePreviousButtonClick } = useCTAButton(); return ( @@ -37,16 +37,16 @@ const 성적입력 = () => { {FIELD_DATA.map((item, index) => ( - setFieldStep(item)}> + setScoreStep(item)}> {item} - + ))} , 출결상황: , diff --git "a/apps/user/src/app/form/\354\265\234\354\242\205\354\240\234\354\266\234/\354\265\234\354\242\205\354\240\234\354\266\234.tsx" "b/apps/user/src/app/form/\354\265\234\354\242\205\354\240\234\354\266\234/\354\265\234\354\242\205\354\240\234\354\266\234.tsx" index f7eb57a92..3708eaa4c 100644 --- "a/apps/user/src/app/form/\354\265\234\354\242\205\354\240\234\354\266\234/\354\265\234\354\242\205\354\240\234\354\266\234.tsx" +++ "b/apps/user/src/app/form/\354\265\234\354\242\205\354\240\234\354\266\234/\354\265\234\354\242\205\354\240\234\354\266\234.tsx" @@ -35,7 +35,7 @@ const 최종제출 = () => { }; return ( - + @@ -111,6 +111,7 @@ const Styled최종제출 = styled.div` ${flex({ alignItems: 'center', justifyContent: 'space-between' })} width: 100%; height: 100%; + padding: 58px 96px 0px; `; const ContentBox = styled.div` diff --git "a/apps/user/src/app/form/\354\266\234\354\213\240\355\225\231\352\265\220\353\260\217\355\225\231\353\240\245/\354\266\234\354\213\240\355\225\231\352\265\220\353\260\217\355\225\231\353\240\245.tsx" "b/apps/user/src/app/form/\354\266\234\354\213\240\355\225\231\352\265\220\353\260\217\355\225\231\353\240\245/\354\266\234\354\213\240\355\225\231\352\265\220\353\260\217\355\225\231\353\240\245.tsx" index d12757851..ab87eae29 100644 --- "a/apps/user/src/app/form/\354\266\234\354\213\240\355\225\231\352\265\220\353\260\217\355\225\231\353\240\245/\354\266\234\354\213\240\355\225\231\352\265\220\353\260\217\355\225\231\353\240\245.tsx" +++ "b/apps/user/src/app/form/\354\266\234\354\213\240\355\225\231\352\265\220\353\260\217\355\225\231\353\240\245/\354\266\234\354\213\240\355\225\231\352\265\220\353\260\217\355\225\231\353\240\245.tsx" @@ -1,7 +1,8 @@ import { FindSchoolModal, FormController } from '@/components/form'; import { FormLayout } from '@/layouts'; import { useFormValueStore } from '@/store'; -import { ButtonInput, Input, RadioGroup } from '@maru/ui'; +import { ButtonInput, Input, RadioGroup, Row } from '@maru/ui'; +import { flex } from '@maru/utils'; import { useOverlay } from '@toss/use-overlay'; import styled from 'styled-components'; import { useCTAButton, useInput } from './출신학교및학력.hooks'; @@ -30,7 +31,6 @@ const 출신학교및학력 = () => { value={form.education.graduationType} onChange={handle출신학교및학력DataChange} /> -
{ isError={form.education.schoolName.length > 20} errorMessage="20자 이하여야 합니다." /> -
- - 20} - errorMessage="20자여야 합니다." - width="100%" - value={form.education.schoolLocation} - onChange={handle출신학교및학력DataChange} - /> - - 11} - errorMessage="11자 이하여야 합니다." - /> - 20} - errorMessage="20자 이하여야 합니다." - /> - 11} - errorMessage="11자 이하여야 합니다." - /> + + + 20} + errorMessage="20자여야 합니다." + width="100%" + value={form.education.schoolLocation} + onChange={handle출신학교및학력DataChange} + /> + + + + 11} + errorMessage="11자 이하여야 합니다." + /> + + + 20} + errorMessage="20자 이하여야 합니다." + /> + 11} + errorMessage="11자 이하여야 합니다." + /> + { export default 출신학교및학력; const Styled출신학교및학력 = styled.div` - display: grid; - grid-template-columns: 1fr 1fr; - gap: 30px 48px; + ${flex({ flexDirection: 'column' })} + gap:30px; + width: 100%; + height: 100%; `; diff --git a/apps/user/src/app/login/login.hooks.ts b/apps/user/src/app/login/login.hooks.ts index 98e529008..a680c726c 100644 --- a/apps/user/src/app/login/login.hooks.ts +++ b/apps/user/src/app/login/login.hooks.ts @@ -31,12 +31,8 @@ export const useInput = () => { export const useCTAButton = () => { const router = useRouter(); - const handleGoSingUpPageButtonClick = () => { - router.push(ROUTES.SIGNUP); - }; - const handleGoMainPageButtonClick = () => { router.push(ROUTES.MAIN); }; - return { handleGoSingUpPageButtonClick, handleGoMainPageButtonClick }; + return { handleGoMainPageButtonClick }; }; diff --git a/apps/user/src/app/login/page.tsx b/apps/user/src/app/login/page.tsx index 5fc85d4f6..60a55a9d0 100644 --- a/apps/user/src/app/login/page.tsx +++ b/apps/user/src/app/login/page.tsx @@ -1,16 +1,18 @@ 'use client'; +import { ROUTES } from '@/constants/common/constant'; import { AppLayout } from '@/layouts'; import { IconArrowRight } from '@maru/icon'; import { color, font } from '@maru/theme'; import { Button, Column, Input, PreviewInput } from '@maru/ui'; import { flex } from '@maru/utils'; import Image from 'next/image'; +import Link from 'next/link'; import styled from 'styled-components'; import { useCTAButton, useInput, useLoginAction } from './login.hooks'; const LoginPage = () => { - const { handleGoSingUpPageButtonClick, handleGoMainPageButtonClick } = useCTAButton(); + const { handleGoMainPageButtonClick } = useCTAButton(); const { loginUserData, handleLoginUserDataChange } = useInput(); const { handleLoginButtonClick } = useLoginAction(loginUserData); @@ -18,7 +20,12 @@ const LoginPage = () => { - + { height={70} alt="logo" /> - - + + { onChange={handleLoginUserDataChange} /> - + - + 비밀번호 찾기 - + - + 회원이 아니신가요? - - 회원가입 - - - + 회원가입 + +
@@ -68,7 +73,7 @@ const LoginPage = () => { export default LoginPage; const StyledLoginPage = styled.div` - ${flex({ justifyContent: 'center' })} + ${flex({ justifyContent: 'center', alignItems: 'center' })}; width: 100%; height: 100vh; `; @@ -81,16 +86,9 @@ const LoginBox = styled.div` background-color: ${color.white}; `; -const LoginBoxWrap = styled.div` - ${flex({ flexDirection: 'column', alignItems: 'center' })} - gap: 56px; - width: 446px; -`; - -const GoFindPasswordPageButton = styled.a` +const FindPasswordLink = styled(Link)` ${font.p2} color: ${color.gray500}; - cursor: pointer; ${flex({ alignItems: 'center' })} &:hover { text-decoration-line: underline; @@ -98,17 +96,16 @@ const GoFindPasswordPageButton = styled.a` } `; -const GoSignUpPageButtonBox = styled.div` +const SignUpLinkBox = styled.div` ${font.p2} color: ${color.gray500}; ${flex({ alignItems: 'center' })} gap: 8px; `; -const GoSignUpPageButton = styled.a` +const SignUpLink = styled(Link)` ${font.p2} color: ${color.gray500}; text-decoration-line: underline; text-decoration-color: ${color.gray500}; - cursor: pointer; `; diff --git a/apps/user/src/app/mobile/page.tsx b/apps/user/src/app/mobile/page.tsx index f24d6af58..ec721e67a 100644 --- a/apps/user/src/app/mobile/page.tsx +++ b/apps/user/src/app/mobile/page.tsx @@ -37,6 +37,5 @@ const StyledMobilPage = styled.div` ${flex({ flexDirection: 'column', alignItems: 'center' })}; padding-top: 200px; gap: 36px; - width: 100%; height: 100%; `; diff --git a/apps/user/src/app/notice/[id]/page.tsx b/apps/user/src/app/notice/[id]/page.tsx index dafae8e5c..aab8db769 100644 --- a/apps/user/src/app/notice/[id]/page.tsx +++ b/apps/user/src/app/notice/[id]/page.tsx @@ -6,9 +6,9 @@ import { ROUTES } from '@/constants/common/constant'; import { AppLayout } from '@/layouts'; import { IconArrowLeft } from '@maru/icon'; import { color } from '@maru/theme'; -import { Link, Text } from '@maru/ui'; +import { Text } from '@maru/ui'; import { flex } from '@maru/utils'; -import { useRouter } from 'next/navigation'; +import Link from 'next/link'; import { Suspense } from 'react'; import styled from 'styled-components'; @@ -17,17 +17,15 @@ interface Props { } const NoticeDetailPage = ({ params: { id } }: Props) => { - const router = useRouter(); - return ( - + - router.push(ROUTES.NOTICE)} gap="2px"> + 공지사항 - + }> @@ -43,5 +41,13 @@ const StyledNoticeDetailPage = styled.div` ${flex({ flexDirection: 'column' })} gap: 36px; width: 100%; + max-width: 1240px; height: 100%; + margin: 0 auto; + padding: 52px 204px 240px; +`; + +const BackLink = styled(Link)` + ${flex({ alignItems: 'center' })}; + gap: 8px; `; diff --git a/apps/user/src/app/notice/page.tsx b/apps/user/src/app/notice/page.tsx index c273d50e8..50a003eb7 100644 --- a/apps/user/src/app/notice/page.tsx +++ b/apps/user/src/app/notice/page.tsx @@ -11,7 +11,7 @@ import styled from 'styled-components'; const NoticePage = () => { return ( - + 공지사항 @@ -31,5 +31,8 @@ const StyledNoticePage = styled.div` ${flex({ flexDirection: 'column' })} gap: 48px; width: 100%; + max-width: 1240px; height: 100%; + margin: 0 auto; + padding: 82px 204px 240px; `; diff --git a/apps/user/src/app/page.tsx b/apps/user/src/app/page.tsx index ba34c778b..0805c3089 100644 --- a/apps/user/src/app/page.tsx +++ b/apps/user/src/app/page.tsx @@ -1,6 +1,14 @@ 'use client'; -import { Dday, Faq, Notice, Schedule } from '@/components/main'; +import { + ApplicationBox, + Dday, + Faq, + GuidelineBox, + Notice, + Schedule, + SimulatorBox, +} from '@/components/main'; import { AppLayout } from '@/layouts'; import { Row } from '@maru/ui'; import { flex } from '@maru/utils'; @@ -12,12 +20,17 @@ if (process.env.NODE_ENV === 'development') { const MainPage = () => { return ( - + + + + + + @@ -31,8 +44,6 @@ export default MainPage; const StyledMainPage = styled.div` ${flex({ flexDirection: 'column', alignItems: 'center' })}; - padding-bottom: 227px; - gap: 60px; - width: 100%; - height: 100%; + padding: 52px 96px 240px; + gap: 80px; `; diff --git a/apps/user/src/app/result/final/page.tsx b/apps/user/src/app/result/final/page.tsx index c59eba0d6..36f9bdd92 100644 --- a/apps/user/src/app/result/final/page.tsx +++ b/apps/user/src/app/result/final/page.tsx @@ -1,14 +1,14 @@ 'use client'; +import { FinalResultTable, ResultMain } from '@/components/result'; import { AppLayout } from '@/layouts'; +import { ResultStep } from '@/types/result/client'; import { color } from '@maru/theme'; import { Column, Text } from '@maru/ui'; import { flex } from '@maru/utils'; import { SwitchCase } from '@toss/react'; -import { ResultStep } from '@/types/result/client'; -import styled from 'styled-components'; import { useState } from 'react'; -import { ResultMain, FinalResultTable } from '@/components/result'; +import styled from 'styled-components'; const FinalResultPage = () => { const [ResultStep, setResultStep] = useState('MAIN'); diff --git a/apps/user/src/app/result/first/page.tsx b/apps/user/src/app/result/first/page.tsx index e2b3ed016..0f75b8601 100644 --- a/apps/user/src/app/result/first/page.tsx +++ b/apps/user/src/app/result/first/page.tsx @@ -1,14 +1,14 @@ 'use client'; +import { FirstResultTable, ResultMain } from '@/components/result'; import { AppLayout } from '@/layouts'; +import { ResultStep } from '@/types/result/client'; import { color } from '@maru/theme'; import { Column, Text } from '@maru/ui'; import { flex } from '@maru/utils'; import { SwitchCase } from '@toss/react'; -import { ResultStep } from '@/types/result/client'; -import styled from 'styled-components'; import { useState } from 'react'; -import { ResultMain, FirstResultTable } from '@/components/result'; +import styled from 'styled-components'; const FirstResultPage = () => { const [ResultStep, setResultStep] = useState('MAIN'); diff --git a/apps/user/src/app/grade-simulation/page.tsx b/apps/user/src/app/score-simulation/page.tsx similarity index 87% rename from apps/user/src/app/grade-simulation/page.tsx rename to apps/user/src/app/score-simulation/page.tsx index 22ee2e899..d05f4fb92 100644 --- a/apps/user/src/app/grade-simulation/page.tsx +++ b/apps/user/src/app/score-simulation/page.tsx @@ -10,18 +10,18 @@ import { import { FIELD_DATA } from '@/constants/form/data'; import { AppLayout } from '@/layouts'; import { color } from '@maru/theme'; -import { Column, Text, UnderLineButton } from '@maru/ui'; +import { Column, Text, UnderlineButton } from '@maru/ui'; import { flex } from '@maru/utils'; import { SwitchCase } from '@toss/react'; import { useState } from 'react'; import styled from 'styled-components'; -const GradeSimulation = () => { +const ScoreSimulation = () => { const [fieldStep, setFieldStep] = useState('성적 입력'); return ( - + 성적 모의 계산 @@ -42,12 +42,12 @@ const GradeSimulation = () => { {FIELD_DATA.map((item, index) => ( - setFieldStep(item)}> {item} - + ))} { defaultComponent={} /> - + ); }; -export default GradeSimulation; +export default ScoreSimulation; -const GradeSimulationPage = styled.div` +const ScoreSimulationPage = styled.div` ${flex({ flexDirection: 'column' })}; gap: 48px; width: 816px; - margin: 82px auto 93px; + padding: 82px 0 172px; + margin: 0 auto; `; const NavigationBar = styled.div` diff --git a/apps/user/src/app/signup/page.tsx b/apps/user/src/app/signup/page.tsx index b2419728a..24899cb6a 100644 --- a/apps/user/src/app/signup/page.tsx +++ b/apps/user/src/app/signup/page.tsx @@ -14,8 +14,13 @@ const SignUpPage = () => { const [termsAgree, setTermsAgree] = useState(false); const { startTimer, timerTime, setTimerTime } = useTimer(); const { joinUserData, handleJoinUserDataChange } = useInput(); - const { handleVerificationButtonClick, isButtonDisabled, isVerification } = - useVerificationAction(joinUserData.phoneNumber); + const { + handleRequestVerificationButtonClick, + handleVerificationButtonClick, + isRequestVerificationDisabled, + isVerificationDisabled, + isVerification, + } = useVerificationAction(joinUserData); const { handleJoinButtonClick } = useJoinAction(joinUserData, termsAgree); return ( @@ -45,10 +50,10 @@ const SignUpPage = () => { label="전화번호 인증" buttonText={isVerification ? '재전송' : '인증'} onClick={() => { - handleVerificationButtonClick(); + handleRequestVerificationButtonClick(); startTimer(); }} - enabled={isButtonDisabled} + enabled={isRequestVerificationDisabled} type="phoneNumber" placeholder="- 없이 입력해주세요" width="100%" @@ -63,9 +68,12 @@ const SignUpPage = () => { maxLength={6} name="code" onChange={handleJoinUserDataChange} - timerTime={timerTime} + onClick={handleVerificationButtonClick} + timerTime={isVerificationDisabled ? 0 : timerTime} setTimerTime={setTimerTime} isError={joinUserData.code.length < 6} + buttonText="인증번호 확인" + enabled={isVerificationDisabled} errorMessage="발송된 전화번호의 인증번호를 입력해주세요." /> )} @@ -101,7 +109,7 @@ export default SignUpPage; const StyledSignUpPage = styled.div` ${flex({ justifyContent: 'space-between', alignItems: 'center' })} width: 100%; - height: 100%; + height: 100vh; background-color: ${color.gray100}; `; @@ -118,5 +126,6 @@ const SignUpBox = styled.div` ${flex({ flexDirection: 'column' })}; gap: 36px; width: 446px; + height: fit-content; margin: 120px 0; `; diff --git a/apps/user/src/app/signup/signup.hooks.ts b/apps/user/src/app/signup/signup.hooks.ts index 4e5ec4dd9..240bf11b8 100644 --- a/apps/user/src/app/signup/signup.hooks.ts +++ b/apps/user/src/app/signup/signup.hooks.ts @@ -1,9 +1,13 @@ -import { useJoinUserMutation, useVerificationMutation } from '@/services/auth/mutations'; -import { PostJoinAuthReq } from '@/types/auth/remote'; +import { + useJoinUserMutation, + useRequestVerificationMutation, + useVerificationMutation, +} from '@/services/auth/mutations'; +import { Join } from '@/types/auth/client'; import { useBooleanState } from '@maru/hooks'; import { ChangeEventHandler, useState } from 'react'; -export const useJoinAction = (joinUserData: PostJoinAuthReq, termsAgree: boolean) => { +export const useJoinAction = (joinUserData: Join, termsAgree: boolean) => { const { joinUserMutate } = useJoinUserMutation(joinUserData); const handleJoinButtonClick = () => { @@ -26,25 +30,42 @@ export const useJoinAction = (joinUserData: PostJoinAuthReq, termsAgree: boolean return { handleJoinButtonClick }; }; -export const useVerificationAction = (phoneNumber: string) => { +export const useVerificationAction = (joinUserData: Join) => { // 전화번호 요청을 보냈는가? const { value: isVerification, setValue: setIsVerification } = useBooleanState(false); // 전화번호 전송 활성화 비활성화 - const { value: isButtonDisabled, setValue: setIsButtonDisabled } = useBooleanState(false); - const { requestVerificationMutate } = useVerificationMutation(phoneNumber); + const { value: isRequestVerificationDisabled, setValue: setIsRequestVerificationDisabled } = + useBooleanState(false); + // 전화번호 인증 확인 여부 + const { value: isVerificationDisabled, setValue: setIsVerificationDisabled } = + useBooleanState(false); - const handleVerificationButtonClick = () => { + const { verificationMutate } = useVerificationMutation(setIsVerificationDisabled); + const { requestVerificationMutate } = useRequestVerificationMutation(joinUserData.phoneNumber); + + const handleRequestVerificationButtonClick = () => { requestVerificationMutate(); - setIsButtonDisabled(true); + setIsRequestVerificationDisabled(true); setIsVerification(true); + setIsVerificationDisabled(false); // 5초뒤 비활성화를 풀어줌 setTimeout(() => { - setIsButtonDisabled(false); + setIsRequestVerificationDisabled(false); }, 7000); }; - return { handleVerificationButtonClick, isButtonDisabled, isVerification }; + const handleVerificationButtonClick = () => { + verificationMutate(joinUserData); + }; + + return { + handleRequestVerificationButtonClick, + handleVerificationButtonClick, + isRequestVerificationDisabled, + isVerificationDisabled, + isVerification, + }; }; export const useTimer = () => { @@ -58,7 +79,7 @@ export const useTimer = () => { }; export const useInput = () => { - const [joinUserData, setJoinUserData] = useState({ + const [joinUserData, setJoinUserData] = useState({ phoneNumber: '', code: '', name: '', diff --git a/apps/user/src/components/common/Footer/Footer.tsx b/apps/user/src/components/common/Footer/Footer.tsx index a1c75b9e2..a3588e881 100644 --- a/apps/user/src/components/common/Footer/Footer.tsx +++ b/apps/user/src/components/common/Footer/Footer.tsx @@ -1,83 +1,81 @@ import { ROUTES } from '@/constants/common/constant'; import { color, font } from '@maru/theme'; -import { Column, Link, Row, Text } from '@maru/ui'; +import { Column, Row, Text } from '@maru/ui'; import { flex } from '@maru/utils'; import Image from 'next/image'; -import { useRouter } from 'next/navigation'; +import Link from 'next/link'; import styled from 'styled-components'; const Footer = () => { - const router = useRouter(); - return ( - - logo_gray - - - - 주소: 부산광역시 강서구 가락대로 1393 봉림동 15 (46708) - - - 교무실(입학처): 051-971-2153, Fax: 051-971-2061 + + + logo_gray + + + + 주소: 부산광역시 강서구 가락대로 1393 봉림동 15 (46708) + + + 교무실(입학처): 051-971-2153, Fax: 051-971-2061 + + + 행정실:051-971-2152, Fax: 051-971-6325 + + + + Copyright © 밤돌이로 all rights reserved. - - 행정실:051-971-2152, Fax: 051-971-6325 - - - - Copyright © 밤돌이로 all rights reserved. - - - - - - - - router.push(ROUTES.FORM)}>원서접수 - router.push(ROUTES.NOTICE)}>공지사항 - router.push(ROUTES.FAQ)}>자주묻는질문 - console.log('학교 소개 페이지')}>학교소개 - - - console.log('test')}>이용약관 - console.log('test')}>개인정보처리방침 - console.log('test')}>학교 홈페이지 - - - round_instagram - round_bamdoliro + + + + + 원서접수 + 공지사항 + 자주묻는질문 + 학교소개 + + + 이용약관 + 개인정보처리방침 + 학교 홈페이지 + + + + round-instagram + round-bamdoliro + - + ); }; export default Footer; -const StyledFooter = styled.div` - ${flex({ justifyContent: 'space-between', alignItems: 'center' })} +const StyledFooter = styled.footer` background-color: ${color.gray100}; - height: 350px; - width: 100%; - padding: 40px 100px; + padding: 40px 96px 82px; `; -const InfoBox = styled.div` - ${flex({ flexDirection: 'column' })} - gap: 40px; - width: 489px; +const FooterBox = styled.div` + ${flex({ justifyContent: 'space-between', alignItems: 'center' })} + height: 350px; + max-width: 1248px; + width: 100%; + margin: 0 auto; `; const ContentBox = styled.div` @@ -87,10 +85,7 @@ const ContentBox = styled.div` padding-bottom: 20px; `; -const NavigationBox = styled.div` - // @TODO Link 리팩토링하면서 +const DirectLink = styled(Link)` ${font.p3} color: ${color.gray600}; - display: flex; - gap: 132px; `; diff --git a/apps/user/src/components/common/Header/Header.tsx b/apps/user/src/components/common/Header/Header.tsx index ee825dc0b..962e85f8e 100644 --- a/apps/user/src/components/common/Header/Header.tsx +++ b/apps/user/src/components/common/Header/Header.tsx @@ -1,8 +1,7 @@ import { ROUTES } from '@/constants/common/constant'; import { useUser } from '@/hooks'; import { color } from '@maru/theme'; -import { Button, Row, UnderLineButton } from '@maru/ui'; -import { flex } from '@maru/utils'; +import { Button, Row, UnderlineButton } from '@maru/ui'; import Image from 'next/image'; import { usePathname, useRouter } from 'next/navigation'; import styled from 'styled-components'; @@ -14,7 +13,7 @@ const NAVIGATION_DATA = [ }, { name: '성적 모의 계산', - route: ROUTES.GRADE_SIMULATION, + route: ROUTES.SCORE_SIMULATION, }, { name: '원서작성', @@ -37,46 +36,52 @@ const Header = () => { return ( - - + router.push(ROUTES.MAIN)} - alt="logo" - /> - {isLoggedIn ? ( - - ) : ( - - - - - )} - - - {NAVIGATION_DATA.map(({ route, name }, index) => { - return ( - router.push(route)}> - {name} - - ); - })} - + alignItems="center" + justifyContent="space-between"> + router.push(ROUTES.MAIN)} + alt="logo" + /> + {isLoggedIn ? ( + + ) : ( + + + + + )} + + + {NAVIGATION_DATA.map(({ route, name }, index) => { + return ( + router.push(route)}> + {name} + + ); + })} + + ); }; @@ -84,22 +89,14 @@ const Header = () => { export default Header; const StyledHeader = styled.div` - max-width: 1448px; - height: 118px; - background-color: ${color.white}; - margin: 0 auto; - padding: 0px 100px; - border-bottom: 1px solid ${color.gray200}; -`; - -const HeaderBar = styled.div` - ${flex({ alignItems: 'center', justifyContent: 'space-between' })} width: 100%; + height: 118px; + box-shadow: 0 1px 0 0 ${color.gray200}; `; -const NavigationBar = styled.div` - ${flex({ alignItems: 'center' })} - width: 100%; - height: 54px; +const HeaderBox = styled.div` + max-width: 1448px; + height: 100%; background-color: ${color.white}; + margin: 0 auto; `; diff --git a/apps/user/src/components/common/Header/Profile/Profile.tsx b/apps/user/src/components/common/Header/Profile/Profile.tsx index 52b0cf8f2..8b31d609e 100644 --- a/apps/user/src/components/common/Header/Profile/Profile.tsx +++ b/apps/user/src/components/common/Header/Profile/Profile.tsx @@ -36,7 +36,7 @@ const Profile = () => { {userData.name} - @{userData.phoneNumber.split('@')[0]} + @{userData.name} diff --git a/apps/user/src/components/common/Wrappers/AuthWrapper/AuthWrapper.tsx b/apps/user/src/components/common/Wrappers/AuthWrapper/AuthWrapper.tsx index 8c2b1dd60..a41cbc7d8 100644 --- a/apps/user/src/components/common/Wrappers/AuthWrapper/AuthWrapper.tsx +++ b/apps/user/src/components/common/Wrappers/AuthWrapper/AuthWrapper.tsx @@ -50,7 +50,10 @@ const AuthWrapper = ({ children }: Props) => { if (LOGGEDIN_PRIVATE_PAGE.includes(pathName)) { redirect(ROUTES.MAIN); } - if (dayjs().isBefore(제출_시작_날짜) || dayjs().isAfter(제출_마감_날짜)) { + if ( + dayjs().isBefore(제출_시작_날짜) || + (dayjs().isAfter(제출_마감_날짜) && process.env.NODE_ENV !== 'development') + ) { if (pathName === ROUTES.FORM) { overlay.open(({ isOpen, close }) => ( { const [newSubjectList, setNewSubjectList] = useNewSubjectStore(); const [subjectList, setSubjectList] = useSubjectStore(); const setForm = useSetFormStore(); - const footerRef = useRef(null); - const isMount = useRef(true); const newSubjectIdRef = useRef(newSubjectList.length); const handleAddNewSubjectButtonClick = () => { @@ -28,14 +26,6 @@ const GradeCalculator = () => { setNewSubjectList((prev) => [...prev, newSubject]); }; - useEffect(() => { - if (isMount.current) { - isMount.current = false; - return; - } - if (newSubjectList.length) footerRef.current?.scrollIntoView(); - }, [newSubjectList]); - useEffect(() => { const studentSubjectList = [...subjectList, ...newSubjectList].map( ({ id, ...rest }) => rest, @@ -82,7 +72,7 @@ const GradeCalculator = () => { setNewSubjectList={setNewSubjectList} /> ))} - + diff --git a/apps/user/src/components/form/FindSchoolModal/FindSchoolModal.tsx b/apps/user/src/components/form/FindSchoolModal/FindSchoolModal.tsx index 6916046d3..18a1a3b51 100644 --- a/apps/user/src/components/form/FindSchoolModal/FindSchoolModal.tsx +++ b/apps/user/src/components/form/FindSchoolModal/FindSchoolModal.tsx @@ -1,5 +1,6 @@ import { Loader } from '@/components/common'; import { useSetFormStore } from '@/store'; +import { School } from '@/types/form/client'; import { useDebounceInput } from '@maru/hooks'; import { Column, Modal, SearchInput } from '@maru/ui'; import { Suspense, useState } from 'react'; @@ -12,28 +13,33 @@ interface Props { const FindSchoolModal = ({ isOpen, onClose }: Props) => { const setForm = useSetFormStore(); - const [selectedSchool, setSelectedSchool] = useState({ name: '', location: '', code: '' }); + const [school, setSchool] = useState({ + name: '', + location: '', + code: '', + }); const { - value: schoolSearchQuery, - onChange: handleSchoolSearchQueryDataChange, - debouncedValue: debouncedSchoolSearchQuery, + value: schoolName, + onChange: handleSchoolNameDataChange, + debouncedValue: debouncedSchoolName, } = useDebounceInput({ initialValue: '' }); const handleConfirmModalButtonClick = () => { - const { name, location } = selectedSchool; + const { name, location, code } = school; setForm((prev) => ({ ...prev, education: { ...prev.education, schoolName: name, schoolLocation: location, + schoolCode: code, }, })); onClose(); }; const handleCloseModalButtonClick = () => { - setSelectedSchool({ name: '', location: '', code: '' }); + setSchool({ name: '', location: '', code: '' }); onClose(); }; @@ -48,16 +54,16 @@ const FindSchoolModal = ({ isOpen, onClose }: Props) => { onConfirm={handleConfirmModalButtonClick}> }> diff --git a/apps/user/src/components/form/FindSchoolModal/SchoolList/SchoolList.tsx b/apps/user/src/components/form/FindSchoolModal/SchoolList/SchoolList.tsx index 7329afd8b..06fdc3416 100644 --- a/apps/user/src/components/form/FindSchoolModal/SchoolList/SchoolList.tsx +++ b/apps/user/src/components/form/FindSchoolModal/SchoolList/SchoolList.tsx @@ -8,12 +8,12 @@ import { Dispatch, SetStateAction } from 'react'; import styled, { css } from 'styled-components'; interface Props { - selectedSchool: School; - setSelectedSchool: Dispatch>; + school: School; + setSchool: Dispatch>; debouncedSchoolSearchQuery: string; } -const SchoolList = ({ selectedSchool, setSelectedSchool, debouncedSchoolSearchQuery }: Props) => { +const SchoolList = ({ school, setSchool, debouncedSchoolSearchQuery }: Props) => { const { data: schoolListData } = useSchoolListQuery(debouncedSchoolSearchQuery); return schoolListData ? ( @@ -21,10 +21,10 @@ const SchoolList = ({ selectedSchool, setSelectedSchool, debouncedSchoolSearchQu {schoolListData.map(({ name, location, code }: School) => ( setSelectedSchool({ name, location, code })}> + selected={school.code === code} + onClick={() => setSchool({ name, location, code })}> - {selectedSchool.code === code && ( + {school.code === code && ( )} {name} diff --git a/apps/user/src/components/form/ProgressBar/ProgressBar.tsx b/apps/user/src/components/form/ProgressSteps/ProgressSteps.tsx similarity index 87% rename from apps/user/src/components/form/ProgressBar/ProgressBar.tsx rename to apps/user/src/components/form/ProgressSteps/ProgressSteps.tsx index fec204b97..1fcaa3d17 100644 --- a/apps/user/src/components/form/ProgressBar/ProgressBar.tsx +++ b/apps/user/src/components/form/ProgressSteps/ProgressSteps.tsx @@ -21,11 +21,11 @@ const PROGRESS_BAR_DATA = [ '자기소개서', ] as const; -const ProgressBar = () => { +const ProgressSteps = () => { const formStep = useFormStepValueStore(); return ( - + {PROGRESS_BAR_DISPLAY_DATA.map((item, index) => ( { {index + 1} ))} - + ); }; -export default ProgressBar; +export default ProgressSteps; -const StyledProgressBar = styled.div` +const StyledProgressSteps = styled.div` position: relative; ${flex({ alignItems: 'center', justifyContent: 'space-between' })} width: 100%; - padding: 0px 205px; - margin: 52px 0px 72px 0px; + max-width: 1440px; + margin: 0 auto; + padding: 52px 100px 86px; &::before { position: absolute; content: ''; - width: calc(100% - 410px); + width: calc(100% - 200px); height: 2px; background-color: ${color.gray300}; } diff --git a/apps/user/src/components/form/index.ts b/apps/user/src/components/form/index.ts index 5333f7f41..eaf4cc618 100644 --- a/apps/user/src/components/form/index.ts +++ b/apps/user/src/components/form/index.ts @@ -1,13 +1,13 @@ export * from './Calculators'; -export { default as FindAddressModal } from './FindAddressModal/FindAddressModal'; -export { default as FindSchoolModal } from './FindSchoolModal/FindSchoolModal'; -export { default as FormController } from './FormController/FormController'; -export { default as ProfileUploader } from './ProfileUploader/ProfileUploader'; -export { default as ProgressBar } from './ProgressBar/ProgressBar'; -export { default as CompleteAlaram } from './CompleteAlaram/CompleteAlaram'; export { default as CheckFormComplete } from './CheckFormComplete/CheckFormComplete'; +export { default as CompleteAlaram } from './CompleteAlaram/CompleteAlaram'; +export { default as DateBox } from './DateBox/DateBox'; export { default as DraftFormConfirm } from './DraftFormConfirm/DraftFormConfirm'; export { default as FinalFormConfirm } from './FinalFormConfirm/FinalFormConfirm'; export { default as FinalFormTable } from './FinalFormTable/FinalFormTable'; -export { default as DateBox } from './DateBox/DateBox'; +export { default as FindAddressModal } from './FindAddressModal/FindAddressModal'; +export { default as FindSchoolModal } from './FindSchoolModal/FindSchoolModal'; +export { default as FormController } from './FormController/FormController'; export { default as GradePreview } from './GradePreview/GradePreview'; +export { default as ProfileUploader } from './ProfileUploader/ProfileUploader'; +export { default as ProgressSteps } from './ProgressSteps/ProgressSteps'; diff --git a/apps/user/src/components/main/ApplicationBox/ApplicationBox.tsx b/apps/user/src/components/main/ApplicationBox/ApplicationBox.tsx new file mode 100644 index 000000000..139721a26 --- /dev/null +++ b/apps/user/src/components/main/ApplicationBox/ApplicationBox.tsx @@ -0,0 +1,36 @@ +import { IconArrowOutward } from '@maru/icon'; +import { color } from '@maru/theme'; +import { Row, Text } from '@maru/ui'; +import { flex } from '@maru/utils'; +import styled from 'styled-components'; + +const ApplicationBox = () => { + return ( + alert('준비 중입니다.')}> + + + 입학전형 설명회 신청 + + + + + 일시: 7월 8일, 8월 26일, 9월 16일, 10월 4일 +
+ 장소: 본교 SRC관 1층 +
+
+ ); +}; + +export default ApplicationBox; + +const StyledApplicationBox = styled.div` + ${flex({ flexDirection: 'column', justifyContent: 'space-between' })} + width: 384px; + height: 180px; + padding: 28px 32px; + background-color: ${color.white}; + border: 1px solid ${color.gray200}; + border-radius: 12px; + cursor: pointer; +`; diff --git a/apps/user/src/components/main/Faq/MainFaq.tsx b/apps/user/src/components/main/Faq/MainFaq.tsx index 22cece26b..fbedffefe 100644 --- a/apps/user/src/components/main/Faq/MainFaq.tsx +++ b/apps/user/src/components/main/Faq/MainFaq.tsx @@ -2,8 +2,9 @@ import { Loader } from '@/components/common'; import { ROUTES } from '@/constants/common/constant'; import { IconArrowRight } from '@maru/icon'; import { color } from '@maru/theme'; -import { Link, Text } from '@maru/ui'; +import { Text } from '@maru/ui'; import { flex } from '@maru/utils'; +import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { Suspense } from 'react'; import styled from 'styled-components'; @@ -14,12 +15,12 @@ const MainFaq = () => { return ( - router.push(ROUTES.FAQ)} gap="8px"> + 자주묻는 질문 - + }> @@ -36,3 +37,8 @@ const StyledMainFaq = styled.div` width: 596px; height: 100%; `; + +const DirectLink = styled(Link)` + ${flex({ alignItems: 'center' })}; + gap: 8px; +`; diff --git a/apps/user/src/components/main/GuidelineBox/GuidelineBox.tsx b/apps/user/src/components/main/GuidelineBox/GuidelineBox.tsx new file mode 100644 index 000000000..070c10cf5 --- /dev/null +++ b/apps/user/src/components/main/GuidelineBox/GuidelineBox.tsx @@ -0,0 +1,39 @@ +import { IconArrowOutward } from '@maru/icon'; +import { color } from '@maru/theme'; +import { Row, Text } from '@maru/ui'; +import { flex } from '@maru/utils'; +import styled from 'styled-components'; + +const GuidelineBox = () => { + return ( + + window.open( + 'https://school.busanedu.net/viewer/doc.html?fn=f9ccabacf50aba9dbe108bdbccf244f34b1a4bf9118f8c63e034e9af8c30afc1&rs=/upload/temp/convertToHtml/202308/bssm-h/', + ) + }> + + + 입학 전형 요강 + + + + + 클릭해서 입학 전형 요강을 확인하세요 + + + ); +}; + +export default GuidelineBox; + +const StyledGuidelineBox = styled.div` + ${flex({ flexDirection: 'column', justifyContent: 'space-between' })} + width: 384px; + height: 180px; + padding: 28px 32px; + background-color: ${color.white}; + border: 1px solid ${color.gray200}; + border-radius: 12px; + cursor: pointer; +`; diff --git a/apps/user/src/components/main/Notice/MainNotice.tsx b/apps/user/src/components/main/Notice/MainNotice.tsx index 25dbfa4be..667a5f168 100644 --- a/apps/user/src/components/main/Notice/MainNotice.tsx +++ b/apps/user/src/components/main/Notice/MainNotice.tsx @@ -1,25 +1,23 @@ -import { useRouter } from 'next/navigation'; import { Loader } from '@/components/common'; import { ROUTES } from '@/constants/common/constant'; import { IconArrowRight } from '@maru/icon'; import { color } from '@maru/theme'; -import { Link, Text } from '@maru/ui'; +import { Text } from '@maru/ui'; import { flex } from '@maru/utils'; +import Link from 'next/link'; import { Suspense } from 'react'; import styled from 'styled-components'; import MainNoticeList from './MainNoticeList/MainNoticeList'; const MainNotice = () => { - const router = useRouter(); - return ( - router.push(ROUTES.NOTICE)} gap="8px"> + 공지사항 - + }> @@ -36,3 +34,8 @@ const StyledMainNotice = styled.div` width: 596px; height: 100%; `; + +const DirectLink = styled(Link)` + ${flex({ alignItems: 'center' })}; + gap: 8px; +`; diff --git a/apps/user/src/components/main/SimulatorBox/SimulatorBox.tsx b/apps/user/src/components/main/SimulatorBox/SimulatorBox.tsx new file mode 100644 index 000000000..25f8eac3b --- /dev/null +++ b/apps/user/src/components/main/SimulatorBox/SimulatorBox.tsx @@ -0,0 +1,39 @@ +import { ROUTES } from '@/constants/common/constant'; +import { IconFunction } from '@maru/icon'; +import { color } from '@maru/theme'; +import { Row, Text } from '@maru/ui'; +import { flex } from '@maru/utils'; +import { useRouter } from 'next/navigation'; +import styled from 'styled-components'; + +const SimulatorBox = () => { + const router = useRouter(); + + return ( + router.push(ROUTES.SCORE_SIMULATION)}> + + + 성적 모의 계산 + + + + + 복잡한 성적 산출 공식을 +
자동으로 계산할 수 있어요 +
+
+ ); +}; + +export default SimulatorBox; + +const StyledSimulatorBox = styled.div` + ${flex({ flexDirection: 'column', justifyContent: 'space-between' })} + width: 384px; + height: 180px; + padding: 28px 32px; + background-color: ${color.white}; + border: 1px solid ${color.gray200}; + border-radius: 12px; + cursor: pointer; +`; diff --git a/apps/user/src/components/main/index.ts b/apps/user/src/components/main/index.ts index 4a64996a3..ed6a1da42 100644 --- a/apps/user/src/components/main/index.ts +++ b/apps/user/src/components/main/index.ts @@ -1,4 +1,7 @@ +export { default as ApplicationBox } from './ApplicationBox/ApplicationBox'; export { default as Dday } from './Dday/Dday'; export { default as Faq } from './Faq/MainFaq'; +export { default as GuidelineBox } from './GuidelineBox/GuidelineBox'; export { default as Notice } from './Notice/MainNotice'; export { default as Schedule } from './Schedule/Schedule'; +export { default as SimulatorBox } from './SimulatorBox/SimulatorBox'; diff --git a/apps/user/src/components/notice/NoticeDetailContent/NoticeDetailContent.tsx b/apps/user/src/components/notice/NoticeDetailContent/NoticeDetailContent.tsx index e0bc33686..0486203f2 100644 --- a/apps/user/src/components/notice/NoticeDetailContent/NoticeDetailContent.tsx +++ b/apps/user/src/components/notice/NoticeDetailContent/NoticeDetailContent.tsx @@ -1,7 +1,7 @@ import { useNoticeDetailQuery } from '@/services/notice/queries'; -import { color } from '@maru/theme'; +import { color, font } from '@maru/theme'; import { Column, Text } from '@maru/ui'; -import { flex, formatCreatedAt } from '@maru/utils'; +import { convertLink, flex, formatCreatedAt } from '@maru/utils'; import styled from 'styled-components'; interface Props { @@ -23,9 +23,7 @@ const NoticeDetailContent = ({ id }: Props) => {
- - {noticeDetailData.content} - + ) : null; }; @@ -45,3 +43,7 @@ const NoticeHeader = styled.div` border-bottom: 1px solid ${color.gray300}; margin-bottom: 8px; `; +const Content = styled.div` + ${font.p2}; + color: ${color.gray900}; +`; diff --git a/apps/user/src/constants/common/constant.ts b/apps/user/src/constants/common/constant.ts index 1746b4e84..2066efa4d 100644 --- a/apps/user/src/constants/common/constant.ts +++ b/apps/user/src/constants/common/constant.ts @@ -21,7 +21,7 @@ export const ROUTES = { SIGNUP: '/signup', FIRST_RESULT: '/result/first', FINAL_RESULT: '/result/first', - GRADE_SIMULATION: '/grade-simulation', + SCORE_SIMULATION: '/score-simulation', } as const; export const TOKEN = { diff --git a/apps/user/src/layouts/AppLayout.tsx b/apps/user/src/layouts/AppLayout.tsx index b63014b75..1bf84df21 100644 --- a/apps/user/src/layouts/AppLayout.tsx +++ b/apps/user/src/layouts/AppLayout.tsx @@ -8,21 +8,19 @@ interface Props { header?: boolean; footer?: boolean; children: ReactNode; - backgroundColor?: string; - style?: CSSProperties; + backgroundColor?: CSSProperties['backgroundColor']; } const AppLayout = ({ children, backgroundColor = color.white, - style, header = false, footer = false, }: Props) => { return ( <> {header &&
} - {children} + {children} {footer &&