diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..ba7f29f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "singleQuote": true, + "semi": true, + "tabWidth": 2, + "trailingComma": "all", + "printWidth": 80, + "bracketSpacing": true, + "arrowParens": "avoid", + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/index.html b/index.html index 14eecfc..f52af8c 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Vite + React + TS + 농기구온
diff --git a/package.json b/package.json index ea7e869..89408e2 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@tanstack/react-query": "^5.81.5", "axios": "^1.10.0", "clsx": "^2.1.1", + "daisyui": "^5.0.43", "prettier": "^3.6.2", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 774ce72..e5dd208 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + daisyui: + specifier: ^5.0.43 + version: 5.0.43 prettier: specifier: ^3.6.2 version: 3.6.2 @@ -839,6 +842,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + daisyui@5.0.43: + resolution: {integrity: sha512-2pshHJ73vetSpsbAyaOncGnNYL0mwvgseS1EWy1I9Qpw8D11OuBoDNIWrPIME4UFcq2xuff3A9x+eXbuFR9fUQ==} + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -2173,6 +2179,8 @@ snapshots: csstype@3.1.3: {} + daisyui@5.0.43: {} + debug@4.4.1: dependencies: ms: 2.1.3 diff --git a/src/app/global.css b/src/app/global.css index 0789208..f67e06e 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -1,16 +1,21 @@ -@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.8/dist/web/variable/pretendardvariable-dynamic-subset.css"); -@import "tailwindcss"; +@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.8/dist/web/variable/pretendardvariable-dynamic-subset.css'); +@import 'tailwindcss'; +@plugin "daisyui" { + themes: light --default; +} @theme { - --color-m: #377ff8; + --color-m: #6a8e3f; + --color-m-transparent: rgba(106, 142, 63, 0.04); + --color-m-hover: #6a8e3f33; --color-md: #324160; --color-ml: #6da2fe; --color-mxl: #ecf5fe; - --color-s: #767676; + --color-s: #191919; --color-sd: #333; - --color-sl: #dadada; - --color-sxl: #f5f5f5; + --color-sl: #d9d9d9; + --color-sxl: #dadada; --color-res: #8b9aad; @@ -29,33 +34,33 @@ --spacing-normal: 20px; --spacing-normal-half: 10px; --spacing-mt: 24px; - + --spacing-dock-height: 100px; --radius-lg: 20px; --radius-md: 12px; } @font-face { - font-family: "Fredoka"; - src: url("../assets/font/fredoka-variable.ttf") format("truetype"); + font-family: 'Fredoka'; + src: url('../assets/font/fredoka-variable.ttf') format('truetype'); font-weight: 600, 700; } :root { font-family: - "Pretendard Variable", - "Pretendard", + 'Pretendard Variable', + 'Pretendard', -apple-system, BlinkMacSystemFont, system-ui, Roboto, - "Helvetica Neue", - "Segoe UI", - "Apple SD Gothic Neo", - "Noto Sans KR", - "Malgun Gothic", - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol", + 'Helvetica Neue', + 'Segoe UI', + 'Apple SD Gothic Neo', + 'Noto Sans KR', + 'Malgun Gothic', + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol', sans-serif; font-size: 16px; color: #111; @@ -63,30 +68,48 @@ } /* Chrome, Safari, Edge, Opera */ -input[type="number"]::-webkit-outer-spin-button, -input[type="number"]::-webkit-inner-spin-button { +input[type='number']::-webkit-outer-spin-button, +input[type='number']::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } /* Firefox */ -input[type="number"] { +input[type='number'] { -moz-appearance: textfield; } @layer utilities { .container-mobile { - max-width: 402px; + max-width: 390px; margin: 0 auto; + overflow-x: hidden; + } + .scrollbar-hide { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ } .scrollbar-hide::-webkit-scrollbar { - display: none; + display: none; /* Chrome, Safari, Opera */ + width: 0; + height: 0; } - .scrollbar-hide { - -ms-overflow-style: none; - scrollbar-width: none; + .scrollbar-hide::-webkit-scrollbar-thumb { + background: transparent; /* 혹시라도 Thumb이 남아있으면 제거 */ + } + + .scrollbar-hide::-webkit-scrollbar-track { + background: transparent; /* 트랙도 제거 */ + } + + .shadow-dock { + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.12); + } + + .shadow-homeBox { + box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.12); } .overlay { diff --git a/src/app/main.tsx b/src/app/main.tsx index 318c8cd..810f71c 100644 --- a/src/app/main.tsx +++ b/src/app/main.tsx @@ -1,13 +1,13 @@ -import { createRoot } from "react-dom/client"; -import "./global.css"; -import "@stackflow/plugin-basic-ui/index.css"; -import App from "./App"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { createRoot } from 'react-dom/client'; +import '@stackflow/plugin-basic-ui/index.css'; +import './global.css'; +import App from './App'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; const queryClient = new QueryClient(); -createRoot(document.getElementById("root")!).render( +createRoot(document.getElementById('root')!).render( - + , ); diff --git a/src/app/stackflow/Stack.tsx b/src/app/stackflow/Stack.tsx index dda899c..efc1388 100644 --- a/src/app/stackflow/Stack.tsx +++ b/src/app/stackflow/Stack.tsx @@ -1,14 +1,20 @@ -import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; -import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; -import { stackflow } from "@stackflow/react"; +import { HomeScreen } from '@/screen/home/ui'; +import { JoinScreen } from '@/screen/join/ui'; +import { basicUIPlugin } from '@stackflow/plugin-basic-ui'; +import { basicRendererPlugin } from '@stackflow/plugin-renderer-basic'; +import { stackflow } from '@stackflow/react'; export const { Stack, useFlow } = stackflow({ transitionDuration: 350, - activities: {}, + activities: { + JoinScreen, + HomeScreen, + }, plugins: [ basicRendererPlugin(), basicUIPlugin({ - theme: "cupertino", + theme: 'cupertino', }), ], + initialActivity: () => 'HomeScreen', }); diff --git a/src/assets/icons/icon-camera-solid.svg b/src/assets/icons/icon-camera-solid.svg new file mode 100644 index 0000000..e4581fb --- /dev/null +++ b/src/assets/icons/icon-camera-solid.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/icons/icon-camera.svg b/src/assets/icons/icon-camera.svg new file mode 100644 index 0000000..ee6e88d --- /dev/null +++ b/src/assets/icons/icon-camera.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/icon-edit.svg b/src/assets/icons/icon-edit.svg new file mode 100644 index 0000000..bf3a9fa --- /dev/null +++ b/src/assets/icons/icon-edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/icon-home-selected.svg b/src/assets/icons/icon-home-selected.svg new file mode 100644 index 0000000..127ccbd --- /dev/null +++ b/src/assets/icons/icon-home-selected.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/icon-home.svg b/src/assets/icons/icon-home.svg new file mode 100644 index 0000000..d8a6a11 --- /dev/null +++ b/src/assets/icons/icon-home.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/icon-phone.svg b/src/assets/icons/icon-phone.svg new file mode 100644 index 0000000..b1f6712 --- /dev/null +++ b/src/assets/icons/icon-phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/icon-reservation-selected.svg b/src/assets/icons/icon-reservation-selected.svg new file mode 100644 index 0000000..b0d0b98 --- /dev/null +++ b/src/assets/icons/icon-reservation-selected.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/icon-reservation.svg b/src/assets/icons/icon-reservation.svg new file mode 100644 index 0000000..4ef5671 --- /dev/null +++ b/src/assets/icons/icon-reservation.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/icon-tractor-black.svg b/src/assets/icons/icon-tractor-black.svg new file mode 100644 index 0000000..5e63453 --- /dev/null +++ b/src/assets/icons/icon-tractor-black.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/icons/icon-tractor.png b/src/assets/icons/icon-tractor.png new file mode 100644 index 0000000..17d5ad9 Binary files /dev/null and b/src/assets/icons/icon-tractor.png differ diff --git a/src/assets/icons/icon-user-selected.svg b/src/assets/icons/icon-user-selected.svg new file mode 100644 index 0000000..e1846c1 --- /dev/null +++ b/src/assets/icons/icon-user-selected.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/icon-user.svg b/src/assets/icons/icon-user.svg new file mode 100644 index 0000000..faab88d --- /dev/null +++ b/src/assets/icons/icon-user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts new file mode 100644 index 0000000..a498b9c --- /dev/null +++ b/src/assets/icons/index.ts @@ -0,0 +1,27 @@ +import Logo from './icon-tractor.png'; +import CameraIcon from './icon-camera.svg'; +import TractorBlackIcon from './icon-tractor-black.svg'; +import PhoneIcon from './icon-phone.svg'; +import CameraSolidIcon from './icon-camera-solid.svg'; +import EditIcon from './icon-edit.svg'; +import HomeIcon from './icon-home.svg'; +import UserIcon from './icon-user.svg'; +import ReservationIcon from './icon-reservation.svg'; +import HomeSelectedIcon from './icon-home-selected.svg'; +import UserSelectedIcon from './icon-user-selected.svg'; +import ReservationSelectedIcon from './icon-reservation-selected.svg'; + +export { + Logo, + CameraIcon, + TractorBlackIcon, + PhoneIcon, + CameraSolidIcon, + EditIcon, + HomeIcon, + UserIcon, + ReservationIcon, + HomeSelectedIcon, + UserSelectedIcon, + ReservationSelectedIcon, +}; diff --git a/src/assets/images/background.png b/src/assets/images/background.png new file mode 100644 index 0000000..7f11f23 Binary files /dev/null and b/src/assets/images/background.png differ diff --git a/src/assets/images/index.ts b/src/assets/images/index.ts new file mode 100644 index 0000000..501e8ea --- /dev/null +++ b/src/assets/images/index.ts @@ -0,0 +1,3 @@ +import BackgroundImage from "./background.png"; + +export { BackgroundImage }; diff --git a/src/screen/home/ui/HomeScreen.tsx b/src/screen/home/ui/HomeScreen.tsx new file mode 100644 index 0000000..ad9c054 --- /dev/null +++ b/src/screen/home/ui/HomeScreen.tsx @@ -0,0 +1,19 @@ +import { AppScreen } from '@stackflow/plugin-basic-ui'; +import { BackgroundImage } from '@/assets/images'; +import { BasicAppBar, Dock } from '@/shared/ui'; +import { HomeContainer } from '@/widgets/home/ui'; + +export default function HomeScreen() { + return ( + <> + + + + + + ); +} diff --git a/src/screen/home/ui/index.ts b/src/screen/home/ui/index.ts new file mode 100644 index 0000000..b9e63f1 --- /dev/null +++ b/src/screen/home/ui/index.ts @@ -0,0 +1 @@ +export { default as HomeScreen } from './HomeScreen'; diff --git a/src/screen/join/ui/JoinScreen.tsx b/src/screen/join/ui/JoinScreen.tsx new file mode 100644 index 0000000..4e7e2ca --- /dev/null +++ b/src/screen/join/ui/JoinScreen.tsx @@ -0,0 +1,11 @@ +import { AppScreen } from '@stackflow/plugin-basic-ui'; +import { BackgroundImage } from '@/assets/images'; +import { JoinContainer } from '@/widgets/join/ui'; + +export default function JoinScreen() { + return ( + + + + ); +} diff --git a/src/screen/join/ui/index.ts b/src/screen/join/ui/index.ts new file mode 100644 index 0000000..53b8a69 --- /dev/null +++ b/src/screen/join/ui/index.ts @@ -0,0 +1 @@ +export { default as JoinScreen } from './JoinScreen'; diff --git a/src/shared/api/axios.ts b/src/shared/api/axios.ts new file mode 100644 index 0000000..c6d7c96 --- /dev/null +++ b/src/shared/api/axios.ts @@ -0,0 +1,70 @@ +import axios, { type AxiosResponse } from 'axios'; + +interface PostRequestParams { + request: string; + headers?: { [key: string]: string }; + data?: TData; +} + +interface GetRequestParams { + request: string; + headers?: { [key: string]: string }; + params?: TParams; +} + +const instance = axios.create({ + baseURL: 'https://usfarmtools.com/api', +}); + +export async function get( + config: GetRequestParams, +): Promise> { + const { request, headers, params } = config; + try { + const response = await instance.get(request, { + withCredentials: true, + params: params, + headers: headers || undefined, + }); + return response; + } catch (error: unknown) { + console.log(error); + if (axios.isAxiosError(error)) + throw new Error(error.response?.data.message); + else throw new Error('에러가 발생했습니다'); + } +} + +export async function post( + config: PostRequestParams, +): Promise> { + const { request, data, headers } = config; + try { + const response = await instance.post< + TResponse, + AxiosResponse, + TData + >(request, data, { + withCredentials: true, + headers: headers || undefined, + }); + return response; + } catch (error: unknown) { + console.log(error); + if (axios.isAxiosError(error)) + throw new Error(error.response?.data.message); + else throw new Error('에러가 발생했습니다'); + } +} + +export const del = async (request: string) => { + try { + const response = await instance.delete(`${request}`); + return response; + } catch (error: unknown) { + console.log(error); + if (axios.isAxiosError(error)) + throw new Error(error.response?.data.message); + else throw new Error('에러가 발생했습니다'); + } +}; diff --git a/src/shared/api/index.ts b/src/shared/api/index.ts new file mode 100644 index 0000000..706bbd0 --- /dev/null +++ b/src/shared/api/index.ts @@ -0,0 +1,2 @@ +export * from './axios'; +export * from './request'; diff --git a/src/shared/api/request.ts b/src/shared/api/request.ts new file mode 100644 index 0000000..0d9efcd --- /dev/null +++ b/src/shared/api/request.ts @@ -0,0 +1,3 @@ +export const REQUEST = { + JOIN: '/ocr/idcard', +}; diff --git a/src/shared/constants/dock.tsx b/src/shared/constants/dock.tsx new file mode 100644 index 0000000..7b1c649 --- /dev/null +++ b/src/shared/constants/dock.tsx @@ -0,0 +1,30 @@ +import type { DockItem } from '@/shared/types'; +import { PATH } from './path'; +import { + HomeIcon, + HomeSelectedIcon, + // ReservationIcon, + // ReservationSelectedIcon, + // UserIcon, + // UserSelectedIcon, +} from '@/assets/icons'; + +export const DOCK = { + // ['']: { + // title: '예약 현황', + // icon: , + // selectedIcon: , + // }, + [PATH.HOME]: { + title: '홈', + icon: , + selectedIcon: , + }, + // ['.']: { + // title: '나의 정보', + // icon: , + // selectedIcon: , + // }, +}; + +export const DOCK_ITEMS = Object.keys(DOCK) as Array; diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts new file mode 100644 index 0000000..a9ba2d6 --- /dev/null +++ b/src/shared/constants/index.ts @@ -0,0 +1,2 @@ +export * from './path'; +export * from './dock'; diff --git a/src/shared/constants/path.ts b/src/shared/constants/path.ts new file mode 100644 index 0000000..f3528f4 --- /dev/null +++ b/src/shared/constants/path.ts @@ -0,0 +1,4 @@ +export const PATH = { + HOME: 'HomeScreen', + JOIN: 'JoinScreen', +} as const; diff --git a/src/shared/types/dock.ts b/src/shared/types/dock.ts new file mode 100644 index 0000000..0892859 --- /dev/null +++ b/src/shared/types/dock.ts @@ -0,0 +1,3 @@ +import { DOCK } from '@/shared/constants'; + +export type DockItem = keyof typeof DOCK; diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts new file mode 100644 index 0000000..05a1f8b --- /dev/null +++ b/src/shared/types/index.ts @@ -0,0 +1,2 @@ +export * from './dock'; +export * from './path'; diff --git a/src/shared/types/path.ts b/src/shared/types/path.ts new file mode 100644 index 0000000..142c9e2 --- /dev/null +++ b/src/shared/types/path.ts @@ -0,0 +1,5 @@ +import { PATH } from '../constants'; + +type ValueOf = T[keyof T]; + +export type PathItem = ValueOf; diff --git a/src/shared/ui/AppBar.tsx b/src/shared/ui/AppBar.tsx new file mode 100644 index 0000000..e2340b9 --- /dev/null +++ b/src/shared/ui/AppBar.tsx @@ -0,0 +1,9 @@ +import { BackgroundImage } from '@/assets/images'; + +const baseStyle = { height: '58px', border: false }; + +export const BasicAppBar = { + ...baseStyle, + backgroundImage: `url(${BackgroundImage})`, + renderRight: () => <>, +}; diff --git a/src/shared/ui/Dock.tsx b/src/shared/ui/Dock.tsx new file mode 100644 index 0000000..75af86b --- /dev/null +++ b/src/shared/ui/Dock.tsx @@ -0,0 +1,59 @@ +import { useStack } from '@stackflow/react'; + +import { useFlow } from '@/app/stackflow'; + +import { DOCK, DOCK_ITEMS } from '@/shared/constants'; +import type { DockItem, PathItem } from '../types'; +import { cn } from '../utils'; + +interface DockProps { + isLoading?: boolean; +} + +interface DockButtonProps { + item: DockItem; + selected: boolean; +} + +export default function Dock(isLoading: DockProps) { + const stack = useStack(); + const info = stack.activities; + const current = info + .filter(i => i.transitionState === 'enter-done') + .map(i => i.name) + .pop() as PathItem; + + return ( + <> + {DOCK_ITEMS.length > 0 && ( +
+ {DOCK_ITEMS.map(item => ( + + ))} +
+ )} + + ); +} + +const DockButton = ({ item, selected }: DockButtonProps) => { + const { replace } = useFlow(); + + const onClick = () => { + replace(item, { animate: false }, { animate: false }); + }; + + return ( +
+ {selected ? DOCK[item].selectedIcon : DOCK[item].icon} +

+ {DOCK[item].title} +

+
+ ); +}; diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts new file mode 100644 index 0000000..d026ed2 --- /dev/null +++ b/src/shared/ui/index.ts @@ -0,0 +1,2 @@ +export * from './AppBar'; +export { default as Dock } from './Dock'; diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts new file mode 100644 index 0000000..57f9f48 --- /dev/null +++ b/src/shared/utils/index.ts @@ -0,0 +1 @@ +export * from './string'; diff --git a/src/shared/utils/string.ts b/src/shared/utils/string.ts new file mode 100644 index 0000000..33e3cfe --- /dev/null +++ b/src/shared/utils/string.ts @@ -0,0 +1,10 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export function getPath(base: string, path: string) { + return `${base}/${path}`; +} diff --git a/src/widgets/home/ui/HomeButton.tsx b/src/widgets/home/ui/HomeButton.tsx new file mode 100644 index 0000000..e7bc4f7 --- /dev/null +++ b/src/widgets/home/ui/HomeButton.tsx @@ -0,0 +1,43 @@ +export const HomeButton = ({ + icon, + label, + description, + buttonLabel, + onClick = () => {}, + isAtag = false, +}: { + icon: string; + label: string; + description: string; + buttonLabel: string; + onClick?: () => void; + isAtag?: boolean; +}) => { + return ( +
+

{label}

+ {description.split('
').map((line, index) => ( +

+ {line} +

+ ))} + {isAtag ? ( + + + {buttonLabel} + + ) : ( + + )} +
+ ); +}; diff --git a/src/widgets/home/ui/HomeContainer.tsx b/src/widgets/home/ui/HomeContainer.tsx new file mode 100644 index 0000000..8875352 --- /dev/null +++ b/src/widgets/home/ui/HomeContainer.tsx @@ -0,0 +1,39 @@ +import { HomeButton } from './HomeButton'; +import { + CameraSolidIcon, + EditIcon, + PhoneIcon, + TractorBlackIcon, +} from '@/assets/icons'; + +export default function HomeContainer() { + return ( +
+
+ + 농기구 대여 서비스 +
+
+ + + +
+
+ ); +} diff --git a/src/widgets/home/ui/index.ts b/src/widgets/home/ui/index.ts new file mode 100644 index 0000000..8601ff2 --- /dev/null +++ b/src/widgets/home/ui/index.ts @@ -0,0 +1 @@ +export { default as HomeContainer } from './HomeContainer'; diff --git a/src/widgets/join/api/index.ts b/src/widgets/join/api/index.ts new file mode 100644 index 0000000..f141a8d --- /dev/null +++ b/src/widgets/join/api/index.ts @@ -0,0 +1 @@ +export * from './join'; diff --git a/src/widgets/join/api/join.ts b/src/widgets/join/api/join.ts new file mode 100644 index 0000000..4e46635 --- /dev/null +++ b/src/widgets/join/api/join.ts @@ -0,0 +1,30 @@ +import { useFlow } from '@/app/stackflow'; +import { post, REQUEST } from '@/shared/api'; +import { PATH } from '@/shared/constants'; +import { useMutation } from '@tanstack/react-query'; +import type { Dispatch, SetStateAction } from 'react'; + +const submitIdCard = async (data: FormData) => { + const response = await post({ + request: REQUEST.JOIN, + data: data, + }); + return response.data; +}; + +export const useSubmitIdCard = ( + setSelectedImage: Dispatch>, +) => { + const { replace } = useFlow(); + + return useMutation({ + mutationFn: submitIdCard, + onSuccess: () => { + replace(PATH.HOME, {}); + }, + onError: () => { + alert('회원가입에 실패했어요. 다시 시도해 주세요'); + setSelectedImage(null); + }, + }); +}; diff --git a/src/widgets/join/model/hooks/index.ts b/src/widgets/join/model/hooks/index.ts new file mode 100644 index 0000000..47d62ec --- /dev/null +++ b/src/widgets/join/model/hooks/index.ts @@ -0,0 +1 @@ +export { default as useIdImage } from './useIdImage'; diff --git a/src/widgets/join/model/hooks/useIdImage.ts b/src/widgets/join/model/hooks/useIdImage.ts new file mode 100644 index 0000000..6f4510b --- /dev/null +++ b/src/widgets/join/model/hooks/useIdImage.ts @@ -0,0 +1,24 @@ +import { useState, type ChangeEvent } from 'react'; +import { useSubmitIdCard } from '@/widgets/join/api'; + +export default function useIdImage() { + const [image, setImage] = useState(null); + const { mutate: submitIdCard } = useSubmitIdCard(setImage); + + const handleImageInputChange = (e: ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setImage(reader.result as string); + }; + reader.readAsDataURL(file); + const formData = new FormData(); + formData.append('image', file); + submitIdCard(formData); + } else { + setImage(''); + } + }; + return { image, handleImageInputChange, setImage }; +} diff --git a/src/widgets/join/model/index.ts b/src/widgets/join/model/index.ts new file mode 100644 index 0000000..4cc90d0 --- /dev/null +++ b/src/widgets/join/model/index.ts @@ -0,0 +1 @@ +export * from './hooks'; diff --git a/src/widgets/join/ui/JoinContainer.tsx b/src/widgets/join/ui/JoinContainer.tsx new file mode 100644 index 0000000..c7e0d2d --- /dev/null +++ b/src/widgets/join/ui/JoinContainer.tsx @@ -0,0 +1,40 @@ +import { CameraIcon, Logo } from '@/assets/icons'; +import { useIdImage } from '../model'; + +export default function JoinContainer() { + const { handleImageInputChange, image } = useIdImage(); + + return ( +
+
+ +

농기구 임대 서비스

+

+ 주민등록증만 있으면 바로 시작할 수 있어요 +

+ +
+
+ ); +} diff --git a/src/widgets/join/ui/index.ts b/src/widgets/join/ui/index.ts new file mode 100644 index 0000000..8136080 --- /dev/null +++ b/src/widgets/join/ui/index.ts @@ -0,0 +1 @@ +export { default as JoinContainer } from './JoinContainer'; diff --git a/tsconfig.app.json b/tsconfig.app.json index 1597d8c..6840382 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -6,6 +6,10 @@ "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + }, /* Bundler mode */ "moduleResolution": "bundler", diff --git a/vite.config.ts b/vite.config.ts index 2328e17..983eb94 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,13 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; +import tailwindcss from "@tailwindcss/vite"; // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], -}) + plugins: [react(), tailwindcss()], + resolve: { + alias: { + "@": "/src", + }, + }, +});