├── app ├── .eslintignore ├── .eslintrc.json ├── postcss.config.js ├── .prettierrc ├── src │ ├── middleware.ts │ ├── components │ │ ├── ui │ │ │ ├── skeleton.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── heading.tsx │ │ │ ├── chat │ │ │ │ ├── chat-message-list.tsx │ │ │ │ ├── chat-input.tsx │ │ │ │ ├── message-loading.tsx │ │ │ │ ├── expandable-chat.tsx │ │ │ │ └── chat-bubble.tsx │ │ │ ├── textarea.tsx │ │ │ ├── label.tsx │ │ │ ├── input.tsx │ │ │ ├── separator.tsx │ │ │ ├── toaster.tsx │ │ │ ├── modal.tsx │ │ │ ├── sonner.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── badge.tsx │ │ │ ├── avatar.tsx │ │ │ ├── alert.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── button.tsx │ │ │ ├── tabs.tsx │ │ │ ├── accordion.tsx │ │ │ ├── card.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── table.tsx │ │ │ ├── data-table.tsx │ │ │ ├── dialog.tsx │ │ │ ├── sheet.tsx │ │ │ ├── form.tsx │ │ │ ├── alert-dialog.tsx │ │ │ ├── toast.tsx │ │ │ ├── select.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── menubar.tsx │ │ │ └── chart.tsx │ │ ├── shared │ │ │ ├── theme-provider.tsx │ │ │ ├── theme-toggle.tsx │ │ │ ├── brand-icons.tsx │ │ │ └── icons.tsx │ │ ├── go-back.tsx │ │ ├── layout │ │ │ ├── page-container.tsx │ │ │ └── cancel-confirm-modal.tsx │ │ ├── OGImgEl.tsx │ │ ├── copy-button.tsx │ │ ├── modals │ │ │ └── alert-modal.tsx │ │ └── billing-form.tsx │ ├── lib │ │ ├── server │ │ │ ├── prisma.ts │ │ │ └── minio │ │ │ │ ├── client.ts │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── helpers.ts │ │ ├── client │ │ │ └── minio │ │ │ │ ├── index.ts │ │ │ │ └── helpers.ts │ │ └── utils.ts │ ├── app │ │ ├── manifest.json │ │ ├── error.tsx │ │ ├── global-error.tsx │ │ ├── _components │ │ │ ├── upload-gallery.tsx │ │ │ └── upload-form.tsx │ │ ├── layout.tsx │ │ ├── sw.ts │ │ ├── not-found.tsx │ │ ├── page.tsx │ │ ├── api │ │ │ └── s3 │ │ │ │ └── presigned │ │ │ │ └── route.ts │ │ └── globals.css │ ├── hooks │ │ ├── use-scroll.ts │ │ ├── use-mobile.tsx │ │ ├── use-breadcrumbs.tsx │ │ └── use-toast.ts │ └── types │ │ └── index.ts ├── .env.example ├── components.json ├── prisma │ └── schema.prisma ├── public │ ├── vercel.svg │ └── next.svg ├── next.config.mjs ├── velite.config.ts ├── tsconfig.json ├── README.md ├── Dockerfile ├── package.json └── tailwind.config.js ├── .env.example ├── .gitignore ├── docker-compose-traefik.yml ├── docker-compose.yml └── README.md /app/.eslintignore: -------------------------------------------------------------------------------- 1 | ui/ 2 | *.d.ts 3 | *.config.* -------------------------------------------------------------------------------- /app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "prettier" 5 | ] 6 | } -------------------------------------------------------------------------------- /app/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": false, 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "plugins": ["prettier-plugin-tailwindcss"] 8 | } 9 | -------------------------------------------------------------------------------- /app/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest } from "next/server"; 2 | 3 | export function middleware(request: NextRequest) {} 4 | 5 | export const config = { 6 | matcher: [ 7 | "/((?!api|static|.*\\..*|_next|favicon.ico|sitemap.xml|robots.txt).*)", 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /app/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "~/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /app/src/lib/server/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | declare global { 4 | // eslint-disable-next-line no-var 5 | var prisma: PrismaClient | undefined; 6 | } 7 | 8 | const prisma = global.prisma || new PrismaClient(); 9 | 10 | if (process.env.NODE_ENV !== "production") global.prisma = prisma; 11 | 12 | export default prisma; 13 | -------------------------------------------------------------------------------- /app/.env.example: -------------------------------------------------------------------------------- 1 | # Supabase 2 | # Connect to Supabase via connection pooling with Supavisor. 3 | DATABASE_URL= 4 | # Direct connection to the database. Used for migrations. 5 | DIRECT_URL= 6 | 7 | # MinIo S3 Access 8 | MINIO_ENDPOINT=s3.example.com 9 | MINIO_SSL=true 10 | MINIO_PORT=443 11 | MINIO_ACCESS_KEY=minio 12 | MINIO_SECRET_KEY=miniosecret 13 | MINIO_BUCKET_NAME=boot -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Traefik Config 2 | TRAEFIK_USERNAME=admin 3 | ACME_EMAIL=admin@example.com 4 | 5 | # Traefik Domains 6 | TRAEFIK_DOMAIN=traefik.example.com 7 | MINIO_CONSOLE_DOMAIN=s3-console.example.com 8 | MINIO_DOMAIN=s3.example.com 9 | FRONTEND_APP_DOMAIN=app.example.com 10 | 11 | # MinIo 12 | MINIO_ROOT_USER=admin 13 | MINIO_ROOT_PASSWORD=12345678 14 | MINIO_API_PORT=9000 15 | MINIO_CONSOLE_PORT=9001 16 | -------------------------------------------------------------------------------- /app/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "~/components", 14 | "utils": "~/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /app/src/components/ui/heading.tsx: -------------------------------------------------------------------------------- 1 | interface HeadingProps { 2 | title: string; 3 | description: string; 4 | } 5 | 6 | export const Heading: React.FC = ({ title, description }) => { 7 | return ( 8 |
9 |

{title}

10 |

{description}

11 |
12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /app/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "postgresql" 7 | url = env("DATABASE_URL") 8 | directUrl = env("DIRECT_URL") 9 | } 10 | 11 | model File { 12 | id String @id @default(cuid()) 13 | 14 | bucket String? 15 | fileName String? 16 | originalName String? 17 | size Float? 18 | url String? 19 | 20 | createdAt DateTime @default(now()) 21 | } -------------------------------------------------------------------------------- /app/src/lib/client/minio/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | handleUpload, 3 | formatBytes, 4 | validateFiles, 5 | createFormData, 6 | getPresignedUrls, 7 | MAX_FILE_SIZE_NEXTJS_ROUTE, 8 | MAX_FILE_SIZE_S3_ENDPOINT, 9 | FILE_NUMBER_LIMIT, 10 | } from "./helpers"; 11 | 12 | export { 13 | handleUpload, 14 | formatBytes, 15 | validateFiles, 16 | createFormData, 17 | getPresignedUrls, 18 | MAX_FILE_SIZE_NEXTJS_ROUTE, 19 | MAX_FILE_SIZE_S3_ENDPOINT, 20 | FILE_NUMBER_LIMIT, 21 | }; 22 | -------------------------------------------------------------------------------- /app/src/components/shared/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 4 | import { type ThemeProviderProps } from "next-themes"; 5 | 6 | export default function ThemeProvider({ 7 | children, 8 | ...props 9 | }: ThemeProviderProps) { 10 | return ( 11 | 17 | {children} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /app/src/components/go-back.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useRouter } from "next/navigation"; 3 | import Icons from "./shared/icons"; 4 | import { Button } from "./ui/button"; 5 | 6 | export default function GoBack() { 7 | const router = useRouter(); 8 | return ( 9 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Boot", 3 | "short_name": "Boot", 4 | "description": "پلتفرم هوش تجاری Boot", 5 | "start_url": "/", 6 | "display": "standalone", 7 | "background_color": "#FCFCFC", 8 | "theme_color": "#DEF81B", 9 | "icons": [ 10 | { 11 | "src": "/icons/192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "/icons/512.png", 17 | "sizes": "512x512", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "/icons/700.png", 22 | "sizes": "700x700", 23 | "type": "image/png" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /app/src/hooks/use-scroll.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react"; 2 | 3 | export default function useScroll(threshold: number) { 4 | const [scrolled, setScrolled] = useState(false); 5 | 6 | const onScroll = useCallback(() => { 7 | setScrolled(window.scrollY > threshold); 8 | }, [threshold]); 9 | 10 | useEffect(() => { 11 | onScroll(); 12 | }, [onScroll]); 13 | 14 | useEffect(() => { 15 | window.addEventListener("scroll", onScroll); 16 | return () => window.removeEventListener("scroll", onScroll); 17 | }, [onScroll]); 18 | 19 | return scrolled; 20 | } 21 | -------------------------------------------------------------------------------- /app/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/lib/server/minio/client.ts: -------------------------------------------------------------------------------- 1 | import * as Minio from "minio"; 2 | 3 | const MINIO_ENDPOINT = process.env.MINIO_ENDPOINT; 4 | const MINIO_SSL = process.env.MINIO_SSL; 5 | const MINIO_PORT = parseInt(process.env.MINIO_PORT!); 6 | const MINIO_ACCESS_KEY = process.env.MINIO_ACCESS_KEY; 7 | const MINIO_SECRET_KEY = process.env.MINIO_SECRET_KEY; 8 | const MINIO_BUCKET_NAME = process.env.MINIO_BUCKET_NAME; 9 | 10 | export const MinioClient = new Minio.Client({ 11 | endPoint: MINIO_ENDPOINT!, 12 | port: MINIO_PORT!, 13 | useSSL: MINIO_SSL === "true", 14 | accessKey: MINIO_ACCESS_KEY, 15 | secretKey: MINIO_SECRET_KEY, 16 | }); 17 | -------------------------------------------------------------------------------- /app/src/hooks/use-mobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined) 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 | } 13 | mql.addEventListener("change", onChange) 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 | return () => mql.removeEventListener("change", onChange) 16 | }, []) 17 | 18 | return !!isMobile 19 | } 20 | -------------------------------------------------------------------------------- /app/src/lib/server/minio/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createBucketIfNotExists, 3 | saveFileInBucket, 4 | checkFileExistsInBucket, 5 | getFileFromBucket, 6 | deleteFileFromBucket, 7 | createPresignedUrlToUpload, 8 | createPresignedUrlToDownload, 9 | } from "./helpers"; 10 | 11 | import type { 12 | ShortFileProp, 13 | PresignedUrlProp, 14 | FileInDBProp, 15 | } from "~/lib/server/minio/types"; 16 | 17 | export type { ShortFileProp, PresignedUrlProp, FileInDBProp }; 18 | 19 | export { 20 | saveFileInBucket, 21 | checkFileExistsInBucket, 22 | getFileFromBucket, 23 | deleteFileFromBucket, 24 | createPresignedUrlToUpload, 25 | createPresignedUrlToDownload, 26 | }; 27 | -------------------------------------------------------------------------------- /app/src/components/ui/chat/chat-message-list.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { cn } from "~/lib/utils"; 3 | 4 | interface ChatMessageListProps extends React.HTMLAttributes {} 5 | 6 | const ChatMessageList = React.forwardRef( 7 | ({ className, children, ...props }, ref) => ( 8 |
16 | {children} 17 |
18 | ) 19 | ); 20 | 21 | ChatMessageList.displayName = "ChatMessageList"; 22 | 23 | export { ChatMessageList }; 24 | -------------------------------------------------------------------------------- /app/next.config.mjs: -------------------------------------------------------------------------------- 1 | import withSerwistInit from "@serwist/next"; 2 | 3 | const withSerwist = withSerwistInit({ 4 | // Note: This is only an example. If you use Pages Router, 5 | // use something else that works, such as "service-worker/index.ts". 6 | swSrc: "src/app/sw.ts", 7 | swDest: "public/sw.js", 8 | disable: process.env.NODE_ENV !== "production", 9 | }); 10 | 11 | /** 12 | * @type {import('next').NextConfig} 13 | */ 14 | const nextConfig = { 15 | output: "standalone", 16 | productionBrowserSourceMaps: process.env.NODE_ENV !== "production", 17 | // trailingSlash: true, 18 | eslint: { 19 | ignoreDuringBuilds: true, 20 | }, 21 | }; 22 | 23 | export default withSerwist(nextConfig); 24 | -------------------------------------------------------------------------------- /app/src/app/error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import { Button } from "~/components/ui/button"; 5 | 6 | export default function Error({ 7 | error, 8 | reset, 9 | }: { 10 | error: Error; 11 | reset: () => void; 12 | }) { 13 | useEffect(() => { 14 | // Log the error to an error reporting service 15 | console.log(error); 16 | }, [error]); 17 | 18 | return ( 19 |
20 |

21 | Oops, Something Went Wrong! 22 |

23 | 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /app/src/components/layout/page-container.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ScrollArea } from "~/components/ui/scroll-area"; 3 | 4 | export default function PageContainer({ 5 | children, 6 | scrollable = true, 7 | }: { 8 | children: React.ReactNode; 9 | scrollable?: boolean; 10 | }) { 11 | return ( 12 | <> 13 | {scrollable ? ( 14 | 15 |
16 | {children} 17 |
18 |
19 | ) : ( 20 |
21 | {children} 22 |
23 | )} 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /app/src/app/global-error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import { Button } from "~/components/ui/button"; 5 | 6 | export default function GlobalError({ 7 | error, 8 | reset, 9 | }: { 10 | error: Error; 11 | reset: () => void; 12 | }) { 13 | useEffect(() => { 14 | // Log the error to an error reporting service 15 | console.log(error); 16 | }, [error]); 17 | 18 | return ( 19 |
20 |

21 | Oops, Something Went Wrong! 22 |

23 | 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /app/src/components/OGImgEl.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | 3 | export const RenderIMGEl = ({ 4 | logo, 5 | image, 6 | }: { 7 | logo: string; 8 | image: string; 9 | }) => { 10 | return ( 11 |
12 |
13 | ChadNext Logo 14 |
ChadNext
15 |
20 |
21 | ChadNext Logo 22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /app/src/app/_components/upload-gallery.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | import prisma from "~/lib/server/prisma"; 3 | import { toast } from "sonner"; 4 | 5 | export default async function UploadGallery() { 6 | const files = await prisma.file.findMany({ 7 | orderBy: { 8 | createdAt: "desc", 9 | }, 10 | }); 11 | 12 | if (!files) { 13 | toast.error("Something went wrong"); 14 | return
Something went wrong
; 15 | } 16 | 17 | return ( 18 |
19 | {files.map((file) => ( 20 | Image uploaded by user 26 | ))} 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /app/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "~/lib/utils"; 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<"textarea"> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |