- account settings: sidebar sections (profile, devices, security, invitations, sync and sign-in) instead of one long scroll; inline device rename, confirmations for destructive actions and toast feedback - sign-in/register: split layout with a quiet brand column, text tabs and underlined inputs; registering asks for the password twice and explains that it cannot be recovered - move invitation accept/label helpers into store/invitations so the toast and the settings inbox share them - add the home page zone styles used by the next commit
32 lines
1.4 KiB
TypeScript
32 lines
1.4 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
import { accountApi } from '@/core-bridge/account';
|
|
import { acceptInvitation, invitationLabel } from '@/store/invitations';
|
|
import { useToast } from '@/store/toast';
|
|
|
|
const POLL_MS = 8000;
|
|
|
|
/** 登录后轮询账号邀请,出现新邀请就弹通知,可直接接受 */
|
|
export function useInvitationNotices(username: string | null): void {
|
|
const seen = useRef<Set<string> | null>(null);
|
|
useEffect(() => {
|
|
if (!username) { seen.current = null; return; }
|
|
let stopped = false;
|
|
async function poll() {
|
|
try {
|
|
const rows = await accountApi.invitations();
|
|
if (stopped) return;
|
|
const known = seen.current;
|
|
seen.current = new Set(rows.map((row) => row.channel_id));
|
|
const fresh = known ? rows.filter((row) => !known.has(row.channel_id)) : rows;
|
|
for (const row of fresh) {
|
|
useToast.getState().push({ kind: 'info', text: row.invitedBy ? `${row.invitedBy} 邀请你加入 ${invitationLabel(row)}` : `你收到了加入 ${invitationLabel(row)} 的邀请`,
|
|
action: { label: '接受', run: () => { void acceptInvitation(row); } } });
|
|
}
|
|
} catch { /* 下一轮再试,不打扰用户 */ }
|
|
}
|
|
void poll();
|
|
const timer = window.setInterval(() => { void poll(); }, POLL_MS);
|
|
return () => { stopped = true; window.clearInterval(timer); };
|
|
}, [username]);
|
|
}
|