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",
+ },
+ },
+});