feat: onboarding optimization, invite links, admin roles, and event board
- Home: hide duplicate create/join buttons when user has no groups - Invite links: /join/group/:id and /join/team/:id pages for one-click joining - Admin: group admins field, ownership transfer, member management toggle - Events: new events collection with RSVP (going/interested/maybe) and comments Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import pb from './pocketbase'
|
||||
import type { Event, EventComment, EventRSVP, RSVPType } from '@/types'
|
||||
|
||||
// 获取群组的活动列表
|
||||
export async function listEvents(groupId: string): Promise<Event[]> {
|
||||
const result = await pb.collection('events').getFullList({
|
||||
filter: `group="${groupId}"`,
|
||||
sort: '-startTime',
|
||||
expand: 'creator',
|
||||
$autoCancel: false
|
||||
})
|
||||
return result as unknown as Event[]
|
||||
}
|
||||
|
||||
// 获取活动详情
|
||||
export async function getEvent(eventId: string): Promise<Event> {
|
||||
return pb.collection('events').getOne(eventId, {
|
||||
expand: 'creator,group',
|
||||
$autoCancel: false
|
||||
}) as unknown as Event
|
||||
}
|
||||
|
||||
// 创建活动
|
||||
export async function createEvent(data: {
|
||||
group: string
|
||||
title: string
|
||||
description?: string
|
||||
location?: string
|
||||
startTime: string
|
||||
endTime?: string
|
||||
maxParticipants?: number
|
||||
}): Promise<Event> {
|
||||
const user = pb.authStore.model
|
||||
if (!user) throw new Error('未登录')
|
||||
|
||||
return pb.collection('events').create({
|
||||
...data,
|
||||
creator: user.id,
|
||||
status: 'upcoming'
|
||||
}) as unknown as Event
|
||||
}
|
||||
|
||||
// 更新活动
|
||||
export async function updateEvent(eventId: string, data: Partial<Event>): Promise<Event> {
|
||||
return pb.collection('events').update(eventId, data) as unknown as Event
|
||||
}
|
||||
|
||||
// 删除活动
|
||||
export async function deleteEvent(eventId: string) {
|
||||
return pb.collection('events').delete(eventId)
|
||||
}
|
||||
|
||||
// 获取活动的评论
|
||||
export async function listEventComments(eventId: string): Promise<EventComment[]> {
|
||||
const result = await pb.collection('event_comments').getFullList({
|
||||
filter: `event="${eventId}"`,
|
||||
sort: '-created',
|
||||
expand: 'user',
|
||||
$autoCancel: false
|
||||
})
|
||||
return result as unknown as EventComment[]
|
||||
}
|
||||
|
||||
// 创建评论
|
||||
export async function createEventComment(eventId: string, content: string): Promise<EventComment> {
|
||||
const user = pb.authStore.model
|
||||
if (!user) throw new Error('未登录')
|
||||
|
||||
return pb.collection('event_comments').create({
|
||||
event: eventId,
|
||||
user: user.id,
|
||||
content
|
||||
}) as unknown as EventComment
|
||||
}
|
||||
|
||||
// 删除评论
|
||||
export async function deleteEventComment(commentId: string) {
|
||||
return pb.collection('event_comments').delete(commentId)
|
||||
}
|
||||
|
||||
// 获取活动的 RSVP 列表
|
||||
export async function listEventRSVPs(eventId: string): Promise<EventRSVP[]> {
|
||||
const result = await pb.collection('event_rsvps').getFullList({
|
||||
filter: `event="${eventId}"`,
|
||||
sort: '-created',
|
||||
expand: 'user',
|
||||
$autoCancel: false
|
||||
})
|
||||
return result as unknown as EventRSVP[]
|
||||
}
|
||||
|
||||
// 创建/更新 RSVP
|
||||
export async function setEventRSVP(
|
||||
eventId: string,
|
||||
type: RSVPType,
|
||||
comment?: string
|
||||
): Promise<EventRSVP> {
|
||||
const user = pb.authStore.model
|
||||
if (!user) throw new Error('未登录')
|
||||
|
||||
// 检查是否已有 RSVP
|
||||
const existing = await pb.collection('event_rsvps').getList(1, 1, {
|
||||
filter: `event="${eventId}" && user="${user.id}"`
|
||||
})
|
||||
|
||||
if (existing.items.length > 0) {
|
||||
return pb.collection('event_rsvps').update(existing.items[0].id, {
|
||||
type,
|
||||
comment
|
||||
}) as unknown as EventRSVP
|
||||
}
|
||||
|
||||
return pb.collection('event_rsvps').create({
|
||||
event: eventId,
|
||||
user: user.id,
|
||||
type,
|
||||
comment
|
||||
}) as unknown as EventRSVP
|
||||
}
|
||||
|
||||
// 取消 RSVP
|
||||
export async function cancelEventRSVP(eventId: string) {
|
||||
const user = pb.authStore.model
|
||||
if (!user) throw new Error('未登录')
|
||||
|
||||
const existing = await pb.collection('event_rsvps').getList(1, 1, {
|
||||
filter: `event="${eventId}" && user="${user.id}"`
|
||||
})
|
||||
|
||||
if (existing.items.length > 0) {
|
||||
return pb.collection('event_rsvps').delete(existing.items[0].id)
|
||||
}
|
||||
}
|
||||
|
||||
// 订阅活动变更
|
||||
export function subscribeEvents(groupId: string, callback: () => void) {
|
||||
return pb.collection('events').subscribe('*', (payload) => {
|
||||
const record = payload.record as any
|
||||
if (record.group === groupId) {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 订阅活动评论变更
|
||||
export function subscribeEventComments(eventId: string, callback: () => void) {
|
||||
return pb.collection('event_comments').subscribe('*', (payload) => {
|
||||
const record = payload.record as any
|
||||
if (record.event === eventId) {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 订阅 RSVP 变更
|
||||
export function subscribeEventRSVPs(eventId: string, callback: () => void) {
|
||||
return pb.collection('event_rsvps').subscribe('*', (payload) => {
|
||||
const record = payload.record as any
|
||||
if (record.event === eventId) {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -189,6 +189,76 @@ export function subscribeGroup(groupId: string, callback: (group: Group) => void
|
||||
})
|
||||
}
|
||||
|
||||
// 转让群主
|
||||
export async function transferGroupOwnership(groupId: string, newOwnerId: string) {
|
||||
const user = pb.authStore.model
|
||||
if (!user) throw new Error('未登录')
|
||||
|
||||
const group = await pb.collection('groups').getOne(groupId)
|
||||
if (group.owner !== user.id) {
|
||||
throw new Error('只有群主可以转让群组')
|
||||
}
|
||||
|
||||
const admins = (group.admins as string[]) || []
|
||||
|
||||
// 将原群主加入 admins,新群主从 admins 中移除
|
||||
const newAdmins = [...new Set([...admins, user.id])].filter(id => id !== newOwnerId)
|
||||
|
||||
return pb.collection('groups').update(groupId, {
|
||||
owner: newOwnerId,
|
||||
admins: newAdmins
|
||||
})
|
||||
}
|
||||
|
||||
// 添加管理员
|
||||
export async function addGroupAdmin(groupId: string, userId: string) {
|
||||
const user = pb.authStore.model
|
||||
if (!user) throw new Error('未登录')
|
||||
|
||||
const group = await pb.collection('groups').getOne(groupId)
|
||||
if (group.owner !== user.id) {
|
||||
throw new Error('只有群主可以设置管理员')
|
||||
}
|
||||
|
||||
const admins = (group.admins as string[]) || []
|
||||
if (admins.includes(userId)) {
|
||||
throw new Error('该用户已是管理员')
|
||||
}
|
||||
|
||||
return pb.collection('groups').update(groupId, {
|
||||
admins: [...admins, userId]
|
||||
})
|
||||
}
|
||||
|
||||
// 移除管理员
|
||||
export async function removeGroupAdmin(groupId: string, userId: string) {
|
||||
const user = pb.authStore.model
|
||||
if (!user) throw new Error('未登录')
|
||||
|
||||
const group = await pb.collection('groups').getOne(groupId)
|
||||
if (group.owner !== user.id) {
|
||||
throw new Error('只有群主可以移除管理员')
|
||||
}
|
||||
|
||||
const admins = (group.admins as string[]) || []
|
||||
return pb.collection('groups').update(groupId, {
|
||||
admins: admins.filter(id => id !== userId)
|
||||
})
|
||||
}
|
||||
|
||||
// 更新全员管理设置
|
||||
export async function updateGroupMemberManage(groupId: string, allow: boolean) {
|
||||
const user = pb.authStore.model
|
||||
if (!user) throw new Error('未登录')
|
||||
|
||||
const group = await pb.collection('groups').getOne(groupId)
|
||||
if (group.owner !== user.id) {
|
||||
throw new Error('只有群主可以修改此设置')
|
||||
}
|
||||
|
||||
return pb.collection('groups').update(groupId, { allowMemberManage: allow })
|
||||
}
|
||||
|
||||
// 订阅加入申请变更
|
||||
export function subscribeJoinRequests(groupId: string, callback: (request: JoinRequest) => void) {
|
||||
return pb.collection('join_requests').subscribe('*', (payload) => {
|
||||
|
||||
Reference in New Issue
Block a user