diff --git a/app/(authenticated)/(app)/DesktopSidebar.tsx b/app/(authenticated)/(app)/DesktopSidebar.tsx new file mode 100644 index 0000000..f5291ab --- /dev/null +++ b/app/(authenticated)/(app)/DesktopSidebar.tsx @@ -0,0 +1,84 @@ +import { Logo } from "@/components/logo"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { BarChart, Database, FileKey, Filter, FormInput, Home } from "lucide-react"; +import { ChannelLink } from "./channelLink"; +import { TeamSwitcher } from "./TeamSwitcher"; +import Link from "next/link"; +type Props = { + navigation: { + href: string; + external?: boolean; + label: string; + }[]; + + channels: { + name: string; + }[]; +}; + +export const DesktopSidebar: React.FC = ({ navigation, channels }) => { + return ( + + ); +}; diff --git a/app/(authenticated)/(app)/MobileSidebar.tsx b/app/(authenticated)/(app)/MobileSidebar.tsx new file mode 100644 index 0000000..122af63 --- /dev/null +++ b/app/(authenticated)/(app)/MobileSidebar.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { Logo } from "@/components/logo"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { BarChart, Database, FileKey, Filter, FormInput, Home, Menu } from "lucide-react"; +import { ChannelLink } from "./channelLink"; +import { TeamSwitcher } from "./TeamSwitcher"; +import Link from "next/link"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +type Props = { + navigation: { + href: string; + external?: boolean; + label: string; + }[]; + + channels: { + name: string; + }[]; +}; + +export const MobileSidebar: React.FC = ({ navigation, channels }) => { + return ( +
+ +
+ + + +
+ + + + {" "} + + Omnidash + + {/* + Make changes to your profile here. Click save when you're done. + */} + +
+
+

{/* Events */}

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

Events

+ +
+ {channels.map((channel) => ( + + ))} +
+
+
+
+ + + +
+
+
+ ); +}; diff --git a/app/(authenticated)/(app)/TeamSwitcher.tsx b/app/(authenticated)/(app)/TeamSwitcher.tsx new file mode 100644 index 0000000..91c6520 --- /dev/null +++ b/app/(authenticated)/(app)/TeamSwitcher.tsx @@ -0,0 +1,99 @@ +"use client"; + +import { + DropdownMenuTrigger, + DropdownMenu, + DropdownMenuContent, + DropdownMenuCheckboxItem, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, +} from "@/components/ui/dropdown-menu"; +import { Check, ChevronsUpDown, Plus, Key, Book, LogOut, Rocket } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Loading } from "@/components/loading"; + +import { cn } from "@/lib/utils"; +import { useAuth, useOrganization, useOrganizationList, useUser } from "@clerk/clerk-react"; +import { Avatar, AvatarImage } from "@/components/ui/avatar"; +import { AvatarFallback } from "@radix-ui/react-avatar"; + +type Props = {}; + +export const TeamSwitcher: React.FC = (): JSX.Element => { + const { setActive, organizationList } = useOrganizationList(); + const { organization: currentOrg } = useOrganization(); + + const { signOut } = useAuth(); + const { user } = useUser(); + + const router = useRouter(); + + const [loading, setLoading] = useState(false); + + async function changeOrg(id: string | null) { + if (!setActive) { + return; + } + try { + setLoading(true); + await setActive({ organization: id }); + router.refresh(); + } finally { + setLoading(false); + } + } + + return ( + + {loading ? ( + + ) : ( + +
+ + {user?.profileImageUrl ? ( + + ) : null} + + {(currentOrg?.slug ?? user?.username ?? "").slice(0, 2).toUpperCase() ?? "P"} + + + {currentOrg?.name ?? "Personal"} +
+ {/* */} + +
+ )} + + + + + + + +
+ ); +}; diff --git a/app/(authenticated)/(app)/channelLink.tsx b/app/(authenticated)/(app)/channelLink.tsx new file mode 100644 index 0000000..d1ac988 --- /dev/null +++ b/app/(authenticated)/(app)/channelLink.tsx @@ -0,0 +1,28 @@ +"use client"; + +import Link from "next/link"; +import { useSelectedLayoutSegments } from "next/navigation"; +import { Hash } from "lucide-react"; + +import { Button } from "@/components/ui/button"; + +type Props = { + href: string; + channelName: string | null; +}; + +export const ChannelLink: React.FC = ({ href, channelName }) => { + const isActive = channelName === useSelectedLayoutSegments().at(1); + return ( + + + + ); +}; diff --git a/app/(authenticated)/(app)/layout.tsx b/app/(authenticated)/(app)/layout.tsx new file mode 100644 index 0000000..6c83cdb --- /dev/null +++ b/app/(authenticated)/(app)/layout.tsx @@ -0,0 +1,32 @@ +import Link from "next/link"; +import { BarChart, FileKey, Filter, FormInput, Keyboard, Menu, Tornado } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { TeamSwitcher } from "./TeamSwitcher"; +import { auth } from "@clerk/nextjs/app-beta"; +import { notFound } from "next/navigation"; +import { ChannelLink } from "./channelLink"; +import { Logo } from "@/components/logo"; +import { DesktopSidebar } from "./DesktopSidebar"; +import { MobileNav } from "@/components/mobile-nav"; +import { MobileSidebar } from "./MobileSidebar"; +import { getTenantId } from "@/lib/auth"; +import { Fragment } from "react"; + +interface LayoutProps { + children: React.ReactNode; +} + +export default async function Layout({ children }: LayoutProps) { + const tenantId = getTenantId(); + + return ( + + + + + +
{children}
+
+ ); +} diff --git a/app/(authenticated)/(app)/overview/loading.tsx b/app/(authenticated)/(app)/overview/loading.tsx new file mode 100644 index 0000000..f37ff65 --- /dev/null +++ b/app/(authenticated)/(app)/overview/loading.tsx @@ -0,0 +1,9 @@ +import { Loading as Spinner } from "@/components/loading"; + +export default function Loading() { + return ( +
+ +
+ ); +} diff --git a/app/(authenticated)/(app)/overview/page.tsx b/app/(authenticated)/(app)/overview/page.tsx new file mode 100644 index 0000000..3cfd61f --- /dev/null +++ b/app/(authenticated)/(app)/overview/page.tsx @@ -0,0 +1,119 @@ +import Link from "next/link"; +import { notFound, redirect } from "next/navigation"; + +import { auth } from "@clerk/nextjs/app-beta"; +import { PageHeader } from "@/components/page-header"; +import { useUser } from "@clerk/clerk-react"; +import { Fragment } from "react"; +import { cn } from "@/lib/utils"; +import { getTenantId } from "@/lib/auth"; + +export default async function Page(_props: { + params: { tenantSlug: string }; +}) { + const tenantId = getTenantId(); + + const stats: { + label: string; + value: string; + }[] = [ + { + label: "Total Channels", + value: '0', + }, + { + label: "Total Events (7 days)", + value: '0', + }, + ]; + return ( +
+
+ {/* Stats */} +
+
+
+
+ {/*
+
+
*/} +

+ + {"Personal Account"} + +

+
+ {/*

{channel.description}

*/} +
+
+ test +
+
+
= 4, + }, + )} + > + {" "} + {stats.map((stat, statIdx) => ( +
+
{stat.label}
+ {/*
+ {stat.change} +
*/} +
+ {stat.value} +
+
+ ))} +
+
+
+ +
+ {/* Recent activity table */} +
+
+

+ Recent events +

+
+
+
+
+ + + + + + + + + + + +
EventContentMore details
+
+
+
+
+
+
+ ); +} diff --git a/app/(authenticated)/layout.tsx b/app/(authenticated)/layout.tsx index 53c63f6..d972fce 100644 --- a/app/(authenticated)/layout.tsx +++ b/app/(authenticated)/layout.tsx @@ -20,8 +20,8 @@ export default function AppLayout({ colorText: "#161616", }, }} - afterSignInUrl={"/"} - afterSignUpUrl={"/"} + afterSignInUrl={"/overview"} + afterSignUpUrl={"/overview"} /> diff --git a/app/(landing)/css/additional-styles/theme.css b/app/(landing)/css/additional-styles/theme.css new file mode 100644 index 0000000..afe3f08 --- /dev/null +++ b/app/(landing)/css/additional-styles/theme.css @@ -0,0 +1,83 @@ + +/* Custom AOS distance */ +@media screen { + html:not(.no-js) [data-aos=fade-up] { + -webkit-transform: translate3d(0, 14px, 0); + transform: translate3d(0, 14px, 0); + } + + html:not(.no-js) [data-aos=fade-down] { + -webkit-transform: translate3d(0, -14px, 0); + transform: translate3d(0, -14px, 0); + } + + html:not(.no-js) [data-aos=fade-right] { + -webkit-transform: translate3d(-14px, 0, 0); + transform: translate3d(-14px, 0, 0); + } + + html:not(.no-js) [data-aos=fade-left] { + -webkit-transform: translate3d(14px, 0, 0); + transform: translate3d(14px, 0, 0); + } + + html:not(.no-js) [data-aos=fade-up-right] { + -webkit-transform: translate3d(-14px, 14px, 0); + transform: translate3d(-14px, 14px, 0); + } + + html:not(.no-js) [data-aos=fade-up-left] { + -webkit-transform: translate3d(14px, 14px, 0); + transform: translate3d(14px, 14px, 0); + } + + html:not(.no-js) [data-aos=fade-down-right] { + -webkit-transform: translate3d(-14px, -14px, 0); + transform: translate3d(-14px, -14px, 0); + } + + html:not(.no-js) [data-aos=fade-down-left] { + -webkit-transform: translate3d(14px, -14px, 0); + transform: translate3d(14px, -14px, 0); + } + + html:not(.no-js) [data-aos=zoom-in-up] { + -webkit-transform: translate3d(0, 14px, 0) scale(.6); + transform: translate3d(0, 14px, 0) scale(.6); + } + + html:not(.no-js) [data-aos=zoom-in-down] { + -webkit-transform: translate3d(0, -14px, 0) scale(.6); + transform: translate3d(0, -14px, 0) scale(.6); + } + + html:not(.no-js) [data-aos=zoom-in-right] { + -webkit-transform: translate3d(-14px, 0, 0) scale(.6); + transform: translate3d(-14px, 0, 0) scale(.6); + } + + html:not(.no-js) [data-aos=zoom-in-left] { + -webkit-transform: translate3d(14px, 0, 0) scale(.6); + transform: translate3d(14px, 0, 0) scale(.6); + } + + html:not(.no-js) [data-aos=zoom-out-up] { + -webkit-transform: translate3d(0, 14px, 0) scale(1.2); + transform: translate3d(0, 14px, 0) scale(1.2); + } + + html:not(.no-js) [data-aos=zoom-out-down] { + -webkit-transform: translate3d(0, -14px, 0) scale(1.2); + transform: translate3d(0, -14px, 0) scale(1.2); + } + + html:not(.no-js) [data-aos=zoom-out-right] { + -webkit-transform: translate3d(-14px, 0, 0) scale(1.2); + transform: translate3d(-14px, 0, 0) scale(1.2); + } + + html:not(.no-js) [data-aos=zoom-out-left] { + -webkit-transform: translate3d(14px, 0, 0) scale(1.2); + transform: translate3d(14px, 0, 0) scale(1.2); + } +} \ No newline at end of file diff --git a/app/(landing)/css/style.css b/app/(landing)/css/style.css new file mode 100644 index 0000000..5bd9cac --- /dev/null +++ b/app/(landing)/css/style.css @@ -0,0 +1,14 @@ +@import 'tailwindcss/base'; +@import 'tailwindcss/components'; + +@import 'additional-styles/theme.css'; + +@import 'tailwindcss/utilities'; + +/* Additional Tailwind directives: https://tailwindcss.com/docs/functions-and-directives/#responsive */ +@layer utilities { + .rtl { + direction: rtl; + } +} + diff --git a/app/(landing)/layout.tsx b/app/(landing)/layout.tsx index 249748e..b681a8a 100644 --- a/app/(landing)/layout.tsx +++ b/app/(landing)/layout.tsx @@ -4,6 +4,7 @@ import { useEffect } from "react"; import AOS from "aos"; import "aos/dist/aos.css"; +import "./css/style.css"; import { Header } from "@/components/landing/ui/header"; import Link from "next/link"; diff --git a/app/layout.tsx b/app/layout.tsx index c0748a9..4f018e2 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,7 +1,6 @@ import { Inter } from 'next/font/google'; import LocalFont from "next/font/local"; -import { TailwindIndicator } from "@/components/tailwind-indicator"; import { ThemeProvider } from "@/components/theme-provider"; import "tailwindcss/tailwind.css"; import { ToastProvider } from "../toast-provider"; @@ -72,7 +71,6 @@ export default async function RootLayout({ children }: RootLayoutProps) { {children} - diff --git a/components/icons.tsx b/components/icons.tsx new file mode 100644 index 0000000..b9dfbb2 --- /dev/null +++ b/components/icons.tsx @@ -0,0 +1,114 @@ +import { + AlertTriangle, + ArrowRight, + Check, + ChevronLeft, + ChevronRight, + Circle, + ClipboardCheck, + Copy, + CreditCard, + File, + FileText, + HelpCircle, + Image, + Laptop, + Loader2, + LucideProps, + Moon, + MoreVertical, + Pizza, + Plus, + Settings, + SunMedium, + Trash, + Twitter, + User, + X, + type Icon as LucideIcon, +} from "lucide-react"; + +export type Icon = LucideIcon; + +export const Icons = { + logo: (props: LucideProps) => ( + + + + ), + close: X, + spinner: Loader2, + chevronLeft: ChevronLeft, + chevronRight: ChevronRight, + trash: Trash, + post: FileText, + page: File, + media: Image, + settings: Settings, + billing: CreditCard, + ellipsis: MoreVertical, + add: Plus, + warning: AlertTriangle, + user: User, + arrowRight: ArrowRight, + help: HelpCircle, + pizza: Pizza, + twitter: Twitter, + check: Check, + copy: Copy, + copyDone: ClipboardCheck, + sun: SunMedium, + moon: Moon, + laptop: Laptop, + gitHub: (props: LucideProps) => ( + + + + ), + radix: (props: LucideProps) => ( + + + + + + ), + npm: (props: LucideProps) => ( + + + + ), + yarn: (props: LucideProps) => ( + + + + ), + pnpm: (props: LucideProps) => ( + + + + ), + react: (props: LucideProps) => ( + + + + ), + tailwind: (props: LucideProps) => ( + + + + ), +}; diff --git a/components/landing/hero.tsx b/components/landing/hero.tsx index 5241658..ac14a37 100644 --- a/components/landing/hero.tsx +++ b/components/landing/hero.tsx @@ -29,7 +29,7 @@ export const Hero: React.FC = () => {

One Dashboard, Countless Solutions diff --git a/components/landing/particles.tsx b/components/landing/particles.tsx index cb38c7c..c18c3a6 100644 --- a/components/landing/particles.tsx +++ b/components/landing/particles.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ "use client"; import React, { useRef, useEffect } from "react"; diff --git a/components/landing/ui/header.tsx b/components/landing/ui/header.tsx index c601a5f..711863a 100644 --- a/components/landing/ui/header.tsx +++ b/components/landing/ui/header.tsx @@ -17,7 +17,7 @@ export const Header: React.FC = () => {
  • Sign in diff --git a/components/loading.tsx b/components/loading.tsx new file mode 100644 index 0000000..7e592a8 --- /dev/null +++ b/components/loading.tsx @@ -0,0 +1,27 @@ +import React, { SVGProps } from "react"; + +export function Loading({ + width = 24, + height = 24, + dur = "0.75s", +}: SVGProps): JSX.Element { + return ( + + + + + + + + + + + + ); +} diff --git a/components/logo.tsx b/components/logo.tsx index 3cbdaf6..03b5d24 100644 --- a/components/logo.tsx +++ b/components/logo.tsx @@ -5,16 +5,21 @@ export const Logo: React.FC = ({ className }) => { return ( - {/* */} - - - - - + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + stroke="current" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round"> + + + + ); }; + + diff --git a/components/mobile-nav.tsx b/components/mobile-nav.tsx new file mode 100644 index 0000000..0883aaa --- /dev/null +++ b/components/mobile-nav.tsx @@ -0,0 +1,78 @@ +"use client"; + +import * as React from "react"; +import Link from "next/link"; + +// import { docsConfig } from "@/config/docs" +import { cn } from "@/lib/utils"; +import { Icons } from "@/components/icons"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { ScrollArea } from "@/components/ui/scroll-area"; + +export function MobileNav() { + return ( + + + + + + + + Omnidash + + + + + {/* {docsConfig.sidebarNav?.map( + (item, index) => + item.href && ( + + {item.title} + + ) + )} + {docsConfig.sidebarNav.map((item, index) => ( + + + {item.title} + + {item?.items?.length && + item.items.map((item) => ( + + {item.href ? ( + {item.title} + ) : ( + item.title + )} + + ))} + + ))} */} + + + + ); +} diff --git a/components/page-header.tsx b/components/page-header.tsx new file mode 100644 index 0000000..00859ac --- /dev/null +++ b/components/page-header.tsx @@ -0,0 +1,21 @@ +type Props = { + title: string; + description?: string; + actions?: React.ReactNode[]; +}; + +export const PageHeader: React.FC = ({ title, description, actions }) => { + return ( +
    +
    +

    {title}

    +

    {description}

    +
    +
      + {(actions ?? []).map((action, i) => ( +
    • {action}
    • + ))} +
    +
    + ); +}; diff --git a/components/tailwind-indicator.tsx b/components/tailwind-indicator.tsx deleted file mode 100644 index 38d20e1..0000000 --- a/components/tailwind-indicator.tsx +++ /dev/null @@ -1,16 +0,0 @@ -export function TailwindIndicator() { - if (process.env.NODE_ENV === "production") { - return null; - } - return null; - return ( -
    -
    xs
    -
    sm
    -
    md
    -
    lg
    -
    xl
    -
    2xl
    -
    - ); -} diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..1d9ba9d --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,47 @@ +"use client"; + +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "@/lib/utils"; + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 0000000..ba96182 --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,47 @@ +import * as React from "react"; +import { VariantProps, cva } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none dark:hover:bg-zinc-800 dark:hover:text-zinc-100 disabled:opacity-50 disabled:pointer-events-none data-[state=open]:bg-zinc-100 dark:data-[state=open]:bg-zinc-800", + { + variants: { + variant: { + default: "bg-zinc-900 text-white hover:bg-zinc-700 dark:bg-zinc-50 dark:text-zinc-900", + destructive: "bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600", + outline: + "bg-transparent border border-zinc-200 hover:bg-zinc-100 dark:border-zinc-700 dark:text-zinc-100", + subtle: "bg-zinc-100 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-100", + ghost: + "bg-transparent hover:bg-zinc-100 dark:hover:bg-zinc-800 dark:text-zinc-100 dark:hover:text-zinc-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent", + link: "bg-transparent dark:bg-transparent underline-offset-4 hover:underline text-zinc-900 dark:text-zinc-100 hover:bg-transparent dark:hover:bg-transparent", + }, + size: { + square: "h-10 w-10", + default: "h-10 py-2 px-4", + sm: "h-9 px-2 rounded-md", + lg: "h-11 px-8 rounded-md", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps {} + +const Button = React.forwardRef( + ({ className, variant, size, ...props }, ref) => { + return ( +