From 1e1926539ab1e24e3996afa65ab01b33ea1a334d Mon Sep 17 00:00:00 2001 From: EdiFarcas Date: Mon, 7 Jul 2025 11:08:29 +0300 Subject: [PATCH] Logic + UI upgrade --- README.md | 86 +++++++++--- src/app/auth/login/page.tsx | 14 +- src/app/auth/register/page.tsx | 16 +-- .../dashboard/cars/[carId]/fillups/page.tsx | 109 ++++++++------- .../dashboard/cars/[carId]/mileage/page.tsx | 42 +++--- src/app/dashboard/cars/[carId]/page.tsx | 21 ++- src/app/dashboard/cars/[carId]/stats/page.tsx | 31 +++-- src/app/dashboard/cars/new/page.tsx | 22 ++-- src/app/dashboard/page.tsx | 18 +-- src/app/globals.css | 17 +++ src/app/layout.tsx | 21 ++- src/app/page.tsx | 124 +++++------------- src/components/CarCard.tsx | 12 +- src/components/StatCard.tsx | 17 ++- 14 files changed, 305 insertions(+), 245 deletions(-) diff --git a/README.md b/README.md index e215bc4..9fa9d34 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,82 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Car Fuel Tracking App + +A modern web application to track your car's fuel fill-ups, mileage, and fuel statistics. Built with Next.js, React, Prisma, and PostgreSQL, it provides a beautiful dashboard for managing your vehicles and analyzing fuel efficiency over time. + +## Features + +- 🚗 **Multi-car support**: Track multiple vehicles, each with its own stats and history. +- ⛽ **Fill-up logging**: Record every fuel fill-up with mileage, liters, cost, and currency. +- 🛣️ **Mileage tracking**: Log odometer readings to monitor your driving habits. +- 📊 **Statistics dashboard**: View total distance, fuel used, cost, average consumption, and more. +- 🔒 **Authentication**: Secure login and registration with hashed passwords. +- 🌗 **Responsive & modern UI**: Clean, mobile-friendly design with dark mode support. + +## Tech Stack + +- [Next.js](https://nextjs.org/) (App Router) +- [React](https://react.dev/) +- [Prisma ORM](https://www.prisma.io/) +- [PostgreSQL](https://www.postgresql.org/) +- [NextAuth.js](https://next-auth.js.org/) (Credentials provider) +- [Tailwind CSS](https://tailwindcss.com/) +- TypeScript ## Getting Started -First, run the development server: +### Prerequisites +- Node.js 18+ +- PostgreSQL database +### 1. Clone the repository ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +git clone +cd Car-Fuel-Tracking-App ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +### 2. Install dependencies +```bash +npm install +``` -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +### 3. Configure environment variables +Create a `.env` file in the root with the following: +``` +DATABASE_URL=postgresql://:@:/ +NEXTAUTH_SECRET=your_secret_key +``` -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +### 4. Set up the database +Run Prisma migrations to set up the schema: +```bash +npx prisma migrate deploy +# or for development +npx prisma migrate dev +``` -## Learn More +### 5. Start the development server +```bash +npm run dev +``` -To learn more about Next.js, take a look at the following resources: +Open [http://localhost:3000](http://localhost:3000) in your browser. -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +## Project Structure -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +- `src/app/` — Next.js app directory (routes, pages, API) +- `src/components/` — Reusable UI components +- `prisma/schema.prisma` — Database schema +- `src/lib/` — Auth and Prisma helpers +- `public/` — Static assets -## Deploy on Vercel +## Scripts +- `npm run dev` — Start development server +- `npm run build` — Build for production +- `npm start` — Start production server +- `npm run lint` — Lint code -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +## Database Schema +See [`prisma/schema.prisma`](prisma/schema.prisma) for models: `User`, `Car`, `FillUp`, `MileageEntry`, enums for `FuelType` and `Currency`. -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +--- + +> Built with passion using Next.js, Prisma, and Tailwind CSS. diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 0ed84d8..9bc021c 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -27,16 +27,16 @@ export default function LoginPage() { }; return ( -
-

Login

-
+
+

Login

+ setEmail(e.target.value)} - className="w-full border px-3 py-2 rounded" + className="w-full border border-[var(--border)] px-4 py-3 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-[var(--primary)]" /> setPassword(e.target.value)} - className="w-full border px-3 py-2 rounded" + className="w-full border border-[var(--border)] px-4 py-3 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-[var(--primary)]" /> - - {error &&

{error}

} + {error &&

{error}

}
); diff --git a/src/app/auth/register/page.tsx b/src/app/auth/register/page.tsx index ac43d92..2fce23f 100644 --- a/src/app/auth/register/page.tsx +++ b/src/app/auth/register/page.tsx @@ -11,13 +11,11 @@ export default function RegisterPage() { const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); - const res = await fetch('/api/auth/register', { method: 'POST', body: JSON.stringify({ email, password }), headers: { 'Content-Type': 'application/json' }, }); - if (res.ok) { router.push('/auth/login'); } else { @@ -27,16 +25,16 @@ export default function RegisterPage() { }; return ( -
-

Register

-
+
+

Register

+ setEmail(e.target.value)} - className="w-full border px-3 py-2 rounded" + className="w-full border border-[var(--border)] px-4 py-3 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-[var(--secondary)]" /> setPassword(e.target.value)} - className="w-full border px-3 py-2 rounded" + className="w-full border border-[var(--border)] px-4 py-3 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-[var(--secondary)]" /> - - {error &&

{error}

} + {error &&

{error}

}
); diff --git a/src/app/dashboard/cars/[carId]/fillups/page.tsx b/src/app/dashboard/cars/[carId]/fillups/page.tsx index e5c8690..60437f5 100644 --- a/src/app/dashboard/cars/[carId]/fillups/page.tsx +++ b/src/app/dashboard/cars/[carId]/fillups/page.tsx @@ -57,56 +57,75 @@ export default function FillUpsPage() { }; return ( -
-

Fuel Fill-Ups

+
+

Fuel Fill-Ups

-
- - - - - - {error &&

{error}

} + {error &&

{error}

}
-
    +
      {fillups.map((fill) => ( // ✅ ))} diff --git a/src/app/dashboard/cars/[carId]/mileage/page.tsx b/src/app/dashboard/cars/[carId]/mileage/page.tsx index a937c58..19babc2 100644 --- a/src/app/dashboard/cars/[carId]/mileage/page.tsx +++ b/src/app/dashboard/cars/[carId]/mileage/page.tsx @@ -44,30 +44,40 @@ export default function MileagePage() { }; return ( -
      -

      Mileage Log

      +
      +

      + 🛣️ Mileage Log +

      -
      - setMileage(e.target.value)} - placeholder="Odometer (e.g. 102300)" - className="flex-1 border px-3 py-2 rounded" - required - /> -
      - {error &&

      {error}

      } + {error &&

      {error}

      }
        {entries.map((entry) => ( -
      • -

        {entry.mileage} km

        -

        {new Date(entry.date).toLocaleString()}

        +
      • + +
        +

        {entry.mileage} km

        +

        {new Date(entry.date).toLocaleString()}

        +
      • ))}
      diff --git a/src/app/dashboard/cars/[carId]/page.tsx b/src/app/dashboard/cars/[carId]/page.tsx index ecf4978..9266d65 100644 --- a/src/app/dashboard/cars/[carId]/page.tsx +++ b/src/app/dashboard/cars/[carId]/page.tsx @@ -22,33 +22,30 @@ export default async function CarDetailPage({ params }: CarDetailPageProps) { }); if (!car) { - return
      Car not found or unauthorized access.
      ; + return
      Car not found or unauthorized access.
      ; } return ( -
      -

      {car.name}

      -

      {car.make} {car.model} ({car.year})

      -

      Fuel Type: {car.fuelType}

      - -
      +
      +

      {car.name}

      +

      {car.make} {car.model} ({car.year})

      +

      Fuel Type: {car.fuelType}

      +
      ➕ Add Mileage - ➕ Add Fill-Up - 📊 View Stats diff --git a/src/app/dashboard/cars/[carId]/stats/page.tsx b/src/app/dashboard/cars/[carId]/stats/page.tsx index 6df3f63..da98e35 100644 --- a/src/app/dashboard/cars/[carId]/stats/page.tsx +++ b/src/app/dashboard/cars/[carId]/stats/page.tsx @@ -16,7 +16,7 @@ export default async function StatsPage({ params }: StatsProps) { const car = await prisma.car.findFirst({ where: { id: params.carId, - user: { email: session.user?.email! }, + user: { email: session.user?.email || '' }, }, include: { fillUps: { @@ -30,9 +30,13 @@ export default async function StatsPage({ params }: StatsProps) { if (fillUps.length < 2) { return ( -
      -

      Not Enough Data

      -

      Add at least 2 fill-ups to see statistics.

      +
      +
      + +

      Not Enough Data

      +

      Add at least 2 fill-ups to see your fuel statistics and insights.

      + ➕ Add Fill-Up +
      ); } @@ -49,15 +53,16 @@ export default async function StatsPage({ params }: StatsProps) { const costPerKm = totalCost / totalDistance; return ( -
      -

      Fuel Stats

      - -
      - - - - - +
      +

      + 📊 Fuel Stats +

      +
      + + + + +
      ); diff --git a/src/app/dashboard/cars/new/page.tsx b/src/app/dashboard/cars/new/page.tsx index b89db08..843ba8a 100644 --- a/src/app/dashboard/cars/new/page.tsx +++ b/src/app/dashboard/cars/new/page.tsx @@ -22,13 +22,11 @@ export default function AddCarPage() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - const res = await fetch('/api/cars', { method: 'POST', body: JSON.stringify(form), headers: { 'Content-Type': 'application/json' }, }); - if (res.ok) { router.push('/dashboard'); } else { @@ -38,16 +36,16 @@ export default function AddCarPage() { }; return ( -
      -

      Add New Car

      -
      +
      +

      Add New Car

      + - - {error &&

      {error}

      } + {error &&

      {error}

      }
      ); diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 7824ddc..73624bf 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,6 +1,6 @@ +import Link from 'next/link'; import { getSession } from '@/lib/auth'; import { prisma } from '@/lib/prisma'; -import Link from 'next/link'; import { redirect } from 'next/navigation'; import CarCard from '@/components/CarCard'; @@ -15,22 +15,24 @@ export default async function DashboardPage() { }); return ( -
      -
      -

      Your Cars

      - +
      +
      +

      Your Cars

      + + Add Car
      - {user?.cars.length ? ( -
        +
          {user.cars.map((car) => ( ))}
        ) : ( -

        No cars added yet.

        +
        +

        No cars added yet.

        + Add your first car +
        )}
      ); diff --git a/src/app/globals.css b/src/app/globals.css index a2dc41e..1f0af3e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -3,6 +3,11 @@ :root { --background: #ffffff; --foreground: #171717; + --primary: #2563eb; /* blue-600 */ + --secondary: #22c55e; /* green-500 */ + --accent: #a21caf; /* purple-700 */ + --muted: #f3f4f6; /* gray-100 */ + --border: #e5e7eb; /* gray-200 */ } @theme inline { @@ -16,6 +21,8 @@ :root { --background: #0a0a0a; --foreground: #ededed; + --muted: #171717; + --border: #232323; } } @@ -23,4 +30,14 @@ body { background: var(--background); color: var(--foreground); font-family: Arial, Helvetica, sans-serif; + transition: background 0.2s, color 0.2s; +} + +::-webkit-scrollbar { + width: 8px; + background: var(--muted); +} +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 4px; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87e..d37ab25 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 Link from "next/link"; 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: "Car Fuel Tracker", + description: "Track your car's fuel and mileage easily.", }; export default function RootLayout({ @@ -25,9 +26,21 @@ export default function RootLayout({ return ( - {children} +
      + + 🚗 Car Fuel Tracker + + +
      +
      +
      {/* Spacer between navbar and content */} + {children} +
      ); diff --git a/src/app/page.tsx b/src/app/page.tsx index e68abe6..64ead0c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,102 +1,48 @@ import Image from "next/image"; +import Link from "next/link"; export default function Home() { return ( -
      -
      - Next.js logo -
        -
      1. - Get started by editing{" "} - - src/app/page.tsx - - . -
      2. -
      3. - Save and see your changes instantly. -
      4. -
      - -
      +
      +
      +
      + Next.js logo +

      + Welcome to Car Fuel Tracker +

      +

      + Track your car's fuel fill-ups, mileage, and stats with a beautiful, + simple dashboard. +

      +
      + -
      -
      ); diff --git a/src/components/CarCard.tsx b/src/components/CarCard.tsx index c87421f..7f7cd1a 100644 --- a/src/components/CarCard.tsx +++ b/src/components/CarCard.tsx @@ -1,13 +1,13 @@ -import Link from 'next/link'; import { Car } from '@prisma/client'; +import Link from 'next/link'; export default function CarCard({ car }: { car: Car }) { return ( - -
      -

      {car.name}

      -

      {car.make} {car.model} ({car.year})

      -

      Fuel Type: {car.fuelType}

      + +
      +

      {car.name}

      +

      {car.make} {car.model} ({car.year})

      +

      Fuel Type: {car.fuelType}

      ); diff --git a/src/components/StatCard.tsx b/src/components/StatCard.tsx index 2d36197..2bf7e12 100644 --- a/src/components/StatCard.tsx +++ b/src/components/StatCard.tsx @@ -1,8 +1,17 @@ -export default function StatCard({ label, value }: { label: string; value: string }) { +export default function StatCard({ label, value, icon, color }: { label: string; value: string; icon?: string; color?: 'primary' | 'secondary' | 'accent' }) { + const valueClass = + label === 'Total Fuel Cost' + ? 'text-gray-900' + : color + ? `text-[var(--${color})]` + : 'text-[var(--foreground)]'; return ( -
      -

      {label}

      -

      {value}

      +
      + {icon && {icon}} +
      +

      {label}

      +

      {value}

      +
      ); }