@@ -302,7 +283,7 @@ export default function SettingsPage() {
-
+
@@ -554,66 +535,63 @@ export default function SettingsPage() {
Anslutna banker
- {activeConnections.map((connection) => {
- const daysUntilExpiry = getDaysUntilExpiry(connection.consent_expires)
- const isExpiring = isConsentExpiringSoon(connection.consent_expires)
-
- return (
-
-
-
-
-
-
-
{connection.bank_name}
-
-
- Senast synkad: {formatDate(connection.last_synced_at || connection.created_at)}
-
- {isExpiring && (
-
-
- {daysUntilExpiry} dagar kvar
-
- )}
-
-
-
-
-
-
-
-
- )
- })}
+ {activeConnections.map((connection) => (
+
+ ))}
)}
+ {/* Connect new bank */}
+
+
+ Anslut ny bank
+
+ Välj din bank nedan för att koppla ditt konto via PSD2.
+
+
+
+
+ {selectedBank && (
+
+
+
+
+
{selectedBank.name}
+
{selectedBank.country}
+
+
+
+
+ )}
+
+
+
{/* Info about PSD2 */}
- Bankintegration (PSD2)
+ Om bankintegration (PSD2)
Automatisk import av transaktioner via PSD2 open banking.
Samtycket gäller i 90 dagar och behöver sedan förnyas.
diff --git a/components/dashboard/DashboardNav.tsx b/components/dashboard/DashboardNav.tsx
index a3b7d8b6..dfc2ac40 100644
--- a/components/dashboard/DashboardNav.tsx
+++ b/components/dashboard/DashboardNav.tsx
@@ -76,11 +76,14 @@ export default function DashboardNav({ companyName, entityType, enabledExtension
const isOvrigtExpanded = isOnOvrigtPage || manualOvrigtExpanded
// Auto-expand Tillägg when on an extension page, or when manually toggled (persisted)
const isOnExtensionPage = pathname.startsWith('/e/')
- const [manualTillaggExpanded, setManualTillaggExpanded] = useState(() => {
- if (typeof window === 'undefined') return false
- return localStorage.getItem('tillagg-expanded') === 'true'
- })
+ const [manualTillaggExpanded, setManualTillaggExpanded] = useState(false)
const isTillaggExpanded = isOnExtensionPage || manualTillaggExpanded
+
+ // Restore persisted state after hydration to avoid SSR mismatch
+ useEffect(() => {
+ const stored = localStorage.getItem('tillagg-expanded') === 'true'
+ if (stored) setManualTillaggExpanded(true)
+ }, [])
const [liveExtensions, setLiveExtensions] = useState(enabledExtensions ?? [])
const fetchExtensions = useCallback(async () => {
diff --git a/components/extensions/ExtensionToggleButton.tsx b/components/extensions/ExtensionToggleButton.tsx
index f39fcb98..38d7860b 100644
--- a/components/extensions/ExtensionToggleButton.tsx
+++ b/components/extensions/ExtensionToggleButton.tsx
@@ -1,23 +1,71 @@
'use client'
+import { useState } from 'react'
import { Switch } from '@/components/ui/switch'
+import { Button } from '@/components/ui/button'
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
import { useExtensionToggle } from '@/lib/extensions/hooks'
export default function ExtensionToggleButton({
sectorSlug,
extensionSlug,
+ subscriptionNotice,
}: {
sectorSlug: string
extensionSlug: string
+ subscriptionNotice?: string
}) {
const { enabled, isLoading, toggle } = useExtensionToggle(sectorSlug, extensionSlug)
+ const [showConfirm, setShowConfirm] = useState(false)
+
+ function handleToggle() {
+ // Show confirmation dialog when enabling an extension with a subscription notice
+ if (!enabled && subscriptionNotice) {
+ setShowConfirm(true)
+ return
+ }
+ toggle()
+ }
+
+ function handleConfirm() {
+ setShowConfirm(false)
+ toggle()
+ }
return (
-
+ <>
+
+
+ {subscriptionNotice && (
+
+ )}
+ >
)
}
diff --git a/components/extensions/general/EnableBankingWorkspace.tsx b/components/extensions/general/EnableBankingWorkspace.tsx
index 451bcaee..484610b5 100644
--- a/components/extensions/general/EnableBankingWorkspace.tsx
+++ b/components/extensions/general/EnableBankingWorkspace.tsx
@@ -1,15 +1,24 @@
'use client'
import type { WorkspaceComponentProps } from '@/lib/extensions/workspace-registry'
-import EmptyExtensionState from '@/components/extensions/shared/EmptyExtensionState'
-import { Landmark } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+import { Landmark, Settings } from 'lucide-react'
+import Link from 'next/link'
export default function EnableBankingWorkspace({ userId }: WorkspaceComponentProps) {
return (
- }
- />
+
+
+
Bankintegration (PSD2)
+
+ Koppla ditt bankkonto under Inställningar för att synka transaktioner automatiskt.
+
+
+
)
}
diff --git a/lib/extensions/loader.ts b/lib/extensions/loader.ts
index 0160a685..bd45fb32 100644
--- a/lib/extensions/loader.ts
+++ b/lib/extensions/loader.ts
@@ -14,11 +14,7 @@ import { intrastatExtension } from '@/extensions/export/intrastat'
import { currencyReceivablesExtension } from '@/extensions/export/currency-receivables'
import type { Extension } from './types'
-// ── Enable Banking (PSD2) — opt-in extension ───────────────────────────
-// Uncomment the following line to enable automatic PSD2 bank transaction sync.
-// Requires ENABLE_BANKING_APP_ID and ENABLE_BANKING_PRIVATE_KEY env vars.
-//
-// import { enableBankingExtension } from '@/extensions/general/enable-banking'
+import { enableBankingExtension } from '@/extensions/general/enable-banking'
/**
* Explicit list of first-party extensions.
@@ -36,7 +32,7 @@ const FIRST_PARTY_EXTENSIONS: Extension[] = [
invoiceInboxExtension,
calendarExtension,
userDescriptionMatchExtension,
- // enableBankingExtension, // Uncomment to activate PSD2 bank sync
+ enableBankingExtension,
// ── Export sector ──────────────────────────────────────────
euSalesListExtension,
diff --git a/lib/extensions/sectors.ts b/lib/extensions/sectors.ts
index 60b457fb..38d816c7 100644
--- a/lib/extensions/sectors.ts
+++ b/lib/extensions/sectors.ts
@@ -117,6 +117,8 @@ export const SECTORS: Sector[] = [
description: 'Automatisk banktransaktionssynk via PSD2',
longDescription:
'Koppla ditt bankkonto direkt och synka transaktioner automatiskt via säker PSD2-bankintegration. Stöder de flesta svenska banker.',
+ subscriptionNotice:
+ 'Denna integration kräver ett aktivt Enable Banking-abonnemang. Utan abonnemang kommer bankintegration inte att fungera.',
},
],
},
diff --git a/lib/extensions/types.ts b/lib/extensions/types.ts
index 4e38fdb9..93e43df0 100644
--- a/lib/extensions/types.ts
+++ b/lib/extensions/types.ts
@@ -39,6 +39,8 @@ export interface ExtensionDefinition {
readsCoreTables?: string[]
hasOwnData?: boolean
quickAction?: QuickActionDefinition
+ /** Notice shown when enabling — e.g. external subscription requirement */
+ subscriptionNotice?: string
}
/** Sector definition with its extensions */
diff --git a/lib/transactions/category-suggestions.ts b/lib/transactions/category-suggestions.ts
index 53bae4e9..99a826e0 100644
--- a/lib/transactions/category-suggestions.ts
+++ b/lib/transactions/category-suggestions.ts
@@ -166,8 +166,8 @@ function accountToCategory(account: string, amount: number): string | null {
}
/**
- * Source priority for sorting — higher-quality sources rank first.
- * AI and mapping rules always beat history-based guesses.
+ * Source priority for tiebreaking — used when two suggestions
+ * have the same confidence score.
*/
const SOURCE_PRIORITY: Record = {
mapping_rule: 3,
@@ -187,19 +187,28 @@ export function mergeAiSuggestions(
aiSuggestions: { category: string; basAccount: string; confidence: number; reasoning: string }[],
transactionAmount?: number
): SuggestedCategory[] {
- const seen = new Set(existing.map((s) => s.category))
const merged = [...existing]
for (const ai of aiSuggestions) {
- if (seen.has(ai.category)) continue
-
// Skip suggestions that don't match transaction direction
if (transactionAmount !== undefined) {
if (transactionAmount > 0 && ai.category.startsWith('expense_')) continue
if (transactionAmount < 0 && ai.category.startsWith('income_')) continue
}
- seen.add(ai.category)
+ const existingIdx = merged.findIndex((s) => s.category === ai.category)
+ if (existingIdx !== -1) {
+ // Upgrade existing entry if the AI has higher confidence
+ if (ai.confidence > merged[existingIdx].confidence) {
+ merged[existingIdx] = {
+ ...merged[existingIdx],
+ account: ai.basAccount || merged[existingIdx].account,
+ confidence: ai.confidence,
+ source: 'ai',
+ }
+ }
+ continue
+ }
merged.push({
category: ai.category as TransactionCategory,
@@ -210,12 +219,12 @@ export function mergeAiSuggestions(
})
}
- // Sort by source priority first, then by confidence within the same tier
+ // Sort by confidence first, then by source priority as tiebreaker
return merged
.sort((a, b) => {
- const priorityDiff = SOURCE_PRIORITY[b.source] - SOURCE_PRIORITY[a.source]
- if (priorityDiff !== 0) return priorityDiff
- return b.confidence - a.confidence
+ const confidenceDiff = b.confidence - a.confidence
+ if (confidenceDiff !== 0) return confidenceDiff
+ return SOURCE_PRIORITY[b.source] - SOURCE_PRIORITY[a.source]
})
.slice(0, 5)
}