feat: add invitation and join team features

This commit is contained in:
TinsFox 2025-08-16 22:45:46 +08:00
parent 60eaaaff60
commit c61188d524
8 changed files with 110 additions and 0 deletions

View file

@ -0,0 +1,22 @@
.container {
margin: 0 auto;
max-width: 400px;
padding: 20px;
}
.form {
background: var(--base50);
border-radius: 8px;
padding: 20px;
}
@media screen and (max-width: 768px) {
.container {
margin: 0;
padding: 10px;
}
.form {
padding: 15px;
}
}

View file

@ -0,0 +1,55 @@
'use client';
import { Form, FormRow, FormInput, FormButtons, TextField, Button } from 'react-basics';
import { useApi, useMessages, useModified } from '@/components/hooks';
import styles from './InviteForm.module.css';
import { useSearchParams } from 'next/navigation';
export function InviteForm() {
const searchParams = useSearchParams();
const accessCode = searchParams.get('accessCode');
const { formatMessage, labels } = useMessages();
const { post, useMutation } = useApi();
const { mutate, error } = useMutation({
mutationFn: (data: any) => post('/teams/join', data),
});
const { touch } = useModified();
const handleSubmit = async (data: any) => {
mutate(data, {
onSuccess: async () => {
touch('teams:members');
},
});
};
return (
<div className={styles.container}>
<div className={styles.form}>
<Form
onSubmit={handleSubmit}
error={error}
values={{
accessCode: accessCode || '',
}}
autoComplete="off"
preventSubmit={false}
>
<FormRow label={formatMessage(labels.accessCode)}>
<FormInput name="accessCode" rules={{ required: formatMessage(labels.required) }}>
<TextField autoComplete="off" />
</FormInput>
</FormRow>
<FormButtons flex>
<Button type="submit" variant="primary">
{formatMessage(labels.join)}
</Button>
<Button onClick={() => {}}>{formatMessage(labels.cancel)}</Button>
</FormButtons>
</Form>
</div>
</div>
);
}

View file

@ -0,0 +1,11 @@
'use client';
import { InviteForm } from './InviteForm';
export default function InvitePage() {
return (
<section style={{ marginBottom: 60 }}>
<InviteForm />
</section>
);
}

View file

@ -0,0 +1,10 @@
import { Metadata } from 'next';
import InvitePage from './InvitePage';
export const metadata: Metadata = {
title: 'Join team',
};
export default function () {
return <InvitePage />;
}

View file

@ -71,6 +71,15 @@ export function TeamEditForm({ teamId, allowEdit }: { teamId: string; allowEdit?
</Flexbox>
</FormRow>
)}
<FormRow label={formatMessage(labels.invitationLink)}>
<Flexbox gap={10}>
<TextField
value={`${window.location.host}/invite?accessCode=${accessCode}`}
readOnly
allowCopy
/>
</Flexbox>
</FormRow>
{allowEdit && (
<FormButtons>
<SubmitButton variant="primary">{formatMessage(labels.save)}</SubmitButton>

View file

@ -33,6 +33,7 @@ export const labels = defineMessages({
member: { id: 'label.member', defaultMessage: 'Member' },
members: { id: 'label.members', defaultMessage: 'Members' },
accessCode: { id: 'label.access-code', defaultMessage: 'Access code' },
invitationLink: { id: 'label.invitation-link', defaultMessage: 'Invitation link' },
teamId: { id: 'label.team-id', defaultMessage: 'Team ID' },
team: { id: 'label.team', defaultMessage: 'Team' },
teamName: { id: 'label.team-name', defaultMessage: 'Team name' },

View file

@ -235,6 +235,7 @@
"label.websites": "Websites",
"label.window": "Window",
"label.yesterday": "Yesterday",
"label.invitation-link": "邀请链接",
"message.action-confirmation": "Type {confirmation} in the box below to confirm.",
"message.active-users": "{x} current {x, plural, one {visitor} other {visitors}}",
"message.collected-data": "Collected data",

View file

@ -236,6 +236,7 @@
"label.websites": "网站",
"label.window": "窗口",
"label.yesterday": "昨天",
"label.invitation-link": "邀请链接",
"message.action-confirmation": "请在下方输入框中输入 {confirmation} 以确认操作。",
"message.active-users": "当前在线 {x} 位访客",
"message.collected-data": "已收集的数据",