11b1089126
- usePermissions ORs current user roles + @everyone only (not all server roles) - cache myRolesByServer; load on active server; refresh after self role edit - gate/notify @everyone and @channel; plain @username push; special mention UI - refresh FEATURE_PARITY (DMs exist; drop stale critical gaps) - README production deploy notes dumpster.service - unit tests for permission bits and broadcast mention tokens
183 lines
5.1 KiB
TypeScript
183 lines
5.1 KiB
TypeScript
import { create } from 'zustand';
|
|
import { api } from '../lib/api.ts';
|
|
|
|
export interface Role {
|
|
id: string;
|
|
server_id: string;
|
|
name: string;
|
|
color: string;
|
|
permissions: number;
|
|
position: number;
|
|
is_default: boolean;
|
|
}
|
|
|
|
export interface CreateRoleData {
|
|
name: string;
|
|
color: string;
|
|
permissions: number;
|
|
position: number;
|
|
}
|
|
|
|
export interface UpdateRoleData {
|
|
name?: string;
|
|
color?: string;
|
|
permissions?: number;
|
|
position?: number;
|
|
}
|
|
|
|
interface RoleState {
|
|
roles: Role[];
|
|
/** Assigned roles for the current user, keyed by server id. Does not include @everyone. */
|
|
myRolesByServer: Record<string, Role[]>;
|
|
loading: boolean;
|
|
error: string | null;
|
|
|
|
fetchRoles: (serverId: string) => Promise<void>;
|
|
fetchMyRoles: (serverId: string, userId: string) => Promise<void>;
|
|
createRole: (serverId: string, data: CreateRoleData) => Promise<Role>;
|
|
updateRole: (serverId: string, roleId: string, data: UpdateRoleData) => Promise<Role>;
|
|
deleteRole: (serverId: string, roleId: string) => Promise<void>;
|
|
setMemberRoles: (serverId: string, userId: string, roleIds: string[]) => Promise<void>;
|
|
getMemberRoles: (serverId: string, userId: string) => Promise<Role[]>;
|
|
}
|
|
|
|
export const useRoleStore = create<RoleState>((set) => ({
|
|
roles: [],
|
|
myRolesByServer: {},
|
|
loading: false,
|
|
error: null,
|
|
|
|
fetchRoles: async (serverId) => {
|
|
set({ loading: true, error: null });
|
|
try {
|
|
const roles = await api.get<Role[]>(`/servers/${serverId}/roles`);
|
|
set((state) => ({
|
|
// Keep roles from other servers if mixed; replace same-server entries.
|
|
roles: [
|
|
...state.roles.filter((r) => r.server_id !== serverId),
|
|
...(Array.isArray(roles) ? roles : []),
|
|
].sort((a, b) => b.position - a.position),
|
|
loading: false,
|
|
}));
|
|
} catch (error) {
|
|
set({
|
|
loading: false,
|
|
error: error instanceof Error ? error.message : 'Failed to fetch roles',
|
|
});
|
|
}
|
|
},
|
|
|
|
fetchMyRoles: async (serverId, userId) => {
|
|
try {
|
|
const roles = await api.get<Role[]>(`/servers/${serverId}/members/${userId}/roles`);
|
|
set((state) => ({
|
|
myRolesByServer: {
|
|
...state.myRolesByServer,
|
|
[serverId]: Array.isArray(roles) ? roles : [],
|
|
},
|
|
}));
|
|
} catch {
|
|
set((state) => ({
|
|
myRolesByServer: {
|
|
...state.myRolesByServer,
|
|
[serverId]: [],
|
|
},
|
|
}));
|
|
}
|
|
},
|
|
|
|
createRole: async (serverId, data) => {
|
|
set({ loading: true, error: null });
|
|
try {
|
|
const role = await api.post<Role>(`/servers/${serverId}/roles`, data);
|
|
set((state) => ({
|
|
roles: [...state.roles, role].sort((a, b) => b.position - a.position),
|
|
loading: false,
|
|
}));
|
|
return role;
|
|
} catch (error) {
|
|
set({
|
|
loading: false,
|
|
error: error instanceof Error ? error.message : 'Failed to create role',
|
|
});
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
updateRole: async (serverId, roleId, data) => {
|
|
set({ loading: true, error: null });
|
|
try {
|
|
const role = await api.patch<Role>(`/servers/${serverId}/roles/${roleId}`, data);
|
|
set((state) => {
|
|
const myRoles = state.myRolesByServer[serverId];
|
|
const nextMy = myRoles
|
|
? myRoles.map((r) => (r.id === roleId ? role : r))
|
|
: myRoles;
|
|
return {
|
|
roles: state.roles.map((r) => (r.id === roleId ? role : r)).sort((a, b) => b.position - a.position),
|
|
myRolesByServer: nextMy
|
|
? { ...state.myRolesByServer, [serverId]: nextMy }
|
|
: state.myRolesByServer,
|
|
loading: false,
|
|
};
|
|
});
|
|
return role;
|
|
} catch (error) {
|
|
set({
|
|
loading: false,
|
|
error: error instanceof Error ? error.message : 'Failed to update role',
|
|
});
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
deleteRole: async (serverId, roleId) => {
|
|
set({ loading: true, error: null });
|
|
try {
|
|
await api.delete(`/servers/${serverId}/roles/${roleId}`);
|
|
set((state) => {
|
|
const myRoles = state.myRolesByServer[serverId];
|
|
return {
|
|
roles: state.roles.filter((r) => r.id !== roleId),
|
|
myRolesByServer: myRoles
|
|
? {
|
|
...state.myRolesByServer,
|
|
[serverId]: myRoles.filter((r) => r.id !== roleId),
|
|
}
|
|
: state.myRolesByServer,
|
|
loading: false,
|
|
};
|
|
});
|
|
} catch (error) {
|
|
set({
|
|
loading: false,
|
|
error: error instanceof Error ? error.message : 'Failed to delete role',
|
|
});
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
setMemberRoles: async (serverId, userId, roleIds) => {
|
|
set({ error: null });
|
|
try {
|
|
await api.put(`/servers/${serverId}/members/${userId}/roles`, { role_ids: roleIds });
|
|
} catch (error) {
|
|
set({
|
|
error: error instanceof Error ? error.message : 'Failed to set member roles',
|
|
});
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
getMemberRoles: async (serverId, userId) => {
|
|
try {
|
|
return await api.get<Role[]>(`/servers/${serverId}/members/${userId}/roles`);
|
|
} catch (error) {
|
|
set({
|
|
error: error instanceof Error ? error.message : 'Failed to get member roles',
|
|
});
|
|
return [];
|
|
}
|
|
},
|
|
}));
|