diff --git a/api/api.ts b/api/api.ts index 19b88e21b..3c87182ea 100644 --- a/api/api.ts +++ b/api/api.ts @@ -1,5 +1,5 @@ import { USER_ID, totalFolderId } from '@/util/constants'; -import { Folder, FolderAPI, LinkTypes, LinkAPITypes, User } from '@/types/types'; +import type { Folder, FolderAPI, LinkTypes, LinkAPITypes, User } from '@/types/types'; const BASE_URL = 'https://bootcamp-api.codeit.kr/api'; diff --git a/components/common/FolderList.tsx b/components/common/FolderList.tsx index 5e3632bff..2ac2748aa 100644 --- a/components/common/FolderList.tsx +++ b/components/common/FolderList.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components'; import LinkCard from './LinkCard'; import { useContext, useEffect, useState } from 'react'; -import { LinkTypes } from '@/types/types'; +import type { LinkTypes } from '@/types/types'; import { LinkListContext } from '@/context/createContext.'; const List = styled.ul` diff --git a/components/common/LinkCard.tsx b/components/common/LinkCard.tsx index 333f12f33..6107a7046 100644 --- a/components/common/LinkCard.tsx +++ b/components/common/LinkCard.tsx @@ -2,7 +2,7 @@ import styled from 'styled-components'; import LinkImage, { ImageCard } from './LinkImage'; import LinkInfo, { InfoGroup } from './LinkInfo'; import Link from 'next/link'; -import { LinkTypes } from '@/types/types'; +import type { LinkTypes } from '@/types/types'; const LinkItem = styled.li` width: calc(100% / 3 - 4rem / 3); diff --git a/components/common/LinkInfo.tsx b/components/common/LinkInfo.tsx index 7fa862fdd..4704cc156 100644 --- a/components/common/LinkInfo.tsx +++ b/components/common/LinkInfo.tsx @@ -3,7 +3,7 @@ import { format, formatDistanceToNowStrict } from 'date-fns'; import { useState, MouseEvent } from 'react'; import { modalTypes } from '../../util/constants'; import Popover from './Popover'; -import { LinkTypes } from '@/types/types'; +import type { LinkTypes } from '@/types/types'; export const InfoGroup = styled.div` display: flex; diff --git a/components/common/Popover.tsx b/components/common/Popover.tsx index cb2d01a81..6fa72fb99 100644 --- a/components/common/Popover.tsx +++ b/components/common/Popover.tsx @@ -1,9 +1,10 @@ -import { useState, MouseEvent } from 'react'; +import { MouseEvent } from 'react'; import { createPortal } from 'react-dom'; import { modalTypes } from '@/util/constants'; import styles from './Popover.module.css'; import DeleteLink from './modal/DeleteLink'; import Add from './modal/Add'; +import { useOpenModal } from '@/hooks/modal'; interface PopoverProps { url: string; @@ -12,42 +13,27 @@ interface PopoverProps { } const Popover = ({ url, show, onPopoverClick }: PopoverProps) => { - const [showDeleteModal, setShowDeleteModal] = useState(false); - const [showAddModal, setShowAddModal] = useState(false); - - const handleShowDeleteModal = () => { - setShowDeleteModal(true); - }; - - const handleShowAddModal = () => { - setShowAddModal(true); - }; - - const handleCloseDeleteModal = () => { - setShowDeleteModal(false); - }; - - const handleCloseAddModal = () => { - setShowAddModal(false); - }; + const { + isOpenModal: isOpenDeleteModal, + openModal: openDeleteModal, + closeModal: closeDeleteModal, + } = useOpenModal(false); + const { isOpenModal: isOpenAddModal, openModal: openAddModal, closeModal: closeAddModal } = useOpenModal(false); return ( <> {show && (
- -
)} - {showDeleteModal && createPortal(, document.body)} - {showAddModal && createPortal(, document.body)} + {isOpenDeleteModal && createPortal(, document.body)} + {isOpenAddModal && createPortal(, document.body)} ); }; diff --git a/components/common/modal/Add.tsx b/components/common/modal/Add.tsx index 0bd742fa0..8f2260a2e 100644 --- a/components/common/modal/Add.tsx +++ b/components/common/modal/Add.tsx @@ -4,7 +4,7 @@ import Title from './title/Title'; import SubmitButton from './submitButton/SubmitButton'; import styles from './Add.module.css'; import { FoldersContext } from '@/context/createContext.'; -import { Modal } from '@/types/types'; +import type { Modal } from '@/types/types'; interface AddProps { link: string; diff --git a/components/common/modal/AddFolder.tsx b/components/common/modal/AddFolder.tsx index 975871f26..f351a752a 100644 --- a/components/common/modal/AddFolder.tsx +++ b/components/common/modal/AddFolder.tsx @@ -1,8 +1,8 @@ -import { Modal } from '@/types/types'; import FolderNameInput from './folderNameInput/folderNameInput'; import Frame from './frame/Frame'; import SubmitButton from './submitButton/SubmitButton'; import Title from './title/Title'; +import type { Modal } from '@/types/types'; interface AddFolderProps { onCloseModal: Modal['closeModal']; diff --git a/components/common/modal/DeleteFolder.tsx b/components/common/modal/DeleteFolder.tsx index 998370586..5eeb6794a 100644 --- a/components/common/modal/DeleteFolder.tsx +++ b/components/common/modal/DeleteFolder.tsx @@ -1,7 +1,7 @@ import Title from './title/Title'; import Frame from './frame/Frame'; import SubmitButton from './submitButton/SubmitButton'; -import { Modal } from '@/types/types'; +import type { Modal } from '@/types/types'; interface DeleteFolder { selectedFolderName: string; diff --git a/components/common/modal/DeleteLink.tsx b/components/common/modal/DeleteLink.tsx index 31ba80d20..22c1f429b 100644 --- a/components/common/modal/DeleteLink.tsx +++ b/components/common/modal/DeleteLink.tsx @@ -1,4 +1,4 @@ -import { Modal } from '@/types/types'; +import type { Modal } from '@/types/types'; import Frame from './frame/Frame'; import SubmitButton from './submitButton/SubmitButton'; import Title from './title/Title'; diff --git a/components/common/modal/Edit.tsx b/components/common/modal/Edit.tsx index 3e4a19162..34105507d 100644 --- a/components/common/modal/Edit.tsx +++ b/components/common/modal/Edit.tsx @@ -1,4 +1,4 @@ -import { Modal } from '@/types/types'; +import type { Modal } from '@/types/types'; import FolderNameInput from './folderNameInput/folderNameInput'; import Frame from './frame/Frame'; import SubmitButton from './submitButton/SubmitButton'; diff --git a/components/common/modal/Share.tsx b/components/common/modal/Share.tsx index 76b7cc193..0a0ee48fd 100644 --- a/components/common/modal/Share.tsx +++ b/components/common/modal/Share.tsx @@ -6,7 +6,7 @@ import styles from './Share.module.css'; import ShareBtn from './shareBtn/ShareBtn'; import { sampleUrl } from '@/util/constants'; import { useKaKaoScript } from '@/hooks/kakao'; -import { Modal } from '@/types/types'; +import type { Modal } from '@/types/types'; interface ShareProps { currentId: string; diff --git a/components/common/modal/frame/Frame.tsx b/components/common/modal/frame/Frame.tsx index 55ec6b83a..fd7ef508f 100644 --- a/components/common/modal/frame/Frame.tsx +++ b/components/common/modal/frame/Frame.tsx @@ -1,7 +1,7 @@ import { MouseEvent, PropsWithChildren, ReactNode } from 'react'; import styles from './Frame.module.css'; import { exitBtnId, modalBackground } from '@/util/constants'; -import { Modal } from '@/types/types'; +import type { Modal } from '@/types/types'; interface FrameProps { onCloseModal: Modal['closeModal']; diff --git a/components/layout/Layout.tsx b/components/layout/Layout.tsx index 617016e4e..d11f46abc 100644 --- a/components/layout/Layout.tsx +++ b/components/layout/Layout.tsx @@ -1,8 +1,15 @@ import Header from './header/Header'; import Footer from './footer/Footer'; import { PropsWithChildren } from 'react'; +import { useRouter } from 'next/router'; function Layout({ children }: PropsWithChildren) { + const { pathname } = useRouter(); + + if (pathname === '/signin' || pathname === '/signup') { + return <>{children}; + } + return ( <>
diff --git a/components/layout/footer/IconWithLink.tsx b/components/layout/footer/IconWithLink.tsx index 64e95c918..360f891f5 100644 --- a/components/layout/footer/IconWithLink.tsx +++ b/components/layout/footer/IconWithLink.tsx @@ -1,4 +1,4 @@ -import { FooterItem } from '@/types/types'; +import type { FooterItem } from '@/types/types'; import styles from './IconWithLink.module.css'; import Link from 'next/link'; import Image from 'next/image'; diff --git a/components/layout/header/Header.tsx b/components/layout/header/Header.tsx index 4c9250766..4980e984c 100644 --- a/components/layout/header/Header.tsx +++ b/components/layout/header/Header.tsx @@ -2,7 +2,7 @@ import Link from 'next/link'; import styles from './Header.module.css'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/router'; -import { User } from '@/types/types'; +import type { User } from '@/types/types'; import { getUser } from '@/api/api'; import Image from 'next/image'; @@ -38,7 +38,7 @@ function Header() { {user.email} ) : ( - + 로그인 )} diff --git a/components/pages/folder/LinkList.tsx b/components/pages/folder/LinkList.tsx index 554b70c40..a2a53df34 100644 --- a/components/pages/folder/LinkList.tsx +++ b/components/pages/folder/LinkList.tsx @@ -1,9 +1,9 @@ import styled from 'styled-components'; -import { useContext, useEffect, useState } from 'react'; +import { useContext } from 'react'; import UpdateBtnList from './UpdateBtnList'; -import { Folder, LinkTypes } from '@/types/types'; +import type { Folder } from '@/types/types'; import { totalFolderId, totalFolderName } from '@/util/constants'; -import { FoldersContext, LinkListContext } from '@/context/createContext.'; +import { FoldersContext } from '@/context/createContext.'; import FolderList from '@/components/common/FolderList'; import { useRouter } from 'next/router'; diff --git a/components/pages/sign/InputGroup.tsx b/components/pages/sign/InputGroup.tsx new file mode 100644 index 000000000..92fa00455 --- /dev/null +++ b/components/pages/sign/InputGroup.tsx @@ -0,0 +1,38 @@ +import { useFormContext } from 'react-hook-form'; +import styles from '@/styles/sign.module.css'; +import { useState } from 'react'; + +const InputGroup = ({ info }: any) => { + const { + register, + trigger, + formState: { errors }, + } = useFormContext(); + const [showText, setShowText] = useState(() => (info.id !== 'email' ? false : true)); + const type = info.id === 'email' ? info.type : showText ? 'text' : info.type; + const eyeClassName = `${styles.iconEye} ${showText ? styles.on : ''}`; + const inputClassName = `${styles.input} ${errors[info.id]?.message ? styles.error : ''}`; + + return ( + <> + +
+ await trigger(info.id) })} + placeholder={info.placeholder} + className={inputClassName} + /> + {info.type === 'password' && ( +
+ {errors?.[info.id] &&

{errors[info.id]?.message as string}

} + + ); +}; + +export default InputGroup; diff --git a/constants/footerItems.ts b/constants/footerItems.ts index 90b5ebbe8..c1d8b0266 100644 --- a/constants/footerItems.ts +++ b/constants/footerItems.ts @@ -1,4 +1,4 @@ -import { FooterItem } from "@/types/types"; +import type { FooterItem } from "@/types/types"; export const SNS_ITEMS: FooterItem[] = [ { diff --git a/constants/sign.ts b/constants/sign.ts new file mode 100644 index 000000000..68dfbb7ca --- /dev/null +++ b/constants/sign.ts @@ -0,0 +1,69 @@ +export const ERROR_MESSAGE = { + email: { + pattern: '올바른 이메일 주소가 아닙니다.', + required: '이메일을 입력해 주세요.', + checkRight: '이메일을 확인해 주세요.', + }, + password: { + pattern: '비밀번호는 영문, 숫자 조합 8자 이상 입력해 주세요.', + required: '비밀번호를 입력해 주세요', + checkSame: '비밀번호가 일치하지 않아요', + checkRight: '비밀번호를 확인해 주세요.', + }, +}; + +export const INPUT_INFO = { + email: { + id: 'email', + type: 'email', + label: '이메일', + placeholder: '이메일를 입력해 주세요.', + validation: { + pattern: { + value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, + message: ERROR_MESSAGE.email.pattern, + }, + required: ERROR_MESSAGE.email.required, + }, + }, + password: { + signIn: { + id: 'password', + type: 'password', + label: '비밀번호', + placeholder: '비밀번호를 입력해 주세요.', + validation: { + required: ERROR_MESSAGE.password.required, + }, + }, + signUp: { + id: 'password', + type: 'password', + label: '비밀번호 ', + placeholder: '비밀번호를 입력해 주세요.', + validation: { + pattern: { + value: /^(?=.*[a-zA-Z])(?=.*[0-9]).{8,}$/, + message: ERROR_MESSAGE.password.pattern, + }, + required: ERROR_MESSAGE.password.required, + }, + }, + }, + passwordCheck: { + id: 'passwordCheck', + type: 'password', + label: '비밀번호 확인', + placeholder: '비밀번호와 일치하는 값을 입력해주세요.', + validation: { + validate: (value: any, formValues: any) => { + if (value.length === 0 && formValues.password.length === 0) return ERROR_MESSAGE.password.required; + return value !== formValues.password && ERROR_MESSAGE.password.checkSame; + }, + pattern: { + value: /^(?=.*[a-zA-Z])(?=.*[0-9]).{8,}$/, + message: ERROR_MESSAGE.password.pattern, + }, + }, + }, +}; diff --git a/context/createContext..tsx b/context/createContext..tsx index 2369eb83c..7d24ef58c 100644 --- a/context/createContext..tsx +++ b/context/createContext..tsx @@ -1,5 +1,5 @@ import { createContext } from 'react'; -import { Folder, LinkTypes } from '@/types/types'; +import type { Folder, LinkTypes } from '@/types/types'; export const FoldersContext = createContext([]); diff --git a/context/sampleFolderContext.tsx b/context/sampleFolderContext.tsx index 8e559a37e..938270ce6 100644 --- a/context/sampleFolderContext.tsx +++ b/context/sampleFolderContext.tsx @@ -1,6 +1,6 @@ import { PropsWithChildren, createContext, useEffect, useState } from 'react'; import { getSampleFolder } from '@/api/api'; -import { LinkTypes } from '@/types/types'; +import type { LinkTypes } from '@/types/types'; interface SampleFolderContext { folderName: string; diff --git a/package-lock.json b/package-lock.json index 920662371..bf01064d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "react": "^18", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18", + "react-hook-form": "^7.51.2", "react-router-dom": "^6.22.3", "styled-components": "^6.1.8", "uuid": "^9.0.1" @@ -3317,6 +3318,21 @@ "react": "^18.2.0" } }, + "node_modules/react-hook-form": { + "version": "7.51.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.2.tgz", + "integrity": "sha512-y++lwaWjtzDt/XNnyGDQy6goHskFualmDlf+jzEZvjvz6KWDf7EboL7pUvRCzPTJd0EOPpdekYaQLEvvG6m6HA==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 312ec13be..c2c18762d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "react": "^18", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18", + "react-hook-form": "^7.51.2", "react-router-dom": "^6.22.3", "styled-components": "^6.1.8", "uuid": "^9.0.1" diff --git a/pages/folder/[id].tsx b/pages/folder/[id].tsx index ea2f46f71..a8a746b37 100644 --- a/pages/folder/[id].tsx +++ b/pages/folder/[id].tsx @@ -2,24 +2,26 @@ import { getUserFolders, getUserLinks } from '@/api/api'; import AddLinkArea from '@/components/pages/folder/AddLinkArea'; import FolderSection from '@/components/pages/folder/FolderSection'; import { FoldersContext, LinkListContext } from '@/context/createContext.'; -import { Folder, LinkTypes } from '@/types/types'; +import type { Folder, LinkTypes } from '@/types/types'; +import { GetServerSideProps } from 'next'; -//? context의 타입은 뭔가요? -export async function getServerSideProps(context: any) { - const folderId = context.params['id']; - let folderList, links; +export const getServerSideProps: GetServerSideProps = async context => { + // TODO: id는 string, string[], undefined가 될 수 있다. + const { id } = context.query; + + if (!id) return { notFound: true }; try { - folderList = await getUserFolders(); - links = await getUserLinks(folderId); + const folderList = await getUserFolders(); + const links = await getUserLinks(id as string); + + return { props: { folderList, links } }; } catch { return { notFound: true, }; } - - return { props: { folderList, links } }; -} +}; const FolderPage = ({ folderList, links }: { folderList: Folder[]; links: LinkTypes[] }) => { return ( diff --git a/pages/folder/index.tsx b/pages/folder/index.tsx index 718ae7ea3..6ae05d90c 100644 --- a/pages/folder/index.tsx +++ b/pages/folder/index.tsx @@ -2,22 +2,20 @@ import { getUserFolders, getUserLinks } from '@/api/api'; import AddLinkArea from '@/components/pages/folder/AddLinkArea'; import FolderSection from '@/components/pages/folder/FolderSection'; import { FoldersContext, LinkListContext } from '@/context/createContext.'; -import { Folder, LinkTypes } from '@/types/types'; +import type { Folder, LinkTypes } from '@/types/types'; import { totalFolderId } from '@/util/constants'; export async function getServerSideProps() { - let folderList, links; - try { - folderList = await getUserFolders(); - links = await getUserLinks(totalFolderId); + const folderList = await getUserFolders(); + const links = await getUserLinks(totalFolderId); + + return { props: { folderList, links } }; } catch { return { notFound: true, }; } - - return { props: { folderList, links } }; } const FolderPage = ({ folderList, links }: { folderList: Folder[]; links: LinkTypes[] }) => { diff --git a/pages/shared.tsx b/pages/shared.tsx index 4a669cf8a..351116154 100644 --- a/pages/shared.tsx +++ b/pages/shared.tsx @@ -2,7 +2,7 @@ import UserFolderNameArea from '@/components/pages/shared/UserFolderNameArea'; import SharedSection from '@/components/pages/shared/SharedSection'; import { createContext, useEffect, useState } from 'react'; import { getSampleFolder } from '@/api/api'; -import { LinkTypes } from '@/types/types'; +import type { LinkTypes } from '@/types/types'; import { LinkListContext } from '@/context/createContext.'; export async function getStaticProps() { diff --git a/pages/signin.tsx b/pages/signin.tsx new file mode 100644 index 000000000..db8f693ce --- /dev/null +++ b/pages/signin.tsx @@ -0,0 +1,56 @@ +import InputGroup from '@/components/pages/sign/InputGroup'; +import { INPUT_INFO } from '@/constants/sign'; +import styles from '@/styles/sign.module.css'; +import Image from 'next/image'; +import Link from 'next/link'; +import { FormProvider, useForm } from 'react-hook-form'; + +export default function SignIn() { + const methods = useForm(); + + const onSubmit = (data: any) => { + console.log(data); + }; + + return ( +
+
+ + 로고 + +

+ 회원이 아니신가요? + + 회원 가입하기 + +

+
+ + +
+ + + + +
+ +
+ 소셜 로그인 +
    +
  • + + 구글로 로그인 + +
  • +
  • + + 카카오로 로그인 + +
  • +
+
+
+ ); +} diff --git a/pages/signup.tsx b/pages/signup.tsx new file mode 100644 index 000000000..d1f74c7fa --- /dev/null +++ b/pages/signup.tsx @@ -0,0 +1,58 @@ +import InputGroup from '@/components/pages/sign/InputGroup'; +import { INPUT_INFO } from '@/constants/sign'; +import styles from '@/styles/sign.module.css'; +import Image from 'next/image'; +import Link from 'next/link'; +import { FormProvider, useForm } from 'react-hook-form'; + +export default function SignIn() { + const methods = useForm(); + + const onSubmit = (data: any) => { + console.log(data); + }; + + return ( +
+
+ + 로고 + +

+ 이미 회원이신가요? + + 로그인 하기 + +

+
+ + +
+ + + + + + +
+ +
+ 다른 방식으로 가입하기 +
    +
  • + + 구글로 회원가입 + +
  • +
  • + + 카카오로 회원가입 + +
  • +
+
+
+ ); +} diff --git a/styles/sign.module.css b/styles/sign.module.css new file mode 100644 index 000000000..83677677f --- /dev/null +++ b/styles/sign.module.css @@ -0,0 +1,174 @@ +/* body { + background: var(--color-gray-200); +} */ + +.container { + margin: 0 3.2rem; +} + +.headerArea { + display: flex; + flex-direction: column; + align-items: center; + gap: 1.6rem; + margin: 23.8rem 0 3rem; +} + +.goToMain { + width: 21.058rem; + height: 3.8rem; +} + +.logo { + width: 100%; + height: 100%; +} + +.question { + display: flex; + align-items: flex-start; + gap: 0.8rem; + font-size: 1.6rem; + font-weight: 400; + line-height: 2.4rem; +} + +.link { + position: relative; + line-height: 1.1; + font-weight: 600; + color: var(--color-primary); +} + +.link::after { + content: ''; + position: absolute; + bottom: -0.3rem; + display: block; + width: 100%; + border-bottom: 0.1rem solid var(--color-primary); +} + +.formArea { + display: flex; + flex-direction: column; + margin: 0 auto 3.2rem; + max-width: 40rem; +} + +.label { + margin: 2.4rem 0 1.2rem; + line-height: 1.7rem; + font-size: 1.4rem; + font-weight: 400; +} + +.label:first-of-type { + margin-top: 0; +} + +.inputGroup { + position: relative; +} + +.input { + padding: 1.8rem 1.5rem; + width: 100%; + height: 6rem; + border: 0.1rem solid var(--color-gray-400); + border-radius: 0.8rem; + font-size: 1.6rem; + font-weight: 400; +} + +.input:focus { + outline: none; + border: 0.1rem solid var(--color-primary); +} + +.input.error { + border: 1px solid var(--color-red); +} + +.messageError { + margin-top: 0.6rem; + font-size: 1.4rem; + font-weight: 400; + color: var(--color-red); +} + +.hidden { + display: none; +} + +.iconEye { + position: absolute; + top: 50%; + right: 1.5rem; + width: 1.6rem; + height: 1.6rem; + transform: translateY(-50%); + background: url('../public/icons/eye-off.svg') no-repeat center bottom/contain; +} + +.iconEye.on { + background: url('../public/icons/eye-on.svg') no-repeat center bottom/contain; +} + +.button { + padding: 1.6rem 2rem; + border-radius: 0.8rem; + background: linear-gradient(91deg, #6d6afe 0.12%, #6ae3fe 101.84%); + line-height: 2.1rem; + font-size: 1.8rem; + font-weight: 600; + color: var(--color-gray-100); + text-align: center; + margin-top: 3rem; +} + +.snsButtonGroup { + display: flex; + justify-content: space-between; + align-items: center; + margin: 0 auto; + padding: 1.2rem 2.4rem; + max-width: 40rem; + height: 6.6rem; + border: 0.1rem solid var(--color-gray-400); + background: var(--color-gray-300); +} + +.snsButtonGroup .title { + flex-shrink: 0; + font-size: 1.4rem; + font-weight: 400; + color: #000; +} + +.buttonList { + display: flex; + gap: 1.6rem; +} + +.buttonItem { + display: flex; + justify-content: center; + align-items: center; + width: 4.2rem; + height: 4.2rem; + border-radius: 50%; +} + +.buttonItem.google { + border: 0.1rem solid #d3d4dd; + background: var(--color-white); +} + +.buttonItem.kakao { + background: var(--color-yellow); +} + +.buttonItem a { + display: flex; +}