Add guest auth and save-account flow

Introduce anonymous sign-in with in-app saving, guest recovery warnings, paywall prompts, and admin guest labeling.
This commit is contained in:
jjbrunton committed 2026-01-17 17:55:06 +00:00
1 parent ce68d57a49
commit 349f460e15
14 files changed
+1236 -176

No files matched your search

+21 -17
View File
@@ -10,23 +10,27 @@ interface ErrorMapping {
message: string;
}
const AUTH_ERROR_MAPPINGS: ErrorMapping[] = [
{ pattern: /invalid login credentials/i, message: "Incorrect email or password. Please try again." },
{ pattern: /email not confirmed/i, message: "Please verify your email before signing in." },
{ pattern: /user already registered/i, message: "An account with this email already exists." },
{ pattern: /invalid email/i, message: "Please enter a valid email address." },
{ pattern: /password.*too short/i, message: "Password must be at least 8 characters." },
{ pattern: /password.*too weak/i, message: "Please choose a stronger password." },
{ pattern: /rate limit/i, message: "Too many attempts. Please wait a moment and try again." },
{ pattern: /email rate limit/i, message: "Too many emails sent. Please wait before requesting another." },
{ pattern: /same as/i, message: "New password must be different from your current password." },
{ pattern: /session.*expired/i, message: "Your session has expired. Please log in again." },
{ pattern: /invalid.*token/i, message: "This link has expired. Please request a new one." },
{ pattern: /otp.*expired/i, message: "This code has expired. Please request a new one." },
{ pattern: /signups.*disabled/i, message: "Sign ups are currently disabled. Please try again later." },
{ pattern: /user not found/i, message: "No account found with this email." },
{ pattern: /provider.*disabled/i, message: "This sign-in method is currently unavailable." },
];
const AUTH_ERROR_MAPPINGS: ErrorMapping[] = [
{ pattern: /invalid login credentials/i, message: "Incorrect email or password. Please try again." },
{ pattern: /email not confirmed/i, message: "Please verify your email before signing in." },
{ pattern: /user already registered/i, message: "An account with this email already exists." },
{ pattern: /email.*already.*(registered|exists|in use|used)/i, message: "This email is already in use. Please use a different one." },
{ pattern: /duplicate key value violates unique constraint.*users_email_key/i, message: "This email is already in use. Please use a different one." },
{ pattern: /invalid email/i, message: "Please enter a valid email address." },
{ pattern: /password.*too short/i, message: "Password must be at least 8 characters." },
{ pattern: /password.*too weak/i, message: "Please choose a stronger password." },
{ pattern: /rate limit/i, message: "Too many attempts. Please wait a moment and try again." },
{ pattern: /email rate limit/i, message: "Too many emails sent. Please wait before requesting another." },
{ pattern: /same as/i, message: "New password must be different from your current password." },
{ pattern: /session.*expired/i, message: "Your session has expired. Please log in again." },
{ pattern: /invalid.*token/i, message: "This link has expired. Please request a new one." },
{ pattern: /otp.*expired/i, message: "This code has expired. Please request a new one." },
{ pattern: /signups.*disabled/i, message: "Sign ups are currently disabled. Please try again later." },
{ pattern: /user not found/i, message: "No account found with this email." },
{ pattern: /provider.*disabled/i, message: "This sign-in method is currently unavailable." },
{ pattern: /manual.*link/i, message: "Saving accounts is temporarily unavailable. Please try again later." },
{ pattern: /identity.*link/i, message: "Saving accounts is temporarily unavailable. Please try again later." },
];
const PAIRING_ERROR_MAPPINGS: ErrorMapping[] = [
{ pattern: /invalid.*invite.*code/i, message: "This invite code is invalid. Please check and try again." },
@@ -0,0 +1,47 @@
import * as SecureStore from "expo-secure-store";
import { Platform } from "react-native";
const STORAGE_KEY = "guest_account_recovery_warning_seen";
async function getStorage(): Promise<boolean> {
try {
let data: string | null = null;
if (Platform.OS === "web") {
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
data = window.localStorage.getItem(STORAGE_KEY);
}
} else {
data = await SecureStore.getItemAsync(STORAGE_KEY);
}
return data === "true";
} catch (error) {
console.error("Error reading guest warning state:", error);
}
return false;
}
async function setStorage(seen: boolean): Promise<void> {
try {
const data = seen ? "true" : "false";
if (Platform.OS === "web") {
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
window.localStorage.setItem(STORAGE_KEY, data);
}
} else {
await SecureStore.setItemAsync(STORAGE_KEY, data);
}
} catch (error) {
console.error("Error saving guest warning state:", error);
}
}
export async function hasSeenGuestAccountWarning(): Promise<boolean> {
return getStorage();
}
export async function markGuestAccountWarningSeen(): Promise<void> {
await setStorage(true);
}