├── supabase ├── seed.sql ├── .gitignore └── templates │ ├── magic_link.html │ ├── confirmation.html │ ├── recovery.html │ ├── email_change.html │ └── invite.html ├── docker-compose.yaml ├── public ├── bloomicon.jpg ├── favicon.ico ├── auth_banner.jpg ├── favicon-16x16.png ├── favicon-32x32.png ├── mstile-150x150.png ├── preview_large.jpg ├── preview_small.jpg ├── apple-touch-icon.png ├── bloom_icon_large.jpg ├── opengraph-image.jpg ├── android-chrome-192x192.png ├── apple-touch-icon-precomposed.png ├── browserconfig.xml ├── site.webmanifest ├── vercel.svg ├── usericon.svg ├── safari-pinned-tab.svg ├── next.svg ├── bloom2x1.svg └── bloom2x1dark.svg ├── assets └── ToM Chain Flow.png ├── postcss.config.js ├── .dockerignore ├── fonts └── DepartureMono-Regular.woff2 ├── .cursor └── rules │ └── pnpm.mdc ├── utils ├── fonts.ts ├── unstableCache.ts ├── ai │ ├── stream.ts │ ├── types.ts │ ├── validation.ts │ ├── summary.ts │ └── conversation.ts ├── honcho.ts ├── supabase │ ├── server.ts │ ├── client.ts │ ├── actions.ts │ ├── queries.ts │ └── middleware.ts ├── prompts │ ├── name.ts │ ├── summary.ts │ └── pdf.ts ├── swrCache.ts ├── types.ts ├── parseFiles.ts ├── parsePdf.ts ├── pdfChat.ts ├── retryUtils.ts └── helpers.ts ├── components ├── auth │ ├── index.ts │ ├── discord.tsx │ ├── google.tsx │ ├── forgot.tsx │ ├── reset.tsx │ └── signIn.tsx ├── loading.tsx ├── settings │ ├── SecuritySettings.tsx │ ├── AccountSettings.tsx │ ├── SupportSettings.tsx │ └── SubscriptionSettings.tsx ├── ui │ ├── sonner.tsx │ ├── label.tsx │ ├── textarea.tsx │ ├── input.tsx │ ├── scroll-area.tsx │ ├── button.tsx │ ├── card.tsx │ ├── resizable.tsx │ ├── tooltip.tsx │ ├── tabs.tsx │ ├── alert-dialog.tsx │ ├── dialog.tsx │ ├── file-upload.tsx │ └── prompt-input.tsx ├── bloomlogo.tsx ├── spinner.tsx ├── messages │ ├── UserMessage.tsx │ └── AIMessage.tsx ├── FileUpload.tsx ├── thoughts.tsx ├── PriceCard.tsx ├── header.tsx ├── MessageList.tsx ├── cookieConsentBanner.tsx ├── markdownWrapper.tsx └── conversationtab.tsx ├── CLAUDE.md ├── app ├── global-error.tsx ├── settings │ ├── page.tsx │ └── SettingsLayout.tsx ├── api │ ├── chat │ │ ├── name │ │ │ └── route.ts │ │ └── route.ts │ └── webhook │ │ └── route.ts ├── auth │ ├── confirm │ │ └── route.ts │ ├── callback │ │ └── route.ts │ ├── actions.ts │ ├── reset │ │ └── page.tsx │ └── page.tsx ├── page.tsx ├── providers.tsx ├── layout.tsx └── actions │ └── conversations.ts ├── tests ├── setup.ts └── arcjet │ └── README.md ├── components.json ├── instrumentation.ts ├── vitest.config.ts ├── tsconfig.json ├── .env.template ├── eslint.config.mjs ├── sentry.server.config.ts ├── sentry.edge.config.ts ├── hooks ├── autoscroll.ts ├── useReactions.ts └── useThoughts.ts ├── .gitignore ├── sentry.client.config.ts ├── Dockerfile ├── middleware.ts ├── CHANGELOG.md ├── package.json └── scripts ├── export-users.js └── stripeSync.mjs /supabase/seed.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /supabase/.gitignore: -------------------------------------------------------------------------------- 1 | # Supabase 2 | .branches 3 | .temp 4 | .env 5 | -------------------------------------------------------------------------------- /public/bloomicon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/bloomicon.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/auth_banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/auth_banner.jpg -------------------------------------------------------------------------------- /assets/ToM Chain Flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/assets/ToM Chain Flow.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/preview_large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/preview_large.jpg -------------------------------------------------------------------------------- /public/preview_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/preview_small.jpg -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/bloom_icon_large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/bloom_icon_large.jpg -------------------------------------------------------------------------------- /public/opengraph-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/opengraph-image.jpg -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | node_modules 4 | npm-debug.log 5 | README.md 6 | .next 7 | .git 8 | -------------------------------------------------------------------------------- /fonts/DepartureMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/fonts/DepartureMono-Regular.woff2 -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plastic-labs/tutor-gpt/HEAD/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /.cursor/rules/pnpm.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: for installing any packages: USE PNPM. 3 | globs: 4 | alwaysApply: false 5 | --- 6 | Use pnpm for manipulating packages -------------------------------------------------------------------------------- /utils/fonts.ts: -------------------------------------------------------------------------------- 1 | import localFont from 'next/font/local'; 2 | 3 | export const departureMono = localFont({ 4 | src: '../fonts/DepartureMono-Regular.woff2', 5 | }); 6 | -------------------------------------------------------------------------------- /components/auth/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SignUp } from './signUp'; 2 | export { default as SignIn } from './signIn'; 3 | export { default as Forgot } from './forgot'; 4 | export { default as Reset } from './reset'; 5 | -------------------------------------------------------------------------------- /supabase/templates/magic_link.html: -------------------------------------------------------------------------------- 1 |

Magic Link

2 | 3 |

Follow this link to login:

4 |

5 | Log In 9 |

10 | -------------------------------------------------------------------------------- /supabase/templates/confirmation.html: -------------------------------------------------------------------------------- 1 |

Confirm your signup

2 | 3 |

Follow this link to confirm your user:

4 |

5 | Confirm your email 8 |

9 | -------------------------------------------------------------------------------- /supabase/templates/recovery.html: -------------------------------------------------------------------------------- 1 |

Reset Password

2 | 3 |

Follow this link to reset the password for your user:

4 |

5 | Reset Password 9 |

10 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # Package Management 2 | 3 | Use `pnpm` for all package management operations: 4 | - Installing dependencies: `pnpm install` 5 | - Adding packages: `pnpm add ` 6 | - Removing packages: `pnpm remove ` 7 | 8 | This project uses pnpm for faster installs and better dependency management. 9 | -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bloombot.ai", 3 | "short_name": "Bloombot", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "theme_color": "#ffffff", 12 | "background_color": "#ffffff", 13 | "display": "standalone" 14 | } 15 | -------------------------------------------------------------------------------- /supabase/templates/email_change.html: -------------------------------------------------------------------------------- 1 |

Confirm Change of Email

2 | 3 |

4 | Follow this link to confirm the update of your email from {{ .Email }} to {{ 5 | .NewEmail }}: 6 |

7 |

8 | 11 | Change Email 12 | 13 |

14 | -------------------------------------------------------------------------------- /components/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Loading = () => { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | }; 10 | 11 | export default Loading; 12 | -------------------------------------------------------------------------------- /supabase/templates/invite.html: -------------------------------------------------------------------------------- 1 |

You have been invited

2 | 3 |

4 | You have been invited to create a user on {{ .SiteURL }}. Follow this link to 5 | accept the invite: 6 |

7 |

8 | Accept the invite 12 |

13 | -------------------------------------------------------------------------------- /app/global-error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as Sentry from '@sentry/nextjs'; 4 | import Error from 'next/error'; 5 | import { useEffect } from 'react'; 6 | 7 | export default function GlobalError({ error }: { error: Error }) { 8 | useEffect(() => { 9 | Sentry.captureException(error); 10 | }, [error]); 11 | 12 | return ( 13 | 14 | {/* Your Error component here... */} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /utils/unstableCache.ts: -------------------------------------------------------------------------------- 1 | import { cache } from 'react'; 2 | import { unstable_cache as next_unstable_cache } from 'next/cache'; 3 | 4 | export const unstable_cache = ( 5 | callback: (...args: Args) => Promise, 6 | key: string[], 7 | options: { revalidate: number } 8 | ) => { 9 | return cache( 10 | next_unstable_cache( 11 | callback as unknown as (...args: any[]) => Promise, 12 | key, 13 | options 14 | ) 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /tests/setup.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | import { resolve } from 'path'; 3 | 4 | // Load environment variables from .env.local 5 | config({ path: resolve(process.cwd(), '.env.local') }); 6 | 7 | if (!process.env.AI_API_KEY) { 8 | throw new Error('AI_API_KEY must be set in .env.local'); 9 | } 10 | 11 | // Set OpenRouter API key to use the same key 12 | process.env.OPENROUTER_API_KEY = process.env.AI_API_KEY; 13 | 14 | if (!process.env.MODEL) { 15 | throw new Error('MODEL must be set in .env.local'); 16 | } 17 | -------------------------------------------------------------------------------- /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": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/utils/helpers", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /components/settings/SecuritySettings.tsx: -------------------------------------------------------------------------------- 1 | import { User } from '@supabase/supabase-js'; 2 | import { SettingsForm } from './SettingsForm'; 3 | 4 | interface SecuritySettingsProps { 5 | user: User | null; 6 | } 7 | 8 | export function SecuritySettings({ user }: SecuritySettingsProps) { 9 | if (!user) return
Please log in to view security settings.
; 10 | return ( 11 |
12 |

Security Settings

13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /components/settings/AccountSettings.tsx: -------------------------------------------------------------------------------- 1 | import { SettingsForm } from '@/components/settings/SettingsForm'; 2 | import { User } from '@supabase/supabase-js'; 3 | 4 | interface AccountSettingsProps { 5 | user: User | null; 6 | } 7 | 8 | export function AccountSettings({ user }: AccountSettingsProps) { 9 | if (!user) return
Please log in to view account settings.
; 10 | return ( 11 |
12 |

Account Settings

13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /instrumentation.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | import { registerOTel } from '@vercel/otel'; 4 | import { LangfuseExporter } from 'langfuse-vercel' 5 | 6 | export async function register() { 7 | 8 | registerOTel({ 9 | serviceName: 'tutor-gpt', 10 | traceExporter: new LangfuseExporter() 11 | }) 12 | 13 | if (process.env.NEXT_RUNTIME === "nodejs") { 14 | await import("./sentry.server.config"); 15 | } 16 | 17 | if (process.env.NEXT_RUNTIME === "edge") { 18 | await import("./sentry.edge.config"); 19 | } 20 | 21 | } 22 | 23 | export const onRequestError = Sentry.captureRequestError; 24 | -------------------------------------------------------------------------------- /components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useTheme } from "next-themes" 4 | import { Toaster as Sonner, ToasterProps } from "sonner" 5 | 6 | const Toaster = ({ ...props }: ToasterProps) => { 7 | const { theme = "system" } = useTheme() 8 | 9 | return ( 10 | 22 | ) 23 | } 24 | 25 | export { Toaster } 26 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | import tsconfigPaths from 'vite-tsconfig-paths'; 3 | import path from 'path'; 4 | 5 | export default defineConfig({ 6 | plugins: [tsconfigPaths()], 7 | test: { 8 | setupFiles: ['./tests/setup.ts'], 9 | // Ensure environment variables are loaded 10 | env: { 11 | OPENROUTER_API_KEY: process.env.AI_API_KEY, 12 | AI_API_KEY: process.env.AI_API_KEY, 13 | MODEL: process.env.MODEL, 14 | }, 15 | globals: true, 16 | testTimeout: 15000, // Increase timeout to 15 seconds 17 | }, 18 | resolve: { 19 | alias: { 20 | '@': path.resolve(__dirname, './'), 21 | }, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /utils/ai/stream.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { StreamResponseChunk } from './types'; 3 | 4 | const encoder = new TextEncoder(); 5 | 6 | export function stream( 7 | iterator: AsyncGenerator 8 | ) { 9 | return new ReadableStream({ 10 | async pull(controller) { 11 | const { value, done } = await iterator.next(); 12 | 13 | if (done) { 14 | controller.close(); 15 | } else { 16 | controller.enqueue(value); 17 | } 18 | }, 19 | }); 20 | } 21 | 22 | export function formatStreamChunk(chunk: StreamResponseChunk): Uint8Array { 23 | return encoder.encode(JSON.stringify(chunk)); 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /app/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import SettingsLayout from './SettingsLayout'; 2 | import { createClient } from '@/utils/supabase/server'; 3 | import { getSubscription, getProducts } from '@/utils/supabase/queries'; 4 | 5 | export default async function SettingsPage() { 6 | const supabase = await createClient(); 7 | 8 | const { 9 | data: { user }, 10 | } = await supabase.auth.getUser(); 11 | 12 | const subscription = await getSubscription(supabase); 13 | const products = await getProducts(supabase); 14 | 15 | return ( 16 |
17 | 22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components/bloomlogo.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/utils/helpers'; 2 | 3 | interface BloomLogoProps { 4 | className?: string; 5 | } 6 | 7 | export default function BloomLogo({ className }: BloomLogoProps) { 8 | return ( 9 | 17 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /components/spinner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FaCircleNotch } from 'react-icons/fa'; 3 | 4 | interface SpinnerProps { 5 | size?: number; 6 | color?: string; 7 | } 8 | 9 | const Spinner = ({ size = 24, color = '#000000' }: SpinnerProps) => { 10 | const spinnerStyle = { 11 | animation: 'spin 1s linear infinite', 12 | color: color, 13 | fontSize: `${size}px`, 14 | }; 15 | 16 | return ( 17 |
18 | 26 | 27 |
28 | ); 29 | }; 30 | 31 | export default Spinner; 32 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | # Core 2 | NEXT_PUBLIC_SITE_URL= 3 | 4 | # Supabase 5 | NEXT_PUBLIC_SUPABASE_URL= 6 | NEXT_PUBLIC_SUPABASE_ANON_KEY= 7 | SUPABASE_SERVICE_ROLE_KEY= 8 | JWT_SECRET= 9 | 10 | # Sentry 11 | NEXT_PUBLIC_SENTRY_DSN= 12 | SENTRY_ENVIRONMENT= 13 | SENTRY_RELEASE= 14 | 15 | # Posthog 16 | NEXT_PUBLIC_POSTHOG_KEY= 17 | NEXT_PUBLIC_POSTHOG_HOST= 18 | 19 | # Stripe 20 | NEXT_PUBLIC_STRIPE_ENABLED=false 21 | STRIPE_SECRET_KEY= 22 | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= 23 | STRIPE_WEBHOOK_SECRET= 24 | 25 | # Agent 26 | AI_API_KEY= 27 | AI_PROVIDER= 28 | AI_BASE_URL= 29 | MODEL= 30 | 31 | # Mistral (OCR) 32 | MISTRAL_API_KEY= 33 | 34 | # Honcho 35 | HONCHO_URL= 36 | HONCHO_APP_NAME= 37 | 38 | # Langfuse 39 | LANGFUSE_SECRET_KEY= 40 | LANGFUSE_PUBLIC_KEY= 41 | LANGFUSE_BASEURL= 42 | 43 | # Arcjet 44 | ARCJET_KEY= 45 | -------------------------------------------------------------------------------- /public/usericon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /utils/honcho.ts: -------------------------------------------------------------------------------- 1 | import { Honcho } from 'honcho-ai'; 2 | import { unstable_cache } from 'next/cache'; 3 | 4 | export const honcho = new Honcho({ 5 | baseURL: process.env.HONCHO_URL!, 6 | }); 7 | 8 | export const getHonchoApp = unstable_cache( 9 | async () => { 10 | return await honcho.apps.getOrCreate(process.env.HONCHO_APP_NAME!, { 11 | timeout: 5 * 1000, 12 | maxRetries: 5, 13 | }); 14 | }, 15 | [], 16 | { 17 | revalidate: 300, // 5 minutes 18 | } 19 | ); 20 | 21 | export const getHonchoUser = unstable_cache( 22 | async (userId: string) => { 23 | const app = await getHonchoApp(); 24 | return await honcho.apps.users.getOrCreate(app.id, userId, { 25 | timeout: 5 * 1000, 26 | maxRetries: 5, 27 | }); 28 | }, 29 | [], 30 | { 31 | revalidate: 300, 32 | } 33 | ); 34 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | import tseslint from "typescript-eslint"; 4 | import pluginReact from "eslint-plugin-react"; 5 | import eslintConfigPrettier from "eslint-config-prettier"; 6 | 7 | export default [ 8 | { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] }, 9 | { languageOptions: { globals: { ...globals.browser, ...globals.node } } }, 10 | pluginJs.configs.recommended, 11 | ...tseslint.configs.recommended, 12 | pluginReact.configs.flat.recommended, 13 | pluginReact.configs.flat['jsx-runtime'], 14 | eslintConfigPrettier, 15 | { 16 | rules: { 17 | "react/react-in-jsx-scope": "off", 18 | "@typescript-eslint/no-unused-vars": "off", 19 | "@typescript-eslint/no-explicit-any": "off", 20 | "no-case-declarations": "off", 21 | }, 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /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 '@/utils/helpers'; 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 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/utils/helpers'; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes { 7 | className?: string; 8 | } 9 | 10 | const Textarea = React.forwardRef( 11 | ({ className, ...props }, ref) => { 12 | return ( 13 |