diff --git a/packages/www/components/Header/index.tsx b/packages/www/components/Header/index.tsx index a0789910fb..e48734a6f2 100644 --- a/packages/www/components/Header/index.tsx +++ b/packages/www/components/Header/index.tsx @@ -20,6 +20,8 @@ import PolygonIcon from "../../public/img/icons/polygonWithoutBorderBottom.svg"; import CheckedIcon from "../../public/img/icons/checked.svg"; import { useEffect, useState, useRef } from "react"; import { useApi, useHubspotForm } from "hooks"; +import { Breadcrumb } from "layouts/dashboard"; +import { workspaces } from "pages/dashboard/settings"; const StyledHornIcon = styled(HornIcon, { color: "$hiContrast", @@ -47,6 +49,8 @@ const Header = ({ breadcrumbs = [] }) => { reaction: "", feedback: "", }); + const [navBreadcrumbs, setNavBreadcrumbs] = + useState(breadcrumbs); const [formSent, setFormSent] = useState(false); const [errorMessage, setErrorMessage] = useState(""); const { data, handleSubmit } = useHubspotForm({ @@ -67,6 +71,13 @@ const Header = ({ breadcrumbs = [] }) => { } }, [user]); + useEffect(() => { + setNavBreadcrumbs([ + { title: workspaces[0].projects[0].name, href: "/dashboard/settings" }, + ...breadcrumbs, + ]); + }, []); + return ( { align="center" justify="between" css={{ - px: "$6", height: 60, width: "100%", margin: "0 auto", maxWidth: "1520px", }}> - {breadcrumbs.map((breadcrumb, i) => { + {navBreadcrumbs.map((breadcrumb, i) => { if (breadcrumb?.href) { return ( diff --git a/packages/www/components/Project/CreateProjectDialog.tsx b/packages/www/components/Project/CreateProjectDialog.tsx new file mode 100644 index 0000000000..e74469ffb6 --- /dev/null +++ b/packages/www/components/Project/CreateProjectDialog.tsx @@ -0,0 +1,94 @@ +import { + Box, + Button, + Flex, + AlertDialog, + AlertDialogTitle, + AlertDialogContent, + AlertDialogCancel, + AlertDialogDescription, + TextField, + Heading, + Text, + Label, +} from "@livepeer/design-system"; + +const CreateProjectDialog = ({ + isOpen, + onOpenChange, + onCreate, +}: { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; + onCreate: (projectName: string) => Promise; +}) => { + return ( + + + + Create project + + { + // Handle create project + }}> + + + Working with different projects helps create a clear separation + between different customers or working environments. + + + + + + + + + + + + + + + ); +}; + +export default CreateProjectDialog; diff --git a/packages/www/components/Project/ProjectTile.tsx b/packages/www/components/Project/ProjectTile.tsx new file mode 100644 index 0000000000..205c953fe3 --- /dev/null +++ b/packages/www/components/Project/ProjectTile.tsx @@ -0,0 +1,141 @@ +import { + Box, + Flex, + Text, + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@livepeer/design-system"; +import { GoDotFill } from "react-icons/go"; +import Image from "next/image"; +import React from "react"; +import { sanitizeUrl } from "lib/url-sanitizer"; +import { DotsHorizontalIcon } from "@radix-ui/react-icons"; +import { HiDotsHorizontal } from "react-icons/hi"; +import Link from "next/link"; +import { generalSidebarItems } from "components/Sidebar"; + +export default function ProjectTile({ + name, + logo, + url, + activeStreams, + inProgressUploads, + ...props +}) { + return ( + + + Project logo + + + + {name} + + + + + + + + + {generalSidebarItems.map((item) => ( + + {item.title} + + ))} + + + + + + + {sanitizeUrl(url)} + + + + + + {activeStreams} active streams + + + {inProgressUploads} in-progress uploads + + + + ); +} diff --git a/packages/www/components/Sidebar/NavIcons.tsx b/packages/www/components/Sidebar/NavIcons.tsx index 280a855a30..2ba9b0db16 100644 --- a/packages/www/components/Sidebar/NavIcons.tsx +++ b/packages/www/components/Sidebar/NavIcons.tsx @@ -97,6 +97,20 @@ export const UsageIcon = ({ active = false }) => ( ); +export const SettingsIcon = ({ active = false }) => ( + + + +); + export const AssetsIcon = ({ active = false }) => ( ( ); + +export const WorkspaceIcon = ({ active = false }) => ( + + + + +); + +export const AccountIcon = ({ active = false }) => ( + + + + +); + +export const TopBottomChevron = ({ active = false }) => ( + + + + + + +); diff --git a/packages/www/components/Sidebar/index.tsx b/packages/www/components/Sidebar/index.tsx index fd7fff1ae6..fdd83fce3f 100644 --- a/packages/www/components/Sidebar/index.tsx +++ b/packages/www/components/Sidebar/index.tsx @@ -8,14 +8,10 @@ import { Grid, DropdownMenu, DropdownMenuContent, - DropdownMenuGroup, DropdownMenuTrigger, - DropdownMenuItem, - AlertDialog, - AlertDialogTrigger, - AlertDialogContent, + Button, } from "@livepeer/design-system"; -import { ChevronDownIcon } from "@radix-ui/react-icons"; + import ThemeSwitch from "../ThemeSwitch"; import Link from "next/link"; import { @@ -25,11 +21,28 @@ import { BillingIcon, UsageIcon, AssetsIcon, + SettingsIcon, + WorkspaceIcon, + AccountIcon, + TopBottomChevron, } from "./NavIcons"; import { useApi } from "../../hooks"; -import Router from "next/router"; -import { RocketIcon, ChatBubbleIcon, LoopIcon } from "@radix-ui/react-icons"; +import Router, { useRouter } from "next/router"; +import { + RocketIcon, + ChatBubbleIcon, + LoopIcon, + PlusIcon, + CheckIcon, +} from "@radix-ui/react-icons"; import Contact from "../Contact"; +import Image from "next/image"; +import { workspaces } from "pages/dashboard/settings"; +import { User } from "@livepeer.studio/api"; +import { FiCheck, FiChevronLeft } from "react-icons/fi"; +import CreateProjectDialog from "components/Project/CreateProjectDialog"; +import { useState } from "react"; +import { FaCheck } from "react-icons/fa"; export const NavLink = styled(A, { fontSize: 14, @@ -37,8 +50,8 @@ export const NavLink = styled(A, { alignItems: "center", color: "$primary12", px: "$2", - py: 6, - borderRadius: "$2", + py: 8.5, + borderRadius: "$1", cursor: "default", lineHeight: 1.2, fontWeight: 500, @@ -59,20 +72,149 @@ export const NavLink = styled(A, { export type SidebarId = | "home" | "streams" - | "streams/sessions" - // /dashboard/stream-health - unhandled in the sidebar | "streams/health" | "assets" | "developers" - | "developers/signing-keys" - | "developers/webhooks" | "usage" | "billing" - | "billing/plans"; + | "billing/plans" + | "settings" + | "workspace/general" + | "workspace/projects" + | "workspace/members" + | "workspace/plans" + | "workspace/billing" + | "workspace/audit-log" + | "account/profile" + | "account/preferences" + | "account/notifications"; + +export const generalSidebarItems = [ + { + title: "Home", + path: "/dashboard", + icon: , + id: "home", + }, + { + title: "Streams", + path: "/dashboard/streams", + icon: , + id: "streams", + }, + { + title: "Assets", + path: "/dashboard/assets", + icon: , + id: "assets", + }, + { + title: "Developers", + path: "/dashboard/developers", + icon: , + id: "developers", + }, + // { + // title: "Usage", + // path: "/dashboard/usage", + // icon: , + // id: "usage", + // }, + // { + // title: "Billing", + // path: "/dashboard/billing", + // icon: , + // id: "billing", + // children: [ + // { + // title: "Plans", + // path: "/dashboard/billing/plans", + // id: "billing/plans", + // }, + // ], + // }, + { + title: "Settings", + path: "/dashboard/settings", + icon: , + id: "settings", + }, +]; + +const workspaceSidebarItems = [ + { + title: "Workspace", + path: "/dashboard/workspace/general", + icon: , + id: "workspace/general", + children: [ + { + title: "General", + path: "/dashboard/workspace/general", + id: "workspace/general", + }, + { + title: "Projects", + path: "/dashboard/workspace/projects", + id: "workspace/projects", + }, + { + title: "Members", + path: "/dashboard/workspace/members", + id: "workspace/members", + }, + { + title: "Plans", + path: "/dashboard/workspace/plans", + id: "workspace/plans", + }, + { + title: "Usage", + path: "/dashboard/workspace/usage", + id: "workspace/usage", + }, + { + title: "Billing", + path: "/dashboard/workspace/billing", + id: "workspace/billing", + }, + { + title: "Audit Log", + path: "/dashboard/workspace/audit-log", + id: "workspace/audit-log", + }, + ], + }, + { + title: "Account", + path: "/dashboard/account/profile", + icon: , + id: "account/profile", + children: [ + { + title: "Profile", + path: "/dashboard/account/profile", + id: "account/profile", + }, + { + title: "Preferences", + path: "/dashboard/account/preferences", + id: "account/preferences", + }, + { + title: "Notifications", + path: "/dashboard/account/notifications", + id: "account/notifications", + }, + ], + }, +]; const Sidebar = ({ id }: { id: SidebarId }) => { const { user, logout } = useApi(); + const { pathname } = useRouter(); + return ( { justifyContent: "flex-end", bottom: 0, }}> - + {pathname.includes("workspace") ? ( + + ) : ( + + )} + + ); +}; + +const GeneralSidebar = ({ id, user }: { id: SidebarId; user: User }) => { + const [showCreateProjectDialog, setShowCreateProjectDialog] = useState(false); + + return ( + <> + - - - {user?.firstName} + {workspaces[0].name} + + + + {user?.email} - - - - { - e.preventDefault(); - Router.push("/dashboard/billing"); + css={{ + pb: "$3", + pl: "$3", + borderBottom: "1px solid", + borderColor: "$neutral6", + }}> + {workspaces.map((workspace) => ( + + Project logo + {workspace.name} + + ))} + + + - Billing - - { - e.preventDefault(); - logout(); + + Workspace settings + + + Invite & manage members + + + + + - Logout - - + + Create or join a workspace + + + Add an account + + + Log out + + + - + + + + + {workspaces[0].projects[0].name} + + + + + + Projects + + + {workspaces[0].projects.map((project) => ( + + + Project logo + {project.name} + + {project.isDefault && } + + ))} + + + + setShowCreateProjectDialog(true)} + align={"center"} + css={{ + color: "$neutral12", + gap: "$2", + cursor: "pointer", + }}> + + New project + + + + + { textDecoration: "none", }, }}> - - - - Home - - - - - - - Streams - - - - {id?.split("/")[0] === "streams" && ( - :first-child": { - mt: "$1", - }, - }}> - - Sessions - - - )} - - - - - Assets - - - - - - - Developers - - - - {id?.split("/")[0] === "developers" && ( - - - API Keys - - - - Signing Keys - - - - - Webhooks - - - - )} - - - - - - - Usage - - - - - - - - - Billing - - - - {id?.split("/")[0] === "billing" && ( - :first-child": { - mt: "$1", - }, - }}> - - Plans - - - )} - + {generalSidebarItems.map((item) => ( + + + + {item.icon} + {item.title} + + + {item.children && id === item.id && ( + + {item.children.map((child) => ( + + {child.title} + + ))} + + )} + + ))} - + { - + { + console.log(projectName); + }} + /> + + ); +}; + +const WorkspaceSidebar = ({ id, user }: { id: SidebarId; user: User }) => { + const goBack = () => { + Router.push("/dashboard"); + }; + + return ( + <> + + + + + + {workspaceSidebarItems.map((item) => ( + + + {item.icon} + {item.title} + + {item.children && ( + + {item.children.map((child) => ( + + {child.title} + + ))} + + )} + + ))} + + + ); }; diff --git a/packages/www/components/StreamSessionsTable/index.tsx b/packages/www/components/StreamSessionsTable/index.tsx index 47ba860f8f..63cce01341 100644 --- a/packages/www/components/StreamSessionsTable/index.tsx +++ b/packages/www/components/StreamSessionsTable/index.tsx @@ -44,7 +44,6 @@ const StreamSessionsTable = ({ title = "Sessions" }: { title?: string }) => { fetcher={fetcher} initialSortBy={[DefaultSortBy]} showOverflow={true} - filterItems={filterItems} emptyState={makeEmptyState()} /> ); diff --git a/packages/www/components/StreamsTable/helpers.tsx b/packages/www/components/StreamsTable/helpers.tsx index 8ecab2799f..ad53a8e56f 100644 --- a/packages/www/components/StreamsTable/helpers.tsx +++ b/packages/www/components/StreamsTable/helpers.tsx @@ -1,4 +1,4 @@ -import { Box, Text } from "@livepeer/design-system"; +import { Box, Flex, Text } from "@livepeer/design-system"; import { State } from "../Table"; import DateCell, { DateCellProps } from "../Table/cells/date"; import { RenditionDetailsCellProps } from "../Table/cells/streams-table"; @@ -7,6 +7,7 @@ import TableEmptyState from "../Table/components/TableEmptyState"; import { FilterItem, formatFiltersForApiRequest } from "../Table/filters"; import { stringSort, dateSort } from "../Table/sorts"; import { RowsPageFromStateResult, SortTypeArgs } from "../Table/types"; +import Image from "next/image"; export type StreamsTableData = { id: string; @@ -45,19 +46,21 @@ export const makeColumns = () => [ sortType: (...params: SortTypeArgs) => dateSort("original.createdAt.date", ...params), }, - { - Header: "Last seen", - accessor: "lastSeen", - Cell: DateCell, - sortType: (...params: SortTypeArgs) => - dateSort("original.lastSeen.date", ...params), - }, + { Header: "Status", accessor: "status", Cell: TextCell, disableSortBy: true, }, + // TODO: Update this to param from the API + { + Header: "Views", + accessor: "lastSeen", + Cell: DateCell, + sortType: (...params: SortTypeArgs) => + dateSort("original.lastSeen.date", ...params), + }, ]; export const rowsPageFromState = async ( @@ -87,7 +90,30 @@ export const rowsPageFromState = async ( name: { id: stream.id, value: stream.name, - children: {stream.name}, + children: ( + + Placeholder image + + {stream.name} 1 + + + ), tooltipChildren: stream.createdByTokenName ? ( <> Created by token {stream.createdByTokenName} diff --git a/packages/www/components/StreamsTable/index.tsx b/packages/www/components/StreamsTable/index.tsx index 0052732a46..d3c06e7000 100644 --- a/packages/www/components/StreamsTable/index.tsx +++ b/packages/www/components/StreamsTable/index.tsx @@ -79,10 +79,6 @@ const StreamsTable = ({ initialSortBy={[DefaultSortBy]} emptyState={makeEmptyState(createDialogState)} selectAction={makeSelectAction("Delete", deleteDialogState.onOn)} - createAction={makeCreateAction( - "Create livestream", - createDialogState.onOn - )} header={ diff --git a/packages/www/components/Table/cells/date.tsx b/packages/www/components/Table/cells/date.tsx index 2285ed50a6..33963e9a30 100644 --- a/packages/www/components/Table/cells/date.tsx +++ b/packages/www/components/Table/cells/date.tsx @@ -12,7 +12,7 @@ const DateCell = ({ }: CellComponentProps) => { const { date, fallback } = cell.value; try { - return format(date, "MMMM dd, yyyy h:mm a"); + return format(date, "MMM do, h:mm a"); } catch (error) { return fallback; } diff --git a/packages/www/content/index.tsx b/packages/www/content/index.tsx index eb8e380d2e..1d6d6af8cc 100644 --- a/packages/www/content/index.tsx +++ b/packages/www/content/index.tsx @@ -198,6 +198,14 @@ export const DashboardUsage = { }, }; +export const DashboardSettings = { + metaData: { + title: makeCompleteTitle("Settings"), + description: defaultDescription, + url: makeCompleteUrl(), + }, +}; + export const DashboardPlans = { metaData: { title: makeCompleteTitle("Plans"), diff --git a/packages/www/lib/url-sanitizer.ts b/packages/www/lib/url-sanitizer.ts new file mode 100644 index 0000000000..9d68676049 --- /dev/null +++ b/packages/www/lib/url-sanitizer.ts @@ -0,0 +1,4 @@ +export function sanitizeUrl(url: string) { + const urlObject = new URL(url); + return urlObject.hostname + urlObject.pathname; +} diff --git a/packages/www/pages/dashboard/developers/api-keys.tsx b/packages/www/pages/dashboard/developers/api-keys.tsx index 51783deb81..04d46073a3 100644 --- a/packages/www/pages/dashboard/developers/api-keys.tsx +++ b/packages/www/pages/dashboard/developers/api-keys.tsx @@ -5,12 +5,7 @@ import ApiKeysTable from "components/ApiKeys"; import { DashboardAPIKeys as Content } from "content"; import Ripe, { categories, pages } from "lib/ripe"; -Ripe.trackPage({ - category: categories.DASHBOARD, - name: pages.API_KEY, -}); - -const ApiKeys = () => { +const APIKeys = () => { useLoggedIn(); const { user } = useApi(); @@ -20,10 +15,7 @@ const ApiKeys = () => { return ( @@ -34,4 +26,4 @@ const ApiKeys = () => { ); }; -export default ApiKeys; +export default APIKeys; diff --git a/packages/www/pages/dashboard/developers/index.tsx b/packages/www/pages/dashboard/developers/index.tsx new file mode 100644 index 0000000000..eb4dc6193a --- /dev/null +++ b/packages/www/pages/dashboard/developers/index.tsx @@ -0,0 +1,95 @@ +import Layout from "../../../layouts/dashboard"; +import { + Box, + Heading, + Tabs, + TabsTrigger, + TabsList, + TabLink, + TabsContent, +} from "@livepeer/design-system"; +import { useApi, useLoggedIn } from "hooks"; +import ApiKeysTable from "components/ApiKeys"; +import { DashboardAPIKeys as Content } from "content"; +import WebhooksTable from "components/WebhooksTable"; + +const Developers = () => { + useLoggedIn(); + const { user } = useApi(); + + if (!user) { + return ; + } + + const TT = ({ title, value }) => ( + + {title} + + ); + + return ( + + + + + + Developers + + + + + + + + + + + + + + + + + + + ); +}; + +export default Developers; diff --git a/packages/www/pages/dashboard/settings/index.tsx b/packages/www/pages/dashboard/settings/index.tsx new file mode 100644 index 0000000000..b4d1bc435d --- /dev/null +++ b/packages/www/pages/dashboard/settings/index.tsx @@ -0,0 +1,228 @@ +import Layout from "layouts/dashboard"; +import { useApi, useLoggedIn } from "hooks"; +import { + Box, + Heading, + Flex, + Text, + TextField, + Button, +} from "@livepeer/design-system"; +import { DashboardSettings as Content } from "content"; +import React, { useRef, useState } from "react"; +import Image from "next/image"; + +const Settings = () => { + useLoggedIn(); + const { user } = useApi(); + const [projectName, setProjectName] = useState(null); + const [projectLogo, setProjectLogo] = useState(null); + + // TODO: Replace with proper media upload once the API is ready + const logoRef = useRef(null); + + const handleSubmit = () => { + console.log("Project Name: ", projectName); + console.log("Project Logo: ", projectLogo); + }; + + if (!user) { + return ; + } + return ( + + + + + + Settings + + + Manage your project settings + + + + + + + Logo + + logoRef.current?.click()} + src={ + projectLogo + ? URL.createObjectURL(projectLogo) + : workspaces[0].projects[0].logo + } + alt="Project logo" + style={{ + borderRadius: "12px", + cursor: "pointer", + }} + width={90} + height={90} + /> + { + const file = e.target.files?.[0]; + if (file) { + setProjectLogo(file); + } + }} + /> + + Pick a logo for your project. Recommended size is 256x256px. + + + + + Project Name + + setProjectName(e.target.value)} + value={projectName} + id="projectName" + css={{ + width: "20%", + }} + placeholder="Project Name" + /> + + + + + + + Delete Project + + + If you want to permanently delete this project and all of its + data, including but not limited to streams, sessions, and assets, + you can do so below. + + + + + + + ); +}; + +// Placeholder constants, it will be removed and replaced with real data from the API +export const workspaces = [ + { + name: "Paramount", + logo: "https://pbs.twimg.com/profile_images/1712502841494138880/GofqA30R_400x400.jpg", + url: "https://livepeer.studio/paramount", + projects: [ + { + name: "Paramount Plus", + logo: "https://pbs.twimg.com/profile_images/1712502841494138880/GofqA30R_400x400.jpg", + url: "https://livepeer.studio/paramount/paramount-plus", + activeStreams: 10, + isDefault: true, + inProgressUploads: 5, + }, + { + name: "Paramount Dev", + logo: "https://i.pinimg.com/564x/bf/9a/7a/bf9a7a4ead767c8d66c613b76e2d3596.jpg", + url: "https://livepeer.studio/paramount/paramount-dev", + activeStreams: 21, + inProgressUploads: 48, + }, + ], + members: [ + { + name: "John Doe", + email: "john@livepeer.org", + role: "Admin", + }, + ], + }, +]; + +export default Settings; diff --git a/packages/www/pages/dashboard/streams/index.tsx b/packages/www/pages/dashboard/streams/index.tsx index c37bc6f264..83365f654a 100644 --- a/packages/www/pages/dashboard/streams/index.tsx +++ b/packages/www/pages/dashboard/streams/index.tsx @@ -1,9 +1,24 @@ import Layout from "layouts/dashboard"; -import { Box } from "@livepeer/design-system"; import { useApi, useLoggedIn } from "hooks"; import StreamsTable from "components/StreamsTable"; import { DashboardStreams as Content } from "content"; import Ripe, { categories, pages } from "lib/ripe"; +import { + Box, + Heading, + Tabs, + Text, + TabsTrigger, + TabsList, + TabLink, + TabsContent, + Flex, + Button, +} from "@livepeer/design-system"; +import SessionsTable from "components/StreamDetails/SessionsTable"; +import StreamSessionsTable from "components/StreamSessionsTable"; +import { useState } from "react"; +import { PlusCircledIcon, PlusIcon } from "@radix-ui/react-icons"; Ripe.trackPage({ category: categories.DASHBOARD, @@ -13,31 +28,277 @@ Ripe.trackPage({ const Streams = () => { useLoggedIn(); const { user } = useApi(); + const [activeFilter, setActiveFilter] = useState< + "all" | "active" | "unhealthy" + >("all"); if (!user) { return ; } + const TT = ({ title, value }) => ( + + {title} + + ); + return ( - - + + + + + Streams + + + + + + + + + + + + setActiveFilter("all")} + css={{ + px: "$3", + py: "$2", + height: "100%", + border: activeFilter === "all" ? "2px solid" : "1px solid", + borderColor: + activeFilter === "all" ? "$blue11" : "$neutral8", + width: "20%", + borderRadius: "$3", + }}> + + All + + + 255 + + + setActiveFilter("active")} + css={{ + px: "$3", + py: "$2", + height: "100%", + border: + activeFilter === "active" ? "2px solid" : "1px solid", + borderColor: + activeFilter === "active" ? "$blue11" : "$neutral8", + width: "20%", + borderRadius: "$3", + }}> + + Active + + + 5 + + + setActiveFilter("unhealthy")} + css={{ + px: "$3", + py: "$2", + height: "100%", + border: + activeFilter === "unhealthy" ? "2px solid" : "1px solid", + borderColor: + activeFilter === "unhealthy" ? "$blue11" : "$neutral8", + width: "20%", + borderRadius: "$3", + }}> + + Unhealthy + + + 1 + + + + + + + Date Created + + + + Name + + + + Views + + + + Minutes delivered + + + + Minutes transcoded + + + + + + + + + ); diff --git a/packages/www/pages/dashboard/workspace/general.tsx b/packages/www/pages/dashboard/workspace/general.tsx new file mode 100644 index 0000000000..047842c206 --- /dev/null +++ b/packages/www/pages/dashboard/workspace/general.tsx @@ -0,0 +1,234 @@ +import Layout from "layouts/dashboard"; +import { useApi, useLoggedIn } from "hooks"; +import { DashboardStreams as Content } from "content"; +import { + Box, + Heading, + Flex, + Text, + TextField, + Button, +} from "@livepeer/design-system"; +import { workspaces } from "../settings"; +import Image from "next/image"; +import { sanitizeUrl } from "lib/url-sanitizer"; +import { useRef, useState } from "react"; + +const WorkspaceGeneral = () => { + useLoggedIn(); + const { user } = useApi(); + + const [workspaceName, setWorkspaceName] = useState(null); + const [workspaceURL, setWorkspaceURL] = useState(null); + const [workspaceLogo, setWorkspaceLogo] = useState(null); + + const logoRef = useRef(null); + + const handleSubmit = () => { + console.log("Workspace Name: ", workspaceName); + console.log("Workspace URL: ", workspaceURL); + console.log("Workspace Logo: ", workspaceLogo); + }; + + if (!user) { + return ; + } + + return ( + + + + + + Workspace + + + Manage your workspace settings + + + + + + + Logo + + logoRef.current?.click()} + src={ + workspaceLogo + ? URL.createObjectURL(workspaceLogo) + : workspaces[0].logo + } + alt="Project logo" + style={{ + borderRadius: "12px", + }} + width={90} + height={90} + /> + { + const file = e.target.files?.[0]; + if (file) { + setWorkspaceLogo(file); + } + }} + /> + + Pick a logo for your workspace. Recommended size is 256x256px. + + + + + General + + + + Workspace name + + setWorkspaceName(e.target.value)} + value={workspaceName} + id="workspaceName" + css={{ + width: "20%", + }} + placeholder="Workspace Name" + /> + + + + Workspace URL + + setWorkspaceURL(e.target.value)} + value={workspaceURL} + id="workspaceURL" + css={{ + width: "20%", + }} + placeholder="Workspace URL" + /> + + + + + + + + Delete Workspace + + + If you want to permanently delete this workspace and all of its + data, including but not limited to streams, sessions, and assets, + you can do so below. + + + + + + + ); +}; + +export default WorkspaceGeneral; diff --git a/packages/www/pages/dashboard/workspace/members.tsx b/packages/www/pages/dashboard/workspace/members.tsx new file mode 100644 index 0000000000..6258440834 --- /dev/null +++ b/packages/www/pages/dashboard/workspace/members.tsx @@ -0,0 +1,151 @@ +import Layout from "layouts/dashboard"; +import { useApi, useLoggedIn } from "hooks"; +import { DashboardStreams as Content } from "content"; +import { + Box, + Heading, + Flex, + Text, + TextField, + Button, + Table, + Thead, + Tbody, + Tr, + Th, + Td, +} from "@livepeer/design-system"; +import { workspaces } from "../settings"; +import { ChevronDownIcon } from "@radix-ui/react-icons"; + +const WorkspaceMembers = () => { + useLoggedIn(); + const { user } = useApi(); + + if (!user) { + return ; + } + + return ( + + + + + + Members + + + Manage who has access to this workspace + + + + + + + Manage members + + Mange workspace members and their permissions + + + + + + + + + + {workspaces[0].members.length} members + + + + + + + ); +}; + +export default WorkspaceMembers; diff --git a/packages/www/pages/dashboard/workspace/projects.tsx b/packages/www/pages/dashboard/workspace/projects.tsx new file mode 100644 index 0000000000..38c858cbf1 --- /dev/null +++ b/packages/www/pages/dashboard/workspace/projects.tsx @@ -0,0 +1,72 @@ +import Layout from "layouts/dashboard"; +import { useApi, useLoggedIn } from "hooks"; +import { DashboardStreams as Content } from "content"; +import { + Box, + Heading, + Flex, + Text, + TextField, + Button, +} from "@livepeer/design-system"; +import { workspaces } from "../settings"; +import Image from "next/image"; +import ProjectTile from "components/Project/ProjectTile"; + +const WorkspaceProjects = () => { + useLoggedIn(); + const { user } = useApi(); + + if (!user) { + return ; + } + + return ( + + + + + + Projects + + + Manage your workspace projects + + + + + {workspaces[0].projects.map((project) => ( + + ))} + + + + ); +}; + +export default WorkspaceProjects;