Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/fcm-click' into feat/#72
Browse files Browse the repository at this point in the history
  • Loading branch information
faddishcorn committed Nov 15, 2024
2 parents 03097e2 + 79a8957 commit a2c1f5a
Show file tree
Hide file tree
Showing 21 changed files with 3,146 additions and 306 deletions.
1,607 changes: 1,607 additions & 0 deletions dist/assets/index-DNagE171.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/assets/index-KWo6zzW6.css

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions dist/assets/logo-CmO4ZKwy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions dist/assets/mock2-BvRgIRQl.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 72 additions & 0 deletions dist/assets/step1-DsUboUzH.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
408 changes: 408 additions & 0 deletions dist/assets/step2-CJ8X2DvV.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
523 changes: 523 additions & 0 deletions dist/assets/step3-DSVRkaDE.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions dist/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:[email protected]&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-DNagE171.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-KWo6zzW6.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"preview": "vite preview",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"tsc": "tsc --noEmit"
"tsc": "tsc"
},
"dependencies": {
"@emotion/react": "^11.13.3",
Expand Down
14 changes: 13 additions & 1 deletion public/firebase-messaging-sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,20 @@ messaging.onBackgroundMessage((payload) => {
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
icon: "/icon.png", // μ•„μ΄μ½˜ 이미지 경둜
icon: "./src/assets/icon.png", // μ•„μ΄μ½˜ 이미지 경둜
data: { click_action: payload.data.click_action } // 클릭 μ‹œ 이동할 URL을 data에 μΆ”κ°€
};

self.registration.showNotification(notificationTitle, notificationOptions);
});

// μ•Œλ¦Ό 클릭 이벀트 λ¦¬μŠ€λ„ˆ μΆ”κ°€
self.addEventListener("notificationclick", (event) => {
event.notification.close(); // μ•Œλ¦Ό λ‹«κΈ°

// μ•Œλ¦Όμ— μ„€μ •λœ URL둜 이동
const targetUrl = event.notification.data.click_action || "https://splanet.co.kr/";
event.waitUntil(
clients.openWindow(targetUrl)
);
});
33 changes: 0 additions & 33 deletions src/api/hooks/useCreatePlan.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,3 @@
// import { useMutation } from "@tanstack/react-query";
// import { apiClient } from "../instance";

// interface PlanCard {
// title: string;
// description: string;
// startDate: string;
// endDate: string;
// accessibility?: boolean;
// isCompleted?: boolean;
// }
// interface SavePlanData {
// deviceId: string;
// groupId: string;
// planCards: PlanCard[];
// }
// const savePlan = async (data: SavePlanData) => {
// const response = await apiClient.post("/api/gpt/plan/save", data);
// return response.data;
// };

// const useCreatePlan = (p0: {
// onSuccess: (data: any) => void;
// onError: (error: any) => void;
// }) => {
// return useMutation({
// mutationFn: savePlan,
// onSuccess: p0.onSuccess,
// onError: p0.onError,
// });
// };
// export default useCreatePlan;

import { useMutation, UseMutationOptions } from "@tanstack/react-query";
import { AxiosResponse } from "axios";
import { apiClient } from "@/api/instance";
Expand Down
34 changes: 34 additions & 0 deletions src/api/hooks/useFcmOffsetUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useMutation } from "@tanstack/react-query";
import { apiClient } from "../instance";

interface FcmOffsetUpdateRequest {
token: string;
notificationOffset: number;
}

type FcmOffsetUpdateResponse = string;

const useFcmOffsetUpdate = () => {
return useMutation<FcmOffsetUpdateResponse, Error, FcmOffsetUpdateRequest>({
mutationFn: async (data: FcmOffsetUpdateRequest) => {
// query parameter둜 λ³€κ²½
const response = await apiClient.put<FcmOffsetUpdateResponse>(
"/api/fcm/update/notification-offset",
null, // bodyλŠ” null둜 μ„€μ •
{
params: {
// query parameters둜 전솑
token: data.token,
notificationOffset: data.notificationOffset,
},
},
);
return response.data;
},
onError: (error) => {
console.error("FCM μ•Œλ¦Ό μ˜€ν”„μ…‹ μ—…λ°μ΄νŠΈ μ‹€νŒ¨:", error);
},
});
};

export default useFcmOffsetUpdate;
40 changes: 21 additions & 19 deletions src/api/hooks/useFcmUpdate.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
import { useMutation } from "@tanstack/react-query";
import { apiClient } from "../instance";
import { apiClient } from "@/api/instance";

interface FcmUpdateRequest {
interface UpdateNotificationEnabledRequest {
token: string;
isNotificatioinEnabled?: boolean;
notificationOffset?: number;
isNotificationEnabled: boolean;
}

interface FcmUpdateResponse {
message: string;
}

const useFcmUpdate = () => {
return useMutation<FcmUpdateResponse, Error, FcmUpdateRequest>({
mutationFn: async (data: FcmUpdateRequest) => {
const response = await apiClient.put<FcmUpdateResponse>(
"/api/fcm/update",
export const useUpdateNotificationEnabled = () => {
return useMutation({
mutationFn: async ({
token,
isNotificationEnabled,
}: UpdateNotificationEnabledRequest) => {
const response = await apiClient.put(
`/api/fcm/update/notification-enabled?token=${token}&isNotificationEnabled=${isNotificationEnabled}`,
{}, // 빈 body
{
token: data.token,
isNotificationEnabled: data.isNotificatioinEnabled ?? true,
notificationOffset: data.notificationOffset ?? 0,
withCredentials: true,
},
);

return response.data;
},
onError: (error) => {
console.error("FCM 토큰 μ—…λ°μ΄νŠΈ μ‹€νŒ¨:", error);
onError: (error: any) => {
if (error.response?.status === 401) {
console.error("인증 정보가 μ—†κ±°λ‚˜ λ§Œλ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.");
} else {
console.error("μ•Œλ¦Ό μ„€μ • μ—…λ°μ΄νŠΈ μ‹€νŒ¨:", error);
}
},
});
};

export default useFcmUpdate;
export default useUpdateNotificationEnabled;
Binary file added src/assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/components/features/CustomCalendar/CustomCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ const EventContent = ({
}
`,
]}
role="button"
tabIndex={0}
onClick={(e) => {
e.stopPropagation();
handleOptionClick("edit");
Expand All @@ -260,6 +262,8 @@ const EventContent = ({
}
`,
]}
role="button"
tabIndex={0}
onClick={(e) => {
e.stopPropagation();
handleOptionClick("delete");
Expand All @@ -286,10 +290,18 @@ const EventContent = ({
}
`,
]}
role="button"
tabIndex={0}
onClick={(e) => {
e.stopPropagation();
handleOptionClick("toggleComplete");
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleOptionClick("toggleComplete");
}
}}
>
μ™„λ£Œ
</div>
Expand Down
28 changes: 24 additions & 4 deletions src/components/features/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import "react-datepicker/dist/react-datepicker.css";
// StyledInput μŠ€νƒ€μΌ μ •μ˜
const StyledInput = styled.input`
width: 100%;
padding: 12px; /* 쒌우 νŒ¨λ”©μ„ λ™μΌν•˜κ²Œ μœ μ§€ */
padding: 12px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 8px;
font-size: 1rem;
margin-left: -20px;
&:focus {
outline: none;
border-color: #39a7f7;
Expand All @@ -40,6 +41,20 @@ const CustomDateInput = forwardRef<HTMLInputElement, CustomDateInputProps>(
),
);

// CustomInputWrapperλ₯Ό λ§Œλ“€μ–΄ CustomDateInput을 감싸기
const CustomInputWrapper = forwardRef<HTMLInputElement, CustomDateInputProps>(
({ value, onClick, placeholder }, ref) => (
<div className="datepicker-wrapper">
<CustomDateInput
onClick={onClick}
ref={ref}
value={value}
placeholder={placeholder}
/>
</div>
),
);

interface ReactDatePickerProps {
placeholderText: string;
onDateChange: (date: Date | null) => void;
Expand All @@ -48,7 +63,7 @@ interface ReactDatePickerProps {
dateFormat?: string;
}

const ReactDatePickerComponent = ({
const ReactDatePicker = ({
placeholderText,
onDateChange,
selectedDate,
Expand Down Expand Up @@ -119,6 +134,10 @@ const ReactDatePickerComponent = ({
li.react-datepicker__time-list-item--selected:hover {
background-color: #2196f3;
}
/* Custom styling for the wrapper to set full width */
.datepicker-wrapper {
width: 100%;
}
`}
/>
<DatePicker
Expand All @@ -132,10 +151,11 @@ const ReactDatePickerComponent = ({
showTimeSelect ? "yyyy/MM/dd HH:mm" : dateFormat
} /* μ‹œκ°„ 선택 μ‹œ 포맷 포함 */
popperPlacement="bottom-start"
customInput={<CustomDateInput placeholder={placeholderText} />}
wrapperClassName="datepicker-wrapper" /* Wrapper class name */
customInput={<CustomInputWrapper placeholder={placeholderText} />}
/>
</>
);
};

export default ReactDatePickerComponent;
export default ReactDatePicker;
77 changes: 77 additions & 0 deletions src/hooks/useNotificationSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useEffect, useRef } from "react";
import { requestForToken, setupOnMessageListener } from "@/api/firebaseConfig";
import { apiClient } from "@/api/instance";

const FCM_TOKEN_KEY = "fcm_token";

// λͺ¨λ“ˆ λ ˆλ²¨μ—μ„œ μ „μ—­ ν”Œλž˜κ·Έ μ„ μ–Έ
let isTokenRequestedGlobal = false;

const useNotificationSetup = () => {
const tokenRequestedRef = useRef(false);

useEffect(() => {
const initializeFCM = async () => {
// μ•Œλ¦Ό API 지원 μ—¬λΆ€ 확인
if (!("Notification" in window)) {
alert("ν˜„μž¬ μ‚¬μš© 쀑인 λΈŒλΌμš°μ €λŠ” μ•Œλ¦Ό κΈ°λŠ₯을 μ§€μ›ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.");
return;
}

// 이미 토큰 μš”μ²­μ΄ 진행 μ€‘μ΄κ±°λ‚˜ μ™„λ£Œλœ 경우 쀑볡 μ‹€ν–‰ 방지
if (isTokenRequestedGlobal || tokenRequestedRef.current) {
return;
}

try {
isTokenRequestedGlobal = true; // μ „μ—­ ν”Œλž˜κ·Έ μ„€μ •
tokenRequestedRef.current = true;

const existingToken = localStorage.getItem(FCM_TOKEN_KEY);
if (!existingToken) {
if (Notification.permission === "denied") {
return;
}

const permission = await Notification.requestPermission();
if (permission === "granted") {
const fcmToken = await requestForToken();
if (fcmToken) {
await apiClient.post("/api/fcm/register", { token: fcmToken });
localStorage.setItem(FCM_TOKEN_KEY, fcmToken);
console.log("New FCM token registered and saved:", fcmToken);
}
} else if (permission === "denied") {
alert(
"μ•Œλ¦Ό κΆŒν•œμ΄ κ±°λΆ€λ˜μ—ˆμŠ΅λ‹ˆλ‹€. λΈŒλΌμš°μ € μ„€μ •μ—μ„œ μ•Œλ¦Όμ„ ν™œμ„±ν™”ν•΄μ£Όμ„Έμš”.",
);
}
}

// λ©”μ‹œμ§€ λ¦¬μŠ€λ„ˆ μ„€μ •
setupOnMessageListener();
} catch (error) {
console.error("Failed to initialize FCM:", error);
// μ—λŸ¬ λ°œμƒ μ‹œ ν”Œλž˜κ·Έ μ΄ˆκΈ°ν™”
isTokenRequestedGlobal = false;
tokenRequestedRef.current = false;
}
};

initializeFCM();

return () => {
tokenRequestedRef.current = false;
};
}, []);

const getFCMToken = () => {
return localStorage.getItem(FCM_TOKEN_KEY);
};

return {
getFCMToken,
};
};

export default useNotificationSetup;
Loading

0 comments on commit a2c1f5a

Please sign in to comment.