Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
30 changes: 30 additions & 0 deletions TinyBite/GoogleService-Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>AIzaSyCYRetK8aTyzQ0B8FR8ewUvVu7F6gJHxVk</string>
<key>GCM_SENDER_ID</key>
<string>748185101542</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.tinybite2025.TinyBite</string>
<key>PROJECT_ID</key>
<string>tinybite-a7f3c</string>
<key>STORAGE_BUCKET</key>
<string>tinybite-a7f3c.firebasestorage.app</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:748185101542:ios:fa65ef6758ac9e8a28c727</string>
</dict>
</plist>
21 changes: 21 additions & 0 deletions TinyBite/api/notificationApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { privateAxios } from "@/api/axios";
import { ENDPOINT } from "@/api/urls";
/**
* FCM 토큰 등록 API
* @param token FCM 네이티브 푸시 토큰
* @param userId 유저 ID
*/
export const registerFcmToken = async (
token: string,
userId: number
): Promise<void> => {
await privateAxios.post(
ENDPOINT.FCM.TOKEN,
{ token },
{
headers: {
"User-ID": userId.toString(),
},
}
);
};
4 changes: 4 additions & 0 deletions TinyBite/api/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const ENDPOINT = {
},
USER: {
ME: "/api/v1/user/me",
WITHDRAWAL_VALIDATE: "/api/v1/user/me/withdrawal/validate",
NICKNAME_CHECK: "/api/v1/user/nickname/check",
LOCATION: "/api/v1/user/me/location",
ACTIVE_PARTIES: "/api/v1/user/parties/participating",
Expand All @@ -49,4 +50,7 @@ export const ENDPOINT = {
ONE_TO_ONE: "/api/v1/chatroom/one-to-one",
GROUP: "/api/v1/chatroom/group",
},
FCM: {
TOKEN: "/api/v1/fcm/token",
},
};
9 changes: 9 additions & 0 deletions TinyBite/api/userApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ export const checkNickname = async (nickname: string): Promise<void> => {
});
};

/**
* 탈퇴 가능 여부 확인 API
* @returns canWithdraw 값 (true: 탈퇴 가능, false: 탈퇴 불가능)
*/
export const validateWithdrawal = async (): Promise<boolean> => {
const res = await privateAxios.get(ENDPOINT.USER.WITHDRAWAL_VALIDATE);
return res.data.data.canWithdraw;
};

/**
* 회원 탈퇴 API
*/
Expand Down
2 changes: 2 additions & 0 deletions TinyBite/app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
ios: {
supportsTablet: true,
bundleIdentifier: "com.tinybite2025.TinyBite",
googleServicesFile: "./GoogleService-Info.plist",
infoPlist: {
ITSAppUsesNonExemptEncryption: false,
},
Expand All @@ -26,6 +27,7 @@ module.exports = {
edgeToEdgeEnabled: true,
predictiveBackGestureEnabled: false,
package: "com.tinybite2025.TinyBite",
googleServicesFile: "./google-services.json",
splash: {
backgroundColor: "#FE870F",
image: "./assets/images/splash-icon.png",
Expand Down
47 changes: 43 additions & 4 deletions TinyBite/app/(mypage)/setting.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { postLogout } from "@/api/authApi";
import { deleteUserMe } from "@/api/userApi";
import { deleteUserMe, validateWithdrawal } from "@/api/userApi";
import ConfirmModal from "@/components/ConfirmModal";
import { useAuthStore } from "@/stores/authStore";
import { colors } from "@/styles/colors";
Expand Down Expand Up @@ -91,7 +91,7 @@ export default function SettingScreen() {
</View>
{/* Content */}
<View style={styles.contentWrapper}>
{/* 알림 설정 */}
{/* 알림 설정
<Pressable
style={styles.settingItem}
onPress={() => router.push("../(mypage)/notification")}
Expand All @@ -110,8 +110,8 @@ export default function SettingScreen() {
style={styles.chevronIcon}
/>
</Pressable>
{/* 구분선 */}
<View style={styles.divider} />
*/}
{/* 내 동네 설정 */}
{/* <Pressable
style={styles.settingItem}
Expand Down Expand Up @@ -160,7 +160,46 @@ export default function SettingScreen() {
<View style={styles.divider} />
<Pressable
style={styles.settingItem}
onPress={() => setShowWithdrawModal(true)}
onPress={async () => {
try {
const canWithdraw = await validateWithdrawal();
if (canWithdraw) {
setShowWithdrawModal(true);
} else {
Toast.show({
type: "basicToast",
props: { text: "진행 중인 파티가 있어 탈퇴할 수 없습니다." },
position: "bottom",
bottomOffset: 98,
visibilityTime: 2000,
});
}
} catch (error: any) {
const status = error?.response?.status;
if (status === 500) {
Toast.show({
type: "basicToast",
props: {
text: "진행 중인 파티가 있어 탈퇴할 수 없습니다.",
},
position: "bottom",
bottomOffset: 98,
visibilityTime: 2000,
});
} else {
// 기타 서버 오류
Toast.show({
type: "basicToast",
props: {
text: "잠시 후 다시 시도해주세요.",
},
position: "bottom",
bottomOffset: 98,
visibilityTime: 2000,
});
}
}
}}
>
<View style={styles.settingItemLeft}>
<Image
Expand Down
3 changes: 3 additions & 0 deletions TinyBite/app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useUserQuery } from "@/hooks/queries/useUser";
import { registerPushToken } from "@/hooks/usePushNotification";
import { useAuthStore } from "@/stores/authStore";
import { colors } from "@/styles/colors";
import { textStyles } from "@/styles/typography/textStyles";
Expand All @@ -21,6 +22,8 @@ export default function TabsLayout() {
useEffect(() => {
if (user) {
setUser(user);
// 푸시 알림 토큰 등록
registerPushToken(user.userId);
}
}, [user, setUser]);

Expand Down
1 change: 0 additions & 1 deletion TinyBite/app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { StyleSheet, View } from "react-native";

export default function HomeScreen() {
const [isMenuOpen, setIsMenuOpen] = useState(false);

return (
<View style={styles.container}>
<MainHeader />
Expand Down
12 changes: 12 additions & 0 deletions TinyBite/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { toastConfig } from "@/lib/toast/toastConfig";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import * as Notifications from "expo-notifications";
import { Stack } from "expo-router";
import { KeyboardProvider } from "react-native-keyboard-controller";
import Toast from "react-native-toast-message";

const queryClient = new QueryClient();

// 알림 핸들러 설정 - 앱이 포그라운드에 있을 때 알림 표시
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
shouldShowBanner: true,
shouldShowList: true,
}),
});

export default function RootLayout() {
return (
<QueryClientProvider client={queryClient}>
Expand Down
29 changes: 29 additions & 0 deletions TinyBite/google-services.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "748185101542",
"project_id": "tinybite-a7f3c",
"storage_bucket": "tinybite-a7f3c.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:748185101542:android:a3e225373c65b25f28c727",
"android_client_info": {
"package_name": "com.tinybite2025.TinyBite"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBWuDAIiVfblq9U3kCChqeLfIWUkHe6ZnM"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
41 changes: 41 additions & 0 deletions TinyBite/hooks/usePushNotification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { registerFcmToken } from "@/api/notificationApi";
import * as Notifications from "expo-notifications";

/**
* FCM 푸시 토큰 등록 훅
* @param userId 유저 ID
*/
export const registerPushToken = async (userId: number): Promise<void> => {
try {
//개발 환경에서 테스트 할 때 주석 처리
// 실제 기기에서만 작동
//if (!Device.isDevice) {
// console.log("실제 기기에서만 작동합니다.");
// return;
//}

// 1. 권한 확인 및 요청
const { status: existingStatus } =
await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== "granted") {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}

if (finalStatus !== "granted") {
console.log("알림 권한 획득 실패");
return;
}

// 2. FCM 네이티브 토큰 가져오기
const tokenData = await Notifications.getDevicePushTokenAsync();
const fcmToken = tokenData.data;

// 3. 서버 API 호출
await registerFcmToken(fcmToken, userId);
console.log("FCM 토큰 등록 성공");
} catch (error) {
console.error("토큰 등록 중 에러 발생:", error);
}
};
Loading