feat: 增加创建群组永久邀请码的功能

pull/81/head
moonrailgun 3 years ago
parent 05ae07282f
commit faaeadd8a1

@ -15,5 +15,5 @@ export function useAsyncRequest<T extends FunctionReturningPromise>(
} }
}, deps); }, deps);
return [{ loading }, call] as const; return [{ loading }, call as T] as const;
} }

@ -116,10 +116,12 @@ export async function quitGroup(groupId: string) {
* @param groupId id * @param groupId id
*/ */
export async function createGroupInviteCode( export async function createGroupInviteCode(
groupId: string groupId: string,
inviteType: 'normal' | 'permanent'
): Promise<GroupInvite> { ): Promise<GroupInvite> {
const { data } = await request.post('/api/group/invite/createGroupInvite', { const { data } = await request.post('/api/group/invite/createGroupInvite', {
groupId, groupId,
inviteType,
}); });
return data; return data;

@ -0,0 +1,34 @@
import { Tooltip } from 'antd';
import React from 'react';
import {
datetimeFromNow,
formatFullTime,
GroupInvite,
t,
Trans,
} from 'tailchat-shared';
interface InviteCodeExpiredAtProps {
invite: Pick<GroupInvite, 'expiredAt'>;
}
export const InviteCodeExpiredAt: React.FC<InviteCodeExpiredAtProps> =
React.memo((props) => {
const { invite } = props;
if (!invite.expiredAt) {
return t('该邀请码永不过期');
}
return (
<Trans>
{' '}
<Tooltip title={formatFullTime(invite.expiredAt)}>
<span className="font-bold">
{{ date: datetimeFromNow(invite.expiredAt) }}
</span>{' '}
</Tooltip>
</Trans>
);
});
InviteCodeExpiredAt.displayName = 'InviteCodeExpiredAt';

@ -0,0 +1,8 @@
.createInviteBtn {
display: flex;
:global {
.ant-btn:not(.ant-dropdown-trigger) {
flex: 1;
}
}
}

@ -0,0 +1,75 @@
import { InviteCodeExpiredAt } from '@/components/InviteCodeExpiredAt';
import { Menu, Typography, Dropdown } from 'antd';
import React, { useState } from 'react';
import {
useAsyncRequest,
createGroupInviteCode,
t,
GroupInvite,
} from 'tailchat-shared';
import styles from './CreateInviteCode.module.less';
enum InviteCodeType {
Normal = 'normal',
Permanent = 'permanent',
}
interface CreateInviteCodeProps {
groupId: string;
onInviteCreated?: () => void;
}
export const CreateInviteCode: React.FC<CreateInviteCodeProps> = React.memo(
({ groupId, onInviteCreated }) => {
const [createdInvite, setCreateInvite] = useState<GroupInvite | null>(null);
const [{ loading }, handleCreateInviteLink] = useAsyncRequest(
async (inviteType: InviteCodeType) => {
const invite = await createGroupInviteCode(groupId, inviteType);
setCreateInvite(invite);
onInviteCreated?.();
},
[groupId, onInviteCreated]
);
const menu = (
<Menu>
<Menu.Item
onClick={() => handleCreateInviteLink(InviteCodeType.Permanent)}
>
{t('创建永久邀请码')}
</Menu.Item>
</Menu>
);
return (
<div>
{createdInvite ? (
<div>
<Typography.Title
className="bg-white bg-opacity-30 dark:bg-black dark:bg-opacity-30 px-2 py-1 select-text text-lg rounded border border-black border-opacity-20"
level={4}
copyable={true}
>
{`${location.origin}/invite/${createdInvite.code}`}
</Typography.Title>
<p className="text-gray-500 text-sm">
<InviteCodeExpiredAt invite={createdInvite} />
</p>
</div>
) : (
<Dropdown.Button
className={styles.createInviteBtn}
size="large"
type="primary"
loading={loading}
onClick={() => handleCreateInviteLink(InviteCodeType.Normal)}
overlay={menu}
trigger={['click']}
>
{t('创建链接')}
</Dropdown.Button>
)}
</div>
);
}
);
CreateInviteCode.displayName = 'CreateInviteCode';

@ -0,0 +1,60 @@
import { Icon } from '@/components/Icon';
import React from 'react';
import { useGroupInfo, t } from 'tailchat-shared';
import { ModalWrapper } from '../../Modal';
import { CreateInviteCode } from './CreateInviteCode';
/**
*
*/
interface CreateGroupInviteProps {
groupId: string;
onInviteCreated?: () => void;
}
export const CreateGroupInvite: React.FC<CreateGroupInviteProps> = React.memo(
(props) => {
const groupId = props.groupId;
const groupInfo = useGroupInfo(groupId);
// const [searchName, setSearchName] = useState('');
// const handleSearch = useCallback(() => {
// console.log('searchName', searchName);
// }, []);
if (!groupInfo) {
return <div>{t('异常')}</div>;
}
return (
<ModalWrapper>
{/* <div> {groupInfo.name}</div>
<div>
<Input.Search
value={searchName}
onChange={(e) => setSearchName(e.target.value)}
onSearch={handleSearch}
/>
</div>
<Divider></Divider> */}
<Icon
className="text-6xl block m-auto opacity-30 mb-4 mt-2"
icon="mdi:email-edit-outline"
/>
<div className="text-gray-400 font-bold text-lg mb-2">
{t('创建链接并发送给外部好友')}
</div>
<CreateInviteCode
groupId={groupId}
onInviteCreated={props.onInviteCreated}
/>
</ModalWrapper>
);
}
);
CreateGroupInvite.displayName = 'GroupInvite';

@ -1,4 +1,3 @@
import { LoadingSpinner } from '@/plugin/component';
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { import {
getAllGroupInviteCode, getAllGroupInviteCode,
@ -13,7 +12,8 @@ import { Button, Table, Tooltip } from 'antd';
import type { ColumnType } from 'antd/lib/table'; import type { ColumnType } from 'antd/lib/table';
import { UserName } from '@/components/UserName'; import { UserName } from '@/components/UserName';
import { Loading } from '@/components/Loading'; import { Loading } from '@/components/Loading';
import { openReconfirmModalP } from '@/components/Modal'; import { openModal, openReconfirmModalP } from '@/components/Modal';
import { CreateGroupInvite } from '../CreateGroupInvite';
export const GroupInvite: React.FC<{ export const GroupInvite: React.FC<{
groupId: string; groupId: string;
@ -22,9 +22,20 @@ export const GroupInvite: React.FC<{
const { loading, value, refresh } = useAsyncRefresh(async () => { const { loading, value, refresh } = useAsyncRefresh(async () => {
const list = await getAllGroupInviteCode(groupId); const list = await getAllGroupInviteCode(groupId);
return list; return list.reverse(); // 倒序返回
}, [groupId]); }, [groupId]);
const handleCreateInvite = useCallback(() => {
openModal(
<CreateGroupInvite
groupId={groupId}
onInviteCreated={() => {
refresh();
}}
/>
);
}, [groupId, refresh]);
const handleDeleteInvite = useCallback( const handleDeleteInvite = useCallback(
async (inviteId: string) => { async (inviteId: string) => {
if (await openReconfirmModalP()) { if (await openReconfirmModalP()) {
@ -53,11 +64,17 @@ export const GroupInvite: React.FC<{
{ {
title: t('过期时间'), title: t('过期时间'),
dataIndex: 'expiredAt', dataIndex: 'expiredAt',
render: (date) => ( render: (date) => {
<Tooltip title={formatFullTime(date)}> if (!date) {
{datetimeFromNow(date)} return t('永不过期');
</Tooltip> }
),
return (
<Tooltip title={formatFullTime(date)}>
{datetimeFromNow(date)}
</Tooltip>
);
},
}, },
{ {
title: t('创建者'), title: t('创建者'),
@ -87,6 +104,12 @@ export const GroupInvite: React.FC<{
return ( return (
<Loading spinning={loading}> <Loading spinning={loading}>
<div className="text-right mb-2">
<Button type="primary" onClick={handleCreateInvite}>
{t('创建邀请码')}
</Button>
</div>
<Table <Table
columns={columns} columns={columns}
dataSource={value} dataSource={value}

@ -1,92 +0,0 @@
import { Icon } from '@/components/Icon';
import { Button, Typography } from 'antd';
import React, { useState } from 'react';
import {
createGroupInviteCode,
useAsyncRequest,
useGroupInfo,
isValidStr,
t,
} from 'tailchat-shared';
import { ModalWrapper } from '../Modal';
/**
*
*/
interface GroupInviteProps {
groupId: string;
}
export const GroupInvite: React.FC<GroupInviteProps> = React.memo((props) => {
const groupId = props.groupId;
const groupInfo = useGroupInfo(groupId);
// const [searchName, setSearchName] = useState('');
const [inviteLink, setInviteLink] = useState('');
// const handleSearch = useCallback(() => {
// console.log('searchName', searchName);
// }, []);
const [{ loading }, handleCreateInviteLink] = useAsyncRequest(async () => {
const invite = await createGroupInviteCode(groupId);
setInviteLink(`${location.origin}/invite/${invite.code}`);
}, [groupId]);
if (!groupInfo) {
return <div>{t('异常')}</div>;
}
return (
<ModalWrapper>
{/* <div> {groupInfo.name}</div>
<div>
<Input.Search
value={searchName}
onChange={(e) => setSearchName(e.target.value)}
onSearch={handleSearch}
/>
</div>
<Divider></Divider> */}
<Icon
className="text-6xl block m-auto opacity-30 mb-4 mt-2"
icon="mdi:email-edit-outline"
/>
<div className="text-gray-400 font-bold text-lg mb-2">
{t('创建链接并发送给外部好友')}
</div>
<div>
{isValidStr(inviteLink) ? (
<div>
<Typography.Title
className="bg-white bg-opacity-30 dark:bg-black dark:bg-opacity-30 px-2 py-1 select-text text-lg rounded border border-black border-opacity-20"
level={4}
copyable={true}
>
{inviteLink}
</Typography.Title>
{/* TODO: 显示过期天数 */}
<p className="text-gray-500 text-sm">
{t('该邀请链接将会于7天后过期')}
</p>
</div>
) : (
<Button
block={true}
size="large"
type="primary"
loading={loading}
onClick={handleCreateInviteLink}
>
{t('创建链接')}
</Button>
)}
</div>
</ModalWrapper>
);
});
GroupInvite.displayName = 'GroupInvite';

@ -1,4 +1,5 @@
import { Avatar } from '@/components/Avatar'; import { Avatar } from '@/components/Avatar';
import { InviteCodeExpiredAt } from '@/components/InviteCodeExpiredAt';
import { LoadingSpinner } from '@/components/LoadingSpinner'; import { LoadingSpinner } from '@/components/LoadingSpinner';
import { UserName } from '@/components/UserName'; import { UserName } from '@/components/UserName';
import React from 'react'; import React from 'react';
@ -66,15 +67,10 @@ export const InviteInfo: React.FC<Props> = React.memo((props) => {
{t('当前成员数')}: {value.group.memberCount} {t('当前成员数')}: {value.group.memberCount}
</div> </div>
{/* 永久邀请码不显示过期时间 */}
{value.expired && ( {value.expired && (
<div> <div>
<Trans> <InviteCodeExpiredAt invite={{ expiredAt: value.expired }} />
{' '}
<span className="font-bold">
{{ date: datetimeFromNow(value.expired) }}
</span>{' '}
</Trans>
</div> </div>
)} )}
</div> </div>

@ -1,6 +1,6 @@
import { closeModal, openModal } from '@/components/Modal'; import { closeModal, openModal } from '@/components/Modal';
import { GroupDetail } from '@/components/modals/GroupDetail'; import { GroupDetail } from '@/components/modals/GroupDetail';
import { GroupInvite } from '@/components/modals/GroupInvite'; import { CreateGroupInvite } from '@/components/modals/CreateGroupInvite';
import React from 'react'; import React from 'react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
@ -25,7 +25,7 @@ export function useGroupHeaderAction(groupId: string) {
}, [groupId]); }, [groupId]);
const handleInviteUser = useCallback(() => { const handleInviteUser = useCallback(() => {
openModal(<GroupInvite groupId={groupId} />); openModal(<CreateGroupInvite groupId={groupId} />);
}, [groupId]); }, [groupId]);
const handleQuitGroup = useCallback(() => { const handleQuitGroup = useCallback(() => {

Loading…
Cancel
Save