Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: 공유 기능 구현 #208

Merged
merged 12 commits into from
Jan 21, 2024
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-toggle-group": "^1.0.4",
"@stomp/stompjs": "^7.0.0",
"@svgr/rollup": "^8.1.0",
Expand Down
35 changes: 35 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/api/trips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,15 @@ export const getTripsAuthority = async (tripId: string) => {
const res = await authClient.get(`trips/${tripId}/authority`);
return res;
};

// 여정 참여 코드 조회
export const getTripsjoin = async (tripId: string) => {
const res = await authClient.get(`trips/${tripId}/join`);
return res;
};

// 여정 참여
export const postTripsjoin = async (tripId: number, joinCode: string) => {
const res = await authClient.post(`trips/${tripId}/join`, { joinCode });
return res;
};
10 changes: 8 additions & 2 deletions src/components/Auth/Login/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { AuthRequest } from '@/@types/auth.types';
import { SubmitHandler, useForm } from 'react-hook-form';
import { postEmailLogin } from '@api/auth';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { AxiosError } from 'axios';
import { useState } from 'react';
import SubmitBtn from '@components/common/button/SubmitBtn';
Expand All @@ -26,6 +26,8 @@ const LoginForm = () => {
});

const navigate = useNavigate();
const { state } = useLocation();

// const [userInfo, setUserInfo] = useRecoilState(UserInfoState);

const onLoginSubmit: SubmitHandler<AuthRequest> = async (data) => {
Expand All @@ -38,7 +40,11 @@ const LoginForm = () => {
if (res.data.status === 200) {
setItem('accessToken', res.data.data.tokenInfo.accessToken);
// setUserInfo(res.data.data.memberDto);
navigate('/');
if (state) {
navigate(state.prevPath);
} else {
navigate('/');
}
}
} catch (err) {
if (err instanceof AxiosError) {
Expand Down
6 changes: 4 additions & 2 deletions src/components/Plan/PlanSectionTop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { tripIdState, memberIdState } from '@recoil/socket';
import { calculateDayAndDate } from '@utils/utils';
import PlanSchedule from './PlanSchedule';


const PlanSectionTop = () => {
const navigate = useNavigate();
const tripId = useRecoilValue(tripIdState);
Expand Down Expand Up @@ -52,10 +51,13 @@ const PlanSectionTop = () => {
<div className="min-h-screen">
<BackBox
showBack={true}
showShare={true}
backHandler={() => {
navigate(-1);
}}
showShare={true}
shareHandler={() => {
navigate(`/trip/${tripId}/share`);
}}
/>
<TripRealtimeEditor />
<PlanSchedule />
Expand Down
39 changes: 39 additions & 0 deletions src/components/Share/CodeInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
interface Props {
inputCode: string;
setInputCode: React.Dispatch<React.SetStateAction<string>>;
showError: boolean;
}

const CodeInput = ({ inputCode, setInputCode, showError }: Props) => {
const onCodeChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const changeValue = e.target.value;
if (changeValue.length <= 5) {
setInputCode(e.target.value);
}
};
return (
<div className="relative flex w-full flex-col items-start px-2 ">
<div
className={`mb-10 mt-6 w-full border-b-[1.5px] border-solid ${
showError ? 'border-red' : 'border-gray3 focus-within:border-main2'
}`}>
<input
type="number"
autoFocus
maxLength={5}
placeholder="편집 초대 코드를 입력해주세요."
className="title3 mb-2 h-5 w-full bg-transparent text-gray4 outline-none placeholder:text-gray2"
onChange={onCodeChange}
value={inputCode}
/>
</div>
{showError && (
<div className="body5 absolute top-[62px] text-red">
편집 참여 코드를 다시 한번 확인해주세요.
</div>
)}
</div>
);
};

export default CodeInput;
38 changes: 38 additions & 0 deletions src/components/Share/CopyBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import CopyToast from './CopyToast';

interface Props {
title: string;
subTitle: string;
copyValue: string;
}

const CopyBox = ({ title, subTitle, copyValue }: Props) => {
const onCopyClick = () => {
navigator.clipboard.writeText(copyValue);
};

return (
<div>
<div className="mb-4 flex flex-col gap-1">
<span className="title3 text-gray7">{`${title} 복사`}</span>
<span className="body6 text-gray4">{subTitle}</span>
</div>
<div className="flex h-12 w-full items-center justify-between rounded-lg border border-solid border-gray2 px-4 py-2">
<input
className="body5 mr-4 w-full text-gray6 outline-none"
readOnly
value={copyValue}
/>
<CopyToast title={title}>
<div
onClick={onCopyClick}
className="bg-main3 body3 w-12 rounded-lg p-2 text-main1">
복사
</div>
</CopyToast>
</div>
</div>
);
};

export default CopyBox;
36 changes: 36 additions & 0 deletions src/components/Share/CopyToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as Toast from '@radix-ui/react-toast';
import { ReactNode, useState } from 'react';
import { ReactComponent as CircleCheckIcon } from '@assets/images/CircleCheck.svg';

interface Props {
title: string;
children: ReactNode;
}

const CopyToast = ({ title, children }: Props) => {
const [open, setOpen] = useState(false);

return (
<Toast.Provider duration={2000}>
<button
onClick={() => {
setOpen(true);
}}>
{children}
</button>

<Toast.Root
className="bg-main4 flex h-16 w-[370px] items-center rounded-lg border border-solid border-main2 px-4 py-2 shadow-md"
open={open}
onOpenChange={setOpen}>
<CircleCheckIcon fill="#29DDF6" className="mr-2" />
<Toast.Title className="body4 text-main1 [grid-area:_title]">
{`${title}가 복사되었습니다.`}
</Toast.Title>
</Toast.Root>
<Toast.Viewport className="fixed left-[50%] top-12 translate-x-[-50%]" />
</Toast.Provider>
);
};

export default CopyToast;
68 changes: 68 additions & 0 deletions src/components/Share/IsEditableModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as Dialog from '@radix-ui/react-dialog';
import { EditStarIcon } from '@components/common/icons/Icons';
import Alert from '@components/common/alert/Alert';
import { useLocation, useNavigate } from 'react-router-dom';
import { getItem } from '@utils/localStorageFun';

interface Props {
isEditable: boolean;
setIsEditable: React.Dispatch<React.SetStateAction<boolean>>;
}

const IsEditableModal = ({ isEditable, setIsEditable }: Props) => {
const navigate = useNavigate();
const { pathname } = useLocation();

const isLogin = getItem('accessToken');

const handleConfirm = () => {
navigate('/login', { state: { prevPath: `${pathname}/code` } });
};

return (
<Dialog.Root open={isEditable} onOpenChange={setIsEditable} modal>
<Dialog.Portal>
<Dialog.Overlay className="data-[state=open]:animate-overlayShow fixed inset-0 z-10 bg-black opacity-70" />
<Dialog.Content className="data-[state=open]:animate-contentShow fixed bottom-0 left-[50%] z-10 flex w-[412px] translate-x-[-50%] flex-col items-center rounded-t-2xl bg-white px-5 pb-8 pt-9">
<Dialog.Title className="title3 pb-2.5 text-center text-gray7">
편집 참여 코드를 입력하시면
<br />
여행 계획을 함께 편집할 수 있어요!
</Dialog.Title>
<Dialog.Description className="pb-8">
<EditStarIcon />
</Dialog.Description>
{isLogin ? (
<button
onClick={() => {
navigate('code');
}}
className="headline1 mb-4 h-14 w-full rounded-lg bg-main2 text-white outline-none">
초대코드 입력하기
</button>
) : (
<Alert
title={'로그인'}
message={
<>
편집 참여 코드 입력을 위해 로그인이 필요해요.
<br />
로그인하시겠어요?
</>
}
onConfirm={handleConfirm}>
<button className="headline1 mb-4 h-14 w-full rounded-lg bg-main2 text-white outline-none">
초대코드 입력하기
</button>
</Alert>
)}
<Dialog.Close>
<button className="body1 text-gray5">보기만 할게요</button>
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
};

export default IsEditableModal;
59 changes: 59 additions & 0 deletions src/components/Trip/EditCodeModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { postTripsjoin } from '@api/trips';
import CodeInput from '@components/Share/CodeInput';
import Alert from '@components/common/alert/Alert';
import { useGetTripsAuthority } from '@hooks/useGetTripsAuthority';
import { useState } from 'react';
import { useParams } from 'react-router-dom';

const EditCodeModal = () => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [inputCode, setInputCode] = useState<string>('');
const [showError, setShowError] = useState<boolean>(false);

const { id: tripId } = useParams();
const { tripAuthority } = useGetTripsAuthority();

const handleConfirm = async () => {
if (tripId) {
try {
const { data } = await postTripsjoin(Number(tripId), inputCode);
if (data.status === 200) {
setIsModalOpen(false);
}
} catch (err) {
setShowError(true);
setInputCode('');
console.error('참여 코드 요청 중 에러 발생', err);
}
}
};

return (
<>
{tripAuthority === 'WRITE' ? (
<button className="body3 rounded-lg border-2 border-solid border-gray2 p-2 text-gray4">
편집
</button>
) : (
<Alert
title="편집 참여 코드 입력"
content={
<CodeInput
inputCode={inputCode}
setInputCode={setInputCode}
showError={showError}
/>
}
isOpen={isModalOpen}
setIsOpen={setIsModalOpen}
onConfirm={handleConfirm}>
<button className="body3 rounded-lg border-2 border-solid border-gray2 p-2 text-gray4">
편집
</button>
</Alert>
)}
</>
);
};

export default EditCodeModal;
2 changes: 2 additions & 0 deletions src/components/Trip/TripSchedule.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UserIcon } from '@components/common/icons/Icons';
import EditCodeModal from './EditCodeModal';

export const TripSchedule = () => {
return (
Expand All @@ -12,6 +13,7 @@ export const TripSchedule = () => {
<span className="body4 pt-[1px] text-gray4">5</span>
</div>
</div>
<EditCodeModal />
</div>
<span className="body1 text-gray4">23.12.23 - 23.12.25</span>
</>
Expand Down
Loading