feat(home): first-run block as a numbered four-step thread with partner marks (#1149)
* feat(home): first-run block as a numbered three-step thread with partner marks The founder-picked stepped shape for 'Hur vill du komma igang?': 1 Fa in din bokforing (primary Flytta bokforingen + Fortnox/Visma/Bokio marks + '+ SIE'; Starta fran borjan as an inline alternative that just checks the step off), 2 Koppla banken (Enable Banking mark only), 3 Bygg din bokforingsassistent (Beta chip, no vendor logo). Dots walk number -> filled active -> sage check; the persisted state machine is unchanged, but choosing a path no longer auto-completes the setup: the block retires when all three steps are done (or via Dolj). DashboardContent's build-assistant hero now waits until the checklist is gone so the assistant is not pitched twice. initial_setup i18n rewritten for the stepped copy (sv+en), unused selected-state keys removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(home): Skatteverket as step three, assistant last, ticked steps collapse Founder feedback on #1149: the thread is now four steps: 1 Fa in din bokforing, 2 Koppla banken, 3 Anslut Skatteverket (with the SKV mark, BankID authorize link; skipped entirely in builds without the skatteverket extension), 4 Bygg din bokforingsassistent. A completed step drops its description and actions and keeps only the checked muted title, so the fresh-start pitch never lingers after the books are in. The heading counts honestly ({count} steg) and completion now requires all four steps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * polish(home): one-line checklist header Founder feedback: the title and sub-line said the same thing twice; only '4 steg sa ar bokforingen igang' remains (Dolj stays beside it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
63fd5311ed
commit
9dfa6c6708
@@ -87,7 +87,10 @@ export const PATCH = withRouteContext(
|
||||
const update: Record<string, unknown> = {}
|
||||
if (body.path !== undefined) {
|
||||
update.initial_setup_path = body.path
|
||||
update.initial_setup_completed_at = body.path === 'fresh' ? now : null
|
||||
// Choosing a path no longer completes the setup: since the stepped
|
||||
// Hem block (rest-of-nav 2), "fresh" just checks off step one and the
|
||||
// block completes when every step is done.
|
||||
update.initial_setup_completed_at = null
|
||||
update.initial_setup_dismissed_at = null
|
||||
}
|
||||
if (body.completed !== undefined) {
|
||||
|
||||
@@ -101,8 +101,10 @@ export default function DashboardContent({
|
||||
|
||||
{/* Build-assistant hero: shown only until the company has a verified
|
||||
agent_profile, so existing/migrated users get a clear prompt instead
|
||||
of a full-screen onboarding takeover. */}
|
||||
{!agentBuilt && (
|
||||
of a full-screen onboarding takeover. While the stepped first-run
|
||||
checklist is visible it already carries the assistant as step 3, so
|
||||
the hero waits until that block is dismissed or completed. */}
|
||||
{!agentBuilt && (initialSetup.dismissedAt || initialSetup.completedAt) && (
|
||||
<section>
|
||||
{/* Non-payers keep seeing the hero (conversion surface) but it
|
||||
routes to billing instead of a build flow that would 403. */}
|
||||
|
||||
@@ -2,18 +2,9 @@
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import {
|
||||
ArrowRight,
|
||||
ArrowRightLeft,
|
||||
FileCheck,
|
||||
Landmark,
|
||||
MessageCircle,
|
||||
Sparkles,
|
||||
} from 'lucide-react'
|
||||
import { Check } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useErrorToast } from '@/lib/hooks/use-error-toast'
|
||||
import { ENABLED_EXTENSION_IDS } from '@/lib/extensions/_generated/enabled-extensions'
|
||||
@@ -30,12 +21,14 @@ interface NewUserChecklistProps {
|
||||
hasAgentBuilt?: boolean
|
||||
}
|
||||
|
||||
const pathIcons = {
|
||||
migration: ArrowRightLeft,
|
||||
bank: Landmark,
|
||||
fresh: Sparkles,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* First-run getting-started block on Hem, in the founder-picked stepped
|
||||
* shape: a numbered three-step thread (get the books in, connect the bank,
|
||||
* build the assistant) with the partner marks on the steps that have them.
|
||||
* The persisted state machine is unchanged (path / completedAt /
|
||||
* dismissedAt via /api/onboarding/state); "Starta från början" records the
|
||||
* fresh path and simply checks off step one.
|
||||
*/
|
||||
export default function NewUserChecklist({
|
||||
initialState,
|
||||
className,
|
||||
@@ -81,111 +74,238 @@ export default function NewUserChecklist({
|
||||
}
|
||||
}
|
||||
|
||||
const step1Done = hasBookkeepingImported || state.path === 'fresh'
|
||||
const step2Done = hasBankConnected
|
||||
// Companies built without the skatteverket extension skip that step.
|
||||
const step3Done = !hasSkatteverket || hasSkatteverketConnected
|
||||
const step4Done = hasAgentBuilt
|
||||
|
||||
useEffect(() => {
|
||||
const selectedPathComplete =
|
||||
(state.path === 'migration' && hasBookkeepingImported) ||
|
||||
(state.path === 'bank' && hasBankConnected)
|
||||
if (!state.completedAt && selectedPathComplete && saving === null) {
|
||||
// The block retires itself once every step is done; Dölj remains the
|
||||
// manual way out.
|
||||
if (!state.completedAt && step1Done && step2Done && step3Done && step4Done && saving === null) {
|
||||
void persist({ completed: true }, 'complete')
|
||||
}
|
||||
// persist intentionally stays out: its identity follows the toast hook and
|
||||
// would retrigger this completion sync after every render.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hasBankConnected, hasBookkeepingImported, saving, state.completedAt, state.path])
|
||||
}, [step1Done, step2Done, step3Done, step4Done, saving, state.completedAt])
|
||||
|
||||
if (state.dismissedAt || state.completedAt) return null
|
||||
|
||||
const choosePath = async (path: InitialSetupPath) => {
|
||||
const updated = await persist({ path }, path)
|
||||
if (!updated || path === 'fresh') return
|
||||
if (path === 'migration') {
|
||||
router.push(hasMigration ? '/import?mode=migration' : '/import?mode=sie')
|
||||
} else {
|
||||
router.push(hasBanking ? '/import?mode=psd2' : '/import?mode=bank')
|
||||
}
|
||||
const goMigration = async () => {
|
||||
const updated = await persist({ path: 'migration' }, 'migration')
|
||||
if (updated) router.push(hasMigration ? '/import?mode=migration' : '/import?mode=sie')
|
||||
}
|
||||
const goFresh = () => void persist({ path: 'fresh' }, 'fresh')
|
||||
const goBank = async () => {
|
||||
const updated = await persist({ path: state.path ?? 'bank' }, 'bank')
|
||||
if (updated) router.push(hasBanking ? '/import?mode=psd2' : '/import?mode=bank')
|
||||
}
|
||||
|
||||
if (!state.path) {
|
||||
return (
|
||||
<Card className={cn('border-foreground/20', className)}>
|
||||
<CardHeader className="pb-4">
|
||||
<CardTitle className="text-lg">{t('title')}</CardTitle>
|
||||
<CardDescription>{t('description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-3 md:grid-cols-3">
|
||||
{(['migration', 'bank', 'fresh'] as const).map((path) => {
|
||||
const Icon = pathIcons[path]
|
||||
return (
|
||||
<button
|
||||
key={path}
|
||||
type="button"
|
||||
disabled={saving !== null}
|
||||
onClick={() => void choosePath(path)}
|
||||
className="group min-h-28 rounded-lg border border-border p-4 text-left transition-colors hover:border-foreground/40 hover:bg-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<Icon className="h-5 w-5" />
|
||||
<ArrowRight className="h-4 w-4 text-muted-foreground transition-transform group-hover:translate-x-0.5" />
|
||||
</div>
|
||||
<p className="mt-4 text-sm font-medium">{t(`${path}_title`)}</p>
|
||||
<p className="mt-1 text-xs leading-relaxed text-muted-foreground">
|
||||
{t(`${path}_description`)}
|
||||
</p>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const primaryHref = state.path === 'migration'
|
||||
? (hasMigration ? '/import?mode=migration' : '/import?mode=sie')
|
||||
: (hasBanking ? '/import?mode=psd2' : '/import?mode=bank')
|
||||
const PrimaryIcon = pathIcons[state.path]
|
||||
const activeStep = !step1Done ? 1 : !step2Done ? 2 : !step3Done ? 3 : 4
|
||||
|
||||
return (
|
||||
<Card className={cn('border-foreground/20', className)}>
|
||||
<CardHeader className="pb-4">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-lg">{t(`${state.path}_selected_title`)}</CardTitle>
|
||||
<CardDescription className="mt-1">{t(`${state.path}_selected_description`)}</CardDescription>
|
||||
<section className={className} aria-label={t('title', { count: hasSkatteverket ? 4 : 3 })}>
|
||||
<div className="mb-1 flex items-center justify-between gap-3">
|
||||
<h2 className="text-sm font-medium">{t('title', { count: hasSkatteverket ? 4 : 3 })}</h2>
|
||||
<button
|
||||
type="button"
|
||||
disabled={saving !== null}
|
||||
onClick={() => void persist({ dismissed: true }, 'dismiss')}
|
||||
className="shrink-0 text-xs text-muted-foreground underline decoration-border underline-offset-4 transition-colors hover:text-foreground"
|
||||
>
|
||||
{t('dismiss')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<Step
|
||||
number={1}
|
||||
done={step1Done}
|
||||
active={activeStep === 1}
|
||||
title={t('step_books_title')}
|
||||
>
|
||||
<p className="text-xs leading-5 text-muted-foreground">
|
||||
{t('step_books_description')}{' '}
|
||||
<button
|
||||
type="button"
|
||||
disabled={saving !== null}
|
||||
onClick={goFresh}
|
||||
className="underline decoration-border underline-offset-4 transition-colors hover:text-foreground"
|
||||
>
|
||||
{t('step_books_fresh_link')}
|
||||
</button>{' '}
|
||||
{t('step_books_fresh_suffix')}
|
||||
</p>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-3">
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={saving !== null}
|
||||
onClick={() => void goMigration()}
|
||||
>
|
||||
{t('step_books_action')}
|
||||
</Button>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<LogoMark src="/logos/fortnox.svg" name="Fortnox" />
|
||||
<LogoMark src="/logos/visma.jpeg" name="Visma" />
|
||||
<LogoMark src="/logos/bokio.png" name="Bokio" />
|
||||
<span className="ml-1 text-[11px] text-muted-foreground">{t('step_books_sie')}</span>
|
||||
</span>
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
number={2}
|
||||
done={step2Done}
|
||||
active={activeStep === 2}
|
||||
title={t('step_bank_title')}
|
||||
>
|
||||
<p className="text-xs leading-5 text-muted-foreground">{t('step_bank_description')}</p>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-3">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={activeStep === 2 ? 'default' : 'outline'}
|
||||
disabled={saving !== null}
|
||||
onClick={() => void goBank()}
|
||||
>
|
||||
{t('step_bank_action')}
|
||||
</Button>
|
||||
{hasBanking && (
|
||||
<LogoMark
|
||||
src="/logos/enable-banking-icon.png"
|
||||
name="Enable Banking"
|
||||
mono
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={saving !== null}
|
||||
onClick={() => void persist({ dismissed: true }, 'dismiss')}
|
||||
</Step>
|
||||
|
||||
{hasSkatteverket && (
|
||||
<Step
|
||||
number={3}
|
||||
done={step3Done}
|
||||
active={activeStep === 3}
|
||||
title={t('step_skv_title')}
|
||||
>
|
||||
{t('dismiss')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<Button asChild>
|
||||
<Link href={primaryHref}>
|
||||
<PrimaryIcon className="mr-2 h-4 w-4" />
|
||||
{t(`${state.path}_action`)}
|
||||
</Link>
|
||||
</Button>
|
||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-muted-foreground">
|
||||
{hasSkatteverket && !hasSkatteverketConnected && (
|
||||
// The authorize endpoint redirects off-site to Skatteverket.
|
||||
// eslint-disable-next-line @next/next/no-html-link-for-pages
|
||||
<a href="/api/extensions/ext/skatteverket/authorize?return_to=/" className="inline-flex min-h-10 items-center hover:text-foreground">
|
||||
<FileCheck className="mr-2 h-4 w-4" />
|
||||
{t('optional_skatteverket')}
|
||||
</a>
|
||||
<p className="text-xs leading-5 text-muted-foreground">{t('step_skv_description')}</p>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-3">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={activeStep === 3 ? 'default' : 'outline'}
|
||||
asChild
|
||||
>
|
||||
{/* The authorize endpoint redirects off-site to Skatteverket. */}
|
||||
{/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
|
||||
<a href="/api/extensions/ext/skatteverket/authorize?return_to=/">
|
||||
{t('step_skv_action')}
|
||||
</a>
|
||||
</Button>
|
||||
<LogoMark src="/logos/skatteverket_color.svg" name="Skatteverket" />
|
||||
</div>
|
||||
</Step>
|
||||
)}
|
||||
|
||||
<Step
|
||||
number={hasSkatteverket ? 4 : 3}
|
||||
done={step4Done}
|
||||
active={activeStep === 4 || (!hasSkatteverket && activeStep === 3)}
|
||||
title={t('step_assistant_title')}
|
||||
badge={t('step_assistant_beta')}
|
||||
last
|
||||
>
|
||||
<p className="text-xs leading-5 text-muted-foreground">{t('step_assistant_description')}</p>
|
||||
<div className="mt-3">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={activeStep >= 3 ? 'default' : 'outline'}
|
||||
onClick={() => router.push(hasAi ? '/onboarding/agent' : '/settings/billing')}
|
||||
>
|
||||
{t('step_assistant_action')}
|
||||
</Button>
|
||||
</div>
|
||||
</Step>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
/** One numbered step on the thread: dot + hairline down to the next step. */
|
||||
function Step({
|
||||
number,
|
||||
done,
|
||||
active,
|
||||
title,
|
||||
badge,
|
||||
last = false,
|
||||
children,
|
||||
}: {
|
||||
number: number
|
||||
done: boolean
|
||||
active: boolean
|
||||
title: string
|
||||
badge?: string
|
||||
last?: boolean
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="relative flex gap-4 pb-6 last:pb-0">
|
||||
{!last && (
|
||||
<span
|
||||
className="absolute bottom-0 left-[13px] top-8 w-px bg-border"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
'relative z-10 flex h-7 w-7 shrink-0 items-center justify-center rounded-full border text-xs tabular-nums',
|
||||
done
|
||||
? 'border-success/40 text-success'
|
||||
: active
|
||||
? 'border-foreground bg-foreground text-background'
|
||||
: 'border-border text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{done ? <Check className="h-3.5 w-3.5" aria-hidden="true" /> : number}
|
||||
</span>
|
||||
<div className="min-w-0 flex-1 pt-1">
|
||||
<p className={cn('flex items-center gap-2 text-sm', active && !done && 'font-medium', done && 'text-muted-foreground')}>
|
||||
{title}
|
||||
{badge && (
|
||||
<span className="rounded-full border border-border px-2 py-0.5 text-[10px] leading-none text-muted-foreground">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
{!hasAgentBuilt && (
|
||||
<Link href={hasAi ? '/onboarding/agent' : '/settings/billing'} className="inline-flex min-h-10 items-center hover:text-foreground">
|
||||
<MessageCircle className="mr-2 h-4 w-4" />
|
||||
{t('optional_assistant')}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</p>
|
||||
{/* A ticked step needs no pitch: the description and actions only
|
||||
render while the step is still open. */}
|
||||
{!done && <div className="mt-1">{children}</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** Tiny partner mark: real logo on a white chip; `mono` darkens a white
|
||||
* source logo in light mode and lifts it in dark (Enable Banking). */
|
||||
function LogoMark({ src, name, mono = false }: { src: string; name: string; mono?: boolean }) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-6 w-6 items-center justify-center overflow-hidden rounded-md border border-border',
|
||||
!mono && 'bg-white',
|
||||
)}
|
||||
title={name}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={src}
|
||||
alt={name}
|
||||
className={cn(
|
||||
'h-4 w-4 object-contain',
|
||||
mono &&
|
||||
'opacity-90 [filter:grayscale(100%)_brightness(0.18)] dark:[filter:grayscale(100%)_brightness(1.5)]',
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
+17
-19
@@ -1132,26 +1132,24 @@
|
||||
"security_note": "Your data is encrypted and stored securely in Sweden"
|
||||
},
|
||||
"initial_setup": {
|
||||
"title": "How would you like to get started?",
|
||||
"description": "Choose the path that gets you to value fastest. You can switch or do the rest later.",
|
||||
"migration_title": "Move existing books",
|
||||
"migration_description": "Import from another system or upload a SIE file.",
|
||||
"bank_title": "Connect your bank",
|
||||
"bank_description": "Fetch transactions automatically or import a bank statement.",
|
||||
"fresh_title": "Start from scratch",
|
||||
"fresh_description": "Continue to the overview without prior bookkeeping.",
|
||||
"migration_selected_title": "Move your books",
|
||||
"migration_selected_description": "Import your history first. The overview stays available while you work.",
|
||||
"migration_action": "Open import",
|
||||
"bank_selected_title": "Connect your bank",
|
||||
"bank_selected_description": "Fetch transactions so you can start reconciling and bookkeeping.",
|
||||
"bank_action": "Connect or import bank",
|
||||
"fresh_selected_title": "You are ready",
|
||||
"fresh_selected_description": "Start working directly from the overview.",
|
||||
"fresh_action": "Continue",
|
||||
"title": "{count} steps and your bookkeeping is running",
|
||||
"step_books_title": "Get your books in",
|
||||
"step_books_description": "Import from another system or upload a SIE file. New business?",
|
||||
"step_books_fresh_link": "Start from scratch",
|
||||
"step_books_fresh_suffix": "instead.",
|
||||
"step_books_action": "Move the books",
|
||||
"step_books_sie": "+ SIE",
|
||||
"step_bank_title": "Connect the bank",
|
||||
"step_bank_description": "Fetch transactions automatically, or import a bank statement.",
|
||||
"step_bank_action": "Connect the bank",
|
||||
"step_assistant_title": "Build your bookkeeping assistant",
|
||||
"step_assistant_beta": "Beta",
|
||||
"step_assistant_description": "A few questions about your business calibrate an assistant that suggests bookkeeping for you.",
|
||||
"step_assistant_action": "Get started",
|
||||
"dismiss": "Hide",
|
||||
"optional_skatteverket": "Connect Skatteverket, optional",
|
||||
"optional_assistant": "Create assistant, optional"
|
||||
"step_skv_title": "Connect Skatteverket",
|
||||
"step_skv_description": "See the tax account and file VAT and employer declarations right from here. Connects with BankID in a couple of minutes.",
|
||||
"step_skv_action": "Connect Skatteverket"
|
||||
},
|
||||
"tax_assessment_notices": {
|
||||
"title": "Final tax notices and remaining tax",
|
||||
|
||||
+17
-19
@@ -1132,26 +1132,24 @@
|
||||
"security_note": "Din data är krypterad och lagras säkert i Sverige"
|
||||
},
|
||||
"initial_setup": {
|
||||
"title": "Hur vill du komma igång?",
|
||||
"description": "Välj den väg som ger dig första nyttan snabbast. Du kan alltid byta eller göra resten senare.",
|
||||
"migration_title": "Flytta befintlig bokföring",
|
||||
"migration_description": "Importera från ett annat system eller ladda upp en SIE-fil.",
|
||||
"bank_title": "Koppla banken",
|
||||
"bank_description": "Hämta transaktioner automatiskt eller importera ett kontoutdrag.",
|
||||
"fresh_title": "Starta från början",
|
||||
"fresh_description": "Fortsätt direkt till översikten utan tidigare bokföring.",
|
||||
"migration_selected_title": "Flytta in din bokföring",
|
||||
"migration_selected_description": "Importera historiken först. Översikten är tillgänglig under tiden.",
|
||||
"migration_action": "Öppna importen",
|
||||
"bank_selected_title": "Koppla din bank",
|
||||
"bank_selected_description": "Hämta transaktionerna så att du kan börja stämma av och bokföra.",
|
||||
"bank_action": "Koppla eller importera bank",
|
||||
"fresh_selected_title": "Du är redo",
|
||||
"fresh_selected_description": "Börja arbeta direkt i översikten.",
|
||||
"fresh_action": "Fortsätt",
|
||||
"title": "{count} steg så är bokföringen igång",
|
||||
"step_books_title": "Få in din bokföring",
|
||||
"step_books_description": "Importera från ett annat system eller ladda upp en SIE-fil. Ny verksamhet?",
|
||||
"step_books_fresh_link": "Starta från början",
|
||||
"step_books_fresh_suffix": "i stället.",
|
||||
"step_books_action": "Flytta bokföringen",
|
||||
"step_books_sie": "+ SIE",
|
||||
"step_bank_title": "Koppla banken",
|
||||
"step_bank_description": "Hämta transaktioner automatiskt, eller importera ett kontoutdrag.",
|
||||
"step_bank_action": "Koppla banken",
|
||||
"step_assistant_title": "Bygg din bokföringsassistent",
|
||||
"step_assistant_beta": "Beta",
|
||||
"step_assistant_description": "Några frågor om din verksamhet kalibrerar en assistent som föreslår bokföring åt dig.",
|
||||
"step_assistant_action": "Kom igång",
|
||||
"dismiss": "Dölj",
|
||||
"optional_skatteverket": "Anslut Skatteverket, valfritt",
|
||||
"optional_assistant": "Skapa assistent, valfritt"
|
||||
"step_skv_title": "Anslut Skatteverket",
|
||||
"step_skv_description": "Se skattekontot och lämna moms- och arbetsgivardeklarationer direkt härifrån. Ansluts med BankID på ett par minuter.",
|
||||
"step_skv_action": "Anslut Skatteverket"
|
||||
},
|
||||
"tax_assessment_notices": {
|
||||
"title": "Slutskattebesked och kvarskatt",
|
||||
|
||||
Reference in New Issue
Block a user