Skip to content

Instantly share code, notes, and snippets.

@Vetrivel-VP
Last active October 24, 2024 06:10
Show Gist options
  • Save Vetrivel-VP/96043efe0449fa19e4bec3cedc37ee7b to your computer and use it in GitHub Desktop.
Save Vetrivel-VP/96043efe0449fa19e4bec3cedc37ee7b to your computer and use it in GitHub Desktop.
YT-Used Vehicles - NextJs Prisma
Firebase confif ENV variables
----------------------------------------------------------------------------------------------------------------------------
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
----------------------------------------------------------------------------------------------------------------------------
schema.prisma
----------------------------------------------------------------------------------------------------------------------------
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
previewFeatures = ["fullTextIndex", "fullTextSearch"]
}
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
relationMode = "prisma"
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
clerkId String @unique
email String @unique
name String?
contact String?
address String?
location String?
profileImage String?
role String @default("user")
// relations for other colleciton
vehicles Vehicle[]
favourites Favourite[]
views View[]
notifications Notification[]
chatRoomsInitiated ChatRoom[] @relation("ChatRoomsA") // Chat rooms initiated by this user
chatRoomsReceived ChatRoom[] @relation("ChatRoomsB") // Chat rooms received by this user
sentMessages Message[] @relation("SentMessages") // One-to-many: messages sent by the user
receivedMessages Message[] @relation("ReceivedMessages") // One-to-many: messages received by the user
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Category {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
slug String @unique
subCategories SubCategory[]
vehicles Vehicle[]
}
model SubCategory {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
slug String @unique
categoryId String
category Category @relation(fields: [categoryId], references: [id])
vehicles Vehicle[]
}
model Vehicle {
id String @id @default(auto()) @map("_id") @db.ObjectId
make String
model String
year Int
price Float
categoryId String
subCategoryId String
ownerId String
owner User @relation(fields: [ownerId], references: [clerkId])
category Category @relation(fields: [categoryId], references: [id])
subCategory SubCategory @relation(fields: [subCategoryId], references: [id])
coverImage String
images String @default("[]")
status String @default("pending")
report String @default("low-mileage")
location String
state String
district String
postalCode String
mileage String
engine String
transmission String
favourites Favourite[]
views View[]
notifications Notification[]
chatRooms ChatRoom[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([make])
}
model Favourite {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String
vehicleId String
user User @relation(fields: [userId], references: [clerkId], onDelete: Cascade)
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, vehicleId])
}
model View {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String
vehicleId String
user User @relation(fields: [userId], references: [clerkId], onDelete: Cascade)
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, vehicleId])
}
model Notification {
id String @id @default(auto()) @map("_id") @db.ObjectId
message String
userId String
user User @relation(fields: [userId], references: [clerkId])
type String
status String @default("pending")
vehicleId String?
vehicle Vehicle? @relation(fields: [vehicleId], references: [id])
isRead Boolean? @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model ChatRoom {
id String @id @default(auto()) @map("_id") @db.ObjectId
userAId String
userBId String
chatRoomName String
chatProfilePic String
vehicleId String?
vehicle Vehicle? @relation(fields: [vehicleId], references: [id])
userA User @relation("ChatRoomsA", fields: [userAId], references: [clerkId])
userB User @relation("ChatRoomsB", fields: [userBId], references: [clerkId])
messages Message[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userAId, userBId])
}
model Message {
id String @id @default(auto()) @map("_id") @db.ObjectId
chatRoomId String
chatRoom ChatRoom @relation(fields: [chatRoomId], references: [id])
senderId String
sender User @relation("SentMessages", fields: [senderId], references: [clerkId], onDelete: Cascade) // Relation for sent messages
receiverId String
receiver User @relation("ReceivedMessages", fields: [receiverId], references: [clerkId], onDelete: Cascade) // Relation for received messages
content String
delivered Boolean @default(false)
read Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Contact {
id String @id @default(auto()) @map("_id") @db.ObjectId
firstName String
lastName String
message String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
----------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------
MobileNavbar.tsx
----------------------------------------------------------------------------------------------------------------------------
"use client";
import React from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useAuth } from "@clerk/nextjs"; // Assuming you're using Clerk for authentication
const MobileNavbar = () => {
const pathname = usePathname();
const { isSignedIn } = useAuth(); // Use this to check if the user is authenticated
const routes = [
{ path: "/", label: "Home" },
{ path: "/vehicles", label: "Vehicles" },
{ path: "/contact", label: "Contact" },
];
if (isSignedIn) {
routes.push({ path: "/overview", label: "Dashboard" });
}
return (
<div className="flex flex-col space-y-4 p-4 bg-white">
{routes.map(({ path, label }) => (
<Link
key={path}
href={path}
passHref
className={`relative px-4 py-2 text-lg font-medium transition-all duration-300 ease-in-out ${
pathname === path
? "text-blue-600 font-semibold after:absolute after:-bottom-1 after:left-0 after:h-[2px] after:w-full after:bg-blue-600 after:scale-x-100 after:transition-transform after:duration-300"
: "text-gray-700 hover:text-blue-500 hover:after:absolute hover:after:-bottom-1 hover:after:left-0 hover:after:h-[2px] hover:after:w-full hover:after:bg-blue-500 hover:after:scale-x-0 hover:after:transition-transform hover:after:duration-300"
}`}
>
{label}
</Link>
))}
</div>
);
};
export default MobileNavbar;
----------------------------------------------------------------------------------------------------------------------------
Footer.tsx
----------------------------------------------------------------------------------------------------------------------------
"use client";
import React from "react";
import Link from "next/link"; // Adjust this if you are using a different routing solution
import {
Mail,
Phone,
MapPin,
Facebook,
Twitter,
Instagram,
Send,
} from "lucide-react";
import { Input } from "./ui/input";
import { Button } from "./ui/button";
import { LogoContainer } from "./logo-container";
export const Footer = () => {
const menus = [
{ name: "Home", href: "/" },
{ name: "Vehicles", href: "/vehicles" },
// { name: "Blog", href: "/blog" },
{ name: "Contact", href: "/contact" },
];
return (
<footer className="bg-gray-900 text-gray-400 py-10 rounded-3xl">
<div className="container mx-auto px-4 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
{/* About Us */}
<div className="flex flex-col">
<LogoContainer isFooter />
<h4 className="text-white text-lg font-bold mb-4">
The Ultimate Marketplace
</h4>
<p className="text-gray-400 mb-4">
AutoFind connects buyers and sellers of pre-owned vehicles with a
seamless and trusted platform.
</p>
</div>
{/* Quick Links */}
<div className="flex flex-col">
<h4 className="text-white text-lg font-bold mb-4">Quick Links</h4>
<ul className="space-y-2">
{menus.map((menu) => (
<li key={menu.name}>
<Link
href={menu.href}
className="hover:text-blue-500 transition-colors"
>
{menu.name}
</Link>
</li>
))}
</ul>
</div>
{/* Social Links */}
<div className="flex flex-col">
<h4 className="text-white text-lg font-bold mb-4">Follow Us</h4>
<div className="flex space-x-4">
<Link
href="https://facebook.com"
aria-label="Facebook"
className="hover:text-blue-500 transition-colors"
>
<Facebook size={24} />
</Link>
<Link
href="https://twitter.com"
aria-label="Twitter"
className="hover:text-blue-500 transition-colors"
>
<Twitter size={24} />
</Link>
<Link
href="https://instagram.com"
aria-label="Instagram"
className="hover:text-blue-500 transition-colors"
>
<Instagram size={24} />
</Link>
</div>
<div className="mt-4">
<p className="flex items-center mb-2">
<MapPin className="mr-2 w-4 text-blue-500" />
123 Main St, Anytown, USA
</p>
<p className="flex items-center mb-2">
<Phone className="mr-2 w-4 text-blue-500" />
+1 234 567 8900
</p>
<p className="flex items-center">
<Mail className="mr-2 w-4 text-blue-500" />
[email protected]
</p>
</div>
</div>
{/* Subscribe Section */}
<div className="flex flex-col">
<h4 className="text-white text-lg font-bold mb-4">
Subscribe to Our Newsletter
</h4>
<form className="flex flex-col sm:flex-row gap-4">
<div className="relative w-full bg-gray-800 border-gray-700 border flex items-center rounded-lg">
<Input
type="email"
placeholder="Enter your email"
className="w-full p-3 rounded-lg bg-transparent text-gray-200 border-none focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 placeholder-gray-500"
required
/>
<Button className="bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg transition-all duration-300">
<Send className="w-5 h-5" />
</Button>
</div>
</form>
</div>
</div>
<div className="mt-8 border-t border-gray-800 pt-4">
<p className="text-center text-gray-500">
© {new Date().getFullYear()} AutoFind. All rights reserved.
</p>
</div>
</footer>
);
};
export default Footer;
----------------------------------------------------------------------------------------------------------------------------
banner-container.tsx
----------------------------------------------------------------------------------------------------------------------------
"use client";
import { Button } from "@/components/ui/button";
import SearchContainer from "./search-container";
export const BannerContainer = () => {
return (
<div
className="relative bg-cover bg-center lg:min-h-[60vh] flex flex-col justify-start items-center text-center p-6 lg:p-12 rounded-xl overflow-hidden"
style={{ backgroundImage: "url(/hero.jpg)" }}
>
{/* Overlay for background image */}
<div className="absolute inset-0 bg-black bg-opacity-20"></div>
{/* Content on top of background image */}
<div className="relative z-10 text-white max-w-3xl space-y-6 lg:space-y-8">
<h1 className="text-2xl lg:text-5xl font-bold leading-tight">
The Ultimate Marketplace for{" "}
<span className="text-black font-bold text-2xl lg:text-5xl block my-3 uppercase">
Used Vehicles
</span>
</h1>
<p className="text-sm lg:text-xl leading-relaxed">
AutoFind connects buyers and sellers of pre-owned vehicles with a
seamless and trusted platform. Browse through thousands of listings,
filter by make, model, price, and location to find the perfect match.
</p>
<Button>Browse Vehicles</Button>
</div>
{/* Search form */}
<SearchContainer />
</div>
);
};
export default BannerContainer;
----------------------------------------------------------------------------------------------------------------------------
services-container.tsx
----------------------------------------------------------------------------------------------------------------------------
"use client";
import { Button } from "@/components/ui/button";
import Image from "next/image";
import React from "react";
export const ServicesContainer = () => {
const services = [
{
title: "Vehicle Inspection",
description:
"We provide detailed vehicle inspection reports to ensure you are getting the best value for your money.",
image:
"https://cdn.pixabay.com/photo/2019/01/30/11/22/oil-3964367_1280.jpg",
},
{
title: "Finance Assistance",
description:
"Our finance experts help you find the best loan options available, ensuring smooth and affordable financing.",
image:
"https://cdn.pixabay.com/photo/2014/07/06/13/55/calculator-385506_1280.jpg",
},
{
title: "Trade-In Valuation",
description:
"Get a fair trade-in valuation for your current vehicle and use it towards your next purchase.",
image:
"https://cdn.pixabay.com/photo/2018/01/23/03/39/handshake-3100563_1280.jpg",
},
{
title: "Warranty Options",
description:
"We offer extended warranty options for your peace of mind, so you can enjoy your vehicle worry-free.",
image:
"https://thumbs.dreamstime.com/b/warranty-pressing-button-virtual-screen-35134149.jpg",
},
];
return (
<div className=" mx-auto px-4 py-16">
{/* Title and Description */}
<div className="text-center mb-12">
<h2 className="text-5xl font-bold text-gray-900">Services</h2>
<p className="text-gray-600 mt-4">
Discover the wide range of services we offer to help you find the best
used vehicles that suit your needs.
</p>
</div>
{/* Services Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{services.map((service, index) => (
<div
key={index}
className="bg-neutral-100 shadow-lg rounded-lg p-6 hover:shadow-2xl transition-shadow duration-300 flex-1 flex flex-col md:flex-row items-start gap-2"
>
{/* Image */}
<div className="w-full min-w-[225px] h-52 md:h-40 overflow-hidden rounded-lg mb-4 lg:mb-0">
<Image
src={service.image}
alt={service.title}
width={500}
height={400}
className="w-full h-full object-cover"
priority
/>
</div>
<div className="space-y-3 pl-2">
{/* Title */}
<h3 className="text-xl font-semibold text-gray-900">
{service.title}
</h3>
{/* Description */}
<p className="text-gray-600 mt-2">{service.description}</p>
<Button>Read More</Button>
</div>
</div>
))}
</div>
</div>
);
};
export default ServicesContainer;
----------------------------------------------------------------------------------------------------------------------------
subscribe-now.tsx
----------------------------------------------------------------------------------------------------------------------------
"use client";
import React, { useState } from "react";
import { Mail } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
export const SubscribeNow = () => {
const [email, setEmail] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Handle the subscription logic here (e.g., API call)
toast("Subscribed", {
description: "You have successfully subscribed to our newsletter!",
});
};
return (
<div className="bg-gray-900 text-white py-16 rounded-3xl">
<div className="container mx-auto px-4">
{/* Title and Short Description */}
<div className="text-center mb-8">
<h2 className="text-4xl font-bold">Subscribe Now</h2>
<p className="text-gray-400 mt-2">
Stay updated with the latest vehicle offers and news. Subscribe to
our newsletter!
</p>
</div>
{/* Subscription Form */}
<div className="flex justify-center">
<form
onSubmit={handleSubmit}
className="w-full max-w-lg flex flex-col sm:flex-row gap-4 items-center"
>
<div className="relative w-full sm:flex-1">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" />
<Input
type="email"
className="w-full p-4 pl-12 h-12 rounded-lg bg-gray-800 border border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-white placeholder-gray-400"
placeholder="Enter your email"
value={email}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setEmail(e.target.value)
}
required
/>
</div>
<Button
type="submit"
className="px-8 py-4 h-12 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg transition-all duration-300 w-full sm:w-auto"
>
Subscribe
</Button>
</form>
</div>
</div>
</div>
);
};
export default SubscribeNow;
----------------------------------------------------------------------------------------------------------------------------
why-choose-us.tsx
----------------------------------------------------------------------------------------------------------------------------
"use client";
import { Users, ClipboardCheck, ThumbsUp, MapPin } from "lucide-react";
import React from "react";
export const WhyChooseUsContainer = () => {
const reasons = [
{
title: "Expert Team",
description:
"Our team consists of automotive experts who ensure you get top-quality service every time.",
icon: Users,
},
{
title: "Comprehensive Services",
description:
"We offer a full range of services, from inspection to financing, making your vehicle purchase seamless.",
icon: ClipboardCheck,
},
{
title: "Customer Satisfaction",
description:
"We prioritize customer satisfaction with transparent pricing and no hidden fees.",
icon: ThumbsUp,
},
{
title: "Nationwide Coverage",
description:
"Our network covers all major regions, giving you access to a vast inventory of vehicles.",
icon: MapPin,
},
];
return (
<div className="container mx-auto px-4 py-16">
{/* Title and Description */}
<div className="text-center mb-12">
<h2 className="text-4xl font-bold text-gray-900">Why Choose Us</h2>
<p className="text-gray-600 mt-4">
Here’s why we stand out from the competition and why we are the best
choice for your vehicle needs.
</p>
</div>
{/* Reasons Flex Layout */}
<div className="flex flex-col lg:flex-row gap-8">
{reasons.map((reason, index) => {
const IconComponent = reason.icon;
// Conditional styling for odd and even cards
const isOdd = index % 2 !== 0;
const iconBgClass = isOdd ? "bg-gray-900" : "bg-blue-100";
const iconColorClass = isOdd ? "text-white" : "text-blue-600";
return (
<div
key={index}
className="rounded-lg p-6 hover:shadow-2xl transition-shadow duration-300 flex-1 flex flex-col items-center text-center"
>
{/* Icon with conditional styling */}
<div
className={`mb-4 w-40 h-40 rounded-lg min-w-40 min-h-40 flex items-center justify-center ${iconBgClass}`}
>
<IconComponent size={75} className={iconColorClass} />
</div>
{/* Title */}
<h3 className="text-xl font-semibold text-gray-900 whitespace-nowrap">
{reason.title}
</h3>
{/* Description */}
<p className="text-gray-600 mt-2">{reason.description}</p>
</div>
);
})}
</div>
</div>
);
};
export default WhyChooseUsContainer;
----------------------------------------------------------------------------------------------------------------------------
seeds.ts
----------------------------------------------------------------------------------------------------------------------------
/* eslint-disable @typescript-eslint/ban-ts-comment */
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { PrismaClient } = require("@prisma/client");
const database = new PrismaClient();
// Function to generate slugs
// @ts-expect-error
const generateSlug = (name) => {
return name
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^\w\-]+/g, "");
};
const main = async () => {
try {
// Seed Categories for Used Cars
await database.category.createMany({
data: [
{ name: "Cars", slug: generateSlug("Cars") },
{ name: "Bikes", slug: generateSlug("Bikes") },
],
});
// Seed Sub-categories for Used Cars and Bikes
await database.subCategory.createMany({
data: [
// Subcategories for Cars
{
name: "SUV",
slug: generateSlug("SUV"),
categoryId: (
await database.category.findFirst({ where: { name: "Cars" } })
).id,
},
{
name: "Sedan",
slug: generateSlug("Sedan"),
categoryId: (
await database.category.findFirst({ where: { name: "Cars" } })
).id,
},
{
name: "Hatchback",
slug: generateSlug("Hatchback"),
categoryId: (
await database.category.findFirst({ where: { name: "Cars" } })
).id,
},
{
name: "Convertible",
slug: generateSlug("Convertible"),
categoryId: (
await database.category.findFirst({ where: { name: "Cars" } })
).id,
},
{
name: "Coupe",
slug: generateSlug("Coupe"),
categoryId: (
await database.category.findFirst({ where: { name: "Cars" } })
).id,
},
{
name: "Pickup Truck",
slug: generateSlug("Pickup Truck"),
categoryId: (
await database.category.findFirst({ where: { name: "Cars" } })
).id,
},
{
name: "Minivan",
slug: generateSlug("Minivan"),
categoryId: (
await database.category.findFirst({ where: { name: "Cars" } })
).id,
},
{
name: "Electric",
slug: generateSlug("Electric"),
categoryId: (
await database.category.findFirst({ where: { name: "Cars" } })
).id,
},
// Subcategories for Bikes
{
name: "Cruiser",
slug: generateSlug("Cruiser"),
categoryId: (
await database.category.findFirst({ where: { name: "Bikes" } })
).id,
},
{
name: "Sports Bike",
slug: generateSlug("Sports Bike"),
categoryId: (
await database.category.findFirst({ where: { name: "Bikes" } })
).id,
},
{
name: "Touring",
slug: generateSlug("Touring"),
categoryId: (
await database.category.findFirst({ where: { name: "Bikes" } })
).id,
},
{
name: "Dirt Bike",
slug: generateSlug("Dirt Bike"),
categoryId: (
await database.category.findFirst({ where: { name: "Bikes" } })
).id,
},
{
name: "Scooter",
slug: generateSlug("Scooter"),
categoryId: (
await database.category.findFirst({ where: { name: "Bikes" } })
).id,
},
{
name: "Electric Bike",
slug: generateSlug("Electric Bike"),
categoryId: (
await database.category.findFirst({ where: { name: "Bikes" } })
).id,
},
{
name: "Naked Bike",
slug: generateSlug("Naked Bike"),
categoryId: (
await database.category.findFirst({ where: { name: "Bikes" } })
).id,
},
],
});
console.log(
"Seeding successful for categories and sub-categories related to used cars and bikes."
);
} catch (error) {
console.log(`Error on seeding the database for used vehicles: ${error}`);
} finally {
await database.$disconnect();
}
};
main();
----------------------------------------------------------------------------------------------------------------------------
helpers.ts
----------------------------------------------------------------------------------------------------------------------------
const carMakes = [
{ label: "Honda", value: "honda" },
{ label: "Toyota", value: "toyota" },
{ label: "Ford", value: "ford" },
{ label: "Chevrolet", value: "chevrolet" },
{ label: "BMW", value: "bmw" },
{ label: "Mercedes-Benz", value: "mercedes-benz" },
{ label: "Audi", value: "audi" },
{ label: "Hyundai", value: "hyundai" },
{ label: "Kia", value: "kia" },
{ label: "Volkswagen", value: "volkswagen" },
{ label: "Nissan", value: "nissan" },
{ label: "Mazda", value: "mazda" },
{ label: "Subaru", value: "subaru" },
{ label: "Lexus", value: "lexus" },
{ label: "Jaguar", value: "jaguar" },
{ label: "Land Rover", value: "land-rover" },
{ label: "Tesla", value: "tesla" },
{ label: "Volvo", value: "volvo" },
{ label: "Porsche", value: "porsche" },
{ label: "Mitsubishi", value: "mitsubishi" },
{ label: "Ferrari", value: "ferrari" },
{ label: "Lamborghini", value: "lamborghini" },
{ label: "Aston Martin", value: "aston-martin" },
{ label: "Bentley", value: "bentley" },
{ label: "Bugatti", value: "bugatti" },
];
const bikeMakes = [
{ label: "Yamaha", value: "yamaha" },
{ label: "Suzuki", value: "suzuki" },
{ label: "Kawasaki", value: "kawasaki" },
{ label: "Ducati", value: "ducati" },
{ label: "Harley-Davidson", value: "harley-davidson" },
{ label: "Royal Enfield", value: "royal-enfield" },
{ label: "Triumph", value: "triumph" },
{ label: "KTM", value: "ktm" },
{ label: "Indian", value: "indian" },
{ label: "Aprilia", value: "aprilia" },
{ label: "MV Agusta", value: "mv-agusta" },
{ label: "Benelli", value: "benelli" },
{ label: "Bajaj", value: "bajaj" },
{ label: "Hero", value: "hero" },
{ label: "TVS", value: "tvs" },
{ label: "Vespa", value: "vespa" },
{ label: "Husqvarna", value: "husqvarna" },
{ label: "Moto Guzzi", value: "moto-guzzi" },
{ label: "Cagiva", value: "cagiva" },
];
export const vehicleModels = [
{ label: "Civic", value: "civic" },
{ label: "Accord", value: "accord" },
{ label: "Corolla", value: "corolla" },
{ label: "Camry", value: "camry" },
{ label: "Mustang", value: "mustang" },
{ label: "F-150", value: "f-150" },
{ label: "Ranger", value: "ranger" },
{ label: "Altima", value: "altima" },
{ label: "Sentra", value: "sentra" },
{ label: "Pathfinder", value: "pathfinder" },
{ label: "Cherokee", value: "cherokee" },
{ label: "Wrangler", value: "wrangler" },
{ label: "Model 3", value: "model-3" },
{ label: "Model S", value: "model-s" },
{ label: "Model X", value: "model-x" },
{ label: "Model Y", value: "model-y" },
{ label: "RX", value: "rx" },
{ label: "NX", value: "nx" },
{ label: "CX-5", value: "cx-5" },
{ label: "CX-9", value: "cx-9" },
{ label: "CR-V", value: "cr-v" },
{ label: "Pilot", value: "pilot" },
{ label: "Rav4", value: "rav4" },
{ label: "Highlander", value: "highlander" },
{ label: "Outback", value: "outback" },
{ label: "Forester", value: "forester" },
{ label: "Impreza", value: "impreza" },
{ label: "3 Series", value: "3-series" },
{ label: "5 Series", value: "5-series" },
{ label: "C-Class", value: "c-class" },
{ label: "E-Class", value: "e-class" },
{ label: "Q5", value: "q5" },
{ label: "Q7", value: "q7" },
{ label: "X3", value: "x3" },
{ label: "X5", value: "x5" },
{ label: "Golf", value: "golf" },
{ label: "Jetta", value: "jetta" },
{ label: "Tiguan", value: "tiguan" },
{ label: "Atlas", value: "atlas" },
{ label: "Explorer", value: "explorer" },
{ label: "Edge", value: "edge" },
{ label: "Escape", value: "escape" },
{ label: "Bronco", value: "bronco" },
];
export const indiaStatesAndDistricts = [
{
state: "Tamil Nadu",
districts: [
{ district: "Chennai", postalCodes: [600001, 600002, 600003] },
{ district: "Coimbatore", postalCodes: [641001, 641002, 641003] },
{ district: "Madurai", postalCodes: [625001, 625002, 625003] },
],
},
{
state: "Maharashtra",
districts: [
{ district: "Mumbai", postalCodes: [400001, 400002, 400003] },
{ district: "Pune", postalCodes: [411001, 411002, 411003] },
{ district: "Nagpur", postalCodes: [440001, 440002, 440003] },
],
},
{
state: "Karnataka",
districts: [
{ district: "Bengaluru", postalCodes: [560001, 560002, 560003] },
{ district: "Mysuru", postalCodes: [570001, 570002, 570003] },
{ district: "Mangaluru", postalCodes: [575001, 575002, 575003] },
],
},
{
state: "Uttar Pradesh",
districts: [
{ district: "Lucknow", postalCodes: [226001, 226002, 226003] },
{ district: "Kanpur", postalCodes: [208001, 208002, 208003] },
{ district: "Varanasi", postalCodes: [221001, 221002, 221003] },
],
},
{
state: "West Bengal",
districts: [
{ district: "Kolkata", postalCodes: [700001, 700002, 700003] },
{ district: "Howrah", postalCodes: [711101, 711102, 711103] },
{ district: "Darjeeling", postalCodes: [734101, 734102, 734103] },
],
},
{
state: "Rajasthan",
districts: [
{ district: "Jaipur", postalCodes: [302001, 302002, 302003] },
{ district: "Jodhpur", postalCodes: [342001, 342002, 342003] },
{ district: "Udaipur", postalCodes: [313001, 313002, 313003] },
],
},
{
state: "Gujarat",
districts: [
{ district: "Ahmedabad", postalCodes: [380001, 380002, 380003] },
{ district: "Surat", postalCodes: [395001, 395002, 395003] },
{ district: "Vadodara", postalCodes: [390001, 390002, 390003] },
],
},
{
state: "Kerala",
districts: [
{ district: "Thiruvananthapuram", postalCodes: [695001, 695002, 695003] },
{ district: "Kochi", postalCodes: [682001, 682002, 682003] },
{ district: "Kozhikode", postalCodes: [673001, 673002, 673003] },
],
},
{
state: "Delhi",
districts: [
{ district: "Central Delhi", postalCodes: [110001, 110002, 110003] },
{ district: "South Delhi", postalCodes: [110011, 110012, 110013] },
{ district: "North Delhi", postalCodes: [110006, 110007, 110008] },
],
},
{
state: "Punjab",
districts: [
{ district: "Amritsar", postalCodes: [143001, 143002, 143003] },
{ district: "Ludhiana", postalCodes: [141001, 141002, 141003] },
{ district: "Jalandhar", postalCodes: [144001, 144002, 144003] },
],
},
];
export const vehicleMakes = [...carMakes, ...bikeMakes];
----------------------------------------------------------------------------------------------------------------------------
uploading-progress.tsx
----------------------------------------------------------------------------------------------------------------------------
<div className="relative w-32 h-32">
<svg className="w-full h-full" viewBox="0 0 100 100">
<circle
className="text-gray-200 stroke-current"
stroke-width="10"
cx="50"
cy="50"
r="40"
fill="transparent"
></circle>
<circle
className="text-indigo-500 animate-pulse progress-ring__circle stroke-current transition-all duration-500"
stroke-width="10"
stroke-linecap="round"
cx="50"
cy="50"
r="40"
fill="transparent"
stroke-dasharray="251.2"
stroke-dashoffset={`calc(251.2 - (251.2 * ${progress}) / 100)`}
></circle>
<text
x="50"
y="50"
font-family="Verdana"
font-size="12"
text-anchor="middle"
alignment-baseline="middle"
>
{progress.toFixed(2)}%
</text>
</svg>
</div>
----------------------------------------------------------------------------------------------------------------------------
data-table.tsx
----------------------------------------------------------------------------------------------------------------------------
"use client";
import { useState } from "react";
import {
ColumnDef,
ColumnFiltersState,
SortingState,
flexRender,
getFilteredRowModel,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
searchKey: string;
}
export function DataTable<TData, TValue>({
columns,
data,
searchKey,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
state: {
sorting,
columnFilters,
},
});
return (
<div>
<div className="flex items-center py-4">
<Input
placeholder={`Search by ${searchKey}`}
value={(table.getColumn(searchKey)?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn(searchKey)?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
);
}
----------------------------------------------------------------------------------------------------------------------------
columns.tsx
----------------------------------------------------------------------------------------------------------------------------
"use client";
import Image from "next/image";
import { ColumnDef } from "@tanstack/react-table";
import { ArrowUpDown } from "lucide-react";
import { Button } from "@/components/ui/button";
import { CellAction } from "./cell-actions"; // Assuming you have this component
import { format } from "date-fns";
// Define the types for each vehicle column, including status
export type VehiclesColumn = {
id: string;
make: string;
model: string;
year: number;
price: string; // Assuming price is formatted as a string
category: string; // Category name instead of ID
subCategory: string; // Sub-category name instead of ID
coverImage: string; // Cover image URL
createdAt: string; // Formatted date string
status: string; // Status is now explicitly typed
owner: { name: string; profileImage: string };
};
// Define the columns for the table
export const columns: ColumnDef<VehiclesColumn>[] = [
{
accessorKey: "owner",
header: "Owner",
cell: ({ row }) => {
const { owner } = row.original;
return (
<div className="flex items-center space-x-2">
<Image
width={32}
height={32}
className="object-cover rounded-full"
src={owner.profileImage}
alt={owner.name}
/>
</div>
);
},
},
{
accessorKey: "coverImage",
header: "Image",
cell: ({ row }) => {
const { coverImage } = row.original;
return (
<Image
width={80}
height={45}
className="object-cover rounded-md"
src={coverImage}
alt="Vehicle Image"
priority
/>
);
},
},
{
accessorKey: "make",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Make
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const { make } = row.original;
return <p className="w-24 truncate">{make}</p>;
},
},
{
accessorKey: "model",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Model
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const { model } = row.original;
return <p className="w-24 truncate">{model}</p>;
},
},
{
accessorKey: "category",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Category
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const { category } = row.original;
return <p className="w-24 truncate">{category}</p>;
},
},
{
accessorKey: "subCategory",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Sub Category
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const { subCategory } = row.original;
return <p className="w-24 truncate">{subCategory}</p>;
},
},
{
accessorKey: "year",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Year
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const { year } = row.original;
return <p>{year}</p>;
},
},
{
accessorKey: "price",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Price
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const { price } = row.original;
return <p>{price}</p>;
},
},
{
accessorKey: "createdAt",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Date
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const { createdAt } = row.original;
const formattedDate = format(new Date(createdAt), "dd/MM/yyyy"); // Format the date consistently
return <p>{formattedDate}</p>;
},
},
{
accessorKey: "status",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Status
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const { status } = row.original;
// Conditional styling based on the status
const statusStyles: Record<typeof status, string> = {
pending: "bg-yellow-200 text-yellow-700",
approved: "bg-green-200 text-green-700",
rejected: "bg-red-200 text-red-700",
};
return (
<span
className={`px-2 py-1 rounded-lg font-medium ${statusStyles[status]}`}
>
{status?.charAt(0).toUpperCase() + status?.slice(1)}
</span>
);
},
},
{
id: "actions",
cell: ({ row }) => <CellAction data={row.original} />, // Assuming you have a CellAction component
},
];
----------------------------------------------------------------------------------------------------------------------------
ContactClient.tsx
----------------------------------------------------------------------------------------------------------------------------
"use client";
import React from "react";
export const ContactClient = () => {
return (
<section className="bg-white dark:bg-gray-900">
<div className="container px-6 py-12 mx-auto">
<div className="text-center">
<h1 className="mt-2 text-2xl font-semibold text-gray-800 md:text-3xl dark:text-white">
Get in touch
</h1>
<p className="mt-3 text-gray-500 dark:text-gray-400">
Our friendly team is always here to chat.
</p>
</div>
<div className="grid grid-cols-1 gap-12 mt-10 md:grid-cols-2 lg:grid-cols-3">
<div className="flex flex-col items-center justify-center text-center">
<span className="p-3 text-blue-500 rounded-full bg-blue-100/80 dark:bg-gray-800">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
className="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"
/>
</svg>
</span>
<h2 className="mt-4 text-lg font-medium text-gray-800 dark:text-white">
Email
</h2>
<p className="mt-2 text-gray-500 dark:text-gray-400">
Our friendly team is here to help.
</p>
<p className="mt-2 text-blue-500 dark:text-blue-400">
[email protected]
</p>
</div>
<div className="flex flex-col items-center justify-center text-center">
<span className="p-3 text-blue-500 rounded-full bg-blue-100/80 dark:bg-gray-800">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
className="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z"
/>
</svg>
</span>
<h2 className="mt-4 text-lg font-medium text-gray-800 dark:text-white">
Office
</h2>
<p className="mt-2 text-gray-500 dark:text-gray-400">
Come say hello at our office HQ.
</p>
<p className="mt-2 text-blue-500 dark:text-blue-400">
100 Smith Street Collingwood VIC 3066 AU
</p>
</div>
<div className="flex flex-col items-center justify-center text-center">
<span className="p-3 text-blue-500 rounded-full bg-blue-100/80 dark:bg-gray-800">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
className="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z"
/>
</svg>
</span>
<h2 className="mt-4 text-lg font-medium text-gray-800 dark:text-white">
Phone
</h2>
<p className="mt-2 text-gray-500 dark:text-gray-400">
Mon-Fri from 8am to 5pm.
</p>
<p className="mt-2 text-blue-500 dark:text-blue-400">
+1 (555) 000-0000
</p>
</div>
</div>
<div className="p-4 py-6 rounded-lg md:p-8 mt-12">
<form>
<div className="-mx-2 md:items-center md:flex">
<div className="flex-1 px-2">
<label className="block mb-2 text-sm text-gray-600 dark:text-gray-200">
First Name
</label>
<input
type="text"
placeholder="John "
className="block w-full px-5 py-2.5 mt-2 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-lg dark:placeholder-gray-600 dark:bg-gray-900 dark:text-gray-300 dark:border-gray-700 focus:border-blue-400 dark:focus:border-blue-400 focus:ring-blue-400 focus:outline-none focus:ring focus:ring-opacity-40"
/>
</div>
<div className="flex-1 px-2 mt-4 md:mt-0">
<label className="block mb-2 text-sm text-gray-600 dark:text-gray-200">
Last Name
</label>
<input
type="text"
placeholder="Doe"
className="block w-full px-5 py-2.5 mt-2 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-lg dark:placeholder-gray-600 dark:bg-gray-900 dark:text-gray-300 dark:border-gray-700 focus:border-blue-400 dark:focus:border-blue-400 focus:ring-blue-400 focus:outline-none focus:ring focus:ring-opacity-40"
/>
</div>
</div>
<div className="mt-4">
<label className="block mb-2 text-sm text-gray-600 dark:text-gray-200">
Email address
</label>
<input
type="email"
placeholder="[email protected]"
className="block w-full px-5 py-2.5 mt-2 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-lg dark:placeholder-gray-600 dark:bg-gray-900 dark:text-gray-300 dark:border-gray-700 focus:border-blue-400 dark:focus:border-blue-400 focus:ring-blue-400 focus:outline-none focus:ring focus:ring-opacity-40"
/>
</div>
<div className="w-full mt-4">
<label className="block mb-2 text-sm text-gray-600 dark:text-gray-200">
Message
</label>
<textarea
className="block w-full h-32 px-5 py-2.5 mt-2 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-lg md:h-56 dark:placeholder-gray-600 dark:bg-gray-900 dark:text-gray-300 dark:border-gray-700 focus:border-blue-400 dark:focus:border-blue-400 focus:ring-blue-400 focus:outline-none focus:ring focus:ring-opacity-40"
placeholder="Message"
></textarea>
</div>
<button className="w-full px-6 py-3 mt-4 text-sm font-medium tracking-wide text-white capitalize transition-colors duration-300 transform bg-blue-500 rounded-lg hover:bg-blue-400 focus:outline-none focus:ring focus:ring-blue-300 focus:ring-opacity-50">
Send message
</button>
</form>
</div>
</div>
</section>
);
};
----------------------------------------------------------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment