Fix/multiple company (#203)

* Refactor onboarding and dashboard logic; add silent team creation for users

- Removed unnecessary useCompany context in DashboardContent and SettingsSidebar components.
- Simplified onboarding setup logic to allow direct access to the dashboard for users without companies.
- Introduced WelcomeOnboarding component to handle user onboarding steps.
- Added migration to create silent teams for all users at signup, backfilling existing users without teams, and cleaning up incomplete companies.

* fix: update greeting logic and improve email handling in TIC extension

* Redirect to onboarding for users without companies and update onboarding flow

* Build issue fix

* Enhance onboarding experience by adding existing companies check
This commit is contained in:
Mattsson
2026-04-09 11:54:12 +02:00
committed by GitHub
parent dec9a37d2d
commit bf5a8d9195
24 changed files with 1101 additions and 1173 deletions
+51 -30
View File
@@ -22,7 +22,7 @@ import type { TICCompanyProfile } from './lib/tic-types'
import type { BankIdCompleteRequest } from './lib/bankid-types'
import type { CompanyLookupResult } from '@/lib/company-lookup/types'
import { hashPersonalNumber, encryptPersonalNumber } from '@/lib/auth/bankid'
import { createServiceClientNoCookies } from '@/lib/auth/api-keys'
import { createServiceClient } from '@/lib/supabase/server'
import crypto from 'crypto'
// Server-side per-IP rate limit for /bankid/start (each call = billable TIC session)
@@ -450,7 +450,9 @@ export const ticExtension: Extension = {
)
}
if (mode === 'signup' && !email) {
const trimmedEmail = email?.trim().toLowerCase()
if (mode === 'signup' && !trimmedEmail) {
return NextResponse.json(
{ error: 'email is required for signup' },
{ status: 400 }
@@ -468,7 +470,7 @@ export const ticExtension: Extension = {
const { personalNumber, givenName, surname, name } = session.user
const pnrHash = hashPersonalNumber(personalNumber)
const supabase = createServiceClientNoCookies()
const supabase = createServiceClient()
// Look up existing BankID identity
const { data: existing } = await supabase
@@ -525,30 +527,51 @@ export const ticExtension: Extension = {
)
}
// Create new Supabase user
const randomPassword = crypto.randomBytes(32).toString('base64url')
const { data: newUser, error: createError } = await supabase.auth.admin.createUser({
email: email!,
email_confirm: true,
password: randomPassword,
user_metadata: { full_name: name },
})
// Check if email is already taken by a non-BankID user
const { data: existingByEmail } = await supabase
.from('profiles')
.select('id')
.eq('email', trimmedEmail!)
.single()
if (createError || !newUser?.user) {
console.error('[tic/bankid] createUser failed', createError)
return NextResponse.json(
{ error: 'Failed to create account', message: createError?.message },
{ status: 500 }
)
let userId: string
let isNewUser = true
if (existingByEmail) {
// Email already exists — link BankID to existing account
userId = existingByEmail.id
isNewUser = false
await supabase.auth.admin.updateUserById(userId, {
app_metadata: { bankid_linked: true },
user_metadata: { full_name: name },
})
} else {
// Create new Supabase user
const randomPassword = crypto.randomBytes(32).toString('base64url')
const { data: newUser, error: createError } = await supabase.auth.admin.createUser({
email: trimmedEmail!,
email_confirm: true,
password: randomPassword,
user_metadata: { full_name: name },
})
if (createError || !newUser?.user) {
console.error('[tic/bankid] createUser failed', { email: trimmedEmail, status: createError?.status, code: (createError as any)?.code, message: createError?.message })
return NextResponse.json(
{ error: 'Failed to create account', message: createError?.message },
{ status: 500 }
)
}
userId = newUser.user.id
// Mark user as BankID-linked (skips TOTP MFA)
await supabase.auth.admin.updateUserById(userId, {
app_metadata: { bankid_linked: true },
})
}
const userId = newUser.user.id
// Mark user as BankID-linked (skips TOTP MFA)
await supabase.auth.admin.updateUserById(userId, {
app_metadata: { bankid_linked: true },
})
// Store BankID identity
const { error: insertError } = await supabase
.from('bankid_identities')
@@ -562,8 +585,6 @@ export const ticExtension: Extension = {
if (insertError) {
console.error('[tic/bankid] insert bankid_identities failed', insertError)
// Clean up the created user if identity linking fails
await supabase.auth.admin.deleteUser(userId)
return NextResponse.json(
{ error: 'Failed to link BankID identity' },
{ status: 500 }
@@ -573,7 +594,7 @@ export const ticExtension: Extension = {
// Generate magic link for session
const { data: link, error: linkError } = await supabase.auth.admin.generateLink({
type: 'magiclink',
email: email!,
email: trimmedEmail!,
})
if (linkError || !link?.properties?.hashed_token) {
@@ -613,7 +634,7 @@ export const ticExtension: Extension = {
data: {
tokenHash: link.properties.hashed_token,
type: 'magiclink',
isNewUser: true,
isNewUser,
},
})
} catch (error) {
@@ -681,7 +702,7 @@ export const ticExtension: Extension = {
const { personalNumber, givenName, surname } = session.user
const pnrHash = hashPersonalNumber(personalNumber)
const supabase = createServiceClientNoCookies()
const supabase = createServiceClient()
// Check personnummer not already linked to another user
const { data: existing } = await supabase
@@ -746,7 +767,7 @@ export const ticExtension: Extension = {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const supabase = createServiceClientNoCookies()
const supabase = createServiceClient()
// Delete bankid_identities row
const { error: deleteError } = await supabase