diff --git a/apps/console/src/app.tsx b/apps/console/src/app.tsx index 6f5852c6..4614cc0b 100644 --- a/apps/console/src/app.tsx +++ b/apps/console/src/app.tsx @@ -38,6 +38,7 @@ import { Suspense } from "react"; import { FullScreenLoader } from "./components/common/FullScreenLoader"; import { OrgPage } from "./pages/projects/OrgPage"; import { useCurrentOrganization } from "./lib/hooks/useCurrentOrganization"; +import { WaitlistWrapper } from "~/pages/WaitlistWrapper"; initSuperTokens(); @@ -101,7 +102,9 @@ export function App() { path="/onboarding" element={ - + + + } /> @@ -112,7 +115,9 @@ export function App() { element={ }> - + + + } @@ -131,7 +136,9 @@ export function App() { - + + + diff --git a/apps/console/src/components/layout/OrgSubHeader.tsx b/apps/console/src/components/layout/OrgSubHeader.tsx index f664280e..1f34f7d4 100644 --- a/apps/console/src/components/layout/OrgSubHeader.tsx +++ b/apps/console/src/components/layout/OrgSubHeader.tsx @@ -8,9 +8,9 @@ const isActive = (href: string) => { export const OrgSubHeader = () => { const navigate = useNavigate(); - const { organization } = useCurrentOrganization(); + const { organization, waitlisted } = useCurrentOrganization(); - if (!organization) { + if (!organization || waitlisted) { return; } diff --git a/apps/console/src/graphql/definitions/queries/organizations.tsx b/apps/console/src/graphql/definitions/queries/organizations.tsx index b099650b..520d645e 100644 --- a/apps/console/src/graphql/definitions/queries/organizations.tsx +++ b/apps/console/src/graphql/definitions/queries/organizations.tsx @@ -18,6 +18,7 @@ export const GET_ORGANIZATION = graphql(/* GraphQL */ ` organization(data: $data) { id name + waitlisted members @include(if: $includeMembers) { id role diff --git a/apps/console/src/lib/hooks/useCurrentOrganization.tsx b/apps/console/src/lib/hooks/useCurrentOrganization.tsx index 7a3502b1..8d03f965 100644 --- a/apps/console/src/lib/hooks/useCurrentOrganization.tsx +++ b/apps/console/src/lib/hooks/useCurrentOrganization.tsx @@ -59,5 +59,6 @@ export const useCurrentOrganization = ({ error, currentOrgId, selectOrg, + waitlisted: data?.organization?.waitlisted, }; }; diff --git a/apps/console/src/lib/hooks/useWaitlist.ts b/apps/console/src/lib/hooks/useWaitlist.ts new file mode 100644 index 00000000..adccc66e --- /dev/null +++ b/apps/console/src/lib/hooks/useWaitlist.ts @@ -0,0 +1,14 @@ +import { useMemo } from "react"; +import { useCurrentOrganization } from "./useCurrentOrganization"; + +export const useWaitlist = () => { + const { isSuccess, currentOrgId, waitlisted } = useCurrentOrganization(); + + const shouldRenderWaitlistNotice = useMemo(() => { + return isSuccess && currentOrgId && waitlisted; + }, [isSuccess, currentOrgId, waitlisted]); + + return { + shouldRenderWaitlistNotice, + }; +}; diff --git a/apps/console/src/pages/WaitlistWrapper.tsx b/apps/console/src/pages/WaitlistWrapper.tsx new file mode 100644 index 00000000..6cd9e91c --- /dev/null +++ b/apps/console/src/pages/WaitlistWrapper.tsx @@ -0,0 +1,36 @@ +import { useGetCurrentUser } from "~/graphql/hooks/queries"; +import { useWaitlist } from "~/lib/hooks/useWaitlist"; + +export const WaitlistWrapper = ({ children }) => { + const { shouldRenderWaitlistNotice } = useWaitlist(); + const { data: currentUserData } = useGetCurrentUser(); + + if (shouldRenderWaitlistNotice) { + return ( +
+

+ You're on the waitlist! +

+
+

Thank you for signing up for Pezzo Cloud.

+

+ You will receive an invitation at{" "} + {currentUserData.me.email}{" "} + soon. +

+

+ Need access sooner? Email us at{" "} + + hello@pezzo.ai + +

+
+
+ ); + } else { + return children; + } +}; diff --git a/apps/server/prisma/migrations/20240105073154_add_organization_waitlisted_and_default_to_false/migration.sql b/apps/server/prisma/migrations/20240105073154_add_organization_waitlisted_and_default_to_false/migration.sql new file mode 100644 index 00000000..92c6c8e9 --- /dev/null +++ b/apps/server/prisma/migrations/20240105073154_add_organization_waitlisted_and_default_to_false/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Organization" ADD COLUMN "waitlisted" BOOLEAN NOT NULL DEFAULT false; diff --git a/apps/server/prisma/schema.prisma b/apps/server/prisma/schema.prisma index 26efa639..36e5f0eb 100644 --- a/apps/server/prisma/schema.prisma +++ b/apps/server/prisma/schema.prisma @@ -83,6 +83,7 @@ model Organization { apiKeys ApiKey[] providerApiKeys ProviderApiKey[] invitations Invitation[] + waitlisted Boolean @default(false) } model OrganizationMember { diff --git a/apps/server/src/app/config/common-config-schema.ts b/apps/server/src/app/config/common-config-schema.ts index e223e179..3f695f95 100644 --- a/apps/server/src/app/config/common-config-schema.ts +++ b/apps/server/src/app/config/common-config-schema.ts @@ -22,6 +22,7 @@ const commonConfigSchema = { KMS_KEY_ARN: Joi.string().default( "arn:aws:kms:us-east-1:111122223333:key/demo-master-key" ), + WAITLIST_ENABLED: Joi.boolean().default(false), }; const cloudConfigSchema = { diff --git a/apps/server/src/app/identity/organizations.service.ts b/apps/server/src/app/identity/organizations.service.ts index 1bf30634..4edb874c 100644 --- a/apps/server/src/app/identity/organizations.service.ts +++ b/apps/server/src/app/identity/organizations.service.ts @@ -1,10 +1,14 @@ import { Injectable } from "@nestjs/common"; import { PrismaService } from "../prisma.service"; import { OrgRole } from "@prisma/client"; +import { ConfigService } from "@nestjs/config"; @Injectable() export class OrganizationsService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private config: ConfigService + ) {} async getById(id: string) { return await this.prisma.organization.findUnique({ where: { id } }); @@ -108,9 +112,12 @@ export class OrganizationsService { } async createOrg(name: string, creatorUserId: string) { + const waitlisted = this.config.get("WAITLIST_ENABLED"); + return await this.prisma.organization.create({ data: { name, + waitlisted, members: { create: { userId: creatorUserId, diff --git a/apps/server/src/app/identity/users.service.ts b/apps/server/src/app/identity/users.service.ts index b4bd0bfb..f4e759dc 100644 --- a/apps/server/src/app/identity/users.service.ts +++ b/apps/server/src/app/identity/users.service.ts @@ -6,6 +6,7 @@ import { ExtendedUser } from "./models/extended-user.model"; import UserMetadata from "supertokens-node/recipe/usermetadata"; import { SupertokensMetadata } from "../auth/auth.types"; import { randomBytes } from "crypto"; +import { ConfigService } from "@nestjs/config"; export type UserWithOrgMemberships = User & { orgMemberships: OrganizationMember[]; @@ -13,7 +14,10 @@ export type UserWithOrgMemberships = User & { @Injectable() export class UsersService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private config: ConfigService + ) {} async createUser( userCreateRequest: UserCreateRequest @@ -28,9 +32,12 @@ export class UsersService { }, }); + const waitlisted = this.config.get("WAITLIST_ENABLED"); + const organization = await this.prisma.organization.create({ data: { name: `${userCreateRequest.name}'s Organization`, + waitlisted, members: { create: { userId: userCreateRequest.id,