Last active
October 24, 2024 06:10
-
-
Save Vetrivel-VP/96043efe0449fa19e4bec3cedc37ee7b to your computer and use it in GitHub Desktop.
YT-Used Vehicles - NextJs Prisma
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"> | |
</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