Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
3cba0be
✨ feat(#170): 비회원 편지 전송 처리 및 UI 변경
yyypearl May 17, 2025
ec39eb0
💄 design(#170): 회원가입 BottomSheet UI 추가
yyypearl May 18, 2025
a35cf87
💄 design(#170): 편지 전달 완료 페이지 비회원 나가기 버튼 추가
yyypearl May 18, 2025
15367aa
💄 design(#170): 편지 전달 완료 페이지 float 애니메이션 추가
yyypearl May 18, 2025
4eca67d
✨ feat(#170): / 루트 경로 로딩 컴포넌트 적용
yyypearl May 18, 2025
828e761
✨ feat(#170): 로그인 페이지에서 편지 전송 store 초기화 (비회원용)
yyypearl May 18, 2025
ef40d78
🐛 fix(#170): 편지 전송 단계 Suspense로 로딩 상태 제공
yyypearl May 18, 2025
15d879b
✨ feat(#170): 이미지 업로드 API에 비인증 요청 허용 처리
yyypearl May 18, 2025
624d9f4
✨ feat(#170): 비회원 편지 쓰기 API 연동
yyypearl May 18, 2025
844b20a
✨ feat(#170): 회원가입 API에 letterCode 전달 추가
yyypearl May 18, 2025
37ef5ea
✨ feat(#170): anonymousSendLetterCode 관련 로컬스토리지 함수 추가
yyypearl May 18, 2025
bc4e3ef
✨ feat(#170): 편지 공유 상태 조회 API에 비인증 요청 허용 처리
yyypearl May 18, 2025
c753bc8
🐛 fix(#170): 구글 아이콘 오타 수정 및 localStorage 변수명 수정
yyypearl May 18, 2025
6cdcae7
🐛 fix(#170): 편지 전송 완료 페이지로 라우터 이동 시 isGuest 여부 params 전달
yyypearl May 18, 2025
6d9e219
🐛 fix(#170): useEffect 안에서 letterCode 값 가져오기
yyypearl May 18, 2025
f10adf1
🐛 fix(#170): 편지 열람 위해 회원가입하는 경우 anonymousSendLetterCode 값 null 설정
yyypearl May 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions public/assets/icons/ic_x.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions src/api/image/image.tsx
Original file line number Diff line number Diff line change
@@ -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'
}
});
};
14 changes: 7 additions & 7 deletions src/api/letter/share.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { authClient } from "../client";
import client, { authClient } from '../client';

export const getLetterShareStatus = async (
letterCode: string
): Promise<ShareStatusData> => {
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;
Expand Down
6 changes: 4 additions & 2 deletions src/api/login/user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
};

Expand Down
24 changes: 22 additions & 2 deletions src/api/send/send.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
});
};
35 changes: 28 additions & 7 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Container>
<ImageWrapper>
Expand All @@ -38,8 +59,8 @@ export default function Login() {
))}
</Suspense>
</OauthWrapper>
<LetterBtnText onClick={notReady}>
로그인 없이 편지 작성해보기
<LetterBtnText onClick={handleGuestLetterStart}>
로그인 없이 편지 보내기
</LetterBtnText>
</ImageWrapper>
</Container>
Expand Down
52 changes: 11 additions & 41 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,28 @@
"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();
const accessToken = getAccessToken();

useEffect(() => {
if (!accessToken) {
router.push("/login");
router.push('/login');
} else {
router.push("/planet");
router.push('/planet');
}
}, []);

return (
<Container>
{/* <ButtonContainer>
<Button
buttonType="primary"
text="로그아웃하기"
onClick={handleLogout}
></Button>
<KakaoShareButton letterId="aa" />
</ButtonContainer> */}
<LoaderContainer>
<Loader />
</LoaderContainer>
</Container>
);
}
Expand All @@ -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;
`;
44 changes: 31 additions & 13 deletions src/app/send/(process)/content/page.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<string | null>(null);
const [receiver, setReceiver] = useState<string>('');
Expand All @@ -39,6 +40,8 @@ const SendContentPage = () => {
const [isImageUploadLoading, setImageUploadLoading] =
useState<boolean>(false); // 서버 이미지 업로드 상태

const isGuest = searchParams.get('guest') === 'true';

const [draftModal, setDraftModal] = useRecoilState(draftModalState);
const [letterState, setLetterState] = useRecoilState(sendLetterState);
const [tempCount, setTempCount] = useState<number>(3);
Expand Down Expand Up @@ -81,7 +84,7 @@ const SendContentPage = () => {
}
};

fetchGetDraftCount();
if (!isGuest) fetchGetDraftCount();

if (draftKey) {
fetchGetDraft();
Expand Down Expand Up @@ -263,7 +266,8 @@ const SendContentPage = () => {
images: images,
previewImages: previewImages
}));
router.push('/send/template');

router.push(`/send/template${isGuest ? '?guest=true' : ''}`);
};

/* 임시 저장 삭제 핸들러 */
Expand Down Expand Up @@ -317,13 +321,15 @@ const SendContentPage = () => {

return (
<>
<DraftButton
handleSaveLetter={handleSaveLetter}
handleDraftBottom={handleDraftBottom}
isDraftDisabled={isDraftDisabled}
isImageUploadLoading={isImageUploadLoading}
tempCount={tempCount}
/>
{!isGuest && (
<DraftButton
handleSaveLetter={handleSaveLetter}
handleDraftBottom={handleDraftBottom}
isDraftDisabled={isDraftDisabled}
isImageUploadLoading={isImageUploadLoading}
tempCount={tempCount}
/>
)}
<Container>
<div>
<Label>
Expand Down Expand Up @@ -420,7 +426,19 @@ const SendContentPage = () => {
);
};

export default SendContentPage;
export default function SendContentPaging() {
return (
<Suspense
fallback={
<LoaderContainer>
<Loader />
</LoaderContainer>
}
>
<SendContentPage />
</Suspense>
);
}

const Container = styled.div`
width: 100%;
Expand Down
Loading