Skip to content

Commit e1732c3

Browse files
DangDang
authored andcommitted
fix FE
1 parent c610366 commit e1732c3

File tree

32 files changed

+987
-172
lines changed

32 files changed

+987
-172
lines changed

VehicleShowroom/.env

66 Bytes
Binary file not shown.

VehicleShowroom/craco.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ module.exports = {
5353
return webpackConfig;
5454
},
5555
},
56+
devServer: (devServerConfig) => {
57+
devServerConfig.proxy = {
58+
'/api': {
59+
target: 'http://localhost:5010',
60+
changeOrigin: true,
61+
secure: false,
62+
},
63+
};
64+
return devServerConfig;
65+
},
5666
babel: {
5767
plugins: [
5868
// Add lazy loading plugin

VehicleShowroom/package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,16 @@
4545
"build": "react-scripts build",
4646
"test": "react-scripts test",
4747
"test:ci": "react-scripts test --coverage --watchAll=false --passWithNoTests",
48+
"test:api": "node ./scripts/test-api-connection.mjs",
49+
"dev": "npm run test:api && react-scripts start",
4850
"lint": "eslint src --ext .js,.jsx --max-warnings 0",
4951
"lint:fix": "eslint src --ext .js,.jsx --fix",
5052
"eject": "react-scripts eject",
5153
"predeploy": "npm run build",
5254
"deploy": "gh-pages -d build",
53-
"preinstall": "npx npm-force-resolutions"
55+
"preinstall": "npx npm-force-resolutions",
56+
"smoke": "node ./scripts/smoke.mjs",
57+
"predeploy:render": "npm run lint && npm run build && npm run smoke"
5458
},
5559
"resolutions": {
5660
"react-error-overlay": "6.0.9"
@@ -79,6 +83,7 @@
7983
"anymatch": "^3.1.3",
8084
"chokidar": "^3.6.0",
8185
"gh-pages": "^6.1.1",
82-
"micromatch": "^4.0.7"
86+
"micromatch": "^4.0.7",
87+
"node-fetch": "^3.3.2"
8388
}
8489
}

VehicleShowroom/public/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@
2929
<body>
3030
<noscript>You need to enable JavaScript to run this app.</noscript>
3131
<div id="root"></div>
32+
<script src="%PUBLIC_URL%/clear-sw.js"></script>
3233
</body>
3334
</html>
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import axios from 'axios';
2+
3+
const BASE = 'https://eproject3.onrender.com/api';
4+
5+
const SAMPLE_MODELS = [
6+
{
7+
name: 'Porsche 911',
8+
price: 120000,
9+
description: 'High-performance sports car',
10+
level: 1,
11+
},
12+
{
13+
name: '911 Carrera',
14+
price: 130000,
15+
description: 'Classic Porsche sports model',
16+
parentId: 'Porsche 911',
17+
level: 2,
18+
slug: '911-carrera',
19+
},
20+
{
21+
name: 'Mercedes-Benz S-Class',
22+
price: 110000,
23+
description: 'Luxury sedan',
24+
level: 1,
25+
},
26+
{
27+
name: 'S 500',
28+
price: 120000,
29+
description: 'Premium S-Class variant',
30+
parentId: 'Mercedes-Benz S-Class',
31+
level: 2,
32+
slug: 's-500',
33+
},
34+
{
35+
name: 'BMW 7 Series',
36+
price: 100000,
37+
description: 'Premium sedan',
38+
level: 1,
39+
},
40+
];
41+
42+
async function seedDatabase() {
43+
try {
44+
console.log('🌱 Seeding database...\n');
45+
46+
// 1. Login as admin
47+
console.log('1️⃣ Logging in as admin...');
48+
const loginRes = await axios.post(`${BASE}/auth/login`, {
49+
username: 'admin',
50+
password: 'Admin123!',
51+
});
52+
53+
const token = loginRes.data?.accessToken || loginRes.data?.token;
54+
if (!token) {
55+
throw new Error('Login failed: no token returned');
56+
}
57+
console.log('✅ Login successful\n');
58+
59+
const headers = { Authorization: `Bearer ${token}` };
60+
61+
// 2. Check if models exist
62+
console.log('2️⃣ Checking existing vehicle models...');
63+
const listRes = await axios.get(`${BASE}/VehicleModels?pageSize=1`);
64+
const existingCount = listRes.data?.totalCount || 0;
65+
66+
if (existingCount > 0) {
67+
console.log(`ℹ️ Database already has ${existingCount} model(s). Skipping creation.\n`);
68+
} else {
69+
console.log('3️⃣ Creating sample vehicle models...');
70+
let createdCount = 0;
71+
for (const model of SAMPLE_MODELS) {
72+
try {
73+
await axios.post(`${BASE}/VehicleModels`, model, { headers });
74+
createdCount++;
75+
console.log(` ✅ Created: ${model.name}`);
76+
} catch (err) {
77+
console.warn(` ⚠️ Failed to create ${model.name}:`, err.response?.data?.message || err.message);
78+
}
79+
}
80+
console.log(`\n✅ Created ${createdCount}/${SAMPLE_MODELS.length} models\n`);
81+
}
82+
83+
// 3. Run vehicle models migration
84+
console.log('4️⃣ Running vehicle models migration...');
85+
const migRes = await axios.post(
86+
`${BASE}/migrations/vehicle-models-v2`,
87+
{},
88+
{ headers }
89+
);
90+
console.log('✅ Migration result:', migRes.data, '\n');
91+
92+
console.log('🎉 Database seeding completed!');
93+
} catch (err) {
94+
console.error('❌ Seeding failed:', err.response?.data || err.message);
95+
process.exit(1);
96+
}
97+
}
98+
99+
seedDatabase();

VehicleShowroom/scripts/smoke.mjs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import axios from 'axios';
2+
3+
const BASE = process.env.REACT_APP_API_URL || process.env.API_URL || 'http://localhost:5010/api';
4+
5+
async function main() {
6+
const failures = [];
7+
8+
const tryCall = async (name, fn) => {
9+
try {
10+
await fn();
11+
console.log(`[OK] ${name}`);
12+
} catch (e) {
13+
console.error(`[FAIL] ${name}:`, e.response?.status, e.response?.data || e.message);
14+
failures.push(name);
15+
}
16+
};
17+
18+
// Optional: attempt login if env creds provided
19+
const { SMOKE_USER, SMOKE_PASS } = process.env;
20+
let token = null;
21+
if (SMOKE_USER && SMOKE_PASS) {
22+
await tryCall('auth/login', async () => {
23+
const res = await axios.post(`${BASE}/auth/login`, { username: SMOKE_USER, password: SMOKE_PASS });
24+
token = res.data?.accessToken || res.data?.token || null;
25+
});
26+
}
27+
28+
const client = axios.create({ baseURL: BASE, headers: token ? { Authorization: `Bearer ${token}` } : {} });
29+
30+
await tryCall('VehicleModels list', async () => {
31+
await client.get('/VehicleModels?pageSize=1');
32+
});
33+
34+
if (token) {
35+
await tryCall('Orders list', async () => {
36+
await client.get('/Orders?pageSize=1');
37+
});
38+
} else {
39+
console.log('[SKIP] Orders list (no auth token provided)');
40+
}
41+
42+
if (failures.length) {
43+
console.error(`Smoke failed: ${failures.join(', ')}`);
44+
process.exit(1);
45+
} else {
46+
console.log('Smoke passed');
47+
}
48+
}
49+
50+
main();
51+
52+
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* API Connection Test Script
5+
* Tests backend connectivity and CORS configuration before starting the frontend
6+
*/
7+
8+
// Use built-in fetch for Node.js 18+ or fallback to node-fetch
9+
let fetch;
10+
try {
11+
// Try to use built-in fetch (Node.js 18+)
12+
fetch = globalThis.fetch;
13+
if (!fetch) {
14+
throw new Error('Built-in fetch not available');
15+
}
16+
} catch (e) {
17+
// Fallback to node-fetch for older Node.js versions
18+
const { default: nodeFetch } = await import('node-fetch');
19+
fetch = nodeFetch;
20+
}
21+
22+
const API_BASE_URL = process.env.REACT_APP_API_URL || 'https://eproject3.onrender.com/api';
23+
24+
const tests = [
25+
{
26+
name: 'Health Check',
27+
url: `${API_BASE_URL.replace('/api', '')}/health`,
28+
method: 'GET',
29+
expectStatus: 200,
30+
},
31+
{
32+
name: 'Vehicle Models (Anonymous)',
33+
url: `${API_BASE_URL}/VehicleModels?pageNumber=1&pageSize=5`,
34+
method: 'GET',
35+
expectStatus: 200,
36+
checkCors: true,
37+
},
38+
{
39+
name: 'Auth Login Endpoint',
40+
url: `${API_BASE_URL}/auth/login`,
41+
method: 'POST',
42+
expectStatus: 400, // Should return 400 for missing body, not 404
43+
body: {},
44+
},
45+
];
46+
47+
async function testEndpoint(test) {
48+
console.log(`\n🧪 Testing: ${test.name}`);
49+
console.log(` URL: ${test.url}`);
50+
console.log(` Method: ${test.method}`);
51+
52+
try {
53+
const options = {
54+
method: test.method,
55+
headers: {
56+
'Content-Type': 'application/json',
57+
'Origin': 'http://localhost:3000', // Simulate frontend origin
58+
},
59+
};
60+
61+
if (test.body) {
62+
options.body = JSON.stringify(test.body);
63+
}
64+
65+
const response = await fetch(test.url, options);
66+
67+
console.log(` Status: ${response.status} ${response.statusText}`);
68+
69+
// Check CORS headers
70+
if (test.checkCors) {
71+
const corsOrigin = response.headers.get('Access-Control-Allow-Origin');
72+
const corsMethods = response.headers.get('Access-Control-Allow-Methods');
73+
const corsHeaders = response.headers.get('Access-Control-Allow-Headers');
74+
75+
console.log(` CORS Origin: ${corsOrigin || 'NOT SET'}`);
76+
console.log(` CORS Methods: ${corsMethods || 'NOT SET'}`);
77+
console.log(` CORS Headers: ${corsHeaders || 'NOT SET'}`);
78+
79+
if (!corsOrigin) {
80+
console.log(' ❌ CORS not configured - requests from localhost will fail');
81+
return false;
82+
} else {
83+
console.log(' ✅ CORS configured');
84+
}
85+
}
86+
87+
// Check response status
88+
if (response.status === test.expectStatus) {
89+
console.log(` ✅ Status matches expected (${test.expectStatus})`);
90+
} else {
91+
console.log(` ⚠️ Status ${response.status} differs from expected ${test.expectStatus}`);
92+
}
93+
94+
// Try to parse response body for additional info
95+
try {
96+
const data = await response.text();
97+
if (data) {
98+
console.log(` Response preview: ${data.substring(0, 100)}${data.length > 100 ? '...' : ''}`);
99+
}
100+
} catch (e) {
101+
// Ignore parsing errors
102+
}
103+
104+
return true;
105+
106+
} catch (error) {
107+
console.log(` ❌ Network Error: ${error.message}`);
108+
109+
if (error.code === 'ENOTFOUND') {
110+
console.log(' 💡 Check if the backend URL is correct and accessible');
111+
} else if (error.code === 'ECONNREFUSED') {
112+
console.log(' 💡 Backend server might be down or not accessible');
113+
}
114+
115+
return false;
116+
}
117+
}
118+
119+
async function runTests() {
120+
console.log('🚀 Starting API Connection Tests');
121+
console.log(`📡 API Base URL: ${API_BASE_URL}`);
122+
console.log('=' .repeat(60));
123+
124+
let passedTests = 0;
125+
let totalTests = tests.length;
126+
127+
for (const test of tests) {
128+
const passed = await testEndpoint(test);
129+
if (passed) passedTests++;
130+
131+
// Small delay between tests
132+
await new Promise(resolve => setTimeout(resolve, 500));
133+
}
134+
135+
console.log('\n' + '=' .repeat(60));
136+
console.log(`📊 Test Results: ${passedTests}/${totalTests} tests passed`);
137+
138+
if (passedTests === totalTests) {
139+
console.log('✅ All tests passed! Backend is ready for frontend connection.');
140+
process.exit(0);
141+
} else {
142+
console.log('❌ Some tests failed. Check backend configuration and connectivity.');
143+
console.log('\n💡 Common issues:');
144+
console.log(' - Backend server not running');
145+
console.log(' - CORS not configured for localhost:3000');
146+
console.log(' - Wrong API URL in environment variables');
147+
console.log(' - Network connectivity issues');
148+
process.exit(1);
149+
}
150+
}
151+
152+
// Handle unhandled promise rejections
153+
process.on('unhandledRejection', (reason, promise) => {
154+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
155+
process.exit(1);
156+
});
157+
158+
runTests().catch(error => {
159+
console.error('Test runner failed:', error);
160+
process.exit(1);
161+
});

VehicleShowroom/src/api/ApiClient.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import axios from 'axios';
22
import qs from 'qs';
33
import AuthService from 'services/AuthService';
4-
5-
const BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8090/api';
4+
import { BASE_URL } from 'constants/ApiBaseUrl';
65

76
// ✅ Serialize query param (color=red&color=blue)
87
const paramsSerializer = (params) =>

0 commit comments

Comments
 (0)