├── .env.local.example ├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg └── vercel.svg ├── src ├── app │ ├── api │ │ └── copilotkit │ │ │ └── route.ts │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── components │ ├── app │ │ ├── App.tsx │ │ ├── CampaignForm.tsx │ │ ├── MainNav.tsx │ │ └── UserNav.tsx │ ├── dashboard │ │ ├── ActiveCampaigns.tsx │ │ ├── Dashboard.tsx │ │ ├── Overview.tsx │ │ ├── Segments.tsx │ │ └── TeamSwitcher.tsx │ └── ui │ │ ├── avatar.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── popover.tsx │ │ ├── select.tsx │ │ └── tabs.tsx └── lib │ ├── data.ts │ ├── guideline.ts │ ├── script.ts │ ├── types.ts │ └── utils.ts ├── tailwind.config.ts └── tsconfig.json /.env.local.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=xxxxxxx 2 | OPENAI_MODEL=gpt-4-1106-preview 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a demo that showcases using CopilotKit to build a PowerPoint like web app. 2 | 3 | ## Deploy with Vercel 4 | 5 | To deploy with Vercel, click the button below: 6 | 7 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FCopilotKit%2Fdemo-campaign-manager&env=NEXT_PUBLIC_COPILOT_CLOUD_API_KEY&project-name=copilotkit-demo-campaign-manager&repository-name=copilotkit-demo-campaign-manager) 8 | 9 | ## Getting Started 10 | 11 | ### 1. install the needed package: 12 | 13 | ```bash 14 | npm i 15 | ``` 16 | 17 | ### 2. Set the required environment variables: 18 | 19 | copy `.env.local.example` to `.env.local` and populate the required environment variables. 20 | 21 | > ⚠️ **Important:** Not all users have access to the GPT-4 model yet. If you don't have access, you can use GPT-3 by setting `OPENAI_MODEL` to `gpt-3.5-turbo` in the `.env.local` file. 22 | 23 | ### 3. Run the app 24 | 25 | ```bash 26 | npm run dev 27 | ``` 28 | 29 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 30 | 31 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 32 | 33 | ### 4. Use the Copilot 34 | 35 | Create a new campaign, or edit an existing campaign, and use the Copilot assistant to configure the form without requiring domain knowledge. 36 | 37 | ## Zoom in on the CopilotKit code 38 | 39 | 1. Search for `useCopilotReadable` to see where frontend application information is being made accessible to the Copilot engine 40 | 41 | 2. Search for `updateCurrentCampaign` and `retrieveHistoricalData` to see where the frontend application action is made accessible to the Copilot engine. 42 | 43 | ## Learn More 44 | 45 | To learn more about CopilotKit, take a look at the following resources: 46 | 47 | - [CopilotKit Documentation](https://docs.copilotkit.ai/getting-started/quickstart-chatbot) - learn about CopilotKit features and API. 48 | - [GitHub](https://github.com/CopilotKit/CopilotKit) - Check out the CopilotKit GitHub repository. 49 | - [Discord](https://discord.gg/6dffbvGU3D) - Join the CopilotKit Discord community. 50 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "campaign-manager-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@copilotkit/runtime": "1.0.0-beta.2", 13 | "@copilotkit/react-core": "1.0.0-beta.2", 14 | "@copilotkit/react-textarea": "1.0.0-beta.2", 15 | "@copilotkit/react-ui": "1.0.0-beta.2", 16 | "@copilotkit/shared": "1.0.0-beta.2", 17 | "@heroicons/react": "^2.1.1", 18 | "@radix-ui/react-avatar": "^1.0.4", 19 | "@radix-ui/react-dialog": "^1.0.5", 20 | "@radix-ui/react-dropdown-menu": "^2.0.6", 21 | "@radix-ui/react-icons": "^1.3.0", 22 | "@radix-ui/react-label": "^2.0.2", 23 | "@radix-ui/react-popover": "^1.0.7", 24 | "@radix-ui/react-select": "^2.0.0", 25 | "@radix-ui/react-slot": "^1.0.2", 26 | "@radix-ui/react-tabs": "^1.0.4", 27 | "class-variance-authority": "^0.7.0", 28 | "clsx": "^2.1.0", 29 | "cmdk": "^1.0.0", 30 | "date-fns": "^3.6.0", 31 | "lodash": "^4.17.21", 32 | "next": "14.1.4", 33 | "react": "^18", 34 | "react-day-picker": "^8.10.0", 35 | "react-dom": "^18", 36 | "recharts": "^2.12.3", 37 | "tailwind-merge": "^2.2.2", 38 | "tailwindcss-animate": "^1.0.7" 39 | }, 40 | "devDependencies": { 41 | "@types/lodash": "^4.17.0", 42 | "@types/node": "^20", 43 | "@types/react": "^18", 44 | "@types/react-dom": "^18", 45 | "autoprefixer": "^10.0.1", 46 | "eslint": "^8", 47 | "eslint-config-next": "14.1.4", 48 | "postcss": "^8", 49 | "tailwindcss": "^3.3.0", 50 | "typescript": "^5" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/copilotkit/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from "next/server"; 2 | import { 3 | CopilotRuntime, 4 | OpenAIAdapter, 5 | copilotRuntimeNextJSAppRouterEndpoint, 6 | } from "@copilotkit/runtime"; 7 | 8 | export const POST = async (req: NextRequest) => { 9 | const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({ 10 | runtime: new CopilotRuntime(), 11 | serviceAdapter: new OpenAIAdapter(), 12 | endpoint: req.nextUrl.pathname, 13 | }); 14 | 15 | return handleRequest(req); 16 | }; 17 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CopilotKit/demo-campaign-manager/2e8062603f0cdb54de8aed09cc07987dadc0d0f6/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | 37 | --copilot-kit-primary-color: var(--primary) !important; 38 | --copilot-kit-contrast-color: var(--secondary) !important; 39 | } 40 | } 41 | 42 | @layer base { 43 | * { 44 | @apply border-border; 45 | } 46 | body { 47 | @apply bg-background text-foreground; 48 | } 49 | } 50 | 51 | .copilotKitWindow { 52 | box-shadow: none !important; 53 | border-left: 1px solid hsl(var(--border)) !important; 54 | } 55 | 56 | .copilotKitHeader { 57 | height: 65px !important; 58 | } 59 | 60 | .copilotKitButton { 61 | background-color: hsl(var(--primary)) !important; 62 | color: hsl(var(--secondary)) !important; 63 | } 64 | 65 | .copilotKitMessage.copilotKitAssistantMessage { 66 | background: hsl(var(--muted)) !important; 67 | } 68 | 69 | .copilotKitMessage.copilotKitUserMessage { 70 | background-color: hsl(var(--primary)) !important; 71 | color: hsl(var(--secondary)) !important; 72 | } 73 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "@copilotkit/react-ui/styles.css"; 4 | import "./globals.css"; 5 | 6 | const inter = Inter({ subsets: ["latin"] }); 7 | 8 | export const metadata: Metadata = { 9 | title: "Create Next App", 10 | description: "Generated by create next app", 11 | }; 12 | 13 | export default function RootLayout({ 14 | children, 15 | }: Readonly<{ 16 | children: React.ReactNode; 17 | }>) { 18 | return ( 19 | 20 | {children} 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { App } from "@/components/app/App"; 3 | import { CopilotKit } from "@copilotkit/react-core"; 4 | import { CopilotSidebar } from "@copilotkit/react-ui"; 5 | 6 | export default function DashboardPage() { 7 | return ( 8 | 13 | 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/app/App.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { DEFAULT_CAMPAIGNS } from "@/lib/data"; 3 | import { Campaign } from "@/lib/types"; 4 | import { use, useState } from "react"; 5 | import { useCopilotAction, useCopilotReadable } from "@copilotkit/react-core"; 6 | 7 | import _ from "lodash"; 8 | import { Dashboard } from "../dashboard/Dashboard"; 9 | import { CampaignForm } from "./CampaignForm"; 10 | import { randomId } from "@/lib/utils"; 11 | import { GUIDELINE } from "@/lib/guideline"; 12 | import { SCRIPT_SUGGESTION } from "@/lib/script"; 13 | 14 | export function App() { 15 | const [segments, setSegments] = useState([ 16 | "Millennials/Female/Urban", 17 | "Parents/30s/Suburbs", 18 | "Seniors/Female/Rural", 19 | "Professionals/40s/Midwest", 20 | "Gamers/Male", 21 | ]); 22 | 23 | const [campaigns, setCampaigns] = useState( 24 | _.cloneDeep(DEFAULT_CAMPAIGNS) 25 | ); 26 | 27 | function saveCampaign(campaign: Campaign) { 28 | if (campaign.id === "") { 29 | campaign.id = randomId(); 30 | setCampaigns([campaign, ...campaigns]); 31 | } else { 32 | const index = campaigns.findIndex((c) => c.id === campaign.id); 33 | if (index === -1) { 34 | setCampaigns([...campaigns, campaign]); 35 | } else { 36 | campaigns[index] = campaign; 37 | setCampaigns([...campaigns]); 38 | } 39 | } 40 | } 41 | 42 | const [currentCampaign, setCurrentCampaign] = useState( 43 | undefined 44 | ); 45 | 46 | // Ground the Copilot with domain-specific knowledge for this use-case: marketing campaigns. 47 | useCopilotReadable({ description: "Guideline", value: GUIDELINE }); 48 | useCopilotReadable({ description: "Script", value: SCRIPT_SUGGESTION }); 49 | 50 | // Provide the Copilot with the current date. 51 | useCopilotReadable({ 52 | description: "Current Date", 53 | value: new Date().toDateString(), 54 | }); 55 | 56 | // Provide this component's Copilot with the ability to update the current campaign. 57 | // 58 | // This implementation uses a single large function with optional parameters to update the current campaign. 59 | // But you can also use multiple smaller actions to update different parts of the campaign - even one for each field. 60 | // Up to you. 61 | // 62 | // (In the near future we will provide CopilotForm types, which unify useCopilotReadable and useCopilotAction for a given form's values. 63 | // Feel free to ask about this on our Discord: https://discord.gg/t89H6TzmKm). 64 | useCopilotAction({ 65 | name: "updateCurrentCampaign", 66 | description: 67 | "Edit an existing campaign or create a new one. To update only a part of a campaign, provide the id of the campaign to edit and the new values only.", 68 | parameters: [ 69 | { 70 | name: "id", 71 | description: 72 | "The id of the campaign to edit. If empty, a new campaign will be created", 73 | type: "string", 74 | }, 75 | { 76 | name: "title", 77 | description: "The title of the campaign", 78 | type: "string", 79 | required: false, 80 | }, 81 | { 82 | name: "keywords", 83 | description: "Search keywords for the campaign", 84 | type: "string", 85 | required: false, 86 | }, 87 | { 88 | name: "url", 89 | description: 90 | "The URL to link the ad to. Most of the time, the user will provide this value, leave it empty unless asked by the user.", 91 | type: "string", 92 | required: false, 93 | }, 94 | { 95 | name: "headline", 96 | description: 97 | "The headline displayed in the ad. This should be a 5-10 words", 98 | type: "string", 99 | required: false, 100 | }, 101 | { 102 | name: "description", 103 | description: 104 | "The description displayed in the ad. This should be a short text", 105 | type: "string", 106 | required: false, 107 | }, 108 | 109 | { 110 | name: "budget", 111 | description: "The budget of the campaign", 112 | type: "number", 113 | required: false, 114 | }, 115 | { 116 | name: "objective", 117 | description: "The objective of the campaign", 118 | type: "string", 119 | enum: [ 120 | "brand-awareness", 121 | "lead-generation", 122 | "sales-conversion", 123 | "website-traffic", 124 | "engagement", 125 | ], 126 | }, 127 | 128 | { 129 | name: "bidStrategy", 130 | description: "The bid strategy of the campaign", 131 | type: "string", 132 | enum: ["manual-cpc", "cpa", "cpm"], 133 | required: false, 134 | }, 135 | { 136 | name: "bidAmount", 137 | description: "The bid amount of the campaign", 138 | type: "number", 139 | required: false, 140 | }, 141 | { 142 | name: "segment", 143 | description: "The segment of the campaign", 144 | type: "string", 145 | required: false, 146 | enum: segments, 147 | }, 148 | ], 149 | handler: (campaign) => { 150 | const newValue = _.assign( 151 | _.cloneDeep(currentCampaign), 152 | _.omitBy(campaign, _.isUndefined) 153 | ) as Campaign; 154 | 155 | setCurrentCampaign(newValue); 156 | }, 157 | render: (props) => { 158 | if (props.status === "complete") { 159 | return "Campaign updated successfully"; 160 | } else { 161 | return "Updating campaign"; 162 | } 163 | }, 164 | }); 165 | 166 | // Provide this component's Copilot with the ability to retrieve historical cost data for certain keywords. 167 | // Will be called automatically when needed by the Copilot. 168 | useCopilotAction({ 169 | name: "retrieveHistoricalData", 170 | description: "Retrieve historical data for certain keywords", 171 | parameters: [ 172 | { 173 | name: "keywords", 174 | description: "The keywords to retrieve data for", 175 | type: "string", 176 | }, 177 | { 178 | name: "type", 179 | description: "The type of data to retrieve for the keywords.", 180 | type: "string", 181 | enum: ["CPM", "CPA", "CPC"], 182 | }, 183 | ], 184 | handler: async ({ type }) => { 185 | // fake an API call that retrieves historical data for cost for certain keywords based on campaign type (CPM, CPA, CPC) 186 | await new Promise((resolve) => setTimeout(resolve, 2000)); 187 | 188 | function getRandomValue(min: number, max: number) { 189 | return (Math.random() * (max - min) + min).toFixed(2); 190 | } 191 | 192 | if (type == "CPM") { 193 | return getRandomValue(0.5, 10); 194 | } else if (type == "CPA") { 195 | return getRandomValue(5, 100); 196 | } else if (type == "CPC") { 197 | return getRandomValue(0.2, 2); 198 | } 199 | }, 200 | render: (props) => { 201 | // Custom in-chat component rendering. Different components can be rendered based on the status of the action. 202 | let label = "Retrieving historical data ..."; 203 | if (props.args.type) { 204 | label = `Retrieving ${props.args.type} for keywords ...`; 205 | } 206 | if (props.status === "complete") { 207 | label = `Done retrieving ${props.args.type} for keywords.`; 208 | } 209 | 210 | const done = props.status === "complete"; 211 | return ( 212 |
213 |
214 |
215 |
216 |

217 | {label} 218 |

219 |

220 | {props.args.type && 221 | `Historical ${props.args.type}: ${props.result || "..."}`} 222 |

223 |
224 |
225 |
226 | ); 227 | }, 228 | }); 229 | 230 | return ( 231 |
232 | { 237 | if (campaign) { 238 | saveCampaign(campaign); 239 | } 240 | setCurrentCampaign(undefined); 241 | }} 242 | /> 243 | 249 |
250 | ); 251 | } 252 | -------------------------------------------------------------------------------- /src/components/app/CampaignForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Campaign } from "@/lib/types"; 3 | import { Button } from "@/components/ui/button"; 4 | import { Label } from "@/components/ui/label"; 5 | import { Input } from "@/components/ui/input"; 6 | import clsx from "clsx"; 7 | import { 8 | Select, 9 | SelectContent, 10 | SelectItem, 11 | SelectTrigger, 12 | SelectValue, 13 | } from "@/components/ui/select"; 14 | import { useCopilotReadable } from "@copilotkit/react-core"; 15 | 16 | interface CampaignFormProps { 17 | currentCampaign?: Campaign; 18 | setCurrentCampaign: (campaign?: Campaign) => void; 19 | segments: string[]; 20 | saveCampaign: (campaign?: Campaign) => void; 21 | } 22 | 23 | export function CampaignForm({ 24 | currentCampaign, 25 | setCurrentCampaign, 26 | saveCampaign, 27 | segments, 28 | }: CampaignFormProps) { 29 | useCopilotReadable({ 30 | description: "Current Campaign", 31 | value: currentCampaign, 32 | }); 33 | 34 | if (!currentCampaign) return null; 35 | return ( 36 |
43 |
44 |

45 | {currentCampaign.id == "" ? "New" : "Edit"} Campaign 46 |

47 |
48 |
49 |

Campaign Information

50 |
51 | 58 | 72 |
73 |
74 | [s, s]))} 81 | /> 82 |
83 |
84 |
85 |

Budget & Bidding

86 |
87 | 94 |
95 |
96 | 108 | 115 |
116 |
117 |
118 |

Ad Copy

119 |
120 | 127 | 134 |
135 |
136 | 143 | 150 |
151 |
152 |
153 |
154 | 161 | 162 |
163 |
164 |
165 | ); 166 | } 167 | 168 | interface TextInputProps { 169 | id: string; 170 | label: string; 171 | className?: string; 172 | campaign: Campaign; 173 | setCampaign: (campaign: Campaign) => void; 174 | } 175 | 176 | function TextInput({ 177 | id, 178 | label, 179 | className, 180 | campaign, 181 | setCampaign, 182 | }: TextInputProps) { 183 | return ( 184 |
185 | 188 | 194 | setCampaign({ 195 | ...campaign, 196 | [id]: e.target.value, 197 | }) 198 | } 199 | value={(campaign as any)[id] || ""} 200 | /> 201 |
202 | ); 203 | } 204 | 205 | interface DropdownProps { 206 | id: string; 207 | label: string; 208 | className?: string; 209 | campaign: Campaign; 210 | setCampaign: (campaign: Campaign) => void; 211 | items: { [key: string]: string }; 212 | } 213 | 214 | function Dropdown({ 215 | id, 216 | label, 217 | className, 218 | campaign, 219 | setCampaign, 220 | items, 221 | }: DropdownProps) { 222 | return ( 223 |
224 | 227 | 242 |
243 | ); 244 | } 245 | -------------------------------------------------------------------------------- /src/components/app/MainNav.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export function MainNav({ 6 | className, 7 | ...props 8 | }: React.HTMLAttributes) { 9 | return ( 10 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/components/app/UserNav.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 2 | import { Button } from "@/components/ui/button"; 3 | import { 4 | DropdownMenu, 5 | DropdownMenuContent, 6 | DropdownMenuGroup, 7 | DropdownMenuItem, 8 | DropdownMenuLabel, 9 | DropdownMenuSeparator, 10 | DropdownMenuShortcut, 11 | DropdownMenuTrigger, 12 | } from "@/components/ui/dropdown-menu"; 13 | import { Input } from "@/components/ui/input"; 14 | 15 | export function UserNav() { 16 | return ( 17 | <> 18 |
19 | 24 |
25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 |
37 |

Markus Ecker

38 |

39 | markus@copilotkit.ai 40 |

41 |
42 |
43 | 44 | 45 | 46 | Profile 47 | ⇧⌘P 48 | 49 | 50 | Billing 51 | ⌘B 52 | 53 | 54 | Settings 55 | ⌘S 56 | 57 | New Team 58 | 59 | 60 | 61 | Log out 62 | ⇧⌘Q 63 | 64 |
65 |
66 | 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /src/components/dashboard/ActiveCampaigns.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 2 | import { Campaign } from "@/lib/types"; 3 | 4 | interface ActiveCampaignsProps { 5 | campaigns: Campaign[]; 6 | setCurrentCampaign: (campaign: Campaign) => void; 7 | } 8 | 9 | export function ActiveCampaigns({ 10 | campaigns, 11 | setCurrentCampaign, 12 | }: ActiveCampaignsProps) { 13 | return ( 14 |
15 | {campaigns.map((campaign) => ( 16 | setCurrentCampaign(campaign)} 20 | /> 21 | ))} 22 |
23 | ); 24 | } 25 | 26 | interface ActiveCampaignProps { 27 | campaign: Campaign; 28 | onClick: (campaign: Campaign) => void; 29 | } 30 | 31 | function ActiveCampaign({ campaign, onClick }: ActiveCampaignProps) { 32 | const titleInitials = (campaign.title || "") 33 | .split(" ") 34 | .map((word) => (word.length > 0 ? word[0] : "")) 35 | .slice(0, 2) 36 | .join("") 37 | .toUpperCase(); 38 | const budget = new Intl.NumberFormat("en-US", { 39 | style: "currency", 40 | currency: "USD", 41 | }).format(Math.abs(campaign.budget)); 42 | 43 | return ( 44 |
{ 47 | onClick(campaign); 48 | }} 49 | > 50 | 51 | 52 | {titleInitials} 53 | 54 | 55 |
56 |

{campaign.title}

57 |

61 | {budget} 62 |

63 |
64 |
65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /src/components/dashboard/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import { MainNav } from "@/components/app/MainNav"; 2 | import { UserNav } from "@/components/app/UserNav"; 3 | import { ActiveCampaigns } from "@/components/dashboard/ActiveCampaigns"; 4 | import { Overview } from "@/components/dashboard/Overview"; 5 | import TeamSwitcher from "@/components/dashboard/TeamSwitcher"; 6 | import { Button } from "@/components/ui/button"; 7 | import { 8 | Card, 9 | CardContent, 10 | CardDescription, 11 | CardHeader, 12 | CardTitle, 13 | } from "@/components/ui/card"; 14 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 15 | import { Campaign } from "@/lib/types"; 16 | import { PlusCircleIcon } from "@heroicons/react/24/outline"; 17 | import { Pencil1Icon } from "@radix-ui/react-icons"; 18 | import { useState } from "react"; 19 | import SegmentsManager from "./Segments"; 20 | 21 | interface DashboardProps { 22 | campaigns: Campaign[]; 23 | setCurrentCampaign: (campaign: Campaign) => void; 24 | segments: string[]; 25 | setSegments: (segments: string[]) => void; 26 | } 27 | 28 | export function Dashboard({ 29 | campaigns, 30 | setCurrentCampaign, 31 | segments, 32 | setSegments, 33 | }: DashboardProps) { 34 | const [isEditingSegments, setIsEditingSegments] = useState(false); 35 | 36 | return ( 37 | <> 38 |
39 |
40 |
41 | 42 | 43 |
44 | 45 |
46 |
47 |
48 |
49 |
50 |

Dashboard

51 |
52 | 60 | 61 | 76 |
77 |
78 | 79 | 80 | Overview 81 | 82 | Analytics 83 | 84 | 85 | Reports 86 | 87 | 88 | Notifications 89 | 90 | 91 | 92 |
93 | 94 | 95 | 96 | Total Cost 97 | 98 | 108 | 109 | 110 | 111 | 112 |
$45,231.89
113 |

114 | +20.1% from last month 115 |

116 |
117 |
118 | 119 | 120 | 121 | Impressions 122 | 123 | 133 | 134 | 135 | 136 | 137 | 138 | 139 |
38M
140 |

141 | +36.2% from last month 142 |

143 |
144 |
145 | 146 | 147 | 148 | Clicks 149 | 150 | 160 | 161 | 162 | 163 | 164 |
2.8M
165 |

166 | +40.8% from last month 167 |

168 |
169 |
170 | 171 | 172 | 173 | Conversions 174 | 175 | 185 | 186 | 187 | 188 | 189 | 190 |
199K
191 |

192 | +31.2% from last month 193 |

194 |
195 |
196 |
197 |
198 | 199 | 200 | Spendings Overview 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | Active Campaigns 209 | 210 | You have {campaigns.length} active campaign 211 | {campaigns.length != 1 && "s"}. 212 | 213 | 214 | 215 | 219 | 220 | 221 |
222 |
223 |
224 |
225 |
226 | {isEditingSegments && ( 227 | { 231 | setIsEditingSegments(false); 232 | }} 233 | /> 234 | )} 235 | 236 | ); 237 | } 238 | -------------------------------------------------------------------------------- /src/components/dashboard/Overview.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from "recharts"; 4 | 5 | // Work around React deprecation warning 6 | const XAxisDefaultProps = XAxis.defaultProps; 7 | const YAxisDefaultProps = YAxis.defaultProps; 8 | delete XAxis.defaultProps; 9 | delete YAxis.defaultProps; 10 | 11 | const MAX_VALUE = 10000; 12 | const MIN_VALUE = 1000; 13 | 14 | const data = [ 15 | { 16 | name: "Jan", 17 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 18 | }, 19 | { 20 | name: "Feb", 21 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 22 | }, 23 | { 24 | name: "Mar", 25 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 26 | }, 27 | { 28 | name: "Apr", 29 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 30 | }, 31 | { 32 | name: "May", 33 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 34 | }, 35 | { 36 | name: "Jun", 37 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 38 | }, 39 | { 40 | name: "Jul", 41 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 42 | }, 43 | { 44 | name: "Aug", 45 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 46 | }, 47 | { 48 | name: "Sep", 49 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 50 | }, 51 | { 52 | name: "Oct", 53 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 54 | }, 55 | { 56 | name: "Nov", 57 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 58 | }, 59 | { 60 | name: "Dec", 61 | total: Math.floor(Math.random() * MAX_VALUE) + MIN_VALUE, 62 | }, 63 | ]; 64 | 65 | export function Overview() { 66 | return ( 67 | 68 | 69 | 77 | `$${value}`} 84 | /> 85 | 91 | 92 | 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/components/dashboard/Segments.tsx: -------------------------------------------------------------------------------- 1 | import { TrashIcon, XCircleIcon, XMarkIcon } from "@heroicons/react/24/outline"; 2 | import React, { useState } from "react"; 3 | import { Input } from "../ui/input"; 4 | import { Button } from "../ui/button"; 5 | 6 | interface SegmentsManagerProps { 7 | segments: string[]; 8 | setSegments: (segments: string[]) => void; 9 | onClose: () => void; 10 | } 11 | 12 | const SegmentsManager = ({ 13 | segments, 14 | setSegments, 15 | onClose, 16 | }: SegmentsManagerProps) => { 17 | const [newSegment, setNewSegment] = useState(""); 18 | 19 | const addSegment = () => { 20 | if (newSegment) { 21 | setSegments([...segments, newSegment]); 22 | setNewSegment(""); // Reset the input 23 | } 24 | }; 25 | 26 | const removeSegment = (index: number) => { 27 | setSegments(segments.filter((_, i) => i !== index)); 28 | }; 29 | 30 | return ( 31 |
38 |
39 |
40 |

41 | Edit Customer Segments 42 |

43 | 46 |
47 |
48 |
49 | setNewSegment(e.target.value)} 54 | placeholder="Enter a new segment" 55 | /> 56 | 57 |
58 | 59 |
    60 | {segments.map((segment, index) => ( 61 |
  • 62 | {segment}{" "} 63 | 66 |
  • 67 | ))} 68 |
69 |
70 |
71 |
72 | ); 73 | }; 74 | 75 | export default SegmentsManager; 76 | -------------------------------------------------------------------------------- /src/components/dashboard/TeamSwitcher.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { 5 | CaretSortIcon, 6 | CheckIcon, 7 | PlusCircledIcon, 8 | } from "@radix-ui/react-icons"; 9 | 10 | import { cn } from "@/lib/utils"; 11 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 12 | import { Button } from "@/components/ui/button"; 13 | import { 14 | Command, 15 | CommandEmpty, 16 | CommandGroup, 17 | CommandInput, 18 | CommandItem, 19 | CommandList, 20 | CommandSeparator, 21 | } from "@/components/ui/command"; 22 | import { 23 | Popover, 24 | PopoverContent, 25 | PopoverTrigger, 26 | } from "@/components/ui/popover"; 27 | 28 | const groups = [ 29 | { 30 | label: "Personal Account", 31 | teams: [ 32 | { 33 | label: "Markus Ecker", 34 | value: "personal", 35 | }, 36 | ], 37 | }, 38 | { 39 | label: "Teams", 40 | teams: [ 41 | { 42 | label: "PilotDesk", 43 | value: "pilotdesk", 44 | }, 45 | { 46 | label: "TawKit Inc.", 47 | value: "tawkit-inc", 48 | }, 49 | ], 50 | }, 51 | ]; 52 | 53 | type PopoverTriggerProps = React.ComponentPropsWithoutRef< 54 | typeof PopoverTrigger 55 | >; 56 | 57 | interface TeamSwitcherProps extends PopoverTriggerProps {} 58 | 59 | export default function TeamSwitcher({ className }: TeamSwitcherProps) { 60 | const [open, setOpen] = React.useState(false); 61 | const selectedTeam = groups[0].teams[0]; 62 | 63 | return ( 64 | 65 | 66 | 84 | 85 | 86 | 87 | 88 | 89 | No team found. 90 | {groups.map((group) => ( 91 | 92 | {group.teams.map((team) => ( 93 | 94 | 95 | 100 | ME 101 | 102 | {team.label} 103 | 111 | 112 | ))} 113 | 114 | ))} 115 | 116 | 117 | 118 | 119 | 120 | 121 | Create Team 122 | 123 | 124 | 125 | 126 | 127 | 128 | ); 129 | } 130 | -------------------------------------------------------------------------------- /src/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /src/components/ui/calendar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons" 5 | import { DayPicker } from "react-day-picker" 6 | 7 | import { cn } from "@/lib/utils" 8 | import { buttonVariants } from "@/components/ui/button" 9 | 10 | export type CalendarProps = React.ComponentProps 11 | 12 | function Calendar({ 13 | className, 14 | classNames, 15 | showOutsideDays = true, 16 | ...props 17 | }: CalendarProps) { 18 | return ( 19 | .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md" 43 | : "[&:has([aria-selected])]:rounded-md" 44 | ), 45 | day: cn( 46 | buttonVariants({ variant: "ghost" }), 47 | "h-8 w-8 p-0 font-normal aria-selected:opacity-100" 48 | ), 49 | day_range_start: "day-range-start", 50 | day_range_end: "day-range-end", 51 | day_selected: 52 | "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", 53 | day_today: "bg-accent text-accent-foreground", 54 | day_outside: 55 | "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30", 56 | day_disabled: "text-muted-foreground opacity-50", 57 | day_range_middle: 58 | "aria-selected:bg-accent aria-selected:text-accent-foreground", 59 | day_hidden: "invisible", 60 | ...classNames, 61 | }} 62 | components={{ 63 | IconLeft: ({ ...props }) => , 64 | IconRight: ({ ...props }) => , 65 | }} 66 | {...props} 67 | /> 68 | ) 69 | } 70 | Calendar.displayName = "Calendar" 71 | 72 | export { Calendar } 73 | -------------------------------------------------------------------------------- /src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLParagraphElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |

53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |

61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /src/components/ui/command.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { type DialogProps } from "@radix-ui/react-dialog" 5 | import { MagnifyingGlassIcon } from "@radix-ui/react-icons" 6 | import { Command as CommandPrimitive } from "cmdk" 7 | 8 | import { cn } from "@/lib/utils" 9 | import { Dialog, DialogContent } from "@/components/ui/dialog" 10 | 11 | const Command = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 23 | )) 24 | Command.displayName = CommandPrimitive.displayName 25 | 26 | interface CommandDialogProps extends DialogProps {} 27 | 28 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => { 29 | return ( 30 | 31 | 32 | 33 | {children} 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | const CommandInput = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 |
45 | 46 | 54 |
55 | )) 56 | 57 | CommandInput.displayName = CommandPrimitive.Input.displayName 58 | 59 | const CommandList = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, ...props }, ref) => ( 63 | 68 | )) 69 | 70 | CommandList.displayName = CommandPrimitive.List.displayName 71 | 72 | const CommandEmpty = React.forwardRef< 73 | React.ElementRef, 74 | React.ComponentPropsWithoutRef 75 | >((props, ref) => ( 76 | 81 | )) 82 | 83 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName 84 | 85 | const CommandGroup = React.forwardRef< 86 | React.ElementRef, 87 | React.ComponentPropsWithoutRef 88 | >(({ className, ...props }, ref) => ( 89 | 97 | )) 98 | 99 | CommandGroup.displayName = CommandPrimitive.Group.displayName 100 | 101 | const CommandSeparator = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName 112 | 113 | const CommandItem = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 125 | )) 126 | 127 | CommandItem.displayName = CommandPrimitive.Item.displayName 128 | 129 | const CommandShortcut = ({ 130 | className, 131 | ...props 132 | }: React.HTMLAttributes) => { 133 | return ( 134 | 141 | ) 142 | } 143 | CommandShortcut.displayName = "CommandShortcut" 144 | 145 | export { 146 | Command, 147 | CommandDialog, 148 | CommandInput, 149 | CommandList, 150 | CommandEmpty, 151 | CommandGroup, 152 | CommandItem, 153 | CommandShortcut, 154 | CommandSeparator, 155 | } 156 | -------------------------------------------------------------------------------- /src/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as DialogPrimitive from "@radix-ui/react-dialog"; 5 | import { Cross2Icon } from "@radix-ui/react-icons"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const Dialog = DialogPrimitive.Root; 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger; 12 | 13 | const DialogPortal = DialogPrimitive.Portal; 14 | 15 | const DialogClose = DialogPrimitive.Close; 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )); 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )); 54 | DialogContent.displayName = DialogPrimitive.Content.displayName; 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
67 | ); 68 | DialogHeader.displayName = "DialogHeader"; 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
81 | ); 82 | DialogFooter.displayName = "DialogFooter"; 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )); 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName; 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )); 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName; 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogTrigger, 116 | DialogClose, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | }; 123 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 | import { 6 | CheckIcon, 7 | ChevronRightIcon, 8 | DotFilledIcon, 9 | } from "@radix-ui/react-icons" 10 | 11 | import { cn } from "@/lib/utils" 12 | 13 | const DropdownMenu = DropdownMenuPrimitive.Root 14 | 15 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 16 | 17 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 18 | 19 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 20 | 21 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 22 | 23 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 24 | 25 | const DropdownMenuSubTrigger = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef & { 28 | inset?: boolean 29 | } 30 | >(({ className, inset, children, ...props }, ref) => ( 31 | 40 | {children} 41 | 42 | 43 | )) 44 | DropdownMenuSubTrigger.displayName = 45 | DropdownMenuPrimitive.SubTrigger.displayName 46 | 47 | const DropdownMenuSubContent = React.forwardRef< 48 | React.ElementRef, 49 | React.ComponentPropsWithoutRef 50 | >(({ className, ...props }, ref) => ( 51 | 59 | )) 60 | DropdownMenuSubContent.displayName = 61 | DropdownMenuPrimitive.SubContent.displayName 62 | 63 | const DropdownMenuContent = React.forwardRef< 64 | React.ElementRef, 65 | React.ComponentPropsWithoutRef 66 | >(({ className, sideOffset = 4, ...props }, ref) => ( 67 | 68 | 78 | 79 | )) 80 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 81 | 82 | const DropdownMenuItem = React.forwardRef< 83 | React.ElementRef, 84 | React.ComponentPropsWithoutRef & { 85 | inset?: boolean 86 | } 87 | >(({ className, inset, ...props }, ref) => ( 88 | 97 | )) 98 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 99 | 100 | const DropdownMenuCheckboxItem = React.forwardRef< 101 | React.ElementRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, children, checked, ...props }, ref) => ( 104 | 113 | 114 | 115 | 116 | 117 | 118 | {children} 119 | 120 | )) 121 | DropdownMenuCheckboxItem.displayName = 122 | DropdownMenuPrimitive.CheckboxItem.displayName 123 | 124 | const DropdownMenuRadioItem = React.forwardRef< 125 | React.ElementRef, 126 | React.ComponentPropsWithoutRef 127 | >(({ className, children, ...props }, ref) => ( 128 | 136 | 137 | 138 | 139 | 140 | 141 | {children} 142 | 143 | )) 144 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 145 | 146 | const DropdownMenuLabel = React.forwardRef< 147 | React.ElementRef, 148 | React.ComponentPropsWithoutRef & { 149 | inset?: boolean 150 | } 151 | >(({ className, inset, ...props }, ref) => ( 152 | 161 | )) 162 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 163 | 164 | const DropdownMenuSeparator = React.forwardRef< 165 | React.ElementRef, 166 | React.ComponentPropsWithoutRef 167 | >(({ className, ...props }, ref) => ( 168 | 173 | )) 174 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 175 | 176 | const DropdownMenuShortcut = ({ 177 | className, 178 | ...props 179 | }: React.HTMLAttributes) => { 180 | return ( 181 | 185 | ) 186 | } 187 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 188 | 189 | export { 190 | DropdownMenu, 191 | DropdownMenuTrigger, 192 | DropdownMenuContent, 193 | DropdownMenuItem, 194 | DropdownMenuCheckboxItem, 195 | DropdownMenuRadioItem, 196 | DropdownMenuLabel, 197 | DropdownMenuSeparator, 198 | DropdownMenuShortcut, 199 | DropdownMenuGroup, 200 | DropdownMenuPortal, 201 | DropdownMenuSub, 202 | DropdownMenuSubContent, 203 | DropdownMenuSubTrigger, 204 | DropdownMenuRadioGroup, 205 | } 206 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Popover = PopoverPrimitive.Root 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger 11 | 12 | const PopoverAnchor = PopoverPrimitive.Anchor 13 | 14 | const PopoverContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 18 | 19 | 29 | 30 | )) 31 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 32 | 33 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } 34 | -------------------------------------------------------------------------------- /src/components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { 5 | CaretSortIcon, 6 | CheckIcon, 7 | ChevronDownIcon, 8 | ChevronUpIcon, 9 | } from "@radix-ui/react-icons" 10 | import * as SelectPrimitive from "@radix-ui/react-select" 11 | 12 | import { cn } from "@/lib/utils" 13 | 14 | const Select = SelectPrimitive.Root 15 | 16 | const SelectGroup = SelectPrimitive.Group 17 | 18 | const SelectValue = SelectPrimitive.Value 19 | 20 | const SelectTrigger = React.forwardRef< 21 | React.ElementRef, 22 | React.ComponentPropsWithoutRef 23 | >(({ className, children, ...props }, ref) => ( 24 | span]:line-clamp-1", 28 | className 29 | )} 30 | {...props} 31 | > 32 | {children} 33 | 34 | 35 | 36 | 37 | )) 38 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 39 | 40 | const SelectScrollUpButton = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 | 52 | 53 | 54 | )) 55 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName 56 | 57 | const SelectScrollDownButton = React.forwardRef< 58 | React.ElementRef, 59 | React.ComponentPropsWithoutRef 60 | >(({ className, ...props }, ref) => ( 61 | 69 | 70 | 71 | )) 72 | SelectScrollDownButton.displayName = 73 | SelectPrimitive.ScrollDownButton.displayName 74 | 75 | const SelectContent = React.forwardRef< 76 | React.ElementRef, 77 | React.ComponentPropsWithoutRef 78 | >(({ className, children, position = "popper", ...props }, ref) => ( 79 | 80 | 91 | 92 | 99 | {children} 100 | 101 | 102 | 103 | 104 | )) 105 | SelectContent.displayName = SelectPrimitive.Content.displayName 106 | 107 | const SelectLabel = React.forwardRef< 108 | React.ElementRef, 109 | React.ComponentPropsWithoutRef 110 | >(({ className, ...props }, ref) => ( 111 | 116 | )) 117 | SelectLabel.displayName = SelectPrimitive.Label.displayName 118 | 119 | const SelectItem = React.forwardRef< 120 | React.ElementRef, 121 | React.ComponentPropsWithoutRef 122 | >(({ className, children, ...props }, ref) => ( 123 | 131 | 132 | 133 | 134 | 135 | 136 | {children} 137 | 138 | )) 139 | SelectItem.displayName = SelectPrimitive.Item.displayName 140 | 141 | const SelectSeparator = React.forwardRef< 142 | React.ElementRef, 143 | React.ComponentPropsWithoutRef 144 | >(({ className, ...props }, ref) => ( 145 | 150 | )) 151 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 152 | 153 | export { 154 | Select, 155 | SelectGroup, 156 | SelectValue, 157 | SelectTrigger, 158 | SelectContent, 159 | SelectLabel, 160 | SelectItem, 161 | SelectSeparator, 162 | SelectScrollUpButton, 163 | SelectScrollDownButton, 164 | } 165 | -------------------------------------------------------------------------------- /src/components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TabsPrimitive from "@radix-ui/react-tabs" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Tabs = TabsPrimitive.Root 9 | 10 | const TabsList = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, ...props }, ref) => ( 14 | 22 | )) 23 | TabsList.displayName = TabsPrimitive.List.displayName 24 | 25 | const TabsTrigger = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 37 | )) 38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName 39 | 40 | const TabsContent = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 | 52 | )) 53 | TabsContent.displayName = TabsPrimitive.Content.displayName 54 | 55 | export { Tabs, TabsList, TabsTrigger, TabsContent } 56 | -------------------------------------------------------------------------------- /src/lib/data.ts: -------------------------------------------------------------------------------- 1 | import { Campaign } from "./types"; 2 | 3 | export let DEFAULT_CAMPAIGNS: Campaign[] = [ 4 | { 5 | id: "1", 6 | title: "CopilotKit", 7 | url: "https://www.copilotkit.ai", 8 | headline: "Copilot Kit - The Open-Source Copilot Framework", 9 | description: 10 | "Build, deploy, and operate fully custom AI Copilots. In-app AI chatbots, AI agents, AI Textareas and more.", 11 | budget: 10000, 12 | keywords: "AI, chatbot, open-source, copilot, framework", 13 | }, 14 | { 15 | id: "2", 16 | title: "EcoHome Essentials", 17 | url: "https://www.ecohomeessentials.com", 18 | headline: "Sustainable Living Made Easy", 19 | description: 20 | "Discover our eco-friendly products that make sustainable living effortless. Shop now for green alternatives!", 21 | budget: 7500, 22 | keywords: "eco-friendly, sustainable, green products, home essentials", 23 | }, 24 | { 25 | id: "3", 26 | title: "TechGear Solutions", 27 | url: "https://www.techgearsolutions.com", 28 | headline: "Innovative Tech for the Modern World", 29 | description: 30 | "Find the latest gadgets and tech solutions. Upgrade your life with smart technology today!", 31 | budget: 12000, 32 | keywords: "tech, gadgets, innovative, modern, electronics", 33 | }, 34 | { 35 | id: "4", 36 | title: "Global Travels", 37 | url: "https://www.globaltravels.com", 38 | headline: "Travel the World with Confidence", 39 | description: 40 | "Experience bespoke travel packages tailored to your dreams. Luxury, adventure, relaxation—your journey starts here.", 41 | budget: 20000, 42 | keywords: "travel, luxury, adventure, tours, global", 43 | }, 44 | { 45 | id: "5", 46 | title: "FreshFit Meals", 47 | url: "https://www.freshfitmeals.com", 48 | headline: "Healthy Eating, Simplified", 49 | description: 50 | "Nutritious, delicious meals delivered to your door. Eating well has never been easier or tastier.", 51 | budget: 5000, 52 | keywords: "healthy, meals, nutrition, delivery, fit", 53 | }, 54 | ]; 55 | -------------------------------------------------------------------------------- /src/lib/guideline.ts: -------------------------------------------------------------------------------- 1 | export const GUIDELINE: string = ` 2 | # Ad Campaign Guidelines Document 3 | 4 | ## Campaign Objectives 5 | 6 | - **Brand Awareness**: Choose when introducing a new product or reaching a new market. The focus is on maximizing exposure. 7 | - **Lead Generation**: Suitable for building a customer database, often for services or B2B products. 8 | - **Sales Conversion**: Prioritize when the primary goal is to drive immediate online sales. 9 | - **Website Traffic**: Select to increase the number of visitors to a particular website or page. 10 | - **Engagement**: Ideal for content-driven campaigns aimed at interaction (likes, comments, shares). 11 | 12 | ## Bid Strategies 13 | 14 | - **Manual CPC (Cost Per Click)**: Use when you want control over the bid amounts for clicks. 15 | - **CPA (Cost Per Acquisition)**: Best for campaigns focused on conversions like sales or sign-ups. 16 | - **CPM (Cost Per Thousand Impressions)**: Recommended for brand awareness campaigns where impressions are more valuable than clicks. 17 | 18 | ## Scenario-Specific Actions 19 | 20 | ### Scenario: Launching a New Product 21 | - **Objective:** Brand Awareness 22 | - **Bid Strategy:** CPM 23 | - **Calculations:** 24 | - Determine your target reach and average industry CPM rates. 25 | 26 | ### Scenario: Maximizing Online Sales During a Holiday 27 | - **Objective:** Sales Conversion 28 | - **Bid Strategy:** CPA 29 | - **Calculations:** 30 | - Estimate the number of sales you aim 31 | 32 | ### Scenario: Growing the Subscriber List 33 | - **Objective:** Lead Generation 34 | - **Bid Strategy:** CPA or Manual CPC 35 | - **Calculations:** 36 | - Decide on Bid Amount based on historical data and the competitive landscape. 37 | 38 | ### Scenario: Increasing Blog Readership 39 | - **Objective:** Website Traffic 40 | - **Bid Strategy:** Manual CPC 41 | - **Calculations:** 42 | - Set a Bid Amount slightly higher than the average CPC to remain competitive. 43 | 44 | ### Scenario: Engaging the Community with Content 45 | - **Objective:** Engagement 46 | - **Bid Strategy:** CPM or Manual CPC (if specific interactions are more valuable) 47 | - **Calculations:** 48 | - Determine Bid Amount based on whether you're paying per interaction (CPC) or per impression (CPM). 49 | 50 | ## Prohibited content 51 | 52 | THE FOLLOWING CONTENT IS PROHIBITED FROM ADVERTISING ON OUR PLATFORM. 53 | DO NOT CREATE CAMPAIGNS THAT PROMOTE OR SELL THE FOLLOWING, INSTEAD INFORM THE USER OF THE VIOLATION. 54 | 55 | ### Counterfeit goods 56 | Campaign manager prohibits the sale or promotion for sale of counterfeit goods. Counterfeit goods contain a trademark or logo that is identical to or substantially indistinguishable from the trademark of another. They mimic the brand features of the product in an attempt to pass themselves off as a genuine product of the brand owner. This policy applies to the content of your ad and your website or app. 57 | 58 | ### Dangerous products or services 59 | We want to help keep people safe both online and offline, so we don't allow the promotion of some products or services that cause damage, harm, or injury. 60 | Examples of dangerous content: Recreational drugs (chemical or herbal); psychoactive substances; equipment to facilitate drug use; weapons, ammunition, explosive materials and fireworks; instructions for making explosives or other harmful products; tobacco products 61 | 62 | ### Enabling dishonest behavior 63 | We value honesty and fairness, so we don't allow the promotion of products or services that are designed to enable dishonest behavior. 64 | Examples of products or services that enable dishonest behavior: Hacking software or instructions; services designed to artificially inflate ad or website traffic; fake documents; academic cheating services 65 | 66 | ### Inappropriate content 67 | We value diversity and respect for others, and we strive to avoid offending users, so we don't allow ads or destinations that display shocking content or promote hatred, intolerance, discrimination, or violence. 68 | 69 | Examples of inappropriate or offensive content: bullying or intimidation of an individual or group, racial discrimination, hate group paraphernalia, graphic crime scene or accident images, cruelty to animals, murder, self-harm, extortion or blackmail, sale or trade of endangered species, ads using profane language 70 | `; 71 | -------------------------------------------------------------------------------- /src/lib/script.ts: -------------------------------------------------------------------------------- 1 | export const SCRIPT_SUGGESTION = ` 2 | WHENEVER THE USER WANTS TO CREATE A NEW CAMPAIGN, THE ASSISTANT MUST ASK A SERIES OF QUESTIONS ONE BY ONE TO GATHER THE NECESSARY INFORMATION TO SET UP THE CAMPAIGN SUCCESSFULLY. 3 | 4 | ONCE YOU HAVE THE REQUIRED INFORMATION, USE YOUR BEST GUESS FOR THE REST OF THE FIELDS. 5 | 6 | **Assistant:** “To begin with, could you please share the main goal of your advertising campaign? Are you looking to enhance brand recognition, generate leads, boost sales, increase traffic to your website, or engage with your audience in a more meaningful way?” 7 | 8 | - This will determine the **Campaign Objective** and guide the choice in **Bid Strategy**. 9 | 10 | **Assistant:** “What budget have you allocated for this campaign, and do you have a specific time frame in mind for how long you'd like the campaign to run?” 11 | 12 | - The answer will help the agent set the **Total Budget**, and possibly even influence the **Bid Amount**. 13 | 14 | **Assistant:** “Could you describe your ideal customer? What are their interests, demographics, and online behaviors?” 15 | 16 | - Understanding the target audience will assist in selecting appropriate **Keywords**, crafting the **Headline** and **Description** for the ad copy, and further refine the **Campaign Objective** and **Bid Strategy**. 17 | - The user has defined their own list of **Segments** that can be used to further target the audience. It's important to set the right segment for the campaign if it can be inferred from the user's response. 18 | 19 | 20 | WHEN FILLING THE FORM, ALSO RETRIEVE THE CORRECT CPC, CPM OR CPA FROM HISTORICAL DATA TO SET THE BID AMOUNT. 21 | `; 22 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | export interface Campaign { 2 | id: string; 3 | objective?: 4 | | "brand-awareness" 5 | | "lead-generation" 6 | | "sales-conversion" 7 | | "website-traffic" 8 | | "engagement"; 9 | title: string; 10 | keywords: string; 11 | url: string; 12 | headline: string; 13 | description: string; 14 | budget: number; 15 | bidStrategy?: "manual-cpc" | "cpa" | "cpm"; 16 | bidAmount?: number; 17 | segment?: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | 8 | export function randomId() { 9 | return Math.random().toString(36).substring(2, 15); 10 | } 11 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss" 2 | 3 | const config = { 4 | darkMode: ["class"], 5 | content: [ 6 | './pages/**/*.{ts,tsx}', 7 | './components/**/*.{ts,tsx}', 8 | './app/**/*.{ts,tsx}', 9 | './src/**/*.{ts,tsx}', 10 | ], 11 | prefix: "", 12 | theme: { 13 | container: { 14 | center: true, 15 | padding: "2rem", 16 | screens: { 17 | "2xl": "1400px", 18 | }, 19 | }, 20 | extend: { 21 | colors: { 22 | border: "hsl(var(--border))", 23 | input: "hsl(var(--input))", 24 | ring: "hsl(var(--ring))", 25 | background: "hsl(var(--background))", 26 | foreground: "hsl(var(--foreground))", 27 | primary: { 28 | DEFAULT: "hsl(var(--primary))", 29 | foreground: "hsl(var(--primary-foreground))", 30 | }, 31 | secondary: { 32 | DEFAULT: "hsl(var(--secondary))", 33 | foreground: "hsl(var(--secondary-foreground))", 34 | }, 35 | destructive: { 36 | DEFAULT: "hsl(var(--destructive))", 37 | foreground: "hsl(var(--destructive-foreground))", 38 | }, 39 | muted: { 40 | DEFAULT: "hsl(var(--muted))", 41 | foreground: "hsl(var(--muted-foreground))", 42 | }, 43 | accent: { 44 | DEFAULT: "hsl(var(--accent))", 45 | foreground: "hsl(var(--accent-foreground))", 46 | }, 47 | popover: { 48 | DEFAULT: "hsl(var(--popover))", 49 | foreground: "hsl(var(--popover-foreground))", 50 | }, 51 | card: { 52 | DEFAULT: "hsl(var(--card))", 53 | foreground: "hsl(var(--card-foreground))", 54 | }, 55 | }, 56 | borderRadius: { 57 | lg: "var(--radius)", 58 | md: "calc(var(--radius) - 2px)", 59 | sm: "calc(var(--radius) - 4px)", 60 | }, 61 | keyframes: { 62 | "accordion-down": { 63 | from: { height: "0" }, 64 | to: { height: "var(--radix-accordion-content-height)" }, 65 | }, 66 | "accordion-up": { 67 | from: { height: "var(--radix-accordion-content-height)" }, 68 | to: { height: "0" }, 69 | }, 70 | }, 71 | animation: { 72 | "accordion-down": "accordion-down 0.2s ease-out", 73 | "accordion-up": "accordion-up 0.2s ease-out", 74 | }, 75 | }, 76 | }, 77 | plugins: [require("tailwindcss-animate")], 78 | } satisfies Config 79 | 80 | export default config -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | --------------------------------------------------------------------------------