diff --git a/app/(dashboard)/u/[username]/(home)/page.tsx b/app/(dashboard)/u/[username]/(home)/page.tsx
new file mode 100644
index 0000000..ec2f080
--- /dev/null
+++ b/app/(dashboard)/u/[username]/(home)/page.tsx
@@ -0,0 +1,21 @@
+import { getUserByUsername } from "@/lib/user-service";
+import { currentUser } from "@clerk/nextjs";
+
+interface CreatorPageProps {
+ params: {
+ username: string;
+ };
+}
+
+const CreatorPage = async ({ params }: CreatorPageProps) => {
+ const externalUser = await currentUser();
+ const user = await getUserByUsername(params.username);
+
+ if (!user || user.externalUserId !== externalUser?.id) {
+ throw new Error("Unauthorized");
+ }
+
+ return
CreatorPage
;
+};
+
+export default CreatorPage;
diff --git a/app/(dashboard)/u/[username]/_components/container.tsx b/app/(dashboard)/u/[username]/_components/container.tsx
new file mode 100644
index 0000000..d109568
--- /dev/null
+++ b/app/(dashboard)/u/[username]/_components/container.tsx
@@ -0,0 +1,35 @@
+"use client";
+
+import { useEffect } from "react";
+import { useMediaQuery } from "usehooks-ts";
+
+import { cn } from "@/lib/utils";
+import { useCreatorSidebar } from "@/store/use-creator-sidebar";
+
+interface ContainerProps {
+ children: React.ReactNode;
+}
+
+export const Container = ({ children }: ContainerProps) => {
+ const { collapsed, onCollapse, onExpand } = useCreatorSidebar(
+ (state) => state
+ );
+
+ const matches = useMediaQuery(`(max-width: 1024px)`);
+
+ useEffect(() => {
+ if (matches) {
+ onCollapse();
+ } else {
+ onExpand();
+ }
+ }, [matches, onCollapse, onExpand]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/app/(dashboard)/u/[username]/_components/navbar/actions.tsx b/app/(dashboard)/u/[username]/_components/navbar/actions.tsx
new file mode 100644
index 0000000..21cf8c6
--- /dev/null
+++ b/app/(dashboard)/u/[username]/_components/navbar/actions.tsx
@@ -0,0 +1,23 @@
+import { Button } from "@/components/ui/button";
+import { UserButton } from "@clerk/nextjs";
+import { LogOut } from "lucide-react";
+import Link from "next/link";
+
+export const Actions = () => {
+ return (
+
+
+
+
+ );
+};
diff --git a/app/(dashboard)/u/[username]/_components/navbar/index.tsx b/app/(dashboard)/u/[username]/_components/navbar/index.tsx
new file mode 100644
index 0000000..308e336
--- /dev/null
+++ b/app/(dashboard)/u/[username]/_components/navbar/index.tsx
@@ -0,0 +1,12 @@
+import React from "react";
+import { Logo } from "./logo";
+import { Actions } from "./actions";
+
+export const Navbar = () => {
+ return (
+
+ );
+};
diff --git a/app/(dashboard)/u/[username]/_components/navbar/logo.tsx b/app/(dashboard)/u/[username]/_components/navbar/logo.tsx
new file mode 100644
index 0000000..3e6ec04
--- /dev/null
+++ b/app/(dashboard)/u/[username]/_components/navbar/logo.tsx
@@ -0,0 +1,25 @@
+import { cn } from "@/lib/utils";
+import { Poppins } from "next/font/google";
+import Image from "next/image";
+import Link from "next/link";
+
+const font = Poppins({
+ subsets: ["latin"],
+ weight: ["200", "300", "400", "500", "600", "700", "800"],
+});
+
+export const Logo = () => {
+ return (
+
+
+
+
+
+
+
Gamehub
+
Creator Dashboard
+
+
+
+ );
+};
diff --git a/app/(dashboard)/u/[username]/_components/sidebar/index.tsx b/app/(dashboard)/u/[username]/_components/sidebar/index.tsx
new file mode 100644
index 0000000..ad294b2
--- /dev/null
+++ b/app/(dashboard)/u/[username]/_components/sidebar/index.tsx
@@ -0,0 +1,12 @@
+import { Navigation } from "./navigation";
+import { Toggle } from "./toggle";
+import { Wrapper } from "./wrapper";
+
+export const Sidebar = async () => {
+ return (
+
+
+
+
+ );
+};
diff --git a/app/(dashboard)/u/[username]/_components/sidebar/nav-item.tsx b/app/(dashboard)/u/[username]/_components/sidebar/nav-item.tsx
new file mode 100644
index 0000000..b1a6991
--- /dev/null
+++ b/app/(dashboard)/u/[username]/_components/sidebar/nav-item.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import { Skeleton } from "@/components/ui/skeleton";
+import { cn } from "@/lib/utils";
+import { useCreatorSidebar } from "@/store/use-creator-sidebar";
+import { LucideIcon } from "lucide-react";
+import Link from "next/link";
+
+interface NavItemProps {
+ icon: LucideIcon;
+ label: string;
+ href: string;
+ isActive: boolean;
+}
+
+export const NavItem = ({
+ icon: Icon,
+ label,
+ href,
+ isActive,
+}: NavItemProps) => {
+ const { collapsed } = useCreatorSidebar((state) => state);
+
+ return (
+
+ );
+};
+
+export const NavItemSkeleton = () => {
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/app/(dashboard)/u/[username]/_components/sidebar/navigation.tsx b/app/(dashboard)/u/[username]/_components/sidebar/navigation.tsx
new file mode 100644
index 0000000..2d8539c
--- /dev/null
+++ b/app/(dashboard)/u/[username]/_components/sidebar/navigation.tsx
@@ -0,0 +1,48 @@
+"use client";
+
+import { useUser } from "@clerk/nextjs";
+import { Fullscreen, KeyRound, MessageSquare, Users } from "lucide-react";
+import { usePathname } from "next/navigation";
+import { NavItem } from "./nav-item";
+
+export const Navigation = () => {
+ const pathname = usePathname();
+ const { user } = useUser();
+
+ const routes = [
+ {
+ label: "Stream",
+ href: `/u/${user?.username}`,
+ icon: Fullscreen,
+ },
+ {
+ label: "Keys",
+ href: `/u/${user?.username}/keys`,
+ icon: KeyRound,
+ },
+ {
+ label: "Chat",
+ href: `/u/${user?.username}/chat`,
+ icon: MessageSquare,
+ },
+ {
+ label: "Community",
+ href: `/u/${user?.username}/community`,
+ icon: Users,
+ },
+ ];
+
+ return (
+
+ {routes.map((route) => (
+
+ ))}
+
+ );
+};
diff --git a/app/(dashboard)/u/[username]/_components/sidebar/toggle.tsx b/app/(dashboard)/u/[username]/_components/sidebar/toggle.tsx
new file mode 100644
index 0000000..1802937
--- /dev/null
+++ b/app/(dashboard)/u/[username]/_components/sidebar/toggle.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import { Hint } from "@/components/hint";
+import { Button } from "@/components/ui/button";
+import { useCreatorSidebar } from "@/store/use-creator-sidebar";
+import { ArrowLeftFromLine, ArrowRightFromLine } from "lucide-react";
+
+export const Toggle = () => {
+ const { collapsed, onExpand, onCollapse } = useCreatorSidebar(
+ (state) => state
+ );
+
+ const label = collapsed ? "Expand" : "Collapse";
+
+ return (
+ <>
+ {collapsed && (
+
+ )}
+ {!collapsed && (
+
+ )}
+ >
+ );
+};
diff --git a/app/(dashboard)/u/[username]/_components/sidebar/wrapper.tsx b/app/(dashboard)/u/[username]/_components/sidebar/wrapper.tsx
new file mode 100644
index 0000000..9c4ffd8
--- /dev/null
+++ b/app/(dashboard)/u/[username]/_components/sidebar/wrapper.tsx
@@ -0,0 +1,24 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import { useCreatorSidebar } from "@/store/use-creator-sidebar";
+import React from "react";
+
+interface WrapperProps {
+ children: React.ReactNode;
+}
+
+export const Wrapper = ({ children }: WrapperProps) => {
+ const { collapsed } = useCreatorSidebar((state) => state);
+
+ return (
+
+ );
+};
diff --git a/app/(dashboard)/u/[username]/layout.tsx b/app/(dashboard)/u/[username]/layout.tsx
new file mode 100644
index 0000000..cbb24b7
--- /dev/null
+++ b/app/(dashboard)/u/[username]/layout.tsx
@@ -0,0 +1,33 @@
+import { getSelfByUsername } from "@/lib/auth-service";
+import { redirect } from "next/navigation";
+import React from "react";
+import { Container } from "./_components/container";
+import { Navbar } from "./_components/navbar";
+import { Sidebar } from "./_components/sidebar";
+
+interface CreatorLayoutProps {
+ params: {
+ username: string;
+ };
+ children: React.ReactNode;
+}
+
+const CreatorLayout = async ({ params, children }: CreatorLayoutProps) => {
+ const self = await getSelfByUsername(params.username);
+
+ if (!self) {
+ redirect("/");
+ }
+
+ return (
+ <>
+
+
+
+ {children}
+
+ >
+ );
+};
+
+export default CreatorLayout;
diff --git a/lib/auth-service.ts b/lib/auth-service.ts
index 9682659..6dcfca1 100644
--- a/lib/auth-service.ts
+++ b/lib/auth-service.ts
@@ -2,21 +2,45 @@ import { currentUser } from "@clerk/nextjs";
import { db } from "./db";
export const getSelf = async () => {
- const self = await currentUser();
+ const self = await currentUser();
- if (!self || !self.username) {
- throw new Error("Unauthorized");
- }
+ if (!self || !self.username) {
+ throw new Error("Unauthorized");
+ }
- const user = await db.user.findUnique({
- where: {
- externalUserId: self.id,
- },
- });
+ const user = await db.user.findUnique({
+ where: {
+ externalUserId: self.id,
+ },
+ });
- if (!user) {
- throw new Error("Not Found");
- }
+ if (!user) {
+ throw new Error("Not Found");
+ }
- return user;
+ return user;
+};
+
+export const getSelfByUsername = async (username: string) => {
+ const self = await currentUser();
+
+ if (!self || !self.username) {
+ throw new Error("Unauthorized");
+ }
+
+ const user = await db.user.findUnique({
+ where: {
+ username,
+ },
+ });
+
+ if (!user) {
+ throw new Error("User not found");
+ }
+
+ if (self.username !== user.username) {
+ throw new Error("Unauthorized");
+ }
+
+ return self;
};
diff --git a/renovate.json b/renovate.json
deleted file mode 100644
index 5db72dd..0000000
--- a/renovate.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "extends": [
- "config:recommended"
- ]
-}
diff --git a/store/use-creator-sidebar.ts b/store/use-creator-sidebar.ts
new file mode 100644
index 0000000..6b13b39
--- /dev/null
+++ b/store/use-creator-sidebar.ts
@@ -0,0 +1,13 @@
+import { create } from "zustand";
+
+interface CreatorSidebarStore {
+ collapsed: boolean;
+ onExpand: () => void;
+ onCollapse: () => void;
+}
+
+export const useCreatorSidebar = create((set) => ({
+ collapsed: false,
+ onExpand: () => set(() => ({ collapsed: false })),
+ onCollapse: () => set(() => ({ collapsed: true })),
+}));