Skip to content

Commit 8cd39ea

Browse files
alexcarpenteraustincalvelage
authored andcommitted
refactor(mosaic): centralize shared constants
1 parent 7615c3b commit 8cd39ea

27 files changed

Lines changed: 82 additions & 71 deletions
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

‎packages/mosaic/AGENTS.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ Most problems a feature hits already have a shared answer. Find the row for your
5454
| An inline message row; height is undefined until measured | `useHeldMessage`, `useMessageHeight`, `FeedbackBody` (`src/utils/feedback.tsx`) + `src/styles/feedback.styles.ts` |
5555
| Data read from a Clerk resource that should be cached across mounts | A query hook in `src/queries/`, called from the model (`src/queries/use-user-sessions-query.ts`) |
5656
| An item changed in place in an infinite list, kept until the next reload | `replaceInPages` (`src/utils/infinite-pages.ts`), passed to the list's `setData` |
57+
| An organization permission checked with `checkAuthorization` | `ORGANIZATION_PERMISSIONS` (`src/utils/permissions.ts`) |
58+
| A verification code's length or resend cooldown | `CODE_LENGTH`, `CODE_RESEND_SECONDS` (`src/utils/verification.ts`) |
59+
| An avatar or logo file picker and its rejection errors | `AVATAR_UPLOAD`, `AVATAR_REJECTION_ERRORS` (`src/utils/avatar-upload.ts`) |
5760
| Siblings that slide when an item is added or removed | `useLayoutAnimation` (`src/primitives/hooks/`) |
5861
| An input that reformats as the user types without moving the caret | `useFormattedInput` (`src/primitives/hooks/`) |
5962
| An interaction with an async lifecycle or two values that move together | A machine in the controller file, via `setup()` (`src/machine/`) |

‎packages/mosaic/src/components/otp/otp.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { MosaicStyleProps } from '../../props';
77
import { mergeStyleProps, themeProps } from '../../props';
88
import { inputStyles } from '../../styles/input.styles';
99
import { reset } from '../../styles/reset.styles';
10+
import { CODE_LENGTH } from '../../utils/verification';
1011
import { useOptionalFieldControlProps } from '../field/field.context';
1112
import { styles } from './otp.styles';
1213

@@ -52,7 +53,7 @@ function OtpSlots({ status, firstSlotRef }: { status: OtpStatus; firstSlotRef: R
5253
*/
5354
export const Otp = React.forwardRef<HTMLInputElement, OtpProps>(function MosaicOtp(
5455
{
55-
length = 6,
56+
length = CODE_LENGTH,
5657
status: statusProp,
5758
disabled: disabledProp,
5859
required: requiredProp,

‎packages/mosaic/src/features/api-keys/api-keys-table.model.ts‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,19 @@ import { useEffect, useRef } from 'react';
66

77
import { useMosaicEnvironment } from '../../hooks/use-mosaic-environment';
88
import { save } from '../../utils/errors';
9+
import { ORGANIZATION_PERMISSIONS } from '../../utils/permissions';
910
import type { APIKeyRecord } from './api-keys-table.types';
1011
import type { CreateAPIKeyInput } from './create-api-key.controller';
1112

1213
export const PAGE_SIZE = 10;
13-
const READ_PERMISSION = 'org:sys_api_keys:read';
14-
const MANAGE_PERMISSION = 'org:sys_api_keys:manage';
1514

1615
export function useAPIKeysAccess(subject: string | undefined) {
1716
const { isLoaded, session } = useSession();
1817
const settings = useMosaicEnvironment()?.apiKeysSettings;
1918
const isOrganization = subject !== undefined && isOrganizationId(subject);
2019
const can = (permission: string) => !isOrganization || (session?.checkAuthorization({ permission }) ?? false);
21-
const canRead = can(READ_PERMISSION);
22-
const canManage = can(MANAGE_PERMISSION);
20+
const canRead = can(ORGANIZATION_PERMISSIONS.apiKeysRead);
21+
const canManage = can(ORGANIZATION_PERMISSIONS.apiKeysManage);
2322
const isAvailable =
2423
subject !== undefined &&
2524
(isOrganization

‎packages/mosaic/src/features/organization-profile/organization-profile-danger-section/organization-profile-danger-section.model.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useOrganization, useOrganizationList, useSession, useUser } from '@cler
22

33
import { useMosaicEnvironment } from '../../../hooks/use-mosaic-environment';
44
import { useMosaicRouter } from '../../../hooks/use-mosaic-router';
5+
import { ORGANIZATION_PERMISSIONS } from '../../../utils/permissions';
56
import { useOrganizationProfileOptions } from '../organization-profile.provider';
67

78
const organizationListParams = {
@@ -50,7 +51,7 @@ export function useOrganizationProfileDangerSectionModel(): OrganizationProfileD
5051
const canDelete =
5152
Boolean(membership) &&
5253
organization.adminDeleteEnabled &&
53-
(session?.checkAuthorization({ permission: 'org:sys_profile:delete' }) ?? false);
54+
(session?.checkAuthorization({ permission: ORGANIZATION_PERMISSIONS.profileDelete }) ?? false);
5455

5556
return {
5657
status: 'ready',

‎packages/mosaic/src/features/organization-profile/organization-profile-members-panel/invitations-table-tab.model.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { useClerk, useOrganization, useSession } from '@clerk/shared/react';
33
import type { ClerkAPIError, OrganizationInvitationResource } from '@clerk/shared/types';
44

55
import { save } from '../../../utils/errors';
6+
import { ORGANIZATION_PERMISSIONS } from '../../../utils/permissions';
67
import type { MembersRoles } from './members-table-tab.types';
78

89
const PAGE_SIZE = 10;
9-
const MANAGE_PERMISSION = 'org:sys_memberships:manage';
1010

1111
export interface InviteMembersInput {
1212
emailAddresses: string[];
@@ -18,7 +18,7 @@ export interface InviteMembersInput {
1818
export function useInvitationsTableModel() {
1919
const clerk = useClerk();
2020
const { session } = useSession();
21-
const canManage = session?.checkAuthorization({ permission: MANAGE_PERMISSION }) ?? false;
21+
const canManage = session?.checkAuthorization({ permission: ORGANIZATION_PERMISSIONS.membershipsManage }) ?? false;
2222
const { organization, invitations } = useOrganization({
2323
invitations: canManage ? { pageSize: PAGE_SIZE, keepPreviousData: true } : undefined,
2424
});

‎packages/mosaic/src/features/organization-profile/organization-profile-members-panel/members-table-tab.model.ts‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ import { useOrganization, useSession } from '@clerk/shared/react';
33
import type { OrganizationMembershipResource } from '@clerk/shared/types';
44
import { useEffect, useRef } from 'react';
55

6+
import { ORGANIZATION_PERMISSIONS } from '../../../utils/permissions';
67
import type { MembersRoles, OrganizationProfileMember } from './members-table-tab.types';
78

89
const PAGE_SIZE = 10;
9-
const READ_PERMISSION = 'org:sys_memberships:read';
10-
const MANAGE_PERMISSION = 'org:sys_memberships:manage';
1110

1211
export function useMembersTableModel(query: string) {
1312
const { session } = useSession();
14-
const canRead = session?.checkAuthorization({ permission: READ_PERMISSION }) ?? false;
15-
const canManage = session?.checkAuthorization({ permission: MANAGE_PERMISSION }) ?? false;
13+
const canRead = session?.checkAuthorization({ permission: ORGANIZATION_PERMISSIONS.membershipsRead }) ?? false;
14+
const canManage = session?.checkAuthorization({ permission: ORGANIZATION_PERMISSIONS.membershipsManage }) ?? false;
1615
const { organization, memberships } = useOrganization({
1716
memberships: canRead ? { pageSize: PAGE_SIZE, keepPreviousData: true, query: query || undefined } : undefined,
1817
});

‎packages/mosaic/src/features/organization-profile/organization-profile-members-panel/organization-profile-members-panel.model.ts‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,19 @@ import { useOrganization, useSession } from '@clerk/shared/react';
22

33
import { useMosaicEnvironment } from '../../../hooks/use-mosaic-environment';
44
import { useOrganizationRolesQuery } from '../../../queries/use-organization-roles-query';
5+
import { ORGANIZATION_PERMISSIONS } from '../../../utils/permissions';
56
import type { MembersPanelRoles } from './members-table-tab.types';
67

7-
const READ_PERMISSION = 'org:sys_memberships:read';
8-
const MANAGE_PERMISSION = 'org:sys_memberships:manage';
9-
108
export function useMembersPanelAccessModel() {
119
const { isLoaded, organization } = useOrganization();
1210
const { isLoaded: isSessionLoaded, session } = useSession();
1311
const environment = useMosaicEnvironment();
1412
if (!isLoaded || !isSessionLoaded || !environment) {
1513
return { status: 'loading' as const };
1614
}
17-
const canReadMembers = session?.checkAuthorization({ permission: READ_PERMISSION }) ?? false;
18-
const canManageInvitations = session?.checkAuthorization({ permission: MANAGE_PERMISSION }) ?? false;
15+
const canReadMembers = session?.checkAuthorization({ permission: ORGANIZATION_PERMISSIONS.membershipsRead }) ?? false;
16+
const canManageInvitations =
17+
session?.checkAuthorization({ permission: ORGANIZATION_PERMISSIONS.membershipsManage }) ?? false;
1918
const canManageRequests = canManageInvitations && environment.organizationSettings.domains.enabled;
2019
if (!organization || !session || (!canReadMembers && !canManageInvitations)) {
2120
return { status: 'hidden' as const };

‎packages/mosaic/src/features/organization-profile/organization-profile-profile-section/organization-profile-logo-row.view.tsx‎

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,11 @@ import { Pen } from '../../../icons/glyphs/pen';
99
import { X } from '../../../icons/glyphs/x';
1010
import type { LocalizableError } from '../../../localization';
1111
import { useErrorText, useMessages } from '../../../localization';
12-
import type { FileRejection, FileRejectionReason } from '../../../primitives/file-upload';
12+
import type { FileRejection } from '../../../primitives/file-upload';
1313
import { FileUpload } from '../../../primitives/file-upload';
14+
import { AVATAR_REJECTION_ERRORS, AVATAR_UPLOAD } from '../../../utils/avatar-upload';
1415
import { useOrganizationProfileLogoController } from './organization-profile-logo.controller';
1516

16-
const LOGO_MIME_TYPES = 'image/png,image/jpeg,image/gif,image/webp';
17-
const LOGO_MAX_BYTES = 10 * 1000 * 1000;
18-
19-
const REJECTION_ERRORS: Record<FileRejectionReason, LocalizableError> = {
20-
accept: { code: 'avatar_file_type_invalid' },
21-
size: { code: 'avatar_file_size_exceeded' },
22-
overflow: { code: 'avatar_file_count_exceeded' },
23-
};
24-
2517
interface OrganizationProfileLogoRowViewProps {
2618
name: string;
2719
imageUrl?: string;
@@ -62,12 +54,11 @@ export function OrganizationProfileLogoRowView({
6254
<FileUpload.Root
6355
disabled={logo.isPending}
6456
aria-busy={logo.isPending || undefined}
65-
accept={LOGO_MIME_TYPES}
66-
maxSize={LOGO_MAX_BYTES}
57+
{...AVATAR_UPLOAD}
6758
render={<Section.Row />}
6859
onReject={rejections => {
6960
const rejected = rejections[0];
70-
setRejection(rejected ? REJECTION_ERRORS[rejected.reason] : undefined);
61+
setRejection(rejected ? AVATAR_REJECTION_ERRORS[rejected.reason] : undefined);
7162
onReject?.(rejections);
7263
}}
7364
onValueChange={files => {

‎packages/mosaic/src/features/organization-profile/organization-profile-profile-section/organization-profile-profile-section.model.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { OrganizationResource } from '@clerk/shared/types';
33

44
import { useMosaicEnvironment } from '../../../hooks/use-mosaic-environment';
55
import { save, SaveError, UNEXPECTED_ERROR } from '../../../utils/errors';
6+
import { ORGANIZATION_PERMISSIONS } from '../../../utils/permissions';
67
import type { OrganizationProfileProfileSectionViewProps } from './organization-profile-profile-section.view';
78

89
type OrganizationProfileProfileSectionModel =
@@ -25,7 +26,8 @@ export function useOrganizationProfileProfileSectionModel(): OrganizationProfile
2526

2627
const organizationId = organization.id;
2728
const canManage =
28-
Boolean(membership) && (session?.checkAuthorization({ permission: 'org:sys_profile:manage' }) ?? false);
29+
Boolean(membership) &&
30+
(session?.checkAuthorization({ permission: ORGANIZATION_PERMISSIONS.profileManage }) ?? false);
2931
const currentOrganization = (): OrganizationResource => {
3032
const current = clerk.organization;
3133
if (!current || current.id !== organizationId) {

0 commit comments

Comments
 (0)