feat: open-source under AGPL-3.0, redesign UI to grayscale palette, add uncategorize API, fix VAT account names

Add LICENSE (AGPL-3.0-or-later), CONTRIBUTING.md, SECURITY.md, DCO, and NOTICE files.
Rewrite README for open-source audience with self-hosting instructions.
Redesign color palette to grayscale chrome theme across all components.
Add transaction uncategorize API route with tests.
Fix VAT account name mismatches in migration 052.
Improve import page with SIE file support and loading skeleton.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-03-05 18:18:11 +01:00
co-authored by Claude Opus 4.6
parent 29240738fa
commit f3ec634a46
58 changed files with 1662 additions and 218 deletions
@@ -176,7 +176,7 @@ export function CalendarDayView({
{deadline.priority !== 'normal' && (
<span className={cn(
'ml-2 px-1.5 py-0.5 rounded',
deadline.priority === 'critical' && 'bg-red-100 text-red-700',
deadline.priority === 'critical' && 'bg-destructive/10 text-destructive',
deadline.priority === 'important' && 'bg-orange-100 text-orange-700'
)}>
{PRIORITY_LABELS[deadline.priority]}
@@ -30,7 +30,7 @@ function isResendConfigured(): boolean {
export class ResendEmailService implements EmailService {
async sendEmail(options: SendEmailOptions): Promise<SendEmailResult> {
const { to, subject, html, text, replyTo, fromName, attachments } = options
const { to, cc, subject, html, text, replyTo, fromName, attachments } = options
if (!this.isConfigured()) {
return { success: false, error: 'Email service is not configured' }
@@ -45,6 +45,7 @@ export class ResendEmailService implements EmailService {
const response = await resend.emails.send({
from,
to: Array.isArray(to) ? to : [to],
cc: cc ? (Array.isArray(cc) ? cc : [cc]) : undefined,
subject,
html,
text,
@@ -34,19 +34,19 @@ export function BankConnectionStatus({
const statusConfig = {
active: {
icon: CheckCircle,
color: 'text-green-600',
color: 'text-success',
label: 'Aktiv',
variant: 'success' as const,
},
pending: {
icon: Loader2,
color: 'text-yellow-600',
color: 'text-warning',
label: 'Väntar',
variant: 'warning' as const,
},
error: {
icon: XCircle,
color: 'text-red-600',
color: 'text-destructive',
label: 'Fel',
variant: 'destructive' as const,
},
@@ -268,15 +268,15 @@ export default function ReceiptCamera({ onCapture, onClose }: ReceiptCameraProps
{isStreaming && (
<div className="absolute bottom-36 left-0 right-0 flex justify-center z-10">
<div className="bg-black/70 rounded-full px-4 py-2 flex items-center gap-4">
<div className={`flex items-center gap-1 ${quality.lightingOk ? 'text-green-400' : 'text-yellow-400'}`}>
<div className={`flex items-center gap-1 ${quality.lightingOk ? 'text-success' : 'text-yellow-400'}`}>
<SunMedium className="h-4 w-4" />
<span className="text-xs">Ljus</span>
</div>
<div className={`flex items-center gap-1 ${quality.distanceOk ? 'text-green-400' : 'text-yellow-400'}`}>
<div className={`flex items-center gap-1 ${quality.distanceOk ? 'text-success' : 'text-yellow-400'}`}>
<Move className="h-4 w-4" />
<span className="text-xs">Avstånd</span>
</div>
<div className={`flex items-center gap-1 ${quality.focusOk ? 'text-green-400' : 'text-yellow-400'}`}>
<div className={`flex items-center gap-1 ${quality.focusOk ? 'text-success' : 'text-yellow-400'}`}>
<Focus className="h-4 w-4" />
<span className="text-xs">Fokus</span>
</div>
@@ -294,7 +294,7 @@ export default function ReceiptReviewView({
<div className="p-2 rounded bg-muted text-xs space-y-1">
<p>
<span className="text-muted-foreground">Avdragsgill del:</span>{' '}
<span className="font-medium text-green-600">
<span className="font-medium text-success">
{formatCurrency(representationCalc.deductibleAmount, receipt.currency)}
</span>
{' '}(max {representationCalc.maxDeductiblePerPerson} kr/person)
@@ -351,7 +351,7 @@ export default function ReceiptReviewView({
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Företag:</span>
<span className="font-medium text-green-600">
<span className="font-medium text-success">
{formatCurrency(splitSummary.businessTotal, receipt.currency)}
</span>
</div>