@studiocubics/cms 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.turbo/turbo-build.log +7 -0
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +15 -0
  4. package/eslint.config.js +21 -0
  5. package/package.json +79 -0
  6. package/rollup.config.js +48 -0
  7. package/src/clerk/_index.ts +6 -0
  8. package/src/clerk/actions/_index.ts +2 -0
  9. package/src/clerk/actions/invitations.ts +78 -0
  10. package/src/clerk/actions/systemUsers.ts +94 -0
  11. package/src/clerk/auth.ts +34 -0
  12. package/src/clerk/clerk.d.ts +105 -0
  13. package/src/clerk/hasPermission.ts +96 -0
  14. package/src/clerk/rbacConfig.ts +68 -0
  15. package/src/clerk/schemas/_index.ts +1 -0
  16. package/src/clerk/schemas/invitation.ts +17 -0
  17. package/src/clerk/schemas/systemUser.ts +16 -0
  18. package/src/clerk/toClientSafeUser.ts +77 -0
  19. package/src/constants/_index.ts +2 -0
  20. package/src/constants/defaults.tsx +62 -0
  21. package/src/constants/pageLimits.ts +2 -0
  22. package/src/declaration.d.ts +5 -0
  23. package/src/index.ts +5 -0
  24. package/src/providers/CMSRootProviders.tsx +13 -0
  25. package/src/providers/_index.ts +1 -0
  26. package/src/routes.d.ts +96 -0
  27. package/src/ui/Inputs/ThemedMonacoEditor/ThemedMonacoEditor.module.css +4 -0
  28. package/src/ui/Inputs/ThemedMonacoEditor/ThemedMonacoEditor.tsx +16 -0
  29. package/src/ui/Inputs/_index.ts +1 -0
  30. package/src/ui/Layout/CMSSecurityLayout.tsx +27 -0
  31. package/src/ui/Layout/CMSSidebar/CMSSidebar.tsx +39 -0
  32. package/src/ui/Layout/CMSSidebar/CMSSidebarBody.tsx +43 -0
  33. package/src/ui/Layout/CMSSidebar/CMSSidebarFooter/CMSSidebarFooter.module.css +7 -0
  34. package/src/ui/Layout/CMSSidebar/CMSSidebarFooter/CMSSidebarFooter.tsx +59 -0
  35. package/src/ui/Layout/CMSSidebar/CMSSidebarHeader/CMSSidebarHeader.module.css +44 -0
  36. package/src/ui/Layout/CMSSidebar/CMSSidebarHeader/CMSSidebarHeader.tsx +30 -0
  37. package/src/ui/Layout/CMSSidebar/_index.ts +4 -0
  38. package/src/ui/Layout/_index.ts +2 -0
  39. package/src/ui/System/Auth/SignIn/SignIn.module.css +50 -0
  40. package/src/ui/System/Auth/SignIn/SignIn.tsx +79 -0
  41. package/src/ui/System/Auth/SignIn/_index.ts +2 -0
  42. package/src/ui/System/Auth/SignIn/useSignInForm.tsx +42 -0
  43. package/src/ui/System/Auth/SignUp/SignUp.module.css +48 -0
  44. package/src/ui/System/Auth/SignUp/SignUp.tsx +138 -0
  45. package/src/ui/System/Auth/SignUp/_index.ts +2 -0
  46. package/src/ui/System/Auth/SignUp/useSignUpForm.tsx +54 -0
  47. package/src/ui/System/Auth/_index.ts +2 -0
  48. package/src/ui/System/Invitations/InvitationList.tsx +9 -0
  49. package/src/ui/System/Invitations/InvitationListActions.tsx +167 -0
  50. package/src/ui/System/Invitations/InvitationListCard.tsx +79 -0
  51. package/src/ui/System/Invitations/InvitationListPage.tsx +32 -0
  52. package/src/ui/System/Invitations/InvitationListPagination.tsx +19 -0
  53. package/src/ui/System/Invitations/_index.ts +5 -0
  54. package/src/ui/System/Permissions/RoleListCard.tsx +33 -0
  55. package/src/ui/System/Permissions/RolePermissionsPage.tsx +18 -0
  56. package/src/ui/System/Permissions/RolePermissionsTable.tsx +36 -0
  57. package/src/ui/System/Permissions/_index.ts +3 -0
  58. package/src/ui/System/SystemUser/CurrentSystemUserButton/CurrentSystemUserButton.module.css +5 -0
  59. package/src/ui/System/SystemUser/CurrentSystemUserButton/CurrentSystemUserButton.tsx +102 -0
  60. package/src/ui/System/SystemUser/CurrentSystemUserPage.tsx +12 -0
  61. package/src/ui/System/SystemUser/SystemUserActions.tsx +45 -0
  62. package/src/ui/System/SystemUser/SystemUserDetails/SystemUserDetails.module.css +6 -0
  63. package/src/ui/System/SystemUser/SystemUserDetails/SystemUserDetails.tsx +71 -0
  64. package/src/ui/System/SystemUser/SystemUserDetailsForm/SystemUserDetailsForm.module.css +7 -0
  65. package/src/ui/System/SystemUser/SystemUserDetailsForm/SystemUserDetailsForm.tsx +114 -0
  66. package/src/ui/System/SystemUser/SystemUserList.tsx +18 -0
  67. package/src/ui/System/SystemUser/SystemUserListActions.tsx +17 -0
  68. package/src/ui/System/SystemUser/SystemUserListCard.tsx +85 -0
  69. package/src/ui/System/SystemUser/SystemUserListPage.tsx +33 -0
  70. package/src/ui/System/SystemUser/SystemUserListPagination.tsx +19 -0
  71. package/src/ui/System/SystemUser/SystemUserPage.tsx +30 -0
  72. package/src/ui/System/SystemUser/SystemUserPageContent.tsx +54 -0
  73. package/src/ui/System/SystemUser/SystemUserRole/SystemUserRole.module.css +17 -0
  74. package/src/ui/System/SystemUser/SystemUserRole/SystemUserRole.tsx +64 -0
  75. package/src/ui/System/SystemUser/SystemUserRoleForm/SystemUserRoleForm.tsx +51 -0
  76. package/src/ui/System/SystemUser/SystemUserTimestamps.tsx +56 -0
  77. package/src/ui/System/SystemUser/_index.ts +14 -0
  78. package/src/ui/System/WelcomePage/WelcomePage.module.css +18 -0
  79. package/src/ui/System/WelcomePage/WelcomePage.tsx +43 -0
  80. package/src/ui/System/_index.ts +6 -0
  81. package/src/ui/System/types.ts +7 -0
  82. package/src/ui/_index.ts +3 -0
  83. package/src/utils/_index.ts +1 -0
  84. package/src/utils/proxyFunctions.ts +37 -0
  85. package/tsconfig.json +32 -0
@@ -0,0 +1,54 @@
1
+ import { SectionWrapper } from "@studiocubics/ui";
2
+ import { SystemUserDetails } from "./SystemUserDetails/SystemUserDetails";
3
+ import { SystemUserRole } from "./SystemUserRole/SystemUserRole";
4
+ import { SystemUserTimestamps } from "./SystemUserTimestamps";
5
+ import Link from "next/link";
6
+ import type { User } from "@clerk/nextjs/server";
7
+ import type { Role } from "../../../clerk/rbacConfig";
8
+
9
+ export function SystemUserPageContent({
10
+ allowEdit,
11
+ user,
12
+ }: {
13
+ allowEdit: boolean;
14
+ user: User;
15
+ }) {
16
+ return (
17
+ <>
18
+ <SectionWrapper title={"Account Details"} noBorders>
19
+ <SystemUserDetails
20
+ editable={allowEdit}
21
+ id={user.id}
22
+ fullName={user.fullName}
23
+ firstName={user.firstName}
24
+ lastName={user.lastName}
25
+ imageUrl={user.imageUrl}
26
+ emailAddress={user.primaryEmailAddress?.emailAddress}
27
+ />
28
+ </SectionWrapper>
29
+ <SectionWrapper title={"Account Timestamps"}>
30
+ <SystemUserTimestamps
31
+ createdAt={user.createdAt}
32
+ updatedAt={user.updatedAt}
33
+ lastSignInAt={user.lastSignInAt}
34
+ />
35
+ </SectionWrapper>
36
+ <SectionWrapper title={"Assigned Role"}>
37
+ <SystemUserRole
38
+ role={user.publicMetadata.role as Role}
39
+ editable={allowEdit}
40
+ />
41
+ </SectionWrapper>
42
+ <SectionWrapper
43
+ title={"Active Sessions"}
44
+ subtitle={
45
+ <p style={{ fontSize: "var(--fs-body2)" }}>
46
+ <Link href={`/dashboard/security/systemUsers/${user.id}/sessions`}>
47
+ View All Sessions
48
+ </Link>
49
+ </p>
50
+ }
51
+ ></SectionWrapper>
52
+ </>
53
+ );
54
+ }
@@ -0,0 +1,17 @@
1
+ .root {
2
+ display: flex;
3
+ justify-content: space-between;
4
+ align-items: flex-start;
5
+ width: 100%;
6
+ }
7
+ .main {
8
+ display: flex;
9
+ flex: 1;
10
+ flex-direction: column;
11
+ gap: var(--spacing-gap-2);
12
+ align-items: flex-start;
13
+ p {
14
+ font-size: var(--fs-body2);
15
+ color: var(--color-on-background-faint);
16
+ }
17
+ }
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ import { toCapitalised } from "@studiocubics/utils";
4
+ import { Edit, User } from "lucide-react";
5
+ import { Button, Tooltip, TransitionAnimation } from "@studiocubics/ui";
6
+ import Link from "next/link";
7
+ import { useDisclosure } from "@studiocubics/hooks";
8
+ import styles from "./SystemUserRole.module.css";
9
+ import { RBAC_CONFIG, type Role } from "../../../../clerk/rbacConfig";
10
+ import type { Route } from "next";
11
+ import { SystemUserRoleForm } from "../SystemUserRoleForm/SystemUserRoleForm";
12
+
13
+ export interface SystemUserRoleProps {
14
+ role: Role;
15
+ editable?: boolean;
16
+ }
17
+
18
+ export function SystemUserRole({ role, editable }: SystemUserRoleProps) {
19
+ const roleDetails = RBAC_CONFIG[role];
20
+ const { open, handleOpen, handleClose } = useDisclosure();
21
+ if (!roleDetails) return null;
22
+
23
+ return (
24
+ <>
25
+ <TransitionAnimation
26
+ in={!open}
27
+ transformOrigin={"top"}
28
+ mountOnly
29
+ unmountOnExit
30
+ >
31
+ <div className={styles.root}>
32
+ <div className={styles.main}>
33
+ <Tooltip renderArrow title={"View resource permissions"}>
34
+ <Button
35
+ as={Link}
36
+ variant="contained"
37
+ color="primary"
38
+ startIcon={<User size={16} />}
39
+ size="sm"
40
+ href={`/dashboard/security/permissions/${role}` as Route}
41
+ >
42
+ {toCapitalised(role)}
43
+ </Button>
44
+ </Tooltip>
45
+ <p>{roleDetails.desc}</p>
46
+ </div>
47
+ {editable && (
48
+ <Button size="sm" startIcon={<Edit />} onClick={handleOpen}>
49
+ Edit Role
50
+ </Button>
51
+ )}
52
+ </div>
53
+ </TransitionAnimation>
54
+ <TransitionAnimation
55
+ in={open}
56
+ transformOrigin={"top right"}
57
+ mountOnly
58
+ unmountOnExit
59
+ >
60
+ <SystemUserRoleForm role={role} onClose={handleClose} />
61
+ </TransitionAnimation>
62
+ </>
63
+ );
64
+ }
@@ -0,0 +1,51 @@
1
+ "use client";
2
+
3
+ import {
4
+ type CardProps,
5
+ Card,
6
+ ConfirmationForm,
7
+ Select,
8
+ } from "@studiocubics/ui";
9
+ import { initialiseForm, toCapitalised } from "@studiocubics/utils";
10
+ import { type Role, roles } from "../../../../clerk/rbacConfig";
11
+ import { useActionState } from "react";
12
+ import { systemUserRoleUpdateAction } from "../../../../clerk/_index";
13
+
14
+ const initialSystemUserRoleUpdateState = initialiseForm("role");
15
+
16
+ export type SystemUserRoleUpdateState = typeof initialSystemUserRoleUpdateState;
17
+
18
+ export function SystemUserRoleForm({
19
+ role,
20
+ onClose,
21
+ ...rest
22
+ }: { role: Role; onClose(): void } & CardProps) {
23
+ const [state, action, pending] = useActionState(
24
+ systemUserRoleUpdateAction.bind(null, "hello"),
25
+ initialSystemUserRoleUpdateState,
26
+ );
27
+ return (
28
+ <Card {...rest} fullWidth>
29
+ <ConfirmationForm
30
+ variant="danger"
31
+ confirmText="Change Role"
32
+ action={action}
33
+ formTitle="Edit Role"
34
+ onCancel={onClose}
35
+ >
36
+ <Select
37
+ label="Current role"
38
+ error={state.fieldErrors?.role}
39
+ defaultValue={role}
40
+ disabled={pending}
41
+ >
42
+ {roles.map((r) => (
43
+ <option key={r} value={r}>
44
+ {toCapitalised(r)}
45
+ </option>
46
+ ))}
47
+ </Select>
48
+ </ConfirmationForm>
49
+ </Card>
50
+ );
51
+ }
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ import { useUser } from "@clerk/nextjs";
4
+ import type { User } from "@clerk/nextjs/server";
5
+ import { LabeledValue, Skeleton } from "@studiocubics/ui";
6
+
7
+ export function CurrentSystemUserTimestamps() {
8
+ const { user, isLoaded } = useUser();
9
+ // if (!user) return null;
10
+ return (
11
+ <SystemUserTimestamps
12
+ isLoaded={isLoaded}
13
+ createdAt={user?.createdAt}
14
+ updatedAt={user?.updatedAt}
15
+ lastSignInAt={user?.lastSignInAt}
16
+ />
17
+ );
18
+ }
19
+ export interface SystemUserTimestampsProps {
20
+ createdAt?: User["createdAt"] | NonNullable<ClerkClientUser>["createdAt"];
21
+ updatedAt?: User["updatedAt"] | NonNullable<ClerkClientUser>["updatedAt"];
22
+ lastSignInAt?:
23
+ | User["lastSignInAt"]
24
+ | NonNullable<ClerkClientUser>["lastSignInAt"];
25
+ isLoaded?: boolean;
26
+ }
27
+
28
+ export function SystemUserTimestamps(props: SystemUserTimestampsProps) {
29
+ const { createdAt, updatedAt, lastSignInAt, isLoaded = true } = props;
30
+ const timestamps = [
31
+ { stamp: "Created at", time: createdAt },
32
+ { stamp: "Updated", time: updatedAt },
33
+ { stamp: "Last Sign in", time: lastSignInAt },
34
+ ];
35
+
36
+ return timestamps.map((timestamp) => {
37
+ let timestampTime =
38
+ timestamp.time && typeof timestamp.time == "number"
39
+ ? new Date(timestamp.time)
40
+ : timestamp.time;
41
+
42
+ return (
43
+ <LabeledValue key={timestamp.stamp} label={timestamp.stamp}>
44
+ {isLoaded ? (
45
+ !timestamp.time ? (
46
+ "-"
47
+ ) : (
48
+ timestampTime?.toLocaleString()
49
+ )
50
+ ) : (
51
+ <Skeleton width={150} height={24} />
52
+ )}
53
+ </LabeledValue>
54
+ );
55
+ });
56
+ }
@@ -0,0 +1,14 @@
1
+ export * from "./CurrentSystemUserButton/CurrentSystemUserButton";
2
+ export * from "./CurrentSystemUserPage";
3
+ export * from "./SystemUserActions";
4
+ export * from "./SystemUserDetails/SystemUserDetails";
5
+ export * from "./SystemUserDetailsForm/SystemUserDetailsForm";
6
+ export * from "./SystemUserList";
7
+ export * from "./SystemUserListActions";
8
+ export * from "./SystemUserListCard";
9
+ export * from "./SystemUserListPage";
10
+ export * from "./SystemUserListPagination";
11
+ export * from "./SystemUserPage";
12
+ export * from "./SystemUserPageContent";
13
+ export * from "./SystemUserRole/SystemUserRole";
14
+ export * from "./SystemUserTimestamps";
@@ -0,0 +1,18 @@
1
+ .root {
2
+ display: flex;
3
+ flex-direction: column;
4
+ justify-content: center;
5
+ align-items: center;
6
+ gap: var(--spacing-gap-4);
7
+ width: 100dvw;
8
+ height: 100dvh;
9
+ }
10
+ .desc {
11
+ max-width: 65ch;
12
+ text-align: center;
13
+ font-size: var(--fs-body);
14
+ color: var(--color-on-background-faint);
15
+ }
16
+ .catcall {
17
+ font-size: var(--fs-body2);
18
+ }
@@ -0,0 +1,43 @@
1
+ import { Button, CubicsUILogo, PoweredByBanner } from "@studiocubics/ui";
2
+ import styles from "./WelcomePage.module.css";
3
+ import Link from "next/link";
4
+ import { LayoutDashboardIcon, LogIn } from "lucide-react";
5
+ import { auth } from "../../../clerk/auth";
6
+ export async function WelcomePage() {
7
+ const { userId } = await auth();
8
+ const loggedOutUser = (
9
+ <Link href={"/auth/signIn/"}>
10
+ <Button variant="contained" endIcon={<LogIn />}>
11
+ Get Started
12
+ </Button>
13
+ </Link>
14
+ );
15
+ const loggedInUser = (
16
+ <Link href={"/dashboard"}>
17
+ <Button variant="contained" endIcon={<LayoutDashboardIcon />}>
18
+ Go to dashboard
19
+ </Button>
20
+ </Link>
21
+ );
22
+ return (
23
+ <div className={styles.root}>
24
+ <CubicsUILogo width={"10rem"} height={"auto"} onlyFavicon />
25
+ <PoweredByBanner size={"md"} />
26
+ <h2>Welcome to Cubics CMS!</h2>
27
+ <p className={styles.desc}>
28
+ A powerful and intuitive platform to manage your content effortlessly.
29
+ Streamline updates, enhance collaboration, and take full control of your
30
+ digital presence.
31
+ </p>
32
+ {userId ? loggedInUser : loggedOutUser}
33
+ {!userId && (
34
+ <p className={styles.catcall}>
35
+ Dont have an account?&nbsp;
36
+ {/* TODO add request account from admin flow.
37
+ //@ts-ignore */}
38
+ <Link href="/auth/requestAccount">Request from an admin</Link>
39
+ </p>
40
+ )}
41
+ </div>
42
+ );
43
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./Auth/_index";
2
+ export * from "./Invitations/_index";
3
+ export * from "./Permissions/_index";
4
+ export * from "./SystemUser/_index";
5
+ export * from "./WelcomePage/WelcomePage";
6
+ export * from "./types";
@@ -0,0 +1,7 @@
1
+ import type { TabProps } from "@studiocubics/ui";
2
+ import type { AppRoutes } from "../../routes";
3
+
4
+ export type SecurityPageProps<T extends AppRoutes = "/dashboard/security"> =
5
+ PageProps<T> & {
6
+ securityLinks: TabProps[];
7
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./Inputs/_index";
2
+ export * from "./Layout/_index";
3
+ export * from "./System/_index";
@@ -0,0 +1 @@
1
+ export * from "./proxyFunctions";
@@ -0,0 +1,37 @@
1
+ import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
2
+ import { NextResponse } from "next/server";
3
+
4
+ const isPublicRoute = createRouteMatcher(["/", "/auth(.*)"]);
5
+
6
+ export const cmsMiddleware = clerkMiddleware(async (auth, req) => {
7
+ const { userId, sessionClaims, redirectToSignIn } = await auth();
8
+
9
+ // If the user isn't signed in and the route is private, redirect to sign-in
10
+ if (!userId && !isPublicRoute(req)) {
11
+ return redirectToSignIn({ returnBackUrl: req.url });
12
+ }
13
+
14
+ // Catch users who do not have `onboardingComplete: true` in their publicMetadata
15
+ // Redirect them to the /auth/onboarding route to complete onboarding
16
+ if (
17
+ userId &&
18
+ !sessionClaims?.metadata?.onboardingComplete &&
19
+ req.nextUrl.pathname !== "/auth/onboarding"
20
+ ) {
21
+ const onboardingUrl = new URL("/auth/onboarding", req.url);
22
+ return NextResponse.redirect(onboardingUrl);
23
+ }
24
+
25
+ // If the user is logged in and the route is protected, let them view.
26
+ if (userId && !isPublicRoute(req)) {
27
+ return NextResponse.next();
28
+ }
29
+ });
30
+ export const cmsConfig = {
31
+ matcher: [
32
+ // Skip Next.js internals and all static files, unless found in search params
33
+ "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
34
+ // Always run for API routes
35
+ "/(api|trpc)(.*)",
36
+ ],
37
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "resolveJsonModule": true,
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+
12
+ /* Bundler mode */
13
+ "moduleResolution": "bundler",
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+ // Output
19
+ "declaration": true,
20
+ "outDir": "./dist",
21
+ "rootDir": "./src",
22
+
23
+ /* Linting */
24
+ "strict": true,
25
+ "noUnusedLocals": true,
26
+ "noUnusedParameters": true,
27
+ "erasableSyntaxOnly": true,
28
+ "noFallthroughCasesInSwitch": true,
29
+ "noUncheckedSideEffectImports": true
30
+ },
31
+ "include": ["./src", "src/clerk/clerk.d.ts"]
32
+ }