mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-09-14 04:35:29 +02:00
Add frontend autoformatting and set CI to require formatted code for all languages (#6052)
# Description of Changes Changes the strategy for autoformatting to reject PRs if they are not formatted correctly instead of allowing them to merge and then spawning a new PR to fix the formatting. The old strategy just caused more work for us because we'd have to manually approve the followup PR and get it merged, which required 2 reviewers so in practice it rarely got done and just meant everyone's PRs ended up containing reformatting for unrelated files, which makes code review unnecessarily difficult. If the PR's code is not formatted correctly after this PR, a comment will be added automatically to tell the author how to run the formatter script to fix their code so it can go in. This also enables autoformatting for the frontend code, using Prettier. I've enabled it for pretty much everything in the frontend folder, other than 3rd party files and files it doesn't make sense for. I also excluded Markdown because it sounds likely to be more annoying to have to autoformat the Markdown in the frontend folder but nowhere else. Open to changing this though if people disagree. > [!note] > > Advice to reviewers: The first commit contains all of the actual logic I've introduced (CI changes, Prettier config, etc.) > The second commit is just the reformatting of the entire frontend folder. > The first commit needs proper review, the second one just give it a spot-check that it's doing what you'd expect.
This commit is contained in:
@@ -1,44 +1,44 @@
|
||||
import { createContext, useContext, useEffect, useState, ReactNode, useCallback } from 'react'
|
||||
import { supabase } from '@app/auth/supabase'
|
||||
import type { Session, User as SupabaseUser, AuthError } from '@supabase/supabase-js'
|
||||
import { CreditSummary, SubscriptionInfo, CreditCheckResult, ApiCredits } from '@app/types/credits'
|
||||
import apiClient, { setGlobalCreditUpdateCallback } from '@app/services/apiClient'
|
||||
import { synchronizeUserUpgrade } from '@app/services/userService'
|
||||
import { syncOAuthAvatar, getProfilePictureMetadata, type ProfilePictureMetadata } from '@app/services/avatarSyncService'
|
||||
import { createContext, useContext, useEffect, useState, ReactNode, useCallback } from "react";
|
||||
import { supabase } from "@app/auth/supabase";
|
||||
import type { Session, User as SupabaseUser, AuthError } from "@supabase/supabase-js";
|
||||
import { CreditSummary, SubscriptionInfo, CreditCheckResult, ApiCredits } from "@app/types/credits";
|
||||
import apiClient, { setGlobalCreditUpdateCallback } from "@app/services/apiClient";
|
||||
import { synchronizeUserUpgrade } from "@app/services/userService";
|
||||
import { syncOAuthAvatar, getProfilePictureMetadata, type ProfilePictureMetadata } from "@app/services/avatarSyncService";
|
||||
|
||||
// Extend Supabase User to include optional username for compatibility
|
||||
export type User = SupabaseUser & { username?: string };
|
||||
|
||||
export interface TrialStatus {
|
||||
isTrialing: boolean
|
||||
trialEnd: string
|
||||
daysRemaining: number
|
||||
hasPaymentMethod: boolean
|
||||
hasScheduledSub: boolean
|
||||
status: string
|
||||
isTrialing: boolean;
|
||||
trialEnd: string;
|
||||
daysRemaining: number;
|
||||
hasPaymentMethod: boolean;
|
||||
hasScheduledSub: boolean;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
session: Session | null
|
||||
user: User | null
|
||||
loading: boolean
|
||||
error: AuthError | null
|
||||
creditBalance: number | null
|
||||
subscription: SubscriptionInfo | null
|
||||
creditSummary: CreditSummary | null
|
||||
isPro: boolean | null
|
||||
trialStatus: TrialStatus | null
|
||||
profilePictureUrl: string | null
|
||||
profilePictureMetadata: ProfilePictureMetadata | null
|
||||
signOut: () => Promise<void>
|
||||
refreshSession: () => Promise<void>
|
||||
hasSufficientCredits: (requiredCredits: number) => CreditCheckResult
|
||||
updateCredits: (newBalance: number) => void
|
||||
refreshCredits: () => Promise<void>
|
||||
refreshProStatus: () => Promise<void>
|
||||
refreshTrialStatus: () => Promise<void>
|
||||
refreshProfilePicture: () => Promise<void>
|
||||
refreshProfilePictureMetadata: () => Promise<void>
|
||||
session: Session | null;
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
error: AuthError | null;
|
||||
creditBalance: number | null;
|
||||
subscription: SubscriptionInfo | null;
|
||||
creditSummary: CreditSummary | null;
|
||||
isPro: boolean | null;
|
||||
trialStatus: TrialStatus | null;
|
||||
profilePictureUrl: string | null;
|
||||
profilePictureMetadata: ProfilePictureMetadata | null;
|
||||
signOut: () => Promise<void>;
|
||||
refreshSession: () => Promise<void>;
|
||||
hasSufficientCredits: (requiredCredits: number) => CreditCheckResult;
|
||||
updateCredits: (newBalance: number) => void;
|
||||
refreshCredits: () => Promise<void>;
|
||||
refreshProStatus: () => Promise<void>;
|
||||
refreshTrialStatus: () => Promise<void>;
|
||||
refreshProfilePicture: () => Promise<void>;
|
||||
refreshProfilePictureMetadata: () => Promise<void>;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType>({
|
||||
@@ -61,467 +61,488 @@ const AuthContext = createContext<AuthContextType>({
|
||||
refreshProStatus: async () => {},
|
||||
refreshTrialStatus: async () => {},
|
||||
refreshProfilePicture: async () => {},
|
||||
refreshProfilePictureMetadata: async () => {}
|
||||
})
|
||||
refreshProfilePictureMetadata: async () => {},
|
||||
});
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [session, setSession] = useState<Session | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<AuthError | null>(null)
|
||||
const [creditBalance, setCreditBalance] = useState<number | null>(null)
|
||||
const [subscription, setSubscription] = useState<SubscriptionInfo | null>(null)
|
||||
const [creditSummary, setCreditSummary] = useState<CreditSummary | null>(null)
|
||||
const [isPro, setIsPro] = useState<boolean | null>(null)
|
||||
const [trialStatus, setTrialStatus] = useState<TrialStatus | null>(null)
|
||||
const [profilePictureUrl, setProfilePictureUrl] = useState<string | null>(null)
|
||||
const [profilePictureMetadata, setProfilePictureMetadata] = useState<ProfilePictureMetadata | null>(null)
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<AuthError | null>(null);
|
||||
const [creditBalance, setCreditBalance] = useState<number | null>(null);
|
||||
const [subscription, setSubscription] = useState<SubscriptionInfo | null>(null);
|
||||
const [creditSummary, setCreditSummary] = useState<CreditSummary | null>(null);
|
||||
const [isPro, setIsPro] = useState<boolean | null>(null);
|
||||
const [trialStatus, setTrialStatus] = useState<TrialStatus | null>(null);
|
||||
const [profilePictureUrl, setProfilePictureUrl] = useState<string | null>(null);
|
||||
const [profilePictureMetadata, setProfilePictureMetadata] = useState<ProfilePictureMetadata | null>(null);
|
||||
|
||||
const fetchCredits = useCallback(async (sessionToUse?: Session | null) => {
|
||||
const currentSession = sessionToUse ?? session
|
||||
const fetchCredits = useCallback(
|
||||
async (sessionToUse?: Session | null) => {
|
||||
const currentSession = sessionToUse ?? session;
|
||||
|
||||
if (!currentSession?.user) {
|
||||
console.debug('[Auth Debug] No user session, skipping credit fetch')
|
||||
setCreditBalance(null)
|
||||
setCreditSummary(null)
|
||||
setSubscription(null)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
console.debug('[Auth Debug] Fetching credits for user:', currentSession.user.id)
|
||||
const response = await apiClient.get<ApiCredits>('/api/v1/credits')
|
||||
const apiCredits = response.data
|
||||
|
||||
// Map server payload to app CreditSummary
|
||||
const credits: CreditSummary = {
|
||||
currentCredits: apiCredits.totalAvailableCredits,
|
||||
maxCredits: apiCredits.weeklyCreditsAllocated + apiCredits.totalBoughtCredits,
|
||||
creditsUsed: (apiCredits.weeklyCreditsAllocated - apiCredits.weeklyCreditsRemaining) + (apiCredits.totalBoughtCredits - apiCredits.boughtCreditsRemaining),
|
||||
creditsRemaining: apiCredits.totalAvailableCredits,
|
||||
resetDate: apiCredits.weeklyResetDate,
|
||||
weeklyAllowance: apiCredits.weeklyCreditsAllocated
|
||||
if (!currentSession?.user) {
|
||||
console.debug("[Auth Debug] No user session, skipping credit fetch");
|
||||
setCreditBalance(null);
|
||||
setCreditSummary(null);
|
||||
setSubscription(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setCreditSummary(credits)
|
||||
setCreditBalance(credits.creditsRemaining)
|
||||
try {
|
||||
console.debug("[Auth Debug] Fetching credits for user:", currentSession.user.id);
|
||||
const response = await apiClient.get<ApiCredits>("/api/v1/credits");
|
||||
const apiCredits = response.data;
|
||||
|
||||
const subscriptionInfo: SubscriptionInfo = {
|
||||
status: 'active',
|
||||
tier: (credits.weeklyAllowance || 0) > 100 ? 'premium' : 'free',
|
||||
creditsPerWeek: credits.weeklyAllowance,
|
||||
maxCredits: credits.maxCredits
|
||||
// Map server payload to app CreditSummary
|
||||
const credits: CreditSummary = {
|
||||
currentCredits: apiCredits.totalAvailableCredits,
|
||||
maxCredits: apiCredits.weeklyCreditsAllocated + apiCredits.totalBoughtCredits,
|
||||
creditsUsed:
|
||||
apiCredits.weeklyCreditsAllocated -
|
||||
apiCredits.weeklyCreditsRemaining +
|
||||
(apiCredits.totalBoughtCredits - apiCredits.boughtCreditsRemaining),
|
||||
creditsRemaining: apiCredits.totalAvailableCredits,
|
||||
resetDate: apiCredits.weeklyResetDate,
|
||||
weeklyAllowance: apiCredits.weeklyCreditsAllocated,
|
||||
};
|
||||
|
||||
setCreditSummary(credits);
|
||||
setCreditBalance(credits.creditsRemaining);
|
||||
|
||||
const subscriptionInfo: SubscriptionInfo = {
|
||||
status: "active",
|
||||
tier: (credits.weeklyAllowance || 0) > 100 ? "premium" : "free",
|
||||
creditsPerWeek: credits.weeklyAllowance,
|
||||
maxCredits: credits.maxCredits,
|
||||
};
|
||||
setSubscription(subscriptionInfo);
|
||||
|
||||
console.debug("[Auth Debug] Credits fetched successfully:", credits);
|
||||
} catch (error: unknown) {
|
||||
console.debug("[Auth Debug] Failed to fetch credits:", error);
|
||||
// Don't set error state for credit fetching failures to avoid disrupting auth flow
|
||||
// Credits might not be available in all deployments
|
||||
setCreditBalance(null);
|
||||
setCreditSummary(null);
|
||||
setSubscription(null);
|
||||
}
|
||||
setSubscription(subscriptionInfo)
|
||||
|
||||
console.debug('[Auth Debug] Credits fetched successfully:', credits)
|
||||
} catch (error: unknown) {
|
||||
console.debug('[Auth Debug] Failed to fetch credits:', error)
|
||||
// Don't set error state for credit fetching failures to avoid disrupting auth flow
|
||||
// Credits might not be available in all deployments
|
||||
setCreditBalance(null)
|
||||
setCreditSummary(null)
|
||||
setSubscription(null)
|
||||
}
|
||||
}, [session])
|
||||
},
|
||||
[session],
|
||||
);
|
||||
|
||||
const refreshCredits = useCallback(async () => {
|
||||
await fetchCredits()
|
||||
}, [fetchCredits])
|
||||
await fetchCredits();
|
||||
}, [fetchCredits]);
|
||||
|
||||
const fetchProStatus = useCallback(async (sessionToUse?: Session | null) => {
|
||||
const currentSession = sessionToUse ?? session
|
||||
const fetchProStatus = useCallback(
|
||||
async (sessionToUse?: Session | null) => {
|
||||
const currentSession = sessionToUse ?? session;
|
||||
|
||||
if (!currentSession?.user) {
|
||||
console.debug('[Auth Debug] No user session, skipping pro status fetch')
|
||||
setIsPro(null)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
console.debug('[Auth Debug] Fetching pro status for user:', currentSession.user.id)
|
||||
const { data: proStatus, error } = await supabase.rpc('is_pro')
|
||||
|
||||
if (error) {
|
||||
console.error('[Auth Debug] Error checking Pro status:', error)
|
||||
setIsPro(false) // Default to false if there's an error
|
||||
} else {
|
||||
const isProUser = Boolean(proStatus)
|
||||
setIsPro(isProUser)
|
||||
console.debug('[Auth Debug] Pro status fetched:', isProUser)
|
||||
if (!currentSession?.user) {
|
||||
console.debug("[Auth Debug] No user session, skipping pro status fetch");
|
||||
setIsPro(null);
|
||||
return;
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.debug('[Auth Debug] Failed to fetch pro status:', error)
|
||||
setIsPro(false) // Default to false if there's an error
|
||||
}
|
||||
}, [session])
|
||||
|
||||
try {
|
||||
console.debug("[Auth Debug] Fetching pro status for user:", currentSession.user.id);
|
||||
const { data: proStatus, error } = await supabase.rpc("is_pro");
|
||||
|
||||
if (error) {
|
||||
console.error("[Auth Debug] Error checking Pro status:", error);
|
||||
setIsPro(false); // Default to false if there's an error
|
||||
} else {
|
||||
const isProUser = Boolean(proStatus);
|
||||
setIsPro(isProUser);
|
||||
console.debug("[Auth Debug] Pro status fetched:", isProUser);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.debug("[Auth Debug] Failed to fetch pro status:", error);
|
||||
setIsPro(false); // Default to false if there's an error
|
||||
}
|
||||
},
|
||||
[session],
|
||||
);
|
||||
|
||||
const refreshProStatus = useCallback(async () => {
|
||||
await fetchProStatus()
|
||||
}, [fetchProStatus])
|
||||
await fetchProStatus();
|
||||
}, [fetchProStatus]);
|
||||
|
||||
const fetchTrialStatus = useCallback(async (sessionToUse?: Session | null) => {
|
||||
const currentSession = sessionToUse ?? session
|
||||
const fetchTrialStatus = useCallback(
|
||||
async (sessionToUse?: Session | null) => {
|
||||
const currentSession = sessionToUse ?? session;
|
||||
|
||||
if (!currentSession?.user) {
|
||||
console.debug('[Auth Debug] No user session, skipping trial status fetch')
|
||||
setTrialStatus(null)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
console.debug('[Auth Debug] Fetching trial status for user:', currentSession.user.id)
|
||||
const { data, error } = await supabase
|
||||
.from('billing_subscriptions')
|
||||
.select('status, trial_end, has_payment_method, scheduled_subscription_id')
|
||||
.in('status', ['trialing', 'incomplete_expired', 'canceled'])
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(1)
|
||||
.maybeSingle()
|
||||
|
||||
if (error) {
|
||||
console.error('[Auth Debug] Error fetching trial status:', error)
|
||||
setTrialStatus(null)
|
||||
return
|
||||
if (!currentSession?.user) {
|
||||
console.debug("[Auth Debug] No user session, skipping trial status fetch");
|
||||
setTrialStatus(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data?.trial_end) {
|
||||
const trialEnd = new Date(data.trial_end)
|
||||
const now = new Date()
|
||||
const daysRemaining = Math.ceil((trialEnd.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
|
||||
try {
|
||||
console.debug("[Auth Debug] Fetching trial status for user:", currentSession.user.id);
|
||||
const { data, error } = await supabase
|
||||
.from("billing_subscriptions")
|
||||
.select("status, trial_end, has_payment_method, scheduled_subscription_id")
|
||||
.in("status", ["trialing", "incomplete_expired", "canceled"])
|
||||
.order("created_at", { ascending: false })
|
||||
.limit(1)
|
||||
.maybeSingle();
|
||||
|
||||
setTrialStatus({
|
||||
isTrialing: data.status === 'trialing' && daysRemaining > 0,
|
||||
trialEnd: data.trial_end,
|
||||
daysRemaining: Math.max(0, daysRemaining),
|
||||
hasPaymentMethod: data.has_payment_method || false,
|
||||
hasScheduledSub: !!data.scheduled_subscription_id,
|
||||
status: data.status
|
||||
})
|
||||
console.debug('[Auth Debug] Trial status fetched:', {
|
||||
status: data.status,
|
||||
daysRemaining: Math.max(0, daysRemaining),
|
||||
hasPaymentMethod: data.has_payment_method,
|
||||
isTrialing: data.status === 'trialing' && daysRemaining > 0
|
||||
})
|
||||
} else {
|
||||
setTrialStatus(null)
|
||||
if (error) {
|
||||
console.error("[Auth Debug] Error fetching trial status:", error);
|
||||
setTrialStatus(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data?.trial_end) {
|
||||
const trialEnd = new Date(data.trial_end);
|
||||
const now = new Date();
|
||||
const daysRemaining = Math.ceil((trialEnd.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
||||
|
||||
setTrialStatus({
|
||||
isTrialing: data.status === "trialing" && daysRemaining > 0,
|
||||
trialEnd: data.trial_end,
|
||||
daysRemaining: Math.max(0, daysRemaining),
|
||||
hasPaymentMethod: data.has_payment_method || false,
|
||||
hasScheduledSub: !!data.scheduled_subscription_id,
|
||||
status: data.status,
|
||||
});
|
||||
console.debug("[Auth Debug] Trial status fetched:", {
|
||||
status: data.status,
|
||||
daysRemaining: Math.max(0, daysRemaining),
|
||||
hasPaymentMethod: data.has_payment_method,
|
||||
isTrialing: data.status === "trialing" && daysRemaining > 0,
|
||||
});
|
||||
} else {
|
||||
setTrialStatus(null);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.debug("[Auth Debug] Failed to fetch trial status:", error);
|
||||
setTrialStatus(null);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.debug('[Auth Debug] Failed to fetch trial status:', error)
|
||||
setTrialStatus(null)
|
||||
}
|
||||
}, [session])
|
||||
},
|
||||
[session],
|
||||
);
|
||||
|
||||
const refreshTrialStatus = useCallback(async () => {
|
||||
await fetchTrialStatus()
|
||||
}, [fetchTrialStatus])
|
||||
await fetchTrialStatus();
|
||||
}, [fetchTrialStatus]);
|
||||
|
||||
const fetchProfilePicture = useCallback(async (sessionToUse?: Session | null) => {
|
||||
const currentSession = sessionToUse ?? session
|
||||
const fetchProfilePicture = useCallback(
|
||||
async (sessionToUse?: Session | null) => {
|
||||
const currentSession = sessionToUse ?? session;
|
||||
|
||||
if (!currentSession?.user) {
|
||||
console.debug('[Auth Debug] No user session, skipping profile picture fetch')
|
||||
setProfilePictureUrl(null)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const PROFILE_BUCKET = 'profile-pictures'
|
||||
const profilePath = `${currentSession.user.id}/avatar`
|
||||
|
||||
console.debug('[Auth Debug] Fetching profile picture for user:', currentSession.user.id)
|
||||
const { data, error } = await supabase
|
||||
.storage
|
||||
.from(PROFILE_BUCKET)
|
||||
.createSignedUrl(profilePath, 60 * 60)
|
||||
|
||||
if (error) {
|
||||
// Profile picture not found is expected for users without uploads
|
||||
console.debug('[Auth Debug] Profile picture not available:', error.message)
|
||||
setProfilePictureUrl(null)
|
||||
} else {
|
||||
setProfilePictureUrl(data.signedUrl)
|
||||
console.debug('[Auth Debug] Profile picture URL fetched successfully')
|
||||
if (!currentSession?.user) {
|
||||
console.debug("[Auth Debug] No user session, skipping profile picture fetch");
|
||||
setProfilePictureUrl(null);
|
||||
return;
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.debug('[Auth Debug] Failed to fetch profile picture:', error)
|
||||
setProfilePictureUrl(null)
|
||||
}
|
||||
}, [session])
|
||||
|
||||
try {
|
||||
const PROFILE_BUCKET = "profile-pictures";
|
||||
const profilePath = `${currentSession.user.id}/avatar`;
|
||||
|
||||
console.debug("[Auth Debug] Fetching profile picture for user:", currentSession.user.id);
|
||||
const { data, error } = await supabase.storage.from(PROFILE_BUCKET).createSignedUrl(profilePath, 60 * 60);
|
||||
|
||||
if (error) {
|
||||
// Profile picture not found is expected for users without uploads
|
||||
console.debug("[Auth Debug] Profile picture not available:", error.message);
|
||||
setProfilePictureUrl(null);
|
||||
} else {
|
||||
setProfilePictureUrl(data.signedUrl);
|
||||
console.debug("[Auth Debug] Profile picture URL fetched successfully");
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.debug("[Auth Debug] Failed to fetch profile picture:", error);
|
||||
setProfilePictureUrl(null);
|
||||
}
|
||||
},
|
||||
[session],
|
||||
);
|
||||
|
||||
const refreshProfilePicture = useCallback(async () => {
|
||||
await fetchProfilePicture()
|
||||
}, [fetchProfilePicture])
|
||||
await fetchProfilePicture();
|
||||
}, [fetchProfilePicture]);
|
||||
|
||||
const fetchProfilePictureMetadata = useCallback(async (sessionToUse?: Session | null) => {
|
||||
const currentSession = sessionToUse ?? session
|
||||
const fetchProfilePictureMetadata = useCallback(
|
||||
async (sessionToUse?: Session | null) => {
|
||||
const currentSession = sessionToUse ?? session;
|
||||
|
||||
if (!currentSession?.user) {
|
||||
console.debug('[Auth Debug] No user session, skipping profile picture metadata fetch')
|
||||
setProfilePictureMetadata(null)
|
||||
return
|
||||
}
|
||||
if (!currentSession?.user) {
|
||||
console.debug("[Auth Debug] No user session, skipping profile picture metadata fetch");
|
||||
setProfilePictureMetadata(null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.debug('[Auth Debug] Fetching profile picture metadata for user:', currentSession.user.id)
|
||||
const metadata = await getProfilePictureMetadata(currentSession.user.id)
|
||||
setProfilePictureMetadata(metadata)
|
||||
console.debug('[Auth Debug] Profile picture metadata fetched:', metadata)
|
||||
} catch (error: unknown) {
|
||||
console.debug('[Auth Debug] Failed to fetch profile picture metadata:', error)
|
||||
setProfilePictureMetadata(null)
|
||||
}
|
||||
}, [session])
|
||||
try {
|
||||
console.debug("[Auth Debug] Fetching profile picture metadata for user:", currentSession.user.id);
|
||||
const metadata = await getProfilePictureMetadata(currentSession.user.id);
|
||||
setProfilePictureMetadata(metadata);
|
||||
console.debug("[Auth Debug] Profile picture metadata fetched:", metadata);
|
||||
} catch (error: unknown) {
|
||||
console.debug("[Auth Debug] Failed to fetch profile picture metadata:", error);
|
||||
setProfilePictureMetadata(null);
|
||||
}
|
||||
},
|
||||
[session],
|
||||
);
|
||||
|
||||
const refreshProfilePictureMetadata = useCallback(async () => {
|
||||
await fetchProfilePictureMetadata()
|
||||
}, [fetchProfilePictureMetadata])
|
||||
await fetchProfilePictureMetadata();
|
||||
}, [fetchProfilePictureMetadata]);
|
||||
|
||||
const updateCredits = useCallback((newBalance: number) => {
|
||||
console.debug('[Auth Debug] Updating credit balance:', { from: creditBalance, to: newBalance })
|
||||
setCreditBalance(newBalance)
|
||||
// Also update the creditSummary if it exists
|
||||
if (creditSummary) {
|
||||
const updatedSummary: CreditSummary = {
|
||||
...creditSummary,
|
||||
creditsRemaining: newBalance,
|
||||
currentCredits: newBalance
|
||||
const updateCredits = useCallback(
|
||||
(newBalance: number) => {
|
||||
console.debug("[Auth Debug] Updating credit balance:", { from: creditBalance, to: newBalance });
|
||||
setCreditBalance(newBalance);
|
||||
// Also update the creditSummary if it exists
|
||||
if (creditSummary) {
|
||||
const updatedSummary: CreditSummary = {
|
||||
...creditSummary,
|
||||
creditsRemaining: newBalance,
|
||||
currentCredits: newBalance,
|
||||
};
|
||||
setCreditSummary(updatedSummary);
|
||||
}
|
||||
setCreditSummary(updatedSummary)
|
||||
}
|
||||
}, [creditSummary])
|
||||
},
|
||||
[creditSummary],
|
||||
);
|
||||
|
||||
const hasSufficientCredits = useCallback((requiredCredits: number): CreditCheckResult => {
|
||||
const currentBalance = creditBalance ?? 0
|
||||
const hasSufficient = currentBalance >= requiredCredits
|
||||
console.debug('[Auth Debug] Credit check:', { requiredCredits, currentBalance, hasSufficient })
|
||||
const hasSufficientCredits = useCallback(
|
||||
(requiredCredits: number): CreditCheckResult => {
|
||||
const currentBalance = creditBalance ?? 0;
|
||||
const hasSufficient = currentBalance >= requiredCredits;
|
||||
console.debug("[Auth Debug] Credit check:", { requiredCredits, currentBalance, hasSufficient });
|
||||
|
||||
return {
|
||||
hasSufficientCredits: hasSufficient,
|
||||
currentBalance,
|
||||
requiredCredits,
|
||||
shortfall: hasSufficient ? undefined : requiredCredits - currentBalance
|
||||
}
|
||||
}, [creditBalance])
|
||||
return {
|
||||
hasSufficientCredits: hasSufficient,
|
||||
currentBalance,
|
||||
requiredCredits,
|
||||
shortfall: hasSufficient ? undefined : requiredCredits - currentBalance,
|
||||
};
|
||||
},
|
||||
[creditBalance],
|
||||
);
|
||||
|
||||
const refreshSession = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
const { data, error } = await supabase.auth.refreshSession()
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const { data, error } = await supabase.auth.refreshSession();
|
||||
|
||||
if (error) {
|
||||
console.error('[Auth Debug] Session refresh error:', error)
|
||||
setError(error)
|
||||
setSession(null)
|
||||
console.error("[Auth Debug] Session refresh error:", error);
|
||||
setError(error);
|
||||
setSession(null);
|
||||
} else {
|
||||
console.debug('[Auth Debug] Session refreshed successfully')
|
||||
setSession(data.session)
|
||||
console.debug("[Auth Debug] Session refreshed successfully");
|
||||
setSession(data.session);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Auth Debug] Unexpected error during session refresh:', err)
|
||||
setError(err as AuthError)
|
||||
console.error("[Auth Debug] Unexpected error during session refresh:", err);
|
||||
setError(err as AuthError);
|
||||
} finally {
|
||||
setLoading(false)
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const signOut = async () => {
|
||||
try {
|
||||
setError(null)
|
||||
const { error } = await supabase.auth.signOut()
|
||||
setError(null);
|
||||
const { error } = await supabase.auth.signOut();
|
||||
|
||||
if (error) {
|
||||
console.error('[Auth Debug] Sign out error:', error)
|
||||
setError(error)
|
||||
console.error("[Auth Debug] Sign out error:", error);
|
||||
setError(error);
|
||||
} else {
|
||||
console.debug('[Auth Debug] Signed out successfully')
|
||||
setSession(null)
|
||||
console.debug("[Auth Debug] Signed out successfully");
|
||||
setSession(null);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Auth Debug] Unexpected error during sign out:', err)
|
||||
setError(err as AuthError)
|
||||
console.error("[Auth Debug] Unexpected error during sign out:", err);
|
||||
setError(err as AuthError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Set up global credit update callback
|
||||
useEffect(() => {
|
||||
setGlobalCreditUpdateCallback(updateCredits)
|
||||
}, [updateCredits])
|
||||
setGlobalCreditUpdateCallback(updateCredits);
|
||||
}, [updateCredits]);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
let mounted = true;
|
||||
|
||||
// Load current session on first mount
|
||||
const initializeAuth = async () => {
|
||||
try {
|
||||
console.debug('[Auth Debug] Initializing auth...')
|
||||
const { data, error } = await supabase.auth.getSession()
|
||||
console.debug("[Auth Debug] Initializing auth...");
|
||||
const { data, error } = await supabase.auth.getSession();
|
||||
|
||||
if (!mounted) return
|
||||
if (!mounted) return;
|
||||
|
||||
if (error) {
|
||||
console.error('[Auth Debug] Initial session error:', error)
|
||||
setError(error)
|
||||
console.error("[Auth Debug] Initial session error:", error);
|
||||
setError(error);
|
||||
} else {
|
||||
console.debug('[Auth Debug] Initial session loaded:', {
|
||||
console.debug("[Auth Debug] Initial session loaded:", {
|
||||
hasSession: !!data.session,
|
||||
userId: data.session?.user?.id,
|
||||
email: data.session?.user?.email
|
||||
})
|
||||
setSession(data.session)
|
||||
email: data.session?.user?.email,
|
||||
});
|
||||
setSession(data.session);
|
||||
|
||||
// Fetch credits, pro status, trial status, profile picture metadata, and profile picture using the session from the response
|
||||
if (data.session?.user) {
|
||||
// Sync OAuth avatar in background
|
||||
syncOAuthAvatar(data.session.user).catch((err) => {
|
||||
console.debug('[Auth Debug] Failed to sync OAuth avatar on init:', err)
|
||||
})
|
||||
console.debug("[Auth Debug] Failed to sync OAuth avatar on init:", err);
|
||||
});
|
||||
|
||||
await fetchCredits(data.session)
|
||||
await fetchProStatus(data.session)
|
||||
await fetchTrialStatus(data.session)
|
||||
await fetchProfilePictureMetadata(data.session)
|
||||
await fetchCredits(data.session);
|
||||
await fetchProStatus(data.session);
|
||||
await fetchTrialStatus(data.session);
|
||||
await fetchProfilePictureMetadata(data.session);
|
||||
|
||||
// Small delay to allow avatar sync to complete if quick
|
||||
setTimeout(() => {
|
||||
fetchProfilePicture(data.session)
|
||||
}, 500)
|
||||
fetchProfilePicture(data.session);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Auth Debug] Unexpected error during auth initialization:', err)
|
||||
console.error("[Auth Debug] Unexpected error during auth initialization:", err);
|
||||
if (mounted) {
|
||||
setError(err as AuthError)
|
||||
setError(err as AuthError);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setLoading(false)
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
initializeAuth()
|
||||
initializeAuth();
|
||||
|
||||
// Subscribe to auth state changes
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
||||
async (event, newSession) => {
|
||||
if (!mounted) return
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange(async (event, newSession) => {
|
||||
if (!mounted) return;
|
||||
|
||||
console.debug('[Auth Debug] Auth state change:', {
|
||||
event,
|
||||
hasSession: !!newSession,
|
||||
userId: newSession?.user?.id,
|
||||
email: newSession?.user?.email,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
console.debug("[Auth Debug] Auth state change:", {
|
||||
event,
|
||||
hasSession: !!newSession,
|
||||
userId: newSession?.user?.id,
|
||||
email: newSession?.user?.email,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Don't run supabase calls inside this callback; schedule them
|
||||
setTimeout(() => {
|
||||
if (mounted) {
|
||||
setSession(newSession)
|
||||
setError(null)
|
||||
// Don't run supabase calls inside this callback; schedule them
|
||||
setTimeout(() => {
|
||||
if (mounted) {
|
||||
setSession(newSession);
|
||||
setError(null);
|
||||
|
||||
// Additional handling for specific events
|
||||
if (event === 'SIGNED_OUT') {
|
||||
console.debug('[Auth Debug] User signed out, clearing session')
|
||||
// Clear credit data, pro status, trial status, profile picture, and metadata on sign out
|
||||
setCreditBalance(null)
|
||||
setCreditSummary(null)
|
||||
setSubscription(null)
|
||||
setIsPro(null)
|
||||
setTrialStatus(null)
|
||||
setProfilePictureUrl(null)
|
||||
setProfilePictureMetadata(null)
|
||||
} else if (event === 'SIGNED_IN') {
|
||||
console.debug('[Auth Debug] User signed in successfully')
|
||||
if (newSession?.user) {
|
||||
setLoading(true)
|
||||
// Additional handling for specific events
|
||||
if (event === "SIGNED_OUT") {
|
||||
console.debug("[Auth Debug] User signed out, clearing session");
|
||||
// Clear credit data, pro status, trial status, profile picture, and metadata on sign out
|
||||
setCreditBalance(null);
|
||||
setCreditSummary(null);
|
||||
setSubscription(null);
|
||||
setIsPro(null);
|
||||
setTrialStatus(null);
|
||||
setProfilePictureUrl(null);
|
||||
setProfilePictureMetadata(null);
|
||||
} else if (event === "SIGNED_IN") {
|
||||
console.debug("[Auth Debug] User signed in successfully");
|
||||
if (newSession?.user) {
|
||||
setLoading(true);
|
||||
|
||||
// Sync OAuth avatar in background (don't block other fetches)
|
||||
syncOAuthAvatar(newSession.user).catch((err) => {
|
||||
console.debug('[Auth Debug] Failed to sync OAuth avatar:', err)
|
||||
// Sync OAuth avatar in background (don't block other fetches)
|
||||
syncOAuthAvatar(newSession.user).catch((err) => {
|
||||
console.debug("[Auth Debug] Failed to sync OAuth avatar:", err);
|
||||
});
|
||||
|
||||
// Fetch user data in parallel
|
||||
Promise.all([
|
||||
fetchCredits(newSession),
|
||||
fetchProStatus(newSession),
|
||||
fetchTrialStatus(newSession),
|
||||
fetchProfilePictureMetadata(newSession),
|
||||
]).then(() => {
|
||||
// Fetch profile picture AFTER sync has had time to complete
|
||||
// Use a small delay to allow avatar sync to finish if it's quick
|
||||
setTimeout(() => {
|
||||
fetchProfilePicture(newSession).finally(() => {
|
||||
setLoading(false);
|
||||
console.debug("[Auth Debug] User data fully loaded after sign in");
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
} else if (event === "TOKEN_REFRESHED") {
|
||||
console.debug("[Auth Debug] Token refreshed");
|
||||
// Optionally refresh credits, pro status, trial status, profile picture metadata, and profile picture on token refresh
|
||||
if (newSession?.user) {
|
||||
Promise.all([
|
||||
fetchCredits(newSession),
|
||||
fetchProStatus(newSession),
|
||||
fetchTrialStatus(newSession),
|
||||
fetchProfilePictureMetadata(newSession),
|
||||
fetchProfilePicture(newSession),
|
||||
]).then(() => {
|
||||
console.debug("[Auth Debug] User data refreshed after token refresh");
|
||||
});
|
||||
}
|
||||
} else if (event === "USER_UPDATED") {
|
||||
console.debug("[Auth Debug] User updated");
|
||||
|
||||
// Check if this is a pending OAuth upgrade completion
|
||||
const pendingUpgrade = sessionStorage.getItem("pendingUpgrade");
|
||||
const upgradeProvider = sessionStorage.getItem("upgradeProvider");
|
||||
|
||||
if (pendingUpgrade && newSession?.user && newSession.user.is_anonymous === false) {
|
||||
console.debug("[Auth Debug] Processing pending OAuth upgrade:", upgradeProvider);
|
||||
|
||||
// Clear the flags first to prevent loops
|
||||
sessionStorage.removeItem("pendingUpgrade");
|
||||
sessionStorage.removeItem("upgradeProvider");
|
||||
|
||||
// Synchronize with backend
|
||||
synchronizeUserUpgrade(upgradeProvider || undefined)
|
||||
.then(() => {
|
||||
console.debug("[Auth Debug] User upgrade synchronized successfully");
|
||||
|
||||
// Refresh credits, pro status, trial status, profile picture metadata, and profile picture after upgrade
|
||||
if (newSession?.user) {
|
||||
return Promise.all([
|
||||
fetchCredits(newSession),
|
||||
fetchProStatus(newSession),
|
||||
fetchTrialStatus(newSession),
|
||||
fetchProfilePictureMetadata(newSession),
|
||||
fetchProfilePicture(newSession),
|
||||
]);
|
||||
}
|
||||
})
|
||||
|
||||
// Fetch user data in parallel
|
||||
Promise.all([
|
||||
fetchCredits(newSession),
|
||||
fetchProStatus(newSession),
|
||||
fetchTrialStatus(newSession),
|
||||
fetchProfilePictureMetadata(newSession),
|
||||
]).then(() => {
|
||||
// Fetch profile picture AFTER sync has had time to complete
|
||||
// Use a small delay to allow avatar sync to finish if it's quick
|
||||
setTimeout(() => {
|
||||
fetchProfilePicture(newSession).finally(() => {
|
||||
setLoading(false)
|
||||
console.debug('[Auth Debug] User data fully loaded after sign in')
|
||||
})
|
||||
}, 500)
|
||||
.then(() => {
|
||||
console.debug("[Auth Debug] User data refreshed after upgrade");
|
||||
})
|
||||
}
|
||||
} else if (event === 'TOKEN_REFRESHED') {
|
||||
console.debug('[Auth Debug] Token refreshed')
|
||||
// Optionally refresh credits, pro status, trial status, profile picture metadata, and profile picture on token refresh
|
||||
if (newSession?.user) {
|
||||
Promise.all([
|
||||
fetchCredits(newSession),
|
||||
fetchProStatus(newSession),
|
||||
fetchTrialStatus(newSession),
|
||||
fetchProfilePictureMetadata(newSession),
|
||||
fetchProfilePicture(newSession)
|
||||
]).then(() => {
|
||||
console.debug('[Auth Debug] User data refreshed after token refresh')
|
||||
})
|
||||
}
|
||||
} else if (event === 'USER_UPDATED') {
|
||||
console.debug('[Auth Debug] User updated')
|
||||
|
||||
// Check if this is a pending OAuth upgrade completion
|
||||
const pendingUpgrade = sessionStorage.getItem('pendingUpgrade')
|
||||
const upgradeProvider = sessionStorage.getItem('upgradeProvider')
|
||||
|
||||
if (pendingUpgrade && newSession?.user && newSession.user.is_anonymous === false) {
|
||||
console.debug('[Auth Debug] Processing pending OAuth upgrade:', upgradeProvider)
|
||||
|
||||
// Clear the flags first to prevent loops
|
||||
sessionStorage.removeItem('pendingUpgrade')
|
||||
sessionStorage.removeItem('upgradeProvider')
|
||||
|
||||
// Synchronize with backend
|
||||
synchronizeUserUpgrade(upgradeProvider || undefined)
|
||||
.then(() => {
|
||||
console.debug('[Auth Debug] User upgrade synchronized successfully')
|
||||
|
||||
// Refresh credits, pro status, trial status, profile picture metadata, and profile picture after upgrade
|
||||
if (newSession?.user) {
|
||||
return Promise.all([
|
||||
fetchCredits(newSession),
|
||||
fetchProStatus(newSession),
|
||||
fetchTrialStatus(newSession),
|
||||
fetchProfilePictureMetadata(newSession),
|
||||
fetchProfilePicture(newSession)
|
||||
])
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
console.debug('[Auth Debug] User data refreshed after upgrade')
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('[Auth Debug] Failed to synchronize user upgrade:', err)
|
||||
})
|
||||
}
|
||||
.catch((err) => {
|
||||
console.error("[Auth Debug] Failed to synchronize user upgrade:", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
)
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
}, [])
|
||||
mounted = false;
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const value: AuthContextType = {
|
||||
session,
|
||||
@@ -543,41 +564,37 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
refreshProStatus,
|
||||
refreshTrialStatus,
|
||||
refreshProfilePicture,
|
||||
refreshProfilePictureMetadata
|
||||
}
|
||||
refreshProfilePictureMetadata,
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const context = useContext(AuthContext)
|
||||
const context = useContext(AuthContext);
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuth must be used within an AuthProvider')
|
||||
throw new Error("useAuth must be used within an AuthProvider");
|
||||
}
|
||||
|
||||
return context
|
||||
return context;
|
||||
}
|
||||
|
||||
// Debug hook to expose auth state for debugging
|
||||
export function useAuthDebug() {
|
||||
const auth = useAuth()
|
||||
const auth = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
console.debug('[Auth Debug] Current auth state:', {
|
||||
console.debug("[Auth Debug] Current auth state:", {
|
||||
hasSession: !!auth.session,
|
||||
hasUser: !!auth.user,
|
||||
loading: auth.loading,
|
||||
hasError: !!auth.error,
|
||||
userId: auth.user?.id,
|
||||
email: auth.user?.email,
|
||||
provider: auth.user?.app_metadata?.provider
|
||||
})
|
||||
}, [auth.session, auth.user, auth.loading, auth.error])
|
||||
provider: auth.user?.app_metadata?.provider,
|
||||
});
|
||||
}, [auth.session, auth.user, auth.loading, auth.error]);
|
||||
|
||||
return auth
|
||||
return auth;
|
||||
}
|
||||
|
||||
@@ -1,56 +1,52 @@
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
// Debug helper to log Supabase configuration
|
||||
const debugConfig = () => {
|
||||
const url = import.meta.env.VITE_SUPABASE_URL
|
||||
const key = import.meta.env.VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY
|
||||
const url = import.meta.env.VITE_SUPABASE_URL;
|
||||
const key = import.meta.env.VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY;
|
||||
|
||||
console.log('[Supabase Debug] Configuration:', {
|
||||
url: url ? '✓ URL configured' : '✗ URL missing',
|
||||
key: key ? '✓ Key configured' : '✗ Key missing',
|
||||
urlValue: url || 'undefined',
|
||||
keyValue: key ? `${key.substring(0, 20)}...` : 'undefined'
|
||||
})
|
||||
console.log("[Supabase Debug] Configuration:", {
|
||||
url: url ? "✓ URL configured" : "✗ URL missing",
|
||||
key: key ? "✓ Key configured" : "✗ Key missing",
|
||||
urlValue: url || "undefined",
|
||||
keyValue: key ? `${key.substring(0, 20)}...` : "undefined",
|
||||
});
|
||||
|
||||
return { url, key }
|
||||
}
|
||||
return { url, key };
|
||||
};
|
||||
|
||||
const config = debugConfig()
|
||||
const config = debugConfig();
|
||||
|
||||
if (!config.url) {
|
||||
throw new Error('Missing VITE_SUPABASE_URL environment variable')
|
||||
throw new Error("Missing VITE_SUPABASE_URL environment variable");
|
||||
}
|
||||
|
||||
if (!config.key) {
|
||||
throw new Error('Missing VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY environment variable')
|
||||
throw new Error("Missing VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY environment variable");
|
||||
}
|
||||
|
||||
export const supabase = createClient(
|
||||
config.url,
|
||||
config.key,
|
||||
{
|
||||
auth: {
|
||||
persistSession: true, // keep session in localStorage
|
||||
autoRefreshToken: true,
|
||||
detectSessionInUrl: true, // helpful on first load after redirect
|
||||
// debug: import.meta.env.DEV, // Enable debug logs in development
|
||||
},
|
||||
}
|
||||
)
|
||||
export const supabase = createClient(config.url, config.key, {
|
||||
auth: {
|
||||
persistSession: true, // keep session in localStorage
|
||||
autoRefreshToken: true,
|
||||
detectSessionInUrl: true, // helpful on first load after redirect
|
||||
// debug: import.meta.env.DEV, // Enable debug logs in development
|
||||
},
|
||||
});
|
||||
|
||||
// Debug helper for auth events
|
||||
export const debugAuthEvents = () => {
|
||||
supabase.auth.onAuthStateChange((event, session) => {
|
||||
console.log('[Supabase Debug] Auth state change:', {
|
||||
console.log("[Supabase Debug] Auth state change:", {
|
||||
event,
|
||||
hasSession: !!session,
|
||||
userId: session?.user?.id,
|
||||
email: session?.user?.email,
|
||||
provider: session?.user?.app_metadata?.provider,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
})
|
||||
}
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Debug auth events can be manually enabled by calling debugAuthEvents()
|
||||
// Commented out to prevent excessive logging on every page load
|
||||
@@ -61,105 +57,108 @@ export const debugAuthEvents = () => {
|
||||
// Anonymous authentication functions
|
||||
export const signInAnonymously = async () => {
|
||||
try {
|
||||
const { data, error } = await supabase.auth.signInAnonymously()
|
||||
const { data, error } = await supabase.auth.signInAnonymously();
|
||||
|
||||
if (error) {
|
||||
console.error('[Supabase] Anonymous sign-in error:', error)
|
||||
throw error
|
||||
console.error("[Supabase] Anonymous sign-in error:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log('[Supabase] Anonymous sign-in successful:', {
|
||||
console.log("[Supabase] Anonymous sign-in successful:", {
|
||||
userId: data.user?.id,
|
||||
isAnonymous: data.user?.is_anonymous
|
||||
})
|
||||
isAnonymous: data.user?.is_anonymous,
|
||||
});
|
||||
|
||||
return { data, error }
|
||||
return { data, error };
|
||||
} catch (error) {
|
||||
console.error('[Supabase] Anonymous sign-in failed:', error)
|
||||
throw error
|
||||
console.error("[Supabase] Anonymous sign-in failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Account linking functions
|
||||
export const linkEmailIdentity = async (email: string, password?: string) => {
|
||||
try {
|
||||
const updateData: { email: string; password?: string } = { email }
|
||||
const updateData: { email: string; password?: string } = { email };
|
||||
if (password) {
|
||||
updateData.password = password
|
||||
updateData.password = password;
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.auth.updateUser(updateData)
|
||||
const { data, error } = await supabase.auth.updateUser(updateData);
|
||||
|
||||
if (error) {
|
||||
console.error('[Supabase] Email linking error:', error)
|
||||
throw error
|
||||
console.error("[Supabase] Email linking error:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Refresh session to get updated token with new user metadata
|
||||
const { error: refreshError } = await supabase.auth.refreshSession()
|
||||
const { error: refreshError } = await supabase.auth.refreshSession();
|
||||
|
||||
if (refreshError) {
|
||||
console.warn('[Supabase] Session refresh after email linking failed:', refreshError)
|
||||
console.warn("[Supabase] Session refresh after email linking failed:", refreshError);
|
||||
// Don't throw - linking was successful, refresh is just for consistency
|
||||
} else {
|
||||
console.log('[Supabase] Session refreshed after email linking')
|
||||
console.log("[Supabase] Session refreshed after email linking");
|
||||
}
|
||||
|
||||
console.log('[Supabase] Email linked successfully:', {
|
||||
console.log("[Supabase] Email linked successfully:", {
|
||||
email,
|
||||
userId: data.user?.id
|
||||
})
|
||||
userId: data.user?.id,
|
||||
});
|
||||
|
||||
return { data, error }
|
||||
return { data, error };
|
||||
} catch (error) {
|
||||
console.error('[Supabase] Email linking failed:', error)
|
||||
throw error
|
||||
console.error("[Supabase] Email linking failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const linkOAuthIdentity = async (provider: 'google' | 'github' | 'apple' | 'azure', redirectTo?: string) => {
|
||||
export const linkOAuthIdentity = async (provider: "google" | "github" | "apple" | "azure", redirectTo?: string) => {
|
||||
try {
|
||||
const { data, error } = await supabase.auth.linkIdentity({
|
||||
provider: provider,
|
||||
options: redirectTo ? { redirectTo } : undefined
|
||||
})
|
||||
options: redirectTo ? { redirectTo } : undefined,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('[Supabase] OAuth linking error:', error)
|
||||
throw error
|
||||
console.error("[Supabase] OAuth linking error:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log('[Supabase] OAuth identity linked successfully:', {
|
||||
console.log("[Supabase] OAuth identity linked successfully:", {
|
||||
provider,
|
||||
redirectTo,
|
||||
url: data.url
|
||||
})
|
||||
url: data.url,
|
||||
});
|
||||
|
||||
return { data, error }
|
||||
return { data, error };
|
||||
} catch (error) {
|
||||
console.error('[Supabase] OAuth linking failed:', error)
|
||||
throw error
|
||||
console.error("[Supabase] OAuth linking failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to check if user is anonymous
|
||||
export const isUserAnonymous = (user: { is_anonymous?: boolean }) => {
|
||||
return user?.is_anonymous === true
|
||||
}
|
||||
return user?.is_anonymous === true;
|
||||
};
|
||||
|
||||
// Get current user session
|
||||
export const getCurrentUser = async () => {
|
||||
try {
|
||||
const { data: { user }, error } = await supabase.auth.getUser()
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (error) {
|
||||
console.error('[Supabase] Get user error:', error)
|
||||
throw error
|
||||
console.error("[Supabase] Get user error:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return user
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error('[Supabase] Get user failed:', error)
|
||||
throw error
|
||||
console.error("[Supabase] Get user failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user