diff --git a/favicon/favicon.ico b/favicon/favicon.ico
new file mode 100644
index 0000000..b9bcc5b
Binary files /dev/null and b/favicon/favicon.ico differ
diff --git a/index.html b/index.html
index f52af8c..11d1d16 100644
--- a/index.html
+++ b/index.html
@@ -2,7 +2,7 @@
-
diff --git a/src/app/stackflow/Stack.tsx b/src/app/stackflow/Stack.tsx
index efc1388..4e021a2 100644
--- a/src/app/stackflow/Stack.tsx
+++ b/src/app/stackflow/Stack.tsx
@@ -1,5 +1,11 @@
+import { CompleteScreen } from '@/screen/complete';
+import { FormScreen } from '@/screen/form/ui';
import { HomeScreen } from '@/screen/home/ui';
import { JoinScreen } from '@/screen/join/ui';
+import { PhotoLoadingScreen } from '@/screen/photo-loading/ui';
+import { PhotoResultScreen } from '@/screen/photo-result/ui';
+import { PhotoUploadScreen } from '@/screen/photo-upload/ui';
+import { fetchSessionData } from '@/shared/utils';
import { basicUIPlugin } from '@stackflow/plugin-basic-ui';
import { basicRendererPlugin } from '@stackflow/plugin-renderer-basic';
import { stackflow } from '@stackflow/react';
@@ -9,6 +15,11 @@ export const { Stack, useFlow } = stackflow({
activities: {
JoinScreen,
HomeScreen,
+ PhotoUploadScreen,
+ FormScreen,
+ CompleteScreen,
+ PhotoLoadingScreen,
+ PhotoResultScreen,
},
plugins: [
basicRendererPlugin(),
@@ -16,5 +27,8 @@ export const { Stack, useFlow } = stackflow({
theme: 'cupertino',
}),
],
- initialActivity: () => 'HomeScreen',
+ initialActivity: () => {
+ if (fetchSessionData('userInfo')) return 'HomeScreen';
+ return 'JoinScreen';
+ },
});
diff --git a/src/assets/icons/icon-bell.svg b/src/assets/icons/icon-bell.svg
new file mode 100644
index 0000000..8a8b46b
--- /dev/null
+++ b/src/assets/icons/icon-bell.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/icon-checked.svg b/src/assets/icons/icon-checked.svg
new file mode 100644
index 0000000..9c72c06
--- /dev/null
+++ b/src/assets/icons/icon-checked.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/icon-help.svg b/src/assets/icons/icon-help.svg
new file mode 100644
index 0000000..eb8b2b3
--- /dev/null
+++ b/src/assets/icons/icon-help.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/icon-search.svg b/src/assets/icons/icon-search.svg
new file mode 100644
index 0000000..9630155
--- /dev/null
+++ b/src/assets/icons/icon-search.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/icon-unchecked.svg b/src/assets/icons/icon-unchecked.svg
new file mode 100644
index 0000000..052f782
--- /dev/null
+++ b/src/assets/icons/icon-unchecked.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts
index a498b9c..bd52db4 100644
--- a/src/assets/icons/index.ts
+++ b/src/assets/icons/index.ts
@@ -10,6 +10,11 @@ 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';
+import BellIcon from './icon-bell.svg';
+import HelpIcon from './icon-help.svg';
+import SearchIcon from './icon-search.svg';
+import CheckIcon from './icon-checked.svg';
+import UnCheckedIcon from './icon-unchecked.svg';
export {
Logo,
@@ -24,4 +29,9 @@ export {
HomeSelectedIcon,
UserSelectedIcon,
ReservationSelectedIcon,
+ BellIcon,
+ HelpIcon,
+ SearchIcon,
+ CheckIcon,
+ UnCheckedIcon,
};
diff --git a/src/assets/images/background-loading.png b/src/assets/images/background-loading.png
new file mode 100644
index 0000000..68168e5
Binary files /dev/null and b/src/assets/images/background-loading.png differ
diff --git a/src/assets/images/call-complete.png b/src/assets/images/call-complete.png
new file mode 100644
index 0000000..097b546
Binary files /dev/null and b/src/assets/images/call-complete.png differ
diff --git a/src/assets/images/index.ts b/src/assets/images/index.ts
index 501e8ea..cbe0535 100644
--- a/src/assets/images/index.ts
+++ b/src/assets/images/index.ts
@@ -1,3 +1,17 @@
-import BackgroundImage from "./background.png";
+import BackgroundImage from './background.png';
+import LoadingBackground from './background-loading.png';
+import PhoneCompleteImage from './call-complete.png';
+import Loader1 from './loader-1.png';
+import Loader2 from './loader-2.png';
+import Loader3 from './loader-3.png';
+import Loader4 from './loader-4.png';
-export { BackgroundImage };
+export {
+ BackgroundImage,
+ LoadingBackground,
+ Loader1,
+ Loader2,
+ Loader3,
+ Loader4,
+ PhoneCompleteImage,
+};
diff --git a/src/assets/images/loader-1.png b/src/assets/images/loader-1.png
new file mode 100644
index 0000000..771f7fb
Binary files /dev/null and b/src/assets/images/loader-1.png differ
diff --git a/src/assets/images/loader-2.png b/src/assets/images/loader-2.png
new file mode 100644
index 0000000..0b08b1b
Binary files /dev/null and b/src/assets/images/loader-2.png differ
diff --git a/src/assets/images/loader-3.png b/src/assets/images/loader-3.png
new file mode 100644
index 0000000..1df1da9
Binary files /dev/null and b/src/assets/images/loader-3.png differ
diff --git a/src/assets/images/loader-4.png b/src/assets/images/loader-4.png
new file mode 100644
index 0000000..c6ab41e
Binary files /dev/null and b/src/assets/images/loader-4.png differ
diff --git a/src/screen/complete/CompleteScreen.tsx b/src/screen/complete/CompleteScreen.tsx
new file mode 100644
index 0000000..eed4188
--- /dev/null
+++ b/src/screen/complete/CompleteScreen.tsx
@@ -0,0 +1,30 @@
+import { AppScreen } from '@stackflow/plugin-basic-ui';
+import { Dock } from '@/shared/ui';
+import { PhoneCompleteImage } from '@/assets/images';
+import Button from '@/shared/ui/Button';
+import { useFlow } from '@/app/stackflow';
+
+export default function PhoneCompleteScreen() {
+ const { pop } = useFlow();
+
+ return (
+ <>
+
+
+
+

+
신청이 완료되었습니다
+
+ AI가 계약서를 자동으로 작성하였으며
+ 예약 현황에서 언제든지 확인하실 수 있습니다
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/screen/complete/index.ts b/src/screen/complete/index.ts
new file mode 100644
index 0000000..687ccfc
--- /dev/null
+++ b/src/screen/complete/index.ts
@@ -0,0 +1 @@
+export { default as CompleteScreen } from './CompleteScreen';
diff --git a/src/screen/form/ui/FormScreen.tsx b/src/screen/form/ui/FormScreen.tsx
new file mode 100644
index 0000000..26c9dab
--- /dev/null
+++ b/src/screen/form/ui/FormScreen.tsx
@@ -0,0 +1,17 @@
+import { NormalAppBar } from '@/shared/ui';
+import { FormContainer } from '@/widgets/form/ui';
+import { AppScreen } from '@stackflow/plugin-basic-ui';
+
+export default function PhotoUploadScreen() {
+ return (
+
+
+
+ );
+}
diff --git a/src/screen/form/ui/index.ts b/src/screen/form/ui/index.ts
new file mode 100644
index 0000000..46e2958
--- /dev/null
+++ b/src/screen/form/ui/index.ts
@@ -0,0 +1 @@
+export { default as FormScreen } from './FormScreen';
diff --git a/src/screen/home/ui/HomeScreen.tsx b/src/screen/home/ui/HomeScreen.tsx
index ad9c054..2ffb39f 100644
--- a/src/screen/home/ui/HomeScreen.tsx
+++ b/src/screen/home/ui/HomeScreen.tsx
@@ -1,6 +1,6 @@
import { AppScreen } from '@stackflow/plugin-basic-ui';
import { BackgroundImage } from '@/assets/images';
-import { BasicAppBar, Dock } from '@/shared/ui';
+import { HomeAppBar, Dock } from '@/shared/ui';
import { HomeContainer } from '@/widgets/home/ui';
export default function HomeScreen() {
@@ -9,7 +9,7 @@ export default function HomeScreen() {
diff --git a/src/screen/photo-loading/ui/PhotoLoadingScreen.tsx b/src/screen/photo-loading/ui/PhotoLoadingScreen.tsx
new file mode 100644
index 0000000..e801900
--- /dev/null
+++ b/src/screen/photo-loading/ui/PhotoLoadingScreen.tsx
@@ -0,0 +1,34 @@
+import { LoadingBackground } from '@/assets/images';
+import { Loader } from '@/shared/ui';
+import { useSubmitPhoto } from '@/widgets/photo-upload/api';
+import { AppScreen } from '@stackflow/plugin-basic-ui';
+import type { ActivityComponentType } from '@stackflow/react';
+import { useEffect } from 'react';
+
+const PhotoLoadingScreen: ActivityComponentType<{ data: FormData }> = ({
+ params,
+}: {
+ params: { data: FormData };
+}) => {
+ const { mutate: submitPhoto } = useSubmitPhoto();
+
+ useEffect(() => {
+ submitPhoto(params.data);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return (
+
+
+
+
+
+ AI가 사진을 분석하고 있어요...
+
+
+
+
+ );
+};
+
+export default PhotoLoadingScreen;
diff --git a/src/screen/photo-loading/ui/index.ts b/src/screen/photo-loading/ui/index.ts
new file mode 100644
index 0000000..0e4f12e
--- /dev/null
+++ b/src/screen/photo-loading/ui/index.ts
@@ -0,0 +1 @@
+export { default as PhotoLoadingScreen } from './PhotoLoadingScreen';
diff --git a/src/screen/photo-result/ui/PhotoResultScreen.tsx b/src/screen/photo-result/ui/PhotoResultScreen.tsx
new file mode 100644
index 0000000..e855f82
--- /dev/null
+++ b/src/screen/photo-result/ui/PhotoResultScreen.tsx
@@ -0,0 +1,19 @@
+import type { Tool } from '@/shared/types';
+import { NormalAppBar } from '@/shared/ui';
+import { AppScreen } from '@stackflow/plugin-basic-ui';
+import type { ActivityComponentType } from '@stackflow/react';
+import { PhotoResultContainer } from '@/widgets/photo-result/ui';
+
+const PhotoLoadingScreen: ActivityComponentType<{ result: Tool[] }> = ({
+ params,
+}: {
+ params: { result: Tool[] };
+}) => {
+ return (
+
+
+
+ );
+};
+
+export default PhotoLoadingScreen;
diff --git a/src/screen/photo-result/ui/index.ts b/src/screen/photo-result/ui/index.ts
new file mode 100644
index 0000000..11ce858
--- /dev/null
+++ b/src/screen/photo-result/ui/index.ts
@@ -0,0 +1 @@
+export { default as PhotoResultScreen } from './PhotoResultScreen';
diff --git a/src/screen/photo-upload/ui/PhotoUploadScreen.tsx b/src/screen/photo-upload/ui/PhotoUploadScreen.tsx
new file mode 100644
index 0000000..30d8cae
--- /dev/null
+++ b/src/screen/photo-upload/ui/PhotoUploadScreen.tsx
@@ -0,0 +1,11 @@
+import { NormalAppBar } from '@/shared/ui';
+import { PhotoUploadContainer } from '@/widgets/photo-upload/ui';
+import { AppScreen } from '@stackflow/plugin-basic-ui';
+
+export default function PhotoUploadScreen() {
+ return (
+
+
+
+ );
+}
diff --git a/src/screen/photo-upload/ui/index.ts b/src/screen/photo-upload/ui/index.ts
new file mode 100644
index 0000000..f16c77c
--- /dev/null
+++ b/src/screen/photo-upload/ui/index.ts
@@ -0,0 +1 @@
+export { default as PhotoUploadScreen } from './PhotoUploadScreen';
diff --git a/src/shared/api/axios.ts b/src/shared/api/axios.ts
index c6d7c96..d1827e1 100644
--- a/src/shared/api/axios.ts
+++ b/src/shared/api/axios.ts
@@ -13,7 +13,7 @@ interface GetRequestParams
{
}
const instance = axios.create({
- baseURL: 'https://usfarmtools.com/api',
+ baseURL: 'https://usfarmtools.com',
});
export async function get(
diff --git a/src/shared/api/request.ts b/src/shared/api/request.ts
index 0d9efcd..3440d87 100644
--- a/src/shared/api/request.ts
+++ b/src/shared/api/request.ts
@@ -1,3 +1,4 @@
export const REQUEST = {
- JOIN: '/ocr/idcard',
+ JOIN: '/api/ocr/idcard',
+ PHOTO_UPLOAD: '/chat/image',
};
diff --git a/src/shared/constants/path.ts b/src/shared/constants/path.ts
index f3528f4..bae17f3 100644
--- a/src/shared/constants/path.ts
+++ b/src/shared/constants/path.ts
@@ -1,4 +1,9 @@
export const PATH = {
HOME: 'HomeScreen',
JOIN: 'JoinScreen',
+ COMPLETE: 'CompleteScreen',
+ PHOTO_UPLOAD: 'PhotoUploadScreen',
+ PHOTO_LOADING: 'PhotoLoadingScreen',
+ PHOTO_RESULT: 'PhotoResultScreen',
+ FORM: 'FormScreen',
} as const;
diff --git a/src/shared/hooks/index.ts b/src/shared/hooks/index.ts
new file mode 100644
index 0000000..e9dd277
--- /dev/null
+++ b/src/shared/hooks/index.ts
@@ -0,0 +1 @@
+export { default as useImageUpload } from './useImageUpload';
diff --git a/src/widgets/join/model/hooks/useIdImage.ts b/src/shared/hooks/useImageUpload.ts
similarity index 58%
rename from src/widgets/join/model/hooks/useIdImage.ts
rename to src/shared/hooks/useImageUpload.ts
index 6f4510b..c66c84c 100644
--- a/src/widgets/join/model/hooks/useIdImage.ts
+++ b/src/shared/hooks/useImageUpload.ts
@@ -1,9 +1,9 @@
import { useState, type ChangeEvent } from 'react';
import { useSubmitIdCard } from '@/widgets/join/api';
-export default function useIdImage() {
+export default function useImageUpload(isPhotoUpload?: boolean) {
const [image, setImage] = useState(null);
- const { mutate: submitIdCard } = useSubmitIdCard(setImage);
+ const { mutate: submitIdCard, isPending } = useSubmitIdCard(setImage);
const handleImageInputChange = (e: ChangeEvent) => {
const file = e.target.files?.[0];
@@ -13,12 +13,14 @@ export default function useIdImage() {
setImage(reader.result as string);
};
reader.readAsDataURL(file);
- const formData = new FormData();
- formData.append('image', file);
- submitIdCard(formData);
+ if (!isPhotoUpload) {
+ const formData = new FormData();
+ formData.append('image', file);
+ submitIdCard(formData);
+ }
} else {
setImage('');
}
};
- return { image, handleImageInputChange, setImage };
+ return { image, handleImageInputChange, setImage, isPending };
}
diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts
index 05a1f8b..b7a5106 100644
--- a/src/shared/types/index.ts
+++ b/src/shared/types/index.ts
@@ -1,2 +1,5 @@
export * from './dock';
export * from './path';
+export * from './reservation';
+export * from './user';
+export * from './tools';
diff --git a/src/shared/types/reservation.ts b/src/shared/types/reservation.ts
new file mode 100644
index 0000000..399091a
--- /dev/null
+++ b/src/shared/types/reservation.ts
@@ -0,0 +1,7 @@
+export type Reservation = {
+ tool: string;
+ startDate: string;
+ endDate: string;
+ location: string;
+ userName: string;
+};
diff --git a/src/shared/types/tools.ts b/src/shared/types/tools.ts
new file mode 100644
index 0000000..888aec4
--- /dev/null
+++ b/src/shared/types/tools.ts
@@ -0,0 +1,8 @@
+export type Tool = {
+ id: number;
+ toolType: string;
+ quantity: number;
+ description: string;
+ image: string;
+ price: number;
+};
diff --git a/src/shared/types/user.ts b/src/shared/types/user.ts
new file mode 100644
index 0000000..3fe2e62
--- /dev/null
+++ b/src/shared/types/user.ts
@@ -0,0 +1 @@
+export type User = { jumin: string; name: string };
diff --git a/src/shared/ui/AppBar.tsx b/src/shared/ui/AppBar.tsx
index e2340b9..27c8510 100644
--- a/src/shared/ui/AppBar.tsx
+++ b/src/shared/ui/AppBar.tsx
@@ -1,9 +1,39 @@
+import { BellIcon, HelpIcon } from '@/assets/icons';
import { BackgroundImage } from '@/assets/images';
-const baseStyle = { height: '58px', border: false };
+const baseStyle = { height: '62px', border: false };
-export const BasicAppBar = {
+export const HomeAppBar = (
+ handleBellClick?: () => void,
+ handleHelpClick?: () => void,
+) => ({
...baseStyle,
backgroundImage: `url(${BackgroundImage})`,
- renderRight: () => <>>,
-};
+ renderRight: () => (
+
+
+
+
+ ),
+});
+
+export const NormalAppBar = (title: string, bgImage?: string) => ({
+ backgroundImage: `url(${bgImage})`,
+ renderLeft: () => {
+ if (title) {
+ return {title};
+ }
+ return null;
+ },
+ ...baseStyle,
+});
diff --git a/src/shared/ui/Button.tsx b/src/shared/ui/Button.tsx
new file mode 100644
index 0000000..3cfe13c
--- /dev/null
+++ b/src/shared/ui/Button.tsx
@@ -0,0 +1,47 @@
+import type { ButtonHTMLAttributes } from 'react';
+import { cva, type VariantProps } from 'class-variance-authority';
+import { cn } from '@/shared/utils';
+
+interface ButtonsProps
+ extends ButtonHTMLAttributes,
+ VariantProps {
+ children?: React.ReactNode;
+}
+
+const ButtonVariants = cva(
+ 'flex cursor-pointer items-center justify-center rounded-full py-3 font-semibold focus:outline-none',
+ {
+ variants: {
+ intent: {
+ home: 'border-m text-m hover:bg-m-hover active:bg-m-hover border-[1px]',
+ primary: 'bg-m text-white',
+ disabled: 'bg-[#D8D8D8] text-white',
+ },
+ size: {
+ md: 'w-full py-3',
+ lg: 'w-full py-5 text-xl',
+ },
+ },
+ defaultVariants: {
+ intent: 'primary',
+ size: 'md',
+ },
+ },
+);
+
+export default function Button({
+ intent,
+ size,
+ children,
+ className,
+ ...props
+}: ButtonsProps) {
+ return (
+
+ );
+}
diff --git a/src/shared/ui/Dock.tsx b/src/shared/ui/Dock.tsx
index 75af86b..581f2d2 100644
--- a/src/shared/ui/Dock.tsx
+++ b/src/shared/ui/Dock.tsx
@@ -2,7 +2,7 @@ import { useStack } from '@stackflow/react';
import { useFlow } from '@/app/stackflow';
-import { DOCK, DOCK_ITEMS } from '@/shared/constants';
+import { DOCK, DOCK_ITEMS, PATH } from '@/shared/constants';
import type { DockItem, PathItem } from '../types';
import { cn } from '../utils';
@@ -23,9 +23,11 @@ export default function Dock(isLoading: DockProps) {
.map(i => i.name)
.pop() as PathItem;
+ const render = current === PATH.HOME;
+
return (
<>
- {DOCK_ITEMS.length > 0 && (
+ {render && (
+ {images.map((src, i) => (
+
+
+
+ ))}
+
+ );
+}
diff --git a/src/shared/ui/ToolButton.tsx b/src/shared/ui/ToolButton.tsx
new file mode 100644
index 0000000..6bef11e
--- /dev/null
+++ b/src/shared/ui/ToolButton.tsx
@@ -0,0 +1,40 @@
+import { CheckIcon, UnCheckedIcon } from '@/assets/icons';
+import type { ButtonHTMLAttributes } from 'react';
+import { cn } from '../utils';
+
+interface ToolButtonProps extends ButtonHTMLAttributes
{
+ image: string;
+ toolType: string;
+ description: string;
+ selected: boolean;
+}
+
+export default function ToolButton({
+ image,
+ toolType,
+ description,
+ selected,
+ ...rest
+}: ToolButtonProps) {
+ return (
+
+ );
+}
diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts
index d026ed2..383cf2b 100644
--- a/src/shared/ui/index.ts
+++ b/src/shared/ui/index.ts
@@ -1,2 +1,5 @@
export * from './AppBar';
export { default as Dock } from './Dock';
+export { default as Button } from './Button';
+export { default as Loader } from './Loader';
+export { default as ToolButton } from './ToolButton';
diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts
index 57f9f48..2573643 100644
--- a/src/shared/utils/index.ts
+++ b/src/shared/utils/index.ts
@@ -1 +1,2 @@
export * from './string';
+export * from './session';
diff --git a/src/shared/utils/session.ts b/src/shared/utils/session.ts
new file mode 100644
index 0000000..94f1bb8
--- /dev/null
+++ b/src/shared/utils/session.ts
@@ -0,0 +1,16 @@
+export const fetchSessionData = (key: string): T | null => {
+ const stored = sessionStorage.getItem(key);
+ if (stored) {
+ const parsed = JSON.parse(stored);
+ return parsed;
+ }
+ return null;
+};
+
+export const setSessionData = (key: string, data: T) => {
+ sessionStorage.setItem(key, JSON.stringify(data));
+};
+
+export const removeSessionData = (key: string) => {
+ sessionStorage.removeItem(key);
+};
diff --git a/src/shared/utils/string.ts b/src/shared/utils/string.ts
index 33e3cfe..3b1b089 100644
--- a/src/shared/utils/string.ts
+++ b/src/shared/utils/string.ts
@@ -8,3 +8,20 @@ export function cn(...inputs: ClassValue[]) {
export function getPath(base: string, path: string) {
return `${base}/${path}`;
}
+
+export const base64ToFile = (
+ base64String: string,
+ filename: string = 'image.jpg',
+): File => {
+ const arr = base64String.split(',');
+ const mime = arr[0].match(/:(.*?);/)?.[1] || 'image/jpeg';
+ const bstr = atob(arr[1]);
+ let n = bstr.length;
+ const u8arr = new Uint8Array(n);
+
+ while (n--) {
+ u8arr[n] = bstr.charCodeAt(n);
+ }
+
+ return new File([u8arr], filename, { type: mime });
+};
diff --git a/src/widgets/form/ui/FormContainer.tsx b/src/widgets/form/ui/FormContainer.tsx
new file mode 100644
index 0000000..6c66538
--- /dev/null
+++ b/src/widgets/form/ui/FormContainer.tsx
@@ -0,0 +1,51 @@
+import { Button } from '@/shared/ui';
+import FormInput from './FormInput';
+
+export default function FormContainer() {
+ const formItems = [
+ { label: '농기계', type: 'search', onClick: () => {} },
+ { label: '대여소', type: 'text' },
+ { label: '대여일자', type: 'date' },
+ { label: '반납일자', type: 'date' },
+ ];
+
+ return (
+
+ {formItems.map(item => (
+
+ ))}
+
+
+
+
계약서는 AI가 자동으로 작성해줍니다
+
· 예약 현황에서 확인 가능
+
임대영업소 운영 시간: 평일 09:00 ~ 18:00
+
+
+
+
+
+ );
+}
+
+const FormItem = ({
+ label,
+ type,
+}: {
+ label: string;
+ type: string;
+ onClick?: () => void;
+}) => (
+
+
+ {label}
+
+ {type === 'search' ? : }
+
+);
diff --git a/src/widgets/form/ui/FormInput.tsx b/src/widgets/form/ui/FormInput.tsx
new file mode 100644
index 0000000..b27391b
--- /dev/null
+++ b/src/widgets/form/ui/FormInput.tsx
@@ -0,0 +1,37 @@
+import { SearchIcon } from '@/assets/icons';
+import type { InputHTMLAttributes } from 'react';
+
+interface FormInputProps extends InputHTMLAttributes {
+ isSearch?: boolean;
+ onClick?: () => void;
+}
+
+export default function FormInput({
+ isSearch,
+ onClick = () => {},
+ ...rest
+}: FormInputProps) {
+ const renderInput = () => {
+ if (isSearch) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ };
+ return renderInput();
+}
diff --git a/src/widgets/form/ui/index.ts b/src/widgets/form/ui/index.ts
new file mode 100644
index 0000000..b7a14c5
--- /dev/null
+++ b/src/widgets/form/ui/index.ts
@@ -0,0 +1 @@
+export { default as FormContainer } from './FormContainer';
diff --git a/src/widgets/home/ui/HomeButton.tsx b/src/widgets/home/ui/HomeButton.tsx
index e7bc4f7..5fa348c 100644
--- a/src/widgets/home/ui/HomeButton.tsx
+++ b/src/widgets/home/ui/HomeButton.tsx
@@ -1,3 +1,5 @@
+import { Button } from '@/shared/ui';
+
export const HomeButton = ({
icon,
label,
@@ -17,7 +19,7 @@ export const HomeButton = ({
{label}
{description.split('
').map((line, index) => (
-
+
{line}
))}
@@ -25,18 +27,16 @@ export const HomeButton = ({
{buttonLabel}
) : (
-
)}
);
diff --git a/src/widgets/home/ui/HomeContainer.tsx b/src/widgets/home/ui/HomeContainer.tsx
index 8875352..0cc2fb4 100644
--- a/src/widgets/home/ui/HomeContainer.tsx
+++ b/src/widgets/home/ui/HomeContainer.tsx
@@ -1,3 +1,4 @@
+import { useFlow } from '@/app/stackflow';
import { HomeButton } from './HomeButton';
import {
CameraSolidIcon,
@@ -5,8 +6,11 @@ import {
PhoneIcon,
TractorBlackIcon,
} from '@/assets/icons';
+import { PATH } from '@/shared/constants';
export default function HomeContainer() {
+ const { push } = useFlow();
+
return (
@@ -16,22 +20,31 @@ export default function HomeContainer() {
{
+ push(PATH.COMPLETE, {});
+ }}
/>
{
+ push(PATH.PHOTO_UPLOAD, {});
+ }}
/>
{
+ push(PATH.FORM, {});
+ }}
/>
diff --git a/src/widgets/join/api/join.ts b/src/widgets/join/api/join.ts
index 4e46635..4274001 100644
--- a/src/widgets/join/api/join.ts
+++ b/src/widgets/join/api/join.ts
@@ -1,6 +1,7 @@
import { useFlow } from '@/app/stackflow';
import { post, REQUEST } from '@/shared/api';
import { PATH } from '@/shared/constants';
+import { setSessionData } from '@/shared/utils';
import { useMutation } from '@tanstack/react-query';
import type { Dispatch, SetStateAction } from 'react';
@@ -19,7 +20,8 @@ export const useSubmitIdCard = (
return useMutation({
mutationFn: submitIdCard,
- onSuccess: () => {
+ onSuccess: data => {
+ setSessionData('userInfo', data);
replace(PATH.HOME, {});
},
onError: () => {
diff --git a/src/widgets/join/model/hooks/index.ts b/src/widgets/join/model/hooks/index.ts
deleted file mode 100644
index 47d62ec..0000000
--- a/src/widgets/join/model/hooks/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as useIdImage } from './useIdImage';
diff --git a/src/widgets/join/model/index.ts b/src/widgets/join/model/index.ts
deleted file mode 100644
index 4cc90d0..0000000
--- a/src/widgets/join/model/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './hooks';
diff --git a/src/widgets/join/ui/JoinContainer.tsx b/src/widgets/join/ui/JoinContainer.tsx
index c7e0d2d..1d0715e 100644
--- a/src/widgets/join/ui/JoinContainer.tsx
+++ b/src/widgets/join/ui/JoinContainer.tsx
@@ -1,8 +1,8 @@
import { CameraIcon, Logo } from '@/assets/icons';
-import { useIdImage } from '../model';
+import { useImageUpload } from '@/shared/hooks';
export default function JoinContainer() {
- const { handleImageInputChange, image } = useIdImage();
+ const { handleImageInputChange, image, isPending } = useImageUpload();
return (
@@ -34,6 +34,7 @@ export default function JoinContainer() {
)}
+ {isPending &&
사진 확인 완료! 가입중..
}
);
diff --git a/src/widgets/photo-result/ui/PhotoResultContainer.tsx b/src/widgets/photo-result/ui/PhotoResultContainer.tsx
new file mode 100644
index 0000000..5b53dc1
--- /dev/null
+++ b/src/widgets/photo-result/ui/PhotoResultContainer.tsx
@@ -0,0 +1,49 @@
+import { useFlow } from '@/app/stackflow';
+import { PATH } from '@/shared/constants';
+import type { Tool } from '@/shared/types';
+import { Button, ToolButton } from '@/shared/ui';
+import { useState } from 'react';
+
+export default function PhotoResultContainer({ tools }: { tools: Tool[] }) {
+ const { replace } = useFlow();
+ const [selected, setSelected] = useState([]);
+
+ return (
+
+
+
+ 상황에 맞는 농기계를 골라봤어요
+
+
필요한 장비를 선택해 예약을 진행해 주세요
+
+
+ {tools.map(({ id, image, toolType, description }) => (
+
+ setSelected(prev =>
+ prev.includes(id) ? prev.filter(t => t !== id) : [...prev, id],
+ )
+ }
+ />
+ ))}
+
+
+ 0 ? 'primary' : 'disabled'}
+ size="lg"
+ className="w-[310px]"
+ onClick={() => {
+ if (selected.length > 0) replace(PATH.COMPLETE, {});
+ }}
+ >
+ 예약하기
+
+
+
+ );
+}
diff --git a/src/widgets/photo-result/ui/index.ts b/src/widgets/photo-result/ui/index.ts
new file mode 100644
index 0000000..cff04f1
--- /dev/null
+++ b/src/widgets/photo-result/ui/index.ts
@@ -0,0 +1 @@
+export { default as PhotoResultContainer } from './PhotoResultContainer';
diff --git a/src/widgets/photo-upload/api/index.ts b/src/widgets/photo-upload/api/index.ts
new file mode 100644
index 0000000..6985ad4
--- /dev/null
+++ b/src/widgets/photo-upload/api/index.ts
@@ -0,0 +1 @@
+export * from './photo';
diff --git a/src/widgets/photo-upload/api/photo.ts b/src/widgets/photo-upload/api/photo.ts
new file mode 100644
index 0000000..04741a1
--- /dev/null
+++ b/src/widgets/photo-upload/api/photo.ts
@@ -0,0 +1,24 @@
+import { useFlow } from '@/app/stackflow';
+import { post, REQUEST } from '@/shared/api';
+import { PATH } from '@/shared/constants';
+import type { Tool } from '@/shared/types';
+import { useMutation } from '@tanstack/react-query';
+
+const submitPhoto = async (data: FormData) => {
+ const response = await post({
+ request: REQUEST.PHOTO_UPLOAD,
+ data: data,
+ });
+ return response.data;
+};
+
+export const useSubmitPhoto = () => {
+ const { replace } = useFlow();
+
+ return useMutation({
+ mutationFn: submitPhoto,
+ onSuccess: data => {
+ replace(PATH.PHOTO_RESULT, { result: data });
+ },
+ });
+};
diff --git a/src/widgets/photo-upload/ui/PhotoUploadContainer.tsx b/src/widgets/photo-upload/ui/PhotoUploadContainer.tsx
new file mode 100644
index 0000000..810de58
--- /dev/null
+++ b/src/widgets/photo-upload/ui/PhotoUploadContainer.tsx
@@ -0,0 +1,59 @@
+import { CameraIcon } from '@/assets/icons';
+import { useImageUpload } from '@/shared/hooks';
+import { Button } from '@/shared/ui';
+import { base64ToFile } from '@/shared/utils';
+import { useFlow } from '@/app/stackflow';
+import { PATH } from '@/shared/constants';
+
+export default function PhotoUploadContainer() {
+ const { handleImageInputChange, image } = useImageUpload(true);
+ const { replace } = useFlow();
+
+ const handleSubmitPhoto = () => {
+ if (image) {
+ const file = base64ToFile(image);
+ const formData = new FormData();
+ formData.append('image', file);
+ replace(PATH.PHOTO_LOADING, { data: formData });
+ }
+ };
+
+ return (
+
+
피해 현장 사진을 올려주세요
+
+ AI가 사진을 보고 필요한 농기계를 추천해 드립니다
+
+
+
+
+ AI가 추천해주는 농기계 보기
+
+
+
+ );
+}
diff --git a/src/widgets/photo-upload/ui/index.ts b/src/widgets/photo-upload/ui/index.ts
new file mode 100644
index 0000000..a6a9726
--- /dev/null
+++ b/src/widgets/photo-upload/ui/index.ts
@@ -0,0 +1 @@
+export { default as PhotoUploadContainer } from './PhotoUploadContainer';