Compare commits

...

7 Commits

Author SHA1 Message Date
1ca5545db7 nextjs upgrade
All checks were successful
Lint / Lint and Typecheck (push) Successful in 50s
2025-07-15 14:38:49 +02:00
bd914b05dd chore(deps): pin dependencies (#7)
All checks were successful
Lint / Lint and Typecheck (push) Successful in 46s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [eslint-plugin-react-hooks](https://react.dev/) ([source](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks)) | devDependencies | pin | [`^5.2.0` -> `5.2.0`](https://renovatebot.com/diffs/npm/eslint-plugin-react-hooks/5.2.0/5.2.0) |
| [pnpm/action-setup](https://github.com/pnpm/action-setup) | action | pinDigest |  -> `a7487c7` |

Add the preset `:preserveSemverRanges` to your config if you don't want to pin your dependencies.

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4zMS4xIiwidXBkYXRlZEluVmVyIjoiNDEuMzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #7
Co-authored-by: Renovate Bot <renovatebot@schulze.network>
Co-committed-by: Renovate Bot <renovatebot@schulze.network>
2025-07-13 08:36:45 +02:00
fc452ebd4c pnpm ci
All checks were successful
Lint / Lint and Typecheck (push) Successful in 54s
2025-07-13 01:37:43 +02:00
201c1ee523 pnpm CI
Some checks failed
Lint / Lint and Typecheck (push) Failing after 5s
2025-07-13 01:35:03 +02:00
1c1b842a15 PNPM
Some checks failed
Lint / Lint and Typecheck (push) Failing after 6s
2025-07-13 01:15:15 +02:00
aba4e4a7f6 npm audit fix
All checks were successful
Lint / Lint and Typecheck (push) Successful in 44s
2025-07-13 00:58:58 +02:00
4dcd24f1fd fix(deps): update dependency zod to v4 (#6)
Some checks failed
Lint / Lint and Typecheck (push) Has been cancelled
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [zod](https://zod.dev) ([source](https://github.com/colinhacks/zod)) | [`^3.24.3` -> `^4.0.0`](https://renovatebot.com/diffs/npm/zod/3.25.76/4.0.5) | [![age](https://developer.mend.io/api/mc/badges/age/npm/zod/4.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/zod/3.25.76/4.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>colinhacks/zod (zod)</summary>

### [`v4.0.5`](https://github.com/colinhacks/zod/releases/tag/v4.0.5)

[Compare Source](https://github.com/colinhacks/zod/compare/v4.0.4...v4.0.5)

#### Commits:

- [`f91a73e`](f91a73ec23) Support pipes in discriminated unions. Closes [#&#8203;4856](https://github.com/colinhacks/zod/issues/4856) ([#&#8203;4861](https://github.com/colinhacks/zod/issues/4861))
- [`45afab0`](45afab0f84) 4.0.5

### [`v4.0.4`](https://github.com/colinhacks/zod/releases/tag/v4.0.4)

[Compare Source](https://github.com/colinhacks/zod/compare/v4.0.3...v4.0.4)

#### Commits:

- [`9335f05`](9335f0543d) Adds `ZodFirstPartyTypeKind` stub to fix module resolution failure inside `zod-to-json-schema`

### [`v4.0.3`](https://github.com/colinhacks/zod/releases/tag/v4.0.3)

[Compare Source](44a936cb77...v4.0.3)

#### Commits:

- [`5905a8d`](5905a8d810) Improve check-versions script
- [`f3e749b`](f3e749b1b0) Remove global File interface
- [`44a936c`](44a936cb77) 4.0.2
- [`74006ed`](74006edd49) Fix JSR provenance
- [`ff4af5e`](ff4af5e889) 4.0.3
- [`ce573e8`](ce573e8799) Update test badge
- [`9a7161a`](9a7161a976) Fix versions

### [`v4.0.2`](https://github.com/colinhacks/zod/compare/v4.0.1...44a936cb77961e57a0988d8a3c63d9c71fce69ac)

[Compare Source](https://github.com/colinhacks/zod/compare/v4.0.1...44a936cb77961e57a0988d8a3c63d9c71fce69ac)

### [`v4.0.1`](https://github.com/colinhacks/zod/releases/tag/v4.0.1): v4.0.0

[Compare Source](79d4d80e3b...v4.0.1)

With this release, `zod@4.0.0` has been published to `npm`. There were no code changes between 3.25.76 and 4.0.0!

Zod 4 has been stable for the past 6 weeks, but it was published inside zod@3.25.x on npm. this transitionary window gave the ecosystem time to incrementally support for Zod 4 (without dropping support for Zod 3). As there is now near-universal support for Zod 4 in the ecosystem, ths time feels right to finally put a bow on things 🎀

To upgrade to Zod 4:

```
npm upgrade zod@^4.0.0
```

If you’ve already migrated to Zod 4 using the subpaths, there are no changes required. however you can optionally simplify your imports (recommended)

```ts
// after upgrading to zod@4.0.0:
import * as z from "zod"; // Zod 4 (regular)
import * as z from "zod/mini" // Zod 4 Mini

// these still work, but are no longer needed
import * as z from "zod/v4";
import * as z from "zod/v4-mini":

// if you still need Zod 3
import * as z from "zod/v3"; // Zod 3
```

**Library authors** — if you've already implemented Zod 4 support according to the best practices outlined in the [Library authors](/library-authors) guide, bump your peer dependency to include `zod@^4.0.0`:

```json
// package.json
{
  "peerDependencies": {
    "zod": "^3.25.0 || ^4.0.0"
  }
}
```

*There should be no other code changes necessary.* No code changes were made between the latest `3.25.x` release and `4.0.0`. This does not require a major version bump.

### [`v4.0.0`](https://github.com/colinhacks/zod/compare/v3.25.76...79d4d80e3b47f04752c5c281077b53f889551441)

[Compare Source](https://github.com/colinhacks/zod/compare/v3.25.76...79d4d80e3b47f04752c5c281077b53f889551441)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40Ni4wIiwidXBkYXRlZEluVmVyIjoiNDEuMzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Felix Schulze <admin@schulze.network>
Reviewed-on: #6
Co-authored-by: Renovate Bot <renovatebot@schulze.network>
Co-committed-by: Renovate Bot <renovatebot@schulze.network>
2025-07-13 00:58:35 +02:00
7 changed files with 5157 additions and 7691 deletions

View File

@@ -4,7 +4,7 @@ on:
pull_request: pull_request:
push: push:
branches: branches:
- '**' # matches every branch - "**" # matches every branch
jobs: jobs:
lint_and_typecheck: lint_and_typecheck:
@@ -15,14 +15,17 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Install pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with: with:
node-version: 22 node-version: 22
cache: 'npm' cache: "pnpm"
- name: Install dependencies - name: Install dependencies
run: npm ci run: pnpm install
- name: Run check - name: Run check
run: npm run check run: pnpm run check

View File

@@ -29,7 +29,6 @@ The projects code is structured using React/Next.js with TypeScript, focusing
The calculator models your FIRE journey in two phases: The calculator models your FIRE journey in two phases:
1. **Accumulation:** 1. **Accumulation:**
- Your starting capital is grown by your expected CAGR (~7% by default). - Your starting capital is grown by your expected CAGR (~7% by default).
- Monthly savings are added for each year until retirement. - Monthly savings are added for each year until retirement.
- Every variable can be adjusted live (capital, savings, age, growth, inflation, spending, target retirement). - Every variable can be adjusted live (capital, savings, age, growth, inflation, spending, target retirement).
@@ -58,11 +57,11 @@ To run locally:
``` ```
2. **Install dependencies** 2. **Install dependencies**
```bash ```bash
npm install pnpm install
``` ```
3. **Run the app** 3. **Run the app**
```bash ```bash
npm run dev pnpm run dev
``` ```
4. Visit [http://localhost:3000](http://localhost:3000) and unleash the fire. 4. Visit [http://localhost:3000](http://localhost:3000) and unleash the fire.

7649
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
"scripts": { "scripts": {
"build": "next build", "build": "next build",
"check": "next lint && tsc --noEmit", "check": "next lint && tsc --noEmit",
"dev": "next dev --turbo", "dev": "next dev --turbopack",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", "format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"lint:fix": "next lint --fix", "lint:fix": "next lint --fix",
@@ -24,14 +24,14 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.525.0", "lucide-react": "^0.525.0",
"next": "^15.2.3", "next": "^15.4.1",
"next-plausible": "^3.12.4", "next-plausible": "^3.12.4",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-hook-form": "^7.56.1", "react-hook-form": "^7.56.1",
"recharts": "^2.15.3", "recharts": "^2.15.3",
"tailwind-merge": "^3.2.0", "tailwind-merge": "^3.2.0",
"zod": "^3.24.3" "zod": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "3.3.1", "@eslint/eslintrc": "3.3.1",
@@ -40,7 +40,8 @@
"@types/react": "19.1.8", "@types/react": "19.1.8",
"@types/react-dom": "19.1.6", "@types/react-dom": "19.1.6",
"eslint": "9.31.0", "eslint": "9.31.0",
"eslint-config-next": "15.3.5", "eslint-config-next": "15.4.1",
"eslint-plugin-react-hooks": "5.2.0",
"postcss": "8.5.6", "postcss": "8.5.6",
"prettier": "3.6.2", "prettier": "3.6.2",
"prettier-plugin-tailwindcss": "0.6.14", "prettier-plugin-tailwindcss": "0.6.14",
@@ -52,5 +53,5 @@
"ct3aMetadata": { "ct3aMetadata": {
"initVersion": "7.39.3" "initVersion": "7.39.3"
}, },
"packageManager": "npm@11.4.2" "packageManager": "pnpm@10.13.1"
} }

5055
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

6
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,6 @@
ignoredBuiltDependencies:
- unrs-resolver
onlyBuiltDependencies:
- '@tailwindcss/oxide'
- sharp

View File

@@ -120,7 +120,7 @@ export default function FireCalculatorForm() {
const [showing4percent, setShowing4percent] = useState(false); const [showing4percent, setShowing4percent] = useState(false);
// Initialize form with default values // Initialize form with default values
const form = useForm<FormValues>({ const form = useForm<z.input<typeof formSchema>, undefined, FormValues>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
startingCapital: 50000, startingCapital: 50000,
@@ -258,11 +258,18 @@ export default function FireCalculatorForm() {
<Input <Input
placeholder="e.g., 10000" placeholder="e.g., 10000"
type="number" type="number"
{...field} value={field.value as number | string | undefined}
onChange={(value) => { onChange={(e) => {
field.onChange(value); field.onChange(
e.target.value === ""
? undefined
: Number(e.target.value),
);
void form.handleSubmit(onSubmit)(); void form.handleSubmit(onSubmit)();
}} }}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -279,11 +286,18 @@ export default function FireCalculatorForm() {
<Input <Input
placeholder="e.g., 500" placeholder="e.g., 500"
type="number" type="number"
{...field} value={field.value as number | string | undefined}
onChange={(value) => { onChange={(e) => {
field.onChange(value); field.onChange(
e.target.value === ""
? undefined
: Number(e.target.value),
);
void form.handleSubmit(onSubmit)(); void form.handleSubmit(onSubmit)();
}} }}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -300,11 +314,18 @@ export default function FireCalculatorForm() {
<Input <Input
placeholder="e.g., 30" placeholder="e.g., 30"
type="number" type="number"
{...field} value={field.value as number | string | undefined}
onChange={(value) => { onChange={(e) => {
field.onChange(value); field.onChange(
e.target.value === ""
? undefined
: Number(e.target.value),
);
void form.handleSubmit(onSubmit)(); void form.handleSubmit(onSubmit)();
}} }}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -321,11 +342,18 @@ export default function FireCalculatorForm() {
<Input <Input
placeholder="e.g., 90" placeholder="e.g., 90"
type="number" type="number"
{...field} value={field.value as number | string | undefined}
onChange={(value) => { onChange={(e) => {
field.onChange(value); field.onChange(
e.target.value === ""
? undefined
: Number(e.target.value),
);
void form.handleSubmit(onSubmit)(); void form.handleSubmit(onSubmit)();
}} }}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -343,11 +371,18 @@ export default function FireCalculatorForm() {
placeholder="e.g., 7" placeholder="e.g., 7"
type="number" type="number"
step="0.1" step="0.1"
{...field} value={field.value as number | string | undefined}
onChange={(value) => { onChange={(e) => {
field.onChange(value); field.onChange(
e.target.value === ""
? undefined
: Number(e.target.value),
);
void form.handleSubmit(onSubmit)(); void form.handleSubmit(onSubmit)();
}} }}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -365,11 +400,18 @@ export default function FireCalculatorForm() {
placeholder="e.g., 2" placeholder="e.g., 2"
type="number" type="number"
step="0.1" step="0.1"
{...field} value={field.value as number | string | undefined}
onChange={(value) => { onChange={(e) => {
field.onChange(value); field.onChange(
e.target.value === ""
? undefined
: Number(e.target.value),
);
void form.handleSubmit(onSubmit)(); void form.handleSubmit(onSubmit)();
}} }}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -388,11 +430,18 @@ export default function FireCalculatorForm() {
<Input <Input
placeholder="e.g., 2000" placeholder="e.g., 2000"
type="number" type="number"
{...field} value={field.value as number | string | undefined}
onChange={(value) => { onChange={(e) => {
field.onChange(value); field.onChange(
e.target.value === ""
? undefined
: Number(e.target.value),
);
void form.handleSubmit(onSubmit)(); void form.handleSubmit(onSubmit)();
}} }}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -406,11 +455,13 @@ export default function FireCalculatorForm() {
name="retirementAge" name="retirementAge"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Retirement Age: {field.value}</FormLabel> <FormLabel>
Retirement Age: {field.value as number}
</FormLabel>
<FormControl> <FormControl>
<Slider <Slider
name="retirementAge" name="retirementAge"
value={[field.value]} value={[field.value as number]}
min={25} min={25}
max={75} max={75}
step={1} step={1}
@@ -563,8 +614,8 @@ export default function FireCalculatorForm() {
<ReferenceLine <ReferenceLine
x={ x={
irlYear + irlYear +
(form.getValues("retirementAge") - (Number(form.getValues("retirementAge")) -
form.getValues("currentAge")) Number(form.getValues("currentAge")))
} }
stroke="var(--primary)" stroke="var(--primary)"
strokeWidth={2} strokeWidth={2}
@@ -579,7 +630,7 @@ export default function FireCalculatorForm() {
x={ x={
irlYear + irlYear +
(result.retirementAge4percent - (result.retirementAge4percent -
form.getValues("currentAge")) Number(form.getValues("currentAge")))
} }
stroke="var(--secondary)" stroke="var(--secondary)"
strokeWidth={1} strokeWidth={1}
@@ -642,8 +693,8 @@ export default function FireCalculatorForm() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<p className="text-3xl font-bold"> <p className="text-3xl font-bold">
{form.getValues("lifeExpectancy") - {Number(form.getValues("lifeExpectancy")) -
form.getValues("retirementAge")} Number(form.getValues("retirementAge"))}
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
@@ -674,7 +725,7 @@ export default function FireCalculatorForm() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<p className="text-3xl font-bold"> <p className="text-3xl font-bold">
{form.getValues("lifeExpectancy") - {Number(form.getValues("lifeExpectancy")) -
(result.retirementAge4percent ?? 0)} (result.retirementAge4percent ?? 0)}
</p> </p>
</CardContent> </CardContent>