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 ( + + + + + + {links.map((link) => ( + + {} + {link.name} + + ))} + + + ); +} 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 +
+ {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 ( +
+ + + + + + Apple + Peach + + +
+ ); +}; + +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]} - - - ) : ( - - - {createButtonChildren} - - - router.push('/site/create')} - > - - Blog site - - - -

    Comment project

    -
    -
    -
    - )} - - ); -} - -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`;