From 067ef6708c3e29dcd141950b572d9b5d1d750db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=EB=AF=BC=EC=A0=95?= Date: Thu, 29 May 2025 22:47:37 +0900 Subject: [PATCH 01/25] =?UTF-8?q?:lipstick:=20support=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20banner=20UI=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/support/ui/banner.tsx | 61 +++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/pages/support/ui/banner.tsx diff --git a/src/pages/support/ui/banner.tsx b/src/pages/support/ui/banner.tsx new file mode 100644 index 0000000..6729a97 --- /dev/null +++ b/src/pages/support/ui/banner.tsx @@ -0,0 +1,61 @@ +import { Divider } from '@heroui/react'; +import { CheckCircle } from 'lucide-react'; + +import { Logo } from '@/shared/ui'; + +const BANNER_OPTION = [ + '활용 사례 상담', + '주요 기능 살펴보기', + '맞춤 견적 받기', +]; + +const Banner = () => { + return ( +
+
+

+ 지금 영업팀에 +
+ 문의하세요. +

+

+ CAMUS가 앱 내외에서 제공하는 더욱 혁신적인{' '} +
커뮤니케이션을 확인해보세요. +

+
+
    + {BANNER_OPTION.map((item) => ( +
  • + + {item} +
  • + ))} +
+
+
+ +
+
+

+ CAMUS와 함께할 파트너를 기다리고 있어요 +

+

+ 귀사의 브랜드가 이 자리에 함께하게 될 수 있습니다. +

+
+
+ {[true, false].map((isBusiness) => ( +
+ +
+ ))} +
+
+
+ ); +}; + +export default Banner; From e11f175ae88c1fc08ca7e7e04b077b461bb18470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=EB=AF=BC=EC=A0=95?= Date: Thu, 29 May 2025 22:47:49 +0900 Subject: [PATCH 02/25] =?UTF-8?q?:lipstick:=20support=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20input=20UI=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/support/ui/input-company.tsx | 39 +++++++++++++++++++++++ src/pages/support/ui/input-email.tsx | 44 ++++++++++++++++++++++++++ src/pages/support/ui/input-name.tsx | 39 +++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 src/pages/support/ui/input-company.tsx create mode 100644 src/pages/support/ui/input-email.tsx create mode 100644 src/pages/support/ui/input-name.tsx diff --git a/src/pages/support/ui/input-company.tsx b/src/pages/support/ui/input-company.tsx new file mode 100644 index 0000000..e535bc9 --- /dev/null +++ b/src/pages/support/ui/input-company.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { type ChangeEvent } from 'react'; + +import { useAssistDataStore } from '@/pages/support/store/assist-data'; +import { Input } from '@/shared/ui'; + +const InputCompany = () => { + const company = useAssistDataStore((state) => state.company); + const setCompany = useAssistDataStore((state) => state.setCompany); + const errorMessage = useAssistDataStore((state) => state.companyError); + const setErrorMessage = useAssistDataStore((state) => state.setCompanyError); + + const handleCompanyChange = (e: ChangeEvent) => { + const { value } = e.target; + setCompany(value); + if (!value) { + setErrorMessage('Enter your company.'); + return; + } + if (errorMessage) { + setErrorMessage(''); + } + }; + + return ( + + ); +}; + +export default InputCompany; diff --git a/src/pages/support/ui/input-email.tsx b/src/pages/support/ui/input-email.tsx new file mode 100644 index 0000000..920a881 --- /dev/null +++ b/src/pages/support/ui/input-email.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { type ChangeEvent } from 'react'; + +import { useAssistDataStore } from '@/pages/support/store/assist-data'; +import { EMAIL_REGEX } from '@/shared/config'; +import { Input } from '@/shared/ui'; + +const InputEmail = () => { + const email = useAssistDataStore((state) => state.email); + const setEmail = useAssistDataStore((state) => state.setEmail); + const errorMessage = useAssistDataStore((state) => state.emailError); + const setErrorMessage = useAssistDataStore((state) => state.setEmailError); + + const handleEmailChange = (e: ChangeEvent) => { + const { value } = e.target; + setEmail(value); + if (!value) { + setErrorMessage('Enter your email address.'); + return; + } + if (!EMAIL_REGEX.test(value)) { + setErrorMessage('Enter a valid email address.'); + return; + } + if (errorMessage) { + setErrorMessage(''); + } + }; + + return ( + + ); +}; + +export default InputEmail; diff --git a/src/pages/support/ui/input-name.tsx b/src/pages/support/ui/input-name.tsx new file mode 100644 index 0000000..f2b19f9 --- /dev/null +++ b/src/pages/support/ui/input-name.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { type ChangeEvent } from 'react'; + +import { useAssistDataStore } from '@/pages/support/store/assist-data'; +import { Input } from '@/shared/ui'; + +const InputName = () => { + const name = useAssistDataStore((state) => state.name); + const setName = useAssistDataStore((state) => state.setName); + const errorMessage = useAssistDataStore((state) => state.nameError); + const setErrorMessage = useAssistDataStore((state) => state.setNameError); + + const handleNameChange = (e: ChangeEvent) => { + const { value } = e.target; + setName(value); + if (!value) { + setErrorMessage('Enter your name.'); + return; + } + if (errorMessage) { + setErrorMessage(''); + } + }; + + return ( + + ); +}; + +export default InputName; From fe1c219c14f11c0d0ef755e1944ce4650a1db4c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=EB=AF=BC=EC=A0=95?= Date: Thu, 29 May 2025 22:48:06 +0900 Subject: [PATCH 03/25] =?UTF-8?q?:lipstick:=20support=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20checkbox-agreement=20UI=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/support/ui/agreement.tsx | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/pages/support/ui/agreement.tsx diff --git a/src/pages/support/ui/agreement.tsx b/src/pages/support/ui/agreement.tsx new file mode 100644 index 0000000..ceeca86 --- /dev/null +++ b/src/pages/support/ui/agreement.tsx @@ -0,0 +1,35 @@ +'use client'; + +import { Checkbox } from '@heroui/react'; +import Link from 'next/link'; + +import { useAssistDataStore } from '@/pages/support/store/assist-data'; + +const Agreement = () => { + const isAgreed = useAssistDataStore((state) => state.isAgreed); + const setIsAgreed = useAssistDataStore((state) => state.setIsAgreed); + + return ( + +

+ {"I agree to CAMUS's "} + + Terms of Service + + {' and '} + + Privacy Policy + + { + ' which includes my consent to receive marketing information from CAMUS. I can unsubscribe from marketing communications at any time.' + } +

+
+ ); +}; + +export default Agreement; From c7c6dd188b617456484f89e399da3446a3e5c396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=EB=AF=BC=EC=A0=95?= Date: Thu, 29 May 2025 22:48:20 +0900 Subject: [PATCH 04/25] =?UTF-8?q?:lipstick:=20support=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20request-button=20UI=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/support/ui/request-button.tsx | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/pages/support/ui/request-button.tsx diff --git a/src/pages/support/ui/request-button.tsx b/src/pages/support/ui/request-button.tsx new file mode 100644 index 0000000..945b88b --- /dev/null +++ b/src/pages/support/ui/request-button.tsx @@ -0,0 +1,37 @@ +'use client'; + +import { useCallback } from 'react'; + +import { useAssistDataStore } from '@/pages/support/store/assist-data'; +import { Button } from '@/shared/ui'; + +const RequestButton = () => { + const isInvalid = useAssistDataStore( + (state) => + Boolean(state.nameError) || + Boolean(state.emailError) || + Boolean(state.companyError) || + !state.name || + !state.email || + !state.company || + !state.isAgreed, + ); + + const handleClick = useCallback(async () => { + alert('성공적으로 제출되었습니다.'); + }, []); + + return ( + + ); +}; + +export default RequestButton; From 6fdf2dfd56ad3894cccb921fdb9bbfabe12aaf61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=EB=AF=BC=EC=A0=95?= Date: Thu, 29 May 2025 22:48:37 +0900 Subject: [PATCH 05/25] =?UTF-8?q?:lipstick:=20support=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=B0=B0=EA=B2=BD=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/images/background-support.svg | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 public/images/background-support.svg diff --git a/public/images/background-support.svg b/public/images/background-support.svg new file mode 100644 index 0000000..16d6741 --- /dev/null +++ b/public/images/background-support.svg @@ -0,0 +1,9 @@ + + + + + + + + + From 944251fd12acaf6313be99ef51063a0c6e6ebc0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=EB=AF=BC=EC=A0=95?= Date: Thu, 29 May 2025 22:49:58 +0900 Subject: [PATCH 06/25] =?UTF-8?q?:sparkles:=20support=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=A0=9C=EC=9E=91=20=EB=B0=8F=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#122?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/support/store/assist-data.ts | 40 ++++++++++++++++++++++++++ src/pages/support/ui/page.tsx | 30 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/pages/support/store/assist-data.ts create mode 100644 src/pages/support/ui/page.tsx diff --git a/src/pages/support/store/assist-data.ts b/src/pages/support/store/assist-data.ts new file mode 100644 index 0000000..fab64a7 --- /dev/null +++ b/src/pages/support/store/assist-data.ts @@ -0,0 +1,40 @@ +import { create } from 'zustand'; + +interface States { + email: string; + name: string; + company: string; + isAgreed: boolean; + emailError: string; + nameError: string; + companyError: string; +} + +interface Actions { + setEmail: (email: string) => void; + setName: (name: string) => void; + setCompany: (company: string) => void; + setIsAgreed: (isAgreed: boolean) => void; + + setEmailError: (emailError: string) => void; + setNameError: (nameError: string) => void; + setCompanyError: (companyError: string) => void; +} + +export const useAssistDataStore = create((set) => ({ + email: '', + name: '', + company: '', + isAgreed: false, + setEmail: (email) => set({ email }), + setName: (name) => set({ name }), + setCompany: (company) => set({ company }), + setIsAgreed: (isAgreed) => set({ isAgreed }), + + emailError: '', + nameError: '', + companyError: '', + setEmailError: (emailError) => set({ emailError }), + setNameError: (nameError) => set({ nameError }), + setCompanyError: (companyError) => set({ companyError }), +})); diff --git a/src/pages/support/ui/page.tsx b/src/pages/support/ui/page.tsx new file mode 100644 index 0000000..880203b --- /dev/null +++ b/src/pages/support/ui/page.tsx @@ -0,0 +1,30 @@ +import Agreement from '@/pages/support/ui/agreement'; +import Banner from '@/pages/support/ui/banner'; +import InputCompany from '@/pages/support/ui/input-company'; +import InputEmail from '@/pages/support/ui/input-email'; +import InputName from '@/pages/support/ui/input-name'; +import RequestButton from '@/pages/support/ui/request-button'; + +const SupportPage = () => { + return ( +
+ +
+
+ + + + + +
+
+
+ ); +}; + +export default SupportPage; From 4a1ec420f3d0edd2c3f9674c67e75a9d6abf5bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=EB=AF=BC=EC=A0=95?= Date: Thu, 29 May 2025 22:53:08 +0900 Subject: [PATCH 07/25] =?UTF-8?q?:sparkles:=20support=20page=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/support/assist/page.tsx | 2 +- app/support/request/page.tsx | 2 +- src/pages/support/index.tsx | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/pages/support/index.tsx diff --git a/app/support/assist/page.tsx b/app/support/assist/page.tsx index 60b1b0d..f7218de 100644 --- a/app/support/assist/page.tsx +++ b/app/support/assist/page.tsx @@ -1 +1 @@ -export { default } from '@/pages/coming-soon'; +export { default } from '@/pages/support'; diff --git a/app/support/request/page.tsx b/app/support/request/page.tsx index 60b1b0d..f7218de 100644 --- a/app/support/request/page.tsx +++ b/app/support/request/page.tsx @@ -1 +1 @@ -export { default } from '@/pages/coming-soon'; +export { default } from '@/pages/support'; diff --git a/src/pages/support/index.tsx b/src/pages/support/index.tsx new file mode 100644 index 0000000..0c0a866 --- /dev/null +++ b/src/pages/support/index.tsx @@ -0,0 +1 @@ +export { default } from './ui/page'; From 8b3e7cb48ec7d91de85c79bf630f8f3d892a8a4b Mon Sep 17 00:00:00 2001 From: nijesmik Date: Mon, 19 May 2025 21:08:06 +0900 Subject: [PATCH 08/25] =?UTF-8?q?:recycle:=20send=20message=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20props=EB=A5=BC=20=ED=86=B5=ED=95=B4=20?= =?UTF-8?q?=EB=B0=9B=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/chatting/index.ts | 1 + src/widgets/chatting/ui/chatting.tsx | 15 +++++- src/widgets/chatting/ui/input-message.tsx | 62 +++++++++++++---------- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/widgets/chatting/index.ts b/src/widgets/chatting/index.ts index 71d9545..0ec27f8 100644 --- a/src/widgets/chatting/index.ts +++ b/src/widgets/chatting/index.ts @@ -1,5 +1,6 @@ export { Chatting } from './ui/chatting'; export { ChattingHeader } from './ui/chatting-header'; export { ChattingTitle } from './ui/chatting-title'; +export { default as ChattingInput } from './ui/input-message'; export { useChattingStore } from './store'; diff --git a/src/widgets/chatting/ui/chatting.tsx b/src/widgets/chatting/ui/chatting.tsx index 4ac53fe..e50fd55 100644 --- a/src/widgets/chatting/ui/chatting.tsx +++ b/src/widgets/chatting/ui/chatting.tsx @@ -2,6 +2,8 @@ import { CardBody, CardFooter } from '@heroui/react'; +import { useWebsocketStore } from '@/features/websocket'; + import { useChattingStore } from '../store'; import InputMessage from './input-message'; import MessagesNew from './messages-new'; @@ -11,6 +13,8 @@ export const Chatting: FC<{ }> = ({ roomId }) => { const currentRoomId = useChattingStore((state) => state.currentRoomId) || roomId; + const client = useWebsocketStore((state) => state.client); + const isConnected = useWebsocketStore((state) => state.isConnected); if (!currentRoomId) { return ( @@ -20,13 +24,22 @@ export const Chatting: FC<{ ); } + const handleSendMessage = (value: string) => { + if (isConnected && !value) { + client.sendMessage(currentRoomId, value); + } + }; + return ( <> - + ); diff --git a/src/widgets/chatting/ui/input-message.tsx b/src/widgets/chatting/ui/input-message.tsx index bc0fb8f..e4de14a 100644 --- a/src/widgets/chatting/ui/input-message.tsx +++ b/src/widgets/chatting/ui/input-message.tsx @@ -2,31 +2,50 @@ import { Input } from '@heroui/react'; import { Send } from 'lucide-react'; -import { useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; -import { useWebsocketStore } from '@/features/websocket/store'; import { Button } from '@/shared/ui'; -const IconButton = ; +const sendIcon = ; -const InputMessage: FC<{ - roomId: string; -}> = ({ roomId }) => { - const client = useWebsocketStore((state) => state.client); - const isConnected = useWebsocketStore((state) => state.isConnected); +interface Props { + onSendMessage: (value: string) => void; + isDisabled?: boolean; +} + +const InputMessage = ({ onSendMessage, isDisabled }: Props) => { const [value, setValue] = useState(''); const hasValue = !!value.trim(); - const handleSendMessage = () => { - if (isConnected && hasValue) { - client.sendMessage(roomId, value); - setValue(''); - } - }; + const handleSendMessage = useCallback(() => { + setValue((prev) => { + const trimmed = prev.trim(); + if (!trimmed) { + return prev; + } + onSendMessage(trimmed); + return ''; + }); + }, []); + + const sendButton = useMemo(() => { + return ( + + ); + }, [hasValue]); return ( - {IconButton} - - } + endContent={sendButton} /> ); }; From 9c9fbbca8cdf0c51faae9702a56f3f9f994cbb32 Mon Sep 17 00:00:00 2001 From: nijesmik Date: Tue, 20 May 2025 02:43:25 +0900 Subject: [PATCH 09/25] =?UTF-8?q?:wrench:=20UI=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20tailwind=20c?= =?UTF-8?q?onfig=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tailwind.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tailwind.config.js b/tailwind.config.js index 72718f5..ccaf769 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,4 +1,4 @@ -import { heroui } from '@heroui/theme'; +import { heroui } from '@heroui/react'; /** @type {import('tailwindcss').Config} */ const config = { @@ -7,7 +7,7 @@ const config = { './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './src/**/*.{js,ts,jsx,tsx,mdx}', - './node_modules/@heroui/theme/dist/components/**/*.{js,ts,jsx,tsx,mdx}', + './node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}', ], theme: { extend: { From 9bcf63cab287b3c5b6d0ebda0385b5c5482da667 Mon Sep 17 00:00:00 2001 From: nijesmik Date: Tue, 20 May 2025 02:47:34 +0900 Subject: [PATCH 10/25] :sparkles: add `isFixed` prop to Header component for fixed positioning --- src/app/layouts/default.tsx | 2 +- src/widgets/header/ui/index.tsx | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/app/layouts/default.tsx b/src/app/layouts/default.tsx index aef412c..cabe721 100644 --- a/src/app/layouts/default.tsx +++ b/src/app/layouts/default.tsx @@ -11,7 +11,7 @@ interface Props { const Layout: FC = ({ children }) => { return ( -
+
{children}
); diff --git a/src/widgets/header/ui/index.tsx b/src/widgets/header/ui/index.tsx index 0eb7024..abdc0b5 100644 --- a/src/widgets/header/ui/index.tsx +++ b/src/widgets/header/ui/index.tsx @@ -12,22 +12,35 @@ import Login from './nav-login'; interface Props { business?: boolean; className?: string; + isFixed?: boolean; } -const wrapper = tv({ - base: 'wrapper flex h-14 items-center justify-between md:h-16', +const createStyle = tv({ + slots: { + base: 'w-full bg-background', + wrapper: 'wrapper flex h-14 items-center justify-between md:h-16', + }, + variants: { + isFixed: { + true: { + base: 'fixed z-10', + }, + }, + }, }); -const Header = async ({ business, className }: Props) => { +const Header = async ({ business, className, isFixed }: Props) => { const isBusiness = !!business; const navigationMenuItems = business ? NAVIGATIONS.business : NAVIGATIONS.personal; + const styles = createStyle({ isFixed }); + return ( -
+
-
+