feat: UI polish, lint fixes, onboarding redesign, help page expansion, and test improvements

Broad update across dashboard pages, components, extensions, and lib code. Includes ESLint config additions, onboarding flow redesign, settings page refactor, help page content expansion, dead code removal, and test mock fixes. Adds dev docs and public assets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-03-05 23:05:49 +01:00
co-authored by Claude Opus 4.6
parent 02ebe50f52
commit 091d043c85
106 changed files with 1177 additions and 326 deletions
@@ -31,6 +31,7 @@ function mockAuth(userId: string | null) {
data: { user: userId ? { id: userId } : null },
}),
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any)
}
@@ -23,6 +23,7 @@ function mockAuth(userId: string | null) {
data: { user: userId ? { id: userId } : null },
}),
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any)
}
@@ -45,9 +46,11 @@ describe('GET /api/audit-trail', () => {
{ id: '1', action: 'INSERT', table_name: 'journal_entries', created_at: '2024-01-01T00:00:00Z' },
{ id: '2', action: 'COMMIT', table_name: 'journal_entries', created_at: '2024-01-02T00:00:00Z' },
]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mockGetAuditLog.mockResolvedValue({ data: entries as any, count: 2 })
const req = createMockRequest('/api/audit-trail')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { status, body } = await parseJsonResponse<{ data: any[]; count: number }>(await GET(req))
expect(status).toBe(200)
+1 -1
View File
@@ -60,7 +60,7 @@ export async function PUT(
const body: Partial<CreateDeadlineInput> = await request.json()
// First, get existing deadline to verify ownership
const { data: existing, error: fetchError } = await supabase
const { data: _existing, error: fetchError } = await supabase
.from('deadlines')
.select('*')
.eq('id', id)
+1 -1
View File
@@ -34,7 +34,7 @@ export async function POST(request: Request) {
}
const body: ExecuteRequest = await request.json()
const { transactions, format, filename, file_hash, skip_duplicates = true, auto_categorize = true } = body
const { transactions, format, filename, file_hash, skip_duplicates: _skip_duplicates = true, auto_categorize: _auto_categorize = true } = body
if (!transactions || transactions.length === 0) {
return NextResponse.json({ error: 'No transactions to import' }, { status: 400 })
@@ -89,7 +89,7 @@ describe('POST /api/invoices/[id]/mark-paid', () => {
const request = createMockRequest('/api/invoices/inv-1/mark-paid', { method: 'POST' })
const response = await POST(request, createMockRouteParams({ id: 'inv-1' }))
const { status, body } = await parseJsonResponse<{ error: string }>(response)
const { status, body: _body } = await parseJsonResponse<{ error: string }>(response)
expect(status).toBe(400)
})
+1 -1
View File
@@ -4,7 +4,7 @@ import { eventBus } from '@/lib/events'
import { ensureInitialized } from '@/lib/init'
import { CreateInvoiceSchema, CreateCreditNoteSchema } from '@/lib/api/schemas'
import type { EntityType, AccountingMethod, Invoice, CreditNote, InvoiceDocumentType } from '@/types'
import { getVatRules, calculateVat, calculateTotal, getAvailableVatRates, getVatTreatmentForRate } from '@/lib/invoices/vat-rules'
import { getVatRules, getAvailableVatRates } from '@/lib/invoices/vat-rules'
import { fetchExchangeRate, convertToSEK } from '@/lib/currency/riksbanken'
import {
createCreditNoteJournalEntry,
@@ -23,6 +23,7 @@ function mockAuth(userId: string | null) {
data: { user: userId ? { id: userId } : null },
}),
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any)
}
@@ -67,6 +68,7 @@ describe('GET /api/reports/audit-trail', () => {
it('returns CSV format with correct headers', async () => {
mockAuth('user-1')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mockGetAuditLog.mockResolvedValue({ data: sampleEntries as any, count: 2 })
const req = createMockRequest('/api/reports/audit-trail', {
@@ -88,6 +90,7 @@ describe('GET /api/reports/audit-trail', () => {
it('returns JSON format as downloadable file', async () => {
mockAuth('user-1')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mockGetAuditLog.mockResolvedValue({ data: sampleEntries as any, count: 2 })
const req = createMockRequest('/api/reports/audit-trail', {
@@ -118,7 +121,9 @@ describe('GET /api/reports/audit-trail', () => {
}))
mockGetAuditLog
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.mockResolvedValueOnce({ data: bigPage as any, count: 600 })
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.mockResolvedValueOnce({ data: lastPage as any, count: 600 })
const req = createMockRequest('/api/reports/audit-trail', {
@@ -1,6 +1,5 @@
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
import { findFiscalPeriod } from '@/lib/bookkeeping/engine'
import {
createSupplierInvoicePaymentEntry,
createSupplierInvoiceCashEntry,
@@ -9,41 +8,6 @@ import { validateBody } from '@/lib/api/validate'
import { MatchSupplierInvoiceSchema } from '@/lib/api/schemas'
import type { SupplierInvoice, SupplierInvoiceItem } from '@/types'
/**
* Ensure a fiscal period exists for the given date
*/
async function ensureFiscalPeriod(
supabase: Awaited<ReturnType<typeof createClient>>,
userId: string,
date: string
): Promise<string | null> {
const existingPeriodId = await findFiscalPeriod(supabase, userId, date)
if (existingPeriodId) return existingPeriodId
const transactionDate = new Date(date)
const year = transactionDate.getFullYear()
const { data, error } = await supabase
.from('fiscal_periods')
.upsert({
user_id: userId,
name: `Räkenskapsår ${year}`,
period_start: `${year}-01-01`,
period_end: `${year}-12-31`,
}, {
onConflict: 'user_id,period_start,period_end',
})
.select('id')
.single()
if (error) {
console.error('Failed to create fiscal period:', error)
return null
}
return data?.id || null
}
/**
* POST /api/transactions/[id]/match-supplier-invoice
*
@@ -1,7 +1,7 @@
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
import { getSuggestedCategories, mergeAiSuggestions, getSuggestedTemplates, type SuggestedCategory, type SuggestedTemplate } from '@/lib/transactions/category-suggestions'
import type { Transaction, TransactionCategory, EntityType } from '@/types'
import type { Transaction, EntityType } from '@/types'
/**
* POST /api/transactions/suggest-categories