diff --git a/.cursor/rules/instructions.mdc b/.cursor/rules/instructions.mdc
new file mode 100644
index 0000000..b81a943
--- /dev/null
+++ b/.cursor/rules/instructions.mdc
@@ -0,0 +1,15 @@
+---
+description:
+globs:
+alwaysApply: true
+---
+# Description
+
+This a fullstack nextjs project, all written in mordern and state of the art typescript.
+The Project follows the latest coding standards for next js projects
+
+# Modules
+
+This Project will use as less modules and node packages as possible. This leads to the following rules:
+- No Tailwind
+- No SCSS / SASS
\ No newline at end of file
diff --git a/.cursor/rules/project.mdc b/.cursor/rules/project.mdc
new file mode 100644
index 0000000..b356ec6
--- /dev/null
+++ b/.cursor/rules/project.mdc
@@ -0,0 +1,14 @@
+---
+description:
+globs:
+alwaysApply: true
+---
+# EWIKI
+
+The EWIKI is a eletric vehicle database, that allows the user to search and compare any information about eletric vehicles.
+
+# Structure
+
+## Start page
+
+The start page looks like a modern web search engine. There are no fancy input and filter options, just a regular search bar
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index f76ade0..3f407fe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "evwiki",
"version": "0.1.0",
"dependencies": {
+ "@heroicons/react": "^2.2.0",
"next": "15.3.2",
"react": "^19.0.0",
"react-dom": "^19.0.0"
@@ -196,6 +197,15 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@heroicons/react": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
+ "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">= 16 || ^19.0.0-rc"
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
diff --git a/package.json b/package.json
index 75556b8..4a961dc 100644
--- a/package.json
+++ b/package.json
@@ -9,17 +9,18 @@
"lint": "next lint"
},
"dependencies": {
+ "@heroicons/react": "^2.2.0",
+ "next": "15.3.2",
"react": "^19.0.0",
- "react-dom": "^19.0.0",
- "next": "15.3.2"
+ "react-dom": "^19.0.0"
},
"devDependencies": {
- "typescript": "^5",
+ "@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.3.2",
- "@eslint/eslintrc": "^3"
+ "typescript": "^5"
}
}
diff --git a/src/app/api/cars/brand/[id]/route.ts b/src/app/api/cars/brand/[id]/route.ts
new file mode 100644
index 0000000..31b6bb0
--- /dev/null
+++ b/src/app/api/cars/brand/[id]/route.ts
@@ -0,0 +1,22 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { CarRepository } from '../../../../../backend/repositories/CarRepository';
+
+const carRepository = new CarRepository();
+
+export async function GET(
+ request: NextRequest,
+ { params }: { params: { id: string } }
+) {
+ const brandId = params.id;
+
+ try {
+ const carModels = await carRepository.getCarModelsByBrandId(brandId);
+ return NextResponse.json({ models: carModels, revisions: [] });
+ } catch (error) {
+ console.error(`Error fetching car models for brand ${brandId}:`, error);
+ return NextResponse.json(
+ { error: 'Failed to fetch car models' },
+ { status: 500 }
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/cars/brands/route.ts b/src/app/api/cars/brands/route.ts
new file mode 100644
index 0000000..d9ac35a
--- /dev/null
+++ b/src/app/api/cars/brands/route.ts
@@ -0,0 +1,17 @@
+import { NextResponse } from 'next/server';
+import { CarRepository } from '../../../../backend/repositories/CarRepository';
+
+const carRepository = new CarRepository();
+
+export async function GET() {
+ try {
+ const brands = await carRepository.getAllBrands();
+ return NextResponse.json(brands);
+ } catch (error) {
+ console.error('Error fetching brands:', error);
+ return NextResponse.json(
+ { error: 'Failed to fetch brands' },
+ { status: 500 }
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/cars/search/route.ts b/src/app/api/cars/search/route.ts
new file mode 100644
index 0000000..c3cdb75
--- /dev/null
+++ b/src/app/api/cars/search/route.ts
@@ -0,0 +1,39 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { CarRepository } from '../../../../backend/repositories/CarRepository';
+
+const carRepository = new CarRepository();
+
+export async function GET(request: NextRequest) {
+ const searchParams = request.nextUrl.searchParams;
+ const query = searchParams.get('query') || '';
+ const category = searchParams.get('category') || '';
+ const startYear = searchParams.get('startYear') ? parseInt(searchParams.get('startYear')!) : 0;
+ const endYear = searchParams.get('endYear') ? parseInt(searchParams.get('endYear')!) : new Date().getFullYear();
+
+ try {
+ let results;
+
+ // If category is provided, search by category
+ if (category) {
+ const models = await carRepository.getCarsByCategory(category);
+ results = { models, revisions: [] };
+ }
+ // If startYear and endYear are provided (and not the default values), search by year range
+ else if (startYear > 0 || endYear < new Date().getFullYear()) {
+ const models = await carRepository.getCarsByYearRange(startYear, endYear);
+ results = { models, revisions: [] };
+ }
+ // Otherwise search by name (using query)
+ else {
+ results = await carRepository.searchCarsByName(query);
+ }
+
+ return NextResponse.json(results);
+ } catch (error) {
+ console.error('Search error:', error);
+ return NextResponse.json(
+ { error: 'Failed to search cars' },
+ { status: 500 }
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/app/components.css b/src/app/components.css
new file mode 100644
index 0000000..7f620e8
--- /dev/null
+++ b/src/app/components.css
@@ -0,0 +1,201 @@
+/* Components CSS - Modern simple design with rounded corners */
+
+/* Buttons */
+.btn {
+ display: inline-block;
+ padding: 0.6rem 1.2rem;
+ font-size: 1rem;
+ font-weight: 500;
+ text-align: center;
+ border: none;
+ border-radius: 0.5rem;
+ cursor: pointer;
+ transition: all 0.2s ease-in-out;
+}
+
+.btn-primary {
+ background-color: var(--color-yale-blue);
+ color: white;
+}
+
+.btn-primary:hover {
+ background-color: #0a4e96;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.btn-secondary {
+ background-color: var(--color-cerise);
+ color: white;
+}
+
+.btn-secondary:hover {
+ background-color: #e4547a;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.btn-outline {
+ background-color: transparent;
+ color: var(--color-yale-blue);
+ border: 1px solid var(--color-yale-blue);
+}
+
+.btn-outline:hover {
+ background-color: rgba(8, 61, 119, 0.05);
+}
+
+/* Typography */
+h1, h2, h3, h4, h5, h6 {
+ margin-bottom: 0.5rem;
+ font-weight: 600;
+ line-height: 1.2;
+ color: var(--foreground);
+}
+
+h1 {
+ font-size: 2.5rem;
+}
+
+h2 {
+ font-size: 2rem;
+}
+
+h3 {
+ font-size: 1.75rem;
+}
+
+h4 {
+ font-size: 1.5rem;
+}
+
+h5 {
+ font-size: 1.25rem;
+}
+
+h6 {
+ font-size: 1rem;
+}
+
+p {
+ margin-bottom: 1rem;
+ line-height: 1.5;
+}
+
+/* Links */
+.link {
+ color: var(--color-yale-blue);
+ text-decoration: none;
+ transition: color 0.2s;
+ border-bottom: 1px solid transparent;
+}
+
+.link:hover {
+ color: var(--color-cerise);
+ border-bottom: 1px solid var(--color-cerise);
+}
+
+/* Form Elements */
+.form-group {
+ margin-bottom: 1rem;
+}
+
+.form-label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+}
+
+.form-input {
+ display: block;
+ width: 100%;
+ padding: 0.6rem 0.8rem;
+ font-size: 1rem;
+ line-height: 1.5;
+ color: var(--foreground);
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 1px solid #ced4da;
+ border-radius: 0.5rem;
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+
+.form-input:focus {
+ color: var(--foreground);
+ background-color: #fff;
+ border-color: var(--color-yale-blue);
+ outline: 0;
+ box-shadow: 0 0 0 0.2rem rgba(8, 61, 119, 0.25);
+}
+
+.form-select {
+ display: block;
+ width: 100%;
+ padding: 0.6rem 2rem 0.6rem 0.8rem;
+ font-size: 1rem;
+ line-height: 1.5;
+ color: var(--foreground);
+ background-color: #fff;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23343a40' d='M6 8.5l4-4 1 1-5 5-5-5 1-1z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.8rem center;
+ background-size: 12px 12px;
+ border: 1px solid #ced4da;
+ border-radius: 0.5rem;
+ appearance: none;
+}
+
+.form-checkbox, .form-radio {
+ display: inline-block;
+ margin-right: 0.5rem;
+ cursor: pointer;
+}
+
+/* Card */
+.card {
+ background-color: white;
+ border-radius: 0.75rem;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.card-header {
+ margin-bottom: 1rem;
+ border-bottom: 1px solid #efefef;
+ padding-bottom: 0.75rem;
+}
+
+.card-title {
+ margin-bottom: 0.25rem;
+}
+
+.card-body {
+ padding: 0.5rem 0;
+}
+
+/* Badge */
+.badge {
+ display: inline-block;
+ padding: 0.25rem 0.5rem;
+ font-size: 0.75rem;
+ font-weight: 500;
+ line-height: 1;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ border-radius: 0.375rem;
+}
+
+.badge-primary {
+ background-color: var(--color-yale-blue);
+ color: white;
+}
+
+.badge-secondary {
+ background-color: var(--color-cerise);
+ color: white;
+}
+
+.badge-light {
+ background-color: var(--color-naples-yellow);
+ color: var(--color-charcoal);
+}
\ No newline at end of file
diff --git a/src/app/globals.css b/src/app/globals.css
index e3734be..d44bd94 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,13 +1,16 @@
:root {
- --background: #ffffff;
- --foreground: #171717;
-}
+ --color-sunset: #F6D8AE;
+ --color-charcoal: #2E4057;
+ --color-yale-blue: #083D77;
+ --color-cerise: #DA4167;
+ --color-naples-yellow: #F4D35E;
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
+ /* Neutral background colors */
+ --color-light-neutral: #F5F5F5;
+ --color-dark-neutral: #1F1F1F;
+
+ --background: var(--color-light-neutral);
+ --foreground: var(--color-charcoal);
}
html,
@@ -17,8 +20,8 @@ body {
}
body {
- color: var(--foreground);
background: var(--background);
+ color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -40,3 +43,8 @@ a {
color-scheme: dark;
}
}
+
+.button-primary {
+ background: var(--color-yale-blue);
+ color: #fff;
+}
diff --git a/src/app/home.module.css b/src/app/home.module.css
new file mode 100644
index 0000000..24eb7df
--- /dev/null
+++ b/src/app/home.module.css
@@ -0,0 +1,180 @@
+/* Home page specific styles */
+.container {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem;
+ background: linear-gradient(135deg, var(--background), rgba(8, 61, 119, 0.05));
+}
+
+.title {
+ font-size: 3.5rem;
+ font-weight: 800;
+ margin-bottom: 2.5rem;
+ text-align: center;
+ letter-spacing: -1px;
+ animation: fadeIn 0.8s ease-in-out;
+}
+
+.gradientText {
+ background: linear-gradient(90deg,
+ var(--color-yale-blue) 0%,
+ var(--color-charcoal) 35%,
+ var(--color-cerise) 100%);
+ -webkit-background-clip: text;
+ background-clip: text;
+ -webkit-text-fill-color: transparent;
+ display: inline-block;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(-20px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+.searchContainer {
+ width: 100%;
+ max-width: 42rem;
+ margin-bottom: 2.5rem;
+ animation: scaleIn 0.5s ease-in-out;
+ animation-delay: 0.3s;
+ animation-fill-mode: both;
+}
+
+@keyframes scaleIn {
+ from { opacity: 0; transform: scale(0.95); }
+ to { opacity: 1; transform: scale(1); }
+}
+
+.searchForm {
+ display: flex;
+ width: 100%;
+ position: relative;
+}
+
+.searchInput {
+ width: 100%;
+ padding: 1.2rem 1.5rem;
+ font-size: 1.2rem;
+ border: 2px solid transparent;
+ border-radius: 30px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ outline: none;
+ transition: all 0.3s ease;
+ background-color: white;
+ color: var(--color-charcoal);
+}
+
+.searchInput:focus {
+ box-shadow: 0 6px 16px rgba(8, 61, 119, 0.2);
+ border-color: var(--color-yale-blue);
+}
+
+.searchInput::placeholder {
+ color: rgba(46, 64, 87, 0.5);
+}
+
+.searchButton {
+ position: absolute;
+ right: 5px;
+ top: 5px;
+ bottom: 5px;
+ padding: 0 1.8rem;
+ background-color: var(--color-yale-blue);
+ color: white;
+ border: none;
+ border-radius: 25px;
+ font-size: 1.1rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.searchButton:hover {
+ background-color: var(--color-charcoal);
+}
+
+.card {
+ width: 100%;
+ max-width: 42rem;
+ background-color: white;
+ padding: 2rem;
+ border-radius: 0.5rem;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+.subtitle {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 1.5rem;
+ color: var(--foreground);
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.yearGrid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+}
+
+.brandSection {
+ margin-top: 2rem;
+ text-align: center;
+ color: var(--color-charcoal);
+ animation: fadeIn 0.5s ease-in-out;
+ animation-delay: 0.6s;
+ animation-fill-mode: both;
+ position: relative;
+}
+
+.brandSection::before {
+ content: '';
+ position: absolute;
+ top: -15px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 50px;
+ height: 3px;
+ background-color: var(--color-naples-yellow);
+ border-radius: 3px;
+}
+
+.brandSection p {
+ font-weight: 600;
+ margin-bottom: 0.8rem;
+ font-size: 1.1rem;
+}
+
+.brandList {
+ margin-top: 0.8rem;
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 1.2rem;
+}
+
+.brandLink {
+ color: var(--color-yale-blue);
+ text-decoration: none;
+ font-weight: 500;
+ padding: 0.5rem 1rem;
+ border-radius: 20px;
+ transition: all 0.2s ease;
+ background-color: white;
+ border: 1px solid rgba(8, 61, 119, 0.1);
+}
+
+.brandLink:hover {
+ background-color: var(--color-sunset);
+ color: var(--color-charcoal);
+}
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 42fc323..adb04af 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
+import "./components.css";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -13,8 +14,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "EV WIKI",
+ description: "Modern search engine for electric vehicle information",
};
export default function RootLayout({
diff --git a/src/app/page.module.css b/src/app/page.module.css
index a11c8f3..0670dbb 100644
--- a/src/app/page.module.css
+++ b/src/app/page.module.css
@@ -166,3 +166,306 @@ a.secondary {
filter: invert();
}
}
+
+.searchPage {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ align-items: center;
+ justify-content: space-between;
+ font-family: var(--font-geist-sans);
+ background: var(--background);
+ color: var(--foreground);
+}
+
+.searchMain {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ flex: 1;
+ width: 100%;
+ max-width: 800px;
+ padding: 0 24px;
+}
+
+.logoContainer {
+ margin-bottom: 36px;
+ text-align: center;
+}
+
+.logo {
+ font-size: 4rem;
+ font-weight: 700;
+ letter-spacing: -1px;
+ background: linear-gradient(to right, var(--color-yale-blue), var(--color-cerise));
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+ margin: 0;
+}
+
+.searchContainer {
+ width: 100%;
+ max-width: 600px;
+ margin-bottom: 24px;
+}
+
+.searchBox {
+ display: flex;
+ width: 100%;
+ position: relative;
+ border-radius: 24px;
+ background: #fff;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+ transition: box-shadow 0.3s ease;
+}
+
+.searchBox:hover, .searchBox:focus-within {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.searchInput {
+ flex: 1;
+ height: 48px;
+ padding: 0 16px;
+ font-size: 16px;
+ border: none;
+ outline: none;
+ background: transparent;
+ color: #333;
+}
+
+.searchButton {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 16px;
+ background: none;
+ border: none;
+ cursor: pointer;
+ color: var(--color-yale-blue);
+ transition: color 0.3s ease;
+}
+
+.searchButton:hover {
+ color: var(--color-cerise);
+}
+
+.searchIcon {
+ width: 24px;
+ height: 24px;
+}
+
+.suggestionsContainer {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 8px;
+ margin-top: 12px;
+}
+
+.suggestionChip {
+ padding: 8px 16px;
+ background: rgba(8, 61, 119, 0.08);
+ border: 1px solid rgba(8, 61, 119, 0.15);
+ border-radius: 20px;
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--foreground);
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.suggestionChip:hover {
+ background: var(--color-yale-blue);
+ color: white;
+ border-color: var(--color-yale-blue);
+}
+
+.searchFooter {
+ width: 100%;
+ padding: 16px 0;
+ border-top: 1px solid rgba(var(--gray-rgb), 0.1);
+ font-size: 14px;
+}
+
+.footerLinks {
+ display: flex;
+ justify-content: center;
+ gap: 24px;
+}
+
+.footerLinks a {
+ color: var(--foreground);
+ opacity: 0.8;
+ transition: opacity 0.2s ease;
+}
+
+.footerLinks a:hover {
+ opacity: 1;
+ text-decoration: underline;
+}
+
+@media (max-width: 600px) {
+ .logo {
+ font-size: 3rem;
+ }
+
+ .searchBox {
+ border-radius: 20px;
+ }
+
+ .searchInput {
+ height: 44px;
+ }
+
+ .suggestionsContainer {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .suggestionChip {
+ width: 100%;
+ max-width: 280px;
+ text-align: center;
+ }
+
+ .footerLinks {
+ flex-wrap: wrap;
+ gap: 16px;
+ justify-content: center;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ .searchBox {
+ background: rgba(255, 255, 255, 0.05);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+ }
+
+ .searchBox:hover, .searchBox:focus-within {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+ }
+
+ .searchInput {
+ color: var(--foreground);
+ }
+
+ .searchButton {
+ color: var(--color-yale-blue);
+ }
+
+ .searchButton:hover {
+ color: var(--color-cerise);
+ }
+
+ .suggestionChip {
+ background: rgba(255, 255, 255, 0.07);
+ border: 1px solid rgba(255, 255, 255, 0.15);
+ }
+}
+
+.pageContainer {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem;
+ background-color: #f9fafb;
+}
+
+.mainTitle {
+ font-size: 2.5rem;
+ font-weight: bold;
+ margin-bottom: 2rem;
+}
+
+.searchCard {
+ width: 100%;
+ max-width: 40rem;
+ background-color: white;
+ padding: 2rem;
+ border-radius: 0.5rem;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+.cardTitle {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 1.5rem;
+}
+
+.formGroup {
+ margin-bottom: 1rem;
+}
+
+.label {
+ display: block;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: #4b5563;
+ margin-bottom: 0.25rem;
+}
+
+.input,
+.select {
+ width: 100%;
+ padding: 0.5rem 1rem;
+ border: 1px solid #d1d5db;
+ border-radius: 0.375rem;
+ font-size: 1rem;
+}
+
+.input:focus,
+.select:focus {
+ outline: none;
+ border-color: #3b82f6;
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+}
+
+.yearGrid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+}
+
+.button {
+ width: 100%;
+ background-color: #3b82f6;
+ color: white;
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 0.375rem;
+ font-size: 1rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.button:hover {
+ background-color: #2563eb;
+}
+
+.brandSection {
+ margin-top: 1.5rem;
+ text-align: center;
+ color: #6b7280;
+}
+
+.brandList {
+ margin-top: 0.5rem;
+ display: flex;
+ justify-content: center;
+ gap: 1rem;
+}
+
+.brandLink {
+ color: #3b82f6;
+}
+
+.brandLink:hover {
+ text-decoration: underline;
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 84af2cb..9f65002 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,95 +1,57 @@
-import Image from "next/image";
-import styles from "./page.module.css";
+'use client';
+
+import { useState, FormEvent } from 'react';
+import { useRouter } from 'next/navigation';
+import Link from 'next/link';
+import styles from './home.module.css';
export default function Home() {
- return (
-
-
-
-
- -
- Get started by editing
src/app/page.tsx.
-
- - Save and see your changes instantly.
-
+ const [query, setQuery] = useState('');
+ const router = useRouter();
-
-
-
- Deploy now
-
-
- Read our docs
-
+ const handleSubmit = (e: FormEvent) => {
+ e.preventDefault();
+
+ if (query.trim()) {
+ router.push(`/results?query=${encodeURIComponent(query)}`);
+ }
+ };
+
+ return (
+
+
+ E-WIKI
+
+
+
+
+
+
Popular brands
+
+ Tesla
+ BMW
+ Toyota
+ Audi
+ Mercedes
-
-
-
+
+
);
}
diff --git a/src/app/results/page.tsx b/src/app/results/page.tsx
new file mode 100644
index 0000000..c27836a
--- /dev/null
+++ b/src/app/results/page.tsx
@@ -0,0 +1,210 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import { useSearchParams } from 'next/navigation';
+import Link from 'next/link';
+import { CarService } from '../services/carService';
+import { Brand, CarModel, CarRevision } from '../../backend/models';
+
+export default function Results() {
+ const searchParams = useSearchParams();
+ const [loading, setLoading] = useState(true);
+ const [models, setModels] = useState
([]);
+ const [revisions, setRevisions] = useState([]);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchResults = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const query = searchParams.get('query') || '';
+ const category = searchParams.get('category') || '';
+ const startYear = searchParams.get('startYear');
+ const endYear = searchParams.get('endYear');
+ const brandId = searchParams.get('brand');
+
+ let results;
+
+ if (brandId) {
+ // Handle brand-specific search (if implemented in CarService)
+ const response = await fetch(`/api/cars/brand/${brandId}`);
+ if (!response.ok) throw new Error('Failed to fetch brand models');
+ results = await response.json();
+ } else if (category) {
+ results = await CarService.searchByCategory(category);
+ } else if (startYear && endYear) {
+ results = await CarService.searchByYearRange(
+ parseInt(startYear),
+ parseInt(endYear)
+ );
+ } else {
+ results = await CarService.searchByQuery(query);
+ }
+
+ setModels(results.models || []);
+ setRevisions(results.revisions || []);
+ } catch (err) {
+ console.error('Error fetching search results:', err);
+ setError('Failed to load search results. Please try again.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchResults();
+ }, [searchParams]);
+
+ return (
+
+
+
+
Search Results
+
+ New Search
+
+
+
+ {loading ? (
+
+ ) : error ? (
+
+ {error}
+
+ ) : models.length === 0 && revisions.length === 0 ? (
+
+
No results found
+
+ Try adjusting your search criteria to find more cars.
+
+
+ ) : (
+
+ {models.length > 0 && (
+
+
Car Models
+
+ {models.map((model) => (
+
+
+ {model.image ? (
+

+ ) : (
+
+ No image available
+
+ )}
+
+
+
+
{model.name}
+
+ {model.brand?.name}
+
+
+
{model.description}
+
+
+ {model.category}
+
+
+ Since {model.productionStartYear}
+ {model.productionEndYear
+ ? ` - ${model.productionEndYear}`
+ : ''}
+
+
+
+
+ ))}
+
+
+ )}
+
+ {revisions.length > 0 && (
+
+
Car Revisions
+
+ {revisions.map((revision) => (
+
+
+ {revision.images && revision.images.length > 0 ? (
+

+ ) : (
+
+ No image available
+
+ )}
+
+
+
+
{revision.name}
+
+ {revision.baseModel?.brand?.name}
+
+
+
+
+ Engine:
+ {revision.engineTypes?.join(', ')}
+
+
+ Power:
+ {revision.horsePower} HP
+
+
+ 0-100 km/h:
+ {revision.acceleration0To100}s
+
+
+ Top Speed:
+ {revision.topSpeed} km/h
+
+
+
+ {revision.features?.slice(0, 3).map((feature, index) => (
+
+ {feature}
+
+ ))}
+ {revision.features && revision.features.length > 3 && (
+
+ +{revision.features.length - 3} more
+
+ )}
+
+
+
+ ))}
+
+
+ )}
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/results/results.module.css b/src/app/results/results.module.css
new file mode 100644
index 0000000..f13d758
--- /dev/null
+++ b/src/app/results/results.module.css
@@ -0,0 +1,209 @@
+.resultsContainer {
+ min-height: 100vh;
+ padding: 2rem;
+ background-color: #f9fafb;
+}
+
+.contentContainer {
+ max-width: 80rem;
+ margin: 0 auto;
+}
+
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+}
+
+.resultsTitle {
+ font-size: 1.875rem;
+ font-weight: bold;
+}
+
+.backButton {
+ background-color: #3b82f6;
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 0.375rem;
+ transition: background-color 0.2s;
+}
+
+.backButton:hover {
+ background-color: #2563eb;
+}
+
+.loadingContainer {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 3rem 0;
+}
+
+.spinner {
+ border-radius: 50%;
+ width: 3rem;
+ height: 3rem;
+ border: 0.25rem solid rgba(59, 130, 246, 0.1);
+ border-top-color: #3b82f6;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+.errorContainer {
+ background-color: #fee2e2;
+ border: 1px solid #f87171;
+ color: #b91c1c;
+ padding: 0.75rem 1rem;
+ border-radius: 0.375rem;
+}
+
+.emptyResultsContainer {
+ background-color: white;
+ padding: 2rem;
+ border-radius: 0.5rem;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ text-align: center;
+}
+
+.emptyResultsTitle {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 1rem;
+}
+
+.emptyResultsMessage {
+ color: #6b7280;
+}
+
+.resultsSection {
+ margin-bottom: 2rem;
+}
+
+.sectionTitle {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 1rem;
+}
+
+.cardsGrid {
+ display: grid;
+ grid-template-columns: repeat(1, 1fr);
+ gap: 1.5rem;
+}
+
+@media (min-width: 768px) {
+ .cardsGrid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (min-width: 1024px) {
+ .cardsGrid {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
+.card {
+ background-color: white;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.cardImageContainer {
+ height: 12rem;
+ background-color: #e5e7eb;
+ position: relative;
+}
+
+.cardImage {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.noImageContainer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: #6b7280;
+}
+
+.cardContent {
+ padding: 1rem;
+}
+
+.cardHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 0.5rem;
+}
+
+.cardTitle {
+ font-size: 1.25rem;
+ font-weight: bold;
+}
+
+.brandName {
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: #3b82f6;
+}
+
+.cardDescription {
+ color: #6b7280;
+ font-size: 0.875rem;
+ margin-bottom: 0.5rem;
+}
+
+.cardFooter {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 0.875rem;
+}
+
+.category {
+ background-color: #f3f4f6;
+ padding: 0.25rem 0.5rem;
+ border-radius: 0.25rem;
+}
+
+.specsGrid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 0.5rem;
+ margin-bottom: 0.5rem;
+ font-size: 0.875rem;
+}
+
+.specLabel {
+ font-weight: 500;
+}
+
+.featuresList {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+ margin-top: 0.5rem;
+}
+
+.featureTag {
+ background-color: #dbeafe;
+ color: #1e40af;
+ font-size: 0.75rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: 0.25rem;
+}
+
+.moreFeatures {
+ font-size: 0.75rem;
+ color: #6b7280;
+}
\ No newline at end of file
diff --git a/src/app/services/carService.ts b/src/app/services/carService.ts
new file mode 100644
index 0000000..49df3d3
--- /dev/null
+++ b/src/app/services/carService.ts
@@ -0,0 +1,80 @@
+import { Brand, CarModel, CarRevision } from '../../backend/models';
+
+interface SearchResults {
+ models: CarModel[];
+ revisions: CarRevision[];
+}
+
+export class CarService {
+ /**
+ * Search cars by free-text query
+ */
+ static async searchByQuery(query: string): Promise {
+ try {
+ const response = await fetch(`/api/cars/search?query=${encodeURIComponent(query)}`);
+
+ if (!response.ok) {
+ throw new Error('Failed to search cars');
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error('Error searching cars:', error);
+ return { models: [], revisions: [] };
+ }
+ }
+
+ /**
+ * Search cars by category
+ */
+ static async searchByCategory(category: string): Promise {
+ try {
+ const response = await fetch(`/api/cars/search?category=${encodeURIComponent(category)}`);
+
+ if (!response.ok) {
+ throw new Error('Failed to search cars by category');
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error('Error searching cars by category:', error);
+ return { models: [], revisions: [] };
+ }
+ }
+
+ /**
+ * Search cars by year range
+ */
+ static async searchByYearRange(startYear: number, endYear: number): Promise {
+ try {
+ const response = await fetch(`/api/cars/search?startYear=${startYear}&endYear=${endYear}`);
+
+ if (!response.ok) {
+ throw new Error('Failed to search cars by year range');
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error('Error searching cars by year range:', error);
+ return { models: [], revisions: [] };
+ }
+ }
+
+ /**
+ * Get all car brands
+ */
+ static async getAllBrands(): Promise {
+ try {
+ const response = await fetch('/api/cars/brands');
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch brands');
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error('Error fetching brands:', error);
+ return [];
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/backend/models/Brand.ts b/src/backend/models/Brand.ts
new file mode 100644
index 0000000..6f1906c
--- /dev/null
+++ b/src/backend/models/Brand.ts
@@ -0,0 +1,13 @@
+import { CarModel } from './CarModel';
+
+export interface Brand {
+ id: string;
+ name: string;
+ logo: string;
+ description: string;
+ foundedYear: number;
+ headquarters: string;
+ website: string;
+ carModels?: CarModel[];
+}
+
diff --git a/src/backend/models/CarModel.ts b/src/backend/models/CarModel.ts
new file mode 100644
index 0000000..fc014d4
--- /dev/null
+++ b/src/backend/models/CarModel.ts
@@ -0,0 +1,14 @@
+import { Brand } from './Brand';
+import { CarRevision } from './CarRevision';
+
+export interface CarModel {
+ id: string;
+ name: string;
+ brand: Brand;
+ productionStartYear: number;
+ productionEndYear?: number;
+ category?: string;
+ description?: string;
+ image?: string;
+ revisions?: CarRevision[];
+}
\ No newline at end of file
diff --git a/src/backend/models/CarRevision.ts b/src/backend/models/CarRevision.ts
new file mode 100644
index 0000000..d50c7db
--- /dev/null
+++ b/src/backend/models/CarRevision.ts
@@ -0,0 +1,25 @@
+import { CarModel } from './CarModel';
+
+export interface Dimensions {
+ length: number;
+ width: number;
+ height: number;
+ wheelbase: number;
+}
+
+export interface CarRevision {
+ id: string;
+ name: string;
+ baseModel: CarModel;
+ releaseYear: number;
+ engineTypes: string[];
+ horsePower: number;
+ torque: number;
+ topSpeed: number;
+ acceleration0To100: number;
+ fuelConsumption: number;
+ dimensions: Dimensions;
+ weight: number;
+ features: string[];
+ images: string[];
+}
\ No newline at end of file
diff --git a/src/backend/models/index.ts b/src/backend/models/index.ts
new file mode 100644
index 0000000..8fd7eec
--- /dev/null
+++ b/src/backend/models/index.ts
@@ -0,0 +1,3 @@
+export * from './Brand';
+export * from './CarModel';
+export * from './CarRevision';
\ No newline at end of file
diff --git a/src/backend/repositories/CarRepository.ts b/src/backend/repositories/CarRepository.ts
new file mode 100644
index 0000000..177de87
--- /dev/null
+++ b/src/backend/repositories/CarRepository.ts
@@ -0,0 +1,512 @@
+import { Brand, CarModel, CarRevision } from '../models';
+
+// Dummy brands
+const brands: Brand[] = [
+ {
+ id: '1',
+ name: 'Tesla',
+ logo: 'https://example.com/tesla-logo.png',
+ description: 'American electric vehicle and clean energy company',
+ foundedYear: 2003,
+ headquarters: 'Palo Alto, California, United States',
+ website: 'https://www.tesla.com',
+ },
+ {
+ id: '2',
+ name: 'BMW',
+ logo: 'https://example.com/bmw-logo.png',
+ description: 'German luxury automobile and motorcycle manufacturer',
+ foundedYear: 1916,
+ headquarters: 'Munich, Germany',
+ website: 'https://www.bmw.com',
+ },
+ {
+ id: '3',
+ name: 'Toyota',
+ logo: 'https://example.com/toyota-logo.png',
+ description: 'Japanese multinational automotive manufacturer',
+ foundedYear: 1937,
+ headquarters: 'Toyota City, Japan',
+ website: 'https://www.toyota.com',
+ },
+ {
+ id: '4',
+ name: 'Audi',
+ logo: 'https://example.com/audi-logo.png',
+ description: 'German luxury automobile manufacturer',
+ foundedYear: 1909,
+ headquarters: 'Ingolstadt, Germany',
+ website: 'https://www.audi.com',
+ },
+ {
+ id: '5',
+ name: 'Mercedes-Benz',
+ logo: 'https://example.com/mercedes-logo.png',
+ description: 'German global automobile marque and a division of Daimler AG',
+ foundedYear: 1926,
+ headquarters: 'Stuttgart, Germany',
+ website: 'https://www.mercedes-benz.com',
+ },
+];
+
+// Dummy car models (without revisions yet)
+const carModels: CarModel[] = [
+ {
+ id: '1',
+ name: 'Model S',
+ brand: brands[0], // Tesla
+ productionStartYear: 2012,
+ category: 'Sedan',
+ description: 'All-electric five-door liftback sedan',
+ image: 'https://example.com/tesla-model-s.jpg',
+ },
+ {
+ id: '2',
+ name: 'Model 3',
+ brand: brands[0], // Tesla
+ productionStartYear: 2017,
+ category: 'Sedan',
+ description: 'All-electric four-door sedan',
+ image: 'https://example.com/tesla-model-3.jpg',
+ },
+ {
+ id: '3',
+ name: '3 Series',
+ brand: brands[1], // BMW
+ productionStartYear: 1975,
+ category: 'Sedan',
+ description: 'Compact executive car',
+ image: 'https://example.com/bmw-3-series.jpg',
+ },
+ {
+ id: '4',
+ name: 'X5',
+ brand: brands[1], // BMW
+ productionStartYear: 1999,
+ category: 'SUV',
+ description: 'Mid-size luxury SUV',
+ image: 'https://example.com/bmw-x5.jpg',
+ },
+ {
+ id: '5',
+ name: 'Camry',
+ brand: brands[2], // Toyota
+ productionStartYear: 1982,
+ category: 'Sedan',
+ description: 'Mid-size car',
+ image: 'https://example.com/toyota-camry.jpg',
+ },
+ {
+ id: '6',
+ name: 'Prius',
+ brand: brands[2], // Toyota
+ productionStartYear: 1997,
+ category: 'Hatchback',
+ description: 'Hybrid electric mid-size car',
+ image: 'https://example.com/toyota-prius.jpg',
+ },
+ {
+ id: '7',
+ name: 'A4',
+ brand: brands[3], // Audi
+ productionStartYear: 1994,
+ category: 'Sedan',
+ description: 'Compact executive car',
+ image: 'https://example.com/audi-a4.jpg',
+ },
+ {
+ id: '8',
+ name: 'Q7',
+ brand: brands[3], // Audi
+ productionStartYear: 2005,
+ category: 'SUV',
+ description: 'Full-size luxury crossover SUV',
+ image: 'https://example.com/audi-q7.jpg',
+ },
+ {
+ id: '9',
+ name: 'C-Class',
+ brand: brands[4], // Mercedes-Benz
+ productionStartYear: 1993,
+ category: 'Sedan',
+ description: 'Compact executive car',
+ image: 'https://example.com/mercedes-c-class.jpg',
+ },
+ {
+ id: '10',
+ name: 'GLE',
+ brand: brands[4], // Mercedes-Benz
+ productionStartYear: 2015,
+ category: 'SUV',
+ description: 'Mid-size luxury crossover SUV',
+ image: 'https://example.com/mercedes-gle.jpg',
+ },
+];
+
+// Dummy car revisions
+const carRevisions: CarRevision[] = [
+ {
+ id: '1',
+ name: 'Model S Long Range Plus',
+ baseModel: carModels[0], // Tesla Model S
+ releaseYear: 2020,
+ engineTypes: ['Electric'],
+ horsePower: 670,
+ torque: 850,
+ topSpeed: 250,
+ acceleration0To100: 3.1,
+ fuelConsumption: 0,
+ dimensions: {
+ length: 4970,
+ width: 1964,
+ height: 1445,
+ wheelbase: 2960,
+ },
+ weight: 2250,
+ features: ['Autopilot', 'Premium Interior', 'All-Wheel Drive'],
+ images: ['https://example.com/tesla-model-s-2020-1.jpg', 'https://example.com/tesla-model-s-2020-2.jpg'],
+ },
+ {
+ id: '2',
+ name: 'Model S Plaid',
+ baseModel: carModels[0], // Tesla Model S
+ releaseYear: 2021,
+ engineTypes: ['Electric'],
+ horsePower: 1020,
+ torque: 1050,
+ topSpeed: 322,
+ acceleration0To100: 2.1,
+ fuelConsumption: 0,
+ dimensions: {
+ length: 4970,
+ width: 1964,
+ height: 1445,
+ wheelbase: 2960,
+ },
+ weight: 2300,
+ features: ['Enhanced Autopilot', 'Yoke Steering', 'All-Wheel Drive', 'New Interior Design'],
+ images: ['https://example.com/tesla-model-s-plaid-1.jpg', 'https://example.com/tesla-model-s-plaid-2.jpg'],
+ },
+ {
+ id: '3',
+ name: 'Model 3 Standard Range Plus',
+ baseModel: carModels[1], // Tesla Model 3
+ releaseYear: 2021,
+ engineTypes: ['Electric'],
+ horsePower: 283,
+ torque: 450,
+ topSpeed: 225,
+ acceleration0To100: 5.6,
+ fuelConsumption: 0,
+ dimensions: {
+ length: 4694,
+ width: 1849,
+ height: 1443,
+ wheelbase: 2875,
+ },
+ weight: 1750,
+ features: ['Basic Autopilot', 'Standard Interior', 'Rear-Wheel Drive'],
+ images: ['https://example.com/tesla-model-3-standard-1.jpg', 'https://example.com/tesla-model-3-standard-2.jpg'],
+ },
+ {
+ id: '4',
+ name: '330i Sedan',
+ baseModel: carModels[2], // BMW 3 Series
+ releaseYear: 2021,
+ engineTypes: ['Gasoline'],
+ horsePower: 255,
+ torque: 400,
+ topSpeed: 209,
+ acceleration0To100: 5.6,
+ fuelConsumption: 7.1,
+ dimensions: {
+ length: 4709,
+ width: 1827,
+ height: 1435,
+ wheelbase: 2851,
+ },
+ weight: 1620,
+ features: ['LED Headlights', 'iDrive Infotainment System', 'Leather Seats'],
+ images: ['https://example.com/bmw-330i-1.jpg', 'https://example.com/bmw-330i-2.jpg'],
+ },
+ {
+ id: '5',
+ name: 'M340i Sedan',
+ baseModel: carModels[2], // BMW 3 Series
+ releaseYear: 2021,
+ engineTypes: ['Gasoline'],
+ horsePower: 382,
+ torque: 500,
+ topSpeed: 250,
+ acceleration0To100: 4.4,
+ fuelConsumption: 8.0,
+ dimensions: {
+ length: 4709,
+ width: 1827,
+ height: 1435,
+ wheelbase: 2851,
+ },
+ weight: 1670,
+ features: ['M Sport Differential', 'M Sport Brakes', 'Adaptive M Suspension'],
+ images: ['https://example.com/bmw-m340i-1.jpg', 'https://example.com/bmw-m340i-2.jpg'],
+ },
+ {
+ id: '6',
+ name: 'X5 xDrive40i',
+ baseModel: carModels[3], // BMW X5
+ releaseYear: 2021,
+ engineTypes: ['Gasoline'],
+ horsePower: 335,
+ torque: 450,
+ topSpeed: 243,
+ acceleration0To100: 5.5,
+ fuelConsumption: 9.2,
+ dimensions: {
+ length: 4922,
+ width: 2004,
+ height: 1745,
+ wheelbase: 2975,
+ },
+ weight: 2260,
+ features: ['Panoramic Roof', 'Head-Up Display', 'Gesture Control'],
+ images: ['https://example.com/bmw-x5-xdrive40i-1.jpg', 'https://example.com/bmw-x5-xdrive40i-2.jpg'],
+ },
+ {
+ id: '7',
+ name: 'Camry LE',
+ baseModel: carModels[4], // Toyota Camry
+ releaseYear: 2021,
+ engineTypes: ['Gasoline'],
+ horsePower: 203,
+ torque: 250,
+ topSpeed: 210,
+ acceleration0To100: 8.1,
+ fuelConsumption: 7.6,
+ dimensions: {
+ length: 4880,
+ width: 1840,
+ height: 1445,
+ wheelbase: 2825,
+ },
+ weight: 1580,
+ features: ['Toyota Safety Sense', 'Apple CarPlay', 'Android Auto'],
+ images: ['https://example.com/toyota-camry-le-1.jpg', 'https://example.com/toyota-camry-le-2.jpg'],
+ },
+ {
+ id: '8',
+ name: 'Camry Hybrid',
+ baseModel: carModels[4], // Toyota Camry
+ releaseYear: 2021,
+ engineTypes: ['Hybrid'],
+ horsePower: 208,
+ torque: 220,
+ topSpeed: 180,
+ acceleration0To100: 7.8,
+ fuelConsumption: 4.2,
+ dimensions: {
+ length: 4880,
+ width: 1840,
+ height: 1445,
+ wheelbase: 2825,
+ },
+ weight: 1680,
+ features: ['Regenerative Braking', 'EV Mode', 'Energy Monitor'],
+ images: ['https://example.com/toyota-camry-hybrid-1.jpg', 'https://example.com/toyota-camry-hybrid-2.jpg'],
+ },
+ {
+ id: '9',
+ name: 'Prius Prime',
+ baseModel: carModels[5], // Toyota Prius
+ releaseYear: 2021,
+ engineTypes: ['Plug-in Hybrid'],
+ horsePower: 121,
+ torque: 142,
+ topSpeed: 165,
+ acceleration0To100: 10.5,
+ fuelConsumption: 1.8,
+ dimensions: {
+ length: 4645,
+ width: 1760,
+ height: 1470,
+ wheelbase: 2700,
+ },
+ weight: 1530,
+ features: ['Electric Range of 40 km', 'Touch-sensitive controls', 'Quad-LED projector headlights'],
+ images: ['https://example.com/toyota-prius-prime-1.jpg', 'https://example.com/toyota-prius-prime-2.jpg'],
+ },
+ {
+ id: '10',
+ name: 'A4 Prestige',
+ baseModel: carModels[6], // Audi A4
+ releaseYear: 2021,
+ engineTypes: ['Gasoline'],
+ horsePower: 261,
+ torque: 370,
+ topSpeed: 210,
+ acceleration0To100: 5.5,
+ fuelConsumption: 7.5,
+ dimensions: {
+ length: 4762,
+ width: 1847,
+ height: 1435,
+ wheelbase: 2820,
+ },
+ weight: 1640,
+ features: ['Audi Virtual Cockpit', 'Bang & Olufsen Sound System', 'Adaptive Cruise Control'],
+ images: ['https://example.com/audi-a4-prestige-1.jpg', 'https://example.com/audi-a4-prestige-2.jpg'],
+ },
+];
+
+// Connect revisions to models
+const connectRevisionsToModels = () => {
+ // Tesla Model S revisions
+ carModels[0].revisions = [carRevisions[0], carRevisions[1]];
+
+ // Tesla Model 3 revisions
+ carModels[1].revisions = [carRevisions[2]];
+
+ // BMW 3 Series revisions
+ carModels[2].revisions = [carRevisions[3], carRevisions[4]];
+
+ // BMW X5 revisions
+ carModels[3].revisions = [carRevisions[5]];
+
+ // Toyota Camry revisions
+ carModels[4].revisions = [carRevisions[6], carRevisions[7]];
+
+ // Toyota Prius revisions
+ carModels[5].revisions = [carRevisions[8]];
+
+ // Audi A4 revisions
+ carModels[6].revisions = [carRevisions[9]];
+};
+
+// Connect car models to brands
+const connectModelsToBrands = () => {
+ // Tesla models
+ brands[0].carModels = [carModels[0], carModels[1]];
+
+ // BMW models
+ brands[1].carModels = [carModels[2], carModels[3]];
+
+ // Toyota models
+ brands[2].carModels = [carModels[4], carModels[5]];
+
+ // Audi models
+ brands[3].carModels = [carModels[6], carModels[7]];
+
+ // Mercedes models
+ brands[4].carModels = [carModels[8], carModels[9]];
+};
+
+// Initialize connections
+connectRevisionsToModels();
+connectModelsToBrands();
+
+// Car Repository Service
+export class CarRepository {
+ /**
+ * Get all brands
+ */
+ getAllBrands(): Promise {
+ return Promise.resolve(brands);
+ }
+
+ /**
+ * Get brand by ID
+ */
+ getBrandById(id: string): Promise {
+ const brand = brands.find(brand => brand.id === id);
+ return Promise.resolve(brand || null);
+ }
+
+ /**
+ * Get all car models
+ */
+ getAllCarModels(): Promise {
+ return Promise.resolve(carModels);
+ }
+
+ /**
+ * Get car models by brand ID
+ */
+ getCarModelsByBrandId(brandId: string): Promise {
+ const brand = brands.find(brand => brand.id === brandId);
+ return Promise.resolve(brand?.carModels || []);
+ }
+
+ /**
+ * Get car model by ID
+ */
+ getCarModelById(id: string): Promise {
+ const carModel = carModels.find(model => model.id === id);
+ return Promise.resolve(carModel || null);
+ }
+
+ /**
+ * Get all car revisions
+ */
+ getAllCarRevisions(): Promise {
+ return Promise.resolve(carRevisions);
+ }
+
+ /**
+ * Get car revisions by model ID
+ */
+ getCarRevisionsByModelId(modelId: string): Promise {
+ const carModel = carModels.find(model => model.id === modelId);
+ return Promise.resolve(carModel?.revisions || []);
+ }
+
+ /**
+ * Get car revision by ID
+ */
+ getCarRevisionById(id: string): Promise {
+ const carRevision = carRevisions.find(revision => revision.id === id);
+ return Promise.resolve(carRevision || null);
+ }
+
+ /**
+ * Search cars by name (searches both models and revisions)
+ */
+ searchCarsByName(name: string): Promise<{ models: CarModel[], revisions: CarRevision[] }> {
+ const lowercaseName = name.toLowerCase();
+
+ const matchingModels = carModels.filter(model =>
+ model.name.toLowerCase().includes(lowercaseName)
+ );
+
+ const matchingRevisions = carRevisions.filter(revision =>
+ revision.name.toLowerCase().includes(lowercaseName)
+ );
+
+ return Promise.resolve({
+ models: matchingModels,
+ revisions: matchingRevisions
+ });
+ }
+
+ /**
+ * Get cars by category
+ */
+ getCarsByCategory(category: string): Promise {
+ const matchingModels = carModels.filter(model =>
+ model.category?.toLowerCase() === category.toLowerCase()
+ );
+
+ return Promise.resolve(matchingModels);
+ }
+
+ /**
+ * Get cars by year range
+ */
+ getCarsByYearRange(startYear: number, endYear: number): Promise {
+ const matchingModels = carModels.filter(model => {
+ const startYearMatch = !model.productionStartYear || model.productionStartYear <= endYear;
+ const endYearMatch = !model.productionEndYear || model.productionEndYear >= startYear;
+ return startYearMatch && endYearMatch;
+ });
+
+ return Promise.resolve(matchingModels);
+ }
+}
\ No newline at end of file
diff --git a/src/backend/repositories/index.ts b/src/backend/repositories/index.ts
new file mode 100644
index 0000000..9e4feb8
--- /dev/null
+++ b/src/backend/repositories/index.ts
@@ -0,0 +1 @@
+export * from './CarRepository';
\ No newline at end of file