mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-24 22:09:08 +08:00
refactor(infra): directory structure (#4615)
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { AuthPageContainer } from '@affine/component/auth-components';
|
||||
import { type GetInviteInfoQuery } from '@affine/graphql';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Avatar } from '@toeverything/components/avatar';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
|
||||
import { FlexWrapper } from '../../ui/layout';
|
||||
import * as styles from './styles.css';
|
||||
export const AcceptInvitePage = ({
|
||||
onOpenWorkspace,
|
||||
inviteInfo,
|
||||
}: {
|
||||
onOpenWorkspace: () => void;
|
||||
inviteInfo: GetInviteInfoQuery['getInviteInfo'];
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<AuthPageContainer
|
||||
title={t['Successfully joined!']()}
|
||||
subtitle={
|
||||
<FlexWrapper alignItems="center">
|
||||
<Avatar
|
||||
url={inviteInfo.user.avatarUrl || ''}
|
||||
name={inviteInfo.user.name}
|
||||
size={20}
|
||||
/>
|
||||
<span className={styles.inviteName}>{inviteInfo.user.name}</span>
|
||||
{t['invited you to join']()}
|
||||
<Avatar
|
||||
url={`data:image/png;base64,${inviteInfo.workspace.avatar}`}
|
||||
name={inviteInfo.workspace.name}
|
||||
size={20}
|
||||
style={{ marginLeft: 4 }}
|
||||
colorfulFallback
|
||||
/>
|
||||
<span className={styles.inviteName}>{inviteInfo.workspace.name}</span>
|
||||
</FlexWrapper>
|
||||
}
|
||||
>
|
||||
<Button type="primary" size="large" onClick={onOpenWorkspace}>
|
||||
{t['Visit Workspace']()}
|
||||
</Button>
|
||||
</AuthPageContainer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './accept-invite-page';
|
||||
export * from './invite-modal';
|
||||
export * from './pagination';
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Permission } from '@affine/graphql';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ConfirmModal } from '@toeverything/components/modal';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { AuthInput } from '..//auth-components';
|
||||
import { emailRegex } from '..//auth-components/utils';
|
||||
|
||||
export interface InviteModalProps {
|
||||
open: boolean;
|
||||
setOpen: (value: boolean) => void;
|
||||
onConfirm: (params: { email: string; permission: Permission }) => void;
|
||||
isMutating: boolean;
|
||||
}
|
||||
|
||||
export const InviteModal = ({
|
||||
open,
|
||||
setOpen,
|
||||
onConfirm,
|
||||
isMutating,
|
||||
}: InviteModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [inviteEmail, setInviteEmail] = useState('');
|
||||
const [permission] = useState(Permission.Write);
|
||||
const [isValidEmail, setIsValidEmail] = useState(true);
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
if (!emailRegex.test(inviteEmail)) {
|
||||
setIsValidEmail(false);
|
||||
return;
|
||||
}
|
||||
setIsValidEmail(true);
|
||||
|
||||
onConfirm({
|
||||
email: inviteEmail,
|
||||
permission,
|
||||
});
|
||||
}, [inviteEmail, onConfirm, permission]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setInviteEmail('');
|
||||
setIsValidEmail(true);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<ConfirmModal
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
title={t['Invite Members']()}
|
||||
description={t['Invite Members Message']()}
|
||||
cancelText={t['com.affine.inviteModal.button.cancel']()}
|
||||
contentOptions={{
|
||||
['data-testid' as string]: 'invite-modal',
|
||||
style: {
|
||||
padding: '20px 26px',
|
||||
},
|
||||
}}
|
||||
confirmButtonOptions={{
|
||||
loading: isMutating,
|
||||
type: 'primary',
|
||||
['data-testid' as string]: 'confirm-enable-affine-cloud-button',
|
||||
children: t['Invite'](),
|
||||
}}
|
||||
onConfirm={handleConfirm}
|
||||
>
|
||||
{/*TODO: check email & add placeholder*/}
|
||||
<AuthInput
|
||||
disabled={isMutating}
|
||||
placeholder="email@example.com"
|
||||
value={inviteEmail}
|
||||
onChange={setInviteEmail}
|
||||
error={!isValidEmail}
|
||||
errorHint={isValidEmail ? '' : t['com.affine.auth.sign.email.error']()}
|
||||
onEnter={handleConfirm}
|
||||
wrapperProps={{
|
||||
style: { padding: 0 },
|
||||
}}
|
||||
size="large"
|
||||
/>
|
||||
</ConfirmModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ArrowLeftSmallIcon, ArrowRightSmallIcon } from '@blocksuite/icons';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import ReactPaginate from 'react-paginate';
|
||||
|
||||
import * as styles from './styles.css';
|
||||
export interface PaginationProps {
|
||||
totalCount: number;
|
||||
countPerPage: number;
|
||||
onPageChange: (skip: number) => void;
|
||||
}
|
||||
|
||||
export const Pagination = ({
|
||||
totalCount,
|
||||
countPerPage,
|
||||
onPageChange,
|
||||
}: PaginationProps) => {
|
||||
const handlePageClick = useCallback(
|
||||
(e: { selected: number }) => {
|
||||
const newOffset = (e.selected * countPerPage) % totalCount;
|
||||
onPageChange(newOffset);
|
||||
},
|
||||
[countPerPage, onPageChange, totalCount]
|
||||
);
|
||||
|
||||
const pageCount = useMemo(
|
||||
() => Math.ceil(totalCount / countPerPage),
|
||||
[countPerPage, totalCount]
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
onPageChange={handlePageClick}
|
||||
pageRangeDisplayed={3}
|
||||
marginPagesDisplayed={2}
|
||||
pageCount={pageCount}
|
||||
previousLabel={<ArrowLeftSmallIcon />}
|
||||
nextLabel={<ArrowRightSmallIcon />}
|
||||
pageClassName={styles.pageItem}
|
||||
previousClassName={clsx(styles.pageItem, 'label')}
|
||||
nextClassName={clsx(styles.pageItem, 'label')}
|
||||
breakLabel="..."
|
||||
breakClassName={styles.pageItem}
|
||||
containerClassName={styles.pagination}
|
||||
activeClassName="active"
|
||||
renderOnZeroPageCount={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const inviteModalTitle = style({
|
||||
fontWeight: '600',
|
||||
fontSize: 'var(--affine-font-h-6)',
|
||||
marginBottom: '20px',
|
||||
});
|
||||
|
||||
export const inviteModalContent = style({
|
||||
marginBottom: '10px',
|
||||
});
|
||||
|
||||
export const inviteModalButtonContainer = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
// marginTop: 10,
|
||||
});
|
||||
|
||||
export const inviteName = style({
|
||||
marginLeft: '4px',
|
||||
marginRight: '10px',
|
||||
color: 'var(--affine-black)',
|
||||
});
|
||||
|
||||
export const pagination = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
marginTop: 5,
|
||||
});
|
||||
|
||||
export const pageItem = style({
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
borderRadius: '4px',
|
||||
|
||||
selectors: {
|
||||
'&:hover': {
|
||||
background: 'var(--affine-hover-color)',
|
||||
},
|
||||
'&.active': {
|
||||
color: 'var(--affine-primary-color)',
|
||||
cursor: 'default',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
'&.label': {
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: '16px',
|
||||
},
|
||||
'&.disabled': {
|
||||
opacity: '.4',
|
||||
cursor: 'default',
|
||||
color: 'var(--affine-disable-color)',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
},
|
||||
});
|
||||
globalStyle(`${pageItem} a`, {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
});
|
||||
Reference in New Issue
Block a user