117 lines
3.2 KiB
TypeScript
117 lines
3.2 KiB
TypeScript
// /composables/useAuth.ts
|
|
|
|
interface User {
|
|
id: number;
|
|
email: string | null; // Can be null for anonymous users
|
|
nickname: string | null;
|
|
avatar: string | null;
|
|
coins: number;
|
|
exp: number;
|
|
dailyStreak: number;
|
|
soundOn: boolean;
|
|
confettiOn: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
isAnonymous?: boolean; // Flag to distinguish anonymous users
|
|
anonymousSessionId?: string;
|
|
}
|
|
|
|
export function useAuth() {
|
|
const user = useState<User | null>('user', () => null);
|
|
const initialized = useState('auth_initialized', () => false);
|
|
|
|
const api = useApi();
|
|
|
|
// A user is fully authenticated only if they exist and are NOT anonymous.
|
|
const isAuthenticated = computed(() => !!user.value && !user.value.isAnonymous);
|
|
// A user is anonymous if they exist and have the isAnonymous flag.
|
|
const isAnonymous = computed(() => !!user.value && !!user.value.isAnonymous);
|
|
|
|
/**
|
|
* Initializes the authentication state for EXISTING users.
|
|
* It should be called once in app.vue.
|
|
* It will only try to fetch a logged-in user via /api/auth/me.
|
|
* If it fails, the user state remains null.
|
|
*/
|
|
const initAuth = async () => {
|
|
if (initialized.value) return;
|
|
|
|
try {
|
|
const response = await api<{ user: User }>('/auth/me');
|
|
if (response.user) {
|
|
user.value = { ...response.user, isAnonymous: false };
|
|
} else {
|
|
user.value = null;
|
|
}
|
|
} catch (error) {
|
|
// It's expected this will fail for non-logged-in users.
|
|
user.value = null;
|
|
} finally {
|
|
initialized.value = true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Starts the onboarding process by creating a new anonymous user.
|
|
*/
|
|
const startOnboarding = async () => {
|
|
try {
|
|
const anonymousUserData = await api<User>('/onboarding/initiate', { method: 'POST' });
|
|
// Explicitly set isAnonymous to true for robustness
|
|
user.value = { ...anonymousUserData, isAnonymous: true };
|
|
} catch (anonError) {
|
|
console.error('Could not initiate anonymous session:', anonError);
|
|
// Optionally, show an error message to the user
|
|
user.value = null;
|
|
}
|
|
};
|
|
|
|
const register = async (email, password, nickname) => {
|
|
await api('/auth/register', {
|
|
method: 'POST',
|
|
body: { email, password, nickname },
|
|
});
|
|
// After a successful registration, force a re-fetch of the new user state.
|
|
initialized.value = false;
|
|
await initAuth();
|
|
};
|
|
|
|
const login = async (email, password) => {
|
|
await api('/auth/login', {
|
|
method: 'POST',
|
|
body: { email, password },
|
|
});
|
|
// After a successful login, force a re-fetch of the new user state.
|
|
initialized.value = false;
|
|
await initAuth();
|
|
};
|
|
|
|
const logout = async () => {
|
|
try {
|
|
await api('/auth/logout', { method: 'POST' });
|
|
} finally {
|
|
user.value = null;
|
|
await navigateTo('/');
|
|
}
|
|
};
|
|
|
|
const updateUser = (partialUser: Partial<User>) => {
|
|
if (user.value) {
|
|
user.value = { ...user.value, ...partialUser };
|
|
}
|
|
};
|
|
|
|
return {
|
|
user,
|
|
isAuthenticated,
|
|
isAnonymous, // Expose this new state
|
|
initialized,
|
|
initAuth, // Called from app.vue
|
|
startOnboarding, // Called from index.vue
|
|
register, // Expose register function
|
|
login,
|
|
logout,
|
|
updateUser,
|
|
};
|
|
}
|