mirror of https://github.com/msgbyte/tailchat
feat: 增加创建群组永久邀请码的功能
parent
05ae07282f
commit
faaeadd8a1
@ -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,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';
|
Loading…
Reference in New Issue