diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7746d64..b9ea2ca 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -31,7 +31,7 @@ jobs: - run: npm i - run: npm run format - test: + test: name: build-app runs-on: ubuntu-latest steps: @@ -42,4 +42,4 @@ jobs: - name: Install dependencies # (assuming your project has dependencies) run: bun install # You can use npm/yarn/pnpm instead if you prefer - name: Run tests - run: bun test \ No newline at end of file + run: bun test diff --git a/Dockerfile.frontend b/Dockerfile.frontend index c1729d3..e69de29 100644 --- a/Dockerfile.frontend +++ b/Dockerfile.frontend @@ -1,11 +0,0 @@ -FROM node:18-alpine AS Runner - -WORKDIR /app - -COPY . . - -RUN npm install - -EXPOSE 3000 - -CMD ["npm" ,"run" ,"start:web"] \ No newline at end of file diff --git a/Dockerfile.worker b/Dockerfile.worker index bee0f1b..fd26bac 100644 --- a/Dockerfile.worker +++ b/Dockerfile.worker @@ -10,4 +10,4 @@ RUN npm install RUN npm run build:worker -CMD ["npm","run", "start:worker"] \ No newline at end of file +CMD ["npm","run", "start:worker"] diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore deleted file mode 100644 index f886745..0000000 --- a/apps/docs/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# env files (can opt-in for commiting if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/apps/docs/README.md b/apps/docs/README.md deleted file mode 100644 index a98bfa8..0000000 --- a/apps/docs/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/apps/docs/app/favicon.ico b/apps/docs/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/apps/docs/app/favicon.ico and /dev/null differ diff --git a/apps/docs/app/fonts/GeistMonoVF.woff b/apps/docs/app/fonts/GeistMonoVF.woff deleted file mode 100644 index f2ae185..0000000 Binary files a/apps/docs/app/fonts/GeistMonoVF.woff and /dev/null differ diff --git a/apps/docs/app/fonts/GeistVF.woff b/apps/docs/app/fonts/GeistVF.woff deleted file mode 100644 index 1b62daa..0000000 Binary files a/apps/docs/app/fonts/GeistVF.woff and /dev/null differ diff --git a/apps/docs/app/globals.css b/apps/docs/app/globals.css deleted file mode 100644 index 6af7ecb..0000000 --- a/apps/docs/app/globals.css +++ /dev/null @@ -1,50 +0,0 @@ -:root { - --background: #ffffff; - --foreground: #171717; -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: var(--foreground); - background: var(--background); -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -a { - color: inherit; - text-decoration: none; -} - -.imgDark { - display: none; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } - - .imgLight { - display: none; - } - .imgDark { - display: unset; - } -} diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx deleted file mode 100644 index 8469537..0000000 --- a/apps/docs/app/layout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { Metadata } from "next"; -import localFont from "next/font/local"; -import "./globals.css"; - -const geistSans = localFont({ - src: "./fonts/GeistVF.woff", - variable: "--font-geist-sans", -}); -const geistMono = localFont({ - src: "./fonts/GeistMonoVF.woff", - variable: "--font-geist-mono", -}); - -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - {children} - - - ); -} diff --git a/apps/docs/app/page.module.css b/apps/docs/app/page.module.css deleted file mode 100644 index 3630662..0000000 --- a/apps/docs/app/page.module.css +++ /dev/null @@ -1,188 +0,0 @@ -.page { - --gray-rgb: 0, 0, 0; - --gray-alpha-200: rgba(var(--gray-rgb), 0.08); - --gray-alpha-100: rgba(var(--gray-rgb), 0.05); - - --button-primary-hover: #383838; - --button-secondary-hover: #f2f2f2; - - display: grid; - grid-template-rows: 20px 1fr 20px; - align-items: center; - justify-items: center; - min-height: 100svh; - padding: 80px; - gap: 64px; - font-synthesis: none; -} - -@media (prefers-color-scheme: dark) { - .page { - --gray-rgb: 255, 255, 255; - --gray-alpha-200: rgba(var(--gray-rgb), 0.145); - --gray-alpha-100: rgba(var(--gray-rgb), 0.06); - - --button-primary-hover: #ccc; - --button-secondary-hover: #1a1a1a; - } -} - -.main { - display: flex; - flex-direction: column; - gap: 32px; - grid-row-start: 2; -} - -.main ol { - font-family: var(--font-geist-mono); - padding-left: 0; - margin: 0; - font-size: 14px; - line-height: 24px; - letter-spacing: -0.01em; - list-style-position: inside; -} - -.main li:not(:last-of-type) { - margin-bottom: 8px; -} - -.main code { - font-family: inherit; - background: var(--gray-alpha-100); - padding: 2px 4px; - border-radius: 4px; - font-weight: 600; -} - -.ctas { - display: flex; - gap: 16px; -} - -.ctas a { - appearance: none; - border-radius: 128px; - height: 48px; - padding: 0 20px; - border: none; - font-family: var(--font-geist-sans); - border: 1px solid transparent; - transition: background 0.2s, color 0.2s, border-color 0.2s; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - line-height: 20px; - font-weight: 500; -} - -a.primary { - background: var(--foreground); - color: var(--background); - gap: 8px; -} - -a.secondary { - border-color: var(--gray-alpha-200); - min-width: 180px; -} - -button.secondary { - appearance: none; - border-radius: 128px; - height: 48px; - padding: 0 20px; - border: none; - font-family: var(--font-geist-sans); - border: 1px solid transparent; - transition: background 0.2s, color 0.2s, border-color 0.2s; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - line-height: 20px; - font-weight: 500; - background: transparent; - border-color: var(--gray-alpha-200); - min-width: 180px; -} - -.footer { - font-family: var(--font-geist-sans); - grid-row-start: 3; - display: flex; - gap: 24px; -} - -.footer a { - display: flex; - align-items: center; - gap: 8px; -} - -.footer img { - flex-shrink: 0; -} - -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - a.primary:hover { - background: var(--button-primary-hover); - border-color: transparent; - } - - a.secondary:hover { - background: var(--button-secondary-hover); - border-color: transparent; - } - - .footer a:hover { - text-decoration: underline; - text-underline-offset: 4px; - } -} - -@media (max-width: 600px) { - .page { - padding: 32px; - padding-bottom: 80px; - } - - .main { - align-items: center; - } - - .main ol { - text-align: center; - } - - .ctas { - flex-direction: column; - } - - .ctas a { - font-size: 14px; - height: 40px; - padding: 0 16px; - } - - a.secondary { - min-width: auto; - } - - .footer { - flex-wrap: wrap; - align-items: center; - justify-content: center; - } -} - -@media (prefers-color-scheme: dark) { - .logo { - filter: invert(); - } -} diff --git a/apps/docs/app/page.tsx b/apps/docs/app/page.tsx deleted file mode 100644 index 828709a..0000000 --- a/apps/docs/app/page.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import Image, { type ImageProps } from "next/image"; -import { Button } from "@repo/ui/button"; -import styles from "./page.module.css"; - -type Props = Omit & { - srcLight: string; - srcDark: string; -}; - -const ThemeImage = (props: Props) => { - const { srcLight, srcDark, ...rest } = props; - - return ( - <> - - - - ); -}; - -export default function Home() { - return ( -
-
- -
    -
  1. - Get started by editing apps/docs/app/page.tsx -
  2. -
  3. Save and see your changes instantly.
  4. -
- -
- - Vercel logomark - Deploy now - - - Read our docs - -
- -
- -
- ); -} diff --git a/apps/docs/eslint.config.js b/apps/docs/eslint.config.js deleted file mode 100644 index e8759ff..0000000 --- a/apps/docs/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { nextJsConfig } from "@repo/eslint-config/next-js"; - -/** @type {import("eslint").Linter.Config} */ -export default nextJsConfig; diff --git a/apps/docs/next.config.js b/apps/docs/next.config.js deleted file mode 100644 index 4678774..0000000 --- a/apps/docs/next.config.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = {}; - -export default nextConfig; diff --git a/apps/docs/package.json b/apps/docs/package.json deleted file mode 100644 index 1e5e4c0..0000000 --- a/apps/docs/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "docs", - "version": "0.1.0", - "type": "module", - "private": true, - "scripts": { - "dev": "next dev --turbopack --port 3001", - "build": "next build", - "start": "next start", - "lint": "next lint --max-warnings 0", - "check-types": "tsc --noEmit" - }, - "dependencies": { - "@repo/ui": "*", - "next": "^15.2.1", - "react": "^19.0.0", - "react-dom": "^19.0.0" - }, - "devDependencies": { - "@repo/eslint-config": "*", - "@repo/typescript-config": "*", - "@types/node": "^22.13.10", - "@types/react": "19.0.10", - "@types/react-dom": "19.0.4", - "eslint": "^9.22.0", - "typescript": "5.8.2" - } -} diff --git a/apps/docs/public/file-text.svg b/apps/docs/public/file-text.svg deleted file mode 100644 index 9cfb3c9..0000000 --- a/apps/docs/public/file-text.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/docs/public/globe.svg b/apps/docs/public/globe.svg deleted file mode 100644 index 4230a3d..0000000 --- a/apps/docs/public/globe.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/apps/docs/public/next.svg b/apps/docs/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/apps/docs/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/docs/public/turborepo-dark.svg b/apps/docs/public/turborepo-dark.svg deleted file mode 100644 index dae38fe..0000000 --- a/apps/docs/public/turborepo-dark.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/apps/docs/public/turborepo-light.svg b/apps/docs/public/turborepo-light.svg deleted file mode 100644 index ddea915..0000000 --- a/apps/docs/public/turborepo-light.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/apps/docs/public/vercel.svg b/apps/docs/public/vercel.svg deleted file mode 100644 index 0164ddc..0000000 --- a/apps/docs/public/vercel.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/apps/docs/public/window.svg b/apps/docs/public/window.svg deleted file mode 100644 index bbc7800..0000000 --- a/apps/docs/public/window.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json deleted file mode 100644 index 7aef056..0000000 --- a/apps/docs/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "@repo/typescript-config/nextjs.json", - "compilerOptions": { - "plugins": [ - { - "name": "next" - } - ] - }, - "include": [ - "**/*.ts", - "**/*.tsx", - "next-env.d.ts", - "next.config.js", - ".next/types/**/*.ts" - ], - "exclude": [ - "node_modules" - ] -} diff --git a/apps/web/app/api/auth/[...nextauth]/route.ts b/apps/web/app/(backend)/api/auth/[...nextauth]/route.ts similarity index 100% rename from apps/web/app/api/auth/[...nextauth]/route.ts rename to apps/web/app/(backend)/api/auth/[...nextauth]/route.ts diff --git a/apps/web/app/api/payment/subscribe/route.ts b/apps/web/app/(backend)/api/payment/subscribe/route.ts similarity index 96% rename from apps/web/app/api/payment/subscribe/route.ts rename to apps/web/app/(backend)/api/payment/subscribe/route.ts index 94bbcb2..7a89e07 100644 --- a/apps/web/app/api/payment/subscribe/route.ts +++ b/apps/web/app/(backend)/api/payment/subscribe/route.ts @@ -3,7 +3,7 @@ import { clusterApiUrl, Connection } from "@solana/web3.js"; import prisma from "@repo/db"; import { z } from "zod"; -const SOLANA_NETWORK = "devnet"; // Change to "mainnet-beta" for production +const SOLANA_NETWORK = "mainnet-beta"; // Change to "mainnet-beta" for production export async function POST(req: NextRequest) { try { diff --git a/apps/web/app/api/payment/verify/route.ts b/apps/web/app/(backend)/api/payment/verify/route.ts similarity index 100% rename from apps/web/app/api/payment/verify/route.ts rename to apps/web/app/(backend)/api/payment/verify/route.ts diff --git a/apps/web/app/api/videos/add-video-record/route.ts b/apps/web/app/(backend)/api/videos/add-video-record/route.ts similarity index 100% rename from apps/web/app/api/videos/add-video-record/route.ts rename to apps/web/app/(backend)/api/videos/add-video-record/route.ts diff --git a/apps/web/app/api/videos/pre-signed-url/route.ts b/apps/web/app/(backend)/api/videos/pre-signed-url/route.ts similarity index 100% rename from apps/web/app/api/videos/pre-signed-url/route.ts rename to apps/web/app/(backend)/api/videos/pre-signed-url/route.ts diff --git a/apps/web/app/api/videos/video-status/route.ts b/apps/web/app/(backend)/api/videos/video-status/route.ts similarity index 100% rename from apps/web/app/api/videos/video-status/route.ts rename to apps/web/app/(backend)/api/videos/video-status/route.ts diff --git a/apps/web/app/(frontend)/about/page.tsx b/apps/web/app/(frontend)/about/page.tsx new file mode 100644 index 0000000..4e35426 --- /dev/null +++ b/apps/web/app/(frontend)/about/page.tsx @@ -0,0 +1,312 @@ +import Link from "next/link"; +import Image from "next/image"; +import { ArrowRight, Video } from "lucide-react"; +import { Button } from "@repo/ui/components/ui/button"; + +export default function AboutPage() { + return ( +
+
+
+ +
+
+
+
+
+
+
+

+ About OpenMux +

+

+ We're on a mission to make video processing accessible to + everyone. +

+
+
+
+ Team at work +
+
+

+ Our Story +

+

+ Founded in 2020, OpenMux began with a simple idea: make + professional video processing tools accessible to everyone. + What started as a small project between friends has grown + into a platform used by creators, businesses, and + enterprises worldwide. +

+

+ Our team of engineers, designers, and video experts are + passionate about building tools that empower creators to + bring their vision to life. +

+
+
+
+
+
+
+
+
+
+

+ Our Mission +

+

+ We believe that everyone should have access to powerful video + processing tools, regardless of technical expertise or budget. +

+
+
+
+
+
+ + + + +
+
+

Accessibility

+

+ Making professional video tools accessible to everyone, + regardless of technical expertise. +

+
+
+
+
+ + + +
+
+

Innovation

+

+ Constantly pushing the boundaries of what's possible with + video processing technology. +

+
+
+
+
+ + + + + +
+
+

Simplicity

+

+ Creating intuitive interfaces that make complex video + processing tasks simple and straightforward. +

+
+
+
+
+
+
+
+
+
+

+ Meet Our Team +

+

+ The passionate people behind OpenMux. +

+
+
+
+
+ Team Member +
+

Alex Johnson

+

+ Co-Founder & CEO +

+
+
+
+ Team Member +
+

Sarah Chen

+

+ Co-Founder & CTO +

+
+
+
+ Team Member +
+

Michael Rodriguez

+

+ Head of Product +

+
+
+
+
+
+
+
+
+
+

Join Us

+

+ We're always looking for talented individuals to join our + team. +

+
+
+ + + +
+
+
+
+
+
+
+
+
+ +
+ + © 2023 OpenMux Inc. + +
+
+
+
+ ); +} diff --git a/apps/web/app/(frontend)/dashboard/layout.tsx b/apps/web/app/(frontend)/dashboard/layout.tsx new file mode 100644 index 0000000..e826f29 --- /dev/null +++ b/apps/web/app/(frontend)/dashboard/layout.tsx @@ -0,0 +1,167 @@ +"use client"; + +import type React from "react"; + +import { useState } from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { + Bell, + FileVideo, + Grid, + LogOut, + Menu, + Settings, + Upload, + User, + Video, +} from "lucide-react"; +import { Button } from "@repo/ui/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@repo/ui/components/ui/dropdown-menu"; +import { + Sheet, + SheetContent, + SheetTrigger, +} from "@repo/ui/components/ui/sheet"; +import { ThemeToggle } from "@repo/ui/components/theme-toggle"; +import { WalletConnectButton } from "@repo/ui/components/wallet-connect-button"; + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + const pathname = usePathname(); + const [isMobileNavOpen, setIsMobileNavOpen] = useState(false); + + const navigation = [ + { name: "Dashboard", href: "/dashboard", icon: Grid }, + { name: "My Videos", href: "/dashboard/videos", icon: FileVideo }, + { name: "Upload", href: "/dashboard/upload", icon: Upload }, + { name: "Settings", href: "/dashboard/settings", icon: Settings }, + { name: "Profile", href: "/dashboard/profile", icon: User }, + ]; + + return ( +
+
+
+
+ + + + + +
+
+ +
+
+ +
+ +
+ + + + + + + + + My Account + + + + Profile + + + + + Settings + + + + + + Logout + + + + +
+
+
+
+ {children} +
+
+ ); +} diff --git a/apps/web/app/(frontend)/dashboard/page.tsx b/apps/web/app/(frontend)/dashboard/page.tsx new file mode 100644 index 0000000..1f37675 --- /dev/null +++ b/apps/web/app/(frontend)/dashboard/page.tsx @@ -0,0 +1,191 @@ +import Link from "next/link"; +import { ArrowRight, Clock, FileVideo, Upload, Zap } from "lucide-react"; +import { Button } from "@repo/ui/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@repo/ui/components/ui/card"; +import { Progress } from "@repo/ui/components/ui/progress"; + +export default function DashboardPage() { + // Sample data for dashboard + const stats = [ + { + title: "Total Videos", + value: "12", + icon: FileVideo, + change: "+2 this month", + }, + { + title: "Processing Hours", + value: "24.5", + icon: Clock, + change: "+5.2 this month", + }, + { + title: "Active Tasks", + value: "3", + icon: Zap, + change: "2 completed today", + }, + ]; + + const recentVideos = [ + { + id: "1", + title: "Product Demo", + thumbnail: "/placeholder.svg?height=120&width=200", + duration: "2:45", + status: "Completed", + progress: 100, + }, + { + id: "2", + title: "Marketing Video", + thumbnail: "/placeholder.svg?height=120&width=200", + duration: "4:12", + status: "Processing", + progress: 65, + }, + { + id: "3", + title: "Team Meeting", + thumbnail: "/placeholder.svg?height=120&width=200", + duration: "32:10", + status: "Queued", + progress: 0, + }, + ]; + + return ( +
+
+
+

Dashboard

+ + + +
+ +
+ {stats.map((stat, index) => ( + + + + {stat.title} + + + + +
{stat.value}
+

{stat.change}

+
+
+ ))} +
+ +
+ + + Recent Videos + + Your recently uploaded and processed videos + + + + {recentVideos.map((video) => ( +
+
+ {video.title} +
+ {video.duration} +
+
+
+
{video.title}
+
+ + {video.status} +
+ {video.status === "Processing" && ( + + )} +
+ + + +
+ ))} +
+ + + + + +
+ + + + Quick Actions + Common tasks and shortcuts + + + + + + + + + + + + + +
+
+
+ ); +} diff --git a/apps/web/app/(frontend)/dashboard/profile/page.tsx b/apps/web/app/(frontend)/dashboard/profile/page.tsx new file mode 100644 index 0000000..4a146af --- /dev/null +++ b/apps/web/app/(frontend)/dashboard/profile/page.tsx @@ -0,0 +1,358 @@ +"use client"; + +import { useState } from "react"; +import Image from "next/image"; +import { + Calendar, + Edit2, + FileVideo, + Link2, + Mail, + MapPin, + Save, + Upload, + Github, + Wallet, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@repo/ui/components/ui/input"; +import { Label } from "@repo/ui/components/ui/label"; +import { Textarea } from "@repo/ui/components/ui/textarea"; +import { Toast } from "@repo/ui/components/ui/toast"; +import { formatDate } from "@repo/lib"; + +export default function ProfilePage() { + const [isEditing, setIsEditing] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + // Sample user data + const user = { + name: "John Doe", + username: "johndoe", + email: "john.doe@example.com", + avatar: "/placeholder.svg?height=200&width=200", + bio: "Video editor and content creator. I love creating stunning visuals and experimenting with new effects.", + location: "New York, USA", + website: "https://johndoe.com", + joinedDate: "2022-06-15", + stats: { + videos: 24, + processingHours: 48.5, + storage: "120 GB", + }, + recentVideos: [ + { + id: "1", + title: "Product Demo", + thumbnail: "/placeholder.svg?height=120&width=200", + date: "2023-03-15", + }, + { + id: "2", + title: "Marketing Video", + thumbnail: "/placeholder.svg?height=120&width=200", + date: "2023-03-10", + }, + { + id: "3", + title: "Team Meeting", + thumbnail: "/placeholder.svg?height=120&width=200", + date: "2023-03-05", + }, + ], + }; + + const handleSaveProfile = () => { + setIsLoading(true); + + // Simulate saving profile + setTimeout(() => { + setIsLoading(false); + setIsEditing(false); + Toast({ + title: "Profile updated", + }); + }, 1000); + }; + + return ( +
+
+
+

My Profile

+ {isEditing ? ( +
+ + +
+ ) : ( + + )} +
+ +
+
+ + +
+
+ {user.name} + {isEditing && ( + + )} +
+ {isEditing ? ( +
+ + +
+ ) : ( +
+

{user.name}

+

@{user.username}

+
+ )} +
+ + {!isEditing && ( +
+
+ + {user.email} +
+ {user.location && ( +
+ + {user.location} +
+ )} + {user.website && ( + + )} +
+ + Joined {formatDate(user.joinedDate)} +
+
+ )} + + {isEditing && ( +
+
+ + +
+
+ + +
+
+ + +
+
+ )} +
+
+ + + + Stats + + +
+
+

{user.stats.videos}

+

Videos

+
+
+

+ {user.stats.processingHours} +

+

Hours

+
+
+

{user.stats.storage}

+

Storage

+
+
+
+
+
+ +
+ + + About + {!isEditing && ( + + Bio and personal information + + )} + + + {isEditing ? ( +
+ +