From 87103dde52a69c61851df04cfe2ac4eb489845f6 Mon Sep 17 00:00:00 2001
From: Sixian Li <43892874+Deerhound579@users.noreply.github.com>
Date: Sat, 25 Feb 2023 13:09:30 +0800
Subject: [PATCH] feat(dashboard): all sites area (#566)
* feat: site card
* fix: remove create project button
* feat: add menu story
* fix: menu item and link
* fix: add focus border to card
* fix: add focus visible to sidebar nav item
* fix: user menu
* fix: add usage threshold
* feat: site card menu
* fix(dashboard): mobile style
* feat: empty site card
---
packages/ui/public/tailwind.css | 50 ++++++--
.../ui/src/blocks/page-title/page-title.tsx | 4 +-
.../src/blocks/site-card/empty-site-card.tsx | 27 +++++
.../src/blocks/site-card/site-card-menu.tsx | 68 +++++++++++
.../ui/src/blocks/site-card/site-card.tsx | 56 ++++-----
.../ui/src/blocks/user-menu/menu-link.tsx | 2 +-
.../ui/src/blocks/user-menu/user-menu.tsx | 24 ++--
.../ui/src/components/heading/heading.tsx | 2 +-
packages/ui/src/components/menu/menu.tsx | 31 ++---
.../components/menu/stories/menu.stories.tsx | 28 +++++
.../app/components/app-layout/app-layout.tsx | 2 +-
.../components/app-layout/mobile-header.tsx | 2 +-
.../app/components/app-layout/sidebar.tsx | 4 +-
.../app/components/app-layout/usage-card.tsx | 7 ++
.../ui/src/pages/app/home/create-button.tsx | 114 ------------------
packages/ui/src/pages/app/home/index.tsx | 79 +++---------
.../pages/app/site/settings/site-settings.tsx | 4 +-
packages/ui/src/styles/common.ts | 2 +-
18 files changed, 239 insertions(+), 267 deletions(-)
create mode 100644 packages/ui/src/blocks/site-card/empty-site-card.tsx
create mode 100644 packages/ui/src/blocks/site-card/site-card-menu.tsx
create mode 100644 packages/ui/src/components/menu/stories/menu.stories.tsx
delete mode 100644 packages/ui/src/pages/app/home/create-button.tsx
diff --git a/packages/ui/public/tailwind.css b/packages/ui/public/tailwind.css
index c93b6ab50..9b8f1dbc6 100644
--- a/packages/ui/public/tailwind.css
+++ b/packages/ui/public/tailwind.css
@@ -16,7 +16,7 @@
/* 2 */
border-style: solid;
/* 2 */
- border-color: hsl(var(--tw-colors-gray-700) / 1);
+ border-color: hsl(var(--tw-colors-gray-500) / 1);
/* 2 */
}
@@ -558,6 +558,11 @@ video {
max-width: 65ch;
}
+.prose :where(p):not(:where([class~="not-prose"] *)) {
+ margin-top: 1.25em;
+ margin-bottom: 1.25em;
+}
+
.prose :where([class~="lead"]):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-lead);
font-size: 1.25em;
@@ -911,11 +916,6 @@ video {
line-height: 1.75;
}
-.prose :where(p):not(:where([class~="not-prose"] *)) {
- margin-top: 1.25em;
- margin-bottom: 1.25em;
-}
-
.prose :where(video):not(:where([class~="not-prose"] *)) {
margin-top: 2em;
margin-bottom: 2em;
@@ -1212,6 +1212,10 @@ video {
margin: 0.25rem;
}
+.m-32 {
+ margin: 8rem;
+}
+
.mx-auto {
margin-left: auto;
margin-right: auto;
@@ -1787,6 +1791,10 @@ video {
width: 12rem;
}
+.w-\[18px\] {
+ width: 18px;
+}
+
.w-96 {
width: 24rem;
}
@@ -3008,6 +3016,18 @@ video {
padding-left: 0.25rem;
}
+.\!pl-3 {
+ padding-left: 0.75rem !important;
+}
+
+.\!pl-2\.5 {
+ padding-left: 0.625rem !important;
+}
+
+.\!pl-2 {
+ padding-left: 0.5rem !important;
+}
+
.pt-8 {
padding-top: 2rem;
}
@@ -3111,6 +3131,11 @@ video {
letter-spacing: -0.02em;
}
+.\!text-display-sm {
+ font-size: 1.875rem !important;
+ line-height: 2.375rem !important;
+}
+
.font-bold {
font-weight: 700;
}
@@ -3175,6 +3200,10 @@ video {
line-height: 1rem;
}
+.leading-9 {
+ line-height: 2.25rem;
+}
+
.tracking-wide {
letter-spacing: 0.025em;
}
@@ -3553,6 +3582,10 @@ video {
-webkit-line-clamp: 2;
}
+.first-letter\:uppercase::first-letter {
+ text-transform: uppercase;
+}
+
.before\:absolute::before {
content: var(--tw-content);
position: absolute;
@@ -3708,11 +3741,6 @@ video {
background-color: hsl(var(--tw-colors-indigo-500) / var(--tw-bg-opacity));
}
-.hover\:bg-red-300:hover {
- --tw-bg-opacity: 1;
- background-color: hsl(var(--tw-colors-red-300) / var(--tw-bg-opacity));
-}
-
.hover\:text-primary-1100:hover {
--tw-text-opacity: 1;
color: hsl(var(--tw-colors-primary-1100) / var(--tw-text-opacity));
diff --git a/packages/ui/src/blocks/page-title/page-title.tsx b/packages/ui/src/blocks/page-title/page-title.tsx
index 302a106f2..18589fe32 100644
--- a/packages/ui/src/blocks/page-title/page-title.tsx
+++ b/packages/ui/src/blocks/page-title/page-title.tsx
@@ -33,9 +33,7 @@ export function PageTitle({
}: PageTitleProps): JSX.Element {
return (
-
- {children}
-
+ {children}
);
}
diff --git a/packages/ui/src/blocks/site-card/empty-site-card.tsx b/packages/ui/src/blocks/site-card/empty-site-card.tsx
new file mode 100644
index 000000000..bcf2efb12
--- /dev/null
+++ b/packages/ui/src/blocks/site-card/empty-site-card.tsx
@@ -0,0 +1,27 @@
+import clsx from 'clsx';
+import * as React from 'react';
+
+import { Card, Link, IconPlus, Text } from '../../components';
+import { cardStyle } from './site-card';
+
+export function EmptySiteCard(): JSX.Element {
+ return (
+
+
+
+
+
+ Create new site
+
+ Connect your Notion database and go live
+
+ );
+}
diff --git a/packages/ui/src/blocks/site-card/site-card-menu.tsx b/packages/ui/src/blocks/site-card/site-card-menu.tsx
new file mode 100644
index 000000000..67f9dd2a8
--- /dev/null
+++ b/packages/ui/src/blocks/site-card/site-card-menu.tsx
@@ -0,0 +1,68 @@
+import {
+ IconExternalLink,
+ IconGlobe,
+ IconLayers,
+ IconMoreVertical,
+ IconPieChart,
+ IconSettings,
+ Menu,
+} from '../../components';
+import { RouterOutputs } from '../../utilities';
+import { MenuLink } from '../user-menu/menu-link';
+
+export function SiteCardMenu({
+ site,
+}: {
+ site: RouterOutputs['site']['all'][number];
+}): JSX.Element {
+ const siteHome = `/site/${site.subdomain}`;
+ const links = [
+ {
+ name: 'Site home',
+ href: siteHome,
+ // TODO: should be double layer
+ icon: IconLayers,
+ },
+ {
+ name: 'Live',
+ href: `https://${site.subdomain}.chirpy.dev`,
+ icon: IconExternalLink,
+ },
+ {
+ name: 'Analytics',
+ href: `${siteHome}/analytics`,
+ icon: IconPieChart,
+ },
+ {
+ name: 'Domain',
+ href: `${siteHome}/domain`,
+ icon: IconGlobe,
+ },
+ {
+ name: 'Settings',
+ href: `${siteHome}/settings`,
+ icon: IconSettings,
+ },
+ ];
+
+ return (
+
+ );
+}
diff --git a/packages/ui/src/blocks/site-card/site-card.tsx b/packages/ui/src/blocks/site-card/site-card.tsx
index a0ce8a6d4..687945694 100644
--- a/packages/ui/src/blocks/site-card/site-card.tsx
+++ b/packages/ui/src/blocks/site-card/site-card.tsx
@@ -1,51 +1,43 @@
import { cpDayjs } from '@chirpy-dev/utils';
-import * as React from 'react';
-import { Card, Divider, Heading, Link, List, Text } from '../../components';
-import { listHoverable } from '../../styles/common';
+import { Card, Link, Text } from '../../components';
import { RouterOutputs } from '../../utilities/trpc-client';
+import { SiteCardMenu } from './site-card-menu';
export type SiteCardProps = {
site: RouterOutputs['site']['all'][number];
};
-export function SiteCard({ site }: SiteCardProps): JSX.Element {
- const posts = site.posts.slice(0, 5);
+export const cardStyle =
+ 'block h-[170px] w-[350px] rounded-xl bg-gray-0 p-6 shadow-sm focus-visible:ring focus-visible:ring-gray-700';
+export function SiteCard({ site }: SiteCardProps): JSX.Element {
return (
-
- {site.name}
-
-
- {site.subdomain}
-
- {posts.length > 0 ? (
-
-
- {posts.map((page) => (
-
- {/* {page.title} */}
-
- ))}
-
+
+
![favicon](https://via.placeholder.com/50)
+
+ {site.name}
+
+ {site.subdomain}
+
- ) : (
-
- No posts yet
-
- )}
-
-
-
- Created {cpDayjs(site.createdAt).fromNow()}
-
+
+
+
+ Created {cpDayjs(site.createdAt).fromNow()}
);
diff --git a/packages/ui/src/blocks/user-menu/menu-link.tsx b/packages/ui/src/blocks/user-menu/menu-link.tsx
index a6d16f2fe..1c93e6263 100644
--- a/packages/ui/src/blocks/user-menu/menu-link.tsx
+++ b/packages/ui/src/blocks/user-menu/menu-link.tsx
@@ -12,4 +12,4 @@ export const MenuLink = React.forwardRef(function MenuLink(
);
});
-export const itemStyle = `space-x-1`;
+export const itemStyle = 'flex flex-row gap-2 items-center';
diff --git a/packages/ui/src/blocks/user-menu/user-menu.tsx b/packages/ui/src/blocks/user-menu/user-menu.tsx
index d3a65b199..ea32881d4 100644
--- a/packages/ui/src/blocks/user-menu/user-menu.tsx
+++ b/packages/ui/src/blocks/user-menu/user-menu.tsx
@@ -1,12 +1,11 @@
-import { SUPPORT_LINK, SIGN_IN_SUCCESS_KEY } from '@chirpy-dev/utils';
+import { SIGN_IN_SUCCESS_KEY, SUPPORT_LINK } from '@chirpy-dev/utils';
import { signOut } from 'next-auth/react';
-import * as React from 'react';
import { Avatar } from '../../components/avatar';
import { IconLifeBuoy, IconLogOut, IconUser } from '../../components/icons';
import { Menu } from '../../components/menu';
import { useCurrentUser } from '../../contexts/current-user-context';
-import { itemStyle, MenuLink } from './menu-link';
+import { MenuLink } from './menu-link';
export async function handleSignOut() {
await signOut();
@@ -32,35 +31,26 @@ export function UserMenu(): JSX.Element {
-
+
Support
{isSignIn && (
<>
-
-
+
+
Profile
-
- Log out
+
+ Log out
>
)}
diff --git a/packages/ui/src/components/heading/heading.tsx b/packages/ui/src/components/heading/heading.tsx
index 49da641c4..126b22a08 100644
--- a/packages/ui/src/components/heading/heading.tsx
+++ b/packages/ui/src/components/heading/heading.tsx
@@ -25,7 +25,7 @@ export function Heading(props: IHeadingProps): JSX.Element {
return (
{children}
diff --git a/packages/ui/src/components/menu/menu.tsx b/packages/ui/src/components/menu/menu.tsx
index af8140000..27c06e3d0 100644
--- a/packages/ui/src/components/menu/menu.tsx
+++ b/packages/ui/src/components/menu/menu.tsx
@@ -5,9 +5,8 @@ import * as React from 'react';
import { bluredBg, listHoverable } from '../../styles/common';
import { Box, BoxProps } from '../box';
-import { BaseButton, Button, IconButton } from '../button';
+import { Button, IconButton } from '../button';
import { Divider } from '../divider';
-import { IconChevronDown } from '../icons';
import styles from './menu.module.scss';
export type Shape = 'circle' | 'square';
@@ -74,14 +73,6 @@ function MenuButton({
onClick={() => onClick?.(open)}
>
{children}
- {shape === 'square' && (
-
- )}
@@ -116,18 +107,16 @@ function MenuItems({
);
}
-export type MenuItemProps = React.PropsWithChildren<{
+export type MenuItemProps = {
disableAutoDismiss?: boolean;
- onClick?: () => void;
disabled?: boolean;
-}> &
- BoxProps;
+} & BoxProps;
function MenuItem({
as,
children,
disableAutoDismiss,
- onClick,
+ disabled,
className,
...asProps
}: MenuItemProps): JSX.Element {
@@ -139,10 +128,12 @@ function MenuItem({
children
);
return (
-
+
;
+
+const Template: ComponentStory = () => {
+ return (
+
+
+
+ );
+};
+
+export const Default = Template.bind({});
diff --git a/packages/ui/src/pages/app/components/app-layout/app-layout.tsx b/packages/ui/src/pages/app/components/app-layout/app-layout.tsx
index 08f1fbfe6..e085eb28d 100644
--- a/packages/ui/src/pages/app/components/app-layout/app-layout.tsx
+++ b/packages/ui/src/pages/app/components/app-layout/app-layout.tsx
@@ -17,7 +17,7 @@ export function AppLayout(props: AppLayoutProps): JSX.Element {
{/* Render Sidebar on desktop view, render MobileHeader on mobile view */}
- {props.children}
+ {props.children}
diff --git a/packages/ui/src/pages/app/components/app-layout/mobile-header.tsx b/packages/ui/src/pages/app/components/app-layout/mobile-header.tsx
index 1dbf34036..7714cc3b6 100644
--- a/packages/ui/src/pages/app/components/app-layout/mobile-header.tsx
+++ b/packages/ui/src/pages/app/components/app-layout/mobile-header.tsx
@@ -82,7 +82,7 @@ function SiteMobileHeader(props: SiteMobileHeaderProps) {
- Site Home
+ Site home
diff --git a/packages/ui/src/pages/app/components/app-layout/sidebar.tsx b/packages/ui/src/pages/app/components/app-layout/sidebar.tsx
index dcda3681f..a636e6e4b 100644
--- a/packages/ui/src/pages/app/components/app-layout/sidebar.tsx
+++ b/packages/ui/src/pages/app/components/app-layout/sidebar.tsx
@@ -114,7 +114,7 @@ function SiteSidebar(props: SiteSidebarProps) {
highlightPattern={/^\/$/}
icon={}
>
- Site Home
+ Site home
@@ -185,7 +185,7 @@ function SiteSidebar(props: SiteSidebarProps) {
}
icon={}
>
- Site Settings
+ Site settings
diff --git a/packages/ui/src/pages/app/components/app-layout/usage-card.tsx b/packages/ui/src/pages/app/components/app-layout/usage-card.tsx
index fd3d6a58f..bba5febfc 100644
--- a/packages/ui/src/pages/app/components/app-layout/usage-card.tsx
+++ b/packages/ui/src/pages/app/components/app-layout/usage-card.tsx
@@ -11,6 +11,8 @@ import {
} from '../../../../components';
import { trpcClient } from '../../../../utilities';
+const USAGE_THRESHOLD = 80;
+
export function UsageCard(): JSX.Element {
const router = useRouter();
const [isDismissed, setIsDismissed] = React.useState(false);
@@ -23,6 +25,11 @@ export function UsageCard(): JSX.Element {
return <>>;
}
const usagePercentage = Math.round((data.usage / data.usageLimit) * 100);
+
+ if (usagePercentage < USAGE_THRESHOLD) {
+ return <>>;
+ }
+
return (
diff --git a/packages/ui/src/pages/app/home/create-button.tsx b/packages/ui/src/pages/app/home/create-button.tsx
deleted file mode 100644
index 80e92720a..000000000
--- a/packages/ui/src/pages/app/home/create-button.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import { isENVProd } from '@chirpy-dev/utils';
-import { useRouter } from 'next/router';
-import * as React from 'react';
-
-import {
- Heading,
- IconBook,
- IconMessageSquare,
- IconPlusCircle,
- Menu,
- Popover,
- Text,
-} from '../../../components';
-import { useCurrentUser } from '../../../contexts';
-
-export type CreateProjectButtonProps = {
- projectCount?: number;
- onClickCreateProject: () => void;
-};
-
-export function CreateProjectButton(
- props: CreateProjectButtonProps,
-): JSX.Element {
- const { data } = useCurrentUser();
- // let disabledType: DisabledType | undefined = 'maintenanceMode';
- let disabledType: DisabledType | undefined;
- if (isENVProd && (props.projectCount || 0) > 0) {
- disabledType = 'projectLimit';
- } else if (!data.email) {
- disabledType = 'anonymous';
- } else if (process.env.NEXT_PUBLIC_MAINTENANCE_MODE) {
- disabledType = 'maintenanceMode';
- }
- const createButtonProps = {
- variant: 'primary',
- className: 'space-x-1',
- } as const;
- const createButtonChildren = (
- <>
-
- Create
- >
- );
- const router = useRouter();
- return (
- <>
- {disabledType ? (
-
-
- {createButtonChildren}
-
-
- {DISABLED_MESSAGE_MAP[disabledType]}
-
-
- ) : (
-
- )}
- >
- );
-}
-
-type DisabledType = 'anonymous' | 'projectLimit' | 'maintenanceMode';
-const DISABLED_MESSAGE_MAP: Record = {
- anonymous: (
-
-
- Connect an email with your account
-
-
- Otherwise, you may lose access to your project after creation. You can
- re-sign in with your email or social media account.
-
-
- ),
- projectLimit: (
-
-
- Reached the maximum number of projects
-
-
- You can delete an existing project to create a new one.
-
-
- ),
- maintenanceMode: (
-
-
- System maintenance mode
-
-
- All editing actions are disabled during this period, Please check back
- in a little while and try again.
-
-
- ),
-};
diff --git a/packages/ui/src/pages/app/home/index.tsx b/packages/ui/src/pages/app/home/index.tsx
index 9334d61a8..bf884ce1f 100644
--- a/packages/ui/src/pages/app/home/index.tsx
+++ b/packages/ui/src/pages/app/home/index.tsx
@@ -1,86 +1,43 @@
import * as React from 'react';
-import {
- PageTitleDeprecated,
- EmptyProjectCard,
- ProjectCard,
- SiteCard,
-} from '../../../blocks';
+import { PageTitle, SiteCard } from '../../../blocks';
+import { EmptySiteCard } from '../../../blocks/site-card/empty-site-card';
import { Heading, Spinner } from '../../../components';
import { useCurrentUser } from '../../../contexts';
import { trpcClient } from '../../../utilities/trpc-client';
import { AppLayout } from '../components/app-layout';
-import { CreateProjectButton } from './create-button';
import { CreateProjectDialog } from './create-project-dialog';
export function DashboardHome(): JSX.Element {
const { loading: userIsLoading } = useCurrentUser();
- const {
- data: projects,
- refetch: fetchUserProjects,
- isFetching: isFetchingProject,
- } = trpcClient.project.all.useQuery();
+ const { refetch: fetchUserProjects } = trpcClient.project.all.useQuery();
const { data: sites, isFetching: isFetchingSites } =
trpcClient.site.all.useQuery();
+ const { data } = useCurrentUser();
const [showProjectDialog, setShowProjectDialog] = React.useState(false);
return (
-
+
-
Dashboard
-
setShowProjectDialog(true)}
- />
-
-
-
- My projects
-
- {projects?.length ? (
-
-
- {projects.map((project) => (
- -
-
-
- ))}
-
-
-
- ) : isFetchingProject || userIsLoading ? (
-
- ) : (
-
-
-
- )}
+
Welcome back, {data.name}
+
-
- My sites
+
+ All sites
{sites?.length ? (
-
-
- {sites.map((project) => (
- -
-
-
- ))}
-
-
-
- ) : isFetchingSites || userIsLoading ? (
-
+
+
+ {sites.map((project) => (
+ -
+
+
+ ))}
+
) : (
-
-
-
+ isFetchingSites || (userIsLoading && )
)}
diff --git a/packages/ui/src/pages/app/site/settings/site-settings.tsx b/packages/ui/src/pages/app/site/settings/site-settings.tsx
index d3f492697..64ad18a0f 100644
--- a/packages/ui/src/pages/app/site/settings/site-settings.tsx
+++ b/packages/ui/src/pages/app/site/settings/site-settings.tsx
@@ -15,8 +15,8 @@ export function SiteSettings({ subdomain }: SiteSettingsProps): JSX.Element {
const { data } = trpcClient.site.bySubdomain.useQuery(subdomain);
return (
-
- Site Settings
+
+ Site settings
General
diff --git a/packages/ui/src/styles/common.ts b/packages/ui/src/styles/common.ts
index 6403322fc..da6352acc 100644
--- a/packages/ui/src/styles/common.ts
+++ b/packages/ui/src/styles/common.ts
@@ -10,7 +10,7 @@ export const bluredBg = `
export const border = `border outline-none focus-visible:border-primary-900`;
-export const listHoverableColor = `transition hover:bg-primary-400 hover:text-primary-1100`;
+export const listHoverableColor = `transition hover:bg-primary-400 hover:text-primary-1100 focus-visible:bg-primary-400 focus-visible:text-primary-1100`;
export const listHoverable = `px-2 py-1 rounded ${listHoverableColor}`;
export const ring = `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-bg focus-visible:ring-offset-2 focus-visible:ring-primary-700`;