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
@@ -16,7 +16,6 @@ VALUES (
ARRAY['text/plain']
)
ON CONFLICT (id) DO NOTHING;
ON CONFLICT (id) DO NOTHING;
-- =============================================================================
-- 2. INSERT policy: Users can upload to companies they belong to
@@ -0,0 +1,151 @@
-- Migration: Silent teams for all users
--
-- Every user now gets a silent team at signup. This migration:
-- 1. Creates an ensure_user_team() RPC for idempotent team creation
-- 2. Backfills: creates teams for existing users who don't have one
-- 3. Assigns orphaned companies (team_id IS NULL) to their owner's team
-- 4. Deletes incomplete companies (mid-onboarding, no journal entries)
-- =============================================================================
-- 1. NEW RPC: ensure_user_team()
-- =============================================================================
-- Idempotently ensures the calling user has a team.
-- Returns the team_id (existing or newly created).
CREATE OR REPLACE FUNCTION public.ensure_user_team()
RETURNS uuid
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_user_id uuid;
v_team_id uuid;
BEGIN
v_user_id := auth.uid();
IF v_user_id IS NULL THEN
RAISE EXCEPTION 'Not authenticated';
END IF;
-- Check if user already has a team
SELECT team_id INTO v_team_id
FROM public.team_members
WHERE user_id = v_user_id
LIMIT 1;
IF v_team_id IS NOT NULL THEN
RETURN v_team_id;
END IF;
-- Create a new team (name doesn't matter — hidden from UI)
INSERT INTO public.teams (name, created_by)
VALUES ('Personal', v_user_id)
RETURNING id INTO v_team_id;
-- Add user as team owner
INSERT INTO public.team_members (team_id, user_id, role)
VALUES (v_team_id, v_user_id, 'owner');
RETURN v_team_id;
END;
$$;
GRANT EXECUTE ON FUNCTION public.ensure_user_team() TO authenticated;
-- =============================================================================
-- 2. BACKFILL: Create teams for existing users without one
-- =============================================================================
-- Find all users who have company_members rows but no team_members rows.
-- Create a team for each and add them as owner.
DO $$
DECLARE
rec RECORD;
v_team_id uuid;
BEGIN
FOR rec IN
SELECT DISTINCT cm.user_id
FROM public.company_members cm
WHERE NOT EXISTS (
SELECT 1 FROM public.team_members tm WHERE tm.user_id = cm.user_id
)
LOOP
-- Create team
INSERT INTO public.teams (name, created_by)
VALUES ('Personal', rec.user_id)
RETURNING id INTO v_team_id;
-- Add as owner
INSERT INTO public.team_members (team_id, user_id, role)
VALUES (v_team_id, rec.user_id, 'owner');
-- Assign all companies owned by this user to the new team
UPDATE public.companies
SET team_id = v_team_id
WHERE created_by = rec.user_id
AND team_id IS NULL;
END LOOP;
END;
$$;
-- =============================================================================
-- 3. Assign any remaining orphaned companies to their creator's team
-- =============================================================================
-- Edge case: companies where team_id IS NULL but the creator already has a team
-- (e.g., they were a team member but also had solo companies).
UPDATE public.companies c
SET team_id = (
SELECT tm.team_id
FROM public.team_members tm
WHERE tm.user_id = c.created_by
LIMIT 1
)
WHERE c.team_id IS NULL
AND EXISTS (
SELECT 1 FROM public.team_members tm WHERE tm.user_id = c.created_by
);
-- =============================================================================
-- 4. Delete incomplete companies (mid-onboarding cleanup)
-- =============================================================================
-- Only delete companies where:
-- - onboarding_complete is false or no settings row exists
-- - There are zero journal entries
-- - There are zero transactions
-- This is safe because no real bookkeeping data exists.
DO $$
DECLARE
rec RECORD;
v_je_count int;
v_tx_count int;
BEGIN
FOR rec IN
SELECT c.id AS company_id
FROM public.companies c
LEFT JOIN public.company_settings cs ON cs.company_id = c.id
WHERE (cs.onboarding_complete IS NULL OR cs.onboarding_complete = false)
LOOP
-- Check for journal entries
SELECT count(*) INTO v_je_count
FROM public.journal_entries
WHERE company_id = rec.company_id;
-- Check for transactions
SELECT count(*) INTO v_tx_count
FROM public.transactions
WHERE company_id = rec.company_id;
-- Only delete if truly empty
IF v_je_count = 0 AND v_tx_count = 0 THEN
-- Delete dependent rows first (order matters for FK constraints)
DELETE FROM public.company_settings WHERE company_id = rec.company_id;
DELETE FROM public.fiscal_periods WHERE company_id = rec.company_id;
DELETE FROM public.chart_of_accounts WHERE company_id = rec.company_id;
DELETE FROM public.company_members WHERE company_id = rec.company_id;
DELETE FROM public.companies WHERE id = rec.company_id;
END IF;
END LOOP;
END;
$$;