diff --git a/bun.lockb b/bun.lockb index 2bd3b63..dfe938e 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/next.config.mjs b/next.config.mjs index 0f605e8..ffb2d84 100755 --- a/next.config.mjs +++ b/next.config.mjs @@ -22,22 +22,6 @@ const nextConfig = { }, ]; }, - webpack: (config, { isServer }) => { - if (isServer) { - if (Array.isArray(config.resolve.alias)) { - config.resolve.alias.push({ name: 'msw/browser', alias: false }); - } else { - config.resolve.alias['msw/browser'] = false; - } - } else { - if (Array.isArray(config.resolve.alias)) { - config.resolve.alias.push({ name: 'msw/node', alias: false }); - } else { - config.resolve.alias['msw/node'] = false; - } - } - return config; - }, }; const ContentSecurityPolicy = ` diff --git a/package.json b/package.json index 74b2563..300d6c0 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "test:pin": "jest --watch /tests/pin/app.spec.tsx" }, "dependencies": { + "@conform-to/react": "^1.1.0", + "@conform-to/zod": "^1.1.0", "@mdx-js/loader": "^2.3.0", "@mdx-js/react": "^2.3.0", "@next/bundle-analyzer": "^13.5.4", @@ -57,13 +59,15 @@ "sharp": "^0.32.6", "styled-components": "^5.3.11", "swr": "^2.2.4", + "tailwind-merge": "^2.2.2", "ts-node": "^10.9.1", "undici": "^6.10.2", "uuid": "^9.0.1", - "valibot": "^0.27.0", + "valibot": "^0.30.0", "web-vitals": "^3.5.0", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", + "zod": "^3.22.4", "zustand": "^4.5.2" }, "devDependencies": { diff --git a/public/font/NanumSquareNeo.woff2 b/public/font/NanumSquareNeo.woff2 new file mode 100755 index 0000000..a512048 Binary files /dev/null and b/public/font/NanumSquareNeo.woff2 differ diff --git a/src/components/NotificationList/NotificationList.tsx b/src/components/NotificationList/NotificationList.tsx index 4c2bedc..af743ec 100644 --- a/src/components/NotificationList/NotificationList.tsx +++ b/src/components/NotificationList/NotificationList.tsx @@ -8,14 +8,8 @@ import NotificationItem from '../NotificationItem'; type NotificationItem = [string, Notification]; function NotificationList() { - const [notificationList, setNotificationList] = React.useState< - NotificationItem[] - >([]); - const { notifications } = useNotificationStore((state) => state); - - React.useEffect(() => { - setNotificationList([...notifications.entries()]); - }, [notifications]); + const notifications = useNotificationStore((state) => state.notifications); + console.log(notifications); return (
    - {notificationList && - notificationList.map(([id, notification]: NotificationItem) => ( - + {notifications && + notifications.map(({ id, message, type }) => ( + ))}
); diff --git a/src/components/Notifications/Notifiactions.tsx b/src/components/Notifications/Notifiactions.tsx deleted file mode 100644 index 32d3d10..0000000 --- a/src/components/Notifications/Notifiactions.tsx +++ /dev/null @@ -1,33 +0,0 @@ -'use client'; - -import React from 'react'; -import { - NotificationContext, - type Notification, -} from '@/context/NotificationContext'; -import NotificationItem from '../NotificationItem'; - -function Notifications() { - const { notificationList } = React.useContext(NotificationContext); - - return ( -
    - {notificationList && - notificationList.map((notification: Notification) => ( - - ))} -
- ); -} - -export default Notifications; diff --git a/src/components/Notifications/index.ts b/src/components/Notifications/index.ts deleted file mode 100644 index 2f536d0..0000000 --- a/src/components/Notifications/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './Notifiactions'; -export { default } from './Notifiactions'; diff --git a/src/components/PinNumber/PinInput.tsx b/src/components/PinNumber/PinInput.tsx index 2709306..c8b5c8a 100644 --- a/src/components/PinNumber/PinInput.tsx +++ b/src/components/PinNumber/PinInput.tsx @@ -9,13 +9,15 @@ import PinSuccess from './PinSuccess'; import styled from 'styled-components'; import { motion } from 'framer-motion'; import { useNumpadStore, useSubmitNumpadStroe } from '@/context/NumpadContext'; +import { useNotificationStore } from '@/context/NotificationContext'; +import { goToUsername } from '@/utils/revalidate'; interface PinInputProps { children: React.ReactNode; uses: 'register' | 'confirm'; padInfo: KeypadInfo; action: ( - decodedPinNumbers: string[], + formData: FormData, padInfo: KeypadInfo, ) => Promise<{ status: string; @@ -58,9 +60,11 @@ function PinInput({ uses, children, padInfo, action }: PinInputProps) { errorId: '', msg: null, }); + const inputRef = React.useRef(null); const containerRef = React.useRef(null); const isHydrated = useHydrated(); + const { updateStatus } = uses === 'register' ? useNumpadStore((state) => state) @@ -101,30 +105,12 @@ function PinInput({ uses, children, padInfo, action }: PinInputProps) { setOpen(true); }} action={async (formData: FormData) => { - updateStatus('pending'); - const isReorder = formData.get('reorder') === 'on'; - - if (isReorder) { - reorderKeypad(isReorder); - return; - } - - let formPinNumber = formData.get('pinNumbers') as string; - const result = v.safeParse(PinNumberSchema, formPinNumber.split(',')); + const actionResult = await action(formData, padInfo); - if (!result.success) { - updateStatus('idle'); - setPinErrorStatus({ - hasError: true, - errorId: 'pin-pattern-input', - msg: result.issues[0].message, - }); - return; - } - - const pinNumber = result.output as string[]; - const actionResult = await action(pinNumber, padInfo); if (actionResult.status === 'error') { + if (actionResult.id === 'username-not-found') { + return goToUsername(); + } updateStatus('idle'); setPinErrorStatus({ hasError: true, @@ -133,10 +119,42 @@ function PinInput({ uses, children, padInfo, action }: PinInputProps) { }); return; } - - setOpen(true); - setSuccess(true); - updateStatus('idle'); + // updateStatus('pending'); + // const isReorder = formData.get('reorder') === 'on'; + + // if (isReorder) { + // reorderKeypad(isReorder); + // return; + // } + + // let formPinNumber = formData.get('pinNumbers') as string; + // const result = v.safeParse(PinNumberSchema, formPinNumber.split(',')); + + // if (!result.success) { + // updateStatus('idle'); + // setPinErrorStatus({ + // hasError: true, + // errorId: 'pin-pattern-input', + // msg: result.issues[0].message, + // }); + // return; + // } + + // const pinNumber = result.output as string[]; + // const actionResult = await action(pinNumber, padInfo); + // if (actionResult.status === 'error') { + // updateStatus('idle'); + // setPinErrorStatus({ + // hasError: true, + // errorId: actionResult.id, + // msg: actionResult.msg, + // }); + // return; + // } + + // setOpen(true); + // setSuccess(true); + // updateStatus('idle'); }} > {isSuccess ? ( diff --git a/src/context/NotificationContext.tsx b/src/context/NotificationContext.tsx index 16e0c74..988a3b6 100644 --- a/src/context/NotificationContext.tsx +++ b/src/context/NotificationContext.tsx @@ -1,77 +1,40 @@ 'use client'; import React from 'react'; -import NotificationItem from '@/components/NotificationItem'; - -interface NotificationContextProps { - notificationList: Notification[]; - action: { - add: (notification: Notification) => void; - remove: (id: string) => void; - }; -} - -export interface Notification { - id: string; - message: string; - type: 'default' | 'error' | 'success'; -} - -interface ContextValue { - notificationList: Notification[]; - action: { - add: (notification: Notification) => void; - remove: (id: string) => void; - }; -} +import { type StoreApi, useStore } from 'zustand'; +import { + type NotificationStore, + createNotificationStore, +} from '@/store/notification-store'; export const NotificationContext = - React.createContext({ - notificationList: [], - action: { - add: () => {}, - remove: () => {}, - }, - }); + React.createContext | null>(null); -function NotificationContextProvider({ +export function NotificationProvider({ children, }: { children: React.ReactNode; }) { - const [notificationList, setNotification] = React.useState( - [], - ); - - const contextValue = React.useMemo((): ContextValue => { - const add = (notification: Notification) => { - const newNotification = [...notificationList, notification]; - setNotification(newNotification); - }; - - const remove = (id: string) => { - if (id.length == 0) { - setNotification([]); - return; - } - - const newNotification = notificationList.filter((item) => item.id !== id); - setNotification(newNotification); - }; - - const action = { - add, - remove, - }; - - return { notificationList, action }; - }, [notificationList, setNotification]); + const notificationRef = React.useRef>(); + if (!notificationRef.current) { + notificationRef.current = createNotificationStore(); + } return ( - + {children} ); } -export default NotificationContextProvider; +export const useNotificationStore = ( + selector: (store: NotificationStore) => T, +): T => { + const notificationContext = React.useContext(NotificationContext); + if (!notificationContext) { + throw new Error( + 'useNotificationStore must be used within a NotificationContextProvider', + ); + } + return useStore(notificationContext, selector); +}; diff --git a/src/fonts/NanumSquareNeo-Variable.woff2 b/src/fonts/NanumSquareNeo-Variable.woff2 new file mode 100644 index 0000000..62f4bae Binary files /dev/null and b/src/fonts/NanumSquareNeo-Variable.woff2 differ diff --git a/src/store/notification-store.ts b/src/store/notification-store.ts index af0cf97..22e21e6 100644 --- a/src/store/notification-store.ts +++ b/src/store/notification-store.ts @@ -2,16 +2,17 @@ import { createStore } from 'zustand'; import { z } from 'zod'; export type Notification = { + id: string; message: string; type: 'default' | 'error' | 'success'; }; export type NotificationState = { - notifications: Map; + notifications: Notification[]; }; export type NotificationAction = { - add: (notification: Notification, id: string) => void; + add: (notification: Notification) => void; remove: (id: string) => void; }; @@ -36,7 +37,7 @@ export const NotificationStateSchema = z.map( ); export const defaultInitState = { - notifications: new Map(), + notifications: [], } as NotificationState; export type NotificationStore = NotificationState & NotificationAction; @@ -46,15 +47,18 @@ export const createNotificationStore = ( ) => { return createStore((set) => ({ ...initState, - add: (notification: Notification, id: string) => { + add: (notification: Notification) => { set((state) => { - state.notifications.set(id, notification); + if (state.notifications.find((n) => n.id === notification.id)) { + return state; + } + state.notifications.push(notification); return state; }); }, remove: (id: string) => { set((state) => { - state.notifications.delete(id); + state.notifications = state.notifications.filter((n) => n.id !== id); return state; }); }, diff --git a/tailwind.config.mjs b/tailwind.config.mjs index 13fe1de..cc4c456 100644 --- a/tailwind.config.mjs +++ b/tailwind.config.mjs @@ -66,6 +66,50 @@ export default { 1: '1px', 2: '2px', }, + colors: { + border: 'oklch(42.44% 0.011 17.58)', + input: { + DEFAULT: 'oklch(65.57% 0.19552898037793698 288.17775174927874)', + invalid: 'oklch(73.96% 0.1963 25.278467161119735)', + }, + ring: { + DEFAULT: 'oklch(86.83% 0.06751643147886291 285.8383540015746)', + invalid: 'oklch(64.17% 0.221 26.06)', + }, + background: 'var(--color-background)', + foreground: { + DEFAULT: 'oklch(76.7% 0.123 284.14)', + destructive: 'oklch(64.17% 0.221 26.06)', + }, + primary: { + DEFAULT: 'var(--primary)', + foreground: 'var(--primary-foreground)', + }, + secondary: { + DEFAULT: 'var(--secondary)', + foreground: 'var(--secondary-foreground)', + }, + destructive: { + DEFAULT: 'var(--destructive)', + foreground: 'var(--destructive-foreground)', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + accent: { + DEFAULT: 'var(--accent)', + foreground: 'var(--color-background)', + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))', + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + }, }, }, plugins: [],