@burdenoff/microfe-devportal 2026.1107.2 → 2026.1108.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devportal/DevportalRoutes.d.ts.map +1 -1
- package/dist/devportal/DevportalRoutes.js +27 -23
- package/dist/devportal/DevportalRoutes.js.map +1 -1
- package/dist/devportal/pages/ApplicationDetailPage.d.ts.map +1 -1
- package/dist/devportal/pages/ApplicationDetailPage.js +743 -726
- package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
- package/dist/devportal/pages/PublisherPayoutsPage.d.ts +21 -0
- package/dist/devportal/pages/PublisherPayoutsPage.d.ts.map +1 -0
- package/dist/devportal/pages/PublisherPayoutsPage.js +657 -0
- package/dist/devportal/pages/PublisherPayoutsPage.js.map +1 -0
- package/dist/devportal/pages/PublisherProfilePage.d.ts.map +1 -1
- package/dist/devportal/pages/PublisherProfilePage.js +166 -153
- package/dist/devportal/pages/PublisherProfilePage.js.map +1 -1
- package/dist/devportal/utils/publisherPayouts.d.ts +137 -0
- package/dist/devportal/utils/publisherPayouts.d.ts.map +1 -0
- package/dist/devportal/utils/publisherPayouts.js +167 -0
- package/dist/devportal/utils/publisherPayouts.js.map +1 -0
- package/dist/devportal/utils/submitForReview.d.ts +40 -0
- package/dist/devportal/utils/submitForReview.d.ts.map +1 -0
- package/dist/devportal/utils/submitForReview.js +37 -0
- package/dist/devportal/utils/submitForReview.js.map +1 -0
- package/dist/generated/global-operations.d.ts +341 -0
- package/dist/generated/global-operations.d.ts.map +1 -1
- package/dist/generated/global-operations.js +409 -270
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.d.ts +2292 -323
- package/dist/generated/global-types.d.ts.map +1 -1
- package/dist/generated/global-types.js +3 -1
- package/dist/generated/global-types.js.map +1 -1
- package/dist/translations/en.d.ts +91 -0
- package/dist/translations/en.d.ts.map +1 -1
- package/dist/translations/en.js +91 -0
- package/dist/translations/en.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PublisherProfilePage.js","names":[],"sources":["../../../src/devportal/pages/PublisherProfilePage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Store,\n Save,\n CheckCircle2,\n AlertCircle,\n Loader2,\n Info,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { InputField } from \"../components/FormField\";\nimport { cn } from \"../utils/cn\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport {\n useCreateDeveloperProfileMutation,\n useGetMyDeveloperProfileQuery,\n useUpdateDeveloperProfileMutation,\n} from \"../hooks/useGraphQL\";\n\n/**\n * Publisher Profile Page\n *\n * Lets developers enrich their store publisher presence.\n * The publisher profile in global-store-svc is auto-created when an app is\n * first published. This page surfaces the underlying developer profile fields\n * that feed into that publisher identity so developers can keep them current.\n */\nexport const PublisherProfilePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const { currentUser } = useDevportal();\n const { allowed: canSave } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.PROFILE_UPDATE,\n );\n const { data, loading, error, refetch } = useGetMyDeveloperProfileQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const [updateProfile, { loading: updating }] =\n useUpdateDeveloperProfileMutation();\n // A developer who arrives from the public registration journey has no\n // profile yet; updateDeveloperProfile refuses that case, so the first save\n // creates the profile instead (BOFF-10636).\n const [createProfile, { loading: creating }] =\n useCreateDeveloperProfileMutation();\n const saving = updating || creating;\n\n const profile = data?.myDeveloperProfile;\n\n // Form state — publisher-relevant fields\n const [companyName, setCompanyName] = useState(\"\");\n const [website, setWebsite] = useState(\"\");\n const [bio, setBio] = useState(\"\");\n const [githubUsername, setGithubUsername] = useState(\"\");\n const [twitterUsername, setTwitterUsername] = useState(\"\");\n const [linkedinUrl, setLinkedinUrl] = useState(\"\");\n\n const [saveStatus, setSaveStatus] = useState<\n \"idle\" | \"saving\" | \"saved\" | \"error\"\n >(\"idle\");\n\n useEffect(() => {\n if (profile) {\n setCompanyName(profile.companyName ?? \"\");\n setWebsite(profile.website ?? \"\");\n setBio(profile.bio ?? \"\");\n setGithubUsername(profile.githubUsername ?? \"\");\n setTwitterUsername(profile.twitterUsername ?? \"\");\n setLinkedinUrl(profile.linkedinUrl ?? \"\");\n } else if (!loading && currentUser) {\n // No profile yet — pre-fill display name from account\n const fullName = [currentUser.firstName, currentUser.lastName]\n .filter(Boolean)\n .join(\" \");\n if (fullName) setCompanyName(fullName);\n }\n }, [profile, loading, currentUser]);\n\n const handleSave = async () => {\n setSaveStatus(\"saving\");\n try {\n if (!profile) {\n const created = await createProfile({\n variables: {\n input: {\n companyName: companyName || undefined,\n website: website || undefined,\n bio: bio || undefined,\n githubUsername: githubUsername || undefined,\n twitterUsername: twitterUsername || undefined,\n linkedinUrl: linkedinUrl || undefined,\n },\n },\n });\n if (created.error) {\n throw created.error;\n }\n setSaveStatus(\"saved\");\n await refetch();\n setTimeout(() => setSaveStatus(\"idle\"), 3000);\n return;\n }\n const result = await updateProfile({\n variables: {\n userId: profile.userId,\n input: {\n companyName: companyName || null,\n website: website || null,\n bio: bio || null,\n githubUsername: githubUsername || null,\n twitterUsername: twitterUsername || null,\n linkedinUrl: linkedinUrl || null,\n // preserve existing notification prefs\n emailNotifications: profile.emailNotifications,\n marketingEmails: profile.marketingEmails,\n },\n },\n });\n if (result.error) {\n throw result.error;\n }\n setSaveStatus(\"saved\");\n await refetch();\n setTimeout(() => setSaveStatus(\"idle\"), 3000);\n } catch {\n setSaveStatus(\"error\");\n setTimeout(() => setSaveStatus(\"idle\"), 5000);\n }\n };\n\n if (loading && !profile) {\n return (\n <PageLayout\n title={tr(\"devportal.publisherProfileTitle\", \"Publisher Profile\")}\n description={tr(\n \"devportal.loadingPublisherProfile\",\n \"Loading your publisher profile...\",\n )}\n >\n <div className=\"flex items-center justify-center py-20\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error && !profile) {\n return (\n <PageLayout\n title={tr(\"devportal.publisherProfileTitle\", \"Publisher Profile\")}\n description={tr(\n \"devportal.manageStorePublisherPresence\",\n \"Manage your store publisher presence\",\n )}\n >\n <div className=\"flex flex-col items-center justify-center gap-4 py-20\">\n <AlertCircle className=\"h-12 w-12 text-status-error-text\" />\n <p className=\"text-text-secondary\">\n {tr(\"devportal.failedToLoadProfile\", \"Failed to load profile\")}\n </p>\n <button\n onClick={() => refetch()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.retryButton\", \"Retry\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.publisherProfileTitle\", \"Publisher Profile\")}\n description={tr(\n \"devportal.manageMarketplaceAppearance\",\n \"Manage how you appear to users in the marketplace\",\n )}\n purpose={tr(\n \"devportal.publisherProfilePurpose\",\n \"This is your public face in the marketplace — the name, logo, bio, and links users see before installing your apps. A complete, trustworthy profile makes shoppers more likely to install and is required before some apps can be published.\",\n )}\n actions={\n <button\n onClick={handleSave}\n disabled={saving || !canSave}\n aria-disabled={!canSave}\n className={cn(\n \"inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors\",\n saveStatus === \"saved\"\n ? \"bg-status-success-bg text-white\"\n : saveStatus === \"error\"\n ? \"bg-status-error-bg text-status-error-text\"\n : \"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90\",\n saving && \"opacity-50 cursor-not-allowed\",\n )}\n >\n {saving ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : saveStatus === \"saved\" ? (\n <CheckCircle2 size={16} />\n ) : saveStatus === \"error\" ? (\n <AlertCircle size={16} />\n ) : (\n <Save size={16} />\n )}\n {saving\n ? tr(\"devportal.savingButton\", \"Saving...\")\n : saveStatus === \"saved\"\n ? tr(\"devportal.savedButton\", \"Saved!\")\n : saveStatus === \"error\"\n ? tr(\"devportal.errorButton\", \"Error\")\n : tr(\"devportal.saveChangesButton\", \"Save Changes\")}\n </button>\n }\n >\n <div className=\"flex flex-col gap-6\">\n <div className=\"flex gap-3 rounded-lg border border-border-subtle bg-status-info-bg-subtle p-4 text-sm text-status-info-text\">\n <Info size={16} className=\"flex-shrink-0 mt-0.5\" />\n <div>\n <p className=\"font-medium\">\n {tr(\n \"devportal.publisherProfilesWorkTitle\",\n \"How publisher profiles work\",\n )}\n </p>\n <p className=\"mt-1 text-status-info-text\">\n {tr(\n \"devportal.publisherProfilesWorkDesc\",\n \"When you publish your first app, a publisher profile is automatically created in the marketplace using your developer details. Keep this information up to date so users can trust and find your apps. Changes here are reflected when your next app is published.\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Main form */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Publisher identity */}\n <PageSection\n title={tr(\n \"devportal.publisherIdentityTitle\",\n \"Publisher Identity\",\n )}\n description={tr(\n \"devportal.publisherIdentityDesc\",\n \"Displayed on your marketplace listings\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\n \"devportal.displayNameLabel\",\n \"Display Name / Company\",\n )}\n value={companyName}\n onChange={(e) => setCompanyName(e.target.value)}\n placeholder={tr(\n \"devportal.displayNamePlaceholder\",\n \"Your name or company (shown on all your apps)\",\n )}\n />\n <div>\n <label className=\"text-sm font-medium text-text-primary\">\n {tr(\"devportal.bioLabel\", \"Bio\")}\n </label>\n <textarea\n value={bio}\n onChange={(e) => setBio(e.target.value)}\n placeholder={tr(\n \"devportal.bioPlaceholder\",\n \"A short description of you or your company shown on the marketplace...\",\n )}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n <InputField\n label={tr(\"devportal.websiteLabel\", \"Website\")}\n value={website}\n onChange={(e) => setWebsite(e.target.value)}\n placeholder={tr(\n \"devportal.websitePlaceholder\",\n \"https://your-website.com\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Social links */}\n <PageSection\n title={tr(\"devportal.socialLinksTitle\", \"Social Links\")}\n description={tr(\n \"devportal.socialLinksDesc\",\n \"Build trust with your developer community\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\n \"devportal.githubUsernameLabel\",\n \"GitHub Username\",\n )}\n value={githubUsername}\n onChange={(e) => setGithubUsername(e.target.value)}\n placeholder={tr(\n \"devportal.githubUsernamePlaceholder\",\n \"your-github-username\",\n )}\n />\n <InputField\n label={tr(\n \"devportal.twitterUsernameLabel\",\n \"Twitter / X Username\",\n )}\n value={twitterUsername}\n onChange={(e) => setTwitterUsername(e.target.value)}\n placeholder={tr(\n \"devportal.twitterUsernamePlaceholder\",\n \"your_twitter\",\n )}\n />\n <InputField\n label={tr(\"devportal.linkedinUrlLabel\", \"LinkedIn URL\")}\n value={linkedinUrl}\n onChange={(e) => setLinkedinUrl(e.target.value)}\n placeholder={tr(\n \"devportal.linkedinUrlPlaceholder\",\n \"https://linkedin.com/in/you\",\n )}\n />\n </div>\n </PageSection>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Publisher status */}\n <PageSection\n title={tr(\"devportal.publisherStatusTitle\", \"Publisher Status\")}\n >\n <div className=\"flex items-center gap-3\">\n <Store className=\"h-8 w-8 text-primary\" />\n <div>\n <p className=\"font-medium\">\n {profile?.isVerified\n ? tr(\n \"devportal.verifiedPublisher\",\n \"Verified Publisher\",\n )\n : tr(\"devportal.publisher\", \"Publisher\")}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {profile?.isVerified\n ? tr(\n \"devportal.publisherIdentityVerified\",\n \"Your publisher identity is verified\",\n )\n : tr(\n \"devportal.publishFirstAppMessage\",\n \"Publish your first app to appear in the marketplace\",\n )}\n </p>\n </div>\n </div>\n </PageSection>\n\n {/* Profile completeness */}\n <PageSection\n title={tr(\n \"devportal.profileCompletenessTitle\",\n \"Profile Completeness\",\n )}\n >\n <div className=\"space-y-2\">\n {[\n {\n label: tr(\n \"devportal.completenessDisplayName\",\n \"Display name\",\n ),\n ok: !!companyName,\n },\n {\n label: tr(\"devportal.completenessBio\", \"Bio\"),\n ok: !!bio,\n },\n {\n label: tr(\"devportal.completenessWebsite\", \"Website\"),\n ok: !!website,\n },\n {\n label: tr(\"devportal.completenessGithub\", \"GitHub\"),\n ok: !!githubUsername,\n },\n {\n label: tr(\n \"devportal.completenessSocialLinks\",\n \"Social links\",\n ),\n ok: !!twitterUsername || !!linkedinUrl,\n },\n ].map(({ label, ok }) => (\n <div\n key={label}\n className=\"flex items-center gap-2 text-sm\"\n >\n {ok ? (\n <CheckCircle2\n size={14}\n className=\"text-status-success-text\"\n />\n ) : (\n <div className=\"h-3.5 w-3.5 rounded-full border-2 border-muted-foreground\" />\n )}\n <span\n className={\n ok ? \"text-text-primary\" : \"text-text-secondary\"\n }\n >\n {label}\n </span>\n </div>\n ))}\n </div>\n </PageSection>\n\n {/* Stats */}\n <PageSection\n title={tr(\n \"devportal.marketplaceStatsTitle\",\n \"Marketplace Stats\",\n )}\n >\n <div className=\"space-y-3 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">\n {tr(\"devportal.publishedAppsLabel\", \"Published apps\")}\n </span>\n <span className=\"font-semibold\">\n {profile?.applicationCount ?? 0}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">\n {tr(\"devportal.totalInstallsLabel\", \"Total installs\")}\n </span>\n <span className=\"font-semibold\">\n {profile?.totalInstalls ?? 0}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">\n {tr(\"devportal.avgRatingLabel\", \"Avg rating\")}\n </span>\n <span className=\"font-semibold\">\n {profile?.averageRating\n ? profile.averageRating.toFixed(1)\n : \"—\"}\n </span>\n </div>\n </div>\n </PageSection>\n </div>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PublisherProfilePage.js","names":[],"sources":["../../../src/devportal/pages/PublisherProfilePage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Store,\n Save,\n CheckCircle2,\n AlertCircle,\n Loader2,\n Info,\n Landmark,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { InputField } from \"../components/FormField\";\nimport { cn } from \"../utils/cn\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport {\n useCreateDeveloperProfileMutation,\n useGetMyDeveloperProfileQuery,\n useUpdateDeveloperProfileMutation,\n} from \"../hooks/useGraphQL\";\n\n/**\n * Publisher Profile Page\n *\n * Lets developers enrich their store publisher presence.\n * The publisher profile in global-store-svc is auto-created when an app is\n * first published. This page surfaces the underlying developer profile fields\n * that feed into that publisher identity so developers can keep them current.\n */\nexport const PublisherProfilePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const { currentUser, navigate, basePath } = useDevportal();\n const goToPayouts = () => {\n const base = (basePath ?? \"\").replace(/\\/$/, \"\");\n navigate?.(`${base}/publisher/payouts`);\n };\n const { allowed: canSave } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.PROFILE_UPDATE,\n );\n const { data, loading, error, refetch } = useGetMyDeveloperProfileQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const [updateProfile, { loading: updating }] =\n useUpdateDeveloperProfileMutation();\n // A developer who arrives from the public registration journey has no\n // profile yet; updateDeveloperProfile refuses that case, so the first save\n // creates the profile instead (BOFF-10636).\n const [createProfile, { loading: creating }] =\n useCreateDeveloperProfileMutation();\n const saving = updating || creating;\n\n const profile = data?.myDeveloperProfile;\n\n // Form state — publisher-relevant fields\n const [companyName, setCompanyName] = useState(\"\");\n const [website, setWebsite] = useState(\"\");\n const [bio, setBio] = useState(\"\");\n const [githubUsername, setGithubUsername] = useState(\"\");\n const [twitterUsername, setTwitterUsername] = useState(\"\");\n const [linkedinUrl, setLinkedinUrl] = useState(\"\");\n\n const [saveStatus, setSaveStatus] = useState<\n \"idle\" | \"saving\" | \"saved\" | \"error\"\n >(\"idle\");\n\n useEffect(() => {\n if (profile) {\n setCompanyName(profile.companyName ?? \"\");\n setWebsite(profile.website ?? \"\");\n setBio(profile.bio ?? \"\");\n setGithubUsername(profile.githubUsername ?? \"\");\n setTwitterUsername(profile.twitterUsername ?? \"\");\n setLinkedinUrl(profile.linkedinUrl ?? \"\");\n } else if (!loading && currentUser) {\n // No profile yet — pre-fill display name from account\n const fullName = [currentUser.firstName, currentUser.lastName]\n .filter(Boolean)\n .join(\" \");\n if (fullName) setCompanyName(fullName);\n }\n }, [profile, loading, currentUser]);\n\n const handleSave = async () => {\n setSaveStatus(\"saving\");\n try {\n if (!profile) {\n const created = await createProfile({\n variables: {\n input: {\n companyName: companyName || undefined,\n website: website || undefined,\n bio: bio || undefined,\n githubUsername: githubUsername || undefined,\n twitterUsername: twitterUsername || undefined,\n linkedinUrl: linkedinUrl || undefined,\n },\n },\n });\n if (created.error) {\n throw created.error;\n }\n setSaveStatus(\"saved\");\n await refetch();\n setTimeout(() => setSaveStatus(\"idle\"), 3000);\n return;\n }\n const result = await updateProfile({\n variables: {\n userId: profile.userId,\n input: {\n companyName: companyName || null,\n website: website || null,\n bio: bio || null,\n githubUsername: githubUsername || null,\n twitterUsername: twitterUsername || null,\n linkedinUrl: linkedinUrl || null,\n // preserve existing notification prefs\n emailNotifications: profile.emailNotifications,\n marketingEmails: profile.marketingEmails,\n },\n },\n });\n if (result.error) {\n throw result.error;\n }\n setSaveStatus(\"saved\");\n await refetch();\n setTimeout(() => setSaveStatus(\"idle\"), 3000);\n } catch {\n setSaveStatus(\"error\");\n setTimeout(() => setSaveStatus(\"idle\"), 5000);\n }\n };\n\n if (loading && !profile) {\n return (\n <PageLayout\n title={tr(\"devportal.publisherProfileTitle\", \"Publisher Profile\")}\n description={tr(\n \"devportal.loadingPublisherProfile\",\n \"Loading your publisher profile...\",\n )}\n >\n <div className=\"flex items-center justify-center py-20\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error && !profile) {\n return (\n <PageLayout\n title={tr(\"devportal.publisherProfileTitle\", \"Publisher Profile\")}\n description={tr(\n \"devportal.manageStorePublisherPresence\",\n \"Manage your store publisher presence\",\n )}\n >\n <div className=\"flex flex-col items-center justify-center gap-4 py-20\">\n <AlertCircle className=\"h-12 w-12 text-status-error-text\" />\n <p className=\"text-text-secondary\">\n {tr(\"devportal.failedToLoadProfile\", \"Failed to load profile\")}\n </p>\n <button\n onClick={() => refetch()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.retryButton\", \"Retry\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.publisherProfileTitle\", \"Publisher Profile\")}\n description={tr(\n \"devportal.manageMarketplaceAppearance\",\n \"Manage how you appear to users in the marketplace\",\n )}\n purpose={tr(\n \"devportal.publisherProfilePurpose\",\n \"This is your public face in the marketplace — the name, logo, bio, and links users see before installing your apps. A complete, trustworthy profile makes shoppers more likely to install and is required before some apps can be published.\",\n )}\n actions={\n <button\n onClick={handleSave}\n disabled={saving || !canSave}\n aria-disabled={!canSave}\n className={cn(\n \"inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors\",\n saveStatus === \"saved\"\n ? \"bg-status-success-bg text-white\"\n : saveStatus === \"error\"\n ? \"bg-status-error-bg text-status-error-text\"\n : \"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90\",\n saving && \"opacity-50 cursor-not-allowed\",\n )}\n >\n {saving ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : saveStatus === \"saved\" ? (\n <CheckCircle2 size={16} />\n ) : saveStatus === \"error\" ? (\n <AlertCircle size={16} />\n ) : (\n <Save size={16} />\n )}\n {saving\n ? tr(\"devportal.savingButton\", \"Saving...\")\n : saveStatus === \"saved\"\n ? tr(\"devportal.savedButton\", \"Saved!\")\n : saveStatus === \"error\"\n ? tr(\"devportal.errorButton\", \"Error\")\n : tr(\"devportal.saveChangesButton\", \"Save Changes\")}\n </button>\n }\n >\n <div className=\"flex flex-col gap-6\">\n <div className=\"flex gap-3 rounded-lg border border-border-subtle bg-status-info-bg-subtle p-4 text-sm text-status-info-text\">\n <Info size={16} className=\"flex-shrink-0 mt-0.5\" />\n <div>\n <p className=\"font-medium\">\n {tr(\n \"devportal.publisherProfilesWorkTitle\",\n \"How publisher profiles work\",\n )}\n </p>\n <p className=\"mt-1 text-status-info-text\">\n {tr(\n \"devportal.publisherProfilesWorkDesc\",\n \"When you publish your first app, a publisher profile is automatically created in the marketplace using your developer details. Keep this information up to date so users can trust and find your apps. Changes here are reflected when your next app is published.\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Main form */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Publisher identity */}\n <PageSection\n title={tr(\n \"devportal.publisherIdentityTitle\",\n \"Publisher Identity\",\n )}\n description={tr(\n \"devportal.publisherIdentityDesc\",\n \"Displayed on your marketplace listings\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\n \"devportal.displayNameLabel\",\n \"Display Name / Company\",\n )}\n value={companyName}\n onChange={(e) => setCompanyName(e.target.value)}\n placeholder={tr(\n \"devportal.displayNamePlaceholder\",\n \"Your name or company (shown on all your apps)\",\n )}\n />\n <div>\n <label className=\"text-sm font-medium text-text-primary\">\n {tr(\"devportal.bioLabel\", \"Bio\")}\n </label>\n <textarea\n value={bio}\n onChange={(e) => setBio(e.target.value)}\n placeholder={tr(\n \"devportal.bioPlaceholder\",\n \"A short description of you or your company shown on the marketplace...\",\n )}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n <InputField\n label={tr(\"devportal.websiteLabel\", \"Website\")}\n value={website}\n onChange={(e) => setWebsite(e.target.value)}\n placeholder={tr(\n \"devportal.websitePlaceholder\",\n \"https://your-website.com\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Social links */}\n <PageSection\n title={tr(\"devportal.socialLinksTitle\", \"Social Links\")}\n description={tr(\n \"devportal.socialLinksDesc\",\n \"Build trust with your developer community\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\n \"devportal.githubUsernameLabel\",\n \"GitHub Username\",\n )}\n value={githubUsername}\n onChange={(e) => setGithubUsername(e.target.value)}\n placeholder={tr(\n \"devportal.githubUsernamePlaceholder\",\n \"your-github-username\",\n )}\n />\n <InputField\n label={tr(\n \"devportal.twitterUsernameLabel\",\n \"Twitter / X Username\",\n )}\n value={twitterUsername}\n onChange={(e) => setTwitterUsername(e.target.value)}\n placeholder={tr(\n \"devportal.twitterUsernamePlaceholder\",\n \"your_twitter\",\n )}\n />\n <InputField\n label={tr(\"devportal.linkedinUrlLabel\", \"LinkedIn URL\")}\n value={linkedinUrl}\n onChange={(e) => setLinkedinUrl(e.target.value)}\n placeholder={tr(\n \"devportal.linkedinUrlPlaceholder\",\n \"https://linkedin.com/in/you\",\n )}\n />\n </div>\n </PageSection>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Publisher status */}\n <PageSection\n title={tr(\"devportal.publisherStatusTitle\", \"Publisher Status\")}\n >\n <div className=\"flex items-center gap-3\">\n <Store className=\"h-8 w-8 text-primary\" />\n <div>\n <p className=\"font-medium\">\n {profile?.isVerified\n ? tr(\n \"devportal.verifiedPublisher\",\n \"Verified Publisher\",\n )\n : tr(\"devportal.publisher\", \"Publisher\")}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {profile?.isVerified\n ? tr(\n \"devportal.publisherIdentityVerified\",\n \"Your publisher identity is verified\",\n )\n : tr(\n \"devportal.publishFirstAppMessage\",\n \"Publish your first app to appear in the marketplace\",\n )}\n </p>\n </div>\n </div>\n </PageSection>\n\n {/* Payout methods (BOFF-10953) */}\n <PageSection\n title={tr(\"devportal.payouts.title\", \"Payouts\")}\n description={tr(\n \"devportal.payouts.profileCardDescription\",\n \"Add and verify where the marketplace sends your earnings\",\n )}\n >\n <button\n type=\"button\"\n onClick={() => goToPayouts()}\n className=\"flex items-center gap-2 text-sm font-medium text-primary hover:underline\"\n >\n <Landmark size={16} />\n {tr(\"devportal.payouts.manage\", \"Manage payout methods\")}\n </button>\n </PageSection>\n\n {/* Profile completeness */}\n <PageSection\n title={tr(\n \"devportal.profileCompletenessTitle\",\n \"Profile Completeness\",\n )}\n >\n <div className=\"space-y-2\">\n {[\n {\n label: tr(\n \"devportal.completenessDisplayName\",\n \"Display name\",\n ),\n ok: !!companyName,\n },\n {\n label: tr(\"devportal.completenessBio\", \"Bio\"),\n ok: !!bio,\n },\n {\n label: tr(\"devportal.completenessWebsite\", \"Website\"),\n ok: !!website,\n },\n {\n label: tr(\"devportal.completenessGithub\", \"GitHub\"),\n ok: !!githubUsername,\n },\n {\n label: tr(\n \"devportal.completenessSocialLinks\",\n \"Social links\",\n ),\n ok: !!twitterUsername || !!linkedinUrl,\n },\n ].map(({ label, ok }) => (\n <div\n key={label}\n className=\"flex items-center gap-2 text-sm\"\n >\n {ok ? (\n <CheckCircle2\n size={14}\n className=\"text-status-success-text\"\n />\n ) : (\n <div className=\"h-3.5 w-3.5 rounded-full border-2 border-muted-foreground\" />\n )}\n <span\n className={\n ok ? \"text-text-primary\" : \"text-text-secondary\"\n }\n >\n {label}\n </span>\n </div>\n ))}\n </div>\n </PageSection>\n\n {/* Stats */}\n <PageSection\n title={tr(\n \"devportal.marketplaceStatsTitle\",\n \"Marketplace Stats\",\n )}\n >\n <div className=\"space-y-3 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">\n {tr(\"devportal.publishedAppsLabel\", \"Published apps\")}\n </span>\n <span className=\"font-semibold\">\n {profile?.applicationCount ?? 0}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">\n {tr(\"devportal.totalInstallsLabel\", \"Total installs\")}\n </span>\n <span className=\"font-semibold\">\n {profile?.totalInstalls ?? 0}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">\n {tr(\"devportal.avgRatingLabel\", \"Avg rating\")}\n </span>\n <span className=\"font-semibold\">\n {profile?.averageRating\n ? profile.averageRating.toFixed(1)\n : \"—\"}\n </span>\n </div>\n </div>\n </PageSection>\n </div>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publisher payout methods and automatic processor onboarding (BOFF-10953).
|
|
3
|
+
*
|
|
4
|
+
* Pure helpers behind the Payouts page: which payout method types a country
|
|
5
|
+
* can use, what state a method is in from the publisher's point of view,
|
|
6
|
+
* client-side checks that mirror the store's validation (the store stays
|
|
7
|
+
* authoritative and re-validates everything), and the mapping from store
|
|
8
|
+
* error codes to messages a publisher can act on.
|
|
9
|
+
*
|
|
10
|
+
* Backend contract: global-store-svc `myPublisherPayoutMethods`,
|
|
11
|
+
* `addMyPublisherPayoutMethod`, `startMyPublisherPayoutOnboarding` and
|
|
12
|
+
* `refreshMyPublisherPayoutOnboarding` (BOFF-10634, BOFF-10895).
|
|
13
|
+
*/
|
|
14
|
+
export type PayoutMethodTypeValue = "BANK_TRANSFER" | "PAYPAL" | "RAZORPAY_X" | "STRIPE_CONNECT";
|
|
15
|
+
/** Method types offered for India (RazorpayX payouts in INR). */
|
|
16
|
+
export declare const INDIA_METHOD_TYPES: readonly PayoutMethodTypeValue[];
|
|
17
|
+
/** Method types offered everywhere else (Stripe Connect in USD). */
|
|
18
|
+
export declare const INTERNATIONAL_METHOD_TYPES: readonly PayoutMethodTypeValue[];
|
|
19
|
+
export interface PayoutRouteLike {
|
|
20
|
+
country?: string | null;
|
|
21
|
+
currency: string;
|
|
22
|
+
processor: string;
|
|
23
|
+
methodTypes: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
export interface PayoutMethodLike {
|
|
26
|
+
id: string;
|
|
27
|
+
type: string;
|
|
28
|
+
status: string;
|
|
29
|
+
processorStatus?: string | null;
|
|
30
|
+
processorFailureReason?: string | null;
|
|
31
|
+
rejectionReason?: string | null;
|
|
32
|
+
}
|
|
33
|
+
/** Normalise a country to an upper-case ISO 3166-1 alpha-2 code, or "". */
|
|
34
|
+
export declare function normalizeCountry(value: string | null | undefined): string;
|
|
35
|
+
/**
|
|
36
|
+
* The payout method types to offer for the country the publisher entered.
|
|
37
|
+
* The store's payoutRoute is used as-is when it is for that same country (or
|
|
38
|
+
* no country is known yet); otherwise the same India / rest-of-world rule the
|
|
39
|
+
* store applies decides. PAYPAL is never offered.
|
|
40
|
+
*/
|
|
41
|
+
export declare function methodTypesForCountry(country: string | null | undefined, route: PayoutRouteLike | null | undefined): PayoutMethodTypeValue[];
|
|
42
|
+
/** Default payout currency for a country (INR for India, USD elsewhere). */
|
|
43
|
+
export declare function defaultCurrencyForCountry(country: string | null | undefined): string;
|
|
44
|
+
/** True for the method types that collect bank details when added. */
|
|
45
|
+
export declare function needsBankDetails(type: string): boolean;
|
|
46
|
+
/** True for the method types that support automatic processor onboarding. */
|
|
47
|
+
export declare function supportsAutomaticOnboarding(type: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Where a payout method stands, from the publisher's point of view.
|
|
50
|
+
*
|
|
51
|
+
* - VERIFIED: payouts can be sent to it.
|
|
52
|
+
* - NOT_STARTED: pending, automatic verification has not been started.
|
|
53
|
+
* - IN_PROGRESS: the processor is still checking the account.
|
|
54
|
+
* - AWAITING_KYC: the processor confirmed the account; the method turns
|
|
55
|
+
* VERIFIED once the publisher's own verification passes.
|
|
56
|
+
* - FAILED: the processor could not verify the account; start again.
|
|
57
|
+
* - MANUAL_REVIEW: a bank transfer the marketplace team verifies by hand.
|
|
58
|
+
* - REJECTED / SUSPENDED / CLOSED: final states set by the marketplace.
|
|
59
|
+
*/
|
|
60
|
+
export type PayoutMethodPhase = "VERIFIED" | "NOT_STARTED" | "IN_PROGRESS" | "AWAITING_KYC" | "FAILED" | "MANUAL_REVIEW" | "REJECTED" | "SUSPENDED" | "CLOSED";
|
|
61
|
+
export declare function payoutMethodPhase(method: PayoutMethodLike): PayoutMethodPhase;
|
|
62
|
+
/** The publisher may (re)start automatic onboarding for this method. */
|
|
63
|
+
export declare function canStartOnboarding(method: PayoutMethodLike): boolean;
|
|
64
|
+
/** Methods whose processor state is worth re-reading while the page is open. */
|
|
65
|
+
export declare function methodsToPoll<T extends PayoutMethodLike>(methods: readonly T[]): T[];
|
|
66
|
+
/** Stripe onboarding hands the publisher back with ?onboarding=return|refresh. */
|
|
67
|
+
export type OnboardingRedirect = "return" | "refresh" | null;
|
|
68
|
+
export declare function readOnboardingRedirect(search: string): OnboardingRedirect;
|
|
69
|
+
/** Pending Stripe methods that already have a processor account. */
|
|
70
|
+
export declare function stripeMethodsInOnboarding<T extends PayoutMethodLike>(methods: readonly T[]): T[];
|
|
71
|
+
/**
|
|
72
|
+
* Send the browser to the processor's hosted onboarding page. Kept here so
|
|
73
|
+
* the page's redirect can be replaced in tests.
|
|
74
|
+
*/
|
|
75
|
+
export declare function redirectToOnboarding(url: string): void;
|
|
76
|
+
/** The redirect URL returned by the store, accepted only over https. */
|
|
77
|
+
export declare function safeOnboardingUrl(url: string | null | undefined): string | null;
|
|
78
|
+
export declare function isValidBankCode(code: string, country: string): boolean;
|
|
79
|
+
export declare function isValidEmail(value: string): boolean;
|
|
80
|
+
export declare function normalizePhone(value: string): string;
|
|
81
|
+
export interface AddPayoutMethodForm {
|
|
82
|
+
type: string;
|
|
83
|
+
country: string;
|
|
84
|
+
accountHolderName: string;
|
|
85
|
+
accountNumberLast4: string;
|
|
86
|
+
bankCode: string;
|
|
87
|
+
bankName: string;
|
|
88
|
+
isDefault: boolean;
|
|
89
|
+
}
|
|
90
|
+
export type AddPayoutMethodField = "type" | "country" | "accountHolderName" | "accountNumberLast4" | "bankCode";
|
|
91
|
+
/** Field errors for the add form, as message keys with English fallbacks. */
|
|
92
|
+
export declare function validateAddPayoutMethod(form: AddPayoutMethodForm, route: PayoutRouteLike | null | undefined): Partial<Record<AddPayoutMethodField, string>>;
|
|
93
|
+
/** The addMyPublisherPayoutMethod input for a validated form. */
|
|
94
|
+
export declare function toAddPayoutMethodInput(form: AddPayoutMethodForm): {
|
|
95
|
+
type: PayoutMethodTypeValue;
|
|
96
|
+
country: string;
|
|
97
|
+
currency: string;
|
|
98
|
+
accountHolderName: string;
|
|
99
|
+
accountNumberLast4?: string;
|
|
100
|
+
bankCode?: string;
|
|
101
|
+
bankName?: string;
|
|
102
|
+
isDefault: boolean;
|
|
103
|
+
};
|
|
104
|
+
export interface OnboardingForm {
|
|
105
|
+
contactEmail: string;
|
|
106
|
+
contactPhone: string;
|
|
107
|
+
accountNumber: string;
|
|
108
|
+
}
|
|
109
|
+
export type OnboardingField = keyof OnboardingForm;
|
|
110
|
+
/**
|
|
111
|
+
* Field errors for the "Verify automatically" form. RazorpayX also needs the
|
|
112
|
+
* full account number once, which must end in the registered last four digits.
|
|
113
|
+
*/
|
|
114
|
+
export declare function validateOnboarding(type: string, form: OnboardingForm, accountLast4: string | null | undefined): Partial<Record<OnboardingField, string>>;
|
|
115
|
+
/** The startMyPublisherPayoutOnboarding input for a validated form. */
|
|
116
|
+
export declare function toOnboardingInput(type: string, form: OnboardingForm): {
|
|
117
|
+
contactEmail: string;
|
|
118
|
+
contactPhone?: string;
|
|
119
|
+
accountNumber?: string;
|
|
120
|
+
};
|
|
121
|
+
/** Last four digits recorded on a method, read from its masked details. */
|
|
122
|
+
export declare function accountLast4Of(maskedDetails: Record<string, unknown> | null | undefined): string | null;
|
|
123
|
+
export type PayoutAction = "agreement" | "add" | "remove" | "start" | "refresh";
|
|
124
|
+
export declare const PAYOUT_GENERIC_ERROR = "Something went wrong. Nothing was changed. Try again in a moment; if it keeps failing, contact support.";
|
|
125
|
+
interface ErrorEntry {
|
|
126
|
+
code: string | null;
|
|
127
|
+
message: string | null;
|
|
128
|
+
}
|
|
129
|
+
/** The first GraphQL error (code and message) carried by an Apollo error. */
|
|
130
|
+
export declare function firstGraphQLError(error: unknown): ErrorEntry;
|
|
131
|
+
/**
|
|
132
|
+
* A message the publisher can act on for a failed payout action. The store's
|
|
133
|
+
* own message is kept for input errors because it names the field at fault.
|
|
134
|
+
*/
|
|
135
|
+
export declare function payoutErrorMessage(error: unknown, action: PayoutAction): string;
|
|
136
|
+
export {};
|
|
137
|
+
//# sourceMappingURL=publisherPayouts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publisherPayouts.d.ts","sourceRoot":"","sources":["../../../src/devportal/utils/publisherPayouts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
//#region src/devportal/utils/publisherPayouts.ts
|
|
2
|
+
var e = ["RAZORPAY_X", "BANK_TRANSFER"], t = ["STRIPE_CONNECT", "BANK_TRANSFER"];
|
|
3
|
+
function n(e) {
|
|
4
|
+
let t = (e ?? "").trim().toUpperCase();
|
|
5
|
+
return /^[A-Z]{2}$/.test(t) ? t : "";
|
|
6
|
+
}
|
|
7
|
+
function r(e) {
|
|
8
|
+
return e === "BANK_TRANSFER" || e === "PAYPAL" || e === "RAZORPAY_X" || e === "STRIPE_CONNECT";
|
|
9
|
+
}
|
|
10
|
+
function i(i, a) {
|
|
11
|
+
let o = n(i), s = n(a?.country);
|
|
12
|
+
if (a && (o === "" || o === s)) {
|
|
13
|
+
let e = a.methodTypes.filter(r).filter((e) => e !== "PAYPAL");
|
|
14
|
+
if (e.length > 0) return e;
|
|
15
|
+
}
|
|
16
|
+
return [...o === "IN" ? e : t];
|
|
17
|
+
}
|
|
18
|
+
function a(e) {
|
|
19
|
+
return n(e) === "IN" ? "INR" : "USD";
|
|
20
|
+
}
|
|
21
|
+
function o(e) {
|
|
22
|
+
return e === "BANK_TRANSFER" || e === "RAZORPAY_X";
|
|
23
|
+
}
|
|
24
|
+
function s(e) {
|
|
25
|
+
return e === "RAZORPAY_X" || e === "STRIPE_CONNECT";
|
|
26
|
+
}
|
|
27
|
+
function c(e) {
|
|
28
|
+
switch (e.status) {
|
|
29
|
+
case "VERIFIED": return "VERIFIED";
|
|
30
|
+
case "REJECTED": return "REJECTED";
|
|
31
|
+
case "SUSPENDED": return "SUSPENDED";
|
|
32
|
+
case "CLOSED": return "CLOSED";
|
|
33
|
+
default: break;
|
|
34
|
+
}
|
|
35
|
+
if (!s(e.type)) return "MANUAL_REVIEW";
|
|
36
|
+
switch (e.processorStatus) {
|
|
37
|
+
case "PENDING_VERIFICATION": return "IN_PROGRESS";
|
|
38
|
+
case "ACTIVE": return "AWAITING_KYC";
|
|
39
|
+
case "FAILED_VERIFICATION": return "FAILED";
|
|
40
|
+
default: return "NOT_STARTED";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function l(e) {
|
|
44
|
+
let t = c(e);
|
|
45
|
+
return t === "NOT_STARTED" || t === "FAILED" ? !0 : t === "IN_PROGRESS" && e.type === "STRIPE_CONNECT";
|
|
46
|
+
}
|
|
47
|
+
function u(e) {
|
|
48
|
+
return e.filter((e) => c(e) === "IN_PROGRESS");
|
|
49
|
+
}
|
|
50
|
+
function d(e) {
|
|
51
|
+
let t = new URLSearchParams(e).get("onboarding");
|
|
52
|
+
return t === "return" || t === "refresh" ? t : null;
|
|
53
|
+
}
|
|
54
|
+
function f(e) {
|
|
55
|
+
return e.filter((e) => e.type === "STRIPE_CONNECT" && e.status === "PENDING" && !!e.processorStatus);
|
|
56
|
+
}
|
|
57
|
+
function p(e) {
|
|
58
|
+
window.location.assign(e);
|
|
59
|
+
}
|
|
60
|
+
function m(e) {
|
|
61
|
+
if (!e) return null;
|
|
62
|
+
try {
|
|
63
|
+
let t = new URL(e);
|
|
64
|
+
return t.protocol === "https:" ? t.toString() : null;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
var h = /^[A-Z]{4}0[A-Z0-9]{6}$/, g = /^[A-Z]{6}[A-Z0-9]+$/, _ = /^\d{4}$/, v = /^[A-Za-z0-9]{5,35}$/, y = /^\+?\d{8,15}$/;
|
|
70
|
+
function b(e, t) {
|
|
71
|
+
let r = e.trim().toUpperCase();
|
|
72
|
+
return n(t) === "IN" ? h.test(r) : (r.length === 8 || r.length === 11) && g.test(r);
|
|
73
|
+
}
|
|
74
|
+
function x(e) {
|
|
75
|
+
let t = e.trim();
|
|
76
|
+
if (!t || t.length > 254 || /\s/.test(t)) return !1;
|
|
77
|
+
let n = t.indexOf("@");
|
|
78
|
+
if (n < 1 || n !== t.lastIndexOf("@")) return !1;
|
|
79
|
+
let r = t.slice(n + 1);
|
|
80
|
+
return r.includes(".") && !r.startsWith(".") && !r.endsWith(".");
|
|
81
|
+
}
|
|
82
|
+
function S(e) {
|
|
83
|
+
return e.replace(/[\s()-]/g, "");
|
|
84
|
+
}
|
|
85
|
+
function C(e, t) {
|
|
86
|
+
let r = {}, a = n(e.country);
|
|
87
|
+
a || (r.country = "Enter the two-letter country code of the account, for example IN or US."), e.type ? a && !i(a, t).some((t) => t === e.type) && (r.type = "This payout method is not available for that country.") : r.type = "Choose how you want to be paid.";
|
|
88
|
+
let s = e.accountHolderName.trim();
|
|
89
|
+
return (!s || s.length > 140) && (r.accountHolderName = "Enter the account holder's name exactly as the bank has it."), o(e.type) && (_.test(e.accountNumberLast4.trim()) || (r.accountNumberLast4 = "Enter the last four digits of the account number."), (!a || !b(e.bankCode, a)) && (r.bankCode = a === "IN" ? "Enter a valid 11-character IFSC, for example HDFC0001234." : "Enter a valid 8 or 11-character SWIFT/BIC code.")), r;
|
|
90
|
+
}
|
|
91
|
+
function w(e) {
|
|
92
|
+
let t = n(e.country), i = r(e.type) ? e.type : "BANK_TRANSFER", s = e.bankName.trim();
|
|
93
|
+
return {
|
|
94
|
+
type: i,
|
|
95
|
+
country: t,
|
|
96
|
+
currency: a(t),
|
|
97
|
+
accountHolderName: e.accountHolderName.trim(),
|
|
98
|
+
...o(i) ? {
|
|
99
|
+
accountNumberLast4: e.accountNumberLast4.trim(),
|
|
100
|
+
bankCode: e.bankCode.trim().toUpperCase(),
|
|
101
|
+
...s ? { bankName: s } : {}
|
|
102
|
+
} : {},
|
|
103
|
+
isDefault: e.isDefault
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function T(e, t, n) {
|
|
107
|
+
let r = {};
|
|
108
|
+
x(t.contactEmail) || (r.contactEmail = "Enter a valid contact email.");
|
|
109
|
+
let i = S(t.contactPhone);
|
|
110
|
+
if (i && !y.test(i) && (r.contactPhone = "Enter 8 to 15 digits, with an optional leading +."), e === "RAZORPAY_X") {
|
|
111
|
+
let e = t.accountNumber.replace(/\s+/g, "");
|
|
112
|
+
v.test(e) ? n && !e.endsWith(n) && (r.accountNumber = `The account number must end in ${n}, the digits you registered.`) : r.accountNumber = "Enter the full account number (5 to 35 letters or digits).";
|
|
113
|
+
}
|
|
114
|
+
return r;
|
|
115
|
+
}
|
|
116
|
+
function E(e, t) {
|
|
117
|
+
let n = S(t.contactPhone);
|
|
118
|
+
return {
|
|
119
|
+
contactEmail: t.contactEmail.trim(),
|
|
120
|
+
...n ? { contactPhone: n } : {},
|
|
121
|
+
...e === "RAZORPAY_X" ? { accountNumber: t.accountNumber.replace(/\s+/g, "") } : {}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function D(e) {
|
|
125
|
+
let t = e?.accountLast4;
|
|
126
|
+
return typeof t == "string" && /^\d{4}$/.test(t) ? t : null;
|
|
127
|
+
}
|
|
128
|
+
function O(e) {
|
|
129
|
+
return typeof e == "object" && e && !Array.isArray(e) ? e : null;
|
|
130
|
+
}
|
|
131
|
+
function k(e) {
|
|
132
|
+
if (typeof e != "string") return null;
|
|
133
|
+
let t = e.trim();
|
|
134
|
+
return !t || /^(unexpected error|response not successful|failed to fetch|network ?error)/i.test(t) ? null : t;
|
|
135
|
+
}
|
|
136
|
+
function A(e) {
|
|
137
|
+
let t = O(e);
|
|
138
|
+
for (let e of ["errors", "graphQLErrors"]) {
|
|
139
|
+
let n = t?.[e];
|
|
140
|
+
if (!Array.isArray(n) || n.length === 0) continue;
|
|
141
|
+
let r = O(n[0]), i = O(r?.extensions)?.code;
|
|
142
|
+
return {
|
|
143
|
+
code: typeof i == "string" ? i : null,
|
|
144
|
+
message: k(r?.message)
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
code: null,
|
|
149
|
+
message: e instanceof Error ? k(e.message) : k(t?.message)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function j(e, t) {
|
|
153
|
+
let { code: n, message: r } = A(e);
|
|
154
|
+
switch (n) {
|
|
155
|
+
case "FAILED_PRECONDITION": return t === "start" || t === "refresh" ? "Automatic verification is not available for this processor yet. Your payout method stays pending and the marketplace team will verify it." : t === "add" ? "Accept the current Marketplace & Developer Agreement before adding a payout method." : r ?? "Something went wrong. Nothing was changed. Try again in a moment; if it keeps failing, contact support.";
|
|
156
|
+
case "BAD_USER_INPUT": return r ?? "Some of the details are not valid. Check them and try again.";
|
|
157
|
+
case "SERVICE_UNAVAILABLE": return "Payout onboarding is temporarily unavailable. Nothing was changed. Try again in a few minutes.";
|
|
158
|
+
case "NOT_FOUND": return t === "add" || t === "agreement" ? "You do not have a publisher profile yet. It is created when you submit your first app to the store." : "This payout method no longer exists. Reload the page.";
|
|
159
|
+
case "FORBIDDEN": return "Your publisher profile is suspended, so payout methods cannot be changed. Contact support.";
|
|
160
|
+
case "INVALID_STATE": return "This payout method is no longer pending, so it cannot be verified again. Reload the page.";
|
|
161
|
+
default: return r ?? "Something went wrong. Nothing was changed. Try again in a moment; if it keeps failing, contact support.";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
export { D as accountLast4Of, l as canStartOnboarding, i as methodTypesForCountry, u as methodsToPoll, o as needsBankDetails, n as normalizeCountry, j as payoutErrorMessage, c as payoutMethodPhase, d as readOnboardingRedirect, p as redirectToOnboarding, m as safeOnboardingUrl, f as stripeMethodsInOnboarding, s as supportsAutomaticOnboarding, w as toAddPayoutMethodInput, E as toOnboardingInput, C as validateAddPayoutMethod, T as validateOnboarding };
|
|
166
|
+
|
|
167
|
+
//# sourceMappingURL=publisherPayouts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publisherPayouts.js","names":[],"sources":["../../../src/devportal/utils/publisherPayouts.ts"],"sourcesContent":["/**\n * Publisher payout methods and automatic processor onboarding (BOFF-10953).\n *\n * Pure helpers behind the Payouts page: which payout method types a country\n * can use, what state a method is in from the publisher's point of view,\n * client-side checks that mirror the store's validation (the store stays\n * authoritative and re-validates everything), and the mapping from store\n * error codes to messages a publisher can act on.\n *\n * Backend contract: global-store-svc `myPublisherPayoutMethods`,\n * `addMyPublisherPayoutMethod`, `startMyPublisherPayoutOnboarding` and\n * `refreshMyPublisherPayoutOnboarding` (BOFF-10634, BOFF-10895).\n */\n\nexport type PayoutMethodTypeValue =\n | \"BANK_TRANSFER\"\n | \"PAYPAL\"\n | \"RAZORPAY_X\"\n | \"STRIPE_CONNECT\";\n\n/** Method types offered for India (RazorpayX payouts in INR). */\nexport const INDIA_METHOD_TYPES: readonly PayoutMethodTypeValue[] = [\n \"RAZORPAY_X\",\n \"BANK_TRANSFER\",\n];\n\n/** Method types offered everywhere else (Stripe Connect in USD). */\nexport const INTERNATIONAL_METHOD_TYPES: readonly PayoutMethodTypeValue[] = [\n \"STRIPE_CONNECT\",\n \"BANK_TRANSFER\",\n];\n\nexport interface PayoutRouteLike {\n country?: string | null;\n currency: string;\n processor: string;\n methodTypes: readonly string[];\n}\n\nexport interface PayoutMethodLike {\n id: string;\n type: string;\n status: string;\n processorStatus?: string | null;\n processorFailureReason?: string | null;\n rejectionReason?: string | null;\n}\n\n/** Normalise a country to an upper-case ISO 3166-1 alpha-2 code, or \"\". */\nexport function normalizeCountry(value: string | null | undefined): string {\n const code = (value ?? \"\").trim().toUpperCase();\n return /^[A-Z]{2}$/.test(code) ? code : \"\";\n}\n\nfunction isPayoutMethodType(value: string): value is PayoutMethodTypeValue {\n return (\n value === \"BANK_TRANSFER\" ||\n value === \"PAYPAL\" ||\n value === \"RAZORPAY_X\" ||\n value === \"STRIPE_CONNECT\"\n );\n}\n\n/**\n * The payout method types to offer for the country the publisher entered.\n * The store's payoutRoute is used as-is when it is for that same country (or\n * no country is known yet); otherwise the same India / rest-of-world rule the\n * store applies decides. PAYPAL is never offered.\n */\nexport function methodTypesForCountry(\n country: string | null | undefined,\n route: PayoutRouteLike | null | undefined,\n): PayoutMethodTypeValue[] {\n const code = normalizeCountry(country);\n const routeCountry = normalizeCountry(route?.country);\n if (route && (code === \"\" || code === routeCountry)) {\n const fromRoute = route.methodTypes\n .filter(isPayoutMethodType)\n .filter((t) => t !== \"PAYPAL\");\n if (fromRoute.length > 0) return fromRoute;\n }\n return [...(code === \"IN\" ? INDIA_METHOD_TYPES : INTERNATIONAL_METHOD_TYPES)];\n}\n\n/** Default payout currency for a country (INR for India, USD elsewhere). */\nexport function defaultCurrencyForCountry(\n country: string | null | undefined,\n): string {\n return normalizeCountry(country) === \"IN\" ? \"INR\" : \"USD\";\n}\n\n/** True for the method types that collect bank details when added. */\nexport function needsBankDetails(type: string): boolean {\n return type === \"BANK_TRANSFER\" || type === \"RAZORPAY_X\";\n}\n\n/** True for the method types that support automatic processor onboarding. */\nexport function supportsAutomaticOnboarding(type: string): boolean {\n return type === \"RAZORPAY_X\" || type === \"STRIPE_CONNECT\";\n}\n\n/**\n * Where a payout method stands, from the publisher's point of view.\n *\n * - VERIFIED: payouts can be sent to it.\n * - NOT_STARTED: pending, automatic verification has not been started.\n * - IN_PROGRESS: the processor is still checking the account.\n * - AWAITING_KYC: the processor confirmed the account; the method turns\n * VERIFIED once the publisher's own verification passes.\n * - FAILED: the processor could not verify the account; start again.\n * - MANUAL_REVIEW: a bank transfer the marketplace team verifies by hand.\n * - REJECTED / SUSPENDED / CLOSED: final states set by the marketplace.\n */\nexport type PayoutMethodPhase =\n | \"VERIFIED\"\n | \"NOT_STARTED\"\n | \"IN_PROGRESS\"\n | \"AWAITING_KYC\"\n | \"FAILED\"\n | \"MANUAL_REVIEW\"\n | \"REJECTED\"\n | \"SUSPENDED\"\n | \"CLOSED\";\n\nexport function payoutMethodPhase(method: PayoutMethodLike): PayoutMethodPhase {\n switch (method.status) {\n case \"VERIFIED\":\n return \"VERIFIED\";\n case \"REJECTED\":\n return \"REJECTED\";\n case \"SUSPENDED\":\n return \"SUSPENDED\";\n case \"CLOSED\":\n return \"CLOSED\";\n default:\n break;\n }\n if (!supportsAutomaticOnboarding(method.type)) return \"MANUAL_REVIEW\";\n switch (method.processorStatus) {\n case \"PENDING_VERIFICATION\":\n return \"IN_PROGRESS\";\n case \"ACTIVE\":\n return \"AWAITING_KYC\";\n case \"FAILED_VERIFICATION\":\n return \"FAILED\";\n default:\n return \"NOT_STARTED\";\n }\n}\n\n/** The publisher may (re)start automatic onboarding for this method. */\nexport function canStartOnboarding(method: PayoutMethodLike): boolean {\n const phase = payoutMethodPhase(method);\n if (phase === \"NOT_STARTED\" || phase === \"FAILED\") return true;\n // A Stripe link can expire before the publisher finishes, so an in-progress\n // Stripe method can always ask for a fresh link.\n return phase === \"IN_PROGRESS\" && method.type === \"STRIPE_CONNECT\";\n}\n\n/** Methods whose processor state is worth re-reading while the page is open. */\nexport function methodsToPoll<T extends PayoutMethodLike>(\n methods: readonly T[],\n): T[] {\n return methods.filter((m) => payoutMethodPhase(m) === \"IN_PROGRESS\");\n}\n\n/** Stripe onboarding hands the publisher back with ?onboarding=return|refresh. */\nexport type OnboardingRedirect = \"return\" | \"refresh\" | null;\n\nexport function readOnboardingRedirect(search: string): OnboardingRedirect {\n const value = new URLSearchParams(search).get(\"onboarding\");\n return value === \"return\" || value === \"refresh\" ? value : null;\n}\n\n/** Pending Stripe methods that already have a processor account. */\nexport function stripeMethodsInOnboarding<T extends PayoutMethodLike>(\n methods: readonly T[],\n): T[] {\n return methods.filter(\n (m) =>\n m.type === \"STRIPE_CONNECT\" &&\n m.status === \"PENDING\" &&\n Boolean(m.processorStatus),\n );\n}\n\n/**\n * Send the browser to the processor's hosted onboarding page. Kept here so\n * the page's redirect can be replaced in tests.\n */\nexport function redirectToOnboarding(url: string): void {\n window.location.assign(url);\n}\n\n/** The redirect URL returned by the store, accepted only over https. */\nexport function safeOnboardingUrl(\n url: string | null | undefined,\n): string | null {\n if (!url) return null;\n try {\n const parsed = new URL(url);\n return parsed.protocol === \"https:\" ? parsed.toString() : null;\n } catch {\n return null;\n }\n}\n\n// ── Client-side validation (mirrors global-store-svc) ───────────────────────\n\nconst IFSC_PATTERN = /^[A-Z]{4}0[A-Z0-9]{6}$/;\nconst SWIFT_PATTERN = /^[A-Z]{6}[A-Z0-9]+$/;\nconst LAST4_PATTERN = /^\\d{4}$/;\nconst ACCOUNT_NUMBER_PATTERN = /^[A-Za-z0-9]{5,35}$/;\nconst PHONE_PATTERN = /^\\+?\\d{8,15}$/;\n\nexport function isValidBankCode(code: string, country: string): boolean {\n const value = code.trim().toUpperCase();\n if (normalizeCountry(country) === \"IN\") return IFSC_PATTERN.test(value);\n return (\n (value.length === 8 || value.length === 11) && SWIFT_PATTERN.test(value)\n );\n}\n\nexport function isValidEmail(value: string): boolean {\n const email = value.trim();\n if (!email || email.length > 254 || /\\s/.test(email)) return false;\n const at = email.indexOf(\"@\");\n if (at < 1 || at !== email.lastIndexOf(\"@\")) return false;\n const domain = email.slice(at + 1);\n return (\n domain.includes(\".\") && !domain.startsWith(\".\") && !domain.endsWith(\".\")\n );\n}\n\nexport function normalizePhone(value: string): string {\n return value.replace(/[\\s()-]/g, \"\");\n}\n\nexport interface AddPayoutMethodForm {\n type: string;\n country: string;\n accountHolderName: string;\n accountNumberLast4: string;\n bankCode: string;\n bankName: string;\n isDefault: boolean;\n}\n\nexport type AddPayoutMethodField =\n | \"type\"\n | \"country\"\n | \"accountHolderName\"\n | \"accountNumberLast4\"\n | \"bankCode\";\n\n/** Field errors for the add form, as message keys with English fallbacks. */\nexport function validateAddPayoutMethod(\n form: AddPayoutMethodForm,\n route: PayoutRouteLike | null | undefined,\n): Partial<Record<AddPayoutMethodField, string>> {\n const errors: Partial<Record<AddPayoutMethodField, string>> = {};\n const country = normalizeCountry(form.country);\n if (!country) {\n errors.country =\n \"Enter the two-letter country code of the account, for example IN or US.\";\n }\n if (!form.type) {\n errors.type = \"Choose how you want to be paid.\";\n } else if (\n country &&\n !methodTypesForCountry(country, route).some((t) => t === form.type)\n ) {\n errors.type = \"This payout method is not available for that country.\";\n }\n const holder = form.accountHolderName.trim();\n if (!holder || holder.length > 140) {\n errors.accountHolderName =\n \"Enter the account holder's name exactly as the bank has it.\";\n }\n if (needsBankDetails(form.type)) {\n if (!LAST4_PATTERN.test(form.accountNumberLast4.trim())) {\n errors.accountNumberLast4 =\n \"Enter the last four digits of the account number.\";\n }\n if (!country || !isValidBankCode(form.bankCode, country)) {\n errors.bankCode =\n country === \"IN\"\n ? \"Enter a valid 11-character IFSC, for example HDFC0001234.\"\n : \"Enter a valid 8 or 11-character SWIFT/BIC code.\";\n }\n }\n return errors;\n}\n\n/** The addMyPublisherPayoutMethod input for a validated form. */\nexport function toAddPayoutMethodInput(form: AddPayoutMethodForm): {\n type: PayoutMethodTypeValue;\n country: string;\n currency: string;\n accountHolderName: string;\n accountNumberLast4?: string;\n bankCode?: string;\n bankName?: string;\n isDefault: boolean;\n} {\n const country = normalizeCountry(form.country);\n // validateAddPayoutMethod has already checked the type is offered.\n const type = isPayoutMethodType(form.type) ? form.type : \"BANK_TRANSFER\";\n const bankName = form.bankName.trim();\n return {\n type,\n country,\n currency: defaultCurrencyForCountry(country),\n accountHolderName: form.accountHolderName.trim(),\n ...(needsBankDetails(type)\n ? {\n accountNumberLast4: form.accountNumberLast4.trim(),\n bankCode: form.bankCode.trim().toUpperCase(),\n ...(bankName ? { bankName } : {}),\n }\n : {}),\n isDefault: form.isDefault,\n };\n}\n\nexport interface OnboardingForm {\n contactEmail: string;\n contactPhone: string;\n accountNumber: string;\n}\n\nexport type OnboardingField = keyof OnboardingForm;\n\n/**\n * Field errors for the \"Verify automatically\" form. RazorpayX also needs the\n * full account number once, which must end in the registered last four digits.\n */\nexport function validateOnboarding(\n type: string,\n form: OnboardingForm,\n accountLast4: string | null | undefined,\n): Partial<Record<OnboardingField, string>> {\n const errors: Partial<Record<OnboardingField, string>> = {};\n if (!isValidEmail(form.contactEmail)) {\n errors.contactEmail = \"Enter a valid contact email.\";\n }\n const phone = normalizePhone(form.contactPhone);\n if (phone && !PHONE_PATTERN.test(phone)) {\n errors.contactPhone = \"Enter 8 to 15 digits, with an optional leading +.\";\n }\n if (type === \"RAZORPAY_X\") {\n const account = form.accountNumber.replace(/\\s+/g, \"\");\n if (!ACCOUNT_NUMBER_PATTERN.test(account)) {\n errors.accountNumber =\n \"Enter the full account number (5 to 35 letters or digits).\";\n } else if (accountLast4 && !account.endsWith(accountLast4)) {\n errors.accountNumber = `The account number must end in ${accountLast4}, the digits you registered.`;\n }\n }\n return errors;\n}\n\n/** The startMyPublisherPayoutOnboarding input for a validated form. */\nexport function toOnboardingInput(\n type: string,\n form: OnboardingForm,\n): { contactEmail: string; contactPhone?: string; accountNumber?: string } {\n const phone = normalizePhone(form.contactPhone);\n return {\n contactEmail: form.contactEmail.trim(),\n ...(phone ? { contactPhone: phone } : {}),\n ...(type === \"RAZORPAY_X\"\n ? { accountNumber: form.accountNumber.replace(/\\s+/g, \"\") }\n : {}),\n };\n}\n\n/** Last four digits recorded on a method, read from its masked details. */\nexport function accountLast4Of(\n maskedDetails: Record<string, unknown> | null | undefined,\n): string | null {\n const value = maskedDetails?.[\"accountLast4\"];\n return typeof value === \"string\" && /^\\d{4}$/.test(value) ? value : null;\n}\n\n// ── Error mapping ───────────────────────────────────────────────────────────\n\nexport type PayoutAction = \"agreement\" | \"add\" | \"remove\" | \"start\" | \"refresh\";\n\nexport const PAYOUT_GENERIC_ERROR =\n \"Something went wrong. Nothing was changed. Try again in a moment; if it keeps failing, contact support.\";\n\ninterface ErrorEntry {\n code: string | null;\n message: string | null;\n}\n\nfunction readRecord(value: unknown): Record<string, unknown> | null {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? // Narrowed to a non-null, non-array object above; fields are read defensively below.\n (value as Record<string, unknown>)\n : null;\n}\n\nfunction usableMessage(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const message = value.trim();\n if (!message) return null;\n if (\n /^(unexpected error|response not successful|failed to fetch|network ?error)/i.test(\n message,\n )\n ) {\n return null;\n }\n return message;\n}\n\n/** The first GraphQL error (code and message) carried by an Apollo error. */\nexport function firstGraphQLError(error: unknown): ErrorEntry {\n const record = readRecord(error);\n // Apollo Client 4 raises CombinedGraphQLErrors (`errors`); older clients\n // raised ApolloError (`graphQLErrors`).\n for (const key of [\"errors\", \"graphQLErrors\"]) {\n const entries = record?.[key];\n if (!Array.isArray(entries) || entries.length === 0) continue;\n const entry = readRecord(entries[0]);\n const code = readRecord(entry?.[\"extensions\"])?.[\"code\"];\n return {\n code: typeof code === \"string\" ? code : null,\n message: usableMessage(entry?.[\"message\"]),\n };\n }\n return {\n code: null,\n message:\n error instanceof Error\n ? usableMessage(error.message)\n : usableMessage(record?.[\"message\"]),\n };\n}\n\n/**\n * A message the publisher can act on for a failed payout action. The store's\n * own message is kept for input errors because it names the field at fault.\n */\nexport function payoutErrorMessage(\n error: unknown,\n action: PayoutAction,\n): string {\n const { code, message } = firstGraphQLError(error);\n switch (code) {\n case \"FAILED_PRECONDITION\":\n if (action === \"start\" || action === \"refresh\") {\n return \"Automatic verification is not available for this processor yet. Your payout method stays pending and the marketplace team will verify it.\";\n }\n if (action === \"add\") {\n return \"Accept the current Marketplace & Developer Agreement before adding a payout method.\";\n }\n return message ?? PAYOUT_GENERIC_ERROR;\n case \"BAD_USER_INPUT\":\n return (\n message ??\n \"Some of the details are not valid. Check them and try again.\"\n );\n case \"SERVICE_UNAVAILABLE\":\n return \"Payout onboarding is temporarily unavailable. Nothing was changed. Try again in a few minutes.\";\n case \"NOT_FOUND\":\n return action === \"add\" || action === \"agreement\"\n ? \"You do not have a publisher profile yet. It is created when you submit your first app to the store.\"\n : \"This payout method no longer exists. Reload the page.\";\n case \"FORBIDDEN\":\n return \"Your publisher profile is suspended, so payout methods cannot be changed. Contact support.\";\n case \"INVALID_STATE\":\n return \"This payout method is no longer pending, so it cannot be verified again. Reload the page.\";\n default:\n return message ?? PAYOUT_GENERIC_ERROR;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Submit-for-review flow for the application page.
|
|
3
|
+
*
|
|
4
|
+
* The page used to run these steps inline and swallow any failure with a
|
|
5
|
+
* console.error, so when the store refused a submission the developer saw no
|
|
6
|
+
* message: the app silently stayed a draft (BOFF-10636). The flow is pure here
|
|
7
|
+
* so the page only renders the outcome, and the error mapping is unit tested.
|
|
8
|
+
*/
|
|
9
|
+
/** Fallback when a failure carries no message a developer can use. */
|
|
10
|
+
export declare const SUBMIT_FAILED_MESSAGE = "Your app could not be submitted for review. Nothing was sent to reviewers. Try again in a moment; if it keeps failing, contact support.";
|
|
11
|
+
export interface VersionLike {
|
|
12
|
+
id: string;
|
|
13
|
+
status: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SubmitForReviewSteps {
|
|
16
|
+
/** Moves one draft version into review together with the app. */
|
|
17
|
+
publishVersion: (versionId: string) => Promise<unknown>;
|
|
18
|
+
/** Submits the application itself. */
|
|
19
|
+
submitApplication: () => Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
export type SubmitForReviewResult = {
|
|
22
|
+
ok: true;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
message: string;
|
|
26
|
+
};
|
|
27
|
+
/** Draft versions go into review together with the app. */
|
|
28
|
+
export declare function draftVersionIds(versions: readonly VersionLike[]): string[];
|
|
29
|
+
/**
|
|
30
|
+
* The message to show for a failed submission: the first GraphQL error the
|
|
31
|
+
* backend sent (it forwards actionable store errors), else the error's own
|
|
32
|
+
* message, else a plain fallback.
|
|
33
|
+
*/
|
|
34
|
+
export declare function submitErrorMessage(error: unknown): string;
|
|
35
|
+
/**
|
|
36
|
+
* Publish the draft versions, then submit the application. Stops at the first
|
|
37
|
+
* failure and reports it instead of throwing.
|
|
38
|
+
*/
|
|
39
|
+
export declare function submitForReview(versions: readonly VersionLike[], steps: SubmitForReviewSteps): Promise<SubmitForReviewResult>;
|
|
40
|
+
//# sourceMappingURL=submitForReview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submitForReview.d.ts","sourceRoot":"","sources":["../../../src/devportal/utils/submitForReview.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,sEAAsE;AACtE,eAAO,MAAM,qBAAqB,4IACyG,CAAC;AAE5I,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,sCAAsC;IACtC,iBAAiB,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnC,2DAA2D;AAC3D,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,EAAE,CAE1E;AAyBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAgBzD;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,qBAAqB,CAAC,CAYhC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function e(e) {
|
|
2
|
+
return e.filter((e) => e.status === "draft").map((e) => e.id);
|
|
3
|
+
}
|
|
4
|
+
function t(e) {
|
|
5
|
+
return typeof e == "object" && e && !Array.isArray(e) ? e : null;
|
|
6
|
+
}
|
|
7
|
+
function n(e) {
|
|
8
|
+
if (typeof e != "string") return null;
|
|
9
|
+
let t = e.trim();
|
|
10
|
+
return !t || /^(unexpected error|response not successful|failed to fetch|network ?error)/i.test(t) ? null : t;
|
|
11
|
+
}
|
|
12
|
+
function r(e) {
|
|
13
|
+
let r = t(e);
|
|
14
|
+
for (let e of ["errors", "graphQLErrors"]) {
|
|
15
|
+
let i = r?.[e];
|
|
16
|
+
if (Array.isArray(i)) for (let e of i) {
|
|
17
|
+
let r = n(t(e)?.message);
|
|
18
|
+
if (r) return r;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return e instanceof Error ? n(e.message) ?? "Your app could not be submitted for review. Nothing was sent to reviewers. Try again in a moment; if it keeps failing, contact support." : n(r?.message) ?? "Your app could not be submitted for review. Nothing was sent to reviewers. Try again in a moment; if it keeps failing, contact support.";
|
|
22
|
+
}
|
|
23
|
+
async function i(t, n) {
|
|
24
|
+
try {
|
|
25
|
+
for (let r of e(t)) await n.publishVersion(r);
|
|
26
|
+
return await n.submitApplication(), { ok: !0 };
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return console.warn("[DevPortal] Submit for review failed:", e), {
|
|
29
|
+
ok: !1,
|
|
30
|
+
message: r(e)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { i as submitForReview };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=submitForReview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submitForReview.js","names":[],"sources":["../../../src/devportal/utils/submitForReview.ts"],"sourcesContent":["/**\n * Submit-for-review flow for the application page.\n *\n * The page used to run these steps inline and swallow any failure with a\n * console.error, so when the store refused a submission the developer saw no\n * message: the app silently stayed a draft (BOFF-10636). The flow is pure here\n * so the page only renders the outcome, and the error mapping is unit tested.\n */\n\n/** Fallback when a failure carries no message a developer can use. */\nexport const SUBMIT_FAILED_MESSAGE =\n \"Your app could not be submitted for review. Nothing was sent to reviewers. Try again in a moment; if it keeps failing, contact support.\";\n\nexport interface VersionLike {\n id: string;\n status: string;\n}\n\nexport interface SubmitForReviewSteps {\n /** Moves one draft version into review together with the app. */\n publishVersion: (versionId: string) => Promise<unknown>;\n /** Submits the application itself. */\n submitApplication: () => Promise<unknown>;\n}\n\nexport type SubmitForReviewResult =\n | { ok: true }\n | { ok: false; message: string };\n\n/** Draft versions go into review together with the app. */\nexport function draftVersionIds(versions: readonly VersionLike[]): string[] {\n return versions.filter((v) => v.status === \"draft\").map((v) => v.id);\n}\n\nfunction readRecord(value: unknown): Record<string, unknown> | null {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? // Narrowed to a non-null, non-array object above; fields are read defensively below.\n (value as Record<string, unknown>)\n : null;\n}\n\nfunction usableMessage(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const message = value.trim();\n if (!message) return null;\n // Transport noise (\"Unexpected error.\", \"Response not successful: ...\",\n // \"Failed to fetch\") tells the developer nothing they can act on.\n if (\n /^(unexpected error|response not successful|failed to fetch|network ?error)/i.test(\n message,\n )\n ) {\n return null;\n }\n return message;\n}\n\n/**\n * The message to show for a failed submission: the first GraphQL error the\n * backend sent (it forwards actionable store errors), else the error's own\n * message, else a plain fallback.\n */\nexport function submitErrorMessage(error: unknown): string {\n const record = readRecord(error);\n // Apollo Client 4 raises CombinedGraphQLErrors (`errors`); older clients\n // raised ApolloError (`graphQLErrors`).\n for (const key of [\"errors\", \"graphQLErrors\"]) {\n const entries = record?.[key];\n if (!Array.isArray(entries)) continue;\n for (const entry of entries) {\n const message = usableMessage(readRecord(entry)?.[\"message\"]);\n if (message) return message;\n }\n }\n if (error instanceof Error) {\n return usableMessage(error.message) ?? SUBMIT_FAILED_MESSAGE;\n }\n return usableMessage(record?.[\"message\"]) ?? SUBMIT_FAILED_MESSAGE;\n}\n\n/**\n * Publish the draft versions, then submit the application. Stops at the first\n * failure and reports it instead of throwing.\n */\nexport async function submitForReview(\n versions: readonly VersionLike[],\n steps: SubmitForReviewSteps,\n): Promise<SubmitForReviewResult> {\n try {\n for (const versionId of draftVersionIds(versions)) {\n await steps.publishVersion(versionId);\n }\n await steps.submitApplication();\n return { ok: true };\n } catch (error) {\n // Keep the raw error for debugging; the developer sees the mapped message.\n console.warn(\"[DevPortal] Submit for review failed:\", error);\n return { ok: false, message: submitErrorMessage(error) };\n }\n}\n"],"mappings":"AA8BA,SAAgB,EAAgB,GAA4C;AAC1E,QAAO,EAAS,QAAQ,MAAM,EAAE,WAAW,QAAQ,CAAC,KAAK,MAAM,EAAE,GAAG;;AAGtE,SAAS,EAAW,GAAgD;AAClE,QAAO,OAAO,KAAU,YAAY,KAAkB,CAAC,MAAM,QAAQ,EAAM,GAEtE,IACD;;AAGN,SAAS,EAAc,GAA+B;AACpD,KAAI,OAAO,KAAU,SAAU,QAAO;CACtC,IAAM,IAAU,EAAM,MAAM;AAW5B,QAVI,CAAC,KAIH,8EAA8E,KAC5E,EACD,GAEM,OAEF;;AAQT,SAAgB,EAAmB,GAAwB;CACzD,IAAM,IAAS,EAAW,EAAM;AAGhC,MAAK,IAAM,KAAO,CAAC,UAAU,gBAAgB,EAAE;EAC7C,IAAM,IAAU,IAAS;AACpB,YAAM,QAAQ,EAAQ,CAC3B,MAAK,IAAM,KAAS,GAAS;GAC3B,IAAM,IAAU,EAAc,EAAW,EAAM,EAAG,QAAW;AAC7D,OAAI,EAAS,QAAO;;;AAMxB,QAHI,aAAiB,QACZ,EAAc,EAAM,QAAQ,IAAA,4IAE9B,EAAc,GAAS,QAAW,IAAA;;AAO3C,eAAsB,EACpB,GACA,GACgC;AAChC,KAAI;AACF,OAAK,IAAM,KAAa,EAAgB,EAAS,CAC/C,OAAM,EAAM,eAAe,EAAU;AAGvC,SADA,MAAM,EAAM,mBAAmB,EACxB,EAAE,IAAI,IAAM;UACZ,GAAO;AAGd,SADA,QAAQ,KAAK,yCAAyC,EAAM,EACrD;GAAE,IAAI;GAAO,SAAS,EAAmB,EAAM;GAAE"}
|