create-react-forge 1.5.2 → 1.6.1

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 (104) hide show
  1. package/README.md +6 -5
  2. package/dist/cli/index.d.ts +2 -2
  3. package/dist/cli/parser.d.ts.map +1 -1
  4. package/dist/cli/parser.js +3 -2
  5. package/dist/cli/parser.js.map +1 -1
  6. package/dist/cli/prompts.d.ts.map +1 -1
  7. package/dist/cli/prompts.js +21 -7
  8. package/dist/cli/prompts.js.map +1 -1
  9. package/dist/config/defaults.d.ts +2 -2
  10. package/dist/config/defaults.d.ts.map +1 -1
  11. package/dist/config/defaults.js +3 -2
  12. package/dist/config/defaults.js.map +1 -1
  13. package/dist/config/schema.d.ts +15 -13
  14. package/dist/config/schema.d.ts.map +1 -1
  15. package/dist/config/schema.js +5 -4
  16. package/dist/config/schema.js.map +1 -1
  17. package/dist/docs/index.d.ts +1 -0
  18. package/dist/docs/index.d.ts.map +1 -1
  19. package/dist/docs/index.js +1 -0
  20. package/dist/docs/index.js.map +1 -1
  21. package/dist/docs/readme-generator.d.ts +6 -0
  22. package/dist/docs/readme-generator.d.ts.map +1 -0
  23. package/dist/docs/readme-generator.js +276 -0
  24. package/dist/docs/readme-generator.js.map +1 -0
  25. package/dist/generator/index.d.ts.map +1 -1
  26. package/dist/generator/index.js +5 -2
  27. package/dist/generator/index.js.map +1 -1
  28. package/dist/templates/registry.d.ts +6 -2
  29. package/dist/templates/registry.d.ts.map +1 -1
  30. package/dist/templates/registry.js +31 -16
  31. package/dist/templates/registry.js.map +1 -1
  32. package/dist/templates/utils.js +4 -4
  33. package/dist/templates/utils.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/templates/overlays/base/manifest.json +4 -3
  36. package/src/templates/overlays/base/src/components/ui/Button.tsx +103 -31
  37. package/src/templates/overlays/base/src/components/ui/Input.tsx +55 -29
  38. package/src/templates/overlays/base/src/lib/utils.ts +0 -10
  39. package/src/templates/overlays/runtime/nextjs/src/app/error.tsx +39 -10
  40. package/src/templates/overlays/runtime/nextjs/src/app/loading.tsx +25 -7
  41. package/src/templates/overlays/runtime/nextjs/src/app/not-found.tsx +54 -13
  42. package/src/templates/overlays/runtime/nextjs/src/app/page.tsx +55 -13
  43. package/src/templates/overlays/runtime/nextjs/src/styles/globals.css +1 -1
  44. package/src/templates/overlays/runtime/vite/src/components/errors/ErrorFallback.tsx +49 -15
  45. package/src/templates/overlays/runtime/vite/src/components/ui/LoadingSpinner.tsx +33 -13
  46. package/src/templates/overlays/runtime/vite/src/features/misc/routes/Landing.tsx +78 -21
  47. package/src/templates/overlays/runtime/vite/src/features/misc/routes/NotFound.tsx +77 -19
  48. package/src/templates/overlays/runtime/vite/src/main.tsx +0 -2
  49. package/src/templates/overlays/state/jotai/manifest.json +16 -0
  50. package/src/templates/overlays/state/jotai/src/stores/atoms.ts +52 -0
  51. package/src/templates/overlays/state/jotai/src/stores/index.ts +30 -0
  52. package/src/templates/overlays/styling/css/_nextjs/src/app/error.css +49 -0
  53. package/src/templates/overlays/styling/css/_nextjs/src/app/error.tsx +34 -0
  54. package/src/templates/overlays/styling/css/_nextjs/src/app/loading.css +28 -0
  55. package/src/templates/overlays/styling/css/_nextjs/src/app/loading.tsx +13 -0
  56. package/src/templates/overlays/styling/css/_nextjs/src/app/not-found.css +70 -0
  57. package/src/templates/overlays/styling/css/_nextjs/src/app/not-found.tsx +25 -0
  58. package/src/templates/overlays/styling/css/_nextjs/src/app/page.css +73 -0
  59. package/src/templates/overlays/styling/css/_nextjs/src/app/page.tsx +32 -0
  60. package/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.css +49 -0
  61. package/src/templates/overlays/styling/css/_vite/src/components/errors/ErrorFallback.tsx +22 -0
  62. package/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.css +40 -0
  63. package/src/templates/overlays/styling/css/_vite/src/components/ui/LoadingSpinner.tsx +21 -0
  64. package/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.css +73 -0
  65. package/src/templates/overlays/styling/css/_vite/src/features/misc/routes/Landing.tsx +32 -0
  66. package/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.css +70 -0
  67. package/src/templates/overlays/styling/css/_vite/src/features/misc/routes/NotFound.tsx +25 -0
  68. package/src/templates/overlays/styling/css/manifest.json +17 -0
  69. package/src/templates/overlays/styling/css/src/styles/globals.css +107 -0
  70. package/src/templates/overlays/styling/css-modules/_nextjs/src/app/loading.tsx +13 -0
  71. package/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.module.css +73 -0
  72. package/src/templates/overlays/styling/css-modules/_nextjs/src/app/page.tsx +32 -0
  73. package/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.module.css +45 -0
  74. package/src/templates/overlays/styling/css-modules/_vite/src/components/errors/ErrorFallback.tsx +22 -0
  75. package/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.module.css +40 -0
  76. package/src/templates/overlays/styling/css-modules/_vite/src/components/ui/LoadingSpinner.tsx +23 -0
  77. package/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.module.css +73 -0
  78. package/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/Landing.tsx +32 -0
  79. package/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.module.css +67 -0
  80. package/src/templates/overlays/styling/css-modules/_vite/src/features/misc/routes/NotFound.tsx +25 -0
  81. package/src/templates/overlays/styling/css-modules/manifest.json +6 -3
  82. package/src/templates/overlays/styling/css-modules/src/css.d.ts +13 -0
  83. package/src/templates/overlays/styling/styled-components/_nextjs/src/app/error.tsx +72 -0
  84. package/src/templates/overlays/styling/styled-components/_nextjs/src/app/layout.tsx +25 -0
  85. package/src/templates/overlays/styling/styled-components/_nextjs/src/app/loading.tsx +40 -0
  86. package/src/templates/overlays/styling/styled-components/_nextjs/src/app/not-found.tsx +91 -0
  87. package/src/templates/overlays/styling/styled-components/_nextjs/src/app/page.tsx +102 -0
  88. package/src/templates/overlays/styling/styled-components/_nextjs/src/app/providers.tsx +22 -0
  89. package/src/templates/overlays/styling/styled-components/{src → _nextjs/src}/lib/StyledComponentsRegistry.tsx +10 -2
  90. package/src/templates/overlays/styling/styled-components/_vite/src/app/provider.tsx +27 -0
  91. package/src/templates/overlays/styling/styled-components/_vite/src/components/errors/ErrorFallback.tsx +59 -0
  92. package/src/templates/overlays/styling/styled-components/_vite/src/components/ui/LoadingSpinner.tsx +47 -0
  93. package/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/Landing.tsx +100 -0
  94. package/src/templates/overlays/styling/styled-components/_vite/src/features/misc/routes/NotFound.tsx +89 -0
  95. package/src/templates/overlays/styling/styled-components/_vite/src/main.tsx +13 -0
  96. package/src/templates/overlays/styling/styled-components/manifest.json +5 -1
  97. package/src/templates/overlays/styling/tailwind/_nextjs/src/app/error.tsx +33 -0
  98. package/src/templates/overlays/styling/tailwind/_nextjs/src/app/loading.tsx +12 -0
  99. package/src/templates/overlays/styling/tailwind/_nextjs/src/app/not-found.tsx +26 -0
  100. package/src/templates/overlays/styling/tailwind/_nextjs/src/app/page.tsx +33 -0
  101. package/src/templates/overlays/styling/tailwind/manifest.json +5 -3
  102. package/src/templates/overlays/runtime/vite/src/styles/globals.css +0 -55
  103. package/src/templates/overlays/styling/css-modules/src/components/ui/Button.module.css +0 -87
  104. package/src/templates/overlays/styling/css-modules/src/styles/globals.css +0 -91
@@ -1,23 +1,43 @@
1
+ import styled, { keyframes } from 'styled-components';
2
+
1
3
  type LoadingSpinnerProps = {
2
4
  size?: 'sm' | 'md' | 'lg';
3
- className?: string;
4
5
  };
5
6
 
7
+ const spin = keyframes`
8
+ from {
9
+ transform: rotate(0deg);
10
+ }
11
+ to {
12
+ transform: rotate(360deg);
13
+ }
14
+ `;
15
+
6
16
  const sizes = {
7
- sm: 'h-4 w-4',
8
- md: 'h-8 w-8',
9
- lg: 'h-16 w-16',
17
+ sm: '1rem',
18
+ md: '2rem',
19
+ lg: '4rem',
10
20
  };
11
21
 
12
- export function LoadingSpinner({ size = 'md', className = '' }: LoadingSpinnerProps) {
22
+ const SpinnerContainer = styled.div`
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ `;
27
+
28
+ const Spinner = styled.div<{ $size: 'sm' | 'md' | 'lg' }>`
29
+ width: ${(props) => sizes[props.$size]};
30
+ height: ${(props) => sizes[props.$size]};
31
+ border: 2px solid #d1d5db;
32
+ border-top-color: #4f46e5;
33
+ border-radius: 50%;
34
+ animation: ${spin} 1s linear infinite;
35
+ `;
36
+
37
+ export function LoadingSpinner({ size = 'md' }: LoadingSpinnerProps) {
13
38
  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>
39
+ <SpinnerContainer>
40
+ <Spinner $size={size} role="status" aria-label="Loading" />
41
+ </SpinnerContainer>
21
42
  );
22
43
  }
23
-
@@ -1,33 +1,90 @@
1
1
  import { Link } from 'react-router-dom';
2
+ import styled from 'styled-components';
3
+
4
+ const Container = styled.div`
5
+ display: flex;
6
+ min-height: 100vh;
7
+ flex-direction: column;
8
+ align-items: center;
9
+ justify-content: center;
10
+ `;
11
+
12
+ const Content = styled.div`
13
+ text-align: center;
14
+ `;
15
+
16
+ const Title = styled.h1`
17
+ font-size: 2.25rem;
18
+ font-weight: 700;
19
+ letter-spacing: -0.025em;
20
+
21
+ @media (min-width: 640px) {
22
+ font-size: 3.75rem;
23
+ }
24
+ `;
25
+
26
+ const Description = styled.p`
27
+ margin-top: 1.5rem;
28
+ font-size: 1.125rem;
29
+ line-height: 2rem;
30
+ color: #4b5563;
31
+ `;
32
+
33
+ const Actions = styled.div`
34
+ margin-top: 2.5rem;
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ gap: 1.5rem;
39
+ `;
40
+
41
+ const PrimaryLink = styled(Link)`
42
+ padding: 0.625rem 0.875rem;
43
+ font-size: 0.875rem;
44
+ font-weight: 600;
45
+ color: white;
46
+ background-color: #4f46e5;
47
+ border-radius: 0.375rem;
48
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
49
+ text-decoration: none;
50
+ transition: background-color 0.2s;
51
+
52
+ &:hover {
53
+ background-color: #6366f1;
54
+ }
55
+ `;
56
+
57
+ const SecondaryLink = styled.a`
58
+ font-size: 0.875rem;
59
+ font-weight: 600;
60
+ line-height: 1.5rem;
61
+ color: inherit;
62
+ text-decoration: none;
63
+
64
+ &:hover {
65
+ text-decoration: underline;
66
+ }
67
+ `;
2
68
 
3
69
  export function Landing() {
4
70
  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">
71
+ <Container>
72
+ <Content>
73
+ <Title>Welcome to Your App</Title>
74
+ <Description>
11
75
  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
76
+ </Description>
77
+ <Actions>
78
+ <PrimaryLink to="/dashboard">Get started</PrimaryLink>
79
+ <SecondaryLink
21
80
  href="https://github.com/alan2207/bulletproof-react"
22
- className="text-sm font-semibold leading-6"
23
81
  target="_blank"
24
82
  rel="noopener noreferrer"
25
83
  >
26
84
  Learn more <span aria-hidden="true">→</span>
27
- </a>
28
- </div>
29
- </div>
30
- </div>
85
+ </SecondaryLink>
86
+ </Actions>
87
+ </Content>
88
+ </Container>
31
89
  );
32
90
  }
33
-
@@ -1,26 +1,84 @@
1
1
  import { Link } from 'react-router-dom';
2
+ import styled from 'styled-components';
3
+
4
+ const Container = styled.div`
5
+ display: flex;
6
+ min-height: 100vh;
7
+ flex-direction: column;
8
+ align-items: center;
9
+ justify-content: center;
10
+ `;
11
+
12
+ const Content = styled.div`
13
+ text-align: center;
14
+ `;
15
+
16
+ const ErrorCode = styled.p`
17
+ font-size: 1rem;
18
+ font-weight: 600;
19
+ color: #4f46e5;
20
+ `;
21
+
22
+ const Title = styled.h1`
23
+ margin-top: 1rem;
24
+ font-size: 1.875rem;
25
+ font-weight: 700;
26
+ letter-spacing: -0.025em;
27
+
28
+ @media (min-width: 640px) {
29
+ font-size: 3rem;
30
+ }
31
+ `;
32
+
33
+ const Description = styled.p`
34
+ margin-top: 1.5rem;
35
+ font-size: 1rem;
36
+ line-height: 1.75rem;
37
+ color: #4b5563;
38
+ `;
39
+
40
+ const Actions = styled.div`
41
+ margin-top: 2.5rem;
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ gap: 1.5rem;
46
+ `;
47
+
48
+ const HomeLink = styled(Link)`
49
+ padding: 0.625rem 0.875rem;
50
+ font-size: 0.875rem;
51
+ font-weight: 600;
52
+ color: white;
53
+ background-color: #4f46e5;
54
+ border-radius: 0.375rem;
55
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
56
+ text-decoration: none;
57
+ transition: background-color 0.2s;
58
+
59
+ &:hover {
60
+ background-color: #6366f1;
61
+ }
62
+
63
+ &:focus-visible {
64
+ outline: 2px solid #4f46e5;
65
+ outline-offset: 2px;
66
+ }
67
+ `;
2
68
 
3
69
  export function NotFound() {
4
70
  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">
71
+ <Container>
72
+ <Content>
73
+ <ErrorCode>404</ErrorCode>
74
+ <Title>Page not found</Title>
75
+ <Description>
12
76
  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>
77
+ </Description>
78
+ <Actions>
79
+ <HomeLink to="/">Go back home</HomeLink>
80
+ </Actions>
81
+ </Content>
82
+ </Container>
24
83
  );
25
84
  }
26
-
@@ -1,5 +1,4 @@
1
1
  import { App } from '@/app/App';
2
- import '@/styles/globals.css';
3
2
  import React from 'react';
4
3
  import ReactDOM from 'react-dom/client';
5
4
 
@@ -8,4 +7,3 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
8
7
  <App />
9
8
  </React.StrictMode>
10
9
  );
11
-
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "state-jotai",
3
+ "version": "1.0.0",
4
+ "description": "Jotai state management with primitive and flexible atoms",
5
+ "compatibleWith": ["runtime-vite", "runtime-nextjs"],
6
+ "dependencies": {
7
+ "jotai": "^2.10.0"
8
+ },
9
+ "devDependencies": {},
10
+ "scripts": {},
11
+ "filePatterns": {
12
+ "include": ["**/*"],
13
+ "exclude": ["manifest.json"]
14
+ }
15
+ }
16
+
@@ -0,0 +1,52 @@
1
+ import { atom } from 'jotai';
2
+
3
+ /**
4
+ * Auth atoms for user authentication state
5
+ */
6
+ export interface User {
7
+ id: string;
8
+ email: string;
9
+ name: string;
10
+ }
11
+
12
+ export const userAtom = atom<User | null>(null);
13
+ export const isAuthenticatedAtom = atom((get) => get(userAtom) !== null);
14
+
15
+ /**
16
+ * Notification atoms for app-wide notifications
17
+ */
18
+ export interface Notification {
19
+ id: string;
20
+ type: 'success' | 'error' | 'warning' | 'info';
21
+ message: string;
22
+ title?: string;
23
+ }
24
+
25
+ export const notificationsAtom = atom<Notification[]>([]);
26
+
27
+ // Derived atom to add a notification
28
+ export const addNotificationAtom = atom(
29
+ null,
30
+ (get, set, notification: Omit<Notification, 'id'>) => {
31
+ const id = crypto.randomUUID();
32
+ set(notificationsAtom, [...get(notificationsAtom), { ...notification, id }]);
33
+ }
34
+ );
35
+
36
+ // Derived atom to remove a notification
37
+ export const removeNotificationAtom = atom(
38
+ null,
39
+ (get, set, id: string) => {
40
+ set(
41
+ notificationsAtom,
42
+ get(notificationsAtom).filter((n) => n.id !== id)
43
+ );
44
+ }
45
+ );
46
+
47
+ /**
48
+ * Theme atom for dark/light mode
49
+ */
50
+ export type Theme = 'light' | 'dark' | 'system';
51
+ export const themeAtom = atom<Theme>('system');
52
+
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Jotai Store Exports
3
+ *
4
+ * This file exports all atoms for easy importing throughout the application.
5
+ * Import atoms from this file to use Jotai state management.
6
+ *
7
+ * @example
8
+ * import { userAtom, notificationsAtom } from '@/stores';
9
+ * import { useAtom, useAtomValue, useSetAtom } from 'jotai';
10
+ *
11
+ * // Read and write
12
+ * const [user, setUser] = useAtom(userAtom);
13
+ *
14
+ * // Read only
15
+ * const notifications = useAtomValue(notificationsAtom);
16
+ *
17
+ * // Write only
18
+ * const addNotification = useSetAtom(addNotificationAtom);
19
+ */
20
+
21
+ export {
22
+ addNotificationAtom, isAuthenticatedAtom,
23
+ // Notification atoms
24
+ notificationsAtom, removeNotificationAtom,
25
+ // Theme atom
26
+ themeAtom,
27
+ // Auth atoms
28
+ userAtom, type Notification, type Theme, type User
29
+ } from './atoms.js';
30
+
@@ -0,0 +1,49 @@
1
+ .error-container {
2
+ display: flex;
3
+ min-height: 100vh;
4
+ flex-direction: column;
5
+ align-items: center;
6
+ justify-content: center;
7
+ }
8
+
9
+ .error-content {
10
+ text-align: center;
11
+ }
12
+
13
+ .error-title {
14
+ font-size: 1.5rem;
15
+ font-weight: 700;
16
+ color: var(--color-danger);
17
+ }
18
+
19
+ .error-message {
20
+ margin-top: 1rem;
21
+ color: var(--color-text-muted);
22
+ }
23
+
24
+ .btn {
25
+ display: inline-block;
26
+ font-weight: 600;
27
+ text-decoration: none;
28
+ transition: all 0.2s;
29
+ border: none;
30
+ cursor: pointer;
31
+ }
32
+
33
+ .btn-primary {
34
+ margin-top: 1.5rem;
35
+ border-radius: var(--radius-md);
36
+ background-color: var(--color-primary);
37
+ padding: 0.625rem 0.875rem;
38
+ font-size: 0.875rem;
39
+ color: white;
40
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
41
+ }
42
+
43
+ .btn-primary:hover {
44
+ background-color: var(--color-primary-hover);
45
+ }
46
+
47
+
48
+
49
+
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+ import './error.css';
5
+
6
+ export default function Error({
7
+ error,
8
+ reset,
9
+ }: {
10
+ error: Error & { digest?: string };
11
+ reset: () => void;
12
+ }) {
13
+ useEffect(() => {
14
+ console.error(error);
15
+ }, [error]);
16
+
17
+ return (
18
+ <div className="error-container" role="alert">
19
+ <div className="error-content">
20
+ <h1 className="error-title">Something went wrong</h1>
21
+ <p className="error-message">
22
+ {error.message || 'An unexpected error occurred'}
23
+ </p>
24
+ <button onClick={reset} className="btn btn-primary">
25
+ Try again
26
+ </button>
27
+ </div>
28
+ </div>
29
+ );
30
+ }
31
+
32
+
33
+
34
+
@@ -0,0 +1,28 @@
1
+ .spinner-container {
2
+ display: flex;
3
+ min-height: 100vh;
4
+ align-items: center;
5
+ justify-content: center;
6
+ }
7
+
8
+ .spinner {
9
+ width: 2rem;
10
+ height: 2rem;
11
+ border-radius: 50%;
12
+ border: 2px solid var(--color-border);
13
+ border-top-color: var(--color-primary);
14
+ animation: spin 1s linear infinite;
15
+ }
16
+
17
+ @keyframes spin {
18
+ from {
19
+ transform: rotate(0deg);
20
+ }
21
+ to {
22
+ transform: rotate(360deg);
23
+ }
24
+ }
25
+
26
+
27
+
28
+
@@ -0,0 +1,13 @@
1
+ import './loading.css';
2
+
3
+ export default function Loading() {
4
+ return (
5
+ <div className="spinner-container">
6
+ <div className="spinner" role="status" aria-label="Loading" />
7
+ </div>
8
+ );
9
+ }
10
+
11
+
12
+
13
+
@@ -0,0 +1,70 @@
1
+ .not-found-container {
2
+ display: flex;
3
+ min-height: 100vh;
4
+ flex-direction: column;
5
+ align-items: center;
6
+ justify-content: center;
7
+ }
8
+
9
+ .not-found-content {
10
+ text-align: center;
11
+ }
12
+
13
+ .not-found-code {
14
+ font-size: 1rem;
15
+ font-weight: 600;
16
+ color: var(--color-primary);
17
+ }
18
+
19
+ .not-found-title {
20
+ margin-top: 1rem;
21
+ font-size: 1.875rem;
22
+ font-weight: 700;
23
+ letter-spacing: -0.025em;
24
+ color: var(--color-text);
25
+ }
26
+
27
+ @media (min-width: 640px) {
28
+ .not-found-title {
29
+ font-size: 3rem;
30
+ }
31
+ }
32
+
33
+ .not-found-description {
34
+ margin-top: 1.5rem;
35
+ font-size: 1rem;
36
+ line-height: 1.75rem;
37
+ color: var(--color-text-muted);
38
+ }
39
+
40
+ .not-found-buttons {
41
+ margin-top: 2.5rem;
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ gap: 1.5rem;
46
+ }
47
+
48
+ .btn {
49
+ display: inline-block;
50
+ font-weight: 600;
51
+ text-decoration: none;
52
+ transition: all 0.2s;
53
+ }
54
+
55
+ .btn-primary {
56
+ border-radius: var(--radius-md);
57
+ background-color: var(--color-primary);
58
+ padding: 0.625rem 0.875rem;
59
+ font-size: 0.875rem;
60
+ color: white;
61
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
62
+ }
63
+
64
+ .btn-primary:hover {
65
+ background-color: var(--color-primary-hover);
66
+ }
67
+
68
+
69
+
70
+
@@ -0,0 +1,25 @@
1
+ import Link from 'next/link';
2
+ import './not-found.css';
3
+
4
+ export default function NotFound() {
5
+ return (
6
+ <div className="not-found-container">
7
+ <div className="not-found-content">
8
+ <p className="not-found-code">404</p>
9
+ <h1 className="not-found-title">Page not found</h1>
10
+ <p className="not-found-description">
11
+ Sorry, we couldn't find the page you're looking for.
12
+ </p>
13
+ <div className="not-found-buttons">
14
+ <Link href="/" className="btn btn-primary">
15
+ Go back home
16
+ </Link>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ );
21
+ }
22
+
23
+
24
+
25
+
@@ -0,0 +1,73 @@
1
+ .landing-container {
2
+ display: flex;
3
+ min-height: 100vh;
4
+ flex-direction: column;
5
+ align-items: center;
6
+ justify-content: center;
7
+ }
8
+
9
+ .landing-content {
10
+ text-align: center;
11
+ }
12
+
13
+ .landing-title {
14
+ font-size: 2.25rem;
15
+ font-weight: 700;
16
+ letter-spacing: -0.025em;
17
+ color: var(--color-text);
18
+ }
19
+
20
+ @media (min-width: 640px) {
21
+ .landing-title {
22
+ font-size: 3.75rem;
23
+ }
24
+ }
25
+
26
+ .landing-description {
27
+ margin-top: 1.5rem;
28
+ font-size: 1.125rem;
29
+ line-height: 2rem;
30
+ color: var(--color-text-muted);
31
+ }
32
+
33
+ .landing-buttons {
34
+ margin-top: 2.5rem;
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ gap: 1.5rem;
39
+ }
40
+
41
+ .btn {
42
+ display: inline-block;
43
+ font-weight: 600;
44
+ text-decoration: none;
45
+ transition: all 0.2s;
46
+ }
47
+
48
+ .btn-primary {
49
+ border-radius: var(--radius-md);
50
+ background-color: var(--color-primary);
51
+ padding: 0.625rem 0.875rem;
52
+ font-size: 0.875rem;
53
+ color: white;
54
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
55
+ }
56
+
57
+ .btn-primary:hover {
58
+ background-color: var(--color-primary-hover);
59
+ }
60
+
61
+ .btn-link {
62
+ font-size: 0.875rem;
63
+ line-height: 1.5rem;
64
+ color: var(--color-text);
65
+ }
66
+
67
+ .btn-link:hover {
68
+ color: var(--color-primary);
69
+ }
70
+
71
+
72
+
73
+