| 1 | export interface LoginRequest {
|
|---|
| 2 | username: string
|
|---|
| 3 | password: string
|
|---|
| 4 | }
|
|---|
| 5 |
|
|---|
| 6 | export interface SignupRequest {
|
|---|
| 7 | username: string
|
|---|
| 8 | email: string
|
|---|
| 9 | password: string
|
|---|
| 10 | firstName: string
|
|---|
| 11 | lastName: string
|
|---|
| 12 | }
|
|---|
| 13 |
|
|---|
| 14 | export interface AuthResult {
|
|---|
| 15 | token?: string
|
|---|
| 16 | user?: {
|
|---|
| 17 | userId: number
|
|---|
| 18 | username: string
|
|---|
| 19 | email: string
|
|---|
| 20 | firstName: string
|
|---|
| 21 | lastName: string
|
|---|
| 22 | userType: string
|
|---|
| 23 | verified: boolean
|
|---|
| 24 | }
|
|---|
| 25 | message?: string
|
|---|
| 26 | }
|
|---|
| 27 |
|
|---|
| 28 | function getBaseUrl(): string {
|
|---|
| 29 | const base = (import.meta.env.VITE_API_BASE_URL as string | undefined) ?? ''
|
|---|
| 30 | return base.replace(/\/$/, '')
|
|---|
| 31 | }
|
|---|
| 32 |
|
|---|
| 33 | function joinUrl(base: string, path: string): string {
|
|---|
| 34 | if (!base) return path
|
|---|
| 35 | return `${base}${path.startsWith('/') ? '' : '/'}${path}`
|
|---|
| 36 | }
|
|---|
| 37 |
|
|---|
| 38 | function extractToken(data: unknown): string | null {
|
|---|
| 39 | if (!data || typeof data !== 'object') return null
|
|---|
| 40 | const rec = data as Record<string, unknown>
|
|---|
| 41 | const candidates = [rec.token, rec.accessToken, rec.jwt, rec.idToken]
|
|---|
| 42 | for (const c of candidates) {
|
|---|
| 43 | if (typeof c === 'string' && c.trim()) return c
|
|---|
| 44 | }
|
|---|
| 45 | // Sometimes APIs wrap: { data: { token: '...' } }
|
|---|
| 46 | const nested = rec.data
|
|---|
| 47 | if (nested && typeof nested === 'object') {
|
|---|
| 48 | const n = nested as Record<string, unknown>
|
|---|
| 49 | const nestedCandidates = [n.token, n.accessToken, n.jwt, n.idToken]
|
|---|
| 50 | for (const c of nestedCandidates) {
|
|---|
| 51 | if (typeof c === 'string' && c.trim()) return c
|
|---|
| 52 | }
|
|---|
| 53 | }
|
|---|
| 54 | return null
|
|---|
| 55 | }
|
|---|
| 56 |
|
|---|
| 57 | async function postJson<T>(path: string, body: unknown, options?: { signal?: AbortSignal }): Promise<T> {
|
|---|
| 58 | const url = joinUrl(getBaseUrl(), path)
|
|---|
| 59 | const res = await fetch(url, {
|
|---|
| 60 | method: 'POST',
|
|---|
| 61 | headers: {
|
|---|
| 62 | 'Content-Type': 'application/json',
|
|---|
| 63 | Accept: 'application/json',
|
|---|
| 64 | },
|
|---|
| 65 | body: JSON.stringify(body),
|
|---|
| 66 | signal: options?.signal,
|
|---|
| 67 | })
|
|---|
| 68 |
|
|---|
| 69 | const text = await res.text()
|
|---|
| 70 | let json: unknown = null
|
|---|
| 71 | try {
|
|---|
| 72 | json = text ? (JSON.parse(text) as unknown) : null
|
|---|
| 73 | } catch {
|
|---|
| 74 | json = null
|
|---|
| 75 | }
|
|---|
| 76 |
|
|---|
| 77 | if (!res.ok) {
|
|---|
| 78 | const message =
|
|---|
| 79 | (json && typeof json === 'object' && typeof (json as any).message === 'string' && (json as any).message) ||
|
|---|
| 80 | text ||
|
|---|
| 81 | `Request failed (${res.status})`
|
|---|
| 82 | throw new Error(message)
|
|---|
| 83 | }
|
|---|
| 84 |
|
|---|
| 85 | return json as T
|
|---|
| 86 | }
|
|---|
| 87 |
|
|---|
| 88 | export async function login(payload: LoginRequest, options?: { signal?: AbortSignal }): Promise<AuthResult> {
|
|---|
| 89 | const data = await postJson<any>('/api/auth/login', payload, options)
|
|---|
| 90 |
|
|---|
| 91 | // Check if there's an error message
|
|---|
| 92 | if (data.message && data.message !== "Login successful") {
|
|---|
| 93 | throw new Error(data.message)
|
|---|
| 94 | }
|
|---|
| 95 |
|
|---|
| 96 | // Extract user information from the response
|
|---|
| 97 | if (data.userId && data.username) {
|
|---|
| 98 | return {
|
|---|
| 99 | user: {
|
|---|
| 100 | userId: data.userId,
|
|---|
| 101 | username: data.username,
|
|---|
| 102 | email: data.email,
|
|---|
| 103 | firstName: data.firstName,
|
|---|
| 104 | lastName: data.lastName,
|
|---|
| 105 | userType: data.userType,
|
|---|
| 106 | verified: data.verified || false // Use verified status from API response
|
|---|
| 107 | },
|
|---|
| 108 | message: data.message
|
|---|
| 109 | }
|
|---|
| 110 | }
|
|---|
| 111 |
|
|---|
| 112 | // If we don't get user data, something went wrong
|
|---|
| 113 | throw new Error('Login failed: Invalid response from server')
|
|---|
| 114 | }
|
|---|
| 115 |
|
|---|
| 116 | export async function signup(payload: SignupRequest, options?: { signal?: AbortSignal }): Promise<AuthResult> {
|
|---|
| 117 | const data = await postJson<any>('/api/auth/signup', payload, options)
|
|---|
| 118 |
|
|---|
| 119 | // Check if there's an error message
|
|---|
| 120 | if (data.message && data.message !== "User registered successfully") {
|
|---|
| 121 | throw new Error(data.message)
|
|---|
| 122 | }
|
|---|
| 123 |
|
|---|
| 124 | // Extract user information from the response if successful
|
|---|
| 125 | if (data.userId && data.username) {
|
|---|
| 126 | return {
|
|---|
| 127 | user: {
|
|---|
| 128 | userId: data.userId,
|
|---|
| 129 | username: data.username,
|
|---|
| 130 | email: data.email,
|
|---|
| 131 | firstName: data.firstName,
|
|---|
| 132 | lastName: data.lastName,
|
|---|
| 133 | userType: data.userType,
|
|---|
| 134 | verified: data.verified || false // Use verified status from API response
|
|---|
| 135 | },
|
|---|
| 136 | message: data.message
|
|---|
| 137 | }
|
|---|
| 138 | }
|
|---|
| 139 |
|
|---|
| 140 | // If registration was successful but no user data returned, that's okay
|
|---|
| 141 | return { message: data.message || "Registration successful" }
|
|---|
| 142 | }
|
|---|