feat(ui): add Button, Modal, Spinner, Toast, and Tooltip components with styles
All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
- Implemented Button component with various props for customization. - Created Modal component with header, content, and footer subcomponents. - Added Spinner component for loading indicators. - Developed Toast component for displaying notifications. - Introduced Tooltip component for contextual hints with keyboard shortcuts. - Added corresponding CSS modules for styling each component. - Updated index file to export new components. - Configured TypeScript settings for the UI package.
This commit is contained in:
@@ -77,6 +77,12 @@ export const update = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const role = await ctx.db.get(args.id);
|
||||
if (!role) throw new Error("Role not found");
|
||||
// Owner is frozen — we can't let a client rename/recolour it
|
||||
// or strip its permissions, since that would defeat the
|
||||
// assign/unassign guards in one shot.
|
||||
if (role.name === "Owner") {
|
||||
throw new Error("The Owner role can't be edited.");
|
||||
}
|
||||
|
||||
const { id, ...fields } = args;
|
||||
const updates: Record<string, unknown> = {};
|
||||
@@ -92,6 +98,33 @@ export const update = mutation({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Batch-reorder roles by passing a list of `{id, position}` pairs.
|
||||
* Used by the Roles & Permissions settings surface after a drag-
|
||||
* drop drop. Owner and @everyone are refused so their positions
|
||||
* stay pinned at the natural extremes of the list.
|
||||
*/
|
||||
export const reorder = mutation({
|
||||
args: {
|
||||
updates: v.array(
|
||||
v.object({
|
||||
id: v.id("roles"),
|
||||
position: v.number(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
for (const u of args.updates) {
|
||||
const role = await ctx.db.get(u.id);
|
||||
if (!role) continue;
|
||||
if (role.name === "Owner" || role.name === "@everyone") continue;
|
||||
await ctx.db.patch(u.id, { position: u.position });
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Delete role
|
||||
export const remove = mutation({
|
||||
args: { id: v.id("roles") },
|
||||
@@ -99,6 +132,12 @@ export const remove = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const role = await ctx.db.get(args.id);
|
||||
if (!role) throw new Error("Role not found");
|
||||
if (role.name === "Owner") {
|
||||
throw new Error("The Owner role can't be deleted.");
|
||||
}
|
||||
if (role.name === "@everyone") {
|
||||
throw new Error("The @everyone role can't be deleted.");
|
||||
}
|
||||
|
||||
const assignments = await ctx.db
|
||||
.query("userRoles")
|
||||
@@ -139,6 +178,16 @@ export const assign = mutation({
|
||||
},
|
||||
returns: v.object({ success: v.boolean() }),
|
||||
handler: async (ctx, args) => {
|
||||
// Owner is immutable — it's granted once during first-user
|
||||
// bootstrap (convex/auth.ts) and the app never reassigns it. The
|
||||
// UI already hides it from the ManageRoles checkbox list, but we
|
||||
// also reject it server-side so a crafted client can't sneak it
|
||||
// onto another user.
|
||||
const role = await ctx.db.get(args.roleId);
|
||||
if (role?.name === "Owner") {
|
||||
throw new Error("The Owner role can't be assigned.");
|
||||
}
|
||||
|
||||
const existing = await ctx.db
|
||||
.query("userRoles")
|
||||
.withIndex("by_user_and_role", (q) =>
|
||||
@@ -165,6 +214,13 @@ export const unassign = mutation({
|
||||
},
|
||||
returns: v.object({ success: v.boolean() }),
|
||||
handler: async (ctx, args) => {
|
||||
// Owner is immutable — see `assign` above. Removing it would
|
||||
// leave the server without a permission-bearing admin.
|
||||
const role = await ctx.db.get(args.roleId);
|
||||
if (role?.name === "Owner") {
|
||||
throw new Error("The Owner role can't be removed.");
|
||||
}
|
||||
|
||||
const existing = await ctx.db
|
||||
.query("userRoles")
|
||||
.withIndex("by_user_and_role", (q) =>
|
||||
|
||||
Reference in New Issue
Block a user