Compare commits

12 Commits

Author SHA1 Message Date
61fb17f984 fix(deps): update dependency recharts to v3
Some checks failed
renovate/stability-days Updates have met minimum release age requirement
Lint / Lint and Typecheck (push) Failing after 24s
Lint / Lint and Typecheck (pull_request) Failing after 24s
2025-11-15 16:03:07 +00:00
8006a65e88 linter issues
Some checks failed
Lint / Lint and Typecheck (push) Failing after 27s
2025-11-15 16:43:44 +01:00
fe4783ae97 fix eslint 2025-11-15 16:43:16 +01:00
d880b3fc26 tailwind 4 2025-11-15 16:40:44 +01:00
299de302d4 prettier file 2025-11-15 16:40:29 +01:00
96a3c13cc7 next16 2025-11-15 16:12:28 +01:00
782ba7b911 chore(deps): update pnpm to v10.22.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
Lint / Lint and Typecheck (push) Successful in 30s
2025-11-15 14:02:23 +00:00
201f133863 chore(deps): update pnpm to v10.21.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
Lint / Lint and Typecheck (push) Successful in 27s
2025-11-15 07:02:47 +00:00
d46fea9ea3 chore(deps): update dependency @types/react-dom to v19.2.3
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
Lint / Lint and Typecheck (push) Successful in 28s
2025-11-15 06:03:37 +00:00
7811949cc4 chore(deps): update dependency tailwind-merge to v3.4.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
Lint / Lint and Typecheck (push) Successful in 27s
2025-11-15 04:02:50 +00:00
9cdedf2bf9 chore(deps): update dependency typescript-eslint to v8.46.4
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
Lint / Lint and Typecheck (push) Successful in 25s
2025-11-15 03:03:24 +00:00
5720571416 chore(deps): update dependency next-plausible to v3.12.5
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
Lint / Lint and Typecheck (push) Successful in 25s
2025-11-15 02:03:20 +00:00
18 changed files with 1037 additions and 770 deletions

View File

@@ -27,5 +27,5 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Run check
run: pnpm run check
- name: Run lint
run: pnpm run lint

6
.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"tabWidth": 2,
"singleQuote": true,
"printWidth": 105,
"plugins": ["prettier-plugin-tailwindcss"]
}

View File

@@ -1,48 +0,0 @@
import { FlatCompat } from "@eslint/eslintrc";
import tseslint from "typescript-eslint";
const compat = new FlatCompat({
baseDirectory: import.meta.dirname,
});
export default tseslint.config(
{
ignores: [".next"],
},
...compat.extends("next/core-web-vitals"),
{
files: ["**/*.ts", "**/*.tsx"],
extends: [
...tseslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
],
rules: {
"@typescript-eslint/array-type": "off",
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/consistent-type-imports": [
"warn",
{ prefer: "type-imports", fixStyle: "inline-type-imports" },
],
"@typescript-eslint/no-unused-vars": [
"warn",
{ argsIgnorePattern: "^_" },
],
"@typescript-eslint/require-await": "off",
"@typescript-eslint/no-misused-promises": [
"error",
{ checksVoidReturn: { attributes: false } },
],
},
},
{
linterOptions: {
reportUnusedDisableDirectives: true,
},
languageOptions: {
parserOptions: {
projectService: true,
},
},
},
);

37
eslint.config.mjs Normal file
View File

@@ -0,0 +1,37 @@
// @ts-check
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
import tseslint from "typescript-eslint";
const eslintConfig = defineConfig([
// Next.js core-web-vitals and TypeScript configs
...nextVitals,
...nextTs,
// Add strict TypeScript rules on top
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
// Configure TypeScript parser options
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
// Override default ignores of eslint-config-next
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
// Additional ignores:
"*.mjs",
"tailwind.config.ts",
]),
]);
export default eslintConfig;

View File

@@ -2,7 +2,7 @@
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
* for Docker builds.
*/
import "./src/env.js";
import './src/env.ts';
/** @type {import("next").NextConfig} */
const config = {};

View File

@@ -9,7 +9,7 @@
"dev": "next dev --turbopack",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"lint:fix": "next lint --fix",
"lint": "next typegen && eslint . && npx tsc --noEmit",
"preview": "next build && next start",
"start": "next start"
},
@@ -24,23 +24,23 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.553.0",
"next": "^15.4.1",
"next": "16.0.3",
"next-plausible": "^3.12.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-hook-form": "^7.56.1",
"recharts": "^2.15.3",
"recharts": "^3.0.0",
"tailwind-merge": "^3.2.0",
"zod": "^4.0.0"
},
"devDependencies": {
"@eslint/eslintrc": "3.3.1",
"@tailwindcss/postcss": "4.1.17",
"@types/node": "24.10.1",
"@types/react": "19.2.3",
"@types/react-dom": "19.2.2",
"@types/react": "19.2.5",
"@types/react-dom": "19.2.3",
"eslint": "9.39.1",
"eslint-config-next": "15.5.6",
"eslint-config-next": "16.0.3",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "5.2.0",
"postcss": "8.5.6",
"prettier": "3.6.2",
@@ -48,10 +48,16 @@
"tailwindcss": "4.1.17",
"tw-animate-css": "1.4.0",
"typescript": "5.9.3",
"typescript-eslint": "8.46.3"
"typescript-eslint": "8.46.4"
},
"ct3aMetadata": {
"initVersion": "7.39.3"
},
"packageManager": "pnpm@10.20.0"
"packageManager": "pnpm@10.22.0",
"pnpm": {
"overrides": {
"@types/react": "19.2.5",
"@types/react-dom": "19.2.3"
}
}
}

1597
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +0,0 @@
/** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */
export default {
plugins: ["prettier-plugin-tailwindcss"],
};

View File

@@ -124,7 +124,7 @@ export default function MultiIconPattern({ opacity = 0.2, spacing = 160 }) {
for (let x = 0; x < columns; x++) {
// Pick a random icon component from the array
const randomIndex = Math.floor(Math.random() * iconComponents.length);
const IconComponent = iconComponents[randomIndex]!;
const IconComponent = iconComponents[randomIndex];
// Slightly randomize size and position for more organic feel
const size = 28 + Math.floor(Math.random() * 8);

View File

@@ -648,7 +648,7 @@ export default function FireCalculatorForm() {
)}
{result && (
<Button
onClick={() => setShowing4percent(!showing4percent)}
onClick={() => { setShowing4percent(!showing4percent); }}
variant={showing4percent ? "secondary" : "default"}
size={"sm"}
>

View File

@@ -66,7 +66,7 @@ export default function HomePage() {
};
return (
<main className="text-primary-foreground to-destructive from-secondary flex min-h-screen flex-col items-center bg-gradient-to-b p-2">
<main className="text-primary-foreground to-destructive from-secondary flex min-h-screen flex-col items-center bg-linear-to-b p-2">
<BackgroundPattern />
<div className="z-10 mx-auto flex flex-col items-center justify-center gap-4 text-center">
<div className="mt-8 flex flex-row flex-wrap items-center justify-center gap-4 align-middle">
@@ -78,7 +78,7 @@ export default function HomePage() {
width={100}
height={100}
/>
<h1 className="from-primary via-primary-foreground to-primary bg-gradient-to-r bg-clip-text text-5xl font-extrabold tracking-tight text-transparent drop-shadow-md sm:text-[5rem]">
<h1 className="from-primary via-primary-foreground to-primary bg-linear-to-r bg-clip-text text-5xl font-extrabold tracking-tight text-transparent drop-shadow-md sm:text-[5rem]">
InvestingFIRE
</h1>
</div>

View File

@@ -20,9 +20,9 @@ export type ChartConfig = Record<
)
>;
type ChartContextProps = {
interface ChartContextProps {
config: ChartConfig;
};
}
const ChartContext = React.createContext<ChartContextProps | null>(null);
@@ -175,7 +175,7 @@ function ChartTooltipContent({
return (
<div
className={cn(
"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
"border-border/50 bg-background grid min-w-32 items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
className,
)}
>
@@ -207,7 +207,7 @@ function ChartTooltipContent({
!hideIndicator && (
<div
className={cn(
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
"shrink-0 rounded-[2px] border-border bg-(--color-bg)",
{
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",

View File

@@ -18,12 +18,12 @@ import { Label } from "@/components/ui/label";
const Form = FormProvider;
type FormFieldContextValue<
interface FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
> {
name: TName;
};
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
@@ -61,9 +61,9 @@ const useFormField = () => {
};
};
type FormItemContextValue = {
interface FormItemContextValue {
id: string;
};
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue,
@@ -110,7 +110,7 @@ function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
? formDescriptionId
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}

View File

@@ -37,7 +37,7 @@ function SelectTrigger({
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"border-input data-placeholder:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
@@ -61,7 +61,7 @@ function SelectContent({
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-32 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
@@ -74,7 +74,7 @@ function SelectContent({
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
"h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1",
)}
>
{children}
@@ -107,7 +107,7 @@ function SelectItem({
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}

View File

@@ -31,7 +31,7 @@ function Slider({
min={min}
max={max}
className={cn(
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
"relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
className,
)}
{...props}

View File

@@ -1,42 +1,35 @@
{
"compilerOptions": {
/* Base Options: */
"esModuleInterop": true,
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": false,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
"verbatimModuleSyntax": true,
/* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true,
"checkJs": true,
/* Bundled projects */
"lib": ["dom", "dom.iterable", "ES2022"],
"noEmit": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"plugins": [{ "name": "next" }],
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
/* Path Aliases */
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"target": "ES2022",
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true,
"noUncheckedIndexedAccess": false,
"exactOptionalPropertyTypes": false,
"noImplicitReturns": false,
"plugins": [{ "name": "next" }]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.cjs",
"**/*.js",
".next/types/**/*.ts"
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": ["node_modules"]
}