diff --git a/admin/.gitignore b/admin/.gitignore
index a547bf36..3b0b4037 100644
--- a/admin/.gitignore
+++ b/admin/.gitignore
@@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
+
+.env
\ No newline at end of file
diff --git a/client/src/apis/linkAPI.ts b/client/src/apis/linkAPI.ts
new file mode 100644
index 00000000..822ec7b4
--- /dev/null
+++ b/client/src/apis/linkAPI.ts
@@ -0,0 +1,22 @@
+import { GetShareLinkResponse } from "@/types/linkApi";
+import { fetchWithTimeout } from "@/utils/fetchWithTimeout";
+
+const baseURL = `${import.meta.env.VITE_API_URL}/link`;
+const headers = {
+ "Content-Type": "application/json",
+};
+
+export const LinkAPI = {
+ async getShareLink(token: string): Promise {
+ try {
+ const response = await fetchWithTimeout(`${baseURL}`, {
+ method: "POST",
+ headers: { ...headers, Authorization: `Bearer ${token}` },
+ });
+ return response.json();
+ } catch (error) {
+ console.error("Error:", error);
+ throw error;
+ }
+ },
+};
diff --git a/client/src/constants/Auth/token.ts b/client/src/constants/Auth/token.ts
deleted file mode 100644
index c8073010..00000000
--- a/client/src/constants/Auth/token.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const COOKIE_TOKEN_KEY = "token";
diff --git a/client/src/constants/cookie.ts b/client/src/constants/cookie.ts
new file mode 100644
index 00000000..7b427b53
--- /dev/null
+++ b/client/src/constants/cookie.ts
@@ -0,0 +1,4 @@
+export const COOKIE_KEY = {
+ ACCESS_TOKEN: "token",
+ INVITE_USER: "referrerId",
+} as const;
diff --git a/client/src/features/CasperCustom/CasperCustomFinish.tsx b/client/src/features/CasperCustom/CasperCustomFinish.tsx
index a3abacd5..d64905a6 100644
--- a/client/src/features/CasperCustom/CasperCustomFinish.tsx
+++ b/client/src/features/CasperCustom/CasperCustomFinish.tsx
@@ -2,14 +2,16 @@ import { useEffect, useRef } from "react";
import { motion } from "framer-motion";
import { useCookies } from "react-cookie";
import { Link } from "react-router-dom";
+import { LinkAPI } from "@/apis/linkAPI";
import { LotteryAPI } from "@/apis/lotteryAPI";
import CTAButton from "@/components/CTAButton";
-import { COOKIE_TOKEN_KEY } from "@/constants/Auth/token";
import { MAX_APPLY } from "@/constants/CasperCustom/customStep";
import { DISSOLVE } from "@/constants/animation";
+import { COOKIE_KEY } from "@/constants/cookie";
import useCasperCustomDispatchContext from "@/hooks/useCasperCustomDispatchContext";
import useCasperCustomStateContext from "@/hooks/useCasperCustomStateContext";
import useFetch from "@/hooks/useFetch";
+import useToast from "@/hooks/useToast";
import { CASPER_ACTION } from "@/types/casperCustom";
import { GetApplyCountResponse } from "@/types/lotteryApi";
import { saveDomImage } from "@/utils/saveDomImage";
@@ -27,10 +29,11 @@ export function CasperCustomFinish({
handleResetStep,
unblockNavigation,
}: CasperCustomFinishProps) {
- const [cookies] = useCookies([COOKIE_TOKEN_KEY]);
+ const [cookies] = useCookies([COOKIE_KEY.ACCESS_TOKEN]);
+ const { showToast, ToastComponent } = useToast("링크가 복사되었어요!");
const { data: applyCountData, fetchData: getApplyCount } = useFetch(() =>
- LotteryAPI.getApplyCount(cookies[COOKIE_TOKEN_KEY])
+ LotteryAPI.getApplyCount(cookies[COOKIE_KEY.ACCESS_TOKEN])
);
const dispatch = useCasperCustomDispatchContext();
@@ -39,7 +42,7 @@ export function CasperCustomFinish({
const casperCustomRef = useRef(null);
useEffect(() => {
- if (!cookies[COOKIE_TOKEN_KEY]) {
+ if (!cookies[COOKIE_KEY.ACCESS_TOKEN]) {
return;
}
@@ -60,6 +63,17 @@ export function CasperCustomFinish({
dispatch({ type: CASPER_ACTION.RESET_CUSTOM });
};
+ const handleClickShareButton = async () => {
+ const link = await LinkAPI.getShareLink(cookies[COOKIE_KEY.ACCESS_TOKEN]);
+
+ try {
+ await navigator.clipboard.writeText(link.shortenLocalUrl);
+ showToast();
+ } catch (err) {
+ console.error("Failed to copy: ", err);
+ }
+ };
+
return (
@@ -102,7 +116,10 @@ export function CasperCustomFinish({
)}
-
+
@@ -112,6 +129,8 @@ export function CasperCustomFinish({
+
+ {ToastComponent}
);
}
diff --git a/client/src/features/CasperCustom/CasperCustomForm.tsx b/client/src/features/CasperCustom/CasperCustomForm.tsx
index 086b3c8c..fd15a037 100644
--- a/client/src/features/CasperCustom/CasperCustomForm.tsx
+++ b/client/src/features/CasperCustom/CasperCustomForm.tsx
@@ -4,9 +4,9 @@ import { useCookies } from "react-cookie";
import { LotteryAPI } from "@/apis/lotteryAPI";
import CTAButton from "@/components/CTAButton";
import TextField from "@/components/TextField";
-import { COOKIE_TOKEN_KEY } from "@/constants/Auth/token";
import { CUSTOM_OPTION } from "@/constants/CasperCustom/casper";
import { DISSOLVE } from "@/constants/animation";
+import { COOKIE_KEY } from "@/constants/cookie";
import useCasperCustomDispatchContext from "@/hooks/useCasperCustomDispatchContext";
import useCasperCustomStateContext from "@/hooks/useCasperCustomStateContext";
import useFetch from "@/hooks/useFetch";
@@ -20,14 +20,17 @@ interface CasperCustomFormProps {
}
export function CasperCustomForm({ navigateNextStep }: CasperCustomFormProps) {
- const [cookies] = useCookies([COOKIE_TOKEN_KEY]);
+ const [cookies] = useCookies([COOKIE_KEY.ACCESS_TOKEN, COOKIE_KEY.INVITE_USER]);
const {
data: casper,
isSuccess: isSuccessPostCasper,
fetchData: postCasper,
- } = useFetch(
- ({ token, casper }) => LotteryAPI.postCasper(token, casper)
+ } = useFetch<
+ PostCasperResponse,
+ { token: string; referrerId: string; casper: CasperInformationType }
+ >(({ token, referrerId, casper }) =>
+ LotteryAPI.postCasper(token, { ...casper, [COOKIE_KEY.INVITE_USER]: referrerId })
);
const { casperName, expectations, selectedCasperIdx } = useCasperCustomStateContext();
@@ -80,7 +83,11 @@ export function CasperCustomForm({ navigateNextStep }: CasperCustomFormProps) {
expectation: expectations,
};
- await postCasper({ token: cookies[COOKIE_TOKEN_KEY], casper });
+ await postCasper({
+ token: cookies[COOKIE_KEY.ACCESS_TOKEN],
+ referrerId: cookies[COOKIE_KEY.INVITE_USER],
+ casper,
+ });
};
return (
diff --git a/client/src/pages/Lottery/index.tsx b/client/src/pages/Lottery/index.tsx
index 448b29df..88d0dc4b 100644
--- a/client/src/pages/Lottery/index.tsx
+++ b/client/src/pages/Lottery/index.tsx
@@ -1,11 +1,11 @@
import { useCallback, useEffect, useState } from "react";
import { useCookies } from "react-cookie";
-import { useLoaderData, useNavigate } from "react-router-dom";
+import { useLoaderData, useLocation, useNavigate } from "react-router-dom";
import { AuthAPI } from "@/apis/authAPI";
import Footer from "@/components/Footer";
import Notice from "@/components/Notice";
-import { COOKIE_TOKEN_KEY } from "@/constants/Auth/token";
import { LOTTERY_SECTIONS } from "@/constants/PageSections/sections.ts";
+import { COOKIE_KEY } from "@/constants/cookie";
import {
CustomDesign,
HeadLamp,
@@ -33,11 +33,16 @@ export default function Lottery() {
useScrollTop();
const navigate = useNavigate();
+ const location = useLocation();
+ const queryParams = new URLSearchParams(location.search);
+ const inviteUser = queryParams.get(COOKIE_KEY.INVITE_USER);
+
+ const lotteryData = useLoaderData() as GetLotteryResponse;
const containerRef = useHeaderStyleObserver({
darkSections: [LOTTERY_SECTIONS.HEADLINE, LOTTERY_SECTIONS.SHORT_CUT],
});
- const [_cookies, setCookie] = useCookies([COOKIE_TOKEN_KEY]);
+ const [_cookies, setCookie] = useCookies([COOKIE_KEY.ACCESS_TOKEN, COOKIE_KEY.INVITE_USER]);
const {
data: authToken,
@@ -52,11 +57,14 @@ export default function Lottery() {
const [phoneNumberState, setPhoneNumberState] = useState(phoneNumber);
- const lotteryData = useLoaderData() as GetLotteryResponse;
-
+ useEffect(() => {
+ if (inviteUser) {
+ setCookie(COOKIE_KEY.INVITE_USER, inviteUser);
+ }
+ }, [inviteUser]);
useEffect(() => {
if (authToken && isSuccessGetAuthToken) {
- setCookie(COOKIE_TOKEN_KEY, authToken.accessToken);
+ setCookie(COOKIE_KEY.ACCESS_TOKEN, authToken.accessToken);
dispatch({ type: PHONE_NUMBER_ACTION.SET_PHONE_NUMBER, payload: phoneNumberState });
navigate("/lottery/custom");
}
diff --git a/client/src/types/linkApi.ts b/client/src/types/linkApi.ts
new file mode 100644
index 00000000..027e56af
--- /dev/null
+++ b/client/src/types/linkApi.ts
@@ -0,0 +1,4 @@
+export interface GetShareLinkResponse {
+ shortenUrl: string;
+ shortenLocalUrl: string;
+}
diff --git a/client/src/types/lotteryApi.ts b/client/src/types/lotteryApi.ts
index 55b5253d..6e9d2c4f 100644
--- a/client/src/types/lotteryApi.ts
+++ b/client/src/types/lotteryApi.ts
@@ -1,3 +1,5 @@
+import { COOKIE_KEY } from "@/constants/cookie";
+
export interface CasperInformationType {
eyeShape: number;
eyePosition: number;
@@ -12,7 +14,9 @@ export type GetCasperListResponse = ({
casperId: number;
} & CasperInformationType)[];
-export interface PostCasperRequestBody extends CasperInformationType {}
+export interface PostCasperRequestBody extends CasperInformationType {
+ [COOKIE_KEY.INVITE_USER]: string;
+}
export interface PostCasperResponse extends CasperInformationType {
casperId: number;