mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-08 21:15:18 +09:00
- Add transfer dialog with workspace selector and email input
- Support moving projects between workspaces (instant) and
transferring to another user via notification (async)
- Add project transfer notification type with accept/decline flow
- Verify sender ownership and auto-decline stale transfers
- Add audit logging for ownership changes
- Add canTransfer permission for owners and admins
- Use camelCase notification types (workspaceInvite, projectTransfer)
- Use .select("id").single() on all notification status updates
- Debounce email search with stale response rejection
- Use Set-based loading state for concurrent notification actions
- Filter unknown notification types before rendering
- Use theme tokens for all spacing values
75 lines
1.7 KiB
TypeScript
75 lines
1.7 KiB
TypeScript
import { z } from "zod";
|
|
|
|
const MIN_TITLE_LENGTH = 2;
|
|
|
|
export const Title = z
|
|
.string()
|
|
.refine(
|
|
(val) => val.length >= MIN_TITLE_LENGTH,
|
|
`Minimum ${MIN_TITLE_LENGTH} characters required`
|
|
);
|
|
|
|
export const MarketplaceApprovalStatus = z.enum([
|
|
"UNLISTED",
|
|
"PENDING",
|
|
"APPROVED",
|
|
"REJECTED",
|
|
]);
|
|
|
|
export type MarketplaceApprovalStatus = z.infer<
|
|
typeof MarketplaceApprovalStatus
|
|
>;
|
|
|
|
export const workspaceRelations = [
|
|
"viewers",
|
|
"editors",
|
|
"builders",
|
|
"administrators",
|
|
] as const;
|
|
|
|
export type WorkspaceRelation = (typeof workspaceRelations)[number];
|
|
|
|
/** Safest default when relation is unknown — principle of least privilege */
|
|
export const defaultWorkspaceRelation: WorkspaceRelation = "viewers";
|
|
|
|
export const workspaceRelationLabels: Record<WorkspaceRelation, string> = {
|
|
viewers: "Viewer",
|
|
editors: "Editor",
|
|
builders: "Builder",
|
|
administrators: "Admin",
|
|
};
|
|
|
|
// ── Notifications ──
|
|
|
|
export const notificationTypes = [
|
|
"workspaceInvite",
|
|
"projectTransfer",
|
|
] as const;
|
|
|
|
export type NotificationType = (typeof notificationTypes)[number];
|
|
|
|
export const notificationStatuses = [
|
|
"pending",
|
|
"accepted",
|
|
"declined",
|
|
] as const;
|
|
|
|
export type NotificationStatus = (typeof notificationStatuses)[number];
|
|
|
|
/** Notifications expire after this many milliseconds (7 days). */
|
|
export const NOTIFICATION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
|
export const WorkspaceInvitePayload = z.object({
|
|
workspaceId: z.string(),
|
|
relation: z.enum(workspaceRelations),
|
|
});
|
|
|
|
export type WorkspaceInvitePayload = z.infer<typeof WorkspaceInvitePayload>;
|
|
|
|
export const ProjectTransferPayload = z.object({
|
|
projectId: z.string(),
|
|
targetWorkspaceId: z.string().optional(),
|
|
});
|
|
|
|
export type ProjectTransferPayload = z.infer<typeof ProjectTransferPayload>;
|