Skip to content

Commit 32e31fe

Browse files
committed
feat: add pro modal
1 parent 3eae55a commit 32e31fe

File tree

16 files changed

+369
-28
lines changed

16 files changed

+369
-28
lines changed

.env

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ REPLICATE_API_TOKEN=r8_3KjZq1tyartJ1tHb6BBeYggCaSra0uT0m0Vkm
1717
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
1818
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
1919

20-
DATABASE_URL='mysql://saeggh34vyen48kcdwmv:pscale_pw_gnIxUV0vYW9L5qc902GUmEsKK5ubGXNWnXfLq0OmcMY@aws.connect.psdb.cloud/deep-ai?sslaccept=strict'
20+
DATABASE_URL='mysql://saeggh34vyen48kcdwmv:pscale_pw_gnIxUV0vYW9L5qc902GUmEsKK5ubGXNWnXfLq0OmcMY@aws.connect.psdb.cloud/deep-ai?sslaccept=strict'
21+
22+
STRIPE_API_KEY=sk_test_51N4oJVSC93JRNtfEpJt7CkqPB24uzNJTKhhAbKucq1RbHwCmD1kQkhsgV0AooUwQk2I9U4VeIQOLsUbh5vkFT42d00EyPQ8pAn

app/(dashboard)/(routes)/code/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import { cn } from "@/lib/utils";
1919
import UserAvatar from "@/components/user-avatar";
2020
import { BotAvatar } from "@/components/bot-avatar";
2121
import ReactMarkdown from "react-markdown";
22+
import { useProModal } from "@/hooks/use-pro-modal";
2223

2324
const CodePage = () => {
25+
const proModal = useProModal();
2426
const router = useRouter();
2527
const [messages, setMessages] = useState<ChatCompletionRequestMessage[]>([]);
2628
const form = useForm<z.infer<typeof formSchema>>({
@@ -45,7 +47,9 @@ const CodePage = () => {
4547
setMessages((current) => [...current, userMessage, response.data]);
4648
form.reset();
4749
} catch (error: any) {
48-
console.log(error);
50+
if (error?.response?.status === 403) {
51+
proModal.onOpen();
52+
}
4953
} finally {
5054
router.refresh();
5155
}

app/(dashboard)/(routes)/conversation/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ import Loader from "@/components/loader";
1818
import { cn } from "@/lib/utils";
1919
import UserAvatar from "@/components/user-avatar";
2020
import { BotAvatar } from "@/components/bot-avatar";
21+
import { useProModal } from "@/hooks/use-pro-modal";
2122

2223
const ConversationPage = () => {
24+
const proModal = useProModal();
2325
const router = useRouter();
2426
const [messages, setMessages] = useState<ChatCompletionRequestMessage[]>([]);
2527
const form = useForm<z.infer<typeof formSchema>>({
@@ -44,7 +46,9 @@ const ConversationPage = () => {
4446
setMessages((current) => [...current, userMessage, response.data]);
4547
form.reset();
4648
} catch (error: any) {
47-
console.log(error);
49+
if (error?.response?.status === 403) {
50+
proModal.onOpen();
51+
}
4852
} finally {
4953
router.refresh();
5054
}

app/(dashboard)/(routes)/image/page.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ import {
2424
} from "@/components/ui/select";
2525
import { Card, CardFooter } from "@/components/ui/card";
2626
import Image from "next/image";
27+
import { useProModal } from "@/hooks/use-pro-modal";
2728

28-
const ConversationPage = () => {
29+
const ImagePage = () => {
30+
const proModal = useProModal();
2931
const router = useRouter();
3032
const [images, setImages] = useState<string[]>([]);
3133

@@ -44,11 +46,13 @@ const ConversationPage = () => {
4446
setImages([]);
4547
const response = await axios.post("/api/image", values);
4648
const urls = response.data.map((image: { url: string }) => image.url);
47-
48-
setImages(urls)
49+
50+
setImages(urls);
4951
form.reset();
5052
} catch (error: any) {
51-
console.log(error);
53+
if (error?.response?.status === 403) {
54+
proModal.onOpen();
55+
}
5256
} finally {
5357
router.refresh();
5458
}
@@ -185,4 +189,4 @@ const ConversationPage = () => {
185189
);
186190
};
187191

188-
export default ConversationPage;
192+
export default ImagePage;

app/(dashboard)/(routes)/music/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import { useRouter } from "next/navigation";
1515
import { ChatCompletionRequestMessage } from "openai";
1616
import { Empty } from "@/components/empty";
1717
import Loader from "@/components/loader";
18+
import { useProModal } from "@/hooks/use-pro-modal";
1819

1920
const MusicPage = () => {
21+
const proModal = useProModal();
2022
const router = useRouter();
2123
const [music, setMusic] = useState<string>();
2224
const form = useForm<z.infer<typeof formSchema>>({
@@ -36,7 +38,9 @@ const MusicPage = () => {
3638
setMusic(response.data.audio);
3739
form.reset();
3840
} catch (error: any) {
39-
console.log(error);
41+
if (error?.response?.status === 403) {
42+
proModal.onOpen();
43+
}
4044
} finally {
4145
router.refresh();
4246
}

app/(dashboard)/(routes)/video/page.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import axios from "axios";
1414
import { useRouter } from "next/navigation";
1515
import { Empty } from "@/components/empty";
1616
import Loader from "@/components/loader";
17+
import { useProModal } from "@/hooks/use-pro-modal";
1718

1819
const VideoPage = () => {
20+
const proModal = useProModal();
1921
const router = useRouter();
2022
const [video, setVideo] = useState<string>();
2123
const form = useForm<z.infer<typeof formSchema>>({
@@ -35,7 +37,9 @@ const VideoPage = () => {
3537
setVideo(response.data[0]);
3638
form.reset();
3739
} catch (error: any) {
38-
console.log(error);
40+
if (error?.response?.status === 403) {
41+
proModal.onOpen();
42+
}
3943
} finally {
4044
router.refresh();
4145
}
@@ -90,7 +94,10 @@ const VideoPage = () => {
9094
{!video && !isLoading && <Empty label="No Video Generated" />}
9195

9296
{video && (
93-
<video controls className="w-full aspect-video rounded-lg border bg-black mt-8">
97+
<video
98+
controls
99+
className="w-full aspect-video rounded-lg border bg-black mt-8"
100+
>
94101
<source src={video} />
95102
</video>
96103
)}

app/layout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import './globals.css'
22
import type { Metadata } from 'next'
33
import { Inter } from 'next/font/google'
44
import { ClerkProvider } from '@clerk/nextjs'
5+
import { ModalProvider } from '@/components/modal-provider'
56

67
const inter = Inter({ subsets: ['latin'] })
78

@@ -18,7 +19,10 @@ export default function RootLayout({
1819
return (
1920
<ClerkProvider>
2021
<html lang="en">
21-
<body className={inter.className}>{children}</body>
22+
<body className={inter.className}>
23+
<ModalProvider />
24+
{children}
25+
</body>
2226
</html>
2327
</ClerkProvider>
2428
)

components/FreeCounter.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { MAX_FREE_COUNTS } from "@/constants";
44
import { Progress } from "@/components/ui/progress";
55
import { Button } from "@/components/ui/button";
66
import { Zap } from "lucide-react";
7+
import { useProModal } from "@/hooks/use-pro-modal";
78

89
interface FreeCounterProps {
910
apiLimitCount: number;
1011
}
1112

1213
function FreeCounter({ apiLimitCount = 0 }: FreeCounterProps) {
1314
const [mounted, setMounted] = useState(false);
15+
const proModal = useProModal();
1416
useEffect(() => {
1517
setMounted(true);
1618
}, []);
@@ -26,10 +28,17 @@ function FreeCounter({ apiLimitCount = 0 }: FreeCounterProps) {
2628
<p>
2729
{apiLimitCount}/{MAX_FREE_COUNTS} Free Generations
2830
</p>
29-
<Progress className="h-3" value={apiLimitCount/MAX_FREE_COUNTS *100} />
31+
<Progress
32+
className="h-3"
33+
value={(apiLimitCount / MAX_FREE_COUNTS) * 100}
34+
/>
3035
</div>
31-
<Button variant="premium" className="w-full">
32-
Upgrade
36+
<Button
37+
onClick={proModal.onOpen}
38+
variant="premium"
39+
className="w-full"
40+
>
41+
Upgrade
3342
<Zap className="w-4 h-4 ml-2 fill-white" />
3443
</Button>
3544
</CardContent>

components/mobile-sidebar.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,20 @@ import { Sheet, SheetTrigger, SheetContent } from "@/components/ui/sheet";
66
import Sidebar from "./sidebar";
77
import { useEffect, useState } from "react";
88

9-
const MobileSidebar = () => {
10-
const [ismounted, setIsMounted]=useState(false);
9+
interface MobileSidebarProps {
10+
apiLimitCount: number;
11+
}
1112

12-
useEffect(()=>{
13-
setIsMounted(true);
14-
}, []);
13+
const MobileSidebar = ({ apiLimitCount }: MobileSidebarProps) => {
14+
const [ismounted, setIsMounted] = useState(false);
1515

16-
if(!ismounted){
17-
return null;
18-
}
16+
useEffect(() => {
17+
setIsMounted(true);
18+
}, []);
19+
20+
if (!ismounted) {
21+
return null;
22+
}
1923
return (
2024
<Sheet>
2125
<SheetTrigger>
@@ -24,7 +28,7 @@ const MobileSidebar = () => {
2428
</Button>
2529
</SheetTrigger>
2630
<SheetContent side="left" className="p-0">
27-
<Sidebar />
31+
<Sidebar apiLimitCount={apiLimitCount} />
2832
</SheetContent>
2933
</Sheet>
3034
);

components/modal-provider.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import ProModal from "@/components/pro-modal";
5+
6+
export const ModalProvider = () => {
7+
const [isMounted, setIsMounted] = useState(false);
8+
9+
useEffect(() => {
10+
setIsMounted(true);
11+
}, []);
12+
13+
if (!isMounted) {
14+
return null;
15+
}
16+
17+
return (
18+
<>
19+
<ProModal />
20+
</>
21+
);
22+
};

components/navbar.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { UserButton } from "@clerk/nextjs"
1+
import { UserButton } from "@clerk/nextjs";
22
import MobileSidebar from "@/components/mobile-sidebar";
3+
import { getApiLimitCount } from "@/lib/api-limits";
34

4-
const Navbar = () => {
5+
const Navbar = async () => {
6+
const apiLimitCount = await getApiLimitCount();
57
return (
68
<div className="flex items-center p-4">
7-
<MobileSidebar />
9+
<MobileSidebar apiLimitCount={apiLimitCount} />
810
<div className="flex w-full justify-end">
9-
<UserButton afterSignOutUrl="/" />
11+
<UserButton afterSignOutUrl="/" />
1012
</div>
1113
</div>
1214
);

components/pro-modal.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"use client";
2+
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogDescription,
7+
DialogFooter,
8+
DialogHeader,
9+
DialogTitle,
10+
} from "@/components/ui/dialog";
11+
import { useProModal } from "@/hooks/use-pro-modal";
12+
import { Badge } from "@/components/ui/badge";
13+
import { Check, MessageSquare, Zap } from "lucide-react";
14+
import { Music } from "lucide-react";
15+
import { ImageIcon } from "lucide-react";
16+
import { VideoIcon } from "lucide-react";
17+
import { Code } from "lucide-react";
18+
import { Card } from "@/components/ui/card";
19+
import { cn } from "@/lib/utils";
20+
import { Button } from "@/components/ui/button";
21+
22+
const tools = [
23+
{
24+
label: "Conversation",
25+
icon: MessageSquare,
26+
color: "text-violet-500",
27+
bgColor: "bg-violet-500/10",
28+
},
29+
{
30+
label: "Music Generation",
31+
icon: Music,
32+
color: "text-emerald-500",
33+
bgColor: "bg-emerald-500/10",
34+
},
35+
{
36+
label: "Image Generation",
37+
icon: ImageIcon,
38+
color: "text-pink-500",
39+
bgColor: "bg-pink-500/10",
40+
},
41+
{
42+
label: "Video Generation",
43+
icon: VideoIcon,
44+
color: "text-orange-500",
45+
bgColor: "bg-orange-500/10",
46+
},
47+
{
48+
label: "Code Generation",
49+
icon: Code,
50+
color: "text-green-500",
51+
bgColor: "bg-green-500/10",
52+
},
53+
];
54+
55+
const ProModal = () => {
56+
const proModal = useProModal();
57+
return (
58+
<div>
59+
<Dialog open={proModal.isOpen} onOpenChange={proModal.onClose}>
60+
<DialogContent>
61+
<DialogHeader>
62+
<DialogTitle className="flex justify-center items-center flex-col gap-y-4 pb-2">
63+
<div className="flex items-center gap-x-2 font-bold py-1">
64+
Upgrade to DeepAI
65+
<Badge variant="premium" className="uppercase text-sm py-1">
66+
pro
67+
</Badge>
68+
</div>
69+
</DialogTitle>
70+
<DialogDescription className="text-center pt-2 space-y-2 text-zinc-900 font-medium">
71+
{tools.map((tool) => (
72+
<Card
73+
key={tool.label}
74+
className="p-3 border-black/5 flex justify-between items-center"
75+
>
76+
<div className="flex items-center gap-x-4">
77+
<div className={cn("p-2 w-fit rounded-md", tool.bgColor)}>
78+
<tool.icon className={cn("w-6 h-6", tool.color)} />
79+
</div>
80+
<div className="font-semibold text-sm ">{tool.label}</div>
81+
</div>
82+
<Check className="text-primary w-5 h-5" />
83+
</Card>
84+
))}
85+
</DialogDescription>
86+
</DialogHeader>
87+
<DialogFooter>
88+
<Button size="lg" variant="premium" className="w-full">
89+
Upgrade
90+
<Zap className="w-4 h-4 ml-2 fill-white" />
91+
</Button>
92+
</DialogFooter>
93+
</DialogContent>
94+
</Dialog>
95+
</div>
96+
);
97+
};
98+
99+
export default ProModal;

0 commit comments

Comments
 (0)