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
4 4
5import { MOBILE_TYPOGRAPHY } from "../../lib/typography";5import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
6import { useThemeColor } from "../../lib/useThemeColor";6import { useThemeColor } from "../../lib/useThemeColor";
7import { CloudWaitlistJoinRejectedError, joinCloudWaitlist } from "./cloudWaitlistJoin";
7 8
8export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }) {9export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }) {
9 const { errors, fetchStatus, waitlist } = useWaitlist();10 const { errors, fetchStatus, waitlist } = useWaitlist();
@@ -21,12 +22,14 @@ export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void } · 11 unmodified lines
21 22
22 setRequestError(null);23 setRequestError(null);
23 try {24 try {
24 const { error } = await waitlist.join({ emailAddress: normalizedEmailAddress });25 await joinCloudWaitlist(waitlist, normalizedEmailAddress);
25 if (error) {26 } catch (error) {
26 setRequestError("Could not join the waitlist. Check your email address and try again.");27 console.error(error);
27 }28 setRequestError(
28 } catch {29 error instanceof CloudWaitlistJoinRejectedError
29 setRequestError("Could not join the waitlist. Check your connection and try again.");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 );
30 }33 }
31 };34 };
32 35
apps/mobile/src/features/cloud/cloudWaitlistJoin.test.ts+48−0
@@ -0,0 +1,48 @@
1import { describe, expect, it, vi } from "vite-plus/test";
2
3import {
4 CloudWaitlistJoinRejectedError,
5 CloudWaitlistJoinRequestError,
6 joinCloudWaitlist,
7} from "./cloudWaitlistJoin";
8
9describe("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 @@
1import * as Schema from "effect/Schema";
2
3interface CloudWaitlistJoiner {
4 readonly join: (input: { emailAddress: string }) => Promise<{
5 readonly error: { readonly code: string } | null;
6 }>;
7}
8
9export 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
21export 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
32export 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}