diff --git a/public/assets/icons/ic_x.svg b/public/assets/icons/ic_x.svg new file mode 100644 index 00000000..19337c66 --- /dev/null +++ b/public/assets/icons/ic_x.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/api/image/image.tsx b/src/api/image/image.tsx index c19f84c1..52482927 100644 --- a/src/api/image/image.tsx +++ b/src/api/image/image.tsx @@ -1,12 +1,12 @@ -import { authClient } from "../client"; +import client from '../client'; export const postImage = async (imageFile: File) => { const formData = new FormData(); - formData.append("image", imageFile); + formData.append('image', imageFile); - return await authClient.post("/api/v1/images", formData, { + return await client.post('/api/v1/images', formData, { headers: { - "Content-Type": "multipart/form-data", - }, + 'Content-Type': 'multipart/form-data' + } }); }; diff --git a/src/api/letter/share.tsx b/src/api/letter/share.tsx index 351635fd..58f653cc 100644 --- a/src/api/letter/share.tsx +++ b/src/api/letter/share.tsx @@ -1,20 +1,20 @@ -import { authClient } from "../client"; +import client, { authClient } from '../client'; export const getLetterShareStatus = async ( letterCode: string ): Promise => { - const response = await authClient.get( + const response = await client.get( `/api/v1/letters/logs/share/status?letterCode=${letterCode}` ); return response.data; }; export type shareStatusType = - | "MEMO_CHAT" - | "DIRECT_CHAT" - | "MULTI_CHAT" - | "OPEN_DIRECT_CHAT" - | "OPEN_MULTI_CHAT"; + | 'MEMO_CHAT' + | 'DIRECT_CHAT' + | 'MULTI_CHAT' + | 'OPEN_DIRECT_CHAT' + | 'OPEN_MULTI_CHAT'; export type ShareStatusData = { isShared: boolean; diff --git a/src/api/login/user.tsx b/src/api/login/user.tsx index b8788c4e..685fed77 100644 --- a/src/api/login/user.tsx +++ b/src/api/login/user.tsx @@ -15,14 +15,16 @@ export const signup = async ({ servicePermission, privatePermission, marketingPermission, - realName + realName, + anonymousSendLetterCode }: RegisterDataType) => { return await client.post(`/api/v1/users`, { registerToken: registerToken, servicePermission: servicePermission, privatePermission: privatePermission, marketingPermission: marketingPermission, - realName: realName + realName: realName, + anonymousSendLetterCode: anonymousSendLetterCode }); }; diff --git a/src/api/send/send.tsx b/src/api/send/send.tsx index da3b3a0b..8e051c9d 100644 --- a/src/api/send/send.tsx +++ b/src/api/send/send.tsx @@ -1,7 +1,7 @@ -import { authClient } from '@/api/client'; +import client, { authClient } from '@/api/client'; // 편지 쓰기 -export const postSendLtter = async ({ +export const postSendLetter = async ({ receiverName, content, images, @@ -22,3 +22,23 @@ export const postSendLtter = async ({ draftId }); }; + +// 비회원 편지 쓰기 +export const postAnonymousSendLetter = async ({ + receiverName, + content, + images, + templateType +}: { + receiverName: string; + content: string; + images: string[]; + templateType: number; +}) => { + return await client.post(`/api/v1/letters/anonymous/send`, { + receiverName, + content, + images, + templateType + }); +}; diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 760717e3..72c777f2 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -3,16 +3,37 @@ import Loader, { LoaderContainer } from '@/components/common/Loader'; import OauthButton from '@/components/signup/OauthButton'; import { OAUTH } from '@/constants/oauth'; +import { sendLetterState } from '@/recoil/letterStore'; import { theme } from '@/styles/theme'; import { OAuthType } from '@/types/login'; -import { Suspense } from 'react'; +import { clearAnonymousSendLetterCode } from '@/utils/storage'; +import { useRouter } from 'next/navigation'; +import { Suspense, useEffect } from 'react'; +import { useRecoilState } from 'recoil'; import styled from 'styled-components'; -const notReady = () => { - alert('준비 중입니다.'); -}; - export default function Login() { + const router = useRouter(); + const [, setSendState] = useRecoilState(sendLetterState); + + /* 로그인 페이지에서 편지 쓰기 store 초기화 */ + useEffect(() => { + clearAnonymousSendLetterCode(); + setSendState({ + draftId: null, + receiverName: '', + content: '', + images: [] as string[], + previewImages: [] as string[], + templateType: 0, + letterId: null + }); + }, []); + + const handleGuestLetterStart = () => { + router.push('/send/receiver?guest=true'); + }; + return ( @@ -38,8 +59,8 @@ export default function Login() { ))} - - 로그인 없이 편지 작성해보기 + + 로그인 없이 편지 보내기 diff --git a/src/app/page.tsx b/src/app/page.tsx index 2864ffb4..6e09152e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,19 +1,10 @@ -"use client"; +'use client'; -import { getAllSpaceName, getNewTokens } from "@/api/login/user"; -import Button from "@/components/common/Button"; -import KakaoShareButton from "@/components/common/KakaoShareButton"; -import Loader from "@/components/common/Loader"; -import { - clearInitUserToast, - clearTokens, - getAccessToken, - getCookie, - setCookie, -} from "@/utils/storage"; -import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; -import styled from "styled-components"; +import Loader, { LoaderContainer } from '@/components/common/Loader'; +import { getAccessToken } from '@/utils/storage'; +import { useRouter } from 'next/navigation'; +import { useEffect } from 'react'; +import styled from 'styled-components'; export default function Home() { const router = useRouter(); @@ -21,22 +12,17 @@ export default function Home() { useEffect(() => { if (!accessToken) { - router.push("/login"); + router.push('/login'); } else { - router.push("/planet"); + router.push('/planet'); } }, []); return ( - {/* - - - */} + + + ); } @@ -49,19 +35,3 @@ const Container = styled.div` padding: 25px; background: ${(props) => props.theme.colors.bg}; `; - -const LoaderContainer = styled.div` - width: 100%; - height: 100%; - min-height: 600px; - display: flex; - align-items: center; - justify-content: center; -`; - -const ButtonContainer = styled.div` - width: 100%; - display: flex; - flex-direction: column; - gap: 10px; -`; diff --git a/src/app/send/(process)/content/page.tsx b/src/app/send/(process)/content/page.tsx index 486129c5..1672bf32 100644 --- a/src/app/send/(process)/content/page.tsx +++ b/src/app/send/(process)/content/page.tsx @@ -1,11 +1,11 @@ 'use client'; -import React, { useEffect, useState } from 'react'; +import React, { Suspense, useEffect, useState } from 'react'; import styled, { css } from 'styled-components'; import { theme } from '@/styles/theme'; import Input from '@/components/common/Input'; import Button from '@/components/common/Button'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import Image from 'next/image'; import { @@ -22,11 +22,12 @@ import { useToast } from '@/hooks/useToast'; import { postImage } from '@/api/image/image'; import ConfirmModal from '@/components/common/ConfirmModal'; import { draftModalState } from '@/recoil/draftStore'; -import imageCompression from 'browser-image-compression'; import DraftButton from '@/components/draft/DraftButton'; +import Loader, { LoaderContainer } from '@/components/common/Loader'; const SendContentPage = () => { const router = useRouter(); + const searchParams = useSearchParams(); const { showToast } = useToast(); const [draftId, setDraftId] = useState(null); const [receiver, setReceiver] = useState(''); @@ -39,6 +40,8 @@ const SendContentPage = () => { const [isImageUploadLoading, setImageUploadLoading] = useState(false); // 서버 이미지 업로드 상태 + const isGuest = searchParams.get('guest') === 'true'; + const [draftModal, setDraftModal] = useRecoilState(draftModalState); const [letterState, setLetterState] = useRecoilState(sendLetterState); const [tempCount, setTempCount] = useState(3); @@ -81,7 +84,7 @@ const SendContentPage = () => { } }; - fetchGetDraftCount(); + if (!isGuest) fetchGetDraftCount(); if (draftKey) { fetchGetDraft(); @@ -263,7 +266,8 @@ const SendContentPage = () => { images: images, previewImages: previewImages })); - router.push('/send/template'); + + router.push(`/send/template${isGuest ? '?guest=true' : ''}`); }; /* 임시 저장 삭제 핸들러 */ @@ -317,13 +321,15 @@ const SendContentPage = () => { return ( <> - + {!isGuest && ( + + )} @@ -420,7 +426,19 @@ const SendContentPage = () => { ); }; -export default SendContentPage; +export default function SendContentPaging() { + return ( + + + + } + > + + + ); +} const Container = styled.div` width: 100%; diff --git a/src/app/send/(process)/preview/page.tsx b/src/app/send/(process)/preview/page.tsx index a285be9d..b65c8920 100644 --- a/src/app/send/(process)/preview/page.tsx +++ b/src/app/send/(process)/preview/page.tsx @@ -1,21 +1,24 @@ 'use client'; -import React, { useEffect, useState } from 'react'; +import React, { Suspense, useEffect, useState } from 'react'; import styled from 'styled-components'; import { theme } from '@/styles/theme'; import Button from '@/components/common/Button'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import Image from 'next/image'; import Letter from '@/components/letter/Letter'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { postSendLtter } from '@/api/send/send'; +import { postAnonymousSendLetter, postSendLetter } from '@/api/send/send'; import { sendLetterState } from '@/recoil/letterStore'; import useKakaoSDK from '@/hooks/useKakaoSDK'; import { userState } from '@/recoil/userStore'; import { getLetterShareStatus } from '@/api/letter/share'; +import Loader, { LoaderContainer } from '@/components/common/Loader'; +import { setAnonymousSendLetterCode } from '@/utils/storage'; const SendPreviewPage = () => { const router = useRouter(); + const searchParams = useSearchParams(); const isKakaoLoaded = useKakaoSDK(); const [letterState, setLetterState] = useRecoilState(sendLetterState); const { draftId, receiverName, content, images, templateType, letterId } = @@ -28,6 +31,8 @@ const SendPreviewPage = () => { const [maxLinesPerPage, setMaxLinesPerPage] = useState(12); const [fontSize, setFontSize] = useState('16px'); + const isGuest = searchParams.get('guest') === 'true'; + useEffect(() => { setIsImage(!!!(content.length > 0)); }, []); @@ -76,33 +81,51 @@ const SendPreviewPage = () => { } try { + let letterCode = ''; // 1. 편지 전송 API 요청 - const response = await postSendLtter({ - draftId, - receiverName, - content, - images, - templateType - }); - console.log('편지 쓰기 성공'); - setLetterState((prevState) => ({ - ...prevState, - letterId: response.data.letterCode - })); - setLetterCode(response.data.letterCode); - console.log(response.data.letterCode); + if (isGuest) { + // 비회원 편지 저장 API 연동 + const response = await postAnonymousSendLetter({ + receiverName, + content, + images, + templateType + }); + setLetterState((prevState) => ({ + ...prevState, + letterId: response.data.letterCode + })); + letterCode = response.data.letterCode; + setLetterCode(response.data.letterCode); + setAnonymousSendLetterCode(response.data.letterCode); + } else { + const response = await postSendLetter({ + draftId, + receiverName, + content, + images, + templateType + }); + console.log('편지 쓰기 성공'); + setLetterState((prevState) => ({ + ...prevState, + letterId: response.data.letterCode + })); + letterCode = response.data.letterCode; + setLetterCode(response.data.letterCode); + } // 2. 카카오 공유 로직 실행 (letterId 상태와 무관하게 항상 실행) Kakao.Share.sendScrap({ requestUrl: location.origin + location.pathname, templateId: 112798, templateArgs: { - senderName: name, - id: response.data.letterCode + senderName: isGuest ? receiverName + ' 님께' : name + ' 님으로부터', + id: letterCode }, serverCallbackArgs: { requestType: 'SHARE', - requestId: response.data.letterCode + requestId: letterCode }, // 카카오톡 미설치 시 카카오톡 설치 경로이동 installTalk: true @@ -125,7 +148,7 @@ const SendPreviewPage = () => { console.log(status); if (status.isShared) { console.log('완료'); - router.push('/send/complete'); + router.push(`/send/complete${isGuest ? '?guest=true' : ''}`); clearInterval(interval); // 폴링 중단 } } catch (error) { @@ -196,7 +219,19 @@ const SendPreviewPage = () => { ); }; -export default SendPreviewPage; +export default function SendPreviewPaging() { + return ( + + + + } + > + + + ); +} const Container = styled.div` width: 100%; diff --git a/src/app/send/(process)/receiver/page.tsx b/src/app/send/(process)/receiver/page.tsx index 8ed92c89..ab54e8d5 100644 --- a/src/app/send/(process)/receiver/page.tsx +++ b/src/app/send/(process)/receiver/page.tsx @@ -1,11 +1,11 @@ 'use client'; -import React, { useEffect, useState } from 'react'; +import React, { Suspense, useEffect, useState } from 'react'; import styled, { css } from 'styled-components'; import { theme } from '@/styles/theme'; import Input from '@/components/common/Input'; import Button from '@/components/common/Button'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { deleteDraftLetter, @@ -23,9 +23,11 @@ import { draftModalState } from '@/recoil/draftStore'; import BottomSheet from '@/components/common/BottomSheet'; import { checkKorean } from '@/utils/checkKorean'; import DraftButton from '@/components/draft/DraftButton'; +import Loader, { LoaderContainer } from '@/components/common/Loader'; const SendReceiverPage = () => { const router = useRouter(); + const searchParams = useSearchParams(); const { showToast } = useToast(); const [draftId, setDraftId] = useState(null); const [receiver, setReceiver] = useState(''); @@ -36,6 +38,8 @@ const SendReceiverPage = () => { const [isImageUploadLoading, setImageUploadLoading] = useState(false); // 서버 이미지 업로드 상태 + const isGuest = searchParams.get('guest') === 'true'; + const [draftModal, setDraftModal] = useRecoilState(draftModalState); const [letterState, setLetterState] = useRecoilState(sendLetterState); const [tempCount, setTempCount] = useState(0); @@ -81,7 +85,7 @@ const SendReceiverPage = () => { } }; - fetchGetDraftCount(); + if (!isGuest) fetchGetDraftCount(); if (draftKey) { fetchGetDraft(); @@ -180,7 +184,8 @@ const SendReceiverPage = () => { images: images, previewImages: previewImages })); - router.push('/send/content'); + + router.push(`/send/content${isGuest ? '?guest=true' : ''}`); }; /* 임시 저장 삭제 핸들러 */ @@ -234,13 +239,15 @@ const SendReceiverPage = () => { return ( <> - + {!isGuest && ( + + )} 편지를 받는 사람 @@ -301,7 +308,19 @@ const SendReceiverPage = () => { ); }; -export default SendReceiverPage; +export default function SendReceiverPaging() { + return ( + + + + } + > + + + ); +} const Container = styled.div` width: 100%; diff --git a/src/app/send/(process)/template/page.tsx b/src/app/send/(process)/template/page.tsx index 2d3e00f3..19962703 100644 --- a/src/app/send/(process)/template/page.tsx +++ b/src/app/send/(process)/template/page.tsx @@ -1,18 +1,20 @@ 'use client'; -import React, { useEffect, useState } from 'react'; -import styled, { css } from 'styled-components'; +import React, { Suspense, useEffect, useState } from 'react'; +import styled from 'styled-components'; import { theme } from '@/styles/theme'; import Button from '@/components/common/Button'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import Letter from '@/components/letter/Letter'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { sendLetterState, useSsrComplectedState } from '@/recoil/letterStore'; import LetterTemplateList from '@/components/letter/LetterTemplateList'; import { ALL_TEMPLATES } from '@/constants/templates'; +import Loader, { LoaderContainer } from '@/components/common/Loader'; const SendTemplatePage = () => { const router = useRouter(); + const searchParams = useSearchParams(); const { receiverName, content, images, templateType } = useRecoilValue(sendLetterState); const setSendLetterState = useSetRecoilState(sendLetterState); @@ -23,6 +25,8 @@ const SendTemplatePage = () => { templateType || ALL_TEMPLATES[0] ); + const isGuest = searchParams.get('guest') === 'true'; + /* SSR 완료 시 상태 업데이트 */ const setSsrCompleted = useSsrComplectedState(); @@ -70,7 +74,8 @@ const SendTemplatePage = () => { ...prevState, templateType: template })); - router.push('/send/preview'); + + router.push(`/send/preview${isGuest ? '?guest=true' : ''}`); }; return ( @@ -119,7 +124,19 @@ const SendTemplatePage = () => { ); }; -export default SendTemplatePage; +export default function SendTemplatePaging() { + return ( + + + + } + > + + + ); +} const Container = styled.div` width: 100%; diff --git a/src/app/send/complete/page.tsx b/src/app/send/complete/page.tsx index 99c8a03b..14660514 100644 --- a/src/app/send/complete/page.tsx +++ b/src/app/send/complete/page.tsx @@ -1,46 +1,136 @@ -"use client"; +'use client'; -import Button from "@/components/common/Button"; -import { sendLetterState } from "@/recoil/letterStore"; -import { theme } from "@/styles/theme"; -import Image from "next/image"; -import { useRouter } from "next/navigation"; -import React from "react"; -import { useRecoilValue } from "recoil"; -import styled from "styled-components"; +import BottomSheet from '@/components/common/BottomSheet'; +import Button from '@/components/common/Button'; +import Loader, { LoaderContainer } from '@/components/common/Loader'; +import OauthButton from '@/components/signup/OauthButton'; +import { OAUTH } from '@/constants/oauth'; +import { SEND_COMPLETE_SUBTEXT } from '@/constants/send/message'; +import { sendLetterState } from '@/recoil/letterStore'; +import { letterFloat } from '@/styles/animation'; +import { theme } from '@/styles/theme'; +import { OAuthType } from '@/types/login'; +import { clearAnonymousSendLetterCode } from '@/utils/storage'; +import Image from 'next/image'; +import { useRouter, useSearchParams } from 'next/navigation'; +import React, { Suspense, useEffect, useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import styled from 'styled-components'; const SendCompletePage = () => { const router = useRouter(); - + const searchParams = useSearchParams(); + const isGuest = searchParams.get('guest') === 'true'; const { receiverName } = useRecoilValue(sendLetterState); + const [isDisplayed, setIsDisplayed] = useState(false); + const [isBottomUp, setIsBottomUp] = useState(false); + + const subText = isGuest + ? SEND_COMPLETE_SUBTEXT.guest + : SEND_COMPLETE_SUBTEXT.member; + + const handleBottomUpChange = (state: boolean) => { + setIsBottomUp(state); + }; + + useEffect(() => { + if (isBottomUp) { + setIsDisplayed(true); + } else { + setTimeout(() => { + setIsDisplayed(false); + }, 490); + } + }, [isBottomUp]); + + const handleComplete = () => { + if (isGuest) { + setIsBottomUp(true); + } else { + router.push('/planet'); + } + }; + + const handleExit = () => { + router.push('/login'); + clearAnonymousSendLetterCode(); + }; return ( - - - - {receiverName}에게 - - 편지를 전달했어요! - 레터링으로 편지에 담긴 진심을 수놓았어요 - - - - - - - { - router.push("/planet"); - }} - /> - - + <> + + {isGuest && ( + + + + )} + + + {receiverName}에게 + + 편지를 전달했어요! + {subText} + + + + + + + + + {isDisplayed && ( + + + 소셜로그인 선택 + + {OAUTH.map((item) => ( + + ))} + + + + )} + + > ); }; -export default SendCompletePage; +export default function SendCompletePaging() { + return ( + + + + } + > + + + ); +} const Layout = styled.div` display: flex; @@ -49,21 +139,31 @@ const Layout = styled.div` width: 100%; height: 100%; color: ${theme.colors.white}; - padding: 76px 24px 54px 24px; overflow-x: hidden; - padding-bottom: 40px; + overflow-y: hidden; background: ${(props) => props.theme.colors.bg}; - background-image: url("/assets/send/img_send_background.png"); + background-image: url('/assets/send/img_send_background.png'); background-size: cover; background-position: bottom 80px center; background-repeat: no-repeat; position: relative; + z-index: 0; +`; + +const CloseIcon = styled(Image)` + width: 24px; + height: 24px; + position: absolute; + top: 10px; + right: 17px; `; const Container = styled.div` width: 100%; + height: 100%; display: flex; flex-direction: column; + padding: 76px 24px 54px 24px; gap: 80px; `; @@ -91,17 +191,24 @@ const ImageWrapper = styled.div` display: flex; align-items: center; justify-content: center; + animation: ${letterFloat} 2s ease-in-out infinite; + + @media (max-height: 680px) { + width: 400px; + height: 380px; + top: 55%; + } @media (max-height: 580px) { width: 350px; height: 340px; - top: 55%; + top: 58%; } @media (max-height: 550px) { width: 300px; height: 290px; - top: 55%; + top: 58%; } `; @@ -115,3 +222,23 @@ const ButtonWrapper = styled.div` bottom: 54px; left: 0; `; + +const BottomWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 26px; +`; + +const SocialTitle = styled.p` + color: ${theme.colors.gray100}; + ${theme.fonts.title02}; +`; + +const LoginList = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 0 14px; + gap: 24px; +`; diff --git a/src/app/signup/step2/page.tsx b/src/app/signup/step2/page.tsx index 0f5c1c90..1617fbd9 100644 --- a/src/app/signup/step2/page.tsx +++ b/src/app/signup/step2/page.tsx @@ -13,7 +13,11 @@ import { Suspense, useState } from 'react'; import Loader, { LoaderContainer } from '@/components/common/Loader'; import { signupState, userInfo } from '@/recoil/signupStore'; import { signup } from '@/api/login/user'; -import { setTokens } from '@/utils/storage'; +import { + clearAnonymousSendLetterCode, + getAnonymousSendLetterCode, + setTokens +} from '@/utils/storage'; import { useToast } from '@/hooks/useToast'; import { checkKorean } from '@/utils/checkKorean'; @@ -30,6 +34,13 @@ const SignupStep2 = () => { const searchParams = useSearchParams(); const url = searchParams.get('url'); + const [anonymousCode, setAnonymousCode] = useState(null); + + useEffect(() => { + const code = getAnonymousSendLetterCode(); + setAnonymousCode(code); + }, []); + const handleButtonClick = () => { if (canSignin()) { setIsBottomUp(true); @@ -59,11 +70,13 @@ const SignupStep2 = () => { privatePermission: user.privatePermission, servicePermission: user.servicePermission, marketingPermission: user.marketingPermission, - realName: name + realName: name, + anonymousSendLetterCode: anonymousCode }) .then((res) => { console.log('accessToken', res.data.accessToken); setTokens(res.data.accessToken, res.data.refreshToken); + clearAnonymousSendLetterCode(); if (url) { router.push(`/signup/complete?url=${url}`); } else { diff --git a/src/app/verify/page.tsx b/src/app/verify/page.tsx index 66106900..b451483c 100644 --- a/src/app/verify/page.tsx +++ b/src/app/verify/page.tsx @@ -26,7 +26,8 @@ const Verify = () => { privatePermission: user.privatePermission, servicePermission: user.servicePermission, marketingPermission: user.marketingPermission, - realName: name + realName: name, + anonymousSendLetterCode: null }) .then((res) => { console.log('accessToken', res.data.accessToken); diff --git a/src/components/signup/OauthButton.tsx b/src/components/signup/OauthButton.tsx index d327a28d..fabd4c12 100644 --- a/src/components/signup/OauthButton.tsx +++ b/src/components/signup/OauthButton.tsx @@ -9,12 +9,14 @@ import { float } from '@/styles/animation'; interface OauthButtonProps { loginType: OAuthType; + shape?: 'circle' | 'list'; bgColor: string; icon: string; size: number; + label?: string; } const OauthButton = (props: OauthButtonProps) => { - const { loginType, bgColor, icon, size } = props; + const { loginType, shape = 'circle', bgColor, icon, size, label } = props; const searchParams = useSearchParams(); const url = searchParams.get('url'); @@ -87,10 +89,21 @@ const OauthButton = (props: OauthButtonProps) => { return ( - {recentLogin === loginType && 최근에 로그인했어요} - - - + {shape === 'circle' && recentLogin === loginType && ( + 최근에 로그인했어요 + )} + + + + + {shape === 'list' && {label}} + ); }; @@ -98,17 +111,21 @@ const OauthButton = (props: OauthButtonProps) => { export default OauthButton; const Wrapper = styled.div` - width: 69px; - height: 69px; position: relative; display: flex; flex-direction: column; align-items: center; `; -const SocialButton = styled.button<{ $bgColor: string }>` - width: 69px; - height: 69px; +const Box = styled.button` + display: flex; + align-items: center; + gap: 20px; +`; + +const SocialButton = styled.div<{ $bgColor: string; $shape?: string }>` + width: ${({ $shape }) => ($shape === 'list' ? '31px' : '69px')}; + height: ${({ $shape }) => ($shape === 'list' ? '31px' : '69px')}; display: flex; justify-content: center; align-items: center; @@ -119,10 +136,9 @@ const SocialButton = styled.button<{ $bgColor: string }>` transition: background-color 0.3s; `; -const SocialIcon = styled(Image)` - width: 100%; - height: 100%; - padding: 0 16px; +const SocialIcon = styled(Image)<{ $size: number }>` + width: ${({ $size }) => $size}px; + height: ${({ $size }) => $size}px; object-fit: contain; `; @@ -153,3 +169,9 @@ const Bubble = styled.div` border-color: ${theme.colors.blue} transparent transparent transparent; } `; + +const ButtonLabel = styled.span` + margin-left: 12px; + color: ${theme.colors.white}; + ${theme.fonts.body02}; +`; diff --git a/src/constants/oauth.ts b/src/constants/oauth.ts index dbbd033f..12c292c4 100644 --- a/src/constants/oauth.ts +++ b/src/constants/oauth.ts @@ -4,20 +4,26 @@ export const OAUTH = [ bgColor: '#03CF5D', icon: '/assets/icons/ic_naver.svg', size: 26, - profile: "/assets/icons/ic_naver.svg" + miniSize: 13, + profile: '/assets/icons/ic_naver.svg', + label: '네이버로 시작하기' }, { key: 'google', bgColor: '#FFFFFF', icon: '/assets/icons/ic_google.svg', size: 32, - profile: "/assets/icons/ic_googler.svg" + miniSize: 16, + profile: '/assets/icons/ic_google.svg', + label: '구글로 시작하기' }, { key: 'kakao', bgColor: '#FEE500', icon: '/assets/icons/ic_kakaotalk.svg', size: 38, + miniSize: 18, profile: '/assets/icons/ic_kakao_profile.svg', + label: '카카오로 시작하기' } ]; \ No newline at end of file diff --git a/src/constants/send/message.ts b/src/constants/send/message.ts new file mode 100644 index 00000000..9ab923eb --- /dev/null +++ b/src/constants/send/message.ts @@ -0,0 +1,5 @@ +export const SEND_COMPLETE_SUBTEXT = { + guest: + '레터링에 가입하면 편지를 보낼 뿐만 아니라\n받은 편지들도 나의 스페이스에 보관할 수 있어요', + member: '레터링으로 편지에 담긴 진심을 수놓았어요' +}; \ No newline at end of file diff --git a/src/lib/.gitkeep b/src/lib/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/styles/animation.ts b/src/styles/animation.ts index e6800433..6a923a71 100644 --- a/src/styles/animation.ts +++ b/src/styles/animation.ts @@ -22,4 +22,16 @@ export const float = keyframes` 100% { transform: translate(-50%, 0); } +`; + +export const letterFloat = keyframes` + 0% { + transform: translate(-50%, -48%); + } + 50% { + transform: translate(-50%, -52%); + } + 100% { + transform: translate(-50%, -48%); + } `; \ No newline at end of file diff --git a/src/types/user.tsx b/src/types/user.tsx index 4f99444f..5ad41369 100644 --- a/src/types/user.tsx +++ b/src/types/user.tsx @@ -4,4 +4,5 @@ export interface RegisterDataType { privatePermission: boolean; marketingPermission: boolean; realName: string; + anonymousSendLetterCode: string | null; } diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 7324835a..4ce8745f 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -116,6 +116,24 @@ export const setInitUserToast = () => { } }; +/* anonymousSendLetterCode */ +export const setAnonymousSendLetterCode = (anonymousSendLetterCode: string) => { + if (typeof window !== 'undefined') { + localStorage.setItem('anonymousSendLetterCode', anonymousSendLetterCode); + } +}; + +export const getAnonymousSendLetterCode = () => { + if (typeof window !== 'undefined') { + return localStorage.getItem('anonymousSendLetterCode'); + } + return null; +}; + +export const clearAnonymousSendLetterCode = () => { + localStorage.removeItem('anonymousSendLetterCode'); +}; + export const getInitUserToast = (): string | null => { if (typeof window !== "undefined") { return sessionStorage.getItem("initUserToast");