Initial project scaffolding
This commit is contained in:
115
app/stores/auth.ts
Normal file
115
app/stores/auth.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { Database, MemberRole } from '~/types/supabase'
|
||||
|
||||
export type Member = Database['public']['Tables']['members']['Row']
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const supabase = useSupabaseClient() as any
|
||||
const user = useSupabaseUser()
|
||||
const member = ref<Member | null>(null)
|
||||
const userNames = ref<Record<string, string>>({})
|
||||
|
||||
const role = computed<MemberRole | null>(() => member.value?.role ?? null)
|
||||
|
||||
function hasRequiredRole(requiredRoles: MemberRole[]): boolean {
|
||||
if (!role.value) return false
|
||||
return requiredRoles.includes(role.value)
|
||||
}
|
||||
|
||||
const isAdmin = computed(() => hasRequiredRole(['admin']))
|
||||
const isBoatswain = computed(() => hasRequiredRole(['admin', 'boatswain']))
|
||||
|
||||
const displayName = computed(() => {
|
||||
if (!member.value) return user.value?.email ?? ''
|
||||
const { first_name, last_name } = member.value
|
||||
if (first_name || last_name) return `${first_name} ${last_name}`.trim()
|
||||
return user.value?.email ?? ''
|
||||
})
|
||||
|
||||
async function fetchMember() {
|
||||
if (!user.value) { member.value = null; return }
|
||||
const { data } = await supabase
|
||||
.from('members')
|
||||
.select('*')
|
||||
.eq('user_id', user.value.id)
|
||||
.single()
|
||||
member.value = data
|
||||
}
|
||||
|
||||
// Returns cached name or triggers async fetch; updates reactively when resolved.
|
||||
function getUserNameById(id: string | null | undefined): string {
|
||||
if (!id) return 'No User'
|
||||
if (!userNames.value[id]) {
|
||||
userNames.value[id] = 'Loading...'
|
||||
supabase
|
||||
.from('members')
|
||||
.select('first_name, last_name')
|
||||
.eq('user_id', id)
|
||||
.single()
|
||||
.then(({ data }: { data: Pick<Member, 'first_name' | 'last_name'> | null }) => {
|
||||
if (data) {
|
||||
userNames.value[id] = `${data.first_name} ${data.last_name}`.trim() || 'Unknown'
|
||||
} else {
|
||||
userNames.value[id] = 'Unknown'
|
||||
}
|
||||
})
|
||||
}
|
||||
return userNames.value[id]!
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
await supabase.auth.signOut()
|
||||
member.value = null
|
||||
await navigateTo('/login')
|
||||
}
|
||||
|
||||
async function sendMagicLink(email: string) {
|
||||
const { error } = await supabase.auth.signInWithOtp({
|
||||
email,
|
||||
options: { emailRedirectTo: window.location.origin + '/auth/callback' },
|
||||
})
|
||||
if (error) throw error
|
||||
}
|
||||
|
||||
async function sendOtp(email: string) {
|
||||
const { error } = await supabase.auth.signInWithOtp({ email })
|
||||
if (error) throw error
|
||||
}
|
||||
|
||||
async function verifyOtp(email: string, token: string) {
|
||||
const { error } = await supabase.auth.verifyOtp({ email, token, type: 'email' })
|
||||
if (error) throw error
|
||||
await fetchMember()
|
||||
}
|
||||
|
||||
async function updateName(firstName: string, lastName: string) {
|
||||
if (!user.value) return
|
||||
const { error } = await supabase
|
||||
.from('members')
|
||||
.update({ first_name: firstName, last_name: lastName })
|
||||
.eq('user_id', user.value.id)
|
||||
if (error) throw error
|
||||
if (member.value) {
|
||||
member.value.first_name = firstName
|
||||
member.value.last_name = lastName
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
member,
|
||||
role,
|
||||
isAdmin,
|
||||
isBoatswain,
|
||||
displayName,
|
||||
hasRequiredRole,
|
||||
getUserNameById,
|
||||
fetchMember,
|
||||
sendMagicLink,
|
||||
sendOtp,
|
||||
verifyOtp,
|
||||
updateName,
|
||||
signOut,
|
||||
}
|
||||
})
|
||||
23
app/stores/boat.ts
Normal file
23
app/stores/boat.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
// TODO: Replace with generated Supabase types after `npx supabase gen types typescript`
|
||||
interface Boat {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
active: boolean
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export const useBoatStore = defineStore('boat', () => {
|
||||
const supabase = useSupabaseClient()
|
||||
const boats = ref<Map<string, Boat>>(new Map())
|
||||
|
||||
async function fetchBoats() {
|
||||
const { data, error } = await supabase.from('boats').select('*').order('name')
|
||||
if (error) throw error
|
||||
boats.value = new Map((data as Boat[]).map((b) => [b.id, b]))
|
||||
}
|
||||
|
||||
return { boats, fetchBoats }
|
||||
})
|
||||
Reference in New Issue
Block a user