-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: ✨ created migrationis and sidebar
- Loading branch information
Showing
37 changed files
with
1,687 additions
and
21 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= | ||
CLERK_SECRET_KEY= | ||
|
||
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in | ||
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up | ||
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard | ||
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard | ||
|
||
OPENAI_API_KEY= | ||
REPLICATE_API_TOKEN= | ||
|
||
DATABASE_URL= | ||
|
||
STRIPE_API_KEY= | ||
STRIPE_WEBHOOK_SECRET= | ||
|
||
NEXT_PUBLIC_APP_URL="http://localhost:3000" |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { SignUp } from "@clerk/nextjs"; | ||
import { SignIn } from "@clerk/nextjs"; | ||
|
||
export default function Page() { | ||
return <SignUp />; | ||
return <SignIn />; | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import Navbar from "@/components/navbar"; | ||
import { Sidebar } from "@/components/sidebar"; | ||
import { getApiLimitCount } from "@/lib/api-limit"; | ||
import { checkSubscription } from "@/lib/subscription"; | ||
import { ReactNode } from "react"; | ||
|
||
export default async function DashboardLayout({ | ||
children, | ||
}: { | ||
children: ReactNode; | ||
}) { | ||
const apiLimitCount = await getApiLimitCount(); | ||
const isPro = await checkSubscription(); | ||
|
||
return ( | ||
<div className="h-full relative"> | ||
<div className="hidden h-full md:flex md:w-72 md:flex-col md:fixed md:inset-y-0 z-80 bg-gray-900"> | ||
<Sidebar isPro={isPro} apiLimitCount={apiLimitCount} /> | ||
</div> | ||
<main className="md:pl-72 pb-10"> | ||
<Navbar /> | ||
{children} | ||
</main> | ||
</div> | ||
); | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { create } from "zustand"; | ||
|
||
interface useProModalStore { | ||
isOpen: boolean; | ||
onOpen: () => void; | ||
onClose: () => void; | ||
} | ||
|
||
export const useProModal = create<useProModalStore>((set) => ({ | ||
isOpen: false, | ||
onOpen: () => set({ isOpen: true }), | ||
onClose: () => set({ isOpen: false }), | ||
})); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { Zap } from "lucide-react"; | ||
import { useEffect, useState } from "react"; | ||
|
||
import { useProModal } from "@/app/hooks/use-pro-modal"; | ||
import { Button } from "@/components/ui/button"; | ||
import { Card, CardContent } from "@/components/ui/card"; | ||
import { Progress } from "@/components/ui/progress"; | ||
import { MAX_FREE_COUNTS } from "@/constants"; | ||
|
||
export const FreeCounter = ({ | ||
isPro = false, | ||
apiLimitCount = 0, | ||
}: { | ||
isPro: boolean; | ||
apiLimitCount: number; | ||
}) => { | ||
const [mounted, setMounted] = useState(false); | ||
const proModal = useProModal(); | ||
|
||
useEffect(() => { | ||
setMounted(true); | ||
}, []); | ||
|
||
if (!mounted) { | ||
return null; | ||
} | ||
|
||
if (isPro) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className="px-3"> | ||
<Card className="bg-white/10 border-0"> | ||
<CardContent className="py-6"> | ||
<div className="text-center text-sm text-white mb-4 space-y-2"> | ||
<p> | ||
{apiLimitCount} / {MAX_FREE_COUNTS} Free Generations | ||
</p> | ||
<Progress | ||
className="h-3" | ||
value={(apiLimitCount / MAX_FREE_COUNTS) * 100} | ||
/> | ||
</div> | ||
<Button | ||
onClick={proModal.onOpen} | ||
variant="premium" | ||
className="w-full" | ||
> | ||
Upgrade | ||
<Zap className="w-4 h-4 ml-2 fill-white" /> | ||
</Button> | ||
</CardContent> | ||
</Card> | ||
</div> | ||
); | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
"use client"; | ||
|
||
import { Menu } from "lucide-react"; | ||
import { useEffect, useState } from "react"; | ||
|
||
import { Sidebar } from "@/components/sidebar"; | ||
import { Button } from "@/components/ui/button"; | ||
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; | ||
|
||
export const MobileSidebar = ({ | ||
apiLimitCount = 0, | ||
isPro = false, | ||
}: { | ||
apiLimitCount: number; | ||
isPro: boolean; | ||
}) => { | ||
const [isMounted, setIsMounted] = useState(false); | ||
|
||
useEffect(() => { | ||
setIsMounted(true); | ||
}, []); | ||
|
||
if (!isMounted) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Sheet> | ||
<SheetTrigger> | ||
<Button variant="ghost" size="icon" className="md:hidden"> | ||
<Menu /> | ||
</Button> | ||
</SheetTrigger> | ||
<SheetContent side="left" className="p-0"> | ||
<Sidebar isPro={isPro} apiLimitCount={apiLimitCount} /> | ||
</SheetContent> | ||
</Sheet> | ||
); | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { UserButton } from "@clerk/nextjs"; | ||
|
||
import { checkSubscription } from "@/lib/subscription"; | ||
import { getApiLimitCount } from "../lib/api-limit"; | ||
import { MobileSidebar } from "./mobile-sidebar"; | ||
|
||
export default async function Navbar() { | ||
const apiLimitCount = await getApiLimitCount(); | ||
const isPro = await checkSubscription(); | ||
|
||
return ( | ||
<div className="flex items-center p-4"> | ||
<MobileSidebar isPro={isPro} apiLimitCount={apiLimitCount} /> | ||
<div className="flex w-full justify-end"> | ||
<UserButton afterSignOutUrl="/" /> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
"use client"; | ||
|
||
import axios from "axios"; | ||
import { Check, Zap } from "lucide-react"; | ||
import { useState } from "react"; | ||
import { toast } from "react-hot-toast"; | ||
|
||
import { Badge } from "@/components/ui/badge"; | ||
import { Button } from "@/components/ui/button"; | ||
import { Card } from "@/components/ui/card"; | ||
import { | ||
Dialog, | ||
DialogContent, | ||
DialogDescription, | ||
DialogFooter, | ||
DialogHeader, | ||
DialogTitle, | ||
} from "@/components/ui/dialog"; | ||
import { tools } from "@/constants"; | ||
// import { useProModal } from "@/hooks/use-pro-modal"; | ||
import { cn } from "@/lib/utils"; | ||
import { useProModal } from "../hooks/use-pro-modal"; | ||
|
||
export const ProModal = () => { | ||
const proModal = useProModal(); | ||
const [loading, setLoading] = useState(false); | ||
|
||
const onSubscribe = async () => { | ||
try { | ||
setLoading(true); | ||
const response = await axios.get("/api/stripe"); | ||
|
||
window.location.href = response.data.url; | ||
} catch (error) { | ||
toast.error("Something went wrong"); | ||
} finally { | ||
setLoading(false); | ||
} | ||
}; | ||
|
||
return ( | ||
<Dialog open={proModal.isOpen} onOpenChange={proModal.onClose}> | ||
<DialogContent> | ||
<DialogHeader> | ||
<DialogTitle className="flex justify-center items-center flex-col gap-y-4 pb-2"> | ||
<div className="flex items-center gap-x-2 font-bold text-xl"> | ||
Upgrade to Genius | ||
<Badge variant="premium" className="uppercase text-sm py-1"> | ||
pro | ||
</Badge> | ||
</div> | ||
</DialogTitle> | ||
<DialogDescription className="text-center pt-2 space-y-2 text-zinc-900 font-medium"> | ||
{tools.map((tool) => ( | ||
<Card | ||
key={tool.href} | ||
className="p-3 border-black/5 flex items-center justify-between" | ||
> | ||
<div className="flex items-center gap-x-4"> | ||
<div className={cn("p-2 w-fit rounded-md", tool.bgColor)}> | ||
<tool.icon className={cn("w-6 h-6", tool.color)} /> | ||
</div> | ||
<div className="font-semibold text-sm">{tool.label}</div> | ||
</div> | ||
<Check className="text-primary w-5 h-5" /> | ||
</Card> | ||
))} | ||
</DialogDescription> | ||
</DialogHeader> | ||
<DialogFooter> | ||
<Button | ||
disabled={loading} | ||
onClick={onSubscribe} | ||
size="lg" | ||
variant="premium" | ||
className="w-full" | ||
> | ||
Upgrade | ||
<Zap className="w-4 h-4 ml-2 fill-white" /> | ||
</Button> | ||
</DialogFooter> | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
"use client"; | ||
|
||
import { | ||
Code, | ||
ImageIcon, | ||
LayoutDashboard, | ||
MessageSquare, | ||
Music, | ||
Settings, | ||
VideoIcon, | ||
} from "lucide-react"; | ||
import { Montserrat } from "next/font/google"; | ||
import Image from "next/image"; | ||
import Link from "next/link"; | ||
import { usePathname } from "next/navigation"; | ||
|
||
import { cn } from "../lib/utils"; | ||
import { FreeCounter } from "./free-counter"; | ||
|
||
const poppins = Montserrat({ weight: "600", subsets: ["latin"] }); | ||
|
||
const routes = [ | ||
{ | ||
label: "Dashboard", | ||
icon: LayoutDashboard, | ||
href: "/dashboard", | ||
color: "text-sky-500", | ||
}, | ||
{ | ||
label: "Conversation", | ||
icon: MessageSquare, | ||
href: "/conversation", | ||
color: "text-violet-500", | ||
}, | ||
{ | ||
label: "Image Generation", | ||
icon: ImageIcon, | ||
color: "text-pink-700", | ||
href: "/image", | ||
}, | ||
{ | ||
label: "Video Generation", | ||
icon: VideoIcon, | ||
color: "text-orange-700", | ||
href: "/video", | ||
}, | ||
{ | ||
label: "Music Generation", | ||
icon: Music, | ||
color: "text-emerald-500", | ||
href: "/music", | ||
}, | ||
{ | ||
label: "Code Generation", | ||
icon: Code, | ||
color: "text-green-700", | ||
href: "/code", | ||
}, | ||
{ | ||
label: "Settings", | ||
icon: Settings, | ||
href: "/settings", | ||
}, | ||
]; | ||
|
||
export const Sidebar = ({ | ||
apiLimitCount = 0, | ||
isPro = false, | ||
}: { | ||
apiLimitCount: number; | ||
isPro: boolean; | ||
}) => { | ||
const pathname = usePathname(); | ||
|
||
return ( | ||
<div className="space-y-4 py-4 flex flex-col h-full bg-[#111827] text-white"> | ||
<div className="px-3 py-2 flex-1"> | ||
<Link href="/dashboard" className="flex items-center pl-3 mb-14"> | ||
<div className="relative h-8 w-8 mr-4"> | ||
<Image fill alt="Logo" src="/logo.png" /> | ||
</div> | ||
<h1 className={cn("text-2xl font-bold", poppins.className)}> | ||
Genius | ||
</h1> | ||
</Link> | ||
<div className="space-y-1"> | ||
{routes.map((route) => ( | ||
<Link | ||
key={route.href} | ||
href={route.href} | ||
className={cn( | ||
"text-sm group flex p-3 w-full justify-start font-medium cursor-pointer hover:text-white hover:bg-white/10 rounded-lg transition", | ||
pathname === route.href | ||
? "text-white bg-white/10" | ||
: "text-zinc-400" | ||
)} | ||
> | ||
<div className="flex items-center flex-1"> | ||
<route.icon className={cn("h-5 w-5 mr-3", route.color)} /> | ||
{route.label} | ||
</div> | ||
</Link> | ||
))} | ||
</div> | ||
</div> | ||
<FreeCounter apiLimitCount={apiLimitCount} isPro={isPro} /> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.