{"record":{"id":"0bca98d3c1ea8808","repo":"antiwork/gumroad","slug":"responsedata-error-message-0bca98","errorCode":null,"errorMessage":"${responseData.error_message}","messagePattern":"\\$\\{responseData\\.error_message\\}","errorType":"exception","errorClass":"ResponseError","httpStatus":null,"severity":"error","filePath":"app/javascript/components/CreateBrandAccountModal.tsx","lineNumber":68,"sourceCode":"    setIsSubmitting(true);\n    try {\n      const response = await request({\n        method: \"POST\",\n        accept: \"json\",\n        url: Routes.sellers_brand_accounts_path(),\n        data: {\n          brand_account: {\n            name,\n            username,\n            email,\n            use_existing_payout_setup: canPortPayoutSetup && useExistingPayoutSetup,\n          },\n        },\n      });\n      const responseData = typia.assert<{ success: true } | { success: false; error_message: string }>(\n        await response.json(),\n      );\n      if (!responseData.success) throw new ResponseError(responseData.error_message);\n      // The session is already switched into the new brand account; land on its dashboard.\n      window.location.href = Routes.dashboard_path();\n    } catch (e) {\n      assertResponseError(e);\n      showAlert(e.message, \"error\");\n      setIsSubmitting(false);\n    }\n  };\n\n  return (\n    <Modal\n      open={open}\n      title=\"Create a new Gumroad\"\n      onClose={onClose}\n      footer={\n        <>\n          <Button onClick={onClose} disabled={isSubmitting}>\n            Cancel","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/javascript/components/CreateBrandAccountModal.tsx#L50-L86","documentation":"CreateBrandAccountModal POSTs the new brand account (name, username, email, payout-porting flag). The server replies with a discriminated union { success: true } | { success: false; error_message } and the throw passes the server's error_message straight through as the ResponseError message, shown via showAlert. typia.assert enforces the union shape — a body matching neither branch throws TypiaError, which assertResponseError in the catch re-throws (uncaught by this handler).","triggerScenarios":"POST returning success:false with error_message: username already taken, email invalid or already in use, payout-setup porting check failed — i.e. classic form-validation rejections delivered in the response body rather than as a 422.","commonSituations":"Seller picks a username that is taken; brand email collides with an existing account; payout porting rejected because the source account is not fully set up; server validation changed but the modal still submits stale fields.","solutions":["Read the alert — it carries the server's exact rejection reason.","If username/email rejections are frequent, validate format client-side and check availability before submit.","If error_message looks truncated or generic, check the controller for what it returns per failure branch.","If nothing shows and the error boundary triggers instead, the body did not match the union — log the raw response once.","Keep the fields in the POST in sync with the server's strong params; a renamed param yields success:false for a confusing reason."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const USERNAME = /^[a-z0-9_-]{3,30}$/i;\nconst EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nif (!USERNAME.test(username) || !EMAIL.test(email)) {\n  showAlert('Check the account name and email before continuing.', 'error');\n  return;\n}","typeGuard":"type BrandAccountResult = { success: true } | { success: false; error_message: string };\nconst isBrandAccountResult = (v: unknown): v is BrandAccountResult =>\n  typeof v === 'object' && v !== null && typeof (v as { success?: unknown }).success === 'boolean';","tryCatchPattern":"try {\n  await createBrandAccount();\n} catch (e) {\n  assertResponseError(e);\n  showAlert(e.message, 'error'); // server's error_message — e.g. 'that username is taken'\n  setIsSubmitting(false); // always re-enable the button\n}","preventionTips":["Validate username/email format client-side; most server rejections are format or availability, both pre-checkable.","Check username availability on blur (a cheap GET) so the submit rarely fails.","Always reset isSubmitting in the catch — a stuck disabled button after failure locks the modal.","Keep param names in the POST aligned with the server's strong params; drift yields confusing success:false rejections."],"tags":["http","brand-account","form-submit","api-error-body","typia"],"backgroundTag":"api-error-response-body","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}