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

- 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:
Bryan1029384756
2026-04-14 09:02:14 -05:00
parent 9ef839938e
commit b7a4cf4ce8
376 changed files with 52619 additions and 167641 deletions

View File

@@ -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) =>