Skip to content

add waitlist submission form#238

Merged
KMKoushik merged 2 commits intomainfrom
km/2025-09-18-waitlist-form
Sep 18, 2025
Merged

add waitlist submission form#238
KMKoushik merged 2 commits intomainfrom
km/2025-09-18-waitlist-form

Conversation

@KMKoushik
Copy link
Copy Markdown
Member

@KMKoushik KMKoushik commented Sep 18, 2025

Summary by CodeRabbit

  • New Features
    • Admin dashboard layout with navigation; added Teams and Waitlist tools (cloud-only) to search/update teams and manage user waitlist status.
    • Authenticated Waitlist page with a request form, validation, toasts, and rate-limited submission.
  • Refactor
    • Refreshed Admin SES Configurations page layout and headings; enhanced waitlisted user screen with a richer card UI.
  • Chores
    • Added optional environment variable for founder email notifications.
  • Documentation
    • Added guideline to prefer TRPC by default.

@vercel
Copy link
Copy Markdown

vercel bot commented Sep 18, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
unsend-marketing Ready Ready Preview Comment Sep 18, 2025 11:01am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 18, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds admin UI and APIs for team management and waitlist control. Introduces Admin layout and pages: SES configurations update, new Teams and Waitlist pages (cloud-gated) using tRPC mutations. Adds waitlist user-facing page and form with Zod schema and tRPC mutation with Redis rate limiting and founder email notification (FOUNDER_EMAIL env). Extends API root with waitlist router; admin router gains find/update for users’ waitlist and teams’ settings. Refactors trpc auth: adds authedProcedure and updates protectedProcedure to gate waitlisted users. Updates next-auth provider to render WaitListForm for waitlisted users. Minor docs rule added in AGENTS.md.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "add waitlist submission form" is concise and accurately describes a primary visible change in this PR — the new WaitListForm and waitlist submission flow — so it is directly related to the changeset. The branch also adds supporting backend and admin waitlist management, making the title slightly narrower than the full scope but still relevant and clear.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3bb5d0b and 817ae79.

📒 Files selected for processing (2)
  • apps/web/src/app/(dashboard)/admin/layout.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/admin/teams/page.tsx (1 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Sep 18, 2025

Deploying usesend with  Cloudflare Pages  Cloudflare Pages

Latest commit: 817ae79
Status:⚡️  Build in progress...

View logs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/server/auth.ts (1)

84-86: Do not use Math.random() for verification tokens.

This is weak and predictable. Use crypto or NextAuth default.

Apply one of the fixes:

-        async generateVerificationToken() {
-          return Math.random().toString(36).substring(2, 7).toLowerCase();
-        },

or

+        async generateVerificationToken() {
+          const { randomBytes } = await import("crypto");
+          return randomBytes(32).toString("hex");
+        },
🧹 Nitpick comments (14)
AGENTS.md (1)

32-35: Fix wording and capitalization in new rule.

Use “tRPC” and correct grammar.

Apply this diff:

-## Rules
-
-- Prefer to use trpc alway unless asked otherwise
+## Rules
+
+- Prefer to use tRPC by default unless asked otherwise.
apps/web/src/env.js (1)

53-53: Validate FOUNDER_EMAIL as an email address.

Type-level guard reduces misconfig at deploy time.

-    FOUNDER_EMAIL: z.string().optional(),
+    FOUNDER_EMAIL: z.string().email().optional(),
apps/web/src/app/wait-list/schema.ts (1)

8-24: Tighten domain validation.

Add a basic FQDN check and normalize casing.

 export const waitlistSubmissionSchema = z.object({
   domain: z
     .string({ required_error: "Domain is required" })
     .trim()
+    .transform((v) => v.toLowerCase())
     .min(1, "Domain is required")
-    .max(255, "Domain must be 255 characters or fewer"),
+    .max(255, "Domain must be 255 characters or fewer")
+    .regex(
+      /^(?=.{1,255}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})+$/,
+      "Enter a valid domain (e.g., example.com)"
+    ),
   emailTypes: z
     .array(z.enum(WAITLIST_EMAIL_TYPES))
     .min(1, "Select at least one email type"),
apps/web/src/server/api/trpc.ts (1)

119-125: Use FORBIDDEN for waitlisted users.

This is an authorization failure, not authentication; improves client handling.

-  if (ctx.session.user.isWaitlisted) {
-    throw new TRPCError({ code: "UNAUTHORIZED" });
-  }
+  if (ctx.session.user.isWaitlisted) {
+    throw new TRPCError({ code: "FORBIDDEN", message: "WAITLISTED" });
+  }
apps/web/src/app/wait-list/waitlist-form.tsx (1)

32-35: Deduplicate EMAIL_TYPE_LABEL across client and server.

Same mapping exists in apps/web/src/server/api/routers/waitlist.ts. Centralize in schema to prevent drift.

Add to schema.ts:

+export const EMAIL_TYPE_LABEL: Record<(typeof WAITLIST_EMAIL_TYPES)[number], string> = {
+  transactional: "Transactional",
+  marketing: "Marketing",
+};

Then update this file:

-import {
-  WAITLIST_EMAIL_TYPES,
-  waitlistSubmissionSchema,
-  type WaitlistSubmissionInput,
-} from "./schema";
+import {
+  WAITLIST_EMAIL_TYPES,
+  waitlistSubmissionSchema,
+  type WaitlistSubmissionInput,
+  EMAIL_TYPE_LABEL,
+} from "./schema";
-
-const EMAIL_TYPE_LABEL: Record<(typeof WAITLIST_EMAIL_TYPES)[number], string> = {
-  transactional: "Transactional",
-  marketing: "Marketing",
-};
apps/web/src/server/api/routers/waitlist.ts (2)

16-19: Move EMAIL_TYPE_LABEL to shared schema to avoid duplication.

Use a single source of truth alongside WAITLIST_EMAIL_TYPES.

Example change here (after exporting from schema):

-import {
-  WAITLIST_EMAIL_TYPES,
-  waitlistSubmissionSchema,
-} from "~/app/wait-list/schema";
+import {
+  WAITLIST_EMAIL_TYPES,
+  waitlistSubmissionSchema,
+  EMAIL_TYPE_LABEL,
+} from "~/app/wait-list/schema";
-
-const EMAIL_TYPE_LABEL: Record<(typeof WAITLIST_EMAIL_TYPES)[number], string> = {
-  transactional: "Transactional",
-  marketing: "Marketing",
-};

61-62: Clarify rate‑limit error message.

Consider indicating the window (e.g., “Try again in ~6 hours”) for better UX.

- message: "You have reached the waitlist request limit. Please try later.",
+ message: "You have reached the waitlist request limit. Please try again in a few hours.",
apps/web/src/server/api/routers/admin.ts (2)

110-126: Use query for read‑only RPC.

findUserByEmail performs a read; make it a .query for clearer intent and caching semantics.

- findUserByEmail: adminProcedure
+ findUserByEmail: adminProcedure
     .input(
       z.object({
         email: z
           .string()
           .email()
           .transform((value) => value.toLowerCase()),
       }),
     )
-    .mutation(async ({ input }) => {
+    .query(async ({ input }) => {
       const user = await db.user.findUnique({
         where: { email: input.email },
         select: waitlistUserSelection,
       });
 
       return user ?? null;
     }),

145-198: Use query for read‑only RPC.

findTeam is read‑only and should be a .query for consistency.

- findTeam: adminProcedure
+ findTeam: adminProcedure
     .input(
       z.object({
         query: z
           .string({ required_error: "Search query is required" })
           .trim()
           .min(1, "Search query is required"),
       }),
     )
-    .mutation(async ({ input }) => {
+    .query(async ({ input }) => {
       const query = input.query.trim();
       …
       return team ?? null;
     }),
apps/web/src/app/(dashboard)/admin/waitlist/page.tsx (2)

112-118: Redundant controlled prop.

{...field} already supplies value/onChange. Explicit value={field.value} is redundant and risks drift.

-                    <Input
+                    <Input
                       placeholder="user@example.com"
                       autoComplete="off"
-                      {...field}
-                      value={field.value}
+                      {...field}
                     />

44-49: Small UX: disable submit until form is valid and reflect busy state.

Prevents useless requests and improves a11y.

   const form = useForm<SearchInput>({
-    resolver: zodResolver(searchSchema),
+    resolver: zodResolver(searchSchema),
+    mode: "onChange",
     defaultValues: {
       email: "",
     },
   });
-            <Button type="submit" disabled={findUser.isPending}>
+            <Button
+              type="submit"
+              disabled={!form.formState.isValid || findUser.isPending}
+              aria-busy={findUser.isPending}
+            >

Also applies to: 123-131

apps/web/src/app/(dashboard)/admin/teams/page.tsx (3)

253-263: Numeric input jank and mobile keypad.

Typing an empty value turns into 1/0 immediately; also, mobile numeric keypad is not hinted.

  • Allow transient empty input and coerce on blur; hint numeric keypad.
-                        <Input
+                        <Input
                           type="number"
                           min={1}
                           max={10000}
+                          inputMode="numeric"
+                          pattern="[0-9]*"
                           {...field}
-                          value={Number.isNaN(field.value) ? 1 : field.value}
-                          onChange={(event) =>
-                            field.onChange(Number(event.target.value))
-                          }
+                          value={Number.isNaN(field.value) ? "" : field.value}
+                          onChange={(e) => {
+                            const v = e.target.value;
+                            field.onChange(v === "" ? Number.NaN : Number(v));
+                          }}
+                          onBlur={() => {
+                            if (Number.isNaN(field.value)) field.onChange(1);
+                          }}
                           disabled={updateTeam.isPending}
                         />

Apply the same pattern to dailyEmailLimit with fallback 0 and min={0}.

Also applies to: 276-286


339-349: A11y: reflect busy state on submit.

Add aria-busy to communicate progress to AT.

-                  <Button type="submit" disabled={updateTeam.isPending}>
+                  <Button
+                    type="submit"
+                    disabled={updateTeam.isPending}
+                    aria-busy={updateTeam.isPending}
+                  >

167-171: Minor UX duplication.

You show both a toast ("No team found") and an inline empty-state message. Consider one.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21cac61 and 3bb5d0b.

📒 Files selected for processing (15)
  • AGENTS.md (1 hunks)
  • apps/web/src/app/(dashboard)/admin/layout.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/admin/page.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/admin/teams/page.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/admin/waitlist/page.tsx (1 hunks)
  • apps/web/src/app/wait-list/page.tsx (1 hunks)
  • apps/web/src/app/wait-list/schema.ts (1 hunks)
  • apps/web/src/app/wait-list/waitlist-form.tsx (1 hunks)
  • apps/web/src/env.js (2 hunks)
  • apps/web/src/providers/next-auth.tsx (2 hunks)
  • apps/web/src/server/api/root.ts (2 hunks)
  • apps/web/src/server/api/routers/admin.ts (2 hunks)
  • apps/web/src/server/api/routers/waitlist.ts (1 hunks)
  • apps/web/src/server/api/trpc.ts (1 hunks)
  • apps/web/src/server/auth.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx,md}

📄 CodeRabbit inference engine (AGENTS.md)

Format code with Prettier 3 via pnpm format for TypeScript and Markdown files

Files:

  • AGENTS.md
  • apps/web/src/app/wait-list/schema.ts
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/admin/layout.tsx
  • apps/web/src/server/auth.ts
  • apps/web/src/providers/next-auth.tsx
  • apps/web/src/server/api/routers/admin.ts
  • apps/web/src/app/wait-list/page.tsx
  • apps/web/src/app/wait-list/waitlist-form.tsx
  • apps/web/src/app/(dashboard)/admin/waitlist/page.tsx
  • apps/web/src/app/(dashboard)/admin/teams/page.tsx
  • apps/web/src/server/api/root.ts
  • apps/web/src/server/api/trpc.ts
  • apps/web/src/server/api/routers/waitlist.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Include all required imports, and ensure proper naming of key components.

Files:

  • apps/web/src/app/wait-list/schema.ts
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/admin/layout.tsx
  • apps/web/src/server/auth.ts
  • apps/web/src/providers/next-auth.tsx
  • apps/web/src/server/api/routers/admin.ts
  • apps/web/src/app/wait-list/page.tsx
  • apps/web/src/env.js
  • apps/web/src/app/wait-list/waitlist-form.tsx
  • apps/web/src/app/(dashboard)/admin/waitlist/page.tsx
  • apps/web/src/app/(dashboard)/admin/teams/page.tsx
  • apps/web/src/server/api/root.ts
  • apps/web/src/server/api/trpc.ts
  • apps/web/src/server/api/routers/waitlist.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use 2-space indentation in TypeScript code (enforced by Prettier)
Use semicolons in TypeScript code (enforced by Prettier)
Do not use dynamic imports

Files:

  • apps/web/src/app/wait-list/schema.ts
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/admin/layout.tsx
  • apps/web/src/server/auth.ts
  • apps/web/src/providers/next-auth.tsx
  • apps/web/src/server/api/routers/admin.ts
  • apps/web/src/app/wait-list/page.tsx
  • apps/web/src/app/wait-list/waitlist-form.tsx
  • apps/web/src/app/(dashboard)/admin/waitlist/page.tsx
  • apps/web/src/app/(dashboard)/admin/teams/page.tsx
  • apps/web/src/server/api/root.ts
  • apps/web/src/server/api/trpc.ts
  • apps/web/src/server/api/routers/waitlist.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

In apps/web, use the / alias for src imports (e.g., import { x } from "/utils/x")

Files:

  • apps/web/src/app/wait-list/schema.ts
  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/admin/layout.tsx
  • apps/web/src/server/auth.ts
  • apps/web/src/providers/next-auth.tsx
  • apps/web/src/server/api/routers/admin.ts
  • apps/web/src/app/wait-list/page.tsx
  • apps/web/src/app/wait-list/waitlist-form.tsx
  • apps/web/src/app/(dashboard)/admin/waitlist/page.tsx
  • apps/web/src/app/(dashboard)/admin/teams/page.tsx
  • apps/web/src/server/api/root.ts
  • apps/web/src/server/api/trpc.ts
  • apps/web/src/server/api/routers/waitlist.ts
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Name React component files in PascalCase (e.g., AppSideBar.tsx)

Files:

  • apps/web/src/app/(dashboard)/admin/page.tsx
  • apps/web/src/app/(dashboard)/admin/layout.tsx
  • apps/web/src/providers/next-auth.tsx
  • apps/web/src/app/wait-list/page.tsx
  • apps/web/src/app/wait-list/waitlist-form.tsx
  • apps/web/src/app/(dashboard)/admin/waitlist/page.tsx
  • apps/web/src/app/(dashboard)/admin/teams/page.tsx
🧠 Learnings (3)
📚 Learning: 2025-09-08T08:10:01.547Z
Learnt from: CR
PR: usesend/useSend#0
File: AGENTS.md:0-0
Timestamp: 2025-09-08T08:10:01.547Z
Learning: Applies to apps/web/**/*.{ts,tsx} : In apps/web, use the ~/ alias for src imports (e.g., import { x } from "~/utils/x")

Applied to files:

  • AGENTS.md
📚 Learning: 2025-09-08T08:10:01.547Z
Learnt from: CR
PR: usesend/useSend#0
File: AGENTS.md:0-0
Timestamp: 2025-09-08T08:10:01.547Z
Learning: Applies to **/*.{ts,tsx} : Do not use dynamic imports

Applied to files:

  • AGENTS.md
📚 Learning: 2025-09-08T08:10:01.547Z
Learnt from: CR
PR: usesend/useSend#0
File: AGENTS.md:0-0
Timestamp: 2025-09-08T08:10:01.547Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Do not add tests unless explicitly required

Applied to files:

  • AGENTS.md
🧬 Code graph analysis (11)
apps/web/src/app/(dashboard)/admin/page.tsx (2)
apps/web/src/app/(dashboard)/admin/add-ses-configuration.tsx (1)
  • AddSesConfiguration (16-40)
apps/web/src/app/(dashboard)/admin/ses-configurations.tsx (1)
  • SesConfigurations (17-84)
apps/web/src/app/(dashboard)/admin/layout.tsx (2)
apps/web/src/app/(dashboard)/dev-settings/settings-nav-button.tsx (1)
  • SettingsNavButton (7-39)
apps/web/src/utils/common.ts (1)
  • isCloud (3-5)
apps/web/src/server/auth.ts (1)
apps/web/src/env.js (2)
  • env (5-134)
  • env (5-134)
apps/web/src/providers/next-auth.tsx (1)
apps/web/src/app/wait-list/waitlist-form.tsx (1)
  • WaitListForm (37-201)
apps/web/src/server/api/routers/admin.ts (2)
apps/web/src/server/api/trpc.ts (1)
  • adminProcedure (267-272)
apps/web/src/server/db.ts (1)
  • db (20-20)
apps/web/src/app/wait-list/page.tsx (2)
apps/web/src/server/auth.ts (1)
  • getServerAuthSession (153-153)
apps/web/src/app/wait-list/waitlist-form.tsx (1)
  • WaitListForm (37-201)
apps/web/src/app/wait-list/waitlist-form.tsx (2)
apps/web/src/app/wait-list/schema.ts (3)
  • WAITLIST_EMAIL_TYPES (3-6)
  • WaitlistSubmissionInput (24-24)
  • waitlistSubmissionSchema (8-22)
packages/ui/src/spinner.tsx (1)
  • Spinner (4-51)
apps/web/src/app/(dashboard)/admin/waitlist/page.tsx (2)
apps/web/src/server/api/root.ts (1)
  • AppRouter (40-40)
apps/web/src/utils/common.ts (1)
  • isCloud (3-5)
apps/web/src/app/(dashboard)/admin/teams/page.tsx (2)
apps/web/src/server/api/root.ts (1)
  • AppRouter (40-40)
apps/web/src/server/service/team-service.ts (1)
  • updateTeam (104-111)
apps/web/src/server/api/root.ts (1)
apps/web/src/server/api/routers/waitlist.ts (1)
  • waitlistRouter (30-107)
apps/web/src/server/api/routers/waitlist.ts (6)
apps/web/src/app/wait-list/schema.ts (2)
  • WAITLIST_EMAIL_TYPES (3-6)
  • waitlistSubmissionSchema (8-22)
apps/web/src/server/api/trpc.ts (2)
  • createTRPCRouter (82-82)
  • authedProcedure (99-109)
apps/web/src/env.js (2)
  • env (5-134)
  • env (5-134)
apps/web/src/server/logger/log.ts (1)
  • logger (31-63)
apps/web/src/server/redis.ts (1)
  • getRedis (6-13)
apps/web/src/server/mailer.ts (1)
  • sendMail (72-135)
🔇 Additional comments (14)
apps/web/src/server/auth.ts (2)

132-141: Waitlist gating logic change looks good.

Behavior aligns with protectedProcedure waitlist checks.

Please verify onboarding flows for:

  • Cloud + no invite → user.isWaitlisted = true; UI shows waitlist.
  • Self-hosted (NEXT_PUBLIC_IS_CLOUD=false) → not waitlisted.

91-93: Typo in error message.

“atleast” → “at least”.
[raise_nitpick_refactor]

-    throw new Error("No auth providers found, need atleast one");
+    throw new Error("No auth providers found, need at least one");
apps/web/src/server/api/trpc.ts (1)

99-109: Auth middleware LGTM.

Session presence check and context shaping look fine.

apps/web/src/server/api/root.ts (1)

15-16: Router wiring LGTM.

waitlistRouter import and exposure are correct.

Also applies to: 36-36

apps/web/src/app/(dashboard)/admin/page.tsx (1)

6-14: Admin SES page refactor LGTM.

Header/actions layout and component split are clean.

apps/web/src/app/wait-list/page.tsx (1)

3-15: Waitlist page gating and UI LGTM.

Server-side redirect and form embedding are correct.

Also applies to: 17-33

apps/web/src/env.js (1)

107-107: FOUNDER_EMAIL is server-only — no client references found.
Defined at apps/web/src/env.js:107 and only used in apps/web/src/server/api/routers/waitlist.ts:40; no client components reference env.FOUNDER_EMAIL.

apps/web/src/app/(dashboard)/admin/layout.tsx (2)

11-29: LGTM: Admin layout and nav structure look good.


21-25: No change needed — isCloud already returns a boolean.

apps/web/src/env.js normalizes NEXT_PUBLIC_IS_CLOUD with .transform((str) => str === "true"), so env.NEXT_PUBLIC_IS_CLOUD is a boolean and apps/web/src/utils/common.ts returning it is correct.

apps/web/src/providers/next-auth.tsx (1)

41-56: Nice UX for waitlisted users; clear CTA and consistent styling.

apps/web/src/app/(dashboard)/admin/teams/page.tsx (2)

30-33: Good: Strong typing via AppRouter + inferRouterOutputs.

Nice use of TRPC types to derive TeamAdmin; avoids drift.

Also applies to: 43-45


85-99: Mutation handling and form sync look correct.

Search/update flows, toasts, and reset-on-success are coherent.

Also applies to: 100-114

apps/web/src/app/(dashboard)/admin/waitlist/page.tsx (2)

160-166: Type safety: createdAt conversion. new Date(userResult.createdAt) safely handles both string and Date inputs; please confirm your tRPC transformer (e.g. superjson) is enabled in apps/web/src/server/api/trpc.ts to serialize/deserialize Date fields correctly.


51-79: Mutation flow OK — server returns isWaitlisted and createdAt.

Confirmed in apps/web/src/server/api/routers/admin.ts: waitlistUserSelection includes isWaitlisted and createdAt (lines 9–15); findUserByEmail returns that selection (lines 120–123) and updateUserWaitlist returns it as well (lines 136–140).

Comment on lines +92 to +99
if (!isCloud()) {
return (
<div className="rounded-lg border bg-muted/30 p-6 text-sm text-muted-foreground">
Waitlist tooling is available only in the cloud deployment.
</div>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Bug: Cloud-gating likely broken if NEXT_PUBLIC_IS_CLOUD is a string.

isCloud() currently returns env.NEXT_PUBLIC_IS_CLOUD (ref: apps/web/src/utils/common.ts), which can be "true"/"false" (strings). In JS, "false" is truthy, so if (!isCloud()) won’t gate correctly. Fix isCloud() to return a real boolean.

Apply in apps/web/src/utils/common.ts:

+import { env } from "~/env";
 
-export function isCloud() {
-  return env.NEXT_PUBLIC_IS_CLOUD;
-}
+export function isCloud(): boolean {
+  const v = env.NEXT_PUBLIC_IS_CLOUD as unknown;
+  return v === true || String(v).toLowerCase() === "true" || String(v) === "1";
+}

Optionally, keep this file unchanged and do a strict check here:

-  if (!isCloud()) {
+  if (!(isCloud() === true)) {
     return (
       <div className="rounded-lg border bg-muted/30 p-6 text-sm text-muted-foreground">
         Waitlist tooling is available only in the cloud deployment.
       </div>
     );
   }
🤖 Prompt for AI Agents
In apps/web/src/app/(dashboard)/admin/waitlist/page.tsx lines 92-99 the
cloud-gating relies on isCloud(), but the real fix must be in
apps/web/src/utils/common.ts where isCloud currently returns the raw
NEXT_PUBLIC_IS_CLOUD string; change isCloud to return a boolean by comparing the
env value to the literal "true" (e.g. return env.NEXT_PUBLIC_IS_CLOUD ===
'true') or by parsing it to boolean safely (JSON.parse with try/catch), ensuring
all callers (including this page) get a real boolean and the gate works as
intended.

} from "@usesend/ui/src/form";
import { Input } from "@usesend/ui/src/input";
import { Textarea } from "@usesend/ui/src/textarea";
import Spinner from "@usesend/ui/src/spinner";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Fix incorrect default import for Spinner (build-time error).

packages/ui exports Spinner as a named export.

Apply:

-import Spinner from "@usesend/ui/src/spinner";
+import { Spinner } from "@usesend/ui/src/spinner";

Quick check:


🏁 Script executed:

#!/bin/bash
rg -nC2 '^export\s+const\s+Spinner\b' packages/ui

Length of output: 307


Fix incorrect default import for Spinner (build-time error).

packages/ui exports Spinner as a named export — change the import to a named import.

File: apps/web/src/app/wait-list/waitlist-form.tsx (around line 17)

Apply:

-import Spinner from "@usesend/ui/src/spinner";
+import { Spinner } from "@usesend/ui/src/spinner";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import Spinner from "@usesend/ui/src/spinner";
import { Spinner } from "@usesend/ui/src/spinner";
🤖 Prompt for AI Agents
In apps/web/src/app/wait-list/waitlist-form.tsx around line 17, the file imports
Spinner as a default export but packages/ui exports it as a named export; change
the import to a named import (e.g., replace the default import with an import
that destructures Spinner from "@usesend/ui/src/spinner") so the build-time
error is resolved.

@KMKoushik KMKoushik merged commit 87c772d into main Sep 18, 2025
2 of 5 checks passed
@KMKoushik KMKoushik deleted the km/2025-09-18-waitlist-form branch September 18, 2025 11:01
@coderabbitai coderabbitai bot mentioned this pull request Nov 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant