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