diff options
| author | JustZvan <justzvan@justzvan.xyz> | 2026-04-06 15:32:51 +0200 |
|---|---|---|
| committer | JustZvan <justzvan@justzvan.xyz> | 2026-04-06 15:32:51 +0200 |
| commit | 3273e7a0fbbce82f4ce6cacbcdb7b6d6848f6c1b (patch) | |
| tree | 7662ab528c950e5b3605d3f134dc89f399417c8d /lib | |
| parent | 7eb8ccae48b0cc18a9dcaa9c3626a02df8e6d919 (diff) | |
feat: gallery scanning preferences
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/auth.tsx | 25 | ||||
| -rw-r--r-- | lib/google-auth.ts | 116 | ||||
| -rw-r--r-- | lib/locales.ts | 63 |
3 files changed, 198 insertions, 6 deletions
diff --git a/lib/auth.tsx b/lib/auth.tsx index b05c6a7..ac394f4 100644 --- a/lib/auth.tsx +++ b/lib/auth.tsx @@ -1,9 +1,9 @@ import { - createContext, - ReactNode, - useContext, - useEffect, - useState, + createContext, + ReactNode, + useContext, + useEffect, + useState, } from "react"; import { apiClient } from "../api/client"; @@ -17,6 +17,9 @@ type AuthContextType = AuthState & { email: string, password: string, ) => Promise<{ success: boolean; reason?: string }>; + signInWithGoogle: ( + idToken: string, + ) => Promise<{ success: boolean; reason?: string }>; signUp: ( email: string, password: string, @@ -60,13 +63,23 @@ export function AuthProvider({ children }: { children: ReactNode }) { return { success: result.success, reason: result.reason }; }; + const signInWithGoogle = async (idToken: string) => { + const result = await apiClient.signInWithGoogle(idToken); + if (result.success) { + setState({ isLoading: false, isAuthenticated: true }); + } + return { success: result.success, reason: result.reason }; + }; + const signOut = async () => { await apiClient.signOut(); setState({ isLoading: false, isAuthenticated: false }); }; return ( - <AuthContext.Provider value={{ ...state, signIn, signUp, signOut }}> + <AuthContext.Provider + value={{ ...state, signIn, signInWithGoogle, signUp, signOut }} + > {children} </AuthContext.Provider> ); diff --git a/lib/google-auth.ts b/lib/google-auth.ts new file mode 100644 index 0000000..769da55 --- /dev/null +++ b/lib/google-auth.ts @@ -0,0 +1,116 @@ +import { ResponseType } from "expo-auth-session"; +import * as Google from "expo-auth-session/providers/google"; +import Constants from "expo-constants"; +import * as WebBrowser from "expo-web-browser"; +import { useEffect, useMemo, useState } from "react"; +import { useAuth } from "./auth"; +import { t } from "./locales"; + +WebBrowser.maybeCompleteAuthSession(); + +type GoogleClientIds = { + iosClientId?: string; + androidClientId?: string; + webClientId?: string; +}; + +function getGoogleClientIds(): GoogleClientIds { + const env = (process as any)?.env || {}; + const extra = + (Constants.manifest as any)?.extra || + (Constants.expoConfig as any)?.extra || + {}; + + return { + iosClientId: + env.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID || extra.GOOGLE_IOS_CLIENT_ID, + androidClientId: + env.EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID || + extra.GOOGLE_ANDROID_CLIENT_ID, + webClientId: + env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID || + extra.GOOGLE_WEB_CLIENT_ID || + env.EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID || + extra.GOOGLE_EXPO_CLIENT_ID, + }; +} + +export function useGoogleAuth() { + const { signInWithGoogle } = useAuth(); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(""); + + const clientIds = useMemo(() => getGoogleClientIds(), []); + + const [request, response, promptAsync] = Google.useAuthRequest({ + iosClientId: clientIds.iosClientId, + androidClientId: clientIds.androidClientId, + webClientId: clientIds.webClientId, + responseType: ResponseType.IdToken, + scopes: ["openid", "profile", "email"], + selectAccount: true, + }); + + useEffect(() => { + const run = async () => { + if (!response) return; + + if (response.type === "success") { + const idToken = + response.params?.id_token || response.authentication?.idToken; + + if (!idToken) { + setError(t("googleMissingIdToken")); + setIsLoading(false); + return; + } + + const result = await signInWithGoogle(idToken); + if (!result.success) { + setError(result.reason || t("googleSignInError")); + } + setIsLoading(false); + return; + } + + if (response.type === "error") { + setError(response.error?.message || t("googleSignInError")); + } + + // On cancel/dismissed/locked, just reset loading and keep the current screen state. + setIsLoading(false); + }; + + run().catch(() => { + setError(t("googleSignInError")); + setIsLoading(false); + }); + }, [response, signInWithGoogle]); + + const continueWithGoogle = async () => { + setError(""); + + if (!request) { + setError(t("googleConfigMissing")); + return; + } + + setIsLoading(true); + + try { + const result = await promptAsync(); + if (result.type === "cancel" || result.type === "dismiss") { + setIsLoading(false); + } + } catch { + setError(t("googleSignInError")); + setIsLoading(false); + } + }; + + return { + continueWithGoogle, + isLoading, + error, + }; +} diff --git a/lib/locales.ts b/lib/locales.ts index d78621f..9ba29ba 100644 --- a/lib/locales.ts +++ b/lib/locales.ts @@ -36,12 +36,36 @@ const en = { createAccount: "Create Account", alreadyHaveAccount: "Already have an account?", dontHaveAccount: "Don't have an account?", + forgotPassword: "Forgot password?", + resetPassword: "Reset Password", + resetPasswordHelp: "Request a reset link or enter a token to set a new password.", + sendResetLink: "Send reset link", + resetPasswordToken: "Reset token", + resetPasswordTokenPlaceholder: "Paste your reset token", + newPassword: "New Password", + newPasswordPlaceholder: "Enter your new password", + confirmNewPassword: "Confirm New Password", + switchToResetWithToken: "I have a reset token", + switchToRequestReset: "I need a reset link", + passwordResetEmailSent: + "If that email exists, a reset link has been sent.", + passwordResetSuccess: "Your password has been reset. You can sign in now.", + resetTokenRequired: "Reset token is required", + resetPasswordError: "Unable to reset password. Please try again.", + backToSignIn: "Back to Sign In", invalidEmail: "Please enter a valid email", passwordRequired: "Password is required", passwordTooShort: "Password must be at least 8 characters", passwordsDoNotMatch: "Passwords do not match", signInError: "Unable to sign in. Please check your credentials.", signUpError: "Unable to create account. Please try again.", + continueWithGoogle: "Continue with Google", + googleAuthUnifiedHint: + "Google will sign you in or create your Buddy account automatically.", + googleSignInError: "Google sign in failed. Please try again.", + googleMissingIdToken: + "Google sign in did not return an ID token. Please try again.", + googleConfigMissing: "Google sign in is not configured yet. Contact support.", loadingAlerts: "Loading alerts...", allClearTitle: "All clear!", noAlertsToReview: "No alerts to review right now.", @@ -74,6 +98,12 @@ const en = { notifications: "Notifications", dangerousMessages: "Dangerous messages", newContactAdded: "New contact added", + galleryScanning: "Gallery scanning", + scanGalleryForInappropriateImages: "Scan gallery for inappropriate images.", + galleryScanningTitle: "Gallery scanning", + galleryScanningNone: "Do nothing", + galleryScanningNotify: "Notify me", + galleryScanningDelete: "Delete detected images", devices: "Devices", device: "device", devicesPlural: "devices", @@ -150,12 +180,39 @@ const hr: typeof en = { createAccount: "Izradi račun", alreadyHaveAccount: "Već imate račun?", dontHaveAccount: "Nemate račun?", + forgotPassword: "Zaboravljena lozinka?", + resetPassword: "Resetiraj lozinku", + resetPasswordHelp: + "Zatražite poveznicu za resetiranje ili unesite token za novu lozinku.", + sendResetLink: "Pošalji poveznicu za resetiranje", + resetPasswordToken: "Token za resetiranje", + resetPasswordTokenPlaceholder: "Zalijepite token za resetiranje", + newPassword: "Nova lozinka", + newPasswordPlaceholder: "Unesite novu lozinku", + confirmNewPassword: "Potvrdite novu lozinku", + switchToResetWithToken: "Imam token za resetiranje", + switchToRequestReset: "Trebam poveznicu za resetiranje", + passwordResetEmailSent: + "Ako taj e-mail postoji, poveznica za resetiranje je poslana.", + passwordResetSuccess: + "Lozinka je uspješno resetirana. Sada se možete prijaviti.", + resetTokenRequired: "Token za resetiranje je obavezan", + resetPasswordError: "Resetiranje lozinke nije uspjelo. Pokušajte ponovno.", + backToSignIn: "Natrag na prijavu", invalidEmail: "Unesite valjanu e-mail adresu", passwordRequired: "Lozinka je obavezna", passwordTooShort: "Lozinka mora imati najmanje 8 znakova", passwordsDoNotMatch: "Lozinke se ne podudaraju", signInError: "Prijava nije uspjela. Provjerite podatke.", signUpError: "Registracija nije uspjela. Pokušajte ponovno.", + continueWithGoogle: "Nastavi s Google računom", + googleAuthUnifiedHint: + "Google će vas prijaviti ili automatski izraditi Buddy račun.", + googleSignInError: "Google prijava nije uspjela. Pokušajte ponovno.", + googleMissingIdToken: + "Google prijava nije vratila ID token. Pokušajte ponovno.", + googleConfigMissing: + "Google prijava još nije konfigurirana. Kontaktirajte podršku.", loadingAlerts: "Učitavanje upozorenja...", allClearTitle: "Sve je u redu!", noAlertsToReview: "Trenutno nema upozorenja za pregled.", @@ -188,6 +245,12 @@ const hr: typeof en = { notifications: "Obavijesti", dangerousMessages: "Opasne poruke", newContactAdded: "Dodan novi kontakt", + galleryScanning: "Skeniranje galerije", + scanGalleryForInappropriateImages: "Skeniraj galeriju za neprimjerene slike.", + galleryScanningTitle: "Skeniranje galerije", + galleryScanningNone: "Ne poduzimaj ništa", + galleryScanningNotify: "Obavijesti me", + galleryScanningDelete: "Izbriši otkrivene slike", devices: "Uređaji", device: "uređaj", devicesPlural: "uređaji", |