diff --git a/apps/front/wraffle-webview/app/api/auth/[...nextauth]/route.ts b/apps/front/wraffle-webview/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 00000000..b8c0354a --- /dev/null +++ b/apps/front/wraffle-webview/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import {handlers} from '@/shared/util/auth'; + +export const {GET, POST} = handlers; diff --git a/apps/front/wraffle-webview/app/join/page.tsx b/apps/front/wraffle-webview/app/join/page.tsx index 686a1516..aa93cf10 100644 --- a/apps/front/wraffle-webview/app/join/page.tsx +++ b/apps/front/wraffle-webview/app/join/page.tsx @@ -1,11 +1,10 @@ 'use client'; import type {z} from 'zod'; -import {joinDefaultValues, joinSchema} from '@/entities/auth/join/schema'; -import type {JoinStep} from '@/entities/auth/join/type'; -import {Header, ProgressBar} from '@/shared/ui'; -import GenericForm from '@/shared/ui/form/GenericForm'; -import {Info, Name, Extra, PhoneFunnel} from '@/widgets/join'; +import {Header, ProgressBar, GenericForm} from '@/shared/ui'; +import type {JoinStep} from '@/widgets/join/config'; +import {joinDefaultValues, joinSchema} from '@/widgets/join/config'; +import {Info, Name, Extra, PhoneFunnel} from '@/widgets/join/ui'; import {zodResolver} from '@hookform/resolvers/zod'; import {useFunnel} from '@use-funnel/browser'; @@ -28,7 +27,7 @@ const Join = () => { return (
-
+
funnel.history.back()} /> diff --git a/apps/front/wraffle-webview/app/layout.tsx b/apps/front/wraffle-webview/app/layout.tsx index b87c036b..c81e95a6 100644 --- a/apps/front/wraffle-webview/app/layout.tsx +++ b/apps/front/wraffle-webview/app/layout.tsx @@ -2,6 +2,7 @@ import './globals.css'; import type {Metadata} from 'next'; import {ReactQueryProviders} from '@/app/providers'; import {pretendard} from '@/shared/util/font'; +import {Toaster} from '@wraffle/ui'; import IconLoader from '@wraffle/ui/src/ui/icon/IconLoader'; export const metadata: Metadata = { @@ -19,6 +20,7 @@ export default function RootLayout({
+ {IconLoader} {children} diff --git a/apps/front/wraffle-webview/app/login/email/page.tsx b/apps/front/wraffle-webview/app/login/email/page.tsx index eef0679f..5cd5c56e 100644 --- a/apps/front/wraffle-webview/app/login/email/page.tsx +++ b/apps/front/wraffle-webview/app/login/email/page.tsx @@ -1,28 +1,34 @@ 'use client'; +import useLoginToast from './useLoginToast'; import type {z} from 'zod'; +import {signIn} from 'next-auth/react'; import Image from 'next/image'; import Link from 'next/link'; -import {useRouter} from 'next/navigation'; -import {loginDefaultValues, loginSchema} from '@/entities/auth'; -import {Header} from '@/shared/ui'; -import GenericForm from '@/shared/ui/form/GenericForm'; -import {EmailForm} from '@/widgets/login/EmailForm'; +import {useSearchParams} from 'next/navigation'; +import {GenericForm, Header} from '@/shared/ui'; +import {loginDefaultValues, loginSchema} from '@/widgets/login/config'; +import {EmailForm} from '@/widgets/login/ui'; import {zodResolver} from '@hookform/resolvers/zod'; import {Typography} from '@wraffle/ui'; const EmailLogin = () => { - const router = useRouter(); - + const params = useSearchParams(); const onSubmit = (data: z.infer) => { - //!TODO: 로그인 로직 구현 - console.log(data.email, data.password); + signIn('credentials', { + email: data.email, + password: data.password, + redirectTo: '/', + }); }; + useLoginToast({code: params.get('code')}); return (
- + + +
logo @@ -40,7 +46,7 @@ const EmailLogin = () => {
- + 비밀번호 찾기 diff --git a/apps/front/wraffle-webview/app/login/email/useLoginToast.tsx b/apps/front/wraffle-webview/app/login/email/useLoginToast.tsx new file mode 100644 index 00000000..023f71aa --- /dev/null +++ b/apps/front/wraffle-webview/app/login/email/useLoginToast.tsx @@ -0,0 +1,46 @@ +import {useEffect} from 'react'; +import {ERROR_STATUS} from '@/shared/util'; +import {useToast} from '@wraffle/ui'; + +interface Props { + code: string | null; +} + +const useLoginToast = ({code}: Props) => { + const {toast} = useToast(); + + if (!code) return; + + useEffect(() => { + switch (code) { + case ERROR_STATUS.NON_EXISTENT_ACCOUNT.code: { + toast({ + title: ERROR_STATUS.NON_EXISTENT_ACCOUNT.message, + duration: 2000, + variant: 'warning', + icon: 'close', + }); + break; + } + case ERROR_STATUS.MISMATCHED_PASSWORD.code: { + toast({ + title: ERROR_STATUS.MISMATCHED_PASSWORD.message, + duration: 2000, + variant: 'warning', + icon: 'close', + }); + break; + } + default: { + toast({ + title: '로그인에 실패했습니다.', + duration: 2000, + variant: 'warning', + icon: 'close', + }); + } + } + }, []); +}; + +export default useLoginToast; diff --git a/apps/front/wraffle-webview/app/login/page.tsx b/apps/front/wraffle-webview/app/login/page.tsx index 003350c2..2a0c3e35 100644 --- a/apps/front/wraffle-webview/app/login/page.tsx +++ b/apps/front/wraffle-webview/app/login/page.tsx @@ -14,8 +14,10 @@ const Login = () => { priority />
- 이미 계정이 있으신가요? diff --git a/apps/front/wraffle-webview/app/page.tsx b/apps/front/wraffle-webview/app/page.tsx index f453fa8c..5da988bd 100644 --- a/apps/front/wraffle-webview/app/page.tsx +++ b/apps/front/wraffle-webview/app/page.tsx @@ -1,113 +1,33 @@ -import Image from 'next/image'; +import {categories} from '@/entities/category'; +import {RecentRaffles} from '@/features/manage-raffle/config'; +import {Banner} from '@/features/manage-raffle/ui'; +import {Header} from '@/shared/ui'; +import {CategoryButtons} from '@/widgets/category-list/ui'; +import {recommendItems} from '@/widgets/recommend-list/config'; +import {RecommendList} from '@/widgets/recommend-list/ui'; +import {BottomNavigation, Icon} from '@wraffle/ui'; export default function Home() { return ( -
-
-

- Get started by editing  - src/app/page.tsx -

- -
- -
- Next.js Logo -
- - +
+
+ + + + + + +
+
+ +
+
+ +
+
+ +
+
); } diff --git a/apps/front/wraffle-webview/app/password/find/page.tsx b/apps/front/wraffle-webview/app/password/find/page.tsx index 2be4d40a..eeb3b637 100644 --- a/apps/front/wraffle-webview/app/password/find/page.tsx +++ b/apps/front/wraffle-webview/app/password/find/page.tsx @@ -1,15 +1,14 @@ 'use client'; -import {useSendEmail} from 'features/password/api/password'; import {useRouter} from 'next/navigation'; import {useForm} from 'react-hook-form'; -import {type EmailPayload, emailObjectSchema} from '@/entities/auth'; -import {Form, RHFInput} from '@/shared/ui'; -import {Header} from '@/shared/ui'; -import BottomFixedBox from '@/shared/ui/bottom/BottomFixedBox'; +import type {EmailPayload} from '@/entities/auth'; +import {emailObjectSchema} from '@/entities/auth'; +import {useSendEmail} from '@/features/password/api'; +import {BottomFixedBox, Form, Header, RHFInput} from '@/shared/ui'; import {getDefaults} from '@/shared/util'; import {zodResolver} from '@hookform/resolvers/zod'; -import {Button, Toaster, Typography, useToast} from '@wraffle/ui'; +import {Button, Typography, useToast} from '@wraffle/ui'; const FindPasswordPage = () => { const router = useRouter(); @@ -45,7 +44,6 @@ const FindPasswordPage = () => { return (
-
비밀번호 찾기 @@ -54,7 +52,7 @@ const FindPasswordPage = () => {
- + 비밀번호 설정을 위해
가입한 이메일을 입력해주세요.
@@ -66,7 +64,7 @@ const FindPasswordPage = () => { placeholder='you@example.com' /> - + *이메일이 수신되지 않거나, 이메일주소가 기억나지 않을 경우 고객센터로 문의주시기 바랍니다. @@ -77,14 +75,12 @@ const FindPasswordPage = () => { className='flex flex-col items-center py-2' onClick={handleContactCustomerCenter} > - + 고객센터 문의
- +
diff --git a/apps/front/wraffle-webview/app/password/reset/page.tsx b/apps/front/wraffle-webview/app/password/reset/page.tsx index 3b27c32b..539ac120 100644 --- a/apps/front/wraffle-webview/app/password/reset/page.tsx +++ b/apps/front/wraffle-webview/app/password/reset/page.tsx @@ -1,15 +1,14 @@ 'use client'; -import {useResetPassword} from 'features/password/api/password'; import {useRouter, useSearchParams} from 'next/navigation'; import {useForm} from 'react-hook-form'; -import {type PasswordPayload, passwordObjectSchema} from '@/entities/auth'; -import {Form, RHFInput} from '@/shared/ui'; -import {Header} from '@/shared/ui'; -import BottomFixedBox from '@/shared/ui/bottom/BottomFixedBox'; +import type {PasswordPayload} from '@/entities/auth'; +import {passwordObjectSchema} from '@/entities/auth'; +import {useResetPassword} from '@/features/password/api'; +import {BottomFixedBox, Form, Header, RHFInput} from '@/shared/ui'; import {getDefaults} from '@/shared/util'; import {zodResolver} from '@hookform/resolvers/zod'; -import {Button, Toaster, Typography, useToast} from '@wraffle/ui'; +import {Button, Typography, useToast} from '@wraffle/ui'; const ResetPasswordPage = () => { const router = useRouter(); @@ -61,7 +60,6 @@ const ResetPasswordPage = () => { return (
-
@@ -69,7 +67,7 @@ const ResetPasswordPage = () => {
- + 새로운 비밀번호 입력 @@ -82,9 +80,7 @@ const ResetPasswordPage = () => {
- +
diff --git a/apps/front/wraffle-webview/app/password/sent/page.tsx b/apps/front/wraffle-webview/app/password/sent/page.tsx index ca56ca77..9cd44c51 100644 --- a/apps/front/wraffle-webview/app/password/sent/page.tsx +++ b/apps/front/wraffle-webview/app/password/sent/page.tsx @@ -1,8 +1,7 @@ 'use client'; import {useRouter} from 'next/navigation'; -import {Header} from '@/shared/ui'; -import BottomFixedBox from '@/shared/ui/bottom/BottomFixedBox'; +import {BottomFixedBox, Header} from '@/shared/ui'; import {Button, Typography} from '@wraffle/ui'; const SendEmailSuccessPage = () => { @@ -24,12 +23,12 @@ const SendEmailSuccessPage = () => {
- + 비밀번호 재설정
이메일을 보냈습니다.
- + 받은편지함을 확인해 주세요. 이메일이 오지 않으면, 스팸 메일함을 확인해 주시거나 아래 버튼을 눌러 이메일을 다시 보내주세요. diff --git a/apps/front/wraffle-webview/app/products/[productId]/page.tsx b/apps/front/wraffle-webview/app/products/[productId]/page.tsx new file mode 100644 index 00000000..b3dc7929 --- /dev/null +++ b/apps/front/wraffle-webview/app/products/[productId]/page.tsx @@ -0,0 +1,148 @@ +'use client'; + +import {useRouter, useSearchParams} from 'next/navigation'; +import {useEffect, useState, useRef} from 'react'; +import {sampleRaffleData, sampleEventData} from '@/entities/product/product'; +import type {RaffleData, EventData} from '@/entities/product/product'; +import ParticipateButton from '@/features/participate/ui/ParticipateButton'; +import ShareDialog from '@/features/share-product-link/ShareDialog'; +import {Header, Divider} from '@/shared/ui'; +import {formatDate} from '@/shared/util/formatDate'; +import { + ProductInfoMenu, + ProductMainSection, + ProductInfoSection, +} from '@/widgets/product-info'; +import {RAFFLE_MENUS, EVENT_MENUS} from '@/widgets/product-info/config/const'; +import { + type RaffleMenu, + type EventMenu, +} from '@/widgets/product-info/config/const'; +import {useMenu} from '@/widgets/product-info/hook/useMenu'; +import {ProductEventSection} from '@/widgets/product-info/ui/ProductInfoSection'; + +const HEADER_OFFSET = 115; + +const ProductPage = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + const type = searchParams.get('type'); + const {selectedMenu, selectMenu} = useMenu('상품' as RaffleMenu | EventMenu); + const [productData, setProductData] = useState( + null, + ); + + const sectionsRef = useRef<{ + [key: string]: React.RefObject; + }>({ + 상품: useRef(null), + '응모 기간': useRef(null), + '당첨자 발표': useRef(null), + '추첨 상품': useRef(null), + 유의사항: useRef(null), + }); + + const menus = type === 'event' ? [...EVENT_MENUS] : [...RAFFLE_MENUS]; + + const data: { + raffle: RaffleData; + event: EventData; + } = { + raffle: sampleRaffleData, + event: sampleEventData, + }; + + useEffect(() => { + if (type === 'raffle' || type === 'event') { + setProductData(data[type]); + } else { + router.push('/404'); + } + }, [type]); + + // 메뉴 선택 시 스크롤 이동 함수 + const scrollToSection = (menu: RaffleMenu | EventMenu) => { + const section = sectionsRef.current[menu]; + if (section && section.current) { + window.scrollTo({ + top: section.current.offsetTop - HEADER_OFFSET, + behavior: 'smooth', + }); + } + }; + + // 메뉴 클릭 시 메뉴를 선택하고 해당 섹션으로 스크롤 이동 + const handleMenuSelect = (menu: RaffleMenu | EventMenu) => { + selectMenu(menu); + scrollToSection(menu); + }; + + if (!productData) { + return
Loading...
; + } + + return ( +
+
+
+ + + + + + +
+ +
+ +
+ + + + + + + {type === 'event' && ( + <> + + + + )} + +
+ +
+ +
+
+ ); +}; + +export default ProductPage; diff --git a/apps/front/wraffle-webview/app/products/create/event/page.tsx b/apps/front/wraffle-webview/app/products/create/event/page.tsx index 851ae2b1..64a3a465 100644 --- a/apps/front/wraffle-webview/app/products/create/event/page.tsx +++ b/apps/front/wraffle-webview/app/products/create/event/page.tsx @@ -5,15 +5,14 @@ import { createEventDefaultValues, createEventSchema, } from '@/entities/product/model'; -import {Header, ProgressBar} from '@/shared/ui'; -import GenericForm from '@/shared/ui/form/GenericForm'; +import {GenericForm, Header, ProgressBar} from '@/shared/ui'; import { DateStep, - ImageStep, EtcStep, - TitleStep, + ImageStep, ProductList, SuccessList, + TitleStep, } from '@/widgets/product-list/create/ui'; import {zodResolver} from '@hookform/resolvers/zod'; import {createFunnelSteps, useFunnel} from '@use-funnel/browser'; diff --git a/apps/front/wraffle-webview/app/products/create/raffle/page.tsx b/apps/front/wraffle-webview/app/products/create/raffle/page.tsx index 6dde3a1e..c48be9de 100644 --- a/apps/front/wraffle-webview/app/products/create/raffle/page.tsx +++ b/apps/front/wraffle-webview/app/products/create/raffle/page.tsx @@ -1,12 +1,11 @@ 'use client'; -import type {RaffleCreateState} from '@/entities/product/model'; import { createRaffleDefaultValues, createRaffleSchema, + type RaffleCreateState, } from '@/entities/product/model'; -import {Header, ProgressBar} from '@/shared/ui'; -import GenericForm from '@/shared/ui/form/GenericForm'; +import {GenericForm, Header, ProgressBar} from '@/shared/ui'; import { DateStep, EtcStep, diff --git a/apps/front/wraffle-webview/app/search/page.tsx b/apps/front/wraffle-webview/app/search/page.tsx new file mode 100644 index 00000000..12745ea5 --- /dev/null +++ b/apps/front/wraffle-webview/app/search/page.tsx @@ -0,0 +1,104 @@ +'use client'; + +import {useRouter} from 'next/navigation'; +import {useState} from 'react'; +import {Header} from '@/shared/ui'; +import {BottomNavigation, Typography, Icon} from '@wraffle/ui'; + +const SearchPage = () => { + const router = useRouter(); + const [keywords, setKeywords] = useState([ + '당일 마감', + 'Vans', + '티셔츠', + 'Stussy', + 'Carhartt', + '행사', + ]); + const [isFocused, setIsFocused] = useState(false); + + return ( +
+
+
+ +
+ +
+
+ +
+ +
+
+
+
+ +
+
+ setIsFocused(true)} + onBlur={() => setIsFocused(false)} + /> + +
+ +
+
+ + 추천 검색어 + + {keywords.length > 0 ? ( +
+ {keywords.map((keyword, index) => ( +
+ # {keyword} +
+ ))} +
+ ) : ( + + 아직 추천 검색어가 없어요. + + )} +
+ +
+
+ + 가장 많이 검색하고 있어요! + +
    + {['당일 마감', 'Vans', 'A/S', '아무거나', '행사'].map( + (item, index) => ( +
  • + + {index + 1} + {' '} + + {item} + {' '} +
  • + ), + )} +
+
+
+
+ +
+ ); +}; + +export default SearchPage; diff --git a/apps/front/wraffle-webview/app/search/results/page.tsx b/apps/front/wraffle-webview/app/search/results/page.tsx new file mode 100644 index 00000000..ab11c75a --- /dev/null +++ b/apps/front/wraffle-webview/app/search/results/page.tsx @@ -0,0 +1,93 @@ +'use client'; + +import {useRouter} from 'next/navigation'; +import {Header} from '@/shared/ui'; +import {Typography} from '@wraffle/ui'; +import {RaffleCard} from '@wraffle/ui'; +import {Icon} from '@wraffle/ui'; + +const SearchResultsPage = () => { + const router = useRouter(); + + return ( +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ + '당일 마감' + + + 검색 결과 + +
+ +
+ + + +
+
+ ); +}; + +export default SearchResultsPage; diff --git a/apps/front/wraffle-webview/middleware.ts b/apps/front/wraffle-webview/middleware.ts new file mode 100644 index 00000000..803630cc --- /dev/null +++ b/apps/front/wraffle-webview/middleware.ts @@ -0,0 +1,32 @@ +import type {NextRequest} from 'next/server'; +import {NextResponse} from 'next/server'; +import {auth} from '@/shared/util/auth'; + +const PUBLIC_ROUTES = ['/login', '/join']; +const LOGIN = '/login'; +const MAIN = '/'; + +export async function middleware(req: NextRequest) { + const {nextUrl} = req; + const session = await auth(); + + const isAuthenticated = !!session; + + const isPublicRoute = PUBLIC_ROUTES.find(route => + nextUrl.pathname.startsWith(route), + ); + + if (isAuthenticated && isPublicRoute) { + return NextResponse.redirect(new URL(MAIN, req.url)); + } + + if (!isAuthenticated && !isPublicRoute) { + return NextResponse.redirect(new URL(LOGIN, nextUrl)); + } + + return NextResponse.next(); +} + +export const config = { + matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'], +}; diff --git a/apps/front/wraffle-webview/next.config.mjs b/apps/front/wraffle-webview/next.config.mjs index 9510663a..5b52b804 100644 --- a/apps/front/wraffle-webview/next.config.mjs +++ b/apps/front/wraffle-webview/next.config.mjs @@ -16,11 +16,21 @@ const nextConfig = { '../../../packages/ui/src/widgets', ), }; + return config; }, experimental: { optimizePackageImports: ['@wraffle/ui'], }, + // !TODO: API 연동 후 삭제 + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'image.vans.co.kr', + }, + ], + }, }; export default nextConfig; diff --git a/apps/front/wraffle-webview/package.json b/apps/front/wraffle-webview/package.json index 8d5b041c..4c1a07d7 100644 --- a/apps/front/wraffle-webview/package.json +++ b/apps/front/wraffle-webview/package.json @@ -18,10 +18,14 @@ "@use-funnel/browser": "^0.0.7", "@wraffle/ui": "workspace:*", "clsx": "^2.1.1", + "dayjs": "^1.11.13", "next": "14.2.5", + "next-auth": "5.0.0-beta.25", "react": "^18", "react-dom": "^18", "react-hook-form": "^7.53.0", + "swiper": "^11.1.14", + "tailwind-scrollbar-hide": "^1.1.7", "zod": "^3.23.8" }, "devDependencies": { diff --git a/apps/front/wraffle-webview/src/entities/auth/auth.d.ts b/apps/front/wraffle-webview/src/entities/auth/auth.d.ts new file mode 100644 index 00000000..ca905e3c --- /dev/null +++ b/apps/front/wraffle-webview/src/entities/auth/auth.d.ts @@ -0,0 +1,17 @@ +export declare module 'next-auth' { + interface User { + accessToken: string; + refreshToken: string; + } + interface Session { + accessToken: string; + refreshToken: string; + } +} +export declare module '@auth/core/jwt' { + interface JWT { + accessToken: string; + refreshToken: string; + accessTokenExpires: number; + } +} diff --git a/apps/front/wraffle-webview/src/entities/auth/index.ts b/apps/front/wraffle-webview/src/entities/auth/index.ts index 6cc1e6e2..e27a6e2f 100644 --- a/apps/front/wraffle-webview/src/entities/auth/index.ts +++ b/apps/front/wraffle-webview/src/entities/auth/index.ts @@ -1 +1 @@ -export * from './login'; +export * from './schema'; diff --git a/apps/front/wraffle-webview/src/entities/auth/join/index.ts b/apps/front/wraffle-webview/src/entities/auth/join/index.ts deleted file mode 100644 index df92df92..00000000 --- a/apps/front/wraffle-webview/src/entities/auth/join/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './constant'; -export * from './schema'; -export * from './type'; diff --git a/apps/front/wraffle-webview/src/entities/auth/login/schema.ts b/apps/front/wraffle-webview/src/entities/auth/login/schema.ts deleted file mode 100644 index 7db81655..00000000 --- a/apps/front/wraffle-webview/src/entities/auth/login/schema.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {z} from 'zod'; -import {getDefaults} from '@/shared/util'; - -const passwordSchema = z - .string() - .min(1, {message: '비밀번호를 입력해 주세요.'}) - .refine( - value => - /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[~@#$!%?&])[a-zA-Z\d~@#$!%*?&]{8,}$/.test( - value, - ), - { - message: - '비밀번호는 영문, 숫자, 특수문자를 조합하여 8자 이상이어야 합니다.', - }, - ) - .default(''); - -const emailSchema = z - .string() - .min(1, {message: '이메일을 입력해 주세요.'}) - .email({message: '유효하지 않은 이메일 형식입니다.'}) - .default(''); - -export const loginSchema = z.object({ - email: emailSchema, - password: passwordSchema, -}); - -export type LoginPayload = z.infer; - -export const loginDefaultValues = getDefaults(loginSchema); - -export const passwordObjectSchema = z.object({password: passwordSchema}); -export const emailObjectSchema = z.object({email: emailSchema}); - -export type PasswordPayload = z.infer; -export type EmailPayload = z.infer; diff --git a/apps/front/wraffle-webview/src/entities/auth/schema.ts b/apps/front/wraffle-webview/src/entities/auth/schema.ts new file mode 100644 index 00000000..b70b3691 --- /dev/null +++ b/apps/front/wraffle-webview/src/entities/auth/schema.ts @@ -0,0 +1,25 @@ +import {z} from 'zod'; + +export const passwordRegex = new RegExp( + /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[~@#$!%*?&])[a-zA-Z\d~@#$!%*?&]{8,}$/, +); + +export const emailSchema = z + .string() + .min(1, {message: '이메일을 입력해 주세요.'}) + .email({message: '유효하지 않은 이메일 형식입니다.'}) + .default(''); + +export const passwordSchema = z + .string() + .regex( + passwordRegex, + '비밀번호는 영문, 숫자, 특수문자 조합으로 8자 이상 형식입니다.', + ) + .default(''); + +export const passwordObjectSchema = z.object({password: passwordSchema}); +export const emailObjectSchema = z.object({email: emailSchema}); + +export type PasswordPayload = z.infer; +export type EmailPayload = z.infer; diff --git a/apps/front/wraffle-webview/src/entities/category/const.ts b/apps/front/wraffle-webview/src/entities/category/const.ts new file mode 100644 index 00000000..3312e80e --- /dev/null +++ b/apps/front/wraffle-webview/src/entities/category/const.ts @@ -0,0 +1,76 @@ +import type {CategoryItem} from './type'; + +export const categories: CategoryItem[] = [ + { + id: 1, + name: '패션', + parentId: null, + depth: 1, + }, + { + id: 2, + name: '가전 디지털', + parentId: null, + depth: 1, + }, + { + id: 3, + name: '전자제품', + parentId: null, + depth: 1, + }, + { + id: 4, + name: '식품', + parentId: null, + depth: 1, + }, + { + id: 5, + name: '의류', + parentId: null, + depth: 1, + }, + { + id: 6, + name: '생활', + parentId: null, + depth: 1, + }, + { + id: 11, + name: '패션', + parentId: null, + depth: 1, + }, + { + id: 12, + name: '가전 디지털', + parentId: null, + depth: 1, + }, + { + id: 13, + name: '전자 제품', + parentId: null, + depth: 1, + }, + { + id: 14, + name: '식품', + parentId: null, + depth: 1, + }, + { + id: 15, + name: '의류', + parentId: null, + depth: 1, + }, + { + id: 16, + name: '생활', + parentId: null, + depth: 1, + }, +]; diff --git a/apps/front/wraffle-webview/src/entities/category/index.ts b/apps/front/wraffle-webview/src/entities/category/index.ts new file mode 100644 index 00000000..9711ec4a --- /dev/null +++ b/apps/front/wraffle-webview/src/entities/category/index.ts @@ -0,0 +1,2 @@ +export * from './const'; +export * from './type'; diff --git a/apps/front/wraffle-webview/src/entities/category/type.ts b/apps/front/wraffle-webview/src/entities/category/type.ts new file mode 100644 index 00000000..d53573d2 --- /dev/null +++ b/apps/front/wraffle-webview/src/entities/category/type.ts @@ -0,0 +1,6 @@ +export interface CategoryItem { + id: number; + name: string; + parentId: number | null; + depth: number; +} diff --git a/apps/front/wraffle-webview/src/entities/product/product.ts b/apps/front/wraffle-webview/src/entities/product/product.ts new file mode 100644 index 00000000..f98209cf --- /dev/null +++ b/apps/front/wraffle-webview/src/entities/product/product.ts @@ -0,0 +1,127 @@ +export interface Tag { + id: string; + name: string; +} + +export interface Product { + id: number; + name: string; + imageUrl: string; +} + +export interface BaseProductData { + id: number; + title: string; + price: number; + startDate: string; + endDate: string; + announceAt: string; + description: string; + etc: string; + clipCount: number; + status: string; + applyCount: number; + isApplied: boolean; + createUserId: number; + tags: Tag[]; + images: string[]; +} + +export interface RaffleData extends BaseProductData {} + +export interface EventData extends BaseProductData { + products: Product[]; +} + +export const sampleRaffleData: RaffleData = { + id: 1, + title: '[Vans] 올드스쿨', + price: 78000, + startDate: '2024-07-31T00:00:00.000Z', + endDate: '2024-08-02T00:00:00.000Z', + announceAt: '2024-08-03T00:00:00.000Z', + description: + '제작 박스로 준비해드립니다. 오후 3시 이전 결제 완료 시 택배 출고 드립니다. 당일 상품 출고 마감 시간 3시입니다.', + etc: '유의사항', + clipCount: 53, + status: 'before', + applyCount: 0, + isApplied: false, + createUserId: 1, + tags: [ + {id: '1', name: 'Vans'}, + {id: '2', name: '래플'}, + ], + images: [ + 'https://github.com/user-attachments/assets/73684618-8305-4a78-bcd6-e36342b46c22', + 'https://github.com/user-attachments/assets/4a104905-0106-4b8a-8dcd-06926162e2e6', + ], +}; + +export const sampleEventData: EventData = { + id: 1, + title: '이벤트 제목', + price: 10000, + startDate: '2021-08-05T00:00:00.000Z', + endDate: '2021-08-05T00:00:00.000Z', + announceAt: '2021-08-05T00:00:00.000Z', + description: '어쩌고 설명 저쩌고 설명', + etc: '이벤트 유의사항', + clipCount: 10, + applyCount: 10, + status: 'waiting', + isApplied: false, + createUserId: 1, + tags: [ + {id: '1', name: 'Vans'}, + {id: '2', name: '래플'}, + ], + images: [ + 'https://github.com/user-attachments/assets/73684618-8305-4a78-bcd6-e36342b46c22', + 'https://github.com/user-attachments/assets/4a104905-0106-4b8a-8dcd-06926162e2e6', + ], + products: [ + { + id: 1, + name: '1번 상품', + imageUrl: + 'https://github.com/user-attachments/assets/4a104905-0106-4b8a-8dcd-06926162e2e6', + }, + { + id: 2, + name: '2번 상품', + imageUrl: + 'https://github.com/user-attachments/assets/4a104905-0106-4b8a-8dcd-06926162e2e6', + }, + { + id: 3, + name: '3번 상품', + imageUrl: + 'https://github.com/user-attachments/assets/4a104905-0106-4b8a-8dcd-06926162e2e6', + }, + { + id: 4, + name: '4번 상품', + imageUrl: + 'https://github.com/user-attachments/assets/4a104905-0106-4b8a-8dcd-06926162e2e6', + }, + { + id: 5, + name: '5번 상품', + imageUrl: + 'https://github.com/user-attachments/assets/4a104905-0106-4b8a-8dcd-06926162e2e6', + }, + { + id: 6, + name: '6번 상품', + imageUrl: + 'https://github.com/user-attachments/assets/4a104905-0106-4b8a-8dcd-06926162e2e6', + }, + { + id: 7, + name: '7번 상품', + imageUrl: + 'https://github.com/user-attachments/assets/4a104905-0106-4b8a-8dcd-06926162e2e6', + }, + ], +}; diff --git a/apps/front/wraffle-webview/src/entities/raffle/const.ts b/apps/front/wraffle-webview/src/entities/raffle/const.ts new file mode 100644 index 00000000..c73b3dcc --- /dev/null +++ b/apps/front/wraffle-webview/src/entities/raffle/const.ts @@ -0,0 +1 @@ +export const RAFFLE_TYPE = ['RAFFLE', 'EVENT'] as const; diff --git a/apps/front/wraffle-webview/src/entities/raffle/index.ts b/apps/front/wraffle-webview/src/entities/raffle/index.ts new file mode 100644 index 00000000..9711ec4a --- /dev/null +++ b/apps/front/wraffle-webview/src/entities/raffle/index.ts @@ -0,0 +1,2 @@ +export * from './const'; +export * from './type'; diff --git a/apps/front/wraffle-webview/src/entities/raffle/type.ts b/apps/front/wraffle-webview/src/entities/raffle/type.ts new file mode 100644 index 00000000..6969fd45 --- /dev/null +++ b/apps/front/wraffle-webview/src/entities/raffle/type.ts @@ -0,0 +1,13 @@ +import type {RAFFLE_TYPE} from './const'; +import type {TagType} from '@wraffle/ui'; + +export interface Raffle { + id: number; + title: string; + price: number; + thumbnail: string; + clipCount: number; + hashtags: TagType[]; +} + +export type RaffleType = (typeof RAFFLE_TYPE)[number]; diff --git a/apps/front/wraffle-webview/src/features/get-category/ui/CategoryButton.tsx b/apps/front/wraffle-webview/src/features/get-category/ui/CategoryButton.tsx new file mode 100644 index 00000000..1e348bd6 --- /dev/null +++ b/apps/front/wraffle-webview/src/features/get-category/ui/CategoryButton.tsx @@ -0,0 +1,25 @@ +'use client'; + +import {useRouter} from 'next/navigation'; +import type {CategoryItem} from '@/entities/category'; +import {Typography} from '@wraffle/ui'; + +interface CategoryButtonProps { + category: CategoryItem; +} + +const CategoryButton = ({category}: CategoryButtonProps) => { + const router = useRouter(); + return ( + + ); +}; + +export {CategoryButton}; diff --git a/apps/front/wraffle-webview/src/features/get-category/ui/index.ts b/apps/front/wraffle-webview/src/features/get-category/ui/index.ts new file mode 100644 index 00000000..c714862d --- /dev/null +++ b/apps/front/wraffle-webview/src/features/get-category/ui/index.ts @@ -0,0 +1 @@ +export * from './CategoryButton'; diff --git a/apps/front/wraffle-webview/src/features/manage-raffle/config/const.ts b/apps/front/wraffle-webview/src/features/manage-raffle/config/const.ts new file mode 100644 index 00000000..f01cc966 --- /dev/null +++ b/apps/front/wraffle-webview/src/features/manage-raffle/config/const.ts @@ -0,0 +1,10 @@ +import type {RecentRaffle} from '../model'; + +export const RecentRaffles: RecentRaffle = { + id: 1, + title: '이번주 최신 래플 ❤️', + subTitle: '🎄기간한정🎄', + imageUrl: + 'https://image.vans.co.kr/cmsstatic/product/VN000D3HY281_VN000D3HY281_primary.jpg', + toGoUrl: '/', +}; diff --git a/apps/front/wraffle-webview/src/features/manage-raffle/config/index.ts b/apps/front/wraffle-webview/src/features/manage-raffle/config/index.ts new file mode 100644 index 00000000..e47ea3a4 --- /dev/null +++ b/apps/front/wraffle-webview/src/features/manage-raffle/config/index.ts @@ -0,0 +1 @@ +export * from './const'; diff --git a/apps/front/wraffle-webview/src/features/manage-raffle/model/index.ts b/apps/front/wraffle-webview/src/features/manage-raffle/model/index.ts new file mode 100644 index 00000000..4e4aa420 --- /dev/null +++ b/apps/front/wraffle-webview/src/features/manage-raffle/model/index.ts @@ -0,0 +1,2 @@ +export * from './recent'; +export * from './recommend'; diff --git a/apps/front/wraffle-webview/src/features/manage-raffle/model/recent.ts b/apps/front/wraffle-webview/src/features/manage-raffle/model/recent.ts new file mode 100644 index 00000000..9236fb6d --- /dev/null +++ b/apps/front/wraffle-webview/src/features/manage-raffle/model/recent.ts @@ -0,0 +1,7 @@ +export interface RecentRaffle { + id: number; + title: string; + subTitle: string; + imageUrl: string; + toGoUrl: string; +} diff --git a/apps/front/wraffle-webview/src/features/manage-raffle/model/recommend.ts b/apps/front/wraffle-webview/src/features/manage-raffle/model/recommend.ts new file mode 100644 index 00000000..a5c351e3 --- /dev/null +++ b/apps/front/wraffle-webview/src/features/manage-raffle/model/recommend.ts @@ -0,0 +1,6 @@ +import type {Raffle, RaffleType} from '@/entities/raffle'; + +export interface RecommendRaffle extends Raffle { + type: RaffleType; + isBookmarked: boolean; +} diff --git a/apps/front/wraffle-webview/src/features/manage-raffle/ui/Banner.tsx b/apps/front/wraffle-webview/src/features/manage-raffle/ui/Banner.tsx new file mode 100644 index 00000000..0319ae79 --- /dev/null +++ b/apps/front/wraffle-webview/src/features/manage-raffle/ui/Banner.tsx @@ -0,0 +1,33 @@ +'use client'; + +import type {RecentRaffle} from '../model'; +import Image from 'next/image'; +import {useRouter} from 'next/navigation'; +import {Typography} from '@wraffle/ui'; + +interface BannerProps { + recentRaffle: RecentRaffle; +} + +const Banner = ({recentRaffle}: BannerProps) => { + const {id, title, subTitle, imageUrl, toGoUrl} = recentRaffle; + const router = useRouter(); + return ( +
router.push(toGoUrl)} + > + {`${id} +
+ + {title} + + + {subTitle} + +
+
+ ); +}; + +export {Banner}; diff --git a/apps/front/wraffle-webview/src/features/manage-raffle/ui/index.ts b/apps/front/wraffle-webview/src/features/manage-raffle/ui/index.ts new file mode 100644 index 00000000..bc95f09d --- /dev/null +++ b/apps/front/wraffle-webview/src/features/manage-raffle/ui/index.ts @@ -0,0 +1 @@ +export * from './Banner'; diff --git a/apps/front/wraffle-webview/src/features/participate/ui/ParticipateButton.tsx b/apps/front/wraffle-webview/src/features/participate/ui/ParticipateButton.tsx new file mode 100644 index 00000000..e2f8ea81 --- /dev/null +++ b/apps/front/wraffle-webview/src/features/participate/ui/ParticipateButton.tsx @@ -0,0 +1,56 @@ +'use client'; + +import ParticipateDialog from './ParticipateDialog'; +import React, {useState} from 'react'; +import {Button, Icon} from '@wraffle/ui'; + +interface ParticipateButtonProps { + status: string; + clipCount: number; + isApplied: boolean; + productImage: string; +} + +const ParticipateButton = ({ + status, + clipCount, + isApplied: initialApplyStatus, + productImage, +}: ParticipateButtonProps) => { + const [isBookmarked, setIsBookmarked] = useState(false); + const [isApplied, setIsApplied] = useState(initialApplyStatus); + + const handleBookmark = () => { + setIsBookmarked(prev => !prev); + }; + + const handleApply = () => { + setIsApplied(true); + }; + + if (status === 'after') { + return ( +
+ +
+ ); + } + + return ( +
+ + +
+ ); +}; + +export default ParticipateButton; diff --git a/apps/front/wraffle-webview/src/features/participate/ui/ParticipateDialog.tsx b/apps/front/wraffle-webview/src/features/participate/ui/ParticipateDialog.tsx new file mode 100644 index 00000000..53b9bb6c --- /dev/null +++ b/apps/front/wraffle-webview/src/features/participate/ui/ParticipateDialog.tsx @@ -0,0 +1,61 @@ +'use client'; + +import Image from 'next/image'; +import React from 'react'; +import { + Button, + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@wraffle/ui'; + +interface DialogComponentProps { + isApplied: boolean; + handleApply: () => void; + productImage: string; +} + +const ParticipateDialog = ({ + isApplied, + handleApply, + productImage, +}: DialogComponentProps) => { + return ( + + + + + + + 참여가 완료되었습니다 + + 응모 후 당첨 시에만 결제를 진행해요! + +
+ Product Thumbnail +
+
+ + + + + +
+
+ ); +}; + +export default ParticipateDialog; diff --git a/apps/front/wraffle-webview/src/features/password/api/index.ts b/apps/front/wraffle-webview/src/features/password/api/index.ts new file mode 100644 index 00000000..47b7e7a2 --- /dev/null +++ b/apps/front/wraffle-webview/src/features/password/api/index.ts @@ -0,0 +1 @@ +export * from './password'; diff --git a/apps/front/wraffle-webview/features/password/api/password.ts b/apps/front/wraffle-webview/src/features/password/api/password.ts similarity index 100% rename from apps/front/wraffle-webview/features/password/api/password.ts rename to apps/front/wraffle-webview/src/features/password/api/password.ts diff --git a/apps/front/wraffle-webview/src/features/share-product-link/ShareDialog.tsx b/apps/front/wraffle-webview/src/features/share-product-link/ShareDialog.tsx new file mode 100644 index 00000000..a7370e0e --- /dev/null +++ b/apps/front/wraffle-webview/src/features/share-product-link/ShareDialog.tsx @@ -0,0 +1,61 @@ +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogTrigger, + Icon, +} from '@wraffle/ui'; + +const ShareDialog = () => { + return ( + + + + + + + 공유하기 + 래플을 친구에게 공유해보세요! +
+ + + +
+
+
+
+ ); +}; + +export default ShareDialog; diff --git a/apps/front/wraffle-webview/src/features/validate-phone/config/const.ts b/apps/front/wraffle-webview/src/features/validate-phone/config/const.ts new file mode 100644 index 00000000..7a61dd5a --- /dev/null +++ b/apps/front/wraffle-webview/src/features/validate-phone/config/const.ts @@ -0,0 +1,3 @@ +export const DEFAULT_ERROR_MESSAGE = ''; +export const MISMATCHED_CODE_ERROR_MESSAGE = '인증번호가 일치하지 않습니다.'; +export const VERIFY_CODE_LENGTH = 4; diff --git a/apps/front/wraffle-webview/src/features/validate-phone/config/index.ts b/apps/front/wraffle-webview/src/features/validate-phone/config/index.ts new file mode 100644 index 00000000..e47ea3a4 --- /dev/null +++ b/apps/front/wraffle-webview/src/features/validate-phone/config/index.ts @@ -0,0 +1 @@ +export * from './const'; diff --git a/apps/front/wraffle-webview/src/features/validate-phone/ui/RequestCode.tsx b/apps/front/wraffle-webview/src/features/validate-phone/ui/RequestCode.tsx new file mode 100644 index 00000000..e66c8d99 --- /dev/null +++ b/apps/front/wraffle-webview/src/features/validate-phone/ui/RequestCode.tsx @@ -0,0 +1,93 @@ +import type {Dispatch, SetStateAction} from 'react'; +import {useEffect, useState} from 'react'; +import type {FieldError} from 'react-hook-form'; +import {useAutoFocus, useInput} from '@/shared/hook'; +import {handleMaxLength} from '@/shared/util'; +import {InputField, Select} from '@wraffle/ui'; + +const MAX_INPUT_LENGTH = 4; + +interface RequestCodeProps { + onChangePhoneNumber: (phone: string) => void; + onChangeIsVerified: Dispatch>; + error?: FieldError; +} + +const RequestCode = ({ + onChangePhoneNumber, + onChangeIsVerified, + error, +}: RequestCodeProps) => { + const [middleInputRef, handleMiddleKeyUp] = useAutoFocus(MAX_INPUT_LENGTH); + const [lastInputRef, handleLastKeyUp] = useAutoFocus(MAX_INPUT_LENGTH); + + const [first, setFirst] = useState(''); + const [middle, handleMiddle] = useInput(''); + const [last, handleLast] = useInput(''); + const [errorMessage, setErrorMessage] = useState(''); + + const isValid = + first && + (middle.length === MAX_INPUT_LENGTH || + middle.length === MAX_INPUT_LENGTH - 1) && + last.length === MAX_INPUT_LENGTH; + + useEffect(() => { + onChangeIsVerified(false); + setErrorMessage(''); + if (isValid) { + const phoneNumber = first.concat(middle, last); + onChangePhoneNumber(phoneNumber); + // !TODO: request API + // 성공 시 onChangeIsVerified(true) + onChangeIsVerified(true); + // 실패 시 setErrorMessage에 에러메세지 + // setErrorMessage('이미 가입된 번호입니다.'); + // onChangeIsVerified(false); + } + }, [first, middle, last]); + + return ( + + 휴대폰 번호* + + {description && {description}} diff --git a/apps/front/wraffle-webview/src/shared/ui/form/index.ts b/apps/front/wraffle-webview/src/shared/ui/form/index.ts index 3cf925c2..c110bb7e 100644 --- a/apps/front/wraffle-webview/src/shared/ui/form/index.ts +++ b/apps/front/wraffle-webview/src/shared/ui/form/index.ts @@ -1,2 +1,3 @@ export * from './RHF/RHFInput'; export * from './RHF/core/Form'; +export * from './GenericForm'; diff --git a/apps/front/wraffle-webview/src/shared/ui/header/core/Header.tsx b/apps/front/wraffle-webview/src/shared/ui/header/core/Header.tsx new file mode 100644 index 00000000..02358105 --- /dev/null +++ b/apps/front/wraffle-webview/src/shared/ui/header/core/Header.tsx @@ -0,0 +1,77 @@ +import Image from 'next/image'; +import {type ReactNode} from 'react'; +import {Icon} from '@wraffle/ui/src/ui/icon/Icon'; +import {Typography} from '@wraffle/ui/src/ui/typography/Typography'; + +interface HeaderProps { + withBackButton?: boolean; + children: ReactNode; +} + +const HeaderPrimitive = ({children}: HeaderProps) => { + return ( +
+ {children} +
+ ); +}; + +type WithChildren = { + children: ReactNode; +}; + +const Left = ({children}: WithChildren) => { + return
{children}
; +}; + +const Middle = ({children}: WithChildren) => { + return
{children}
; +}; + +const Right = ({children}: WithChildren) => { + return
{children}
; +}; + +interface BackButtonProps { + onClick: () => void; +} + +const BackButton = ({onClick}: BackButtonProps) => { + return ( + + ); +}; + +const Logo = () => { + return ( +
+ logo +
+ ); +}; + +interface TitleProps { + children: string; +} + +const Title = ({children}: TitleProps) => { + return ( + + {children} + + ); +}; + +HeaderPrimitive.Left = Left; +HeaderPrimitive.Middle = Middle; +HeaderPrimitive.Right = Right; +HeaderPrimitive.BackButton = BackButton; +HeaderPrimitive.Logo = Logo; +HeaderPrimitive.Title = Title; + +export {HeaderPrimitive as Header}; diff --git a/apps/front/wraffle-webview/src/shared/ui/header/core/Logo.tsx b/apps/front/wraffle-webview/src/shared/ui/header/core/Logo.tsx index bc029de5..71e57f28 100644 --- a/apps/front/wraffle-webview/src/shared/ui/header/core/Logo.tsx +++ b/apps/front/wraffle-webview/src/shared/ui/header/core/Logo.tsx @@ -3,7 +3,7 @@ import Image from 'next/image'; export const Logo = () => { return (
- logo + logo
); }; diff --git a/apps/front/wraffle-webview/src/shared/ui/index.ts b/apps/front/wraffle-webview/src/shared/ui/index.ts index c9b46e45..430a3240 100644 --- a/apps/front/wraffle-webview/src/shared/ui/index.ts +++ b/apps/front/wraffle-webview/src/shared/ui/index.ts @@ -1,4 +1,6 @@ +export * from './bottom'; export * from './form'; export * from './header/Header'; export * from './progress/Progress'; export * from './Timer'; +export * from './divider/Divider'; diff --git a/apps/front/wraffle-webview/src/shared/util/auth.ts b/apps/front/wraffle-webview/src/shared/util/auth.ts new file mode 100644 index 00000000..2a4e1a24 --- /dev/null +++ b/apps/front/wraffle-webview/src/shared/util/auth.ts @@ -0,0 +1,95 @@ +import apiClient from '../api/apiClient'; +import type {Tokens} from '../api/type'; +import {isApiResponseError} from '../api/type'; +import {ACCESS_TOKEN_EXPIRES_IN} from './const'; +import NextAuth, {CredentialsSignin} from 'next-auth'; +import type {JWT} from 'next-auth/jwt'; +import Credentials from 'next-auth/providers/credentials'; +import {loginSchema} from '@/widgets/login/config'; + +interface LoginRequestBody { + email: string; + password: string; +} + +export const {handlers, signIn, signOut, auth} = NextAuth({ + trustHost: true, + pages: { + signIn: '/login/email', + }, + providers: [ + Credentials({ + name: 'credentials', + credentials: { + email: {label: 'Email', type: 'email'}, + password: {label: 'Password', type: 'password'}, + }, + authorize: async credentials => { + const validationFields = await loginSchema.safeParseAsync(credentials); + if (!validationFields.success) return null; + + const {email, password} = validationFields.data; + + try { + const response = await apiClient.post( + '/auth/login', + { + body: {email, password}, + }, + ); + + return response.data; + } catch (error) { + const credentialsSignin = new CredentialsSignin(); + if (isApiResponseError(error) && error.status === 401) { + credentialsSignin.code = error.code; + credentialsSignin.message = error.message; + } + throw credentialsSignin; + } + }, + }), + ], + callbacks: { + async jwt({token, user}) { + if (user) { + token.accessToken = user.accessToken; + token.refreshToken = user.refreshToken; + token.accessTokenExpires = Date.now() + ACCESS_TOKEN_EXPIRES_IN * 1000; + } + + if (Date.now() < token.accessTokenExpires) { + return token; + } + + return await refreshAccessToken(token); + }, + + async session({session, token}) { + session.accessToken = token.accessToken; + session.refreshToken = token.refreshToken; + return session; + }, + }, + secret: process.env.AUTH_SECRET, +}); + +async function refreshAccessToken(token: JWT) { + try { + const response = await apiClient.post< + {accessToken: string}, + {refreshToken: string} + >('/auth/refresh', { + body: {refreshToken: token.refreshToken}, + withAuth: true, + }); + return { + ...token, + accessToken: response.data.accessToken, + accessTokenExpires: Date.now() + ACCESS_TOKEN_EXPIRES_IN * 1000, + }; + } catch (error) { + signOut(); + return {...token, error: 'RefreshAccessTokenError'}; + } +} diff --git a/apps/front/wraffle-webview/src/shared/util/const.ts b/apps/front/wraffle-webview/src/shared/util/const.ts index d3978c1f..ab86d822 100644 --- a/apps/front/wraffle-webview/src/shared/util/const.ts +++ b/apps/front/wraffle-webview/src/shared/util/const.ts @@ -3,3 +3,16 @@ export const TAG_LIMIT = 9; export const PRICE_LIMIT = 11; // 쉼표까지 포함하여 억단위까지 export const ETC_LIMIT = 400; export const WINNER_LIMIT = 3; // 3자리 + +export const ACCESS_TOKEN_EXPIRES_IN = 604800; // 7 Day + +export const ERROR_STATUS = { + NON_EXISTENT_ACCOUNT: { + code: 'A007', + message: '존재하지 않는 계정입니다.', + }, + MISMATCHED_PASSWORD: { + code: 'A012', + message: '비밀번호가 일치하지 않습니다.', + }, +}; diff --git a/apps/front/wraffle-webview/src/shared/util/formatDate.ts b/apps/front/wraffle-webview/src/shared/util/formatDate.ts new file mode 100644 index 00000000..13a11822 --- /dev/null +++ b/apps/front/wraffle-webview/src/shared/util/formatDate.ts @@ -0,0 +1,15 @@ +import dayjs from 'dayjs'; + +export const formatDate = ( + dateString: string, + format: string = 'M월 D일 HH:mm:ss', +): string => { + return dayjs(dateString).format(format); +}; + +/* + 사용 예시 + formatDate("2021-08-05T00:00:00.000Z"); // 기본 포맷: "8월 5일 00:00:00" + formatDate("2021-08-05T00:00:00.000Z", "YYYY.MM.DD HH:mm"); // "2021.08.05 00:00" + formatDate("2021-08-05T00:00:00.000Z", "YYYY-MM-DD"); // "2021-08-05" + */ diff --git a/apps/front/wraffle-webview/src/widgets/category-list/config/const.ts b/apps/front/wraffle-webview/src/widgets/category-list/config/const.ts new file mode 100644 index 00000000..7dfc1a79 --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/category-list/config/const.ts @@ -0,0 +1,76 @@ +import type {CategoryItem} from '@/entities/category'; + +export const categories: CategoryItem[] = [ + { + id: 1, + name: '패션', + parentId: null, + depth: 1, + }, + { + id: 2, + name: '가전 디지털', + parentId: null, + depth: 1, + }, + { + id: 3, + name: '전자제품', + parentId: null, + depth: 1, + }, + { + id: 4, + name: '식품', + parentId: null, + depth: 1, + }, + { + id: 5, + name: '의류', + parentId: null, + depth: 1, + }, + { + id: 6, + name: '생활', + parentId: null, + depth: 1, + }, + { + id: 11, + name: '패션', + parentId: null, + depth: 1, + }, + { + id: 12, + name: '가전 디지털', + parentId: null, + depth: 1, + }, + { + id: 13, + name: '전자 제품', + parentId: null, + depth: 1, + }, + { + id: 14, + name: '식품', + parentId: null, + depth: 1, + }, + { + id: 15, + name: '의류', + parentId: null, + depth: 1, + }, + { + id: 16, + name: '생활', + parentId: null, + depth: 1, + }, +]; diff --git a/apps/front/wraffle-webview/src/widgets/category-list/config/index.ts b/apps/front/wraffle-webview/src/widgets/category-list/config/index.ts new file mode 100644 index 00000000..e47ea3a4 --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/category-list/config/index.ts @@ -0,0 +1 @@ +export * from './const'; diff --git a/apps/front/wraffle-webview/src/widgets/category-list/ui/CategoryButtons.tsx b/apps/front/wraffle-webview/src/widgets/category-list/ui/CategoryButtons.tsx new file mode 100644 index 00000000..e1259799 --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/category-list/ui/CategoryButtons.tsx @@ -0,0 +1,20 @@ +import type {CategoryItem} from '@/entities/category/type'; +import {CategoryButton} from '@/features/get-category/ui'; + +interface CategoryButtonsProps { + categories: CategoryItem[]; +} + +const CategoryButtons = ({categories}: CategoryButtonsProps) => { + return ( +
+
+ {categories.map(category => ( + + ))} +
+
+ ); +}; + +export {CategoryButtons}; diff --git a/apps/front/wraffle-webview/src/widgets/category-list/ui/index.ts b/apps/front/wraffle-webview/src/widgets/category-list/ui/index.ts new file mode 100644 index 00000000..bbe507e6 --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/category-list/ui/index.ts @@ -0,0 +1 @@ +export * from './CategoryButtons'; diff --git a/apps/front/wraffle-webview/src/widgets/join/Phone/PhoneNumber.tsx b/apps/front/wraffle-webview/src/widgets/join/Phone/PhoneNumber.tsx deleted file mode 100644 index edfc5b24..00000000 --- a/apps/front/wraffle-webview/src/widgets/join/Phone/PhoneNumber.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import {useEffect, useState} from 'react'; -import {useFormContext} from 'react-hook-form'; -import type {JoinPayload} from '@/entities/auth/join/schema'; -import useAutoFocus from '@/shared/hook/useAutoFocus'; -import useInput from '@/shared/hook/useInput'; -import {handleMaxLength} from '@/shared/util'; -import {Button, InputField, Select, Typography} from '@wraffle/ui'; - -const MAX_INPUT_LENGTH = 4; - -interface PhoneNumberProps { - onNext(phoneNumber: string): void; -} - -const PhoneNumber = ({onNext}: PhoneNumberProps) => { - const { - formState: {errors}, - setValue, - getValues, - trigger, - } = useFormContext(); - - const [middleInputRef, handleMiddleKeyUp] = useAutoFocus(MAX_INPUT_LENGTH); - const [lastInputRef, handleLastKeyUp] = useAutoFocus(MAX_INPUT_LENGTH); - - const [first, setFirst] = useState(''); - const [middle, handleMiddle] = useInput(''); - const [last, handleLast] = useInput(''); - - const isValid = - first && - (middle.length === MAX_INPUT_LENGTH || - middle.length === MAX_INPUT_LENGTH - 1) && - last.length === MAX_INPUT_LENGTH; - - useEffect(() => { - if (isValid) { - setValue('phoneNumber', first.concat(middle, last)); - trigger('phoneNumber'); - } - }, [first, middle, last]); - - return ( -
-
- - 휴대폰 인증을 진행할게요! - - - 휴대폰 정보를 입력해주세요. - -
- - - 휴대폰 번호* - +
diff --git a/apps/front/wraffle-webview/src/widgets/join/Phone/PhoneFunnel.tsx b/apps/front/wraffle-webview/src/widgets/join/ui/Phone/PhoneFunnel.tsx similarity index 100% rename from apps/front/wraffle-webview/src/widgets/join/Phone/PhoneFunnel.tsx rename to apps/front/wraffle-webview/src/widgets/join/ui/Phone/PhoneFunnel.tsx diff --git a/apps/front/wraffle-webview/src/widgets/join/ui/Phone/PhoneNumber.tsx b/apps/front/wraffle-webview/src/widgets/join/ui/Phone/PhoneNumber.tsx new file mode 100644 index 00000000..b4d39f27 --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/join/ui/Phone/PhoneNumber.tsx @@ -0,0 +1,54 @@ +import type {JoinPayload} from '../../config'; +import {useState} from 'react'; +import {useFormContext} from 'react-hook-form'; +import {RequestCode} from '@/features/validate-phone/ui/RequestCode'; +import {Button, Typography} from '@wraffle/ui'; + +interface PhoneNumberProps { + onNext(phoneNumber: string): void; +} + +const PhoneNumber = ({onNext}: PhoneNumberProps) => { + const { + formState: {errors}, + setValue, + getValues, + } = useFormContext(); + + const [isVerified, handleIsVerified] = useState(false); + + const handlePhoneNumber = (phone: string) => { + setValue('phoneNumber', phone, {shouldValidate: true}); + }; + + return ( +
+
+ + 휴대폰 인증을 진행할게요! + + + 휴대폰 정보를 입력해주세요. + +
+ + + +
+ +
+
+ ); +}; + +export default PhoneNumber; diff --git a/apps/front/wraffle-webview/src/widgets/join/Phone/index.ts b/apps/front/wraffle-webview/src/widgets/join/ui/Phone/index.ts similarity index 100% rename from apps/front/wraffle-webview/src/widgets/join/Phone/index.ts rename to apps/front/wraffle-webview/src/widgets/join/ui/Phone/index.ts diff --git a/apps/front/wraffle-webview/src/widgets/join/index.ts b/apps/front/wraffle-webview/src/widgets/join/ui/index.ts similarity index 100% rename from apps/front/wraffle-webview/src/widgets/join/index.ts rename to apps/front/wraffle-webview/src/widgets/join/ui/index.ts diff --git a/apps/front/wraffle-webview/src/entities/auth/login/index.ts b/apps/front/wraffle-webview/src/widgets/login/config/index.ts similarity index 100% rename from apps/front/wraffle-webview/src/entities/auth/login/index.ts rename to apps/front/wraffle-webview/src/widgets/login/config/index.ts diff --git a/apps/front/wraffle-webview/src/widgets/login/config/schema.ts b/apps/front/wraffle-webview/src/widgets/login/config/schema.ts new file mode 100644 index 00000000..283f7c77 --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/login/config/schema.ts @@ -0,0 +1,12 @@ +import {z} from 'zod'; +import {emailSchema, passwordSchema} from '@/entities/auth/schema'; +import {getDefaults} from '@/shared/util'; + +export const loginSchema = z.object({ + email: emailSchema, + password: passwordSchema, +}); + +export type LoginPayload = z.infer; + +export const loginDefaultValues = getDefaults(loginSchema); diff --git a/apps/front/wraffle-webview/src/widgets/login/EmailForm.tsx b/apps/front/wraffle-webview/src/widgets/login/ui/EmailForm.tsx similarity index 72% rename from apps/front/wraffle-webview/src/widgets/login/EmailForm.tsx rename to apps/front/wraffle-webview/src/widgets/login/ui/EmailForm.tsx index b4ac34b6..b8fb6173 100644 --- a/apps/front/wraffle-webview/src/widgets/login/EmailForm.tsx +++ b/apps/front/wraffle-webview/src/widgets/login/ui/EmailForm.tsx @@ -1,7 +1,7 @@ +import type {LoginPayload} from '../config/schema'; import {useFormContext} from 'react-hook-form'; -import type {LoginPayload} from '@/entities/auth'; import {RHFInput} from '@/shared/ui'; -import {Button} from '@wraffle/ui'; +import {Button, Typography} from '@wraffle/ui'; const EmailForm = () => { const {formState} = useFormContext(); @@ -19,11 +19,7 @@ const EmailForm = () => { label='비밀번호*' placeholder='비밀번호를 입력해주세요.' /> - diff --git a/apps/front/wraffle-webview/src/widgets/login/ui/index.ts b/apps/front/wraffle-webview/src/widgets/login/ui/index.ts new file mode 100644 index 00000000..ce11aad2 --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/login/ui/index.ts @@ -0,0 +1 @@ +export * from './EmailForm'; diff --git a/apps/front/wraffle-webview/src/widgets/product-image-list/ProductImageList.tsx b/apps/front/wraffle-webview/src/widgets/product-image-list/ProductImageList.tsx new file mode 100644 index 00000000..bbde8386 --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/product-image-list/ProductImageList.tsx @@ -0,0 +1,49 @@ +'use client'; + +import 'swiper/css'; +import 'swiper/css/pagination'; +import {Swiper, SwiperSlide} from 'swiper/react'; +import Image from 'next/image'; +import React, {useState} from 'react'; +import type {FunctionComponent} from 'react'; + +interface ProductImageListProps { + images: string[]; +} + +const ProductImageList: FunctionComponent = ({ + images, +}) => { + const [currentIndex, setCurrentIndex] = useState(0); + + return ( +
+ setCurrentIndex(swiper.realIndex)} + > + {images.map((image, index) => ( + + {`Product + + ))} + +
+ {currentIndex + 1}/{images.length} +
+
+ ); +}; + +export default ProductImageList; diff --git a/apps/front/wraffle-webview/src/widgets/product-info/config/const.ts b/apps/front/wraffle-webview/src/widgets/product-info/config/const.ts new file mode 100644 index 00000000..bd3db6ff --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/product-info/config/const.ts @@ -0,0 +1,16 @@ +export const RAFFLE_MENUS = [ + '상품', + '응모 기간', + '당첨자 발표', + '유의사항', +] as const; +export const EVENT_MENUS = [ + '상품', + '응모 기간', + '당첨자 발표', + '추첨 상품', + '유의사항', +] as const; + +export type RaffleMenu = (typeof RAFFLE_MENUS)[number]; +export type EventMenu = (typeof EVENT_MENUS)[number]; diff --git a/apps/front/wraffle-webview/src/widgets/product-info/hook/useMenu.ts b/apps/front/wraffle-webview/src/widgets/product-info/hook/useMenu.ts new file mode 100644 index 00000000..1b85ee30 --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/product-info/hook/useMenu.ts @@ -0,0 +1,17 @@ +import {type RaffleMenu, type EventMenu} from '../config/const'; +import {useState} from 'react'; + +export const useMenu = (initialMenu: RaffleMenu | EventMenu) => { + const [selectedMenu, setSelectedMenu] = useState( + initialMenu, + ); + + const selectMenu = (menu: RaffleMenu | EventMenu) => { + setSelectedMenu(menu); + }; + + return { + selectedMenu, + selectMenu, + }; +}; diff --git a/apps/front/wraffle-webview/src/widgets/product-info/index.ts b/apps/front/wraffle-webview/src/widgets/product-info/index.ts new file mode 100644 index 00000000..5c7616e2 --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/product-info/index.ts @@ -0,0 +1,2 @@ +export * from './ui/ProductInfoSection'; +export * from './ui/ProductInfoMenu'; diff --git a/apps/front/wraffle-webview/src/widgets/product-info/ui/ProductInfoMenu.tsx b/apps/front/wraffle-webview/src/widgets/product-info/ui/ProductInfoMenu.tsx new file mode 100644 index 00000000..559cc2eb --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/product-info/ui/ProductInfoMenu.tsx @@ -0,0 +1,32 @@ +import {type RaffleMenu, type EventMenu} from '../config/const'; + +type ProductInfoMenuProps = { + menus: RaffleMenu[] | EventMenu[]; + selectedMenu: RaffleMenu | EventMenu; + onSelectMenu: (menu: RaffleMenu | EventMenu) => void; +}; + +export const ProductInfoMenu = ({ + menus, + selectedMenu, + onSelectMenu, +}: ProductInfoMenuProps) => { + return ( + + ); +}; diff --git a/apps/front/wraffle-webview/src/widgets/product-info/ui/ProductInfoSection.tsx b/apps/front/wraffle-webview/src/widgets/product-info/ui/ProductInfoSection.tsx new file mode 100644 index 00000000..3656573e --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/product-info/ui/ProductInfoSection.tsx @@ -0,0 +1,88 @@ +import React, {type MutableRefObject, type RefObject} from 'react'; +import type {RaffleData, EventData} from '@/entities/product/product'; +import ProductImageList from '@/widgets/product-image-list/ProductImageList'; +import {Tag, RaffleCard} from '@wraffle/ui'; + +type ProductData = RaffleData | EventData; + +// 이미지, 타이틀, 가격 +export const ProductMainSection = ({ + productData, + sectionRef, +}: { + productData: ProductData; + sectionRef: React.RefObject; +}) => { + return ( +
+ +
+
+
+ {productData.tags.map(tag => ( + {tag.name} + ))} +
+
+

{productData.title}

+

+ {productData.price.toLocaleString()}원 +

+
+
+
+
+ ); +}; + +// 공용으로 사용되는 상품 정보 섹션 +export const ProductInfoSection = ({ + label, + data, + sectionsRef, +}: { + label: string; + data: string; + sectionsRef: MutableRefObject<{ + [key: string]: RefObject; + }>; +}) => { + return ( +
+

{label}

+

{data}

+
+ ); +}; + +// 추첨 상품 +export const ProductEventSection = ({ + productData, + sectionRef, +}: { + productData: EventData; + sectionRef: React.RefObject; +}) => { + return ( +
+

추첨 상품

+
+ {productData.products.map(product => ( + + ))} +
+
+ ); +}; diff --git a/apps/front/wraffle-webview/src/widgets/product-list/create/ui/DateStepList.tsx b/apps/front/wraffle-webview/src/widgets/product-list/create/ui/DateStepList.tsx index 4a6fe3bb..f1791c9b 100644 --- a/apps/front/wraffle-webview/src/widgets/product-list/create/ui/DateStepList.tsx +++ b/apps/front/wraffle-webview/src/widgets/product-list/create/ui/DateStepList.tsx @@ -10,7 +10,7 @@ import { WinnerCountForm, } from '@/features/product-form/ui'; import {getTypeText} from '@/shared/util'; -import {Button, Toaster, Typography, useToast} from '@wraffle/ui'; +import {Button, Typography, useToast} from '@wraffle/ui'; export const DateStep = ({ type, @@ -61,9 +61,6 @@ export const DateStep = ({ return (
-
- -
{eventOrRaffleText.dateStep} diff --git a/apps/front/wraffle-webview/src/widgets/recommend-list/config/const.ts b/apps/front/wraffle-webview/src/widgets/recommend-list/config/const.ts new file mode 100644 index 00000000..7b3d907f --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/recommend-list/config/const.ts @@ -0,0 +1,84 @@ +import type {RecommendRaffle} from '@/features/manage-raffle/model'; + +export const recommendItems: RecommendRaffle[] = [ + { + id: 1, + type: 'RAFFLE', + title: '[Vans] 올드스쿨', + price: 78000, + clipCount: 2, + hashtags: [ + { + id: 1, + name: '한정판', + }, + { + id: 2, + name: 'Vans', + }, + ], + thumbnail: + 'https://image.vans.co.kr/cmsstatic/product/VN000D3HY281_VN000D3HY281_primary.jpg', + isBookmarked: false, + }, + { + id: 2, + type: 'RAFFLE', + title: '[Vans] 올드스쿨', + price: 78000, + clipCount: 2, + hashtags: [ + { + id: 1, + name: '한정판', + }, + { + id: 2, + name: 'Vans', + }, + ], + thumbnail: + 'https://image.vans.co.kr/cmsstatic/product/VN000D3HY281_VN000D3HY281_primary.jpg', + isBookmarked: false, + }, + { + id: 3, + type: 'RAFFLE', + title: '[Vans] 올드스쿨', + price: 78000, + clipCount: 2, + hashtags: [ + { + id: 1, + name: '한정판', + }, + { + id: 2, + name: 'Vans', + }, + ], + thumbnail: + 'https://image.vans.co.kr/cmsstatic/product/VN000D3HY281_VN000D3HY281_primary.jpg', + isBookmarked: false, + }, + { + id: 4, + type: 'RAFFLE', + title: '[Vans] 올드스쿨', + price: 78000, + clipCount: 2, + hashtags: [ + { + id: 1, + name: '한정판', + }, + { + id: 2, + name: 'Vans', + }, + ], + thumbnail: + 'https://image.vans.co.kr/cmsstatic/product/VN000D3HY281_VN000D3HY281_primary.jpg', + isBookmarked: false, + }, +]; diff --git a/apps/front/wraffle-webview/src/widgets/recommend-list/config/index.ts b/apps/front/wraffle-webview/src/widgets/recommend-list/config/index.ts new file mode 100644 index 00000000..e47ea3a4 --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/recommend-list/config/index.ts @@ -0,0 +1 @@ +export * from './const'; diff --git a/apps/front/wraffle-webview/src/widgets/recommend-list/ui/RecommendList.tsx b/apps/front/wraffle-webview/src/widgets/recommend-list/ui/RecommendList.tsx new file mode 100644 index 00000000..861c4f7e --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/recommend-list/ui/RecommendList.tsx @@ -0,0 +1,31 @@ +import type {RecommendRaffle} from '@/features/manage-raffle/model'; +import {RaffleCard, Typography} from '@wraffle/ui'; + +interface RecommendListProps { + recommendItems: RecommendRaffle[]; +} + +const RecommendList = ({recommendItems}: RecommendListProps) => { + return ( +
+ + 이번주 추천 래플 + +
+ {recommendItems.map(item => ( + + ))} +
+
+ ); +}; + +export {RecommendList}; diff --git a/apps/front/wraffle-webview/src/widgets/recommend-list/ui/index.ts b/apps/front/wraffle-webview/src/widgets/recommend-list/ui/index.ts new file mode 100644 index 00000000..5a8eb6cb --- /dev/null +++ b/apps/front/wraffle-webview/src/widgets/recommend-list/ui/index.ts @@ -0,0 +1 @@ +export * from './RecommendList'; diff --git a/apps/front/wraffle-webview/tailwind.config.ts b/apps/front/wraffle-webview/tailwind.config.ts index 69fa5e5f..01146197 100644 --- a/apps/front/wraffle-webview/tailwind.config.ts +++ b/apps/front/wraffle-webview/tailwind.config.ts @@ -2,7 +2,7 @@ import type {Config} from 'tailwindcss'; import sharedConfig from '@wraffle/tailwind-config'; -const config: Pick = { +const config: Pick = { content: ['**/*.{ts,tsx}', '../../packages/ui/src/**/*.{ts,tsx}'], presets: [sharedConfig], extends: { @@ -10,6 +10,7 @@ const config: Pick = { pretendard: ['var(--font-pretendard)'], }, }, + plugins: [require('tailwind-scrollbar-hide')], }; export default config; diff --git a/apps/front/wraffle-webview/tsconfig.json b/apps/front/wraffle-webview/tsconfig.json index 91fb55c0..18efeea1 100644 --- a/apps/front/wraffle-webview/tsconfig.json +++ b/apps/front/wraffle-webview/tsconfig.json @@ -8,15 +8,28 @@ ], "baseUrl": ".", "paths": { - "@/app/*": ["./src/app/*"], - "@/pages/*": ["./src/pages/*"], - "@/shared/*": ["./src/shared/*"], - "@/widgets/*": ["./src/widgets/*"], - "@/features/*": ["./src/features/*"], - "@/entities/*": ["./src/entities/*"] + "@/app/*": [ + "./src/app/*" + ], + "@/pages/*": [ + "./src/pages/*" + ], + "@/shared/*": [ + "./src/shared/*" + ], + "@/widgets/*": [ + "./src/widgets/*" + ], + "@/features/*": [ + "./src/features/*" + ], + "@/entities/*": [ + "./src/entities/*" + ] }, "incremental": true, - "noEmit": true + "noEmit": true, + "strictNullChecks": true }, "include": [ "next-env.d.ts", @@ -25,5 +38,7 @@ ".next/types/**/*.ts", "postcss.config.js" ], - "exclude": ["node_modules"] + "exclude": [ + "node_modules" + ] } diff --git a/apps/front/wraffle-webview/turbo.json b/apps/front/wraffle-webview/turbo.json index e5bcd361..9b459b9c 100644 --- a/apps/front/wraffle-webview/turbo.json +++ b/apps/front/wraffle-webview/turbo.json @@ -3,7 +3,8 @@ "extends": ["//"], "tasks": { "build": { - "outputs": [".next/**", "!.next/cache/**"] + "outputs": [".next/**", "!.next/cache/**"], + "env": ["AUTH_SECRET", "API_BASE_URL", "API_BASE_URL_DEV"] } } } diff --git a/packages/ui/src/ui/bottomNavigation/BottomNavigation.tsx b/packages/ui/src/ui/bottomNavigation/BottomNavigation.tsx index c5fb9f4c..d9aef547 100644 --- a/packages/ui/src/ui/bottomNavigation/BottomNavigation.tsx +++ b/packages/ui/src/ui/bottomNavigation/BottomNavigation.tsx @@ -18,7 +18,7 @@ const menuItems: ItemType[] = [ {name: 'user-circle', label: '내정보'}, ]; -export default function BottomNavigation() { +const BottomNavigation = () => { const [selectedIcon, setSelectedIcon] = useState('홈'); const handleClickIcon = (label: string) => { @@ -44,4 +44,6 @@ export default function BottomNavigation() {
); -} +}; + +export {BottomNavigation}; diff --git a/packages/ui/src/ui/button/Button.tsx b/packages/ui/src/ui/button/Button.tsx index 56832503..b2212858 100644 --- a/packages/ui/src/ui/button/Button.tsx +++ b/packages/ui/src/ui/button/Button.tsx @@ -9,7 +9,7 @@ const buttonVariants = cva( variants: { variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', - gray: 'bg-gray-400 text-white hover:bg-gray-600', + gray: 'bg-gray-400 text-white cursor-not-allowed', stroke: 'ring-1 ring-[#18181B] ring-inset text-primary hover:bg-primary hover:text-white', }, diff --git a/packages/ui/src/ui/form/select/Select.tsx b/packages/ui/src/ui/form/select/Select.tsx index 5442e22a..03729831 100644 --- a/packages/ui/src/ui/form/select/Select.tsx +++ b/packages/ui/src/ui/form/select/Select.tsx @@ -7,6 +7,7 @@ import { SelectTrigger, SelectValue, } from './PrimitiveSelect'; +import {cn} from '@wds/shared/utils'; export interface SelectItem { value: string; @@ -19,7 +20,7 @@ export interface GroupSelectItem { } export interface BaseSelectProps { - className: string; + className?: string; placeholder: string; onValueChange: (value: string) => void; defaultValue?: string; @@ -50,7 +51,7 @@ const Select = ({ }: SelectProps) => { return ( - + diff --git a/packages/ui/src/ui/index.tsx b/packages/ui/src/ui/index.tsx index a99429eb..7044974a 100644 --- a/packages/ui/src/ui/index.tsx +++ b/packages/ui/src/ui/index.tsx @@ -4,6 +4,7 @@ export * from './calendar/Calendar'; export * from './checkbox/CheckBox'; export * from './chipButton/ChipButton'; export * from './dialog/Dialog'; +export * from './dialog/use-dialog'; export * from './form'; export * from './icon/Icon'; export * from './icon/IconLoader'; diff --git a/packages/ui/src/ui/raffle-card/RaffleCard.mdx b/packages/ui/src/ui/raffle-card/RaffleCard.mdx index e7d17f8a..499009df 100644 --- a/packages/ui/src/ui/raffle-card/RaffleCard.mdx +++ b/packages/ui/src/ui/raffle-card/RaffleCard.mdx @@ -1,6 +1,6 @@ -import { Meta, Story, Controls, Canvas, Primary } from "@storybook/blocks"; +import {Meta, Story, Controls, Canvas, Primary} from '@storybook/blocks'; -import * as stories from "./RaffleCard.stories"; +import * as stories from './RaffleCard.stories'; diff --git a/packages/ui/src/ui/raffle-card/RaffleCard.stories.tsx b/packages/ui/src/ui/raffle-card/RaffleCard.stories.tsx index 5dd89c8a..41f78b38 100644 --- a/packages/ui/src/ui/raffle-card/RaffleCard.stories.tsx +++ b/packages/ui/src/ui/raffle-card/RaffleCard.stories.tsx @@ -29,7 +29,7 @@ const meta: Meta = { }, args: { name: '[Vans] 올드스쿨', - price: '78,000', + price: 78000, hashtags: [ { id: 1, diff --git a/packages/ui/src/ui/raffle-card/RaffleCard.tsx b/packages/ui/src/ui/raffle-card/RaffleCard.tsx index 1ba80134..98d42dc6 100644 --- a/packages/ui/src/ui/raffle-card/RaffleCard.tsx +++ b/packages/ui/src/ui/raffle-card/RaffleCard.tsx @@ -5,12 +5,12 @@ import {Icon} from '@wds/ui/icon/Icon'; export interface RaffleCardProps { name: string; - price: string; - hashtags: TagType[]; - scrapCount: number; + price?: string; + hashtags?: TagType[]; + scrapCount?: number; thumbnailUrl: string; endDate?: string; - isBookmarked: boolean; + isBookmarked?: boolean; } const RaffleCard = ({ @@ -25,7 +25,7 @@ const RaffleCard = ({ const isClosed = (endDate && new Date(endDate) < new Date()) || false; return ( -
+
)}
- + {name} - - {price}원 - - - - {isBookmarked && } - {!isBookmarked && } - - {scrapCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')} + {price && ( + + {price}원 - + )} + {hashtags && hashtags.length > 0 && ( + + )} + {scrapCount !== undefined && ( + + {isBookmarked ? ( + + ) : ( + + )} + + {scrapCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')} + + + )}
); }; diff --git a/packages/ui/src/ui/typography/Typography.mdx b/packages/ui/src/ui/typography/Typography.mdx index 7a5d499d..827934d1 100644 --- a/packages/ui/src/ui/typography/Typography.mdx +++ b/packages/ui/src/ui/typography/Typography.mdx @@ -13,8 +13,6 @@ import {Meta, Story, Controls, Canvas, Primary} from '@storybook/blocks'; - [Props](#props) - [Description](#description) - [Font Size](#font-size) -- [Font Weight](#font-weight) -- [Line Height](#line-height) - [Colors](#colors) - [Usage](#usage) @@ -31,12 +29,13 @@ import {Meta, Story, Controls, Canvas, Primary} from '@storybook/blocks'; `Typography`는 아래와 같이 사용하시면 됩니다: - `'as'` prop은 의미론적이며 시각적 변형은 되지 않습니다. -- 지정된 타입 외에 세부적인 조정이 필요하다면 `className` 속성을 사용하여 Tailwind CSS 스타일을 적용하세요. +- `size='h1'` 처럼 사용할 시 디자인 시스템 설계 시 지정된 fontWeight, lineHeight가 같이 적용되는걸 유의해주세요. +- 디자인 시스템 설계 시 지정한 타입 외에 세부적인 조정이 필요하다면 `className` 속성을 사용하여 Tailwind CSS 스타일을 적용하세요. 따라서, 각 스타일이 적용되는 태그를 유념하여 사용해 주세요. ```tsx - + Raffle ``` @@ -171,176 +170,9 @@ import {Meta, Story, Controls, Canvas, Primary} from '@storybook/blocks'; Raffle ``` -## Font Weight - -
- {[ - { - label: 'regular', - weight: 400, - text: 'Raffle', - }, - { - label: 'medium', - weight: 500, - text: 'Raffle', - }, - { - label: 'semibold', - weight: 600, - text: 'Raffle', - }, - { - label: 'bold', - weight: 700, - text: 'Raffle', - }, - ].map(({label, weight, text}) => ( -
-
- {label} -
-
- {weight} -
-
- {text} -
-
- ))} -
-```tsx -Raffle -``` - -## Line Height - -
- {[ - { - label: '1.4', - value: '1.4', - text: 'Raffle', - }, - ].map(({label, value, text}) => ( -
-
- {label} -
-
- {value} -
-
- {text} -
-
- ))} -
-```tsx -Raffle -``` - ## Colors -- 일부 색상만 문서화되어있기에 자세한 색상 정보는 `typography.ts 혹은 tailwind.config.ts`를 참고하시길 바랍니다. + +- 일부 색상만 문서화되어있기에 자세한 색상 정보는 `color.prop.ts 혹은 tailwind.config.ts`를 참고하시길 바랍니다.
( + ].map(({color, label, code, description}, index) => (
-
+
{label}
```tsx -Raffle +Raffle ``` ## Usage diff --git a/packages/ui/src/ui/typography/Typography.stories.tsx b/packages/ui/src/ui/typography/Typography.stories.tsx index 5b330ffd..e21fa1c4 100644 --- a/packages/ui/src/ui/typography/Typography.stories.tsx +++ b/packages/ui/src/ui/typography/Typography.stories.tsx @@ -1,4 +1,5 @@ import {Typography, TypographyProps} from './Typography'; +import {colorStyles} from './prop/color.prop'; import type {Meta, StoryFn} from '@storybook/react'; const meta: Meta = { @@ -51,33 +52,14 @@ const meta: Meta = { ], }, }, - weight: { - description: '텍스트 굵기', - table: { - type: {summary: 'regular | medium | semibold | bold'}, - }, - control: { - type: 'select', - options: ['regular', 'medium', 'semibold', 'bold'], - }, - }, - lineHeight: { - description: '텍스트 라인 높이', - table: { - type: {summary: 'string'}, - }, - control: { - type: 'text', - }, - }, - textColor: { + color: { description: '텍스트 색상', table: { type: {summary: 'enum'}, }, control: { type: 'select', - options: colorList, + options: Object.keys(colorStyles), }, }, children: { @@ -85,14 +67,6 @@ const meta: Meta = { type: 'text', }, }, - asChild: { - table: { - disable: true, - }, - control: { - disable: true, - }, - }, }, }; @@ -104,59 +78,23 @@ export const Heading = Template.bind({}); Heading.args = { children: ( <> - - Heading 1 (32px, Bold, 140%) + + Heading 1 (32px, Bold) - - Heading 2 (24px, Bold, 140%) + + Heading 2 (24px, Bold) - - Heading 3 (20px, Semi Bold, 140%) + + Heading 3 (20px, Semi Bold) - - Heading 4 (17px, Semi Bold, 140%) + + Heading 4 (17px, Semi Bold) - - Heading 5 (16px, Semi Bold, 140%) + + Heading 5 (16px, Semi Bold) - - Heading 6 (15px, Semi Bold, 140%) + + Heading 6 (15px, Semi Bold) ), @@ -166,41 +104,17 @@ export const Paragraph = Template.bind({}); Paragraph.args = { children: ( <> - - Paragraph 1 (16px, Medium, 140%) + + Paragraph 1 (16px) - - Paragraph 2 (14px, Medium, 140%) + + Paragraph 2 (14px) - - Paragraph 3 (14px, Medium, 140%) + + Paragraph 3 (14px) - - Paragraph 4 (13px, Medium, 140%) + + Paragraph 4 (13px) ), @@ -210,23 +124,11 @@ export const SmallText = Template.bind({}); SmallText.args = { children: ( <> - - Small Text 1 (12px, Medium, 140%) + + Small Text 1 (12px) - - Small Text 2 (10px, Medium, 140%) + + Small Text 2 (10px) ), diff --git a/packages/ui/src/ui/typography/Typography.tsx b/packages/ui/src/ui/typography/Typography.tsx index 8446ef4d..15743e78 100644 --- a/packages/ui/src/ui/typography/Typography.tsx +++ b/packages/ui/src/ui/typography/Typography.tsx @@ -19,7 +19,7 @@ interface TypographyProps extends React.HTMLAttributes { export const Typography = React.forwardRef( ( { - as: Tag = 'span', + as: Tag = 'p', size = 'p1', color = 'zinc900', asChild = false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 21e67de6..a28302ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -214,9 +214,15 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 next: specifier: 14.2.5 version: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-auth: + specifier: 5.0.0-beta.25 + version: 5.0.0-beta.25(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) react: specifier: ^18 version: 18.3.1 @@ -226,6 +232,12 @@ importers: react-hook-form: specifier: ^7.53.0 version: 7.53.1(react@18.3.1) + swiper: + specifier: ^11.1.14 + version: 11.1.15 + tailwind-scrollbar-hide: + specifier: ^1.1.7 + version: 1.1.7 zod: specifier: ^3.23.8 version: 3.23.8 @@ -469,6 +481,20 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@auth/core@0.37.2': + resolution: {integrity: sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + nodemailer: ^6.8.0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + '@babel/code-frame@7.10.4': resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==} @@ -1589,9 +1615,6 @@ packages: '@expo/config-plugins@8.0.10': resolution: {integrity: sha512-KG1fnSKRmsudPU9BWkl59PyE0byrE2HTnqbOrgwr2FAhqh7tfr9nRs6A9oLS/ntpGzmFxccTEcsV0L4apsuxxg==} - '@expo/config-plugins@8.0.8': - resolution: {integrity: sha512-Fvu6IO13EUw0R9WeqxUO37FkM62YJBNcZb9DyJAOgMz7Ez/vaKQGEjKt9cwT+Q6uirtCATMgaq6VWAW7YW8xXw==} - '@expo/config-plugins@8.0.9': resolution: {integrity: sha512-dNCG45C7BbDPV9MdWvCbsFtJtVn4w/TJbb5b7Yr6FA8HYIlaaVM0wqUMzTPmGj54iYXw8X/Vge8uCPxg7RWgeA==} @@ -2134,6 +2157,9 @@ packages: resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@panva/hkdf@1.2.1': + resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -3566,6 +3592,7 @@ packages: '@xmldom/xmldom@0.7.13': resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} engines: {node: '>=10.0.0'} + deprecated: this version is no longer supported, please update to at least 0.8.* '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} @@ -3639,6 +3666,9 @@ packages: ajv@8.11.0: resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} + ajv@8.11.0: + resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} + ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} @@ -3711,10 +3741,7 @@ packages: engines: {node: '>=10'} aria-query@5.3.0: - resolution: - { - integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==, - } + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} @@ -3724,13 +3751,6 @@ packages: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} - array-buffer-byte-length@1.0.1: - resolution: - { - integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==, - } - engines: { node: '>= 0.4' } - array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} @@ -4867,6 +4887,11 @@ packages: peerDependencies: expo: '*' + expo-build-properties@0.12.5: + resolution: {integrity: sha512-donC1le0PYfLKCPKRMGQoixuWuwDWCngzXSoQXUPsgHTDHQUKr8aw+lcWkTwZcItgNovcnk784I0dyfYDcxybA==} + peerDependencies: + expo: '*' + expo-constants@16.0.2: resolution: {integrity: sha512-9tNY3OVO0jfiMzl7ngb6IOyR5VFzNoN5OOazUWoeGfmMqVB5kltTemRvKraK9JRbBKIw+SOYLEmF0sEqgFZ6OQ==} peerDependencies: @@ -4887,6 +4912,71 @@ packages: peerDependencies: expo: '*' + expo-dev-menu@5.0.22: + resolution: {integrity: sha512-VzpdQReAtjbI1qIuwOf0sUzf91HsfGThojgJD9Ez0eca12qY5tTGYzHa1EM9V+zIcNuNZ7+A8bHJJdmZ4zvU6g==} + peerDependencies: + expo: "*" + + expo-dev-client@4.0.29: + resolution: {integrity: sha512-aANlw9dC4PJEPaRNpe+X5xwyYI+aCIcbZklAAsFlkv2/05gLrsvAFgmQpRtowAzF+VggHWde1eKUOeUccAYIEg==} + peerDependencies: + expo: "*" + + expo-dev-launcher@4.0.29: + resolution: {integrity: sha512-0a0SL8mc4FrqPeGxJHe9kf0kG+Di+38Gd+HP5DEL9dcOa8m2qffKnk22UcyujCT6+Qk0OUK1s53nnfqFB26uVw==} + peerDependencies: + expo: "*" + + expo-dev-menu-interface@1.8.4: + resolution: {integrity: sha512-FpYI57EUu9qTSOOi+FZJ58xkCGJK7QD0mTiXK/y1I8lRdZGjCmdBqVvC4dAx2GcbIT78EPxaVf4/90tK/KRK6A==} + peerDependencies: + expo: "*" + + expo-dev-menu@5.0.23: + resolution: {integrity: sha512-ztDvrSdFGkRbMoQlGLyKMS6CslMGylonVW4kQHUrBQApCL0c2NtRwLlr2bA1SXF0S7qYdPPg/ayLnj7DDR5X2w==} + peerDependencies: + expo: "*" + + expo-dev-client@4.0.28: + resolution: {integrity: sha512-wz5G4vY3Gbk5GuQTyijdqY4Hwr/NDt5OUTErbOu1vd4XRIAsI+8IkK5hsBUhGmqrdkYnP5NxxOxC/soFzX/9+w==} + peerDependencies: + expo: '*' + + expo-dev-client@4.0.28: + resolution: {integrity: sha512-wz5G4vY3Gbk5GuQTyijdqY4Hwr/NDt5OUTErbOu1vd4XRIAsI+8IkK5hsBUhGmqrdkYnP5NxxOxC/soFzX/9+w==} + peerDependencies: + expo: '*' + + expo-dev-launcher@4.0.28: + resolution: {integrity: sha512-goE7jcaGVA2zu4gV3/hQ9RXqGhUZZAu339VYNLbwPdaNCzFaG6A8MZHg18gytCUnZ5QkRJsYi4q/8YcwUCASlQ==} + peerDependencies: + expo: '*' + + expo-dev-menu-interface@1.8.3: + resolution: {integrity: sha512-QM0LRozeFT5Ek0N7XpV93M+HMdEKRLEOXn0aW5M3uoUlnqC1+PLtF3HMy3k3hMKTTE/kJ1y1Z7akH07T0lunCQ==} + peerDependencies: + expo: '*' + + expo-dev-menu@5.0.22: + resolution: {integrity: sha512-VzpdQReAtjbI1qIuwOf0sUzf91HsfGThojgJD9Ez0eca12qY5tTGYzHa1EM9V+zIcNuNZ7+A8bHJJdmZ4zvU6g==} + peerDependencies: + expo: '*' + + expo-dev-client@4.0.28: + resolution: {integrity: sha512-wz5G4vY3Gbk5GuQTyijdqY4Hwr/NDt5OUTErbOu1vd4XRIAsI+8IkK5hsBUhGmqrdkYnP5NxxOxC/soFzX/9+w==} + peerDependencies: + expo: '*' + + expo-dev-launcher@4.0.28: + resolution: {integrity: sha512-goE7jcaGVA2zu4gV3/hQ9RXqGhUZZAu339VYNLbwPdaNCzFaG6A8MZHg18gytCUnZ5QkRJsYi4q/8YcwUCASlQ==} + peerDependencies: + expo: '*' + + expo-dev-menu-interface@1.8.3: + resolution: {integrity: sha512-QM0LRozeFT5Ek0N7XpV93M+HMdEKRLEOXn0aW5M3uoUlnqC1+PLtF3HMy3k3hMKTTE/kJ1y1Z7akH07T0lunCQ==} + peerDependencies: + expo: '*' + expo-dev-menu@5.0.22: resolution: {integrity: sha512-VzpdQReAtjbI1qIuwOf0sUzf91HsfGThojgJD9Ez0eca12qY5tTGYzHa1EM9V+zIcNuNZ7+A8bHJJdmZ4zvU6g==} peerDependencies: @@ -4905,6 +4995,9 @@ packages: expo-json-utils@0.13.1: resolution: {integrity: sha512-mlfaSArGVb+oJmUcR22jEONlgPp0wj4iNIHfQ2je9Q8WTOqMc0Ws9tUciz3JdJnhffdHqo/k8fpvf0IRmN5HPA==} + expo-json-utils@0.13.1: + resolution: {integrity: sha512-mlfaSArGVb+oJmUcR22jEONlgPp0wj4iNIHfQ2je9Q8WTOqMc0Ws9tUciz3JdJnhffdHqo/k8fpvf0IRmN5HPA==} + expo-keep-awake@13.0.2: resolution: {integrity: sha512-kKiwkVg/bY0AJ5q1Pxnm/GvpeB6hbNJhcFsoOWDh2NlpibhCLaHL826KHUM+WsnJRbVRxJ+K9vbPRHEMvFpVyw==} peerDependencies: @@ -4918,6 +5011,11 @@ packages: peerDependencies: expo: '*' + expo-manifests@0.14.3: + resolution: {integrity: sha512-L3b5/qocBPiQjbW0cpOHfnqdKZbTJS7sA3mgeDJT+mWga/xYsdpma1EfNmsuvrOzjLGjStr1k1fceM9Bl49aqQ==} + peerDependencies: + expo: '*' + expo-modules-autolinking@1.11.2: resolution: {integrity: sha512-fdcaNO8ucHA3yLNY52ZUENBcAG7KEx8QyMmnVNavO1JVBGRMZG8JyVcbrhYQDtVtpxkbai5YzwvLutINvbDZDQ==} hasBin: true @@ -4968,6 +5066,11 @@ packages: peerDependencies: expo: '*' + expo-updates-interface@0.16.2: + resolution: {integrity: sha512-929XBU70q5ELxkKADj1xL0UIm3HvhYhNAOZv5DSk7rrKvLo7QDdPyl+JVnwZm9LrkNbH4wuE2rLoKu1KMgZ+9A==} + peerDependencies: + expo: '*' + expo-web-browser@13.0.3: resolution: {integrity: sha512-HXb7y82ApVJtqk8tManyudtTrCtx8xcUnVzmJECeHCB0SsWSQ+penVLZxJkcyATWoJOsFMnfVSVdrTcpKKGszQ==} peerDependencies: @@ -5924,6 +6027,9 @@ packages: join-component@1.1.0: resolution: {integrity: sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==} + jose@5.9.6: + resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6442,6 +6548,22 @@ packages: nested-error-stacks@2.0.1: resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} + next-auth@5.0.0-beta.25: + resolution: {integrity: sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + next: ^14.0.0-0 || ^15.0.0-0 + nodemailer: ^6.6.5 + react: ^18.2.0 || ^19.0.0-0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + next@14.2.5: resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} engines: {node: '>=18.17.0'} @@ -6525,6 +6647,9 @@ packages: nwsapi@2.2.13: resolution: {integrity: sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==} + oauth4webapi@3.1.3: + resolution: {integrity: sha512-dik5wEMdFL5p3JlijYvM7wMNCgaPhblLIDCZtdXcaZp5wgu5Iwmsu7lMzgFhIDTi5d0BJo03LVoOoFQvXMeOeQ==} + ob1@0.80.12: resolution: {integrity: sha512-VMArClVT6LkhUGpnuEoBuyjG9rzUyEzg4PDkav6wK1cLhOK02gPCYFxoiB4mqVnrMhDpIzJcrGNAMVi9P+hXrw==} engines: {node: '>=18'} @@ -6827,6 +6952,14 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} + preact-render-to-string@5.2.3: + resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + peerDependencies: + preact: '>=10' + + preact@10.11.3: + resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -6919,6 +7052,9 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -7779,6 +7915,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + swiper@11.1.15: + resolution: {integrity: sha512-IzWeU34WwC7gbhjKsjkImTuCRf+lRbO6cnxMGs88iVNKDwV+xQpBCJxZ4bNH6gSrIbbyVJ1kuGzo3JTtz//CBw==} + engines: {node: '>= 4.7.0'} + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -7789,6 +7929,9 @@ packages: tailwind-merge@2.5.4: resolution: {integrity: sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==} + tailwind-scrollbar-hide@1.1.7: + resolution: {integrity: sha512-X324n9OtpTmOMqEgDUEA/RgLrNfBF/jwJdctaPZDzB3mppxJk7TLIDmOreEDm1Bq4R9LSPu4Epf8VSdovNU+iA==} + tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} peerDependencies: @@ -8519,6 +8662,16 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + '@auth/core@0.37.2': + dependencies: + '@panva/hkdf': 1.2.1 + '@types/cookie': 0.6.0 + cookie: 0.7.1 + jose: 5.9.6 + oauth4webapi: 3.1.3 + preact: 10.11.3 + preact-render-to-string: 5.2.3(preact@10.11.3) + '@babel/code-frame@7.10.4': dependencies: '@babel/highlight': 7.25.9 @@ -9721,6 +9874,8 @@ snapshots: '@expo/code-signing-certificates': 0.0.5 '@expo/config': 9.0.4 '@expo/config-plugins': 8.0.10 + '@expo/config': 9.0.4 + '@expo/config-plugins': 8.0.10 '@expo/devcert': 1.1.2 '@expo/env': 0.3.0 '@expo/image-utils': 0.5.1 @@ -9890,6 +10045,7 @@ snapshots: dependencies: '@babel/code-frame': 7.10.4 '@expo/config-plugins': 8.0.10 + '@expo/config-plugins': 8.0.10 '@expo/config-types': 51.0.3 '@expo/json-file': 8.3.3 getenv: 1.0.0 @@ -10024,8 +10180,8 @@ snapshots: '@expo/prebuild-config@7.0.8(expo-modules-autolinking@1.11.2)': dependencies: - '@expo/config': 9.0.3 - '@expo/config-plugins': 8.0.8 + '@expo/config': 9.0.4 + '@expo/config-plugins': 8.0.10 '@expo/config-types': 51.0.2 '@expo/image-utils': 0.5.1 '@expo/json-file': 8.3.3 @@ -10042,6 +10198,8 @@ snapshots: '@expo/prebuild-config@7.0.9(expo-modules-autolinking@1.11.2)': dependencies: + '@expo/config': 9.0.4 + '@expo/config-plugins': 8.0.10 '@expo/config': 9.0.4 '@expo/config-plugins': 8.0.10 '@expo/config-types': 51.0.3 @@ -10768,6 +10926,8 @@ snapshots: dependencies: semver: 7.6.3 + '@panva/hkdf@1.2.1': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -11601,9 +11761,7 @@ snapshots: transitivePeerDependencies: - '@babel/core' - '@babel/preset-env' - - bufferutil - supports-color - - utf-8-validate '@react-native/normalize-colors@0.74.84': {} @@ -12789,6 +12947,13 @@ snapshots: require-from-string: 2.0.2 uri-js: 4.4.1 + ajv@8.11.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 @@ -13895,7 +14060,7 @@ snapshots: eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.2(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -13958,7 +14123,7 @@ snapshots: is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node @@ -14032,7 +14197,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -15708,6 +15873,8 @@ snapshots: join-component@1.1.0: {} + jose@5.9.6: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -16316,6 +16483,12 @@ snapshots: nested-error-stacks@2.0.1: {} + next-auth@5.0.0-beta.25(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + dependencies: + '@auth/core': 0.37.2 + next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.2.5 @@ -16393,6 +16566,8 @@ snapshots: nwsapi@2.2.13: {} + oauth4webapi@3.1.3: {} + ob1@0.80.12: dependencies: flow-enums-runtime: 0.0.6 @@ -16682,6 +16857,13 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + preact-render-to-string@5.2.3(preact@10.11.3): + dependencies: + preact: 10.11.3 + pretty-format: 3.8.0 + + preact@10.11.3: {} + prelude-ls@1.2.1: {} prettier-plugin-packagejson@2.5.3(prettier@3.3.3): @@ -16727,6 +16909,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + pretty-format@3.8.0: {} + process-nextick-args@2.0.1: {} process@0.11.10: {} @@ -17759,6 +17943,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swiper@11.1.15: {} + symbol-tree@3.2.4: {} synckit@0.9.2: @@ -17768,6 +17954,8 @@ snapshots: tailwind-merge@2.5.4: {} + tailwind-scrollbar-hide@1.1.7: {} + tailwindcss-animate@1.0.7(tailwindcss@3.4.14): dependencies: tailwindcss: 3.4.14 diff --git a/turbo.json b/turbo.json index 24f8e9d4..a0af4efa 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,6 @@ { "$schema": "https://turbo.build/schema.json", + "globalEnv": ["NODE_ENV"], "tasks": { "build": { "dependsOn": ["^build"],