refactor: clean up codebase, remove dead code and obsolete docs

Remove influencer-era documentation, unused components, boilerplate
assets, and ghost tiktok cron job. Add supplier invoice management,
document API routes, and PWA icons. Replace boilerplate README.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-02-20 12:23:17 +01:00
co-authored by Claude Opus 4.6
parent e8743e6e03
commit 838dc6b8b5
113 changed files with 6289 additions and 19036 deletions
-258
View File
@@ -1,258 +0,0 @@
'use client'
import { useState, useEffect, useCallback } from 'react'
import { Card, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import {
X,
ChevronRight,
ChevronLeft,
Sparkles,
PiggyBank,
AlertTriangle,
ListTodo,
Zap,
} from 'lucide-react'
import { cn } from '@/lib/utils'
interface TourStep {
id: string
title: string
description: string
icon: React.ComponentType<{ className?: string }>
highlight?: string // CSS selector or element ID to highlight
position?: 'top' | 'bottom' | 'center'
}
const TOUR_COMPLETED_KEY = 'erp_dashboard_tour_completed'
const tourSteps: TourStep[] = [
{
id: 'welcome',
title: 'Välkommen till din översikt!',
description:
'Här ser du hela din ekonomiska situation på ett ställe. Låt oss gå igenom de viktigaste delarna.',
icon: Sparkles,
position: 'center',
},
{
id: 'disponibelt',
title: 'Ditt att spendera',
description:
'Detta är vad du faktiskt kan använda efter att vi räknat bort uppskattad skatt och moms. Tänk på det som "säkra pengar".',
icon: PiggyBank,
highlight: 'disponibelt-section',
position: 'bottom',
},
{
id: 'fskatt',
title: 'F-skatt varning',
description:
'Vi jämför din faktiska skatt med vad du betalat in via F-skatt. Om det skiljer sig mycket får du en varning så du kan justera.',
icon: AlertTriangle,
highlight: 'fskatt-section',
position: 'top',
},
{
id: 'att-hantera',
title: 'Att hantera',
description:
'Här samlas saker som behöver din uppmärksamhet: okategoriserade transaktioner, obetalda fakturor, och liknande.',
icon: ListTodo,
highlight: 'alerts-section',
position: 'top',
},
{
id: 'snabbatgarder',
title: 'Snabbåtgärder',
description:
'Skapa fakturor, logga körningar eller skanna kvitton med ett klick. De vanligaste uppgifterna finns alltid nära till hands.',
icon: Zap,
highlight: 'quick-actions',
position: 'top',
},
]
interface DashboardTourProps {
onComplete?: () => void
forceShow?: boolean
}
export default function DashboardTour({ onComplete, forceShow = false }: DashboardTourProps) {
const [isVisible, setIsVisible] = useState(false)
const [currentStep, setCurrentStep] = useState(0)
const [isAnimating, setIsAnimating] = useState(false)
useEffect(() => {
// Check if tour has been completed
const completed = localStorage.getItem(TOUR_COMPLETED_KEY)
if (forceShow || completed !== 'true') {
// Small delay to let the page render first
const timer = setTimeout(() => setIsVisible(true), 500)
return () => clearTimeout(timer)
}
}, [forceShow])
const completeTour = useCallback(() => {
setIsAnimating(true)
setTimeout(() => {
localStorage.setItem(TOUR_COMPLETED_KEY, 'true')
setIsVisible(false)
onComplete?.()
}, 300)
}, [onComplete])
const nextStep = () => {
if (currentStep < tourSteps.length - 1) {
setCurrentStep((prev) => prev + 1)
} else {
completeTour()
}
}
const prevStep = () => {
if (currentStep > 0) {
setCurrentStep((prev) => prev - 1)
}
}
const skipTour = () => {
completeTour()
}
if (!isVisible) {
return null
}
const step = tourSteps[currentStep]
const Icon = step.icon
const isLastStep = currentStep === tourSteps.length - 1
const isFirstStep = currentStep === 0
return (
<>
{/* Backdrop overlay */}
<div
className={cn(
'fixed inset-0 bg-background/80 backdrop-blur-sm z-50 transition-opacity duration-300',
isAnimating ? 'opacity-0' : 'opacity-100'
)}
onClick={skipTour}
/>
{/* Tour card */}
<div
className={cn(
'fixed z-50 transition-all duration-300',
step.position === 'center' && 'inset-0 flex items-center justify-center p-4',
step.position === 'top' && 'top-4 left-1/2 -translate-x-1/2 w-full max-w-md px-4',
step.position === 'bottom' && 'bottom-20 md:bottom-4 left-1/2 -translate-x-1/2 w-full max-w-md px-4',
isAnimating && 'opacity-0 scale-95'
)}
>
<Card className="shadow-2xl border-primary/20">
<CardContent className="pt-6">
<div className="space-y-4">
{/* Header with icon and close */}
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-primary/10">
<Icon className="h-5 w-5 text-primary" />
</div>
<div>
<h3 className="font-medium">{step.title}</h3>
<p className="text-xs text-muted-foreground">
Steg {currentStep + 1} av {tourSteps.length}
</p>
</div>
</div>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 -mr-2 -mt-2"
onClick={skipTour}
>
<X className="h-4 w-4" />
<span className="sr-only">Stäng guide</span>
</Button>
</div>
{/* Description */}
<p className="text-sm text-muted-foreground leading-relaxed">
{step.description}
</p>
{/* Progress dots */}
<div className="flex items-center justify-center gap-1.5 py-2">
{tourSteps.map((_, idx) => (
<button
key={idx}
onClick={() => setCurrentStep(idx)}
className={cn(
'w-2 h-2 rounded-full transition-all',
idx === currentStep
? 'bg-primary w-4'
: idx < currentStep
? 'bg-primary/40'
: 'bg-muted-foreground/20'
)}
/>
))}
</div>
{/* Navigation buttons */}
<div className="flex items-center justify-between pt-2">
<Button
variant="ghost"
size="sm"
onClick={prevStep}
disabled={isFirstStep}
className={cn(isFirstStep && 'invisible')}
>
<ChevronLeft className="h-4 w-4 mr-1" />
Tillbaka
</Button>
<Button
variant="ghost"
size="sm"
onClick={skipTour}
className="text-muted-foreground"
>
Hoppa över
</Button>
<Button size="sm" onClick={nextStep}>
{isLastStep ? (
'Kom igång'
) : (
<>
Nästa
<ChevronRight className="h-4 w-4 ml-1" />
</>
)}
</Button>
</div>
</div>
</CardContent>
</Card>
</div>
</>
)
}
/**
* Hook för att starta om touren
*/
export function useDashboardTour() {
const resetTour = () => {
localStorage.removeItem(TOUR_COMPLETED_KEY)
window.location.reload()
}
const isTourCompleted = () => {
return localStorage.getItem(TOUR_COMPLETED_KEY) === 'true'
}
return { resetTour, isTourCompleted }
}
-198
View File
@@ -1,198 +0,0 @@
'use client'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { InfoTooltip } from '@/components/ui/info-tooltip'
import { Loader2, ArrowRight, ArrowLeft, Landmark } from 'lucide-react'
const schema = z.object({
bank_name: z.string().optional(),
clearing_number: z.string().optional(),
account_number: z.string().optional(),
iban: z.string().optional(),
bic: z.string().optional(),
})
type FormData = z.infer<typeof schema>
interface Step5Props {
initialData: Partial<FormData>
onNext: (data: FormData) => void
onBack: () => void
isSaving: boolean
}
export default function Step5BankDetails({
initialData,
onNext,
onBack,
isSaving,
}: Step5Props) {
const {
register,
handleSubmit,
} = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
bank_name: initialData.bank_name || '',
clearing_number: initialData.clearing_number || '',
account_number: initialData.account_number || '',
iban: initialData.iban || '',
bic: initialData.bic || '',
},
})
return (
<div className="space-y-6">
<div className="text-center">
<h1 className="text-3xl font-bold tracking-tight">Bankuppgifter för fakturor</h1>
<p className="text-muted-foreground mt-2">
Dessa uppgifter visas på dina fakturor så att kunder kan betala dig.
</p>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Landmark className="h-5 w-5" />
Bankkonto
</CardTitle>
<CardDescription>
Ange kontot som kunder ska betala till.
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit(onNext)} className="space-y-6">
<div className="space-y-2">
<Label htmlFor="bank_name">Bank</Label>
<Input
id="bank_name"
placeholder="t.ex. Nordea, SEB, Swedbank"
{...register('bank_name')}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<InfoTooltip
content={
<div className="space-y-2">
<p className="font-medium">Vad är clearingnummer?</p>
<p>De första 4-5 siffrorna i ditt kontonummer som identifierar din bank.</p>
<ul className="text-xs text-muted-foreground space-y-1">
<li>Nordea: 3300</li>
<li>SEB: 5000</li>
<li>Swedbank: 8XXX</li>
<li>Handelsbanken: 6XXX</li>
<li>Avanza: 9550/9551</li>
</ul>
</div>
}
side="top"
>
<Label htmlFor="clearing_number">Clearingnummer</Label>
</InfoTooltip>
<Input
id="clearing_number"
placeholder="XXXX"
{...register('clearing_number')}
/>
</div>
<div className="space-y-2">
<Label htmlFor="account_number">Kontonummer</Label>
<Input
id="account_number"
placeholder="XXX XXX XXX"
{...register('account_number')}
/>
</div>
</div>
<div className="pt-4 border-t">
<h4 className="font-medium mb-4">Internationella betalningar (valfritt)</h4>
<p className="text-sm text-muted-foreground mb-4">
För kunder utanför Sverige som betalar via banköverföring.
</p>
<div className="space-y-4">
<div className="space-y-2">
<InfoTooltip
content={
<div className="space-y-2">
<p className="font-medium">Vad är IBAN?</p>
<p>Internationellt bankkontonummer. Svenska IBAN börjar med SE och har 24 tecken totalt.</p>
<p className="text-xs text-muted-foreground">Din bank kan ge dig ditt IBAN - kolla internetbanken eller kontakta dem.</p>
</div>
}
side="right"
>
<Label htmlFor="iban">IBAN</Label>
</InfoTooltip>
<Input
id="iban"
placeholder="SE00 0000 0000 0000 0000 0000"
{...register('iban')}
/>
</div>
<div className="space-y-2">
<InfoTooltip
content={
<div className="space-y-2">
<p className="font-medium">Vad är BIC/SWIFT?</p>
<p>Bankens internationella id-kod. Används tillsammans med IBAN för utlandsbetalningar.</p>
<ul className="text-xs text-muted-foreground space-y-1">
<li>Nordea: NDEASESS</li>
<li>SEB: ESSESESS</li>
<li>Swedbank: SWEDSESS</li>
<li>Handelsbanken: HANDSESS</li>
</ul>
</div>
}
side="right"
>
<Label htmlFor="bic">BIC/SWIFT</Label>
</InfoTooltip>
<Input
id="bic"
placeholder="XXXXSESS"
{...register('bic')}
/>
</div>
</div>
</div>
<div className="flex justify-between pt-4">
<Button
type="button"
variant="ghost"
onClick={onBack}
disabled={isSaving}
>
<ArrowLeft className="mr-2 h-4 w-4" />
Tillbaka
</Button>
<Button type="submit" disabled={isSaving}>
{isSaving ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Sparar...
</>
) : (
<>
Fortsätt
<ArrowRight className="ml-2 h-4 w-4" />
</>
)}
</Button>
</div>
</form>
</CardContent>
</Card>
</div>
)
}