diff --git a/web/eslint.config.js b/web/eslint.config.js
index 84a81af..c95aa9b 100644
--- a/web/eslint.config.js
+++ b/web/eslint.config.js
@@ -24,12 +24,4 @@ export default defineConfig([
files: ['src/routes/**/*.{ts,tsx}'],
rules: { 'react-refresh/only-export-components': 'off' },
},
- {
- // Copied from beui.dev; kept as published.
- files: ['src/components/motion/**/*.{ts,tsx}'],
- rules: {
- 'react-refresh/only-export-components': 'off',
- 'react-hooks/set-state-in-effect': 'off',
- },
- },
])
diff --git a/web/package-lock.json b/web/package-lock.json
index 8e9fd73..9d949c6 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -8,13 +8,11 @@
"name": "web",
"version": "0.0.0",
"dependencies": {
- "@fontsource-variable/geist": "^5.3.0",
- "@fontsource-variable/geist-mono": "^5.3.0",
+ "@fontsource-variable/inter": "^5.3.0",
"@tanstack/react-query": "^5.104.0",
"@tanstack/react-router": "^1.170.40",
"clsx": "^2.1.1",
"lucide-react": "^1.49.0",
- "motion": "^13.4.6",
"openapi-fetch": "^0.17.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
@@ -431,19 +429,10 @@
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
- "node_modules/@fontsource-variable/geist": {
+ "node_modules/@fontsource-variable/inter": {
"version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@fontsource-variable/geist/-/geist-5.3.0.tgz",
- "integrity": "sha512-j0m+vLQuG5XAYoHtGCVu0spvlGreR3EzpECUVzkFmI1mTVnAO38l/NEPDCFgZ177JxzYJCLSmTQibIiYPilGrA==",
- "license": "OFL-1.1",
- "funding": {
- "url": "https://github.com/sponsors/ayuhito"
- }
- },
- "node_modules/@fontsource-variable/geist-mono": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@fontsource-variable/geist-mono/-/geist-mono-5.3.0.tgz",
- "integrity": "sha512-vBbuwDEo9AkrqADMXOrlAR3DFcJi4/JxeuU43FoiQERnNwsfXNnvxvReZG02cQKmyk4DZkZdBZX3oTDvy2zBAw==",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz",
+ "integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
@@ -2567,29 +2556,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/framer-motion": {
- "version": "13.4.6",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-13.4.6.tgz",
- "integrity": "sha512-TwKWt2CC8rjhARzwV00TZIh04ipumSAqyKM/JZmS+urdncSHg6wTF/mfj/Fd6Oi2W01TZSWsiA3uxXDrXZtKyw==",
- "license": "MIT",
- "dependencies": {
- "motion-dom": "^13.4.5",
- "motion-utils": "^13.3.0",
- "tslib": "^2.4.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "react": {
- "optional": true
- },
- "react-dom": {
- "optional": true
- }
- }
- },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3165,43 +3131,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/motion": {
- "version": "13.4.6",
- "resolved": "https://registry.npmjs.org/motion/-/motion-13.4.6.tgz",
- "integrity": "sha512-zDXi6lIdyhda6wZlTsv9QgT2ed6UExAfRegtl/akQmZb5iaeLlArXr9Gmo/hx3iTx2y4R7tmdb9k9koFQmV+NQ==",
- "license": "MIT",
- "dependencies": {
- "framer-motion": "^13.4.6",
- "tslib": "^2.4.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "react": {
- "optional": true
- },
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/motion-dom": {
- "version": "13.4.5",
- "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-13.4.5.tgz",
- "integrity": "sha512-p63p2Doq+US/O5wADauIPUVx0/eONHMLPzsudBM9XMWRuBQLXogdxH0vwIA8WYQZw864pyzoV723xoDqakwWTg==",
- "license": "MIT",
- "dependencies": {
- "motion-utils": "^13.3.0"
- }
- },
- "node_modules/motion-utils": {
- "version": "13.3.0",
- "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-13.3.0.tgz",
- "integrity": "sha512-sgSschQp7EseHInIlR7hBbMuvet3RA0bs28KPZAXJcGKGdxHGvh1ogpYDilY3bOMtl73EqPNmp75sAKHYPU5sg==",
- "license": "MIT"
- },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -3646,7 +3575,9 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
},
"node_modules/type-check": {
"version": "0.4.0",
diff --git a/web/package.json b/web/package.json
index daa2634..201ae0c 100644
--- a/web/package.json
+++ b/web/package.json
@@ -12,13 +12,11 @@
"generate:api": "openapi-typescript ../api/openapi.yaml -o src/api/schema.d.ts"
},
"dependencies": {
- "@fontsource-variable/geist": "^5.3.0",
- "@fontsource-variable/geist-mono": "^5.3.0",
+ "@fontsource-variable/inter": "^5.3.0",
"@tanstack/react-query": "^5.104.0",
"@tanstack/react-router": "^1.170.40",
"clsx": "^2.1.1",
"lucide-react": "^1.49.0",
- "motion": "^13.4.6",
"openapi-fetch": "^0.17.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
diff --git a/web/src/components/auth/auth-card.tsx b/web/src/components/auth/auth-card.tsx
deleted file mode 100644
index 6580635..0000000
--- a/web/src/components/auth/auth-card.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { ReactNode } from "react";
-
-type AuthCardProps = {
- title: string;
- description: string;
- children: ReactNode;
-};
-
-export function AuthCard({ title, description, children }: AuthCardProps) {
- return (
-
-
-
{title}
-
{description}
-
-
- {children}
-
- );
-}
diff --git a/web/src/components/auth/auth-layout.tsx b/web/src/components/auth/auth-layout.tsx
index 0422f85..3350d94 100644
--- a/web/src/components/auth/auth-layout.tsx
+++ b/web/src/components/auth/auth-layout.tsx
@@ -1,23 +1,22 @@
import type { ReactNode } from "react";
type AuthLayoutProps = {
- prompt: ReactNode;
+ title: string;
+ footer: ReactNode;
children: ReactNode;
};
-export function AuthLayout({ prompt, children }: AuthLayoutProps) {
+export function AuthLayout({ title, footer, children }: AuthLayoutProps) {
return (
-
-
-
Naplata
-
- Invoicing and payments, without the busywork.
-
-
+
+
+
Naplata
-
-
{prompt}
- {children}
+
+
{title}
+ {children}
+
{footer}
+
);
diff --git a/web/src/components/motion/animated-toast-stack.tsx b/web/src/components/motion/animated-toast-stack.tsx
deleted file mode 100644
index cf4979d..0000000
--- a/web/src/components/motion/animated-toast-stack.tsx
+++ /dev/null
@@ -1,505 +0,0 @@
-"use client";
-
-import {
- AlertCircle,
- Bell,
- Check,
- Info,
- LoaderCircle,
- X,
- type LucideIcon,
-} from "lucide-react";
-import {
- AnimatePresence,
- motion,
- useReducedMotion,
- type Transition,
-} from "motion/react";
-import {
- memo,
- useCallback,
- useEffect,
- useMemo,
- useRef,
- useState,
- type ReactNode,
-} from "react";
-import { createPortal } from "react-dom";
-import { EASE_OUT } from "@/lib/ease";
-import { cn } from "@/lib/utils";
-
-export type ToastStatus = "neutral" | "info" | "loading" | "success" | "error";
-export type ToastPosition =
- | "top-left"
- | "top-center"
- | "top-right"
- | "bottom-left"
- | "bottom-center"
- | "bottom-right";
-
-export type AnimatedToastAction = {
- label: ReactNode;
- onClick: (toast: AnimatedToast) => void;
-};
-
-export type AnimatedToast = {
- id: string;
- title: ReactNode;
- description?: ReactNode;
- status?: ToastStatus;
- icon?: ReactNode;
- action?: AnimatedToastAction;
- duration?: number;
- dismissible?: boolean;
- createdAt?: number;
-};
-
-export type ToastInput = Omit
& {
- id?: string;
-};
-
-export type ToastClassNames = {
- root?: string;
- item?: string;
- surface?: string;
- iconWrap?: string;
- content?: string;
- title?: string;
- description?: string;
- action?: string;
- close?: string;
- progress?: string;
-};
-
-export interface AnimatedToastStackProps {
- toasts: AnimatedToast[];
- onDismiss?: (id: string) => void;
- position?: ToastPosition;
- placement?: "static" | "fixed" | "absolute";
- fixed?: boolean;
- portal?: boolean;
- portalRoot?: Element | null;
- maxVisible?: number;
- className?: string;
- classNames?: ToastClassNames;
- icons?: Partial>;
- renderToast?: (toast: AnimatedToast) => ReactNode;
-}
-
-export interface UseAnimatedToastStackOptions {
- initialToasts?: ToastInput[];
- defaultDuration?: number;
- limit?: number;
-}
-
-const STACK_SPRING: Transition = {
- type: "spring",
- stiffness: 420,
- damping: 34,
- mass: 0.75,
-};
-
-const CONTENT_TRANSITION = {
- duration: 0.28,
- ease: EASE_OUT,
-} as const;
-
-const STATUS_ICON: Record = {
- neutral: Bell,
- info: Info,
- loading: LoaderCircle,
- success: Check,
- error: AlertCircle,
-};
-
-const STATUS_CLASS: Record = {
- neutral: "text-muted-foreground bg-muted/60",
- info: "text-primary bg-muted",
- loading: "text-primary bg-muted",
- success: "text-emerald-600 bg-emerald-500/10 dark:text-emerald-400",
- error: "text-destructive bg-destructive/10",
-};
-
-const POSITION_CLASS: Record = {
- "top-left": "left-4 top-4",
- "top-center": "left-1/2 top-4 -translate-x-1/2",
- "top-right": "right-4 top-4",
- "bottom-left": "bottom-6 left-4",
- "bottom-center": "bottom-6 left-1/2 -translate-x-1/2",
- "bottom-right": "bottom-6 right-4",
-};
-
-let idSeed = 0;
-
-function createToast(input: ToastInput, defaultDuration: number): AnimatedToast {
- return {
- duration: defaultDuration,
- dismissible: true,
- ...input,
- id: input.id ?? `toast-${Date.now()}-${idSeed++}`,
- createdAt: Date.now(),
- };
-}
-
-export function useAnimatedToastStack({
- initialToasts = [],
- defaultDuration = 4200,
- limit,
-}: UseAnimatedToastStackOptions = {}) {
- const toastTimers = useRef