History

[codex] Structure mobile waitlist enrollment failures (#3446)

Julius Marminge committed 4 months ago

Commit 28e7c9a1 parent a9460bb3 files · +103 −6Browse files

Unified · Split

3 files
  1. apps/mobile/src/features/cloud/CloudWaitlistEnrollment.tsx +9 −6
  2. apps/mobile/src/features/cloud/cloudWaitlistJoin.test.ts +48 −0
  3. apps/mobile/src/features/cloud/cloudWaitlistJoin.ts +46 −0
apps/mobile/src/features/cloud/CloudWaitlistEnrollment.tsx+9−6
@@ -4,6 +4,7 @@ import { useState } from "react"; · 3 unmodified lines
44
55 import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
66 import { useThemeColor } from "../../lib/useThemeColor";
7+import { CloudWaitlistJoinRejectedError, joinCloudWaitlist } from "./cloudWaitlistJoin";
78
89 export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }) {
910 const { errors, fetchStatus, waitlist } = useWaitlist();
@@ -21,12 +22,14 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void } · 11 unmodified lines
2122
2223 setRequestError(null);
2324 try {
24− const { error } = await waitlist.join({ emailAddress: normalizedEmailAddress });
25− if (error) {
26− setRequestError("Could not join the waitlist. Check your email address and try again.");
27− }
28− } catch {
29− setRequestError("Could not join the waitlist. Check your connection and try again.");
25+ await joinCloudWaitlist(waitlist, normalizedEmailAddress);
26+ } catch (error) {
27+ console.error(error);
28+ setRequestError(
29+ error instanceof CloudWaitlistJoinRejectedError
30+ ? "Could not join the waitlist. Check your email address and try again."
31+ : "Could not join the waitlist. Check your connection and try again.",
32+ );
3033 }
3134 };
3235
apps/mobile/src/features/cloud/cloudWaitlistJoin.test.ts+48−0
@@ -0,0 +1,48 @@
1+import { describe, expect, it, vi } from "vite-plus/test";
2+
3+import {
4+ CloudWaitlistJoinRejectedError,
5+ CloudWaitlistJoinRequestError,
6+ joinCloudWaitlist,
7+} from "./cloudWaitlistJoin";
8+
9+describe("joinCloudWaitlist", () => {
10+ it("submits the provided email address", async () => {
11+ const join = vi.fn().mockResolvedValue({ error: null });
12+
13+ await joinCloudWaitlist({ join }, "person@example.com");
14+
15+ expect(join).toHaveBeenCalledExactlyOnceWith({ emailAddress: "person@example.com" });
16+ });
17+
18+ it("preserves Clerk rejection details without exposing the email address", async () => {
19+ const cause = Object.assign(new Error("The enrollment was rejected."), {
20+ code: "form_identifier_invalid",
21+ });
22+ const join = vi.fn().mockResolvedValue({ error: cause });
23+
24+ const failure = await joinCloudWaitlist({ join }, "secret@example.com").catch(
25+ (error: unknown) => error,
26+ );
27+
28+ expect(failure).toBeInstanceOf(CloudWaitlistJoinRejectedError);
29+ expect(failure).toMatchObject({
30+ code: "form_identifier_invalid",
31+ cause,
32+ });
33+ expect(String(failure)).not.toContain("secret@example.com");
34+ });
35+
36+ it("distinguishes request failures from rejected enrollments", async () => {
37+ const cause = new Error("network unavailable");
38+ const join = vi.fn().mockRejectedValue(cause);
39+
40+ const failure = await joinCloudWaitlist({ join }, "person@example.com").catch(
41+ (error: unknown) => error,
42+ );
43+
44+ expect(failure).toBeInstanceOf(CloudWaitlistJoinRequestError);
45+ expect(failure).toMatchObject({ cause });
46+ expect(failure).not.toBeInstanceOf(CloudWaitlistJoinRejectedError);
47+ });
48+});
apps/mobile/src/features/cloud/cloudWaitlistJoin.ts+46−0
@@ -0,0 +1,46 @@
1+import * as Schema from "effect/Schema";
2+
3+interface CloudWaitlistJoiner {
4+ readonly join: (input: { emailAddress: string }) => Promise<{
5+ readonly error: { readonly code: string } | null;
6+ }>;
7+}
8+
9+export class CloudWaitlistJoinRejectedError extends Schema.TaggedErrorClass<CloudWaitlistJoinRejectedError>()(
10+ "CloudWaitlistJoinRejectedError",
11+ {
12+ code: Schema.String,
13+ cause: Schema.Defect(),
14+ },
15+) {
16+ override get message(): string {
17+ return `Cloud waitlist enrollment was rejected with code "${this.code}".`;
18+ }
19+}
20+
21+export class CloudWaitlistJoinRequestError extends Schema.TaggedErrorClass<CloudWaitlistJoinRequestError>()(
22+ "CloudWaitlistJoinRequestError",
23+ {
24+ cause: Schema.Defect(),
25+ },
26+) {
27+ override get message(): string {
28+ return "Cloud waitlist enrollment request failed.";
29+ }
30+}
31+
32+export async function joinCloudWaitlist(
33+ waitlist: CloudWaitlistJoiner,
34+ emailAddress: string,
35+): Promise<void> {
36+ const result = await waitlist.join({ emailAddress }).catch((cause) => {
37+ throw new CloudWaitlistJoinRequestError({ cause });
38+ });
39+
40+ if (result.error) {
41+ throw new CloudWaitlistJoinRejectedError({
42+ code: result.error.code,
43+ cause: result.error,
44+ });
45+ }
46+}