diff --git a/apps/web/package.json b/apps/web/package.json
index 284c14d6..4477ccd5 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -120,7 +120,8 @@
"use-stick-to-bottom": "^1.1.1",
"usehooks-ts": "^3.1.1",
"xstate": "^5.24.0",
- "zod": "^4.1.5"
+ "zod": "^4.1.5",
+ "zustand": "^5.0.8"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
diff --git a/apps/web/src/app/providers.tsx b/apps/web/src/app/providers.tsx
index 6fd3d0e8..61a094f6 100644
--- a/apps/web/src/app/providers.tsx
+++ b/apps/web/src/app/providers.tsx
@@ -7,6 +7,7 @@ import { NuqsAdapter } from "nuqs/adapters/next/app";
import { jotaiStore } from "@/atoms/store";
import { ThemeProvider } from "@/components/ui/theme-provider";
import { TRPCReactProvider } from "@/lib/trpc/client";
+import { CounterStoreProvider } from "@/stores/counter-store-provider";
export function Providers(props: Readonly<{ children: ReactNode }>) {
return (
@@ -18,7 +19,9 @@ export function Providers(props: Readonly<{ children: ReactNode }>) {
disableTransitionOnChange
>
- {props.children}
+
+ {props.children}
+
diff --git a/apps/web/src/stores/counter-store-provider.tsx b/apps/web/src/stores/counter-store-provider.tsx
new file mode 100644
index 00000000..ab9dc43e
--- /dev/null
+++ b/apps/web/src/stores/counter-store-provider.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+import { type ReactNode, createContext, useRef, useContext } from "react";
+import { useStore } from "zustand";
+
+import {
+ type CounterStore,
+ createCounterStore,
+ defaultInitState,
+} from "@/stores/counter-store";
+
+export type CounterStoreApi = ReturnType;
+
+export const CounterStoreContext = createContext(
+ undefined
+);
+
+export interface CounterStoreProviderProps {
+ children: ReactNode;
+}
+
+export const CounterStoreProvider = ({ children }: CounterStoreProviderProps) => {
+ const storeRef = useRef(null);
+ if (storeRef.current === null) {
+ storeRef.current = createCounterStore(defaultInitState);
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useCounterStore = (selector: (store: CounterStore) => T): T => {
+ const counterStoreContext = useContext(CounterStoreContext);
+
+ if (!counterStoreContext) {
+ throw new Error("useCounterStore must be used within CounterStoreProvider");
+ }
+
+ return useStore(counterStoreContext, selector);
+};
diff --git a/apps/web/src/stores/counter-store.ts b/apps/web/src/stores/counter-store.ts
new file mode 100644
index 00000000..af52e471
--- /dev/null
+++ b/apps/web/src/stores/counter-store.ts
@@ -0,0 +1,24 @@
+import { createStore } from "zustand/vanilla";
+
+export type CounterState = {
+ count: number;
+};
+
+export type CounterActions = {
+ decrementCount: () => void;
+ incrementCount: () => void;
+};
+
+export type CounterStore = CounterState & CounterActions;
+
+export const defaultInitState: CounterState = {
+ count: 0,
+};
+
+export const createCounterStore = (initState: CounterState = defaultInitState) => {
+ return createStore()((set) => ({
+ ...initState,
+ decrementCount: () => set((state) => ({ count: state.count - 1 })),
+ incrementCount: () => set((state) => ({ count: state.count + 1 })),
+ }));
+};
diff --git a/bun.lock b/bun.lock
index b43c8210..3498607f 100644
--- a/bun.lock
+++ b/bun.lock
@@ -126,6 +126,7 @@
"usehooks-ts": "^3.1.1",
"xstate": "^5.24.0",
"zod": "^4.1.5",
+ "zustand": "^5.0.8",
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
@@ -241,6 +242,9 @@
"@repo/env": "workspace:*",
"postgres": "^3.4.7",
"server-only": "^0.0.1",
+ "superjson": "^2.2.2",
+ "superjson-temporal": "^0.4.0",
+ "temporal-polyfill": "^0.3.0",
},
"devDependencies": {
"@repo/eslint-config": "workspace:*",
@@ -3216,6 +3220,8 @@
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
+ "zustand": ["zustand@5.0.8", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw=="],
+
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],