quikfrontend
Version:
A CLI tool for generating React-Vite frontend projects with pre-configured setups
1,023 lines (922 loc) • 75.8 kB
JavaScript
#!/usr/bin/env node
const { program } = require("commander");
const inquirer = require("inquirer");
const { execSync } = require("child_process");
const fs = require("fs-extra");
const path = require("path");
const { v4: uuidv4 } = require("uuid");
// Utility function to execute shell commands
const runCommand = (command, options = {}) => {
try {
execSync(command, { stdio: "inherit", ...options });
} catch (error) {
console.error(`Error executing command: ${command}`, error);
process.exit(1);
}
};
// Utility function to write file with error handling
const writeFile = (filePath, content) => {
try {
fs.writeFileSync(filePath, content, "utf8");
console.log(`Created: ${filePath}`);
} catch (error) {
console.error(`Error writing file: ${filePath}`, error);
process.exit(1);
}
};
// Main CLI logic
async function main() {
program
.version("1.0.0")
.description("quikfrontend - A CLI tool for frontend project generation")
.action(async () => {
// Step 1: Prompt user for inputs
const answers = await inquirer.prompt([
{
type: "list",
name: "framework",
message: "Select a framework:",
choices: ["React"],
default: "React",
},
{
type: "list",
name: "language",
message: "Select a language:",
choices: ["JavaScript"],
default: "JavaScript",
},
{
type: "input",
name: "projectName",
message: "Enter project name:",
validate: (input) =>
input.trim() !== "" || "Project name cannot be empty",
},
]);
const { projectName } = answers;
const projectDir = path.join(process.cwd(), projectName);
const frontendDir = path.join(process.cwd(), "Frontend");
// Step 2: Create Vite project
console.log(`Creating Vite project: ${projectName}...`);
runCommand(`npm create vite@latest ${projectName} -- --template react`, {
stdio: "inherit",
});
// Step 3: Rename project folder to 'Frontend'
if (fs.existsSync(frontendDir)) {
console.log(`Removing existing 'Frontend' folder...`);
fs.removeSync(frontendDir);
}
fs.renameSync(projectDir, frontendDir);
console.log(`Renamed project folder to 'Frontend'`);
// Step 4: Change to Frontend working directory
process.chdir(frontendDir);
// Step 5: Install dependencies
console.log("Installing dependencies...");
runCommand("npm install");
runCommand(
"npm install tailwindcss @tailwindcss/vite react-router-dom react-hot-toast react-redux @react-oauth/google @reduxjs/toolkit axios lucide-react react-otp-input"
);
// Step 6: Configure vite.config.js
const viteConfigContent = `import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite';
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
})`;
writeFile(path.join(frontendDir, "vite.config.js"), viteConfigContent);
// Step 7: Replace index.css
const indexCssContent = `@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Raleway:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&family=Yatra+One&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Raleway:ital,wght@0,100..900;1,100..900&family=Yatra+One&display=swap");
@import "tailwindcss";
@plugin "tailwindcss-animate";
@theme {
--font-roboto: "Roboto", "sans-serif";
--font-raleway: "Raleway", "sans-serif";
--font-poppins: "Poppins", "sans-serif";
/* --breakpoint-3xl: 1920px; */
--color-dark-blue: #1e2251;
--color-green: #13ec00;
--color-avocado: #5c8001;
--color-superiory-blue: #6ea4bf;
--color-ebony: #626c66;
--color-light-blue: #a7cecb;
--color-madder: #b10f2e;
--color-light-gray: #e5e5e5;
--color-yellow: #FFD700;
--color-rose: #FF2E63;
--color-red: #FF0000;
--color-purple: #9747FF;
--color-sky-blue: #4FACFE;
--color-indigo: #4Fhaviors
--color-teal: #08A9BB;
--color-cerulean: #06B6D4;
--color-logo-blue: #00F2FE;
--ease-fluid: cubic-bezier(0.3, 0, 0, 1);
--ease-snappy: cubic-bezier(0.2, 0, 0, 1);
/* ... */
}
@layer utilities {
.scrollbar-hide {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
/* Chrome, Safari and Opera */
}
}
@layer utilities {
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
.fade-out {
animation: fadeOut 0.5s ease-in-out;
}
.glow {
animation: glow 3s infinite ease-in-out;
}
.gradient-glow {
animation: gradient 15s infinite alternate;
}
.pulse {
animation: pulse 2s infinite; /* Added duration for completeness */
}
}
/* Define keyframes */
@keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; }
}
@keyframes glow {
0%, 100% { box-shadow: 0 0 15px rgba(59, 130, 246, 0.5); }
50% { box-shadow: 0 0 30px rgba(59, 130, 246, 0.8); }
}
@keyframes gradient {
0% { background-position: 0% 50%; }
100% { background-position: 100% 50%; } /* Fixed to create a visible shift */
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 15px rgba(59, 130, 246, 0.5); }
50% { box-shadow: 0 0 30px rgba(59, 130, 246, 0.8); }
}`;
writeFile(path.join(frontendDir, "src", "index.css"), indexCssContent);
// Step 8: Delete App.css
fs.removeSync(path.join(frontendDir, "src", "App.css"));
console.log("Deleted: src/App.css");
// Step 9: Create RoutesConfig.jsx
const routesConfigContent = `import React from "react";
import { Routes, Route } from "react-router-dom";
import { useSelector } from "react-redux";
import { HeroPage, Landing, Login, VerifyEmail } from "./components";
import { dashboardMenuState } from "./app/DashboardSlice";
import { isUserLoggedIn } from "./app/DashboardSlice";
import NavBar from "./components/protected/Dashboard/NavBar";
import Sidebar from "./components/utils/Sidebar";
import Dashboard from "./components/protected/Dashboard/Dashboard";
const RoutesConfig = () => {
const isLoggedIn = useSelector(isUserLoggedIn);
const ifDMenuState = useSelector(dashboardMenuState);
if (!isLoggedIn) {
return (
<Routes>
<Route
path="/"
key={"home"}
className="transition-all scrollbar-hide"
element={[<HeroPage key={"HeroPage"}/>]}
/>
<Route
path="/login"
className="transition-all scrollbar-hide"
element={[<Login />]}
/>
<Route
path="/verify-email"
className="transition-all scrollbar-hide"
element={[<VerifyEmail />]}
/>
<Route
path="/landing"
className="transition-all scrollbar-hide"
element={[<Landing />]}
/>
</Routes>
);
} else {
return (
<div className={\`w-full h-[100vh] bg-[#121212] flex flex-col overflow-y-auto scrollbar-hide\`}>
<Sidebar isOpen={ifDMenuState} />
<NavBar />
<div className={\`\${ifDMenuState && "pl-[4rem]"} \`}>
<Routes>
<Route path="/" element={<Dashboard />} /> {/* Default route */}
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
<div className="bg-gray-900 b-2 pr-2 text-sm pb-1 flex justify-end items-center">
<p className="text-white">Designed and Developed with ❤️ by <a href="https://hareshkurade.netlify.app" className="text-green-400">quikfrontend</a></p>
</div>
</div>
</div>
);
}
};
export default RoutesConfig;`;
writeFile(
path.join(frontendDir, "src", "RoutesConfig.jsx"),
routesConfigContent
);
// Step 10: Rewrite main.jsx
const mainJsxContent = `import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import { BrowserRouter } from 'react-router-dom';
import { Toaster } from 'react-hot-toast';
import { Provider } from 'react-redux';
import Store from './app/store.js';
import { GoogleOAuthProvider } from '@react-oauth/google';
const client_id = import.meta.env.VITE_PUBLIC_GOOGLE_CLIENT;
createRoot(document.getElementById('root')).render(
<BrowserRouter>
<StrictMode>
<GoogleOAuthProvider clientId={client_id}>
<Provider store={Store}>
<Toaster position='top-center' reverseOrder={true}/>
<App />
</Provider>
</GoogleOAuthProvider>
</StrictMode>
</BrowserRouter>
)`;
writeFile(path.join(frontendDir, "src", "main.jsx"), mainJsxContent);
// Step 11: Rewrite App.jsx
const appJsxContent = `import RoutesConfig from "./RoutesConfig"
function App() {
return (
<RoutesConfig />
)
}
export default App`;
writeFile(path.join(frontendDir, "src", "App.jsx"), appJsxContent);
// Step 12: Create app folder and files
fs.ensureDirSync(path.join(frontendDir, "src", "app"));
const dashboardSliceContent = `import { createSlice } from "@reduxjs/toolkit";
const localData = JSON.parse(localStorage.getItem("account"));
const Dstate = JSON.parse(localStorage.getItem("dState"))
const initialState = {
dashboardMenuState: true,
dashboardFeature: Dstate ? Dstate : "Home", //dashboard, orders, appointment, messages, profile
account: localData ? localData : [],
isLoggedIn: localData ? localData.isLoggedIn :true,
profileData: [],
};
const DashboardSlice = createSlice({
initialState,
name: "dashboard",
reducers: {
setOpenDMenu: (state, action) => {
state.dashboardMenuState = action.payload.dashboardMenuState;
},
setCloseDMenu: (state, action) => {
state.dashboardMenuState = action.payload.dashboardMenuState;
},
setDFeature: (state, action) => {
state.dashboardFeature = action.payload.dashboardFeature;
localStorage.setItem("dState", JSON.stringify(action.payload.dashboardFeature));
},
setAccount: (state, action) =>{
state.account = action.payload;
state.isLoggedIn = true;
const temp = {...state.account, "isLoggedIn": state.isLoggedIn};
localStorage.setItem("account", JSON.stringify(temp));
},
LogOut: (state, action) =>{
state.account = [];
state.profileData = [];
state.isLoggedIn = false;
state.dashboardMenuState = false;
state.dashboardFeature = "dashboard";
localStorage.clear();
},
setAccountAfterRegister: (state, action) => {
state.account = action.payload;
state.isLoggedIn = false;
const temp1 = {...state.account, "isLoggedIn": state.isLoggedIn};
localStorage.setItem("account", JSON.stringify(temp1));
},
}
});
export const {setOpenDMenu, setCloseDMenu, setDFeature, setAccount, setAccountAfterRegister, LogOut} = DashboardSlice.actions;
export const dashboardMenuState = (state) => state.dashboard.dashboardMenuState;
export const dashboardFeature = (state) => state.dashboard.dashboardFeature;
export const isUserLoggedIn = (state) => state.dashboard.isLoggedIn;
export const selectAccount = (state) => state.dashboard.account;
export const selectProfileData = (state) => state.dashboard.profileData;
export default DashboardSlice.reducer;`;
writeFile(
path.join(frontendDir, "src", "app", "DashboardSlice.js"),
dashboardSliceContent
);
const storeJsContent = `import { configureStore } from "@reduxjs/toolkit";
import DashboardSlice from "./DashboardSlice.js";
const Store = configureStore({
reducer:{
dashboard: DashboardSlice,
}
});
export default Store;`;
writeFile(
path.join(frontendDir, "src", "app", "store.js"),
storeJsContent
);
// Step 13: Create components folder and structure
fs.ensureDirSync(path.join(frontendDir, "src", "components", "common"));
fs.ensureDirSync(path.join(frontendDir, "src", "components", "data"));
fs.ensureDirSync(
path.join(frontendDir, "src", "components", "protected", "Dashboard")
);
fs.ensureDirSync(
path.join(frontendDir, "src", "components", "protected", "Home")
);
fs.ensureDirSync(path.join(frontendDir, "src", "components", "utils"));
const componentsIndexContent = `export {default as Landing } from './common/Landing';
export {default as Login } from './common/Login';
export {default as VerifyEmail } from './common/VerifyEmail';
export {default as HeroPage } from './common/HeroPage';`;
writeFile(
path.join(frontendDir, "src", "components", "index.js"),
componentsIndexContent
);
// Common folder files
const heroPageContent = `import { useState, useEffect } from 'react';
import { Menu, X, ChevronRight, Star, CheckCircle, Code, Zap, Settings } from 'lucide-react';
export default function HeroPage() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 10);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return (
<div className="min-h-screen flex flex-col">
{/* Navbar */}
<header className={\`fixed w-full z-30 transition-all duration-300 \${
scrolled ? "bg-white shadow-md" : "bg-transparent"
}\`}>
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center py-4">
<div className="flex items-center">
<span className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-teal-500 text-transparent bg-clip-text">Your Brand</span>
</div>
{/* Desktop Navigation */}
<nav className="hidden md:flex items-center space-x-8">
<a href="#features" className="text-gray-700 hover:text-blue-600 transition-colors">Features</a>
<a href="#about" className="text-gray-700 hover:text-blue-600 transition-colors">About</a>
<a href="#contact" className="text-gray-700 hover:text-blue-600 transition-colors">Contact</a>
<a href="#" className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition-colors">Get Started</a>
</nav>
{/* Mobile menu button */}
<div className="md:hidden">
<button onClick={() => setIsMenuOpen(!isMenuOpen)} className="text-gray-700">
{isMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
</button>
</div>
</div>
</div>
{/* Mobile Navigation */}
{isMenuOpen && (
<div className="md:hidden bg-white">
<div className="px-2 pt-2 pb-3 space-y-1 sm:px-3">
<a href="#features" className="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-blue-600 hover:bg-gray-50" onClick={() => setIsMenuOpen(false)}>Features</a>
<a href="#about" className="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-blue-600 hover:bg-gray-50" onClick={() => setIsMenuOpen(false)}>About</a>
<a href="#contact" className="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-blue-600 hover:bg-gray-50" onClick={() => setIsMenuOpen(false)}>Contact</a>
<a href="#" className="block px-3 py-2 rounded-md text-base font-medium bg-blue-600 text-white hover:bg-blue-700" onClick={() => setIsMenuOpen(false)}>Get Started</a>
</div>
</div>
)}
</header>
{/* Hero Section */}
<section className="pt-32 pb-20 md:pt-36 md:pb-24 bg-gradient-to-br from-gray-50 to-gray-100">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex flex-col md:flex-row items-center justify-between">
<div className="md:w-1/2 md:pr-8 mb-10 md:mb-0">
<div className="animate-fade-in-up">
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight mb-4">
Welcome to <span className="bg-gradient-to-r from-blue-600 to-teal-500 text-transparent bg-clip-text">Your New Project</span>
</h1>
<p className="text-lg md:text-xl text-gray-600 mb-8">
This beautiful landing page was generated with quikfrontend. Customize it to build something amazing.
</p>
<div className="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button className="bg-blue-600 text-white px-6 py-3 rounded-md hover:bg-blue-700 transition-all transform hover:scale-105 flex items-center justify-center">
Get Started
<ChevronRight className="ml-2 h-4 w-4" />
</button>
<button className="border border-gray-300 text-gray-700 px-6 py-3 rounded-md hover:bg-gray-50 transition-all flex items-center justify-center">
Learn More
</button>
</div>
</div>
</div>
<div className="md:w-1/2 animate-float">
<div className="bg-white rounded-xl shadow-xl p-6 border border-gray-200">
<div className="relative overflow-hidden rounded-lg bg-gradient-to-br from-blue-500 to-teal-400 aspect-video flex items-center justify-center">
<div className="text-white text-4xl font-bold">Your Product</div>
<div className="absolute -bottom-6 -right-6 h-24 w-24 bg-yellow-400 rounded-full opacity-50"></div>
<div className="absolute top-4 left-4 h-12 w-12 bg-white rounded-full opacity-20"></div>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Features Section */}
<section id="features" className="py-16 md:py-24 bg-white">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center max-w-3xl mx-auto mb-16">
<h2 className="text-3xl md:text-4xl font-bold mb-4">Amazing Features</h2>
<p className="text-lg text-gray-600">Everything you need to showcase your product or service.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-all hover:-translate-y-1 duration-300">
<div className="mb-4">
<Zap className="h-8 w-8 text-blue-500" />
</div>
<h3 className="text-xl font-semibold mb-2">Lightning Fast</h3>
<p className="text-gray-600">Optimized for speed and performance right out of the box.</p>
</div>
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-all hover:-translate-y-1 duration-300">
<div className="mb-4">
<Code className="h-8 w-8 text-blue-500" />
</div>
<h3 className="text-xl font-semibold mb-2">Clean Code</h3>
<p className="text-gray-600">Built with modern best practices for maintainable code.</p>
</div>
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-all hover:-translate-y-1 duration-300">
<div className="mb-4">
<Settings className="h-8 w-8 text-blue-500" />
</div>
<h3 className="text-xl font-semibold mb-2">Customizable</h3>
<p className="text-gray-600">Easily modify all aspects to match your brand identity.</p>
</div>
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-all hover:-translate-y-1 duration-300">
<div className="mb-4">
<CheckCircle className="h-8 w-8 text-blue-500" />
</div>
<h3 className="text-xl font-semibold mb-2">Reliable</h3>
<p className="text-gray-600">Thoroughly tested components you can depend on.</p>
</div>
</div>
</div>
</section>
{/* About Section */}
<section id="about" className="py-16 md:py-24 bg-gradient-to-br from-blue-50 to-teal-50">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex flex-col md:flex-row items-center">
<div className="md:w-1/2 mb-10 md:mb-0 md:pr-8">
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200 transform rotate-1 hover:rotate-0 transition-all duration-300">
<div className="aspect-video relative rounded-md bg-gradient-to-br from-blue-100 to-teal-100 flex items-center justify-center overflow-hidden">
<div className="absolute w-20 h-20 bg-blue-500 rounded-full -top-10 -left-10 opacity-20"></div>
<div className="absolute w-32 h-32 bg-teal-500 rounded-full -bottom-16 -right-16 opacity-20"></div>
<div className="z-10 text-2xl font-bold text-gray-800">About Us</div>
</div>
</div>
</div>
<div className="md:w-1/2 md:pl-8">
<h2 className="text-3xl md:text-4xl font-bold mb-4">Our Story</h2>
<p className="text-lg text-gray-600 mb-6">
Replace this with your company's story. Tell your visitors who you are, what you do, and why they should choose you. Make it personal and engaging.
</p>
<ul className="space-y-4">
<li className="flex items-start">
<div className="flex-shrink-0 h-6 w-6 rounded-full bg-green-100 flex items-center justify-center text-green-500 mr-3">✓</div>
<p className="text-gray-600">Share your company's mission and values</p>
</li>
<li className="flex items-start">
<div className="flex-shrink-0 h-6 w-6 rounded-full bg-green-100 flex items-center justify-center text-green-500 mr-3">✓</div>
<p className="text-gray-600">Highlight what makes your offering unique</p>
</li>
<li className="flex items-start">
<div className="flex-shrink-0 h-6 w-6 rounded-full bg-green-100 flex items-center justify-center text-green-500 mr-3">✓</div>
<p className="text-gray-600">Build trust with testimonials and social proof</p>
</li>
</ul>
<div className="mt-8">
<button className="bg-blue-600 text-white px-6 py-3 rounded-md hover:bg-blue-700 transition-all transform hover:scale-105 flex items-center">
Learn more
<ChevronRight className="ml-2 h-4 w-4" />
</button>
</div>
</div>
</div>
</div>
</section>
{/* Testimonials Section */}
<section className="py-16 md:py-24 bg-white">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center max-w-3xl mx-auto mb-16">
<h2 className="text-3xl md:text-4xl font-bold mb-4">What Our Customers Say</h2>
<p className="text-lg text-gray-600">Don't just take our word for it. Here's what people are saying.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-all">
<div className="flex items-center mb-4">
<div className="text-yellow-400 flex">
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
</div>
</div>
<p className="text-gray-600 italic mb-4">"Replace this with a real testimonial from one of your satisfied customers. Make it specific and authentic."</p>
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-gradient-to-br from-blue-400 to-teal-400 mr-3"></div>
<div>
<h4 className="font-medium">Customer Name</h4>
<p className="text-sm text-gray-500">Position, Company</p>
</div>
</div>
</div>
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-all">
<div className="flex items-center mb-4">
<div className="text-yellow-400 flex">
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
</div>
</div>
<p className="text-gray-600 italic mb-4">"Another testimonial here. Focus on the specific results or benefits your customers have experienced."</p>
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-gradient-to-br from-blue-400 to-teal-400 mr-3"></div>
<div>
<h4 className="font-medium">Customer Name</h4>
<p className="text-sm text-gray-500">Position, Company</p>
</div>
</div>
</div>
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-all">
<div className="flex items-center mb-4">
<div className="text-yellow-400 flex">
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
<Star className="h-5 w-5 fill-current" />
</div>
</div>
<p className="text-gray-600 italic mb-4">"A third testimonial to reinforce your credibility. Real testimonials from happy customers are incredibly powerful."</p>
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-gradient-to-br from-blue-400 to-teal-400 mr-3"></div>
<div>
<h4 className="font-medium">Customer Name</h4>
<p className="text-sm text-gray-500">Position, Company</p>
</div>
</div>
</div>
</div>
</div>
</section>
{/* CTA Section */}
<section className="py-16 md:py-24 bg-blue-600 text-white">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="max-w-3xl mx-auto text-center">
<h2 className="text-3xl md:text-4xl font-bold mb-6">Ready to get started?</h2>
<p className="text-xl text-blue-100 mb-8">Join thousands of satisfied customers today.</p>
<div className="bg-white rounded-lg p-1 flex flex-col sm:flex-row max-w-lg mx-auto">
<input
type="email"
placeholder="Enter your email"
className="flex-grow px-4 py-3 rounded-l-md focus:outline-none text-gray-800"
/>
<button className="bg-blue-600 text-white px-6 py-3 rounded-md sm:rounded-l-none hover:bg-blue-700 transition-all mt-2 sm:mt-0">
Sign Up
</button>
</div>
<p className="text-sm text-blue-200 mt-4">No credit card required. Free 14-day trial.</p>
</div>
</div>
</section>
{/* Contact Section */}
<section id="contact" className="py-16 md:py-24 bg-white">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="max-w-4xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold mb-4">Get In Touch</h2>
<p className="text-lg text-gray-600">We'd love to hear from you. Here's how you can reach us.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<h3 className="text-xl font-semibold mb-4">Send us a message</h3>
<form>
<div className="mb-4">
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">Name</label>
<input
type="text"
id="name"
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Your name"
/>
</div>
<div className="mb-4">
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input
type="email"
id="email"
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="your@email.com"
/>
</div>
<div className="mb-4">
<label htmlFor="message" className="block text-sm font-medium text-gray-700 mb-1">Message</label>
<textarea
id="message"
rows="4"
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="How can we help you?"
></textarea>
</div>
<button type="submit" className="bg-blue-600 text-white px-6 py-3 rounded-md hover:bg-blue-700 transition-colors">
Send Message
</button>
</form>
</div>
<div>
<h3 className="text-xl font-semibold mb-4">Contact Information</h3>
<div className="space-y-4">
<div>
<h4 className="font-medium">Address</h4>
<p className="text-gray-600">123 Street Name, City, Country</p>
</div>
<div>
<h4 className="font-medium">Phone</h4>
<p className="text-gray-600">+1 (555) 123-4567</p>
</div>
<div>
<h4 className="font-medium">Email</h4>
<p className="text-gray-600">contact@yourcompany.com</p>
</div>
<div>
<h4 className="font-medium">Hours</h4>
<p className="text-gray-600">Monday - Friday: 9am - 5pm</p>
<p className="text-gray-600">Saturday & Sunday: Closed</p>
</div>
</div>
<div className="mt-8">
<h4 className="font-medium mb-4">Follow us</h4>
<div className="flex space-x-4">
<a href="#" className="h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 hover:bg-blue-100 hover:text-blue-600 transition-colors">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" /></svg>
</a>
<a href="#" className="h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 hover:bg-blue-100 hover:text-blue-600 transition-colors">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723 10.054 10.054 0 01-3.127 1.184A4.92 4.92 0 0011.78 8.28 13.98 13.98 0 016.407 5.07a4.928 4.928 0 001.522 6.57 4.887 4.887 0 01-2.228-.616v.061A4.926 4.926 0 009.52 15.95a4.916 4.916 0 01-2.228.084 4.93 4.93 0 004.6 3.42A9.88 9.88 0 010 21.44a14 14 0 007.548 2.212c9.057 0 14.01-7.502 14.01-14.01 0-.213-.005-.426-.015-.637a10.025 10.025 0 002.46-2.548l-.047-.02z" /></svg>
</a>
<a href="#" className="h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 hover:bg-blue-100 hover:text-blue-600 transition-colors">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0C8.74 0 8.333.015 7.053.072 5.775.132 4.905.333 4.14.63c-.789.306-1.459.717-2.126 1.384S.935 3.35.63 4.14C.333 4.905.131 5.775.072 7.053.012 8.333 0 8.74 0 12s.015 3.667.072 4.947c.06 1.277.261 2.148.558 2.913a5.885 5.885 0 001.384 2.126A5.868 5.868 0 004.14 23.37c.766.296 1.636.499 2.913.558C8.333 23.988 8.74 24 12 24s3.667-.015 4.947-.072c1.277-.06 2.148-.262 2.913-.558a5.898 5.898 0 002.126-1.384 5.86 5.86 0 001.384-2.126c.296-.765.499-1.636.558-2.913.06-1.28.072-1.687.072-4.947s-.015-3.667-.072-4.947c-.06-1.277-.262-2.149-.558-2.913a5.89 5.89 0 00-1.384-2.126A5.847 5.847 0 0019.86.63c-.765-.297-1.636-.499-2.913-.558C15.667.012 15.26 0 12 0zm0 2.16c3.203 0 3.585.016 4.85.071 1.17.055 1.805.249 2.227.415.562.217.96.477 1.382.896.419.42.679.819.896 1.381.164.422.36 1.057.415 2.227.055 1.266.07 1.646.07 4.85s-.015 3.585-.074 4.85c-.061 1.17-.256 1.805-.421 2.227a3.81 3.81 0 01-.899 1.382 3.744 3.744 0 01-1.38.896c-.42.164-1.065.36-2.235.413-1.274.057-1.649.07-4.859.07-3.211 0-3.586-.015-4.859-.074-1.171-.061-1.816-.256-2.236-.421a3.716 3.716 0 01-1.379-.899 3.644 3.644 0 01-.9-1.38c-.165-.42-.359-1.065-.42-2.235-.045-1.26-.061-1.649-.061-4.844 0-3.196.016-3.586.061-4.861.061-1.17.255-1.814.42-2.234.21-.57.479-.96.9-1.381.419-.419.81-.689 1.379-.898.42-.166 1.051-.361 2.221-.421 1.275-.045 1.65-.06 4.859-.06l.045.03zm0 3.678a6.162 6.162 0 100 12.324 6.162 6.162 0 100-12.324zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm7.846-10.405a1.441 1.441 0 01-2.88 0 1.44 1.44 0 012.88 0z" /></svg>
</a>
<a href="#" className="h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 hover:bg-blue-100 hover:text-blue-600 transition-colors">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M22.23 0H1.77C.8 0 0 .77 0 1.72v20.56C0 23.23.8 24 1.77 24h20.46c.98 0 1.77-.77 1.77-1.72V1.72C24 .77 23.2 0 22.23 0zM7.27 20.1H3.65V9.24h3.62V20.1zM5.47 7.76h-.03c-1.22 0-2-.83-2-1.87 0-1.06.8-1.87 2.05-1.87 1.24 0 2 .8 2.02 1.87 0 1.04-.78 1.87-2.05 1.87zM20.34 20.1h-3.63v-5.8c0-1.45-.52-2.45-1.83-2.45-1 0-1.6.67-1.87 1.32-.1.23-.11.55-.11.88v6.05H9.28s.05-9.82 0-10.84h3.63v1.54a3.6 3.6 0 013.26-1.8c2.39 0 4.18 1.56 4.18 4.89v6.21z" /></svg>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Footer */}
<footer className="bg-gray-900 text-gray-300 py-12">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
<div>
<h3 className="text-xl font-bold mb-4 text-white">Your Brand</h3>
<p className="mb-4">A short description of your company or product goes here. Make it compelling.</p>
<div className="flex space-x-4">
<a href="#" className="text-gray-400 hover:text-white transition-colors">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" /></svg>
</a>
<a href="#" className="text-gray-400 hover:text-white transition-colors">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723 10.054 10.054 0 01-3.127 1.184A4.92 4.92 0 0011.78 8.28 13.98 13.98 0 016.407 5.07a4.928 4.928 0 001.522 6.57 4.887 4.887 0 01-2.228-.616v.061A4.926 4.926 0 009.52 15.95a4.916 4.916 0 01-2.228.084 4.93 4.93 0 004.6 3.42A9.88 9.88 0 010 21.44a14 14 0 007.548 2.212c9.057 0 14.01-7.502 14.01-14.01 0-.213-.005-.426-.015-.637a10.025 10.025 0 002.46-2.548l-.047-.02z" /></svg>
</a>
<a href="#" className="text-gray-400 hover:text-white transition-colors">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0C8.74 0 8.333.015 7.053.072 5.775.132 4.905.333 4.14.63c-.789.306-1.459.717-2.126 1.384S.935 3.35.63 4.14C.333 4.905.131 5.775.072 7.053.012 8.333 0 8.74 0 12s.015 3.667.072 4.947c.06 1.277.261 2.148.558 2.913a5.885 5.885 0 001.384 2.126A5.868 5.868 0 004.14 23.37c.766.296 1.636.499 2.913.558C8.333 23.988 8.74 24 12 24s3.667-.015 4.947-.072c1.277-.06 2.148-.262 2.913-.558a5.898 5.898 0 002.126-1.384 5.86 5.86 0 001.384-2.126c.296-.765.499-1.636.558-2.913.06-1.28.072-1.687.072-4.947s-.015-3.667-.072-4.947c-.06-1.277-.262-2.149-.558-2.913a5.89 5.89 0 00-1.384-2.126A5.847 5.847 0 0019.86.63c-.765-.297-1.636-.499-2.913-.558C15.667.012 15.26 0 12 0zm0 2.16c3.203 0 3.585.016 4.85.071 1.17.055 1.805.249 2.227.415.562.217.96.477 1.382.896.419.42.679.819.896 1.381.164.422.36 1.057.415 2.227.055 1.266.07 1.646.07 4.85s-.015 3.585-.074 4.85c-.061 1.17-.256 1.805-.421 2.227a3.81 3.81 0 01-.899 1.382 3.744 3.744 0 01-1.38.896c-.42.164-1.065.36-2.235.413-1.274.057-1.649.07-4.859.07-3.211 0-3.586-.015-4.859-.074-1.171-.061-1.816-.256-2.236-.421a3.716 3.716 0 01-1.379-.899 3.644 3.644 0 01-.9-1.38c-.165-.42-.359-1.065-.42-2.235-.045-1.26-.061-1.649-.061-4.844 0-3.196.016-3.586.061-4.861.061-1.17.255-1.814.42-2.234.21-.57.479-.96.9-1.381.419-.419.81-.689 1.379-.898.42-.166 1.051-.361 2.221-.421 1.275-.045 1.65-.06 4.859-.06l.045.03zm0 3.678a6.162 6.162 0 100 12.324 6.162 6.162 0 100-12.324zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm7.846-10.405a1.441 1.441 0 01-2.88 0 1.44 1.44 0 012.88 0z" /></svg>
</a>
</div>
</div>
<div>
<h4 className="text-white font-medium mb-4">Products</h4>
<ul className="space-y-2">
<li><a href="#" className="hover:text-white transition-colors">Features</a></li>
<li><a href="#" className="hover:text-white transition-colors">Pricing</a></li>
<li><a href="#" className="hover:text-white transition-colors">FAQ</a></li>
<li><a href="#" className="hover:text-white transition-colors">Downloads</a></li>
</ul>
</div>
<div>
<h4 className="text-white font-medium mb-4">Company</h4>
<ul className="space-y-2">
<li><a href="#" className="hover:text-white transition-colors">About</a></li>
<li><a href="#" className="hover:text-white transition-colors">Blog</a></li>
<li><a href="#" className="hover:text-white transition-colors">Careers</a></li>
<li><a href="#" className="hover:text-white transition-colors">Press</a></li>
</ul>
</div>
<div>
<h4 className="text-white font-medium mb-4">Legal</h4>
<ul className="space-y-2">
<li><a href="#" className="hover:text-white transition-colors">Terms</a></li>
<li><a href="#" className="hover:text-white transition-colors">Privacy</a></li>
<li><a href="#" className="hover:text-white transition-colors">Cookies</a></li>
<li><a href="#" className="hover:text-white transition-colors">Licenses</a></li>
</ul>
</div>
</div>
<div className="border-t border-gray-800 mt-12 pt-8 flex flex-col md:flex-row justify-between items-center">
<p>© {new Date().getFullYear()} Your Company. All rights reserved.</p>
<div className="mt-4 md:mt-0">
<a href="#" className="text-gray-400 hover:text-white transition-colors mr-4">Privacy Policy</a>
<a href="#" className="text-gray-400 hover:text-white transition-colors">Terms of Service</a>
</div>
</div>
</div>
</footer>
{/* Add animations */}
<style jsx>{\`
@keyframes float {
0% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
100% { transform: translateY(0px); }
}
@keyframes fade-in-up {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.animate-float {
animation: float 6s ease-in-out infinite;
}
.animate-fade-in-up {
animation: fade-in-up 1s ease-out;
}
\`}</style>
</div>
);
}`;
writeFile(
path.join(frontendDir, "src", "components", "common", "HeroPage.jsx"),
heroPageContent
);
const loginContent = `import React, { useState } from "react";
import { Eye, EyeOff } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { GoogleLogin } from "@react-oauth/google";
import { login, loginWithGoogle, register } from "../../services/repository/userRepo";
const Login = () => {
const [isLoginView, setIsLoginView] = useState(true);
const [showPassword, setShowPassword] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [name, setName] = useState("");
const [mobile, setMobile] = useState("");
const navigate = useNavigate();
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
if (isLoginView) {
dispatch(login(email, password, navigate));
} else {
dispatch(register(name, email, password, mobile, navigate));
}
};
const handleSuccess = async (credentialResponse) => {
dispatch(loginWithGoogle(credentialResponse, navigate));
};
const handleError = () => {
alert("Google Sign-In failed");
};
const toggleView = () => {
setIsLoginView(!isLoginView);
};
return (
<div className="flex items-center justify-center min-h-screen bg-gray-50">
<div className="w-full max-w-md bg-white rounded-lg shadow-md p-8">
{/* Logo */}
<div className="flex justify-center mb-6">
<div className="w-12 h-12 bg-green-500 rounded-full flex items-center justify-center">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17 8C8 10 5.9 16.17 3.82 21.34L5.71 22L6.66 19.7C7.14 19.87 7.64 20 8 20C19 20 22 3 22 3C21 5 14 5.25 9 6.25C4 7.25 2 11.5 2 13.5C2 15.5 3.75 17.25 3.75 17.25C7 8 17 8 17 8Z"
fill="white"
/>
</svg>
</div>
</div>
{isLoginView ? (
<div>
<h2 className="text-2xl font-bold text-center mb-6 text-gray-800">Sign In</h2>
<form className="space-y-4" onSubmit={handleSubmit}>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
Email
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
placeholder="Enter your email"
required
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
Password
</label>
<div className="relative">
<input
type={showPassword ? "text" : "password"}
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
placeholder="Enter your password"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500"
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center">
<input
id="remember-me"
name="remember-me"
type="checkbox"
className="h-4 w-4 text-green-600 focus:ring-green-500 border-gray-300 rounded"
/>
<label htmlFor="remember-me" className="ml-2 block text-sm text-gray-700">
Remember me
</label>
</div>
<a href="#" className="text-sm font-medium text-green-600 hover:text-green-500">
Forgot password?
</a>
</div>
<button
type="submit"
className="w-full bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700 transition"
>
Sign In
</button>
</form>
<div className="mt-6">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">Or continue with</span>
</div>
</div>
<div className="mt-6">
<div className="w-full">
<GoogleLogin
onSuccess={handleSuccess}
onError={handleError}
scope="email profile"
/>
</div>
</div>
</div>