imlil 1.0.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.
- package/.eslintrc.cjs +40 -0
- package/DOCS.md +63 -0
- package/README.md +160 -0
- package/agentTestSandbox/cli-test-zone/README.md +0 -0
- package/agentTestSandbox/cli-test-zone/imlil.blueprint.json +5 -0
- package/agentTestSandbox/cli-test-zone/notes-warning.md +3 -0
- package/agentTestSandbox/cli-test-zone/package.json +0 -0
- package/agentTestSandbox/cli-test-zone/public/index.html +0 -0
- package/agentTestSandbox/cli-test-zone/src/App.js +0 -0
- package/agentTestSandbox/cli-test-zone/src/App.jsx +29 -0
- package/agentTestSandbox/cli-test-zone/src/__tests__/App.test.jsx +48 -0
- package/agentTestSandbox/cli-test-zone/src/components/AddTodo.js +0 -0
- package/agentTestSandbox/cli-test-zone/src/components/Navigation/Navigation.jsx +48 -0
- package/agentTestSandbox/cli-test-zone/src/components/Navigation/__tests__/Navigation.module.test.js +45 -0
- package/agentTestSandbox/cli-test-zone/src/components/Navigation/__tests__/Navigation.test.jsx +47 -0
- package/agentTestSandbox/cli-test-zone/src/components/Navigation.js +0 -0
- package/agentTestSandbox/cli-test-zone/src/components/TodoItem/TodoItem.jsx +41 -0
- package/agentTestSandbox/cli-test-zone/src/components/TodoItem/__tests__/TodoItem.test.jsx +65 -0
- package/agentTestSandbox/cli-test-zone/src/components/TodoItem.js +0 -0
- package/agentTestSandbox/cli-test-zone/src/components/TodoList/TodoList.module.css +62 -0
- package/agentTestSandbox/cli-test-zone/src/components/TodoList.js +0 -0
- package/agentTestSandbox/cli-test-zone/src/index.js +0 -0
- package/agentTestSandbox/cli-test-zone/src/pages/About.js +0 -0
- package/agentTestSandbox/cli-test-zone/src/pages/Home.js +0 -0
- package/agentTestSandbox/cli-test-zone/src/store/TodoContext.js +0 -0
- package/agentTestSandbox/cli-test-zone/src/styles/Todo.css +0 -0
- package/agentTestSandbox/cli-test-zone/src/styles/index.css +0 -0
- package/agentTestSandbox/cli-test-zone/src/utils/__tests__/localStorage.test.js +48 -0
- package/agentTestSandbox/cli-test-zone/src/utils/localStorage.js +38 -0
- package/agentTestSandbox/parallel-test/.env.example +0 -0
- package/agentTestSandbox/parallel-test/.eslintrc.json +0 -0
- package/agentTestSandbox/parallel-test/.github/workflows/__tests__/workflows.test.ts +115 -0
- package/agentTestSandbox/parallel-test/.github/workflows/cd.yml +0 -0
- package/agentTestSandbox/parallel-test/.github/workflows/ci.yml +4 -0
- package/agentTestSandbox/parallel-test/.imlil/plan-2026-02-08.md +186 -0
- package/agentTestSandbox/parallel-test/.prettierrc +0 -0
- package/agentTestSandbox/parallel-test/Dockerfile +0 -0
- package/agentTestSandbox/parallel-test/README.md +3 -0
- package/agentTestSandbox/parallel-test/ast.json +74 -0
- package/agentTestSandbox/parallel-test/docker-compose.yml +4 -0
- package/agentTestSandbox/parallel-test/jest.config.js +61 -0
- package/agentTestSandbox/parallel-test/k8s/__tests__/deployment.test.ts +168 -0
- package/agentTestSandbox/parallel-test/k8s/frontend-deployment.yaml +4 -0
- package/agentTestSandbox/parallel-test/nginx/nginx.conf +0 -0
- package/agentTestSandbox/parallel-test/package.json +50 -0
- package/agentTestSandbox/parallel-test/prisma/__tests__/schema.test.ts +176 -0
- package/agentTestSandbox/parallel-test/prisma/schema.prisma +109 -0
- package/agentTestSandbox/parallel-test/server/__tests__/controllers/dashboard.controller.test.ts +127 -0
- package/agentTestSandbox/parallel-test/server/__tests__/index.test.ts +60 -0
- package/agentTestSandbox/parallel-test/server/__tests__/models/user.model.test.ts +111 -0
- package/agentTestSandbox/parallel-test/server/config/__tests__/swagger.test.ts +128 -0
- package/agentTestSandbox/parallel-test/server/config/database.ts +0 -0
- package/agentTestSandbox/parallel-test/server/config/redis.ts +0 -0
- package/agentTestSandbox/parallel-test/server/config/swagger.ts +0 -0
- package/agentTestSandbox/parallel-test/server/controllers/__tests__/auth.controller.test.ts +178 -0
- package/agentTestSandbox/parallel-test/server/controllers/__tests__/user.controller.test.ts +105 -0
- package/agentTestSandbox/parallel-test/server/controllers/auth.controller.ts +148 -0
- package/agentTestSandbox/parallel-test/server/controllers/dashboard.controller.ts +137 -0
- package/agentTestSandbox/parallel-test/server/controllers/user.controller.ts +161 -0
- package/agentTestSandbox/parallel-test/server/index.ts +62 -0
- package/agentTestSandbox/parallel-test/server/middleware/__tests__/auth.middleware.test.ts +74 -0
- package/agentTestSandbox/parallel-test/server/middleware/auth.middleware.ts +55 -0
- package/agentTestSandbox/parallel-test/server/middleware/error.middleware.ts +0 -0
- package/agentTestSandbox/parallel-test/server/middleware/validation.middleware.ts +0 -0
- package/agentTestSandbox/parallel-test/server/models/analytics.model.ts +0 -0
- package/agentTestSandbox/parallel-test/server/models/profile.model.ts +0 -0
- package/agentTestSandbox/parallel-test/server/models/user.model.ts +78 -0
- package/agentTestSandbox/parallel-test/server/routes/auth.routes.ts +0 -0
- package/agentTestSandbox/parallel-test/server/routes/dashboard.routes.ts +0 -0
- package/agentTestSandbox/parallel-test/server/routes/user.routes.ts +0 -0
- package/agentTestSandbox/parallel-test/src/App.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/__tests__/config.test.ts +127 -0
- package/agentTestSandbox/parallel-test/src/__tests__/index.test.tsx +36 -0
- package/agentTestSandbox/parallel-test/src/__tests__/setup.test.ts +34 -0
- package/agentTestSandbox/parallel-test/src/__tests__/setupTest.test.ts +44 -0
- package/agentTestSandbox/parallel-test/src/components/common/Button/Button.tsx +80 -0
- package/agentTestSandbox/parallel-test/src/components/common/Button/__tests__/Button.test.tsx +75 -0
- package/agentTestSandbox/parallel-test/src/components/common/Card/Card.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/components/common/Input/Input.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/components/common/Table/Table.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/components/features/Authentication/LoginForm.tsx +75 -0
- package/agentTestSandbox/parallel-test/src/components/features/Authentication/RegisterForm.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/components/features/Authentication/__tests__/LoginForm.test.tsx +101 -0
- package/agentTestSandbox/parallel-test/src/components/features/Dashboard/AnalyticsChart.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/components/features/Dashboard/DashboardStats.tsx +81 -0
- package/agentTestSandbox/parallel-test/src/components/features/Dashboard/__tests__/DashboardStats.test.tsx +122 -0
- package/agentTestSandbox/parallel-test/src/components/layouts/Header.tsx +70 -0
- package/agentTestSandbox/parallel-test/src/components/layouts/MainLayout.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/components/layouts/Sidebar.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/components/layouts/__tests__/MainLayout.test.tsx +65 -0
- package/agentTestSandbox/parallel-test/src/hooks/__tests__/useAuth.test.ts +75 -0
- package/agentTestSandbox/parallel-test/src/hooks/useApi.ts +0 -0
- package/agentTestSandbox/parallel-test/src/hooks/useAuth.ts +54 -0
- package/agentTestSandbox/parallel-test/src/hooks/useTheme.ts +0 -0
- package/agentTestSandbox/parallel-test/src/index.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/services/__tests__/api.service.test.ts +48 -0
- package/agentTestSandbox/parallel-test/src/services/analytics.service.ts +0 -0
- package/agentTestSandbox/parallel-test/src/services/api.service.ts +59 -0
- package/agentTestSandbox/parallel-test/src/services/api.ts +0 -0
- package/agentTestSandbox/parallel-test/src/services/auth.service.ts +0 -0
- package/agentTestSandbox/parallel-test/src/services/user.service.ts +0 -0
- package/agentTestSandbox/parallel-test/src/store/__tests__/store.test.ts +60 -0
- package/agentTestSandbox/parallel-test/src/store/index.ts +23 -0
- package/agentTestSandbox/parallel-test/src/store/slices/authSlice.ts +0 -0
- package/agentTestSandbox/parallel-test/src/store/slices/dashboardSlice.ts +0 -0
- package/agentTestSandbox/parallel-test/src/store/slices/userSlice.ts +0 -0
- package/agentTestSandbox/parallel-test/src/types/auth.types.ts +0 -0
- package/agentTestSandbox/parallel-test/src/types/dashboard.types.ts +0 -0
- package/agentTestSandbox/parallel-test/src/types/user.types.ts +0 -0
- package/agentTestSandbox/parallel-test/src/utils/constants.ts +0 -0
- package/agentTestSandbox/parallel-test/src/utils/formatters.ts +0 -0
- package/agentTestSandbox/parallel-test/src/utils/validation.ts +0 -0
- package/agentTestSandbox/parallel-test/src/views/Dashboard.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/views/Login.tsx +31 -0
- package/agentTestSandbox/parallel-test/src/views/Profile.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/views/Register.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/views/Settings.tsx +0 -0
- package/agentTestSandbox/parallel-test/src/views/__tests__/Login.test.tsx +62 -0
- package/agentTestSandbox/parallel-test/src/vite-env.d.ts +1 -0
- package/agentTestSandbox/parallel-test/tailwind.config.js +0 -0
- package/agentTestSandbox/parallel-test/tests/integration/api/auth.test.ts +120 -0
- package/agentTestSandbox/parallel-test/tests/unit/components/Button.test.tsx +35 -0
- package/agentTestSandbox/parallel-test/tests/unit/config/jest.config.test.js +62 -0
- package/agentTestSandbox/parallel-test/tests/unit/config/jest.setup.test.js +52 -0
- package/agentTestSandbox/parallel-test/tests/unit/infrastructure/__tests__/docker-config.test.ts +107 -0
- package/agentTestSandbox/parallel-test/tsconfig.json +0 -0
- package/agentTestSandbox/zone2/Makefile +58 -0
- package/agentTestSandbox/zone2/README.md +0 -0
- package/agentTestSandbox/zone2/docs/API.md +0 -0
- package/agentTestSandbox/zone2/docs/CONTRIBUTING.md +0 -0
- package/agentTestSandbox/zone2/imlil.blueprint.json +5 -0
- package/agentTestSandbox/zone2/notes-warning.md +3 -0
- package/agentTestSandbox/zone2/src/calculator.c +0 -0
- package/agentTestSandbox/zone2/src/calculator.h +0 -0
- package/agentTestSandbox/zone2/src/core/__tests__/test_memory.c +89 -0
- package/agentTestSandbox/zone2/src/core/memory.c +60 -0
- package/agentTestSandbox/zone2/src/display.c +0 -0
- package/agentTestSandbox/zone2/src/display.h +0 -0
- package/agentTestSandbox/zone2/src/input_handler.c +0 -0
- package/agentTestSandbox/zone2/src/input_handler.h +0 -0
- package/agentTestSandbox/zone2/src/main.c +0 -0
- package/agentTestSandbox/zone2/src/utils/error_handling.c +0 -0
- package/agentTestSandbox/zone2/src/utils/error_handling.h +0 -0
- package/agentTestSandbox/zone2/src/utils/input.c +0 -0
- package/agentTestSandbox/zone2/src/utils/input.h +0 -0
- package/agentTestSandbox/zone2/src/utils/math_utils.c +0 -0
- package/agentTestSandbox/zone2/src/utils/math_utils.h +0 -0
- package/agentTestSandbox/zone2/src/utils/stack.c +0 -0
- package/agentTestSandbox/zone2/src/utils/stack.h +0 -0
- package/agentTestSandbox/zone2/src/utils.c +34 -0
- package/agentTestSandbox/zone2/tests/__tests__/test_makefile.c +58 -0
- package/agentTestSandbox/zone2/tests/calculator_tests.c +0 -0
- package/agentTestSandbox/zone2/tests/input_handler_tests.c +0 -0
- package/agentTestSandbox/zone2/tests/math_utils_tests.c +0 -0
- package/agentTestSandbox/zone2/tests/test_calculator.c +0 -0
- package/agentTestSandbox/zone2/tests/test_input.c +0 -0
- package/agentTestSandbox/zone2/tests/test_stack.c +0 -0
- package/agentTestSandbox/zone2/tests/test_utils.c +8 -0
- package/bin/cli.js +369 -0
- package/imlil.config.js +22 -0
- package/index.js +0 -0
- package/jest.config.js +5 -0
- package/package.json +45 -0
- package/src/__tests__/cli.test.js +5 -0
- package/src/actions/Action.js +125 -0
- package/src/agents/Agent.js +64 -0
- package/src/agents/Operator.js +147 -0
- package/src/agents/ScrumAgent.js +74 -0
- package/src/agents/SupervisorAgent.js +198 -0
- package/src/agents/ValidatorAgent.js +48 -0
- package/src/agents/coder.js +208 -0
- package/src/agents/worker.js +52 -0
- package/src/utils/db.js +40 -0
- package/src/utils/embedapi.js +19 -0
- package/test-api.js +24 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
describe('Project Configuration Files', () => {
|
|
5
|
+
describe('package.json', () => {
|
|
6
|
+
let packageJson: any;
|
|
7
|
+
|
|
8
|
+
beforeAll(() => {
|
|
9
|
+
packageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf8'));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('should have required fields', () => {
|
|
13
|
+
expect(packageJson).toHaveProperty('name');
|
|
14
|
+
expect(packageJson).toHaveProperty('version');
|
|
15
|
+
expect(packageJson).toHaveProperty('scripts');
|
|
16
|
+
expect(packageJson).toHaveProperty('dependencies');
|
|
17
|
+
expect(packageJson).toHaveProperty('devDependencies');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('should have essential development scripts', () => {
|
|
21
|
+
expect(packageJson.scripts).toHaveProperty('start');
|
|
22
|
+
expect(packageJson.scripts).toHaveProperty('build');
|
|
23
|
+
expect(packageJson.scripts).toHaveProperty('test');
|
|
24
|
+
expect(packageJson.scripts).toHaveProperty('lint');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('.gitignore', () => {
|
|
29
|
+
let gitignore: string;
|
|
30
|
+
|
|
31
|
+
beforeAll(() => {
|
|
32
|
+
gitignore = fs.readFileSync(path.resolve(__dirname, '../../.gitignore'), 'utf8');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('should contain essential ignore patterns', () => {
|
|
36
|
+
const requiredPatterns = [
|
|
37
|
+
'node_modules',
|
|
38
|
+
'dist',
|
|
39
|
+
'build',
|
|
40
|
+
'.env',
|
|
41
|
+
'coverage',
|
|
42
|
+
'.DS_Store'
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
requiredPatterns.forEach(pattern => {
|
|
46
|
+
expect(gitignore).toMatch(new RegExp(pattern, 'i'));
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('.env.example', () => {
|
|
52
|
+
let envExample: string;
|
|
53
|
+
|
|
54
|
+
beforeAll(() => {
|
|
55
|
+
envExample = fs.readFileSync(path.resolve(__dirname, '../../.env.example'), 'utf8');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('should contain required environment variables', () => {
|
|
59
|
+
const requiredVars = [
|
|
60
|
+
'NODE_ENV',
|
|
61
|
+
'PORT',
|
|
62
|
+
'DATABASE_URL',
|
|
63
|
+
'REDIS_URL',
|
|
64
|
+
'JWT_SECRET'
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
requiredVars.forEach(variable => {
|
|
68
|
+
expect(envExample).toMatch(new RegExp(`^${variable}=`, 'm'));
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('.eslintrc.json', () => {
|
|
74
|
+
let eslintConfig: any;
|
|
75
|
+
|
|
76
|
+
beforeAll(() => {
|
|
77
|
+
eslintConfig = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../.eslintrc.json'), 'utf8'));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('should have essential ESLint configuration', () => {
|
|
81
|
+
expect(eslintConfig).toHaveProperty('extends');
|
|
82
|
+
expect(eslintConfig.extends).toContain('airbnb');
|
|
83
|
+
expect(eslintConfig).toHaveProperty('rules');
|
|
84
|
+
expect(eslintConfig).toHaveProperty('env');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('.prettierrc', () => {
|
|
89
|
+
let prettierConfig: any;
|
|
90
|
+
|
|
91
|
+
beforeAll(() => {
|
|
92
|
+
prettierConfig = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../.prettierrc'), 'utf8'));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('should have standard Prettier configuration', () => {
|
|
96
|
+
expect(prettierConfig).toHaveProperty('semi');
|
|
97
|
+
expect(prettierConfig).toHaveProperty('singleQuote');
|
|
98
|
+
expect(prettierConfig).toHaveProperty('tabWidth');
|
|
99
|
+
expect(prettierConfig).toHaveProperty('printWidth');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('tsconfig.json', () => {
|
|
104
|
+
let tsConfig: any;
|
|
105
|
+
|
|
106
|
+
beforeAll(() => {
|
|
107
|
+
tsConfig = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../tsconfig.json'), 'utf8'));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('should have essential TypeScript configuration', () => {
|
|
111
|
+
expect(tsConfig).toHaveProperty('compilerOptions');
|
|
112
|
+
expect(tsConfig.compilerOptions).toHaveProperty('target');
|
|
113
|
+
expect(tsConfig.compilerOptions).toHaveProperty('module');
|
|
114
|
+
expect(tsConfig.compilerOptions).toHaveProperty('strict');
|
|
115
|
+
expect(tsConfig.compilerOptions).toHaveProperty('esModuleInterop');
|
|
116
|
+
expect(tsConfig.compilerOptions).toHaveProperty('skipLibCheck');
|
|
117
|
+
expect(tsConfig.compilerOptions).toHaveProperty('forceConsistentCasingInFileNames');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('should include and exclude appropriate paths', () => {
|
|
121
|
+
expect(tsConfig).toHaveProperty('include');
|
|
122
|
+
expect(tsConfig).toHaveProperty('exclude');
|
|
123
|
+
expect(tsConfig.include).toContain('src/**/*');
|
|
124
|
+
expect(tsConfig.exclude).toContain('node_modules');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import ReactDOM from 'react-dom/client';
|
|
4
|
+
import App from '../App';
|
|
5
|
+
|
|
6
|
+
// Mock ReactDOM
|
|
7
|
+
jest.mock('react-dom/client', () => ({
|
|
8
|
+
createRoot: jest.fn(() => ({
|
|
9
|
+
render: jest.fn(),
|
|
10
|
+
})),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe('Application Entry Point', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
// Clear the document body before each test
|
|
16
|
+
document.body.innerHTML = '';
|
|
17
|
+
// Create root element
|
|
18
|
+
const root = document.createElement('div');
|
|
19
|
+
root.id = 'root';
|
|
20
|
+
document.body.appendChild(root);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should render the App component in the root element', () => {
|
|
24
|
+
// Import the index file
|
|
25
|
+
require('../index');
|
|
26
|
+
|
|
27
|
+
// Verify that createRoot was called with the root element
|
|
28
|
+
expect(ReactDOM.createRoot).toHaveBeenCalledWith(
|
|
29
|
+
document.getElementById('root')
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Verify that render was called with the App component
|
|
33
|
+
const root = ReactDOM.createRoot(document.getElementById('root'));
|
|
34
|
+
expect(root.render).toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
describe('Jest Configuration', () => {
|
|
4
|
+
// Test if Jest is properly installed and configured
|
|
5
|
+
test('Jest should be properly configured', () => {
|
|
6
|
+
expect(jest).toBeDefined();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// Test if the test environment is working
|
|
10
|
+
test('Test environment should be properly set up', () => {
|
|
11
|
+
const sum = (a: number, b: number) => a + b;
|
|
12
|
+
expect(sum(1, 2)).toBe(3);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Test if test runner can handle async operations
|
|
16
|
+
test('Async operations should work', async () => {
|
|
17
|
+
const asyncFunction = async () => 'test';
|
|
18
|
+
const result = await asyncFunction();
|
|
19
|
+
expect(result).toBe('test');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Test if TypeScript configuration is working
|
|
23
|
+
test('TypeScript configuration should work', () => {
|
|
24
|
+
interface TestInterface {
|
|
25
|
+
value: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const testObject: TestInterface = {
|
|
29
|
+
value: 'test'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
expect(testObject.value).toBe('test');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
describe('Jest Testing Environment Setup', () => {
|
|
4
|
+
test('Jest is properly configured', () => {
|
|
5
|
+
expect(true).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test('Testing environment has required globals', () => {
|
|
9
|
+
expect(typeof describe).toBe('function');
|
|
10
|
+
expect(typeof test).toBe('function');
|
|
11
|
+
expect(typeof expect).toBe('function');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Test that we can import React components
|
|
15
|
+
test('React environment is properly set up', () => {
|
|
16
|
+
const React = require('react');
|
|
17
|
+
expect(React).toBeDefined();
|
|
18
|
+
expect(typeof React.createElement).toBe('function');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Test basic DOM manipulation capabilities
|
|
22
|
+
test('JSDOM environment is properly configured', () => {
|
|
23
|
+
document.body.innerHTML = '<div id="test">Test</div>';
|
|
24
|
+
const element = document.getElementById('test');
|
|
25
|
+
expect(element).not.toBeNull();
|
|
26
|
+
expect(element?.textContent).toBe('Test');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Test that we can handle async operations
|
|
30
|
+
test('Async testing capabilities work', async () => {
|
|
31
|
+
const asyncFunction = () => Promise.resolve('success');
|
|
32
|
+
const result = await asyncFunction();
|
|
33
|
+
expect(result).toBe('success');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Test TypeScript compilation
|
|
37
|
+
test('TypeScript types are properly recognized', () => {
|
|
38
|
+
interface TestInterface {
|
|
39
|
+
prop: string;
|
|
40
|
+
}
|
|
41
|
+
const testObject: TestInterface = { prop: 'test' };
|
|
42
|
+
expect(testObject.prop).toBe('test');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { ButtonHTMLAttributes } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
|
|
4
|
+
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
5
|
+
variant?: 'primary' | 'secondary' | 'outline' | 'danger';
|
|
6
|
+
size?: 'sm' | 'md' | 'lg';
|
|
7
|
+
isLoading?: boolean;
|
|
8
|
+
fullWidth?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const Button: React.FC<ButtonProps> = ({
|
|
12
|
+
children,
|
|
13
|
+
className,
|
|
14
|
+
variant = 'primary',
|
|
15
|
+
size = 'md',
|
|
16
|
+
isLoading = false,
|
|
17
|
+
fullWidth = false,
|
|
18
|
+
disabled,
|
|
19
|
+
...props
|
|
20
|
+
}) => {
|
|
21
|
+
const baseStyles = 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2';
|
|
22
|
+
|
|
23
|
+
const variants = {
|
|
24
|
+
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
|
|
25
|
+
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500',
|
|
26
|
+
outline: 'border-2 border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-gray-500',
|
|
27
|
+
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const sizes = {
|
|
31
|
+
sm: 'px-3 py-1.5 text-sm',
|
|
32
|
+
md: 'px-4 py-2 text-base',
|
|
33
|
+
lg: 'px-6 py-3 text-lg',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const buttonClasses = classNames(
|
|
37
|
+
baseStyles,
|
|
38
|
+
variants[variant],
|
|
39
|
+
sizes[size],
|
|
40
|
+
{
|
|
41
|
+
'opacity-75 cursor-not-allowed': disabled || isLoading,
|
|
42
|
+
'w-full': fullWidth,
|
|
43
|
+
},
|
|
44
|
+
className
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<button
|
|
49
|
+
className={buttonClasses}
|
|
50
|
+
disabled={disabled || isLoading}
|
|
51
|
+
{...props}
|
|
52
|
+
>
|
|
53
|
+
{isLoading ? (
|
|
54
|
+
<svg
|
|
55
|
+
className="animate-spin -ml-1 mr-2 h-4 w-4 text-current"
|
|
56
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
57
|
+
fill="none"
|
|
58
|
+
viewBox="0 0 24 24"
|
|
59
|
+
>
|
|
60
|
+
<circle
|
|
61
|
+
className="opacity-25"
|
|
62
|
+
cx="12"
|
|
63
|
+
cy="12"
|
|
64
|
+
r="10"
|
|
65
|
+
stroke="currentColor"
|
|
66
|
+
strokeWidth="4"
|
|
67
|
+
/>
|
|
68
|
+
<path
|
|
69
|
+
className="opacity-75"
|
|
70
|
+
fill="currentColor"
|
|
71
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
72
|
+
/>
|
|
73
|
+
</svg>
|
|
74
|
+
) : null}
|
|
75
|
+
{children}
|
|
76
|
+
</button>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default Button;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import Button from '../Button';
|
|
5
|
+
|
|
6
|
+
describe('Button Component', () => {
|
|
7
|
+
// Basic rendering tests
|
|
8
|
+
it('renders button with correct text', () => {
|
|
9
|
+
render(<Button>Click me</Button>);
|
|
10
|
+
expect(screen.getByText('Click me')).toBeInTheDocument();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Props tests
|
|
14
|
+
it('applies variant classes correctly', () => {
|
|
15
|
+
const { rerender } = render(<Button variant="primary">Primary Button</Button>);
|
|
16
|
+
expect(screen.getByRole('button')).toHaveClass('btn-primary');
|
|
17
|
+
|
|
18
|
+
rerender(<Button variant="secondary">Secondary Button</Button>);
|
|
19
|
+
expect(screen.getByRole('button')).toHaveClass('btn-secondary');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('applies size classes correctly', () => {
|
|
23
|
+
const { rerender } = render(<Button size="sm">Small Button</Button>);
|
|
24
|
+
expect(screen.getByRole('button')).toHaveClass('btn-sm');
|
|
25
|
+
|
|
26
|
+
rerender(<Button size="lg">Large Button</Button>);
|
|
27
|
+
expect(screen.getByRole('button')).toHaveClass('btn-lg');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Disabled state test
|
|
31
|
+
it('handles disabled state correctly', () => {
|
|
32
|
+
render(<Button disabled>Disabled Button</Button>);
|
|
33
|
+
const button = screen.getByRole('button');
|
|
34
|
+
expect(button).toBeDisabled();
|
|
35
|
+
expect(button).toHaveClass('btn-disabled');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Click handler test
|
|
39
|
+
it('calls onClick handler when clicked', () => {
|
|
40
|
+
const handleClick = jest.fn();
|
|
41
|
+
render(<Button onClick={handleClick}>Click me</Button>);
|
|
42
|
+
|
|
43
|
+
fireEvent.click(screen.getByText('Click me'));
|
|
44
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Loading state test
|
|
48
|
+
it('shows loading state when isLoading prop is true', () => {
|
|
49
|
+
render(<Button isLoading>Loading Button</Button>);
|
|
50
|
+
expect(screen.getByRole('button')).toHaveClass('btn-loading');
|
|
51
|
+
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Full width test
|
|
55
|
+
it('applies full width class when fullWidth prop is true', () => {
|
|
56
|
+
render(<Button fullWidth>Full Width Button</Button>);
|
|
57
|
+
expect(screen.getByRole('button')).toHaveClass('w-full');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Icon button test
|
|
61
|
+
it('renders with icon correctly', () => {
|
|
62
|
+
render(
|
|
63
|
+
<Button startIcon={<span data-testid="start-icon">→</span>}>
|
|
64
|
+
Icon Button
|
|
65
|
+
</Button>
|
|
66
|
+
);
|
|
67
|
+
expect(screen.getByTestId('start-icon')).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Accessibility test
|
|
71
|
+
it('maintains accessibility attributes', () => {
|
|
72
|
+
render(<Button aria-label="Accessible Button">Click me</Button>);
|
|
73
|
+
expect(screen.getByRole('button')).toHaveAttribute('aria-label', 'Accessible Button');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
import { yupResolver } from '@hookform/resolvers/yup';
|
|
4
|
+
import * as yup from 'yup';
|
|
5
|
+
import { useDispatch } from 'react-redux';
|
|
6
|
+
import { Button } from '../../common/Button/Button';
|
|
7
|
+
import { Input } from '../../common/Input/Input';
|
|
8
|
+
import { useAuth } from '../../../hooks/useAuth';
|
|
9
|
+
import { login } from '../../../store/slices/authSlice';
|
|
10
|
+
|
|
11
|
+
interface LoginFormData {
|
|
12
|
+
email: string;
|
|
13
|
+
password: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const loginSchema = yup.object({
|
|
17
|
+
email: yup
|
|
18
|
+
.string()
|
|
19
|
+
.email('Please enter a valid email')
|
|
20
|
+
.required('Email is required'),
|
|
21
|
+
password: yup
|
|
22
|
+
.string()
|
|
23
|
+
.min(8, 'Password must be at least 8 characters')
|
|
24
|
+
.required('Password is required'),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const LoginForm: React.FC = () => {
|
|
28
|
+
const dispatch = useDispatch();
|
|
29
|
+
const { loginUser } = useAuth();
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
register,
|
|
33
|
+
handleSubmit,
|
|
34
|
+
formState: { errors, isSubmitting },
|
|
35
|
+
} = useForm<LoginFormData>({
|
|
36
|
+
resolver: yupResolver(loginSchema),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const onSubmit = async (data: LoginFormData) => {
|
|
40
|
+
try {
|
|
41
|
+
const response = await loginUser(data);
|
|
42
|
+
dispatch(login(response));
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Login failed:', error);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
|
50
|
+
<div>
|
|
51
|
+
<Input
|
|
52
|
+
type="email"
|
|
53
|
+
label="Email"
|
|
54
|
+
error={errors.email?.message}
|
|
55
|
+
{...register('email')}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
<div>
|
|
59
|
+
<Input
|
|
60
|
+
type="password"
|
|
61
|
+
label="Password"
|
|
62
|
+
error={errors.password?.message}
|
|
63
|
+
{...register('password')}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
<Button
|
|
67
|
+
type="submit"
|
|
68
|
+
disabled={isSubmitting}
|
|
69
|
+
className="w-full"
|
|
70
|
+
>
|
|
71
|
+
{isSubmitting ? 'Logging in...' : 'Login'}
|
|
72
|
+
</Button>
|
|
73
|
+
</form>
|
|
74
|
+
);
|
|
75
|
+
};
|
package/agentTestSandbox/parallel-test/src/components/features/Authentication/RegisterForm.tsx
ADDED
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
3
|
+
import { Provider } from 'react-redux';
|
|
4
|
+
import { createStore } from '@reduxjs/toolkit';
|
|
5
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
6
|
+
import LoginForm from '../LoginForm';
|
|
7
|
+
import { authSlice } from '../../../../store/slices/authSlice';
|
|
8
|
+
|
|
9
|
+
// Mock the auth service
|
|
10
|
+
jest.mock('../../../../services/auth.service', () => ({
|
|
11
|
+
login: jest.fn(() => Promise.resolve({ token: 'fake-token' }))
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const mockStore = createStore(authSlice.reducer);
|
|
15
|
+
|
|
16
|
+
const renderLoginForm = () => {
|
|
17
|
+
render(
|
|
18
|
+
<Provider store={mockStore}>
|
|
19
|
+
<BrowserRouter>
|
|
20
|
+
<LoginForm />
|
|
21
|
+
</BrowserRouter>
|
|
22
|
+
</Provider>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('LoginForm Component', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('renders login form with required fields', () => {
|
|
32
|
+
renderLoginForm();
|
|
33
|
+
|
|
34
|
+
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByRole('button', { name: /sign in/i })).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('displays validation errors for empty fields', async () => {
|
|
40
|
+
renderLoginForm();
|
|
41
|
+
|
|
42
|
+
const submitButton = screen.getByRole('button', { name: /sign in/i });
|
|
43
|
+
fireEvent.click(submitButton);
|
|
44
|
+
|
|
45
|
+
await waitFor(() => {
|
|
46
|
+
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
|
|
47
|
+
expect(screen.getByText(/password is required/i)).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('displays validation error for invalid email format', async () => {
|
|
52
|
+
renderLoginForm();
|
|
53
|
+
|
|
54
|
+
const emailInput = screen.getByLabelText(/email/i);
|
|
55
|
+
fireEvent.change(emailInput, { target: { value: 'invalid-email' } });
|
|
56
|
+
|
|
57
|
+
const submitButton = screen.getByRole('button', { name: /sign in/i });
|
|
58
|
+
fireEvent.click(submitButton);
|
|
59
|
+
|
|
60
|
+
await waitFor(() => {
|
|
61
|
+
expect(screen.getByText(/invalid email format/i)).toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('submits form with valid data', async () => {
|
|
66
|
+
renderLoginForm();
|
|
67
|
+
|
|
68
|
+
const emailInput = screen.getByLabelText(/email/i);
|
|
69
|
+
const passwordInput = screen.getByLabelText(/password/i);
|
|
70
|
+
const submitButton = screen.getByRole('button', { name: /sign in/i });
|
|
71
|
+
|
|
72
|
+
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
|
|
73
|
+
fireEvent.change(passwordInput, { target: { value: 'Password123!' } });
|
|
74
|
+
fireEvent.click(submitButton);
|
|
75
|
+
|
|
76
|
+
await waitFor(() => {
|
|
77
|
+
expect(screen.queryByText(/email is required/i)).not.toBeInTheDocument();
|
|
78
|
+
expect(screen.queryByText(/password is required/i)).not.toBeInTheDocument();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('displays error message on failed login', async () => {
|
|
83
|
+
const mockError = new Error('Invalid credentials');
|
|
84
|
+
jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
85
|
+
require('../../../../services/auth.service').login.mockRejectedValueOnce(mockError);
|
|
86
|
+
|
|
87
|
+
renderLoginForm();
|
|
88
|
+
|
|
89
|
+
const emailInput = screen.getByLabelText(/email/i);
|
|
90
|
+
const passwordInput = screen.getByLabelText(/password/i);
|
|
91
|
+
const submitButton = screen.getByRole('button', { name: /sign in/i });
|
|
92
|
+
|
|
93
|
+
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
|
|
94
|
+
fireEvent.change(passwordInput, { target: { value: 'Password123!' } });
|
|
95
|
+
fireEvent.click(submitButton);
|
|
96
|
+
|
|
97
|
+
await waitFor(() => {
|
|
98
|
+
expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSelector } from 'react-redux';
|
|
3
|
+
import { Card } from '@/components/common/Card/Card';
|
|
4
|
+
import { useApi } from '@/hooks/useApi';
|
|
5
|
+
import type { RootState } from '@/store';
|
|
6
|
+
import { formatNumber, formatCurrency } from '@/utils/formatters';
|
|
7
|
+
|
|
8
|
+
interface StatCardProps {
|
|
9
|
+
title: string;
|
|
10
|
+
value: string | number;
|
|
11
|
+
change: number;
|
|
12
|
+
icon: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const StatCard: React.FC<StatCardProps> = ({ title, value, change, icon }) => (
|
|
16
|
+
<Card className="p-6">
|
|
17
|
+
<div className="flex items-center justify-between">
|
|
18
|
+
<div>
|
|
19
|
+
<p className="text-gray-500 text-sm font-medium">{title}</p>
|
|
20
|
+
<h3 className="text-2xl font-bold mt-2">{value}</h3>
|
|
21
|
+
<p className={`text-sm mt-2 ${change >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
|
22
|
+
{change >= 0 ? '+' : ''}{change}% from last month
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
<div className="p-3 bg-blue-100 rounded-full">
|
|
26
|
+
{icon}
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</Card>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export const DashboardStats: React.FC = () => {
|
|
33
|
+
const { data: statsData, isLoading, error } = useApi('/api/dashboard/stats');
|
|
34
|
+
const { currentPeriod } = useSelector((state: RootState) => state.dashboard);
|
|
35
|
+
|
|
36
|
+
if (isLoading) return <div>Loading statistics...</div>;
|
|
37
|
+
if (error) return <div>Error loading statistics</div>;
|
|
38
|
+
|
|
39
|
+
const stats = [
|
|
40
|
+
{
|
|
41
|
+
title: 'Total Revenue',
|
|
42
|
+
value: formatCurrency(statsData?.revenue || 0),
|
|
43
|
+
change: statsData?.revenueChange || 0,
|
|
44
|
+
icon: <svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
45
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
46
|
+
</svg>
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
title: 'Active Users',
|
|
50
|
+
value: formatNumber(statsData?.activeUsers || 0),
|
|
51
|
+
change: statsData?.userChange || 0,
|
|
52
|
+
icon: <svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
53
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
|
54
|
+
</svg>
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
title: 'Conversion Rate',
|
|
58
|
+
value: `${statsData?.conversionRate || 0}%`,
|
|
59
|
+
change: statsData?.conversionChange || 0,
|
|
60
|
+
icon: <svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
61
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
|
|
62
|
+
</svg>
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
title: 'Average Order Value',
|
|
66
|
+
value: formatCurrency(statsData?.avgOrderValue || 0),
|
|
67
|
+
change: statsData?.avgOrderChange || 0,
|
|
68
|
+
icon: <svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
69
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" />
|
|
70
|
+
</svg>
|
|
71
|
+
}
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
76
|
+
{stats.map((stat, index) => (
|
|
77
|
+
<StatCard key={index} {...stat} />
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
};
|