HonzaH commited on
Commit
b283b34
·
verified ·
1 Parent(s): 549bb48

Upload 184 files

Browse files
src/app/profile/page.tsx CHANGED
@@ -4,7 +4,7 @@ import Link from 'next/link'
4
  import { useEffect, useState } from 'react'
5
  import type { Database } from 'types/database'
6
  import { isSupabaseBrowserConfigured } from 'lib/supabase/publicEnv'
7
- import { useAuth } from 'hooks/useAuth'
8
 
9
  type ProfileRow = Database['public']['Tables']['users']['Row']
10
 
@@ -17,12 +17,12 @@ export default function ProfilePage() {
17
  const [loading, setLoading] = useState(false)
18
 
19
  useEffect(() => {
20
- const load = async () => {
21
- if (!authUser) return
22
- setLoading(true)
23
- try {
24
  const res = await fetch('/api/profile', { cache: 'no-store', credentials: 'include' })
25
- if (res.ok) {
26
  const json = await res.json()
27
  setProfile(json.profile || null)
28
  setRoomsCount(json.roomsCount || 0)
@@ -31,8 +31,8 @@ export default function ProfilePage() {
31
  setLoading(false)
32
  }
33
  }
34
- load()
35
- }, [authUser])
36
 
37
  if (!supabaseConfigured) {
38
  return (
 
4
  import { useEffect, useState } from 'react'
5
  import type { Database } from 'types/database'
6
  import { isSupabaseBrowserConfigured } from 'lib/supabase/publicEnv'
7
+ import { emitAuthChanged, useAuth } from 'hooks/useAuth'
8
 
9
  type ProfileRow = Database['public']['Tables']['users']['Row']
10
 
 
17
  const [loading, setLoading] = useState(false)
18
 
19
  useEffect(() => {
20
+ const load = async () => {
21
+ if (!authUser) return
22
+ setLoading(true)
23
+ try {
24
  const res = await fetch('/api/profile', { cache: 'no-store', credentials: 'include' })
25
+ if (res.ok) {
26
  const json = await res.json()
27
  setProfile(json.profile || null)
28
  setRoomsCount(json.roomsCount || 0)
 
31
  setLoading(false)
32
  }
33
  }
34
+ load()
35
+ }, [authUser])
36
 
37
  if (!supabaseConfigured) {
38
  return (
src/components/forms/LoginForm.tsx CHANGED
@@ -5,6 +5,7 @@ import { useForm } from 'react-hook-form'
5
  import { zodResolver } from '@hookform/resolvers/zod'
6
  import { useRouter } from 'next/navigation'
7
  import { loginSchema, type LoginForm } from 'lib/validations/auth'
 
8
  import { getSupabaseBrowserConfig, isSupabaseBrowserConfigured } from 'lib/supabase/publicEnv'
9
 
10
  export function LoginForm() {
@@ -40,6 +41,7 @@ export function LoginForm() {
40
  return
41
  }
42
 
 
43
  router.push('/dashboard')
44
  } catch (e: any) {
45
  console.error('Login error:', e)
@@ -116,3 +118,4 @@ export function LoginForm() {
116
  </form>
117
  )
118
  }
 
 
5
  import { zodResolver } from '@hookform/resolvers/zod'
6
  import { useRouter } from 'next/navigation'
7
  import { loginSchema, type LoginForm } from 'lib/validations/auth'
8
+ import { emitAuthChanged } from 'hooks/useAuth'
9
  import { getSupabaseBrowserConfig, isSupabaseBrowserConfigured } from 'lib/supabase/publicEnv'
10
 
11
  export function LoginForm() {
 
41
  return
42
  }
43
 
44
+ emitAuthChanged()
45
  router.push('/dashboard')
46
  } catch (e: any) {
47
  console.error('Login error:', e)
 
118
  </form>
119
  )
120
  }
121
+
src/components/layout/Header.tsx CHANGED
@@ -1,6 +1,6 @@
1
  'use client'
2
 
3
- import { useAuth } from 'hooks/useAuth'
4
  import { useRouter, usePathname } from 'next/navigation'
5
  import Link from 'next/link'
6
 
@@ -11,6 +11,7 @@ export function Header() {
11
 
12
  const handleLogout = async () => {
13
  await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' })
 
14
  router.push('/')
15
  }
16
 
 
1
  'use client'
2
 
3
+ import { emitAuthChanged, useAuth } from 'hooks/useAuth'
4
  import { useRouter, usePathname } from 'next/navigation'
5
  import Link from 'next/link'
6
 
 
11
 
12
  const handleLogout = async () => {
13
  await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' })
14
+ emitAuthChanged()
15
  router.push('/')
16
  }
17
 
src/hooks/useAuth.ts CHANGED
@@ -4,25 +4,39 @@ import { useEffect, useState } from 'react'
4
 
5
  type AuthUser = { id: string; email?: string | null }
6
 
 
 
 
 
 
 
 
 
7
  export function useAuth() {
8
  const [user, setUser] = useState<AuthUser | null>(null)
9
  const [loading, setLoading] = useState(true)
10
 
11
- useEffect(() => {
12
- const init = async () => {
13
- try {
14
- const res = await fetch('/api/auth/me', { cache: 'no-store', credentials: 'include' })
15
- const data = await res.json().catch(() => ({}))
16
- setUser(data?.user ? { id: data.user.id, email: data.user.email } as any : null)
17
- } catch (e) {
18
- setUser(null)
19
- } finally {
20
- setLoading(false)
21
- }
22
  }
 
23
 
24
- init()
 
 
 
 
 
 
 
25
  }, [])
26
 
27
- return { user, loading }
28
  }
 
4
 
5
  type AuthUser = { id: string; email?: string | null }
6
 
7
+ const AUTH_EVENT = 'letschat-auth-refresh'
8
+
9
+ export const emitAuthChanged = () => {
10
+ if (typeof window !== 'undefined') {
11
+ window.dispatchEvent(new Event(AUTH_EVENT))
12
+ }
13
+ }
14
+
15
  export function useAuth() {
16
  const [user, setUser] = useState<AuthUser | null>(null)
17
  const [loading, setLoading] = useState(true)
18
 
19
+ const refresh = async () => {
20
+ try {
21
+ const res = await fetch('/api/auth/me', { cache: 'no-store', credentials: 'include' })
22
+ const data = await res.json().catch(() => ({}))
23
+ setUser(data?.user ? { id: data.user.id, email: data.user.email } : null)
24
+ } catch {
25
+ setUser(null)
26
+ } finally {
27
+ setLoading(false)
 
 
28
  }
29
+ }
30
 
31
+ useEffect(() => {
32
+ refresh()
33
+ const listener = () => {
34
+ setLoading(true)
35
+ refresh()
36
+ }
37
+ window.addEventListener(AUTH_EVENT, listener)
38
+ return () => window.removeEventListener(AUTH_EVENT, listener)
39
  }, [])
40
 
41
+ return { user, loading, refresh }
42
  }
src/lib/persistence.ts CHANGED
@@ -63,6 +63,14 @@ async function getSupabase(): Promise<SupabaseClient<Database>> {
63
  return createServerClient()
64
  }
65
 
 
 
 
 
 
 
 
 
66
  function pickFallbackStore() {
67
  // Prefer file store when enabled; otherwise use in-memory store
68
  return ENABLE_FILE_STORE ? readFileStore() : Promise.resolve({ codes: inMemory.codes, rooms: inMemory.rooms, room_messages: inMemory.room_messages })
@@ -168,7 +176,7 @@ export async function insertCode(payload: Database['public']['Tables']['codes'][
168
  await logEvent({ module: 'codes', operation: 'insert', data: res.rows[0] })
169
  return res.rows[0]
170
  }
171
- const supabase = await getSupabase()
172
  try {
173
  const { data, error } = await supabase.from('codes').insert(normalizedPayload).select().single()
174
  if (error) {
@@ -256,7 +264,7 @@ export async function updateCode(id: string, payload: Partial<Database['public']
256
  await logEvent({ module: 'codes', operation: 'update', data: res.rows[0] })
257
  return res.rows[0]
258
  }
259
- const supabase = await getSupabase()
260
  try {
261
  const { data, error } = await supabase.from('codes').update(payload).eq('id', id).select().single()
262
  if (error) {
@@ -300,7 +308,7 @@ export async function insertRoom(payload: Partial<Database['public']['Tables']['
300
  return res.rows[0]
301
  }
302
  try {
303
- const supabase = await getSupabase()
304
  const { data, error } = await supabase.from('rooms').insert(payload).select().single()
305
  if (error) {
306
  console.error('[persistence.insertRoom] supabase error:', error)
 
63
  return createServerClient()
64
  }
65
 
66
+ // Prefer service-role client for write operations to avoid RLS blocks when running on Supabase.
67
+ async function getSupabaseWrite(): Promise<SupabaseClient<Database>> {
68
+ if (process.env.SUPABASE_SERVICE_ROLE_KEY) {
69
+ return createServiceClient()
70
+ }
71
+ return createServerClient()
72
+ }
73
+
74
  function pickFallbackStore() {
75
  // Prefer file store when enabled; otherwise use in-memory store
76
  return ENABLE_FILE_STORE ? readFileStore() : Promise.resolve({ codes: inMemory.codes, rooms: inMemory.rooms, room_messages: inMemory.room_messages })
 
176
  await logEvent({ module: 'codes', operation: 'insert', data: res.rows[0] })
177
  return res.rows[0]
178
  }
179
+ const supabase = await getSupabaseWrite()
180
  try {
181
  const { data, error } = await supabase.from('codes').insert(normalizedPayload).select().single()
182
  if (error) {
 
264
  await logEvent({ module: 'codes', operation: 'update', data: res.rows[0] })
265
  return res.rows[0]
266
  }
267
+ const supabase = await getSupabaseWrite()
268
  try {
269
  const { data, error } = await supabase.from('codes').update(payload).eq('id', id).select().single()
270
  if (error) {
 
308
  return res.rows[0]
309
  }
310
  try {
311
+ const supabase = await getSupabaseWrite()
312
  const { data, error } = await supabase.from('rooms').insert(payload).select().single()
313
  if (error) {
314
  console.error('[persistence.insertRoom] supabase error:', error)