Files
accounted/app/(dashboard)/request-context.ts
T
MattssonandClaude Fable 5 45d7f1be4e feat(mileage): surface Körjournal in the nav behind a settings toggle (#1540)
* feat(mileage): surface Körjournal in the nav behind a settings toggle

The /mileage page shipped hidden: the route works but no nav row points at
it. Add company_settings.mileage_enabled (mirroring dimensions_enabled) with
a switch in Fönster -> Bokföring, and show the Arbeta nav row when the toggle
is on OR the company already has mileage_trips rows, the same hybrid gate as
webshop orders, so trips created via API/MCP can never become invisible
underlag. UI visibility only, never load-bearing for correctness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(migrations): move mileage_enabled migration after already-applied 20260812153208

origin/main merged in 20260812153208 which prod has already applied; a new
file sorting before it risks an out-of-order db push abort.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 21:48:01 +02:00

80 lines
2.6 KiB
TypeScript

import 'server-only'
import { cache } from 'react'
import { createClient } from '@/lib/supabase/server'
import { getActiveCompanyId } from '@/lib/company/context'
import { ensureSandboxAgentProfile } from '@/lib/sandbox/ensure-agent'
/**
* Request-local dashboard auth context. React cache shares the Supabase client
* and auth lookup between the dashboard layout and every nested server page.
*/
export const getDashboardAuthContext = cache(async () => {
const supabase = await createClient()
const {
data: { user },
} = await supabase.auth.getUser()
return { supabase, user }
})
/**
* Request-local active company resolution. Nested layouts and pages commonly
* need the same value, so resolving it once removes repeated preference and
* membership round trips without caching anything across requests.
*/
export const getDashboardCompanyId = cache(async () => {
const { supabase, user } = await getDashboardAuthContext()
return user ? getActiveCompanyId(supabase, user.id) : null
})
export const getDashboardSettings = cache(async () => {
const [{ supabase }, companyId] = await Promise.all([
getDashboardAuthContext(),
getDashboardCompanyId(),
])
if (!companyId) return { data: null, error: null }
return supabase
.from('company_settings')
.select('company_name, onboarding_complete, entity_type, pays_salaries, is_sandbox, dimensions_enabled, mileage_enabled, ore_rounding, initial_setup_path, initial_setup_completed_at, initial_setup_dismissed_at, vat_registered, moms_period')
.eq('company_id', companyId)
.maybeSingle()
})
const getDashboardAgentProfile = cache(async () => {
const [{ supabase }, companyId] = await Promise.all([
getDashboardAuthContext(),
getDashboardCompanyId(),
])
if (!companyId) return { data: null, error: null }
return supabase
.from('agent_profiles')
.select('display_name, avatar_id, verified_at')
.eq('company_id', companyId)
.maybeSingle()
})
export const getResolvedDashboardAgentProfile = cache(async () => {
const [{ supabase }, companyId, settingsResult, profileResult] = await Promise.all([
getDashboardAuthContext(),
getDashboardCompanyId(),
getDashboardSettings(),
getDashboardAgentProfile(),
])
let profile = profileResult.data
if (companyId && settingsResult.data?.is_sandbox === true && !profile?.verified_at) {
await ensureSandboxAgentProfile(supabase, companyId)
const refreshed = await supabase
.from('agent_profiles')
.select('display_name, avatar_id, verified_at')
.eq('company_id', companyId)
.maybeSingle()
profile = refreshed.data ?? profile
}
return profile
})