-
+
{message.length}/{maxLength}자
diff --git a/src/components/Polaroid/PolaroidMaker/index.tsx b/src/components/Polaroid/PolaroidMaker/index.tsx
index 8115c7d..3a7d1bb 100644
--- a/src/components/Polaroid/PolaroidMaker/index.tsx
+++ b/src/components/Polaroid/PolaroidMaker/index.tsx
@@ -9,6 +9,7 @@ import imageCompression from 'browser-image-compression'
import PolaroidFrame from '@/components/Polaroid/Base/PolaroidFrame'
import PolaroidDescription from '@/components/Polaroid/Base/PolaroidDescription'
import { FontKeyType, ThemaKeyType } from '@/types'
+import { getPolaroidStyle } from '@/lib/utils/polaroid'
import PolaroidImageInput from './PolaroidImageInput'
import PolaroidMessageInput from './PolaroidMessageInput'
import PolaroidNicknameInput from './PolaroidNicknameInput'
@@ -30,7 +31,7 @@ interface PolaroidMakerProps {
themaKey: ThemaKeyType
}
-const MAX_MESSAGE_LENGTH = 20
+const MAX_MESSAGE_LENGTH = 30
const MAX_FROM_LENGTH = 10
const PolaroidMaker = ({
@@ -68,7 +69,7 @@ const PolaroidMaker = ({
}
}
- const handleMessageChange = (e: ChangeEvent) => {
+ const handleMessageChange = (e: ChangeEvent) => {
if (e.target.value.length > MAX_MESSAGE_LENGTH) {
e.target.value = e.target.value.slice(0, MAX_MESSAGE_LENGTH)
}
@@ -88,7 +89,7 @@ const PolaroidMaker = ({
fontKey={fontKey}
themaKey={themaKey}
>
-
+
{
+ const [isBoardAvailable, setIsBoardAvailable] = useState(true)
+
+ useEffect(() => {
+ const checkBoardAvailability = async () => {
+ const availableBoardCount = await getBoardAvailableCount()
+ setIsBoardAvailable(availableBoardCount > 0)
+ }
+
+ checkBoardAvailability()
+ }, [])
+
+ return isBoardAvailable
+}
diff --git a/src/hooks/useBoardName.ts b/src/hooks/useBoardName.ts
new file mode 100644
index 0000000..5daa445
--- /dev/null
+++ b/src/hooks/useBoardName.ts
@@ -0,0 +1,34 @@
+import { useMemo } from 'react'
+import { useInputValidation, Validation } from '@/hooks/useInputValidation'
+
+const MAX_BOARD_NAME_LENGTH = 15
+
+const validations: Validation[] = [
+ {
+ validator: (value: string) => value.length <= MAX_BOARD_NAME_LENGTH,
+ errorMessage: `${MAX_BOARD_NAME_LENGTH}자 이내로 입력 가능해요`,
+ },
+ {
+ validator: (value: string) => value.length > 0,
+ errorMessage: `최소 한글자 이상 입력해주세요`,
+ },
+]
+
+export const useBoardName = () => {
+ const { value, setValue, errorMessage, isDirty, isInvalid } =
+ useInputValidation('', validations)
+
+ const description = useMemo(
+ () => `${value.length}/${MAX_BOARD_NAME_LENGTH}자`,
+ [value],
+ )
+
+ return {
+ boardName: value,
+ setBoardName: setValue,
+ isDirty,
+ isInvalid,
+ errorMessage,
+ description,
+ }
+}
diff --git a/src/hooks/useInputValidation.ts b/src/hooks/useInputValidation.ts
new file mode 100644
index 0000000..f1db184
--- /dev/null
+++ b/src/hooks/useInputValidation.ts
@@ -0,0 +1,45 @@
+import { useCallback, useEffect, useState } from 'react'
+
+export type Validation = {
+ validator: (value: T) => boolean
+ errorMessage: string
+}
+
+export const useInputValidation = (
+ initialValue: T,
+ validations: Validation[] = [],
+) => {
+ const [value, setValue] = useState(initialValue)
+ const [isDirty, setIsDirty] = useState(false)
+ const [errorMessage, setErrorMessage] = useState('')
+
+ useEffect(() => {
+ const failedValidation = validations.find(
+ (validation) => !validation.validator(value),
+ )
+
+ if (failedValidation) {
+ setErrorMessage(failedValidation.errorMessage)
+ } else {
+ setErrorMessage('')
+ }
+ }, [value, validations])
+
+ const wrappedSetValue = useCallback(
+ (newValue: T) => {
+ if (!isDirty && value !== newValue) {
+ setIsDirty(true)
+ }
+ setValue(newValue)
+ },
+ [isDirty, value],
+ )
+
+ return {
+ value,
+ setValue: wrappedSetValue,
+ errorMessage,
+ isDirty,
+ isInvalid: !!errorMessage,
+ }
+}
diff --git a/src/lib/constants/boardConfig.ts b/src/lib/constants/boardConfig.ts
new file mode 100644
index 0000000..392a21e
--- /dev/null
+++ b/src/lib/constants/boardConfig.ts
@@ -0,0 +1,14 @@
+export const BOARDTHEMAS = {
+ 'B-0': {
+ title: '기본 테마',
+ },
+ 'B-1': {
+ title: '밝은 테마',
+ },
+ 'B-2': {
+ title: '수능 테마',
+ },
+ 'B-3': {
+ title: '코르크판 테마',
+ },
+}
diff --git a/src/lib/constants/polaroidConfig.ts b/src/lib/constants/polaroidConfig.ts
index 8e24871..33a9948 100644
--- a/src/lib/constants/polaroidConfig.ts
+++ b/src/lib/constants/polaroidConfig.ts
@@ -53,6 +53,21 @@ export const THEMAS: Record = {
descriptionStyle:
'linear-gradient(180deg, rgba(255, 255, 255, 0.20) 10.71%, rgba(255, 255, 255, 0.50) 57.96%, rgba(255, 255, 255, 0.00) 100%), #e6daff',
},
+ 'F-9': {
+ className: 'bg-[#FFFEF2]',
+ descriptionStyle:
+ 'linear-gradient(180deg, rgba(255, 255, 255, 0.20) 10.71%, rgba(255, 255, 255, 0.50) 57.96%, rgba(255, 255, 255, 0.00) 100%), linear-gradient(180deg, #FAF0FE 0%, #F1CFFF 100%)',
+ },
+ 'F-10': {
+ className: 'bg-[#FFFEF2]',
+ descriptionStyle:
+ 'linear-gradient(180deg, rgba(255, 255, 255, 0.20) 10.71%, rgba(255, 255, 255, 0.50) 57.96%, rgba(255, 255, 255, 0.00) 100%), linear-gradient(180deg, #FFF9EA 0%, #FFDE83 100%)',
+ },
+ 'F-11': {
+ className: 'bg-[#FFFEF2]',
+ descriptionStyle:
+ 'linear-gradient(180deg, rgba(255, 255, 255, 0.20) 10.71%, rgba(255, 255, 255, 0.50) 57.96%, rgba(255, 255, 255, 0.00) 100%), linear-gradient(180deg, #FFE7F0 0%, #FFAECD 100%)',
+ },
} as const
export const FONTS: Record = {
diff --git a/src/lib/utils/clipboard.ts b/src/lib/utils/clipboard.ts
new file mode 100644
index 0000000..658ef97
--- /dev/null
+++ b/src/lib/utils/clipboard.ts
@@ -0,0 +1,3 @@
+export const copyToClipboard = (target: string): Promise => {
+ return navigator.clipboard.writeText(target)
+}
diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts
new file mode 100644
index 0000000..57c4f83
--- /dev/null
+++ b/src/lib/utils/index.ts
@@ -0,0 +1 @@
+export * from './clipboard'
diff --git a/src/lib/utils/keyboard.ts b/src/lib/utils/keyboard.ts
index 0603e93..0a13b0b 100644
--- a/src/lib/utils/keyboard.ts
+++ b/src/lib/utils/keyboard.ts
@@ -1,6 +1,8 @@
import { KeyboardEvent } from 'react'
-export const preventKeyboardSubmit = (e: KeyboardEvent) => {
+export const preventKeyboardSubmit = (
+ e: KeyboardEvent | KeyboardEvent,
+) => {
if (e.key === 'Enter') {
e.preventDefault()
}
diff --git a/src/lib/utils/polaroid.ts b/src/lib/utils/polaroid.ts
index 5d7615a..02fca3d 100644
--- a/src/lib/utils/polaroid.ts
+++ b/src/lib/utils/polaroid.ts
@@ -1,6 +1,22 @@
import { Session } from 'next-auth'
+import { ThemaKeyType } from '@/types'
export const getPolaroidNickname = (
nickname: string,
session: Session | null,
) => nickname || session?.profile?.nickName || '익명'
+
+const isPolaroidWithFrame = (themaKey: ThemaKeyType) => {
+ const polaroidWithFrame: ThemaKeyType[] = ['F-9', 'F-10', 'F-11']
+
+ return polaroidWithFrame.includes(themaKey)
+}
+
+export const getPolaroidStyle = (themaKey: ThemaKeyType) => {
+ return isPolaroidWithFrame(themaKey)
+ ? {
+ backgroundImage: `url('/images/polaroidFrames/${themaKey}.svg')`,
+ backgroundSize: 'cover',
+ }
+ : {}
+}
diff --git a/src/types/board.ts b/src/types/board.ts
index 3142507..0999936 100644
--- a/src/types/board.ts
+++ b/src/types/board.ts
@@ -4,9 +4,17 @@ export interface Board {
title: string
items: Polaroid[]
mine: boolean
+ options: {
+ THEMA: BoardThemaKeyType
+ }
}
export interface CreateBoardPayload {
title: string
userId: string | null
+ options: {
+ THEMA: BoardThemaKeyType
+ }
}
+
+export type BoardThemaKeyType = 'B-0' | 'B-1' | 'B-2' | 'B-3'
diff --git a/src/types/polaroid.ts b/src/types/polaroid.ts
index 382210c..091c4b4 100644
--- a/src/types/polaroid.ts
+++ b/src/types/polaroid.ts
@@ -37,6 +37,9 @@ export type ThemaKeyType =
| 'F-6'
| 'F-7'
| 'F-8'
+ | 'F-9'
+ | 'F-10'
+ | 'F-11'
export interface ThemaType {
className: string
diff --git a/tsconfig.json b/tsconfig.json
index 6c52f5f..78b47a7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -21,7 +21,7 @@
"@/*": ["./src/*"],
"public/*": ["./public/*"]
},
- "types": ["node"]
+ "types": ["node", "jest"]
},
"include": [
"next-env.d.ts",