habits.andr33v.ru/app/composables/useAuth.ts

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,
};
}