feat(core): add sign in to not found page (#6496)

close AFF-211
This commit is contained in:
JimmFly
2024-04-10 07:27:02 +00:00
parent 7d131ee9fc
commit 6ea20e477b
16 changed files with 245 additions and 125 deletions

View File

@@ -17,11 +17,12 @@ export const topNav = style({
left: 0,
right: 0,
display: 'flex',
position: 'fixed',
alignItems: 'center',
justifyContent: 'space-between',
padding: '16px 120px',
selectors: {
'&.mobile': {
'@media': {
'screen and (max-width: 1024px)': {
padding: '16px 20px',
},
},
@@ -29,6 +30,11 @@ export const topNav = style({
export const topNavLinks = style({
display: 'flex',
columnGap: 4,
'@media': {
'screen and (max-width: 1024px)': {
display: 'none',
},
},
});
export const topNavLink = style({
color: cssVar('textPrimaryColor'),
@@ -46,6 +52,21 @@ export const iconButton = style({
},
},
});
export const hideInWideScreen = style({
'@media': {
'screen and (min-width: 1024px)': {
display: 'none',
position: 'absolute',
},
},
});
export const hideInSmallScreen = style({
'@media': {
'screen and (max-width: 1024px)': {
display: 'none',
},
},
});
export const menu = style({
width: '100vw',
height: '100vh',

View File

@@ -1,7 +1,6 @@
import { Button } from '@affine/component/ui/button';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Logo1Icon } from '@blocksuite/icons';
import clsx from 'clsx';
import { useCallback } from 'react';
import { DesktopNavbar } from './desktop-navbar';
@@ -9,10 +8,8 @@ import * as styles from './index.css';
import { MobileNavbar } from './mobile-navbar';
export const AffineOtherPageLayout = ({
isSmallScreen,
children,
}: {
isSmallScreen: boolean;
children: React.ReactNode;
}) => {
const t = useAFFiNEI18N();
@@ -23,25 +20,22 @@ export const AffineOtherPageLayout = ({
return (
<div className={styles.root}>
<div
className={clsx(styles.topNav, {
mobile: isSmallScreen,
})}
>
<a href="/" rel="noreferrer" className={styles.affineLogo}>
<Logo1Icon width={24} height={24} />
</a>
{isSmallScreen ? (
{environment.isDesktop ? null : (
<div className={styles.topNav}>
<a href="/" rel="noreferrer" className={styles.affineLogo}>
<Logo1Icon width={24} height={24} />
</a>
<DesktopNavbar />
<Button
onClick={openDownloadLink}
className={styles.hideInSmallScreen}
>
{t['com.affine.auth.open.affine.download-app']()}
</Button>
<MobileNavbar />
) : (
<>
<DesktopNavbar />
<Button onClick={openDownloadLink}>
{t['com.affine.auth.open.affine.download-app']()}
</Button>
</>
)}
</div>
</div>
)}
{children}
</div>

View File

@@ -29,7 +29,7 @@ export const MobileNavbar = () => {
);
return (
<div>
<div className={styles.hideInWideScreen}>
<Menu
items={menuItems}
contentOptions={{

View File

@@ -1,26 +1,14 @@
import type { FC, PropsWithChildren, ReactNode } from 'react';
import { useEffect, useState } from 'react';
import { Empty } from '../../ui/empty';
import { AffineOtherPageLayout } from '../affine-other-page-layout';
import { authPageContainer } from './share.css';
import { authPageContainer, hideInSmallScreen } from './share.css';
export const AuthPageContainer: FC<
PropsWithChildren<{ title?: ReactNode; subtitle?: ReactNode }>
> = ({ children, title, subtitle }) => {
const [isSmallScreen, setIsSmallScreen] = useState(false);
useEffect(() => {
const checkScreenSize = () => {
setIsSmallScreen(window.innerWidth <= 1024);
};
checkScreenSize();
window.addEventListener('resize', checkScreenSize);
return () => window.removeEventListener('resize', checkScreenSize);
}, []);
return (
<AffineOtherPageLayout isSmallScreen={isSmallScreen}>
<AffineOtherPageLayout>
<div className={authPageContainer}>
<div className="wrapper">
<div className="content">
@@ -28,7 +16,9 @@ export const AuthPageContainer: FC<
<p className="subtitle">{subtitle}</p>
{children}
</div>
{isSmallScreen ? null : <Empty />}
<div className={hideInSmallScreen}>
<Empty />
</div>
</div>
</div>
</AffineOtherPageLayout>

View File

@@ -179,8 +179,12 @@ globalStyle(`${authPageContainer} a`, {
color: cssVar('linkColor'),
});
export const signInPageContainer = style({
width: '400px',
margin: '205px auto 0',
height: '100vh',
width: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
});
export const input = style({
width: '330px',
@@ -190,3 +194,11 @@ export const input = style({
},
},
});
export const hideInSmallScreen = style({
'@media': {
'screen and (max-width: 1024px)': {
display: 'none',
},
},
});

View File

@@ -4,6 +4,7 @@ import { SignOutIcon } from '@blocksuite/icons';
import { Avatar } from '../../ui/avatar';
import { Button, IconButton } from '../../ui/button';
import { Tooltip } from '../../ui/tooltip';
import { AffineOtherPageLayout } from '../affine-other-page-layout';
import type { User } from '../auth-components';
import { NotFoundPattern } from './not-found-pattern';
import {
@@ -14,9 +15,57 @@ import {
export interface NotFoundPageProps {
user?: User | null;
signInComponent?: JSX.Element;
onBack: () => void;
onSignOut: () => void;
}
export const NoPermissionOrNotFound = ({
user,
onBack,
onSignOut,
signInComponent,
}: NotFoundPageProps) => {
const t = useAFFiNEI18N();
return (
<AffineOtherPageLayout>
<div className={notFoundPageContainer} data-testid="not-found">
<div>
{user ? (
<>
<div className={wrapper}>
<NotFoundPattern />
</div>
<p className={wrapper}>{t['404.hint']()}</p>
<div className={wrapper}>
<Button
type="primary"
size="extraLarge"
onClick={onBack}
className={largeButtonEffect}
>
{t['404.back']()}
</Button>
</div>
<div className={wrapper}>
<Avatar url={user.avatarUrl ?? user.image} name={user.name} />
<span style={{ margin: '0 12px' }}>{user.email}</span>
<Tooltip content={t['404.signOut']()}>
<IconButton onClick={onSignOut}>
<SignOutIcon />
</IconButton>
</Tooltip>
</div>
</>
) : (
signInComponent
)}
</div>
</div>
</AffineOtherPageLayout>
);
};
export const NotFoundPage = ({
user,
onBack,
@@ -25,35 +74,37 @@ export const NotFoundPage = ({
const t = useAFFiNEI18N();
return (
<div className={notFoundPageContainer} data-testid="not-found">
<div>
<div className={wrapper}>
<NotFoundPattern />
</div>
<p className={wrapper}>{t['404.hint']()}</p>
<div className={wrapper}>
<Button
type="primary"
size="extraLarge"
onClick={onBack}
className={largeButtonEffect}
>
{t['404.back']()}
</Button>
</div>
{user ? (
<AffineOtherPageLayout>
<div className={notFoundPageContainer} data-testid="not-found">
<div>
<div className={wrapper}>
<Avatar url={user.avatarUrl ?? user.image} name={user.name} />
<span style={{ margin: '0 12px' }}>{user.email}</span>
<Tooltip content={t['404.signOut']()}>
<IconButton onClick={onSignOut}>
<SignOutIcon />
</IconButton>
</Tooltip>
<NotFoundPattern />
</div>
) : null}
<p className={wrapper}>{t['404.hint']()}</p>
<div className={wrapper}>
<Button
type="primary"
size="extraLarge"
onClick={onBack}
className={largeButtonEffect}
>
{t['404.back']()}
</Button>
</div>
{user ? (
<div className={wrapper}>
<Avatar url={user.avatarUrl ?? user.image} name={user.name} />
<span style={{ margin: '0 12px' }}>{user.email}</span>
<Tooltip content={t['404.signOut']()}>
<IconButton onClick={onSignOut}>
<SignOutIcon />
</IconButton>
</Tooltip>
</div>
) : null}
</div>
</div>
</div>
</AffineOtherPageLayout>
);
};

View File

@@ -3,7 +3,7 @@ import { style } from '@vanilla-extract/css';
export const notFoundPageContainer = style({
fontSize: cssVar('fontBase'),
color: cssVar('textPrimaryColor'),
height: '100%',
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',

View File

@@ -1,8 +1,16 @@
import { memo } from 'react';
export const EmptySvg = memo(function EmptySvg() {
export const EmptySvg = memo(function EmptySvg({
style,
className,
}: {
style?: React.CSSProperties;
className?: string;
}) {
return (
<svg
className={className}
style={style}
width="248"
height="216"
viewBox="0 0 248 216"

View File

@@ -1,9 +1,16 @@
import { assignInlineVars } from '@vanilla-extract/dynamic';
import type { CSSProperties, ReactNode } from 'react';
import { EmptySvg } from './empty-svg';
import { StyledEmptyContainer } from './style';
import * as styles from './index.css';
type ContainerStyleProps = {
width?: string;
height?: string;
fontSize?: string;
};
export type EmptyContentProps = {
containerStyle?: CSSProperties;
containerStyle?: ContainerStyleProps;
title?: ReactNode;
description?: ReactNode;
descriptionStyle?: CSSProperties;
@@ -15,10 +22,15 @@ export const Empty = ({
description,
descriptionStyle,
}: EmptyContentProps) => {
const cssVar = assignInlineVars({
[styles.svgWidth]: containerStyle?.width,
[styles.svgHeight]: containerStyle?.height,
[styles.svgFontSize]: containerStyle?.fontSize,
});
return (
<StyledEmptyContainer style={containerStyle}>
<div className={styles.emptyContainer}>
<div style={{ color: 'var(--affine-black)' }}>
<EmptySvg />
<EmptySvg className={styles.emptySvg} style={cssVar} />
</div>
{title && (
<p
@@ -36,7 +48,7 @@ export const Empty = ({
{description}
</p>
)}
</StyledEmptyContainer>
</div>
);
};

View File

@@ -0,0 +1,24 @@
import { createVar, style } from '@vanilla-extract/css';
import { displayFlex } from '../../styles';
export const svgWidth = createVar();
export const svgHeight = createVar();
export const svgFontSize = createVar();
export const emptyContainer = style({
height: '100%',
...displayFlex('center', 'center'),
flexDirection: 'column',
color: 'var(--affine-text-secondary-color)',
});
export const emptySvg = style({
vars: {
[svgWidth]: '248px',
[svgHeight]: '216px',
[svgFontSize]: 'inherit',
},
width: svgWidth,
height: svgHeight,
fontSize: svgFontSize,
});

View File

@@ -1,19 +0,0 @@
import type { CSSProperties } from 'react';
import { displayFlex, styled } from '../../styles';
export const StyledEmptyContainer = styled('div')<{ style?: CSSProperties }>(({
style,
}) => {
return {
height: '100%',
...displayFlex('center', 'center'),
flexDirection: 'column',
color: 'var(--affine-text-secondary-color)',
svg: {
width: style?.width ?? '248px',
height: style?.height ?? '216px',
fontSize: style?.fontSize ?? 'inherit',
},
};
});