Compare commits
7 Commits
main
...
d50555cbc4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d50555cbc4 | ||
|
|
51b81c40af | ||
|
|
7641675180 | ||
|
|
be63600ca3 | ||
|
|
a214192c41 | ||
|
|
c52777afa2 | ||
|
|
18ff5b9beb |
13
app/components/ShadcnButton.tsx
Normal file
13
app/components/ShadcnButton.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"use client";
|
||||||
|
import React from 'react';
|
||||||
|
import { Button } from '@shadcn/ui';
|
||||||
|
|
||||||
|
const ShadcnButton: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Button onClick={() => alert('Button clicked!')}>
|
||||||
|
Click me
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShadcnButton;
|
||||||
16
app/components/TailwindButton.tsx
Normal file
16
app/components/TailwindButton.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const TailwindButton: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => alert('Button clicked!')}
|
||||||
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||||
|
>
|
||||||
|
Click me
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TailwindButton;
|
||||||
120
app/globals.css
120
app/globals.css
@@ -1,26 +1,122 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
@import "tw-animate-css";
|
||||||
|
|
||||||
:root {
|
@custom-variant dark (&:is(.dark *));
|
||||||
--background: #ffffff;
|
|
||||||
--foreground: #171717;
|
|
||||||
}
|
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
--font-sans: var(--font-geist-sans);
|
--font-sans: var(--font-geist-sans);
|
||||||
--font-mono: var(--font-geist-mono);
|
--font-mono: var(--font-geist-mono);
|
||||||
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
|
--color-sidebar: var(--sidebar);
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
:root {
|
||||||
--background: #0a0a0a;
|
--radius: 0.625rem;
|
||||||
--foreground: #ededed;
|
--background: oklch(1 0 0);
|
||||||
}
|
--foreground: oklch(0.145 0 0);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
|
--primary: oklch(0.205 0 0);
|
||||||
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
--secondary: oklch(0.97 0 0);
|
||||||
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
|
--muted: oklch(0.97 0 0);
|
||||||
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
|
--accent: oklch(0.97 0 0);
|
||||||
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.922 0 0);
|
||||||
|
--input: oklch(0.922 0 0);
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--sidebar: oklch(0.985 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
.dark {
|
||||||
background: var(--background);
|
--background: oklch(0.145 0 0);
|
||||||
color: var(--foreground);
|
--foreground: oklch(0.985 0 0);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
--card: oklch(0.205 0 0);
|
||||||
|
--card-foreground: oklch(0.985 0 0);
|
||||||
|
--popover: oklch(0.205 0 0);
|
||||||
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
|
--primary: oklch(0.922 0 0);
|
||||||
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
|
--secondary: oklch(0.269 0 0);
|
||||||
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
|
--muted: oklch(0.269 0 0);
|
||||||
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
|
--accent: oklch(0.269 0 0);
|
||||||
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 15%);
|
||||||
|
--ring: oklch(0.556 0 0);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.205 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import type { Metadata } from "next";
|
|||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
|
import { Header } from "@/components/ui/header";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
@@ -27,7 +29,10 @@ export default function RootLayout({
|
|||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
|
<Header/>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
148
app/page.tsx
148
app/page.tsx
@@ -1,103 +1,69 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Header } from "@/components/ui/header";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
|
<div>
|
||||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
<main className="min-h-80" style={{ background: 'linear-gradient(180deg, #fff 10%, #eef0f7)' }}>
|
||||||
<Image
|
<section className="max-w-4xl mx-auto px-4 py-24">
|
||||||
className="dark:invert"
|
<h1 className="text-5xl font-extrabold text-center text-gray-900">
|
||||||
src="/next.svg"
|
Enhance Your Food Photos with Dishpix AI
|
||||||
alt="Next.js logo"
|
</h1>
|
||||||
width={180}
|
<p className="mt-6 text-xl text-center text-gray-600 max-w-2xl mx-auto">
|
||||||
height={38}
|
Dishpix is the perfect website for people who want to take their food photography to the next level. Whether you're a professional chef, a food blogger, or just someone who loves capturing delicious moments, Dishpix offers tools and features to help you create stunning food photos.
|
||||||
priority
|
</p>
|
||||||
/>
|
</section>
|
||||||
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
|
|
||||||
<li className="mb-2 tracking-[-.01em]">
|
|
||||||
Get started by editing{" "}
|
|
||||||
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
|
|
||||||
app/page.tsx
|
|
||||||
</code>
|
|
||||||
.
|
|
||||||
</li>
|
|
||||||
<li className="tracking-[-.01em]">
|
|
||||||
Save and see your changes instantly.
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
|
||||||
<a
|
|
||||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
Deploy now
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Read our docs
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
|
||||||
<a
|
{/*
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
Add a section here to display before/after photo and demonstrate how the application works
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
*/}
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
<section className="w-full" style={{ backgroundColor: 'rgb(248, 249, 254)' }}>
|
||||||
>
|
<div className="max-w-screen-xl mx-auto py-24">
|
||||||
|
<h2 className="text-3xl font-bold text-center text-gray-800 mb-4">See how Dishpix can help you improve your content online.</h2>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<h3 className="text-lg font-semibold text-center text-gray-700 mb-2">
|
||||||
|
Photo Comparison
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-col md:flex-row justify-center space-y-4 md:space-y-0 md:space-x-4 w-full">
|
||||||
|
<div className="w-full md:w-1/2 p-4 bg-gray-100 rounded-lg shadow-md border-2 border-solid border-color-indigo-500">
|
||||||
|
<h4 className="text-sm font-semibold text-center text-gray-700 mb-1">
|
||||||
|
Before
|
||||||
|
</h4>
|
||||||
<Image
|
<Image
|
||||||
aria-hidden
|
src="/before-photo.png"
|
||||||
src="/file.svg"
|
alt="Before photo"
|
||||||
alt="File icon"
|
width={150}
|
||||||
width={16}
|
height={113}
|
||||||
height={16}
|
className="w-full h-auto rounded"
|
||||||
/>
|
/>
|
||||||
Learn
|
</div>
|
||||||
</a>
|
<div className="w-full md:w-1/2 p-4 bg-gray-100 rounded-lg shadow-md border-2 border-solid border-color-indigo-500">
|
||||||
<a
|
<h4 className="text-sm font-semibold text-center text-gray-700 mb-1">
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
After
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
</h4>
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
<Image
|
||||||
aria-hidden
|
src="/after-photo.png"
|
||||||
src="/window.svg"
|
alt="After photo"
|
||||||
alt="Window icon"
|
width={150}
|
||||||
width={16}
|
height={113}
|
||||||
height={16}
|
className="w-full h-auto rounded"
|
||||||
/>
|
/>
|
||||||
Examples
|
</div>
|
||||||
</a>
|
</div>
|
||||||
<a
|
<div className="max-w-4xl mx-auto mt-8 text-center">
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
<h3 className="text-lg font-semibold text-gray-700 mb-2">
|
||||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
What Happened?
|
||||||
target="_blank"
|
</h3>
|
||||||
rel="noopener noreferrer"
|
<p className="text-gray-600">
|
||||||
>
|
Dishpix AI transforms ordinary food photos into stunning, professional-quality images. Our advanced algorithms enhance colors, adjust lighting, and add artistic touches to make your food photos look incredible. Simply upload your photo and let the magic happen!
|
||||||
<Image
|
</p>
|
||||||
aria-hidden
|
</div>
|
||||||
src="/globe.svg"
|
</div>
|
||||||
alt="Globe icon"
|
</div>
|
||||||
width={16}
|
</section>
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Go to nextjs.org →
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
21
components.json
Normal file
21
components.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.js",
|
||||||
|
"css": "app/globals.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide"
|
||||||
|
}
|
||||||
59
components/ui/button.tsx
Normal file
59
components/ui/button.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
|
outline:
|
||||||
|
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||||
|
ghost:
|
||||||
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
|
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
|
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||||
|
icon: "size-9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Button({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
asChild = false,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"button"> &
|
||||||
|
VariantProps<typeof buttonVariants> & {
|
||||||
|
asChild?: boolean
|
||||||
|
}) {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
data-slot="button"
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
||||||
228
components/ui/header.tsx
Normal file
228
components/ui/header.tsx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import Link from "next/link"
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { usePathname } from "next/navigation"
|
||||||
|
|
||||||
|
|
||||||
|
interface HeaderProps {
|
||||||
|
className?: string
|
||||||
|
isLoggedIn?: boolean
|
||||||
|
isAdmin?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigation = [
|
||||||
|
{ name: 'Home', href: '/', current: false, requiresAuth: false },
|
||||||
|
{ name: 'Pricing', href: '/pricing', current: false, requiresAuth: false },
|
||||||
|
{ name: 'FAQ', href: '/faq', current: false, requiresAuth: false },
|
||||||
|
{ name: 'Dashboard', href: '/dashboard', current: false, requiresAuth: false },
|
||||||
|
{ name: 'Projects', href: '/projects', current: false, requiresAuth: true },
|
||||||
|
{ name: 'Calendar', href: '/calendar', current: false, requiresAuth: false },
|
||||||
|
{ name: 'Reports', href: '/reports', current: false, requiresAuth: true },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function Header({ className, isLoggedIn = false, isAdmin = false }: HeaderProps) {
|
||||||
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||||
|
const [profileOpen, setProfileOpen] = useState(false)
|
||||||
|
const [activeNavItem, setActiveNavItem] = useState<string | null>(null)
|
||||||
|
|
||||||
|
// Use usePathname for client-side navigation
|
||||||
|
let pathname = usePathname()
|
||||||
|
|
||||||
|
// Update activeNavItem when pathname changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (pathname) {
|
||||||
|
setActiveNavItem(pathname)
|
||||||
|
}
|
||||||
|
}, [pathname])
|
||||||
|
|
||||||
|
// Only render the header after the pathname is available
|
||||||
|
if (!pathname) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className={cn("bg-gray-900 shadow-sm", className)}>
|
||||||
|
<nav className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex h-16 items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<Link href="/">
|
||||||
|
<img
|
||||||
|
className="h-8 w-auto"
|
||||||
|
src="/logo.svg"
|
||||||
|
alt="Your Company"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="hidden md:block">
|
||||||
|
<div className="ml-10 flex items-baseline space-x-4">
|
||||||
|
{navigation.map((item) => {
|
||||||
|
const isCurrent = activeNavItem === item.href
|
||||||
|
return (
|
||||||
|
(item.requiresAuth ? (isLoggedIn || isAdmin) : true) && (
|
||||||
|
<Link
|
||||||
|
key={item.name}
|
||||||
|
href={item.href}
|
||||||
|
className={cn(
|
||||||
|
isCurrent
|
||||||
|
? 'bg-gray-800 text-white'
|
||||||
|
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
|
||||||
|
'rounded-md px-3 py-2 text-sm font-medium transition-colors'
|
||||||
|
)}
|
||||||
|
aria-current={isCurrent ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isLoggedIn && (
|
||||||
|
<div className="hidden md:block">
|
||||||
|
<div className="ml-4 flex items-center md:ml-6">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
|
||||||
|
>
|
||||||
|
<span className="absolute -inset-1.5" />
|
||||||
|
<span className="sr-only">View notifications</span>
|
||||||
|
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Profile dropdown */}
|
||||||
|
<div className="relative ml-3">
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="relative flex max-w-xs items-center rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
|
||||||
|
onClick={() => setProfileOpen(!profileOpen)}
|
||||||
|
>
|
||||||
|
<span className="absolute -inset-1.5" />
|
||||||
|
<span className="sr-only">Open user menu</span>
|
||||||
|
<img
|
||||||
|
className="h-8 w-8 rounded-full"
|
||||||
|
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{profileOpen && (
|
||||||
|
<div className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||||
|
<Link href="/profile" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||||
|
Your Profile
|
||||||
|
</Link>
|
||||||
|
<Link href="/settings" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||||
|
Settings
|
||||||
|
</Link>
|
||||||
|
<Link href="/logout" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||||
|
Sign out
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="-mr-2 flex md:hidden">
|
||||||
|
{/* Mobile menu button */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="relative inline-flex items-center justify-center rounded-md bg-gray-800 p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
||||||
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
|
>
|
||||||
|
<span className="absolute -inset-0.5" />
|
||||||
|
<span className="sr-only">Open main menu</span>
|
||||||
|
{mobileMenuOpen ? (
|
||||||
|
<svg className="block h-6 w-6" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg className="block h-6 w-6" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Mobile menu */}
|
||||||
|
{mobileMenuOpen && (
|
||||||
|
<div className="md:hidden">
|
||||||
|
<div className="space-y-1 px-2 pb-3 pt-2 sm:px-3">
|
||||||
|
{navigation.map((item) => {
|
||||||
|
const isCurrent = activeNavItem === item.href
|
||||||
|
return (
|
||||||
|
(item.requiresAuth ? (isLoggedIn || isAdmin) : true) && (
|
||||||
|
<Link
|
||||||
|
key={item.name}
|
||||||
|
href={item.href}
|
||||||
|
className={cn(
|
||||||
|
isCurrent
|
||||||
|
? 'bg-gray-900 text-white'
|
||||||
|
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
|
||||||
|
'block rounded-md px-3 py-2 text-base font-medium transition-colors'
|
||||||
|
)}
|
||||||
|
aria-current={isCurrent ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-gray-700 pb-3 pt-4">
|
||||||
|
{isLoggedIn && (
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center px-5">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<img
|
||||||
|
className="h-10 w-10 rounded-full"
|
||||||
|
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3">
|
||||||
|
<div className="text-base font-medium leading-none text-white">User Name</div>
|
||||||
|
<div className="text-sm font-medium leading-none text-gray-400">user@example.com</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="relative ml-auto flex-shrink-0 rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
|
||||||
|
>
|
||||||
|
<span className="absolute -inset-1.5" />
|
||||||
|
<span className="sr-only">View notifications</span>
|
||||||
|
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 space-y-1 px-2">
|
||||||
|
<Link href="/profile" className="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white">
|
||||||
|
Your Profile
|
||||||
|
</Link>
|
||||||
|
<Link href="/settings" className="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white">
|
||||||
|
Settings
|
||||||
|
</Link>
|
||||||
|
<Link href="/logout" className="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white">
|
||||||
|
Sign out
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
6
lib/utils.ts
Normal file
6
lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
796
package-lock.json
generated
796
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -9,19 +9,26 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
"@shadcn/ui": "^0.0.4",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.539.0",
|
||||||
|
"next": "15.4.6",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"next": "15.4.6"
|
"tailwind-merge": "^3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@tailwindcss/postcss": "^4",
|
|
||||||
"tailwindcss": "^4",
|
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.4.6",
|
"eslint-config-next": "15.4.6",
|
||||||
"@eslint/eslintrc": "^3"
|
"tailwindcss": "^4",
|
||||||
|
"tw-animate-css": "^1.3.6",
|
||||||
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
public/after-photo.png
Normal file
BIN
public/after-photo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 MiB |
BIN
public/before-photo.png
Normal file
BIN
public/before-photo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 MiB |
4
public/logo.svg
Normal file
4
public/logo.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<circle cx="50" cy="50" r="40" fill="#007bff" />
|
||||||
|
<text x="50" y="55" font-size="20" text-anchor="middle" fill="white">Logo</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 203 B |
25
tailwind.config.js
Normal file
25
tailwind.config.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
const { createThemes } = require('@shadcn/ui');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
'./app/**/*.{js,ts,jsx,tsx}',
|
||||||
|
'./pages/**/*.{js,ts,jsx,tsx}',
|
||||||
|
'./components/**/*.{js,ts,jsx,tsx}',
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
createThemes({
|
||||||
|
light: {
|
||||||
|
background: '#ffffff',
|
||||||
|
foreground: '#171717',
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
background: '#0a0a0a',
|
||||||
|
foreground: '#ededed',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user