添加Apple登录
This commit is contained in:
2
app.json
2
app.json
@@ -11,6 +11,7 @@
|
|||||||
"ios": {
|
"ios": {
|
||||||
"supportsTablet": true,
|
"supportsTablet": true,
|
||||||
"bundleIdentifier": "physical",
|
"bundleIdentifier": "physical",
|
||||||
|
"usesAppleSignIn": true,
|
||||||
"infoPlist": {
|
"infoPlist": {
|
||||||
"ITSAppUsesNonExemptEncryption": false
|
"ITSAppUsesNonExemptEncryption": false
|
||||||
}
|
}
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"expo-router",
|
"expo-router",
|
||||||
|
"expo-apple-authentication",
|
||||||
[
|
[
|
||||||
"expo-splash-screen",
|
"expo-splash-screen",
|
||||||
{
|
{
|
||||||
|
|||||||
317
app/profile.tsx
317
app/profile.tsx
@@ -1,19 +1,34 @@
|
|||||||
|
import * as AppleAuthentication from "expo-apple-authentication";
|
||||||
|
import Constants from "expo-constants";
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ScrollView, StyleSheet, TouchableOpacity, View } from "react-native";
|
import {
|
||||||
|
Modal,
|
||||||
|
Platform,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
} from "react-native";
|
||||||
|
|
||||||
import { ThemedText } from "@/components/themed-text";
|
import { ThemedText } from "@/components/themed-text";
|
||||||
import { IconSymbol } from "@/components/ui/icon-symbol";
|
import { IconSymbol } from "@/components/ui/icon-symbol";
|
||||||
import { useTheme } from "@/context/ThemeContext";
|
import { useTheme } from "@/context/ThemeContext";
|
||||||
import { changeLanguage } from "@/i18n";
|
import { changeLanguage } from "@/i18n";
|
||||||
|
import { appleSignIn, fetchUserProfile, logout } from "@/lib/api";
|
||||||
|
import { storage } from "@/lib/storage";
|
||||||
|
import type { UserProfile } from "@/types/api";
|
||||||
|
|
||||||
export default function ProfileScreen() {
|
export default function ProfileScreen() {
|
||||||
const { theme, toggleTheme, setTheme, isSystemTheme, useSystemTheme } =
|
const { theme, toggleTheme, setTheme, isSystemTheme, useSystemTheme } =
|
||||||
useTheme();
|
useTheme();
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
|
const [appleAvailable, setAppleAvailable] = React.useState(false);
|
||||||
|
const [user, setUser] = React.useState<UserProfile | null>(null);
|
||||||
|
const [loginModalVisible, setLoginModalVisible] = React.useState(false);
|
||||||
|
|
||||||
const currentLanguage = i18n.language;
|
const currentLanguage = i18n.language;
|
||||||
|
|
||||||
@@ -22,6 +37,107 @@ export default function ProfileScreen() {
|
|||||||
changeLanguage(nextLang);
|
changeLanguage(nextLang);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAppleSignIn = async () => {
|
||||||
|
try {
|
||||||
|
const credential = await AppleAuthentication.signInAsync({
|
||||||
|
requestedScopes: [
|
||||||
|
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
||||||
|
AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Apple credential:", {
|
||||||
|
fullName: credential.fullName,
|
||||||
|
email: credential.email,
|
||||||
|
user: credential.user,
|
||||||
|
});
|
||||||
|
|
||||||
|
const deviceId = Constants.deviceId || Constants.sessionId || "unknown";
|
||||||
|
const platformVersion = Platform.Version.toString();
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
authorizationCode: credential.authorizationCode || "",
|
||||||
|
identityToken: credential.identityToken || "",
|
||||||
|
deviceInfo: {
|
||||||
|
deviceId,
|
||||||
|
platform: Platform.OS,
|
||||||
|
platformVersion,
|
||||||
|
},
|
||||||
|
user:
|
||||||
|
credential.fullName && (credential.fullName.givenName || credential.fullName.familyName)
|
||||||
|
? {
|
||||||
|
name: {
|
||||||
|
firstName: credential.fullName.givenName || undefined,
|
||||||
|
lastName: credential.fullName.familyName || undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("登录参数", options);
|
||||||
|
|
||||||
|
const res = await appleSignIn(options);
|
||||||
|
|
||||||
|
console.log("登录响应", res);
|
||||||
|
|
||||||
|
await storage.setAccessToken(res.accessToken);
|
||||||
|
await storage.setRefreshToken(res.refreshToken);
|
||||||
|
|
||||||
|
if (res.user) {
|
||||||
|
await storage.setUser(res.user);
|
||||||
|
setUser(res.user);
|
||||||
|
} else {
|
||||||
|
const profile = await fetchUserProfile();
|
||||||
|
await storage.setUser(profile);
|
||||||
|
setUser(profile);
|
||||||
|
}
|
||||||
|
setLoginModalVisible(false);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error?.code === "ERR_REQUEST_CANCELED") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error("Apple sign in error:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoginPress = () => {
|
||||||
|
setLoginModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
AppleAuthentication.isAvailableAsync().then(setAppleAvailable);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const loadUser = async () => {
|
||||||
|
const savedUser = await storage.getUser();
|
||||||
|
if (savedUser) {
|
||||||
|
setUser(savedUser);
|
||||||
|
try {
|
||||||
|
const profile = await fetchUserProfile();
|
||||||
|
await storage.setUser(profile);
|
||||||
|
setUser(profile);
|
||||||
|
} catch {
|
||||||
|
setUser(savedUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadUser();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await logout();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Logout error:", error);
|
||||||
|
} finally {
|
||||||
|
await storage.clear();
|
||||||
|
setUser(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLoggedIn = !!user;
|
||||||
|
|
||||||
const iconColor = isDark ? "#FFFFFF" : "#000000";
|
const iconColor = isDark ? "#FFFFFF" : "#000000";
|
||||||
const textColor = isDark ? "#FFFFFF" : "#000000";
|
const textColor = isDark ? "#FFFFFF" : "#000000";
|
||||||
const subTextColor = isDark ? "#AAAAAA" : "#666666";
|
const subTextColor = isDark ? "#AAAAAA" : "#666666";
|
||||||
@@ -51,29 +167,59 @@ export default function ProfileScreen() {
|
|||||||
{ backgroundColor: isDark ? "#000" : "#f2f2f7" },
|
{ backgroundColor: isDark ? "#000" : "#f2f2f7" },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{/* User Info Section */}
|
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.section,
|
styles.section,
|
||||||
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
|
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<View style={styles.profileHeader}>
|
{isLoggedIn ? (
|
||||||
<Image
|
<>
|
||||||
source={{ uri: "https://via.placeholder.com/100" }}
|
<View style={styles.profileHeader}>
|
||||||
style={styles.avatar}
|
<Image
|
||||||
contentFit="cover"
|
source={{ uri: user?.avatar || "https://via.placeholder.com/100" }}
|
||||||
/>
|
style={styles.avatar}
|
||||||
<View style={styles.profileInfo}>
|
contentFit="cover"
|
||||||
<ThemedText type="title">{t("profile.name")}</ThemedText>
|
/>
|
||||||
<ThemedText style={{ color: subTextColor }}>
|
<View style={styles.profileInfo}>
|
||||||
user@example.com
|
<ThemedText type="title">
|
||||||
</ThemedText>
|
{user?.nickname || t("profile.name")}
|
||||||
</View>
|
</ThemedText>
|
||||||
</View>
|
<ThemedText style={{ color: subTextColor }}>
|
||||||
|
{user?.appleId || ""}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.logoutButton,
|
||||||
|
{
|
||||||
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderTopColor: isDark ? "#38383a" : "#c6c6c8",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onPress={handleLogout}
|
||||||
|
>
|
||||||
|
<ThemedText style={{ color: "#FF3B30" }}>登出</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.loginCard}
|
||||||
|
onPress={handleLoginPress}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
>
|
||||||
|
<View style={styles.loginIcon} />
|
||||||
|
<View style={styles.loginInfo}>
|
||||||
|
<ThemedText style={styles.loginTitle}>登录</ThemedText>
|
||||||
|
<ThemedText style={{ color: subTextColor }}>
|
||||||
|
点击登录
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Settings Section */}
|
|
||||||
<ThemedText style={styles.sectionTitle}>
|
<ThemedText style={styles.sectionTitle}>
|
||||||
{t("settings.title")}
|
{t("settings.title")}
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
@@ -84,7 +230,6 @@ export default function ProfileScreen() {
|
|||||||
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
|
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{/* Theme Setting */}
|
|
||||||
<View style={styles.settingItem}>
|
<View style={styles.settingItem}>
|
||||||
<View style={styles.settingLabel}>
|
<View style={styles.settingLabel}>
|
||||||
<IconSymbol
|
<IconSymbol
|
||||||
@@ -104,7 +249,6 @@ export default function ProfileScreen() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Language Setting */}
|
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.settingItem,
|
styles.settingItem,
|
||||||
@@ -134,7 +278,92 @@ export default function ProfileScreen() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* <ThemedText style={styles.sectionTitle}>登录</ThemedText>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.section,
|
||||||
|
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{appleAvailable && (
|
||||||
|
<View style={styles.settingItem}>
|
||||||
|
<AppleAuthentication.AppleAuthenticationButton
|
||||||
|
buttonType={
|
||||||
|
AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN
|
||||||
|
}
|
||||||
|
buttonStyle={
|
||||||
|
isDark
|
||||||
|
? AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
|
||||||
|
: AppleAuthentication.AppleAuthenticationButtonStyle.BLACK
|
||||||
|
}
|
||||||
|
cornerRadius={8}
|
||||||
|
style={{ width: "100%", height: 44 }}
|
||||||
|
onPress={handleAppleSignIn}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.settingItem,
|
||||||
|
{
|
||||||
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderTopColor: isDark ? "#38383a" : "#c6c6c8",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<TouchableOpacity style={styles.googleButton} onPress={() => {}}>
|
||||||
|
<ThemedText>Google 登录</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View> */}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
<Modal
|
||||||
|
visible={loginModalVisible}
|
||||||
|
transparent
|
||||||
|
animationType="fade"
|
||||||
|
onRequestClose={() => setLoginModalVisible(false)}
|
||||||
|
>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.modalMask}
|
||||||
|
activeOpacity={1}
|
||||||
|
onPress={() => setLoginModalVisible(false)}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.modalCard,
|
||||||
|
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<ThemedText style={styles.modalTitle}>选择登录方式</ThemedText>
|
||||||
|
{appleAvailable && (
|
||||||
|
<AppleAuthentication.AppleAuthenticationButton
|
||||||
|
buttonType={
|
||||||
|
AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN
|
||||||
|
}
|
||||||
|
buttonStyle={
|
||||||
|
isDark
|
||||||
|
? AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
|
||||||
|
: AppleAuthentication.AppleAuthenticationButtonStyle.BLACK
|
||||||
|
}
|
||||||
|
cornerRadius={8}
|
||||||
|
style={{ width: "100%", height: 44 }}
|
||||||
|
onPress={handleAppleSignIn}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TouchableOpacity style={styles.googleButton} onPress={() => {}}>
|
||||||
|
<ThemedText>Google 登录</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.modalCancel}
|
||||||
|
onPress={() => setLoginModalVisible(false)}
|
||||||
|
>
|
||||||
|
<ThemedText>取消</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -190,4 +419,56 @@ const styles = StyleSheet.create({
|
|||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
paddingVertical: 5,
|
paddingVertical: 5,
|
||||||
},
|
},
|
||||||
|
googleButton: {
|
||||||
|
width: "100%",
|
||||||
|
height: 44,
|
||||||
|
borderRadius: 8,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: "#c6c6c8",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
loginCard: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingVertical: 12,
|
||||||
|
},
|
||||||
|
loginIcon: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 24,
|
||||||
|
backgroundColor: "#c6c6c8",
|
||||||
|
},
|
||||||
|
loginInfo: {
|
||||||
|
marginLeft: 12,
|
||||||
|
},
|
||||||
|
loginTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
},
|
||||||
|
modalMask: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: "rgba(0,0,0,0.4)",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
},
|
||||||
|
modalCard: {
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: 16,
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
modalTitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
modalCancel: {
|
||||||
|
height: 40,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
logoutButton: {
|
||||||
|
paddingVertical: 12,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,4 +14,8 @@ export const API_ENDPOINTS = {
|
|||||||
ODDS: "/v1/api/odds",
|
ODDS: "/v1/api/odds",
|
||||||
SEARCH: "/v1/api/search",
|
SEARCH: "/v1/api/search",
|
||||||
H2H: "/v1/api/h2h",
|
H2H: "/v1/api/h2h",
|
||||||
|
APPLE_SIGNIN: "/v1/api/auth/apple-signin",
|
||||||
|
LOGOUT: "/v1/api/auth/logout",
|
||||||
|
REFRESH_TOKEN: "/v1/api/auth/refresh-token",
|
||||||
|
USER_PROFILE: "/v1/api/user/profile",
|
||||||
};
|
};
|
||||||
|
|||||||
110
lib/api.ts
110
lib/api.ts
@@ -2,6 +2,8 @@ import { API_CONFIG, API_ENDPOINTS } from "@/constants/api";
|
|||||||
import {
|
import {
|
||||||
ApiListResponse,
|
ApiListResponse,
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
|
AppleSignInRequest,
|
||||||
|
AppleSignInResponse,
|
||||||
Country,
|
Country,
|
||||||
H2HData,
|
H2HData,
|
||||||
League,
|
League,
|
||||||
@@ -9,11 +11,15 @@ import {
|
|||||||
Match,
|
Match,
|
||||||
MatchDetailData,
|
MatchDetailData,
|
||||||
OddsData,
|
OddsData,
|
||||||
|
RefreshTokenRequest,
|
||||||
|
RefreshTokenResponse,
|
||||||
SearchResult,
|
SearchResult,
|
||||||
Sport,
|
Sport,
|
||||||
UpcomingMatch,
|
UpcomingMatch,
|
||||||
|
UserProfile,
|
||||||
} from "@/types/api";
|
} from "@/types/api";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { storage } from "./storage";
|
||||||
|
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
baseURL: API_CONFIG.BASE_URL,
|
baseURL: API_CONFIG.BASE_URL,
|
||||||
@@ -23,6 +29,55 @@ const apiClient = axios.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
apiClient.interceptors.request.use(async (config) => {
|
||||||
|
const token = await storage.getAccessToken();
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshTokenApi = async (
|
||||||
|
request: RefreshTokenRequest
|
||||||
|
): Promise<RefreshTokenResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.post<
|
||||||
|
ApiResponse<RefreshTokenResponse>
|
||||||
|
>(API_ENDPOINTS.REFRESH_TOKEN, request);
|
||||||
|
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(response.data.message);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Refresh token error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
apiClient.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
async (error) => {
|
||||||
|
const originalRequest = error.config;
|
||||||
|
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||||
|
originalRequest._retry = true;
|
||||||
|
try {
|
||||||
|
const refreshTokenValue = await storage.getRefreshToken();
|
||||||
|
if (refreshTokenValue) {
|
||||||
|
const res = await refreshTokenApi({ refreshToken: refreshTokenValue });
|
||||||
|
await storage.setAccessToken(res.accessToken);
|
||||||
|
originalRequest.headers.Authorization = `Bearer ${res.accessToken}`;
|
||||||
|
return apiClient(originalRequest);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
await storage.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const fetchSports = async (): Promise<Sport[]> => {
|
export const fetchSports = async (): Promise<Sport[]> => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get<ApiResponse<ApiListResponse<Sport>>>(
|
const response = await apiClient.get<ApiResponse<ApiListResponse<Sport>>>(
|
||||||
@@ -311,3 +366,58 @@ export const fetchH2H = async (
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const appleSignIn = async (
|
||||||
|
request: AppleSignInRequest
|
||||||
|
): Promise<AppleSignInResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.post<
|
||||||
|
ApiResponse<AppleSignInResponse>
|
||||||
|
>(API_ENDPOINTS.APPLE_SIGNIN, request);
|
||||||
|
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(response.data.message);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Apple sign in error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logout = async (): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.post<ApiResponse<string>>(
|
||||||
|
API_ENDPOINTS.LOGOUT
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(response.data.message);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Logout error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const refreshToken = refreshTokenApi;
|
||||||
|
|
||||||
|
export const fetchUserProfile = async (): Promise<UserProfile> => {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get<ApiResponse<UserProfile>>(
|
||||||
|
API_ENDPOINTS.USER_PROFILE
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(response.data.message);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Fetch user profile error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
48
lib/storage.ts
Normal file
48
lib/storage.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
|
import type { UserProfile } from "@/types/api";
|
||||||
|
|
||||||
|
const STORAGE_KEYS = {
|
||||||
|
ACCESS_TOKEN: "access_token",
|
||||||
|
REFRESH_TOKEN: "refresh_token",
|
||||||
|
USER: "user",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const storage = {
|
||||||
|
async setAccessToken(token: string): Promise<void> {
|
||||||
|
await AsyncStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, token);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getAccessToken(): Promise<string | null> {
|
||||||
|
return await AsyncStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
||||||
|
},
|
||||||
|
|
||||||
|
async setRefreshToken(token: string): Promise<void> {
|
||||||
|
await AsyncStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, token);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getRefreshToken(): Promise<string | null> {
|
||||||
|
return await AsyncStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN);
|
||||||
|
},
|
||||||
|
|
||||||
|
async setUser(user: UserProfile): Promise<void> {
|
||||||
|
await AsyncStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(user));
|
||||||
|
},
|
||||||
|
|
||||||
|
async getUser(): Promise<UserProfile | null> {
|
||||||
|
const userStr = await AsyncStorage.getItem(STORAGE_KEYS.USER);
|
||||||
|
if (!userStr) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(userStr) as UserProfile;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async clear(): Promise<void> {
|
||||||
|
await AsyncStorage.multiRemove([
|
||||||
|
STORAGE_KEYS.ACCESS_TOKEN,
|
||||||
|
STORAGE_KEYS.REFRESH_TOKEN,
|
||||||
|
STORAGE_KEYS.USER,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
37
package-lock.json
generated
37
package-lock.json
generated
@@ -16,6 +16,7 @@
|
|||||||
"@react-navigation/native": "^7.1.8",
|
"@react-navigation/native": "^7.1.8",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"expo": "~54.0.31",
|
"expo": "~54.0.31",
|
||||||
|
"expo-apple-authentication": "~8.0.8",
|
||||||
"expo-blur": "~15.0.8",
|
"expo-blur": "~15.0.8",
|
||||||
"expo-constants": "~18.0.13",
|
"expo-constants": "~18.0.13",
|
||||||
"expo-dev-client": "~6.0.20",
|
"expo-dev-client": "~6.0.20",
|
||||||
@@ -1480,7 +1481,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
@@ -3087,7 +3087,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-7.7.10.tgz",
|
"resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-7.7.10.tgz",
|
||||||
"integrity": "sha512-FGYU5Ebd2whTa4Z+RBCxnWqmyWIQGTJ7PAAhk2RjlVrEXLU0HaFR5JGmEHuNm/Cm9xX3xCwOxYZiA0Xi/DeyAA==",
|
"integrity": "sha512-FGYU5Ebd2whTa4Z+RBCxnWqmyWIQGTJ7PAAhk2RjlVrEXLU0HaFR5JGmEHuNm/Cm9xX3xCwOxYZiA0Xi/DeyAA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/elements": "^2.9.3",
|
"@react-navigation/elements": "^2.9.3",
|
||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
@@ -3132,7 +3131,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.27.tgz",
|
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.27.tgz",
|
||||||
"integrity": "sha512-kW7LGP/RrisktpGyizTKw6HwSeQJdXnAN9L8GyQJcJAlgL9YtfEg6yEyD5n9RWH90CL8G0cRyUhphKIAFf4lVw==",
|
"integrity": "sha512-kW7LGP/RrisktpGyizTKw6HwSeQJdXnAN9L8GyQJcJAlgL9YtfEg6yEyD5n9RWH90CL8G0cRyUhphKIAFf4lVw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/core": "^7.13.7",
|
"@react-navigation/core": "^7.13.7",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
@@ -3331,7 +3329,6 @@
|
|||||||
"integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==",
|
"integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -3402,7 +3399,6 @@
|
|||||||
"integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==",
|
"integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.52.0",
|
"@typescript-eslint/scope-manager": "8.52.0",
|
||||||
"@typescript-eslint/types": "8.52.0",
|
"@typescript-eslint/types": "8.52.0",
|
||||||
@@ -3964,7 +3960,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -4667,7 +4662,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -5666,7 +5660,6 @@
|
|||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -5863,7 +5856,6 @@
|
|||||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.9",
|
"array-includes": "^3.1.9",
|
||||||
@@ -6102,7 +6094,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/expo/-/expo-54.0.31.tgz",
|
"resolved": "https://registry.npmjs.org/expo/-/expo-54.0.31.tgz",
|
||||||
"integrity": "sha512-kQ3RDqA/a59I7y+oqQGyrPbbYlgPMUdKBOgvFLpoHbD2bCM+F75i4N0mUijy7dG5F/CUCu2qHmGGUCXBbMDkCg==",
|
"integrity": "sha512-kQ3RDqA/a59I7y+oqQGyrPbbYlgPMUdKBOgvFLpoHbD2bCM+F75i4N0mUijy7dG5F/CUCu2qHmGGUCXBbMDkCg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.0",
|
"@babel/runtime": "^7.20.0",
|
||||||
"@expo/cli": "54.0.21",
|
"@expo/cli": "54.0.21",
|
||||||
@@ -6150,6 +6141,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expo-apple-authentication": {
|
||||||
|
"version": "8.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-apple-authentication/-/expo-apple-authentication-8.0.8.tgz",
|
||||||
|
"integrity": "sha512-TwCHWXYR1kS0zaeV7QZKLWYluxsvqL31LFJubzK30njZqeWoWO89HZ8nZVaeXbFV1LrArKsze4BmMb+94wS0AQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"expo": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/expo-asset": {
|
"node_modules/expo-asset": {
|
||||||
"version": "12.0.12",
|
"version": "12.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-12.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-12.0.12.tgz",
|
||||||
@@ -6181,7 +6182,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz",
|
||||||
"integrity": "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ==",
|
"integrity": "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/config": "~12.0.13",
|
"@expo/config": "~12.0.13",
|
||||||
"@expo/env": "~2.0.8"
|
"@expo/env": "~2.0.8"
|
||||||
@@ -6285,7 +6285,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.10.tgz",
|
||||||
"integrity": "sha512-UqyNaaLKRpj4pKAP4HZSLnuDQqueaO5tB1c/NWu5vh1/LF9ulItyyg2kF/IpeOp0DeOLk0GY0HrIXaKUMrwB+Q==",
|
"integrity": "sha512-UqyNaaLKRpj4pKAP4HZSLnuDQqueaO5tB1c/NWu5vh1/LF9ulItyyg2kF/IpeOp0DeOLk0GY0HrIXaKUMrwB+Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fontfaceobserver": "^2.1.0"
|
"fontfaceobserver": "^2.1.0"
|
||||||
},
|
},
|
||||||
@@ -6383,7 +6382,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.11.tgz",
|
||||||
"integrity": "sha512-+VSaNL5om3kOp/SSKO5qe6cFgfSIWnnQDSbA7XLs3ECkYzXRquk5unxNS3pg7eK5kNUmQ4kgLI7MhTggAEUBLA==",
|
"integrity": "sha512-+VSaNL5om3kOp/SSKO5qe6cFgfSIWnnQDSbA7XLs3ECkYzXRquk5unxNS3pg7eK5kNUmQ4kgLI7MhTggAEUBLA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"expo-constants": "~18.0.12",
|
"expo-constants": "~18.0.12",
|
||||||
"invariant": "^2.2.4"
|
"invariant": "^2.2.4"
|
||||||
@@ -7768,7 +7766,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4"
|
"@babel/runtime": "^7.28.4"
|
||||||
},
|
},
|
||||||
@@ -10545,7 +10542,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -10565,7 +10561,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.26.0"
|
"scheduler": "^0.26.0"
|
||||||
},
|
},
|
||||||
@@ -10629,7 +10624,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz",
|
||||||
"integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==",
|
"integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/create-cache-key-function": "^29.7.0",
|
"@jest/create-cache-key-function": "^29.7.0",
|
||||||
"@react-native/assets-registry": "0.81.5",
|
"@react-native/assets-registry": "0.81.5",
|
||||||
@@ -10703,7 +10697,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz",
|
||||||
"integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==",
|
"integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@egjs/hammerjs": "^2.0.17",
|
"@egjs/hammerjs": "^2.0.17",
|
||||||
"hoist-non-react-statics": "^3.3.0",
|
"hoist-non-react-statics": "^3.3.0",
|
||||||
@@ -10741,7 +10734,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.6.tgz",
|
||||||
"integrity": "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ==",
|
"integrity": "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-native-is-edge-to-edge": "^1.2.1",
|
"react-native-is-edge-to-edge": "^1.2.1",
|
||||||
"semver": "7.7.2"
|
"semver": "7.7.2"
|
||||||
@@ -10770,7 +10762,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz",
|
||||||
"integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==",
|
"integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*",
|
"react": "*",
|
||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
@@ -10781,7 +10772,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz",
|
||||||
"integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==",
|
"integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-freeze": "^1.0.0",
|
"react-freeze": "^1.0.0",
|
||||||
"react-native-is-edge-to-edge": "^1.2.1",
|
"react-native-is-edge-to-edge": "^1.2.1",
|
||||||
@@ -10807,7 +10797,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
||||||
"integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==",
|
"integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.18.6",
|
"@babel/runtime": "^7.18.6",
|
||||||
"@react-native/normalize-colors": "^0.74.1",
|
"@react-native/normalize-colors": "^0.74.1",
|
||||||
@@ -10840,7 +10829,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz",
|
||||||
"integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==",
|
"integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"invariant": "2.2.4"
|
"invariant": "2.2.4"
|
||||||
@@ -10965,7 +10953,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||||
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
|
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -12286,7 +12273,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -12493,7 +12479,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"android": "expo start --android",
|
"android": "expo run:android",
|
||||||
"ios": "expo start --ios",
|
"ios": "expo run:ios",
|
||||||
"web": "expo start --web",
|
"web": "expo start --web",
|
||||||
"lint": "expo lint"
|
"lint": "expo lint"
|
||||||
},
|
},
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
"@react-navigation/native": "^7.1.8",
|
"@react-navigation/native": "^7.1.8",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"expo": "~54.0.31",
|
"expo": "~54.0.31",
|
||||||
|
"expo-apple-authentication": "~8.0.8",
|
||||||
"expo-blur": "~15.0.8",
|
"expo-blur": "~15.0.8",
|
||||||
"expo-constants": "~18.0.13",
|
"expo-constants": "~18.0.13",
|
||||||
"expo-dev-client": "~6.0.20",
|
"expo-dev-client": "~6.0.20",
|
||||||
|
|||||||
44
types/api.ts
44
types/api.ts
@@ -431,3 +431,47 @@ export interface H2HData {
|
|||||||
firstTeamResults: H2HMatch[];
|
firstTeamResults: H2HMatch[];
|
||||||
secondTeamResults: H2HMatch[];
|
secondTeamResults: H2HMatch[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AppleSignInRequest {
|
||||||
|
authorizationCode: string;
|
||||||
|
deviceInfo: {
|
||||||
|
deviceId: string;
|
||||||
|
platform: string;
|
||||||
|
platformVersion: string;
|
||||||
|
};
|
||||||
|
identityToken: string;
|
||||||
|
user?: {
|
||||||
|
name?: {
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppleSignInResponse {
|
||||||
|
accessToken: string;
|
||||||
|
expiresIn: number;
|
||||||
|
refreshToken: string;
|
||||||
|
user: {
|
||||||
|
id: number;
|
||||||
|
appleId: string;
|
||||||
|
nickname: string;
|
||||||
|
avatar: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RefreshTokenRequest {
|
||||||
|
refreshToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RefreshTokenResponse {
|
||||||
|
accessToken: string;
|
||||||
|
expiresIn: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserProfile {
|
||||||
|
id: number;
|
||||||
|
appleId: string;
|
||||||
|
nickname: string;
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user