create-react-forge 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +88 -61
  2. package/dist/cli/index.js +1 -1
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/cli/parser.js +1 -1
  5. package/dist/cli/parser.js.map +1 -1
  6. package/dist/docs/architecture-generator.js +1 -1
  7. package/dist/generator/index.js +1 -1
  8. package/dist/generator/index.js.map +1 -1
  9. package/package.json +2 -1
  10. package/src/templates/overlays/base/manifest.json +16 -0
  11. package/src/templates/overlays/base/src/components/ui/Button.tsx +66 -0
  12. package/src/templates/overlays/base/src/components/ui/Input.tsx +51 -0
  13. package/src/templates/overlays/base/src/components/ui/index.ts +3 -0
  14. package/src/templates/overlays/base/src/hooks/use-disclosure.ts +21 -0
  15. package/src/templates/overlays/base/src/hooks/use-local-storage.ts +80 -0
  16. package/src/templates/overlays/base/src/lib/api-client.ts +101 -0
  17. package/src/templates/overlays/base/src/lib/utils.ts +34 -0
  18. package/src/templates/overlays/base/src/types/api.ts +47 -0
  19. package/src/templates/overlays/features/tanstack-query/manifest.json +17 -0
  20. package/src/templates/overlays/features/tanstack-query/src/features/users/api/create-user.ts +41 -0
  21. package/src/templates/overlays/features/tanstack-query/src/features/users/api/get-user.ts +27 -0
  22. package/src/templates/overlays/features/tanstack-query/src/features/users/api/get-users.ts +39 -0
  23. package/src/templates/overlays/features/tanstack-query/src/hooks/use-query-config.ts +42 -0
  24. package/src/templates/overlays/features/tanstack-query/src/lib/QueryProvider.tsx +28 -0
  25. package/src/templates/overlays/features/tanstack-query/src/lib/react-query.ts +46 -0
  26. package/src/templates/overlays/runtime/nextjs/manifest.json +27 -0
  27. package/src/templates/overlays/runtime/nextjs/next-env.d.ts +6 -0
  28. package/src/templates/overlays/runtime/nextjs/next.config.js +12 -0
  29. package/src/templates/overlays/runtime/nextjs/src/app/error.tsx +34 -0
  30. package/src/templates/overlays/runtime/nextjs/src/app/layout.tsx +23 -0
  31. package/src/templates/overlays/runtime/nextjs/src/app/loading.tsx +12 -0
  32. package/src/templates/overlays/runtime/nextjs/src/app/not-found.tsx +26 -0
  33. package/src/templates/overlays/runtime/nextjs/src/app/page.tsx +33 -0
  34. package/src/templates/overlays/runtime/nextjs/src/app/providers.tsx +14 -0
  35. package/src/templates/overlays/runtime/nextjs/src/styles/globals.css +50 -0
  36. package/src/templates/overlays/runtime/nextjs/tsconfig.json +29 -0
  37. package/src/templates/overlays/runtime/vite/index.html +14 -0
  38. package/src/templates/overlays/runtime/vite/manifest.json +28 -0
  39. package/src/templates/overlays/runtime/vite/public/vite.svg +2 -0
  40. package/src/templates/overlays/runtime/vite/src/app/App.tsx +11 -0
  41. package/src/templates/overlays/runtime/vite/src/app/provider.tsx +20 -0
  42. package/src/templates/overlays/runtime/vite/src/app/router.tsx +19 -0
  43. package/src/templates/overlays/runtime/vite/src/components/errors/ErrorFallback.tsx +21 -0
  44. package/src/templates/overlays/runtime/vite/src/components/ui/LoadingSpinner.tsx +23 -0
  45. package/src/templates/overlays/runtime/vite/src/features/misc/routes/Landing.tsx +33 -0
  46. package/src/templates/overlays/runtime/vite/src/features/misc/routes/NotFound.tsx +26 -0
  47. package/src/templates/overlays/runtime/vite/src/main.tsx +11 -0
  48. package/src/templates/overlays/runtime/vite/src/styles/globals.css +55 -0
  49. package/src/templates/overlays/runtime/vite/tsconfig.json +32 -0
  50. package/src/templates/overlays/runtime/vite/tsconfig.node.json +23 -0
  51. package/src/templates/overlays/runtime/vite/vite.config.ts +22 -0
  52. package/src/templates/overlays/state/redux/manifest.json +17 -0
  53. package/src/templates/overlays/state/redux/src/stores/Provider.tsx +17 -0
  54. package/src/templates/overlays/state/redux/src/stores/hooks.ts +11 -0
  55. package/src/templates/overlays/state/redux/src/stores/index.ts +17 -0
  56. package/src/templates/overlays/state/redux/src/stores/slices/auth.ts +54 -0
  57. package/src/templates/overlays/state/redux/src/stores/slices/notifications.ts +58 -0
  58. package/src/templates/overlays/state/redux/src/stores/store.ts +27 -0
  59. package/src/templates/overlays/state/zustand/manifest.json +16 -0
  60. package/src/templates/overlays/state/zustand/src/stores/auth.ts +48 -0
  61. package/src/templates/overlays/state/zustand/src/stores/index.ts +3 -0
  62. package/src/templates/overlays/state/zustand/src/stores/notifications.ts +54 -0
  63. package/src/templates/overlays/styling/css-modules/manifest.json +14 -0
  64. package/src/templates/overlays/styling/css-modules/src/components/ui/Button.module.css +87 -0
  65. package/src/templates/overlays/styling/css-modules/src/styles/globals.css +91 -0
  66. package/src/templates/overlays/styling/tailwind/manifest.json +18 -0
  67. package/src/templates/overlays/styling/tailwind/postcss.config.js +7 -0
  68. package/src/templates/overlays/styling/tailwind/src/styles/globals.css +54 -0
  69. package/src/templates/overlays/styling/tailwind/tailwind.config.js +62 -0
  70. package/src/templates/overlays/testing/jest/jest.config.js +24 -0
  71. package/src/templates/overlays/testing/jest/manifest.json +27 -0
  72. package/src/templates/overlays/testing/jest/src/components/ui/__tests__/Button.test.tsx +33 -0
  73. package/src/templates/overlays/testing/jest/src/testing/mocks/browser.ts +17 -0
  74. package/src/templates/overlays/testing/jest/src/testing/mocks/handlers.ts +42 -0
  75. package/src/templates/overlays/testing/jest/src/testing/mocks/server.ts +8 -0
  76. package/src/templates/overlays/testing/jest/src/testing/setup.ts +20 -0
  77. package/src/templates/overlays/testing/jest/src/testing/test-utils.tsx +39 -0
  78. package/src/templates/overlays/testing/playwright/manifest.json +21 -0
  79. package/src/templates/overlays/testing/playwright/playwright.config.ts +53 -0
  80. package/src/templates/overlays/testing/playwright/tests/e2e/accessibility.spec.ts +41 -0
  81. package/src/templates/overlays/testing/playwright/tests/e2e/home.spec.ts +41 -0
  82. package/src/templates/overlays/testing/vitest/manifest.json +28 -0
  83. package/src/templates/overlays/testing/vitest/src/components/ui/__tests__/Button.test.tsx +34 -0
  84. package/src/templates/overlays/testing/vitest/src/testing/mocks/browser.ts +17 -0
  85. package/src/templates/overlays/testing/vitest/src/testing/mocks/handlers.ts +42 -0
  86. package/src/templates/overlays/testing/vitest/src/testing/mocks/server.ts +8 -0
  87. package/src/templates/overlays/testing/vitest/src/testing/setup.ts +21 -0
  88. package/src/templates/overlays/testing/vitest/src/testing/test-utils.tsx +39 -0
  89. package/src/templates/overlays/testing/vitest/vitest.config.ts +31 -0
@@ -0,0 +1,20 @@
1
+ import { ErrorFallback } from '@/components/errors/ErrorFallback';
2
+ import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
3
+ import { Suspense } from 'react';
4
+ import { ErrorBoundary } from 'react-error-boundary';
5
+ import { BrowserRouter } from 'react-router-dom';
6
+
7
+ type AppProviderProps = {
8
+ children: React.ReactNode;
9
+ };
10
+
11
+ export function AppProvider({ children }: AppProviderProps) {
12
+ return (
13
+ <Suspense fallback={<LoadingSpinner />}>
14
+ <ErrorBoundary FallbackComponent={ErrorFallback}>
15
+ <BrowserRouter>{children}</BrowserRouter>
16
+ </ErrorBoundary>
17
+ </Suspense>
18
+ );
19
+ }
20
+
@@ -0,0 +1,19 @@
1
+ import { Landing } from '@/features/misc/routes/Landing';
2
+ import { NotFound } from '@/features/misc/routes/NotFound';
3
+ import { useRoutes } from 'react-router-dom';
4
+
5
+ export function AppRouter() {
6
+ const routes = useRoutes([
7
+ {
8
+ path: '/',
9
+ element: <Landing />,
10
+ },
11
+ {
12
+ path: '*',
13
+ element: <NotFound />,
14
+ },
15
+ ]);
16
+
17
+ return routes;
18
+ }
19
+
@@ -0,0 +1,21 @@
1
+ import { FallbackProps } from 'react-error-boundary';
2
+
3
+ export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
4
+ return (
5
+ <div className="flex min-h-screen flex-col items-center justify-center" role="alert">
6
+ <div className="text-center">
7
+ <h1 className="text-2xl font-bold text-red-600">Something went wrong</h1>
8
+ <p className="mt-4 text-gray-600">
9
+ {error.message || 'An unexpected error occurred'}
10
+ </p>
11
+ <button
12
+ onClick={resetErrorBoundary}
13
+ className="mt-6 rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500"
14
+ >
15
+ Try again
16
+ </button>
17
+ </div>
18
+ </div>
19
+ );
20
+ }
21
+
@@ -0,0 +1,23 @@
1
+ type LoadingSpinnerProps = {
2
+ size?: 'sm' | 'md' | 'lg';
3
+ className?: string;
4
+ };
5
+
6
+ const sizes = {
7
+ sm: 'h-4 w-4',
8
+ md: 'h-8 w-8',
9
+ lg: 'h-16 w-16',
10
+ };
11
+
12
+ export function LoadingSpinner({ size = 'md', className = '' }: LoadingSpinnerProps) {
13
+ return (
14
+ <div className="flex items-center justify-center">
15
+ <div
16
+ className={`animate-spin rounded-full border-2 border-gray-300 border-t-indigo-600 ${sizes[size]} ${className}`}
17
+ role="status"
18
+ aria-label="Loading"
19
+ />
20
+ </div>
21
+ );
22
+ }
23
+
@@ -0,0 +1,33 @@
1
+ import { Link } from 'react-router-dom';
2
+
3
+ export function Landing() {
4
+ return (
5
+ <div className="flex min-h-screen flex-col items-center justify-center">
6
+ <div className="text-center">
7
+ <h1 className="text-4xl font-bold tracking-tight sm:text-6xl">
8
+ Welcome to Your App
9
+ </h1>
10
+ <p className="mt-6 text-lg leading-8 text-gray-600">
11
+ A production-ready React application scaffolded with create-react-forge.
12
+ </p>
13
+ <div className="mt-10 flex items-center justify-center gap-x-6">
14
+ <Link
15
+ to="/dashboard"
16
+ className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
17
+ >
18
+ Get started
19
+ </Link>
20
+ <a
21
+ href="https://github.com/alan2207/bulletproof-react"
22
+ className="text-sm font-semibold leading-6"
23
+ target="_blank"
24
+ rel="noopener noreferrer"
25
+ >
26
+ Learn more <span aria-hidden="true">→</span>
27
+ </a>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ );
32
+ }
33
+
@@ -0,0 +1,26 @@
1
+ import { Link } from 'react-router-dom';
2
+
3
+ export function NotFound() {
4
+ return (
5
+ <div className="flex min-h-screen flex-col items-center justify-center">
6
+ <div className="text-center">
7
+ <p className="text-base font-semibold text-indigo-600">404</p>
8
+ <h1 className="mt-4 text-3xl font-bold tracking-tight sm:text-5xl">
9
+ Page not found
10
+ </h1>
11
+ <p className="mt-6 text-base leading-7 text-gray-600">
12
+ Sorry, we couldn't find the page you're looking for.
13
+ </p>
14
+ <div className="mt-10 flex items-center justify-center gap-x-6">
15
+ <Link
16
+ to="/"
17
+ className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
18
+ >
19
+ Go back home
20
+ </Link>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ );
25
+ }
26
+
@@ -0,0 +1,11 @@
1
+ import { App } from '@/app/App';
2
+ import '@/styles/globals.css';
3
+ import React from 'react';
4
+ import ReactDOM from 'react-dom/client';
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );
11
+
@@ -0,0 +1,55 @@
1
+ /* Base styles - will be enhanced by styling overlay (tailwind/css-modules) */
2
+
3
+ *,
4
+ *::before,
5
+ *::after {
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ * {
10
+ margin: 0;
11
+ }
12
+
13
+ html,
14
+ body {
15
+ height: 100%;
16
+ }
17
+
18
+ body {
19
+ line-height: 1.5;
20
+ -webkit-font-smoothing: antialiased;
21
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
22
+ 'Helvetica Neue', Arial, sans-serif;
23
+ }
24
+
25
+ img,
26
+ picture,
27
+ video,
28
+ canvas,
29
+ svg {
30
+ display: block;
31
+ max-width: 100%;
32
+ }
33
+
34
+ input,
35
+ button,
36
+ textarea,
37
+ select {
38
+ font: inherit;
39
+ }
40
+
41
+ p,
42
+ h1,
43
+ h2,
44
+ h3,
45
+ h4,
46
+ h5,
47
+ h6 {
48
+ overflow-wrap: break-word;
49
+ }
50
+
51
+ #root {
52
+ isolation: isolate;
53
+ height: 100%;
54
+ }
55
+
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "isolatedModules": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+ "jsx": "react-jsx",
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+
23
+ /* Path aliases */
24
+ "baseUrl": ".",
25
+ "paths": {
26
+ "@/*": ["src/*"]
27
+ }
28
+ },
29
+ "include": ["src"],
30
+ "references": [{ "path": "./tsconfig.node.json" }]
31
+ }
32
+
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2023"],
5
+ "module": "ESNext",
6
+ "skipLibCheck": true,
7
+
8
+ /* Bundler mode */
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "isolatedModules": true,
12
+ "moduleDetection": "force",
13
+ "noEmit": true,
14
+
15
+ /* Linting */
16
+ "strict": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noFallthroughCasesInSwitch": true
20
+ },
21
+ "include": ["vite.config.ts"]
22
+ }
23
+
@@ -0,0 +1,22 @@
1
+ import react from '@vitejs/plugin-react';
2
+ import path from 'path';
3
+ import { defineConfig } from 'vite';
4
+
5
+ // https://vitejs.dev/config/
6
+ export default defineConfig({
7
+ plugins: [react()],
8
+ resolve: {
9
+ alias: {
10
+ '@': path.resolve(__dirname, './src'),
11
+ },
12
+ },
13
+ server: {
14
+ port: 3000,
15
+ open: true,
16
+ },
17
+ build: {
18
+ outDir: 'dist',
19
+ sourcemap: true,
20
+ },
21
+ });
22
+
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "state-redux",
3
+ "version": "1.0.0",
4
+ "description": "Redux Toolkit state management with typed hooks",
5
+ "compatibleWith": ["runtime-vite", "runtime-nextjs"],
6
+ "dependencies": {
7
+ "@reduxjs/toolkit": "^2.2.0",
8
+ "react-redux": "^9.1.0"
9
+ },
10
+ "devDependencies": {},
11
+ "scripts": {},
12
+ "filePatterns": {
13
+ "include": ["**/*"],
14
+ "exclude": ["manifest.json"]
15
+ }
16
+ }
17
+
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+
3
+ import { Provider } from 'react-redux';
4
+ import { store } from './store';
5
+
6
+ type StoreProviderProps = {
7
+ children: React.ReactNode;
8
+ };
9
+
10
+ /**
11
+ * Redux Provider component
12
+ * Wrap your app with this to enable Redux state management
13
+ */
14
+ export function StoreProvider({ children }: StoreProviderProps) {
15
+ return <Provider store={store}>{children}</Provider>;
16
+ }
17
+
@@ -0,0 +1,11 @@
1
+ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
2
+ import type { AppDispatch, RootState } from './store';
3
+
4
+ /**
5
+ * Typed hooks for Redux following bulletproof-react patterns
6
+ * Use these instead of plain `useDispatch` and `useSelector`
7
+ */
8
+
9
+ export const useAppDispatch = () => useDispatch<AppDispatch>();
10
+ export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
11
+
@@ -0,0 +1,17 @@
1
+ export { useAppDispatch, useAppSelector } from './hooks';
2
+ export { store, type AppDispatch, type RootState } from './store';
3
+
4
+ // Notifications
5
+ export {
6
+ addNotification, clearNotifications, removeNotification, selectNotifications,
7
+ type Notification,
8
+ type NotificationType
9
+ } from './slices/notifications';
10
+
11
+ // Auth
12
+ export {
13
+ logout, selectCurrentUser,
14
+ selectIsAuthenticated,
15
+ selectToken, setCredentials, updateUser
16
+ } from './slices/auth';
17
+
@@ -0,0 +1,54 @@
1
+ import type { User } from '@/types/api';
2
+ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
3
+
4
+ /**
5
+ * Auth slice following bulletproof-react patterns
6
+ */
7
+
8
+ type AuthState = {
9
+ user: User | null;
10
+ token: string | null;
11
+ isAuthenticated: boolean;
12
+ };
13
+
14
+ const initialState: AuthState = {
15
+ user: null,
16
+ token: null,
17
+ isAuthenticated: false,
18
+ };
19
+
20
+ const authSlice = createSlice({
21
+ name: 'auth',
22
+ initialState,
23
+ reducers: {
24
+ setCredentials: (
25
+ state,
26
+ action: PayloadAction<{ user: User; token: string }>
27
+ ) => {
28
+ state.user = action.payload.user;
29
+ state.token = action.payload.token;
30
+ state.isAuthenticated = true;
31
+ },
32
+ logout: (state) => {
33
+ state.user = null;
34
+ state.token = null;
35
+ state.isAuthenticated = false;
36
+ },
37
+ updateUser: (state, action: PayloadAction<Partial<User>>) => {
38
+ if (state.user) {
39
+ state.user = { ...state.user, ...action.payload };
40
+ }
41
+ },
42
+ },
43
+ });
44
+
45
+ export const { setCredentials, logout, updateUser } = authSlice.actions;
46
+
47
+ export const authReducer = authSlice.reducer;
48
+
49
+ // Selectors
50
+ export const selectCurrentUser = (state: { auth: AuthState }) => state.auth.user;
51
+ export const selectIsAuthenticated = (state: { auth: AuthState }) =>
52
+ state.auth.isAuthenticated;
53
+ export const selectToken = (state: { auth: AuthState }) => state.auth.token;
54
+
@@ -0,0 +1,58 @@
1
+ import { generateId } from '@/lib/utils';
2
+ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
3
+
4
+ /**
5
+ * Notifications slice following bulletproof-react patterns
6
+ */
7
+
8
+ export type NotificationType = 'info' | 'success' | 'warning' | 'error';
9
+
10
+ export type Notification = {
11
+ id: string;
12
+ type: NotificationType;
13
+ title: string;
14
+ message?: string;
15
+ };
16
+
17
+ type NotificationsState = {
18
+ notifications: Notification[];
19
+ };
20
+
21
+ const initialState: NotificationsState = {
22
+ notifications: [],
23
+ };
24
+
25
+ const notificationsSlice = createSlice({
26
+ name: 'notifications',
27
+ initialState,
28
+ reducers: {
29
+ addNotification: (
30
+ state,
31
+ action: PayloadAction<Omit<Notification, 'id'>>
32
+ ) => {
33
+ const notification = {
34
+ ...action.payload,
35
+ id: generateId(),
36
+ };
37
+ state.notifications.push(notification);
38
+ },
39
+ removeNotification: (state, action: PayloadAction<string>) => {
40
+ state.notifications = state.notifications.filter(
41
+ (n) => n.id !== action.payload
42
+ );
43
+ },
44
+ clearNotifications: (state) => {
45
+ state.notifications = [];
46
+ },
47
+ },
48
+ });
49
+
50
+ export const { addNotification, removeNotification, clearNotifications } =
51
+ notificationsSlice.actions;
52
+
53
+ export const notificationsReducer = notificationsSlice.reducer;
54
+
55
+ // Selectors
56
+ export const selectNotifications = (state: { notifications: NotificationsState }) =>
57
+ state.notifications.notifications;
58
+
@@ -0,0 +1,27 @@
1
+ import { configureStore } from '@reduxjs/toolkit';
2
+ import { authReducer } from './slices/auth';
3
+ import { notificationsReducer } from './slices/notifications';
4
+
5
+ /**
6
+ * Redux store configuration following bulletproof-react patterns
7
+ */
8
+
9
+ export const store = configureStore({
10
+ reducer: {
11
+ notifications: notificationsReducer,
12
+ auth: authReducer,
13
+ },
14
+ middleware: (getDefaultMiddleware) =>
15
+ getDefaultMiddleware({
16
+ serializableCheck: {
17
+ // Ignore these action types for serialization check
18
+ ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
19
+ },
20
+ }),
21
+ devTools: process.env.NODE_ENV !== 'production',
22
+ });
23
+
24
+ // Infer the `RootState` and `AppDispatch` types from the store itself
25
+ export type RootState = ReturnType<typeof store.getState>;
26
+ export type AppDispatch = typeof store.dispatch;
27
+
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "state-zustand",
3
+ "version": "1.0.0",
4
+ "description": "Zustand state management following bulletproof-react patterns",
5
+ "compatibleWith": ["runtime-vite", "runtime-nextjs"],
6
+ "dependencies": {
7
+ "zustand": "^4.5.0"
8
+ },
9
+ "devDependencies": {},
10
+ "scripts": {},
11
+ "filePatterns": {
12
+ "include": ["**/*"],
13
+ "exclude": ["manifest.json"]
14
+ }
15
+ }
16
+
@@ -0,0 +1,48 @@
1
+ import type { User } from '@/types/api';
2
+ import { create } from 'zustand';
3
+ import { persist } from 'zustand/middleware';
4
+
5
+ /**
6
+ * Auth store with persistence following bulletproof-react patterns
7
+ */
8
+
9
+ type AuthState = {
10
+ user: User | null;
11
+ token: string | null;
12
+ isAuthenticated: boolean;
13
+ setAuth: (user: User, token: string) => void;
14
+ logout: () => void;
15
+ };
16
+
17
+ export const useAuth = create<AuthState>()(
18
+ persist(
19
+ (set) => ({
20
+ user: null,
21
+ token: null,
22
+ isAuthenticated: false,
23
+
24
+ setAuth: (user, token) =>
25
+ set({
26
+ user,
27
+ token,
28
+ isAuthenticated: true,
29
+ }),
30
+
31
+ logout: () =>
32
+ set({
33
+ user: null,
34
+ token: null,
35
+ isAuthenticated: false,
36
+ }),
37
+ }),
38
+ {
39
+ name: 'auth-storage',
40
+ partialize: (state) => ({
41
+ user: state.user,
42
+ token: state.token,
43
+ isAuthenticated: state.isAuthenticated,
44
+ }),
45
+ }
46
+ )
47
+ );
48
+
@@ -0,0 +1,3 @@
1
+ export { useAuth } from './auth';
2
+ export { useNotifications, type Notification, type NotificationType } from './notifications';
3
+
@@ -0,0 +1,54 @@
1
+ import { generateId } from '@/lib/utils';
2
+ import { create } from 'zustand';
3
+
4
+ /**
5
+ * Notification store following bulletproof-react patterns
6
+ */
7
+
8
+ export type NotificationType = 'info' | 'success' | 'warning' | 'error';
9
+
10
+ export type Notification = {
11
+ id: string;
12
+ type: NotificationType;
13
+ title: string;
14
+ message?: string;
15
+ duration?: number;
16
+ };
17
+
18
+ type NotificationsState = {
19
+ notifications: Notification[];
20
+ addNotification: (notification: Omit<Notification, 'id'>) => void;
21
+ removeNotification: (id: string) => void;
22
+ clearNotifications: () => void;
23
+ };
24
+
25
+ export const useNotifications = create<NotificationsState>((set) => ({
26
+ notifications: [],
27
+
28
+ addNotification: (notification) => {
29
+ const id = generateId();
30
+ const newNotification = { ...notification, id };
31
+
32
+ set((state) => ({
33
+ notifications: [...state.notifications, newNotification],
34
+ }));
35
+
36
+ // Auto-remove after duration (default 5 seconds)
37
+ const duration = notification.duration ?? 5000;
38
+ if (duration > 0) {
39
+ setTimeout(() => {
40
+ set((state) => ({
41
+ notifications: state.notifications.filter((n) => n.id !== id),
42
+ }));
43
+ }, duration);
44
+ }
45
+ },
46
+
47
+ removeNotification: (id) =>
48
+ set((state) => ({
49
+ notifications: state.notifications.filter((n) => n.id !== id),
50
+ })),
51
+
52
+ clearNotifications: () => set({ notifications: [] }),
53
+ }));
54
+
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "styling-css-modules",
3
+ "version": "1.0.0",
4
+ "description": "CSS Modules styling with scoped CSS",
5
+ "compatibleWith": ["runtime-vite", "runtime-nextjs"],
6
+ "dependencies": {},
7
+ "devDependencies": {},
8
+ "scripts": {},
9
+ "filePatterns": {
10
+ "include": ["**/*"],
11
+ "exclude": ["manifest.json"]
12
+ }
13
+ }
14
+