@burdenoff/microfe-devportal 2026.527.1 → 2026.530.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/components/EmptyState.js +9 -23
- package/dist/devportal/components/EmptyState.js.map +1 -1
- package/dist/devportal/components/PageLayout.js +5 -2
- package/dist/devportal/components/PageLayout.js.map +1 -1
- package/dist/devportal/components/TelemetryErrorBoundary.js +37 -17
- package/dist/devportal/components/TelemetryErrorBoundary.js.map +1 -1
- package/dist/devportal/hooks/useGraphQL.js +2 -9
- package/dist/devportal/hooks/useGraphQL.js.map +1 -1
- package/dist/devportal/pages/ApiKeysPage.js +1 -1
- package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
- package/dist/devportal/pages/CreateApiKeyPage.js +78 -73
- package/dist/devportal/pages/CreateApiKeyPage.js.map +1 -1
- package/dist/devportal/pages/CreateApplicationPage.js +1 -1
- package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
- package/dist/devportal/pages/CreateOAuthClientPage.js +135 -131
- package/dist/devportal/pages/CreateOAuthClientPage.js.map +1 -1
- package/dist/devportal/pages/CreateOrganizationPage.js +1 -1
- package/dist/devportal/pages/CreateOrganizationPage.js.map +1 -1
- package/dist/devportal/pages/DashboardPage.js +36 -28
- package/dist/devportal/pages/DashboardPage.js.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.js +3 -3
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
- package/dist/devportal/pages/SettingsPage.js +10 -10
- package/dist/devportal/pages/SettingsPage.js.map +1 -1
- package/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateOrganizationPage.js","names":[],"sources":["../../../src/devportal/pages/CreateOrganizationPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { X, Info, Building2 } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { DeveloperOrgTier } from \"../types\";\nimport { useCreateOrganizationMutation } from \"../../generated/global-operations\";\nimport { useDevportalEventBus } from \"../events\";\n\ninterface CreateOrganizationFormData {\n name: string;\n slug: string;\n description: string;\n tier: DeveloperOrgTier;\n websiteUrl: string;\n contactEmail: string;\n supportEmail: string;\n legalEntityName: string;\n legalEntityType: string;\n taxId: string;\n taxCountry: string;\n streetAddress: string;\n city: string;\n state: string;\n postalCode: string;\n country: string;\n}\n\ninterface FormErrors {\n name?: string;\n slug?: string;\n contactEmail?: string;\n supportEmail?: string;\n}\n\nconst TIER_OPTIONS = [\n { value: \"FREE\", label: \"Free - Personal projects and testing\" },\n {\n value: \"DEVELOPER\",\n label: \"Developer - Small teams, 3 apps, 1M API calls/month\",\n },\n {\n value: \"PROFESSIONAL\",\n label: \"Professional - Growing teams, 10 apps, 10M API calls/month\",\n },\n {\n value: \"ENTERPRISE\",\n label: \"Enterprise - Large organizations, unlimited apps\",\n },\n];\n\nconst LEGAL_ENTITY_TYPES = [\n { value: \"\", label: \"Select entity type\" },\n { value: \"individual\", label: \"Individual / Sole Proprietor\" },\n { value: \"llc\", label: \"Limited Liability Company (LLC)\" },\n { value: \"corporation\", label: \"Corporation\" },\n { value: \"partnership\", label: \"Partnership\" },\n { value: \"nonprofit\", label: \"Non-Profit Organization\" },\n { value: \"other\", label: \"Other\" },\n];\n\nconst COUNTRIES = [\n { value: \"\", label: \"Select country\" },\n { value: \"US\", label: \"United States\" },\n { value: \"GB\", label: \"United Kingdom\" },\n { value: \"CA\", label: \"Canada\" },\n { value: \"AU\", label: \"Australia\" },\n { value: \"DE\", label: \"Germany\" },\n { value: \"FR\", label: \"France\" },\n { value: \"IN\", label: \"India\" },\n { value: \"JP\", label: \"Japan\" },\n { value: \"SG\", label: \"Singapore\" },\n { value: \"NL\", label: \"Netherlands\" },\n];\n\n/**\n * Create new organization page\n */\nexport const CreateOrganizationPage: FC = () => {\n const { navigate, basePath } = useDevportal();\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 [errors, setErrors] = useState<FormErrors>({});\n const [currentStep, setCurrentStep] = useState<\"basic\" | \"billing\">(\"basic\");\n const [createOrganization, { loading: isSubmitting }] =\n useCreateOrganizationMutation();\n const bus = useDevportalEventBus();\n\n const [formData, setFormData] = useState<CreateOrganizationFormData>({\n name: \"\",\n slug: \"\",\n description: \"\",\n tier: \"FREE\",\n websiteUrl: \"\",\n contactEmail: \"\",\n supportEmail: \"\",\n legalEntityName: \"\",\n legalEntityType: \"\",\n taxId: \"\",\n taxCountry: \"\",\n streetAddress: \"\",\n city: \"\",\n state: \"\",\n postalCode: \"\",\n country: \"\",\n });\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const generateSlug = (name: string): string => {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n };\n\n const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setFormData((prev) => ({\n ...prev,\n name,\n slug: prev.slug || generateSlug(name),\n }));\n if (errors.name) {\n setErrors((prev) => ({ ...prev, name: undefined }));\n }\n };\n\n const handleChange = (\n field: keyof CreateOrganizationFormData,\n value: string,\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field as keyof FormErrors]) {\n setErrors((prev) => ({ ...prev, [field]: undefined }));\n }\n };\n\n const validateBasicInfo = (): boolean => {\n const newErrors: FormErrors = {};\n\n if (!formData.name.trim()) {\n newErrors.name = tr(\n \"devportal.orgNameRequired\",\n \"Organization name is required\",\n );\n } else if (formData.name.length < 2) {\n newErrors.name = tr(\n \"devportal.orgNameTooShort\",\n \"Name must be at least 2 characters\",\n );\n } else if (formData.name.length > 100) {\n newErrors.name = tr(\n \"devportal.nameTooLong\",\n \"Name must be less than 100 characters\",\n );\n }\n\n if (!formData.slug.trim()) {\n newErrors.slug = tr(\"devportal.slugRequired\", \"Slug is required\");\n } else if (!/^[a-z0-9-]+$/.test(formData.slug)) {\n newErrors.slug = tr(\n \"devportal.slugInvalid\",\n \"Slug can only contain lowercase letters, numbers, and hyphens\",\n );\n }\n\n if (\n formData.contactEmail &&\n !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(formData.contactEmail)\n ) {\n newErrors.contactEmail = tr(\n \"devportal.invalidEmailAddress\",\n \"Invalid email address\",\n );\n }\n\n if (\n formData.supportEmail &&\n !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(formData.supportEmail)\n ) {\n newErrors.supportEmail = tr(\n \"devportal.invalidEmailAddress\",\n \"Invalid email address\",\n );\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleNext = () => {\n if (validateBasicInfo()) {\n setCurrentStep(\"billing\");\n }\n };\n\n const handleBack = () => {\n setCurrentStep(\"basic\");\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validateBasicInfo()) {\n setCurrentStep(\"basic\");\n return;\n }\n\n try {\n // Build legal address if any fields are provided\n const legalAddress =\n formData.streetAddress ||\n formData.city ||\n formData.state ||\n formData.postalCode ||\n formData.country\n ? {\n street: formData.streetAddress || undefined,\n city: formData.city || undefined,\n state: formData.state || undefined,\n postalCode: formData.postalCode || undefined,\n country: formData.country || undefined,\n }\n : undefined;\n\n const result = await createOrganization({\n variables: {\n input: {\n name: formData.name,\n slug: formData.slug || undefined,\n description: formData.description || undefined,\n websiteUrl: formData.websiteUrl || undefined,\n contactEmail: formData.contactEmail || undefined,\n supportEmail: formData.supportEmail || undefined,\n legalEntityName: formData.legalEntityName || undefined,\n legalEntityType: formData.legalEntityType || undefined,\n legalAddress,\n // Note: tier is stored in metadata since it's not in the input schema\n metadata:\n formData.tier !== \"FREE\" ? { tier: formData.tier } : undefined,\n },\n },\n });\n\n if (result.data?.createDeveloperOrganization?.id) {\n bus.emit(\"devportal.org.created\", {\n organizationId: result.data.createDeveloperOrganization.id,\n name: formData.name,\n });\n // Navigate to the new organization's detail page\n handleNavigate(\n `/organizations/${result.data.createDeveloperOrganization.id}`,\n );\n }\n } catch (error) {\n console.error(\"Failed to create organization:\", error);\n }\n };\n\n const handleCancel = () => {\n handleNavigate(\"/organizations\");\n };\n\n const tierInfo = {\n FREE: {\n apps: 1,\n apiCalls: \"100K\",\n storage: \"1GB\",\n members: 2,\n price: \"Free\",\n },\n DEVELOPER: {\n apps: 3,\n apiCalls: \"1M\",\n storage: \"5GB\",\n members: 5,\n price: \"$29/mo\",\n },\n PROFESSIONAL: {\n apps: 10,\n apiCalls: \"10M\",\n storage: \"25GB\",\n members: 15,\n price: \"$99/mo\",\n },\n ENTERPRISE: {\n apps: \"Unlimited\",\n apiCalls: \"100M+\",\n storage: \"100GB+\",\n members: \"Unlimited\",\n price: \"Custom\",\n },\n };\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.ORG_CREATE}>\n <PageLayout\n title={tr(\"devportal.createOrganization\", \"Create Organization\")}\n description={tr(\n \"devportal.createOrganizationDescription\",\n \"Set up a developer organization to collaborate with your team\",\n )}\n backLink={`${basePath}/organizations`}\n backLabel={tr(\"devportal.backToOrganizations\", \"Back to Organizations\")}\n >\n {/* Progress Steps */}\n <div className=\"flex items-center gap-4 mb-8\">\n <div className=\"flex items-center gap-2\">\n <div\n className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${\n currentStep === \"basic\"\n ? \"bg-primary text-primary-foreground\"\n : \"bg-primary/20 text-primary\"\n }`}\n >\n 1\n </div>\n <span\n className={`text-sm font-medium ${currentStep === \"basic\" ? \"text-foreground\" : \"text-muted-foreground\"}`}\n >\n {tr(\"devportal.basicInfo\", \"Basic Info\")}\n </span>\n </div>\n <div className=\"h-px flex-1 bg-border\" />\n <div className=\"flex items-center gap-2\">\n <div\n className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${\n currentStep === \"billing\"\n ? \"bg-primary text-primary-foreground\"\n : \"bg-muted text-muted-foreground\"\n }`}\n >\n 2\n </div>\n <span\n className={`text-sm font-medium ${currentStep === \"billing\" ? \"text-foreground\" : \"text-muted-foreground\"}`}\n >\n {tr(\"devportal.billingAndLegal\", \"Billing & Legal\")}\n </span>\n </div>\n </div>\n\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {currentStep === \"basic\" && (\n <>\n {/* Basic Information */}\n <PageSection\n title={tr(\n \"devportal.organizationDetails\",\n \"Organization Details\",\n )}\n description={tr(\n \"devportal.organizationDetailsDescription\",\n \"Basic information about your organization\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\n \"devportal.organizationName\",\n \"Organization Name\",\n )}\n value={formData.name}\n onChange={handleNameChange}\n placeholder={tr(\n \"devportal.organizationNamePlaceholder\",\n \"Acme Development\",\n )}\n required\n error={errors.name}\n hint={tr(\n \"devportal.organizationNameHint\",\n \"Your organization's display name\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.slug\", \"Slug\")}\n value={formData.slug}\n onChange={(e) =>\n handleChange(\"slug\", e.target.value.toLowerCase())\n }\n placeholder={tr(\"devportal.orgSlugPlaceholder\", \"acme-dev\")}\n required\n error={errors.slug}\n hint={tr(\n \"devportal.slugHint\",\n \"URL-friendly identifier (auto-generated from name)\",\n )}\n />\n\n <TextareaField\n label={tr(\"devportal.description\", \"Description\")}\n value={formData.description}\n onChange={(e) =>\n handleChange(\"description\", e.target.value)\n }\n placeholder={tr(\n \"devportal.orgDescriptionPlaceholder\",\n \"Tell us about your organization...\",\n )}\n rows={3}\n />\n\n <InputField\n label={tr(\"devportal.websiteUrl\", \"Website URL\")}\n type=\"url\"\n value={formData.websiteUrl}\n onChange={(e) => handleChange(\"websiteUrl\", e.target.value)}\n placeholder=\"https://example.com\"\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.contactEmail\", \"Contact Email\")}\n type=\"email\"\n value={formData.contactEmail}\n onChange={(e) =>\n handleChange(\"contactEmail\", e.target.value)\n }\n placeholder=\"contact@example.com\"\n error={errors.contactEmail}\n />\n\n <InputField\n label={tr(\"devportal.supportEmail\", \"Support Email\")}\n type=\"email\"\n value={formData.supportEmail}\n onChange={(e) =>\n handleChange(\"supportEmail\", e.target.value)\n }\n placeholder=\"support@example.com\"\n error={errors.supportEmail}\n />\n </div>\n </div>\n </PageSection>\n\n {/* Tier Selection */}\n <PageSection\n title={tr(\"devportal.selectPlan\", \"Select Plan\")}\n description={tr(\n \"devportal.selectPlanDescription\",\n \"Choose a plan that fits your needs\",\n )}\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {TIER_OPTIONS.map((tier) => {\n const info = tierInfo[tier.value as DeveloperOrgTier];\n const isSelected = formData.tier === tier.value;\n return (\n <button\n key={tier.value}\n type=\"button\"\n onClick={() => handleChange(\"tier\", tier.value)}\n className={`rounded-lg border p-4 text-left transition-colors ${\n isSelected\n ? \"border-primary bg-primary/5 ring-2 ring-primary\"\n : \"hover:border-muted-foreground/50\"\n }`}\n >\n <div className=\"flex items-start justify-between\">\n <div>\n <p className=\"font-medium\">{tier.value}</p>\n <p className=\"text-sm text-muted-foreground\">\n {info.price}\n </p>\n </div>\n <div\n className={`h-4 w-4 rounded-full border-2 ${\n isSelected\n ? \"border-primary bg-primary\"\n : \"border-muted-foreground\"\n }`}\n >\n {isSelected && (\n <div className=\"h-full w-full flex items-center justify-center\">\n <div className=\"h-1.5 w-1.5 rounded-full bg-white\" />\n </div>\n )}\n </div>\n </div>\n <div className=\"mt-3 grid grid-cols-2 gap-2 text-xs text-muted-foreground\">\n <span>\n {info.apps} {tr(\"devportal.apps\", \"apps\")}\n </span>\n <span>\n {info.apiCalls}{\" \"}\n {tr(\"devportal.apiCalls\", \"API calls\")}\n </span>\n <span>\n {info.storage} {tr(\"devportal.storage\", \"storage\")}\n </span>\n <span>\n {info.members} {tr(\"devportal.members\", \"members\")}\n </span>\n </div>\n </button>\n );\n })}\n </div>\n </PageSection>\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3 border-t pt-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors\"\n >\n <X size={16} />\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleNext}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n {tr(\"devportal.continue\", \"Continue\")}\n </button>\n </div>\n </>\n )}\n\n {currentStep === \"billing\" && (\n <>\n {/* Legal Entity */}\n <PageSection\n title={tr(\"devportal.legalInformation\", \"Legal Information\")}\n description={tr(\n \"devportal.legalInformationDescription\",\n \"Required for payouts and compliance (optional for Free tier)\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.legalEntityName\", \"Legal Entity Name\")}\n value={formData.legalEntityName}\n onChange={(e) =>\n handleChange(\"legalEntityName\", e.target.value)\n }\n placeholder={tr(\n \"devportal.legalEntityNamePlaceholder\",\n \"Acme Inc.\",\n )}\n hint={tr(\n \"devportal.legalEntityNameHint\",\n \"Your registered business name\",\n )}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <SelectField\n label={tr(\"devportal.entityType\", \"Entity Type\")}\n value={formData.legalEntityType}\n onChange={(e) =>\n handleChange(\"legalEntityType\", e.target.value)\n }\n options={LEGAL_ENTITY_TYPES}\n />\n\n <SelectField\n label={tr(\"devportal.taxCountry\", \"Tax Country\")}\n value={formData.taxCountry}\n onChange={(e) =>\n handleChange(\"taxCountry\", e.target.value)\n }\n options={COUNTRIES}\n />\n </div>\n\n <InputField\n label={tr(\n \"devportal.taxIdVatNumber\",\n \"Tax ID / VAT Number\",\n )}\n value={formData.taxId}\n onChange={(e) => handleChange(\"taxId\", e.target.value)}\n placeholder=\"XX-XXXXXXX\"\n hint={tr(\n \"devportal.taxIdHint\",\n \"Your business tax identification number\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Business Address */}\n <PageSection\n title={tr(\"devportal.businessAddress\", \"Business Address\")}\n description={tr(\n \"devportal.businessAddressDescription\",\n \"Used for invoicing and legal documents\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.streetAddress\", \"Street Address\")}\n value={formData.streetAddress}\n onChange={(e) =>\n handleChange(\"streetAddress\", e.target.value)\n }\n placeholder={tr(\n \"devportal.streetAddressPlaceholder\",\n \"123 Main Street\",\n )}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.city\", \"City\")}\n value={formData.city}\n onChange={(e) => handleChange(\"city\", e.target.value)}\n placeholder={tr(\n \"devportal.cityPlaceholder\",\n \"San Francisco\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.stateProvince\", \"State / Province\")}\n value={formData.state}\n onChange={(e) => handleChange(\"state\", e.target.value)}\n placeholder={tr(\n \"devportal.stateProvincePlaceholder\",\n \"California\",\n )}\n />\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.postalCode\", \"Postal Code\")}\n value={formData.postalCode}\n onChange={(e) =>\n handleChange(\"postalCode\", e.target.value)\n }\n placeholder={tr(\n \"devportal.postalCodePlaceholder\",\n \"94102\",\n )}\n />\n\n <SelectField\n label={tr(\"devportal.country\", \"Country\")}\n value={formData.country}\n onChange={(e) => handleChange(\"country\", e.target.value)}\n options={COUNTRIES}\n />\n </div>\n </div>\n </PageSection>\n\n {/* Info banner */}\n <div className=\"rounded-lg border border-border-subtle bg-status-info-bg-subtle p-4 flex gap-3\">\n <Info\n size={20}\n className=\"text-accent-blue flex-shrink-0 mt-0.5\"\n />\n <div className=\"text-sm text-status-info-text\">\n <p className=\"font-medium\">\n {tr(\"devportal.canAddLater\", \"You can add this later\")}\n </p>\n <p className=\"mt-1 text-status-info-text\">\n {tr(\n \"devportal.legalInfoOptionalNote\",\n \"Legal and billing information is only required when you want to receive payouts from app sales or upgrade to a paid plan.\",\n )}\n </p>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-between border-t pt-6\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors\"\n >\n {tr(\"devportal.back\", \"Back\")}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50\"\n >\n {isSubmitting ? (\n <>\n <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n {tr(\"devportal.creating\", \"Creating...\")}\n </>\n ) : (\n <>\n <Building2 size={16} />\n {tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n </>\n )}\n </button>\n </div>\n </>\n )}\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CreateOrganizationPage.js","names":[],"sources":["../../../src/devportal/pages/CreateOrganizationPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { X, Info, Building2 } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { DeveloperOrgTier } from \"../types\";\nimport { useCreateOrganizationMutation } from \"../../generated/global-operations\";\nimport { useDevportalEventBus } from \"../events\";\n\ninterface CreateOrganizationFormData {\n name: string;\n slug: string;\n description: string;\n tier: DeveloperOrgTier;\n websiteUrl: string;\n contactEmail: string;\n supportEmail: string;\n legalEntityName: string;\n legalEntityType: string;\n taxId: string;\n taxCountry: string;\n streetAddress: string;\n city: string;\n state: string;\n postalCode: string;\n country: string;\n}\n\ninterface FormErrors {\n name?: string;\n slug?: string;\n contactEmail?: string;\n supportEmail?: string;\n}\n\nconst TIER_OPTIONS = [\n { value: \"FREE\", label: \"Free - Personal projects and testing\" },\n {\n value: \"DEVELOPER\",\n label: \"Developer - Small teams, 3 apps, 1M API calls/month\",\n },\n {\n value: \"PROFESSIONAL\",\n label: \"Professional - Growing teams, 10 apps, 10M API calls/month\",\n },\n {\n value: \"ENTERPRISE\",\n label: \"Enterprise - Large organizations, unlimited apps\",\n },\n];\n\nconst LEGAL_ENTITY_TYPES = [\n { value: \"\", label: \"Select entity type\" },\n { value: \"individual\", label: \"Individual / Sole Proprietor\" },\n { value: \"llc\", label: \"Limited Liability Company (LLC)\" },\n { value: \"corporation\", label: \"Corporation\" },\n { value: \"partnership\", label: \"Partnership\" },\n { value: \"nonprofit\", label: \"Non-Profit Organization\" },\n { value: \"other\", label: \"Other\" },\n];\n\nconst COUNTRIES = [\n { value: \"\", label: \"Select country\" },\n { value: \"US\", label: \"United States\" },\n { value: \"GB\", label: \"United Kingdom\" },\n { value: \"CA\", label: \"Canada\" },\n { value: \"AU\", label: \"Australia\" },\n { value: \"DE\", label: \"Germany\" },\n { value: \"FR\", label: \"France\" },\n { value: \"IN\", label: \"India\" },\n { value: \"JP\", label: \"Japan\" },\n { value: \"SG\", label: \"Singapore\" },\n { value: \"NL\", label: \"Netherlands\" },\n];\n\n/**\n * Create new organization page\n */\nexport const CreateOrganizationPage: FC = () => {\n const { navigate, basePath } = useDevportal();\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 [errors, setErrors] = useState<FormErrors>({});\n const [currentStep, setCurrentStep] = useState<\"basic\" | \"billing\">(\"basic\");\n const [createOrganization, { loading: isSubmitting }] =\n useCreateOrganizationMutation();\n const bus = useDevportalEventBus();\n\n const [formData, setFormData] = useState<CreateOrganizationFormData>({\n name: \"\",\n slug: \"\",\n description: \"\",\n tier: \"FREE\",\n websiteUrl: \"\",\n contactEmail: \"\",\n supportEmail: \"\",\n legalEntityName: \"\",\n legalEntityType: \"\",\n taxId: \"\",\n taxCountry: \"\",\n streetAddress: \"\",\n city: \"\",\n state: \"\",\n postalCode: \"\",\n country: \"\",\n });\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const generateSlug = (name: string): string => {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n };\n\n const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setFormData((prev) => ({\n ...prev,\n name,\n slug: prev.slug || generateSlug(name),\n }));\n if (errors.name) {\n setErrors((prev) => ({ ...prev, name: undefined }));\n }\n };\n\n const handleChange = (\n field: keyof CreateOrganizationFormData,\n value: string,\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field as keyof FormErrors]) {\n setErrors((prev) => ({ ...prev, [field]: undefined }));\n }\n };\n\n const validateBasicInfo = (): boolean => {\n const newErrors: FormErrors = {};\n\n if (!formData.name.trim()) {\n newErrors.name = tr(\n \"devportal.orgNameRequired\",\n \"Organization name is required\",\n );\n } else if (formData.name.length < 2) {\n newErrors.name = tr(\n \"devportal.orgNameTooShort\",\n \"Name must be at least 2 characters\",\n );\n } else if (formData.name.length > 100) {\n newErrors.name = tr(\n \"devportal.nameTooLong\",\n \"Name must be less than 100 characters\",\n );\n }\n\n if (!formData.slug.trim()) {\n newErrors.slug = tr(\"devportal.slugRequired\", \"Slug is required\");\n } else if (!/^[a-z0-9-]+$/.test(formData.slug)) {\n newErrors.slug = tr(\n \"devportal.slugInvalid\",\n \"Slug can only contain lowercase letters, numbers, and hyphens\",\n );\n }\n\n if (\n formData.contactEmail &&\n !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(formData.contactEmail)\n ) {\n newErrors.contactEmail = tr(\n \"devportal.invalidEmailAddress\",\n \"Invalid email address\",\n );\n }\n\n if (\n formData.supportEmail &&\n !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(formData.supportEmail)\n ) {\n newErrors.supportEmail = tr(\n \"devportal.invalidEmailAddress\",\n \"Invalid email address\",\n );\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleNext = () => {\n if (validateBasicInfo()) {\n setCurrentStep(\"billing\");\n }\n };\n\n const handleBack = () => {\n setCurrentStep(\"basic\");\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validateBasicInfo()) {\n setCurrentStep(\"basic\");\n return;\n }\n\n try {\n // Build legal address if any fields are provided\n const legalAddress =\n formData.streetAddress ||\n formData.city ||\n formData.state ||\n formData.postalCode ||\n formData.country\n ? {\n street: formData.streetAddress || undefined,\n city: formData.city || undefined,\n state: formData.state || undefined,\n postalCode: formData.postalCode || undefined,\n country: formData.country || undefined,\n }\n : undefined;\n\n const result = await createOrganization({\n variables: {\n input: {\n name: formData.name,\n slug: formData.slug || undefined,\n description: formData.description || undefined,\n websiteUrl: formData.websiteUrl || undefined,\n contactEmail: formData.contactEmail || undefined,\n supportEmail: formData.supportEmail || undefined,\n legalEntityName: formData.legalEntityName || undefined,\n legalEntityType: formData.legalEntityType || undefined,\n legalAddress,\n // Note: tier is stored in metadata since it's not in the input schema\n metadata:\n formData.tier !== \"FREE\" ? { tier: formData.tier } : undefined,\n },\n },\n });\n\n if (result.data?.createDeveloperOrganization?.id) {\n bus.emit(\"devportal.org.created\", {\n organizationId: result.data.createDeveloperOrganization.id,\n name: formData.name,\n });\n // Navigate to the new organization's detail page\n handleNavigate(\n `/organizations/${result.data.createDeveloperOrganization.id}`,\n );\n }\n } catch (error) {\n console.error(\"Failed to create organization:\", error);\n }\n };\n\n const handleCancel = () => {\n handleNavigate(\"/organizations\");\n };\n\n const tierInfo = {\n FREE: {\n apps: 1,\n apiCalls: \"100K\",\n storage: \"1GB\",\n members: 2,\n price: \"Free\",\n },\n DEVELOPER: {\n apps: 3,\n apiCalls: \"1M\",\n storage: \"5GB\",\n members: 5,\n price: \"$29/mo\",\n },\n PROFESSIONAL: {\n apps: 10,\n apiCalls: \"10M\",\n storage: \"25GB\",\n members: 15,\n price: \"$99/mo\",\n },\n ENTERPRISE: {\n apps: \"Unlimited\",\n apiCalls: \"100M+\",\n storage: \"100GB+\",\n members: \"Unlimited\",\n price: \"Custom\",\n },\n };\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.ORG_CREATE}>\n <PageLayout\n title={tr(\"devportal.createOrganization\", \"Create Organization\")}\n description={tr(\n \"devportal.createOrganizationDescription\",\n \"Set up a developer organization to collaborate with your team\",\n )}\n backLink={`${basePath}/organizations`}\n backLabel={tr(\"devportal.backToOrganizations\", \"Back to Organizations\")}\n >\n {/* Progress Steps */}\n <div className=\"flex items-center gap-4 mb-8\">\n <div className=\"flex items-center gap-2\">\n <div\n className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${\n currentStep === \"basic\"\n ? \"bg-primary text-primary-foreground\"\n : \"bg-primary/20 text-primary\"\n }`}\n >\n 1\n </div>\n <span\n className={`text-sm font-medium ${currentStep === \"basic\" ? \"text-foreground\" : \"text-muted-foreground\"}`}\n >\n {tr(\"devportal.basicInfo\", \"Basic Info\")}\n </span>\n </div>\n <div className=\"h-px flex-1 bg-border\" />\n <div className=\"flex items-center gap-2\">\n <div\n className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${\n currentStep === \"billing\"\n ? \"bg-primary text-primary-foreground\"\n : \"bg-muted text-muted-foreground\"\n }`}\n >\n 2\n </div>\n <span\n className={`text-sm font-medium ${currentStep === \"billing\" ? \"text-foreground\" : \"text-muted-foreground\"}`}\n >\n {tr(\"devportal.billingAndLegal\", \"Billing & Legal\")}\n </span>\n </div>\n </div>\n\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {currentStep === \"basic\" && (\n <>\n {/* Basic Information */}\n <PageSection\n title={tr(\n \"devportal.organizationDetails\",\n \"Organization Details\",\n )}\n description={tr(\n \"devportal.organizationDetailsDescription\",\n \"Basic information about your organization\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\n \"devportal.organizationName\",\n \"Organization Name\",\n )}\n value={formData.name}\n onChange={handleNameChange}\n placeholder={tr(\n \"devportal.organizationNamePlaceholder\",\n \"Acme Development\",\n )}\n required\n error={errors.name}\n hint={tr(\n \"devportal.organizationNameHint\",\n \"Your organization's display name\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.slug\", \"Slug\")}\n value={formData.slug}\n onChange={(e) =>\n handleChange(\"slug\", e.target.value.toLowerCase())\n }\n placeholder={tr(\"devportal.orgSlugPlaceholder\", \"acme-dev\")}\n required\n error={errors.slug}\n hint={tr(\n \"devportal.slugHint\",\n \"URL-friendly identifier (auto-generated from name)\",\n )}\n />\n\n <TextareaField\n label={tr(\"devportal.description\", \"Description\")}\n value={formData.description}\n onChange={(e) =>\n handleChange(\"description\", e.target.value)\n }\n placeholder={tr(\n \"devportal.orgDescriptionPlaceholder\",\n \"Tell us about your organization...\",\n )}\n rows={3}\n />\n\n <InputField\n label={tr(\"devportal.websiteUrl\", \"Website URL\")}\n type=\"url\"\n value={formData.websiteUrl}\n onChange={(e) => handleChange(\"websiteUrl\", e.target.value)}\n placeholder=\"https://example.com\"\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.contactEmail\", \"Contact Email\")}\n type=\"email\"\n value={formData.contactEmail}\n onChange={(e) =>\n handleChange(\"contactEmail\", e.target.value)\n }\n placeholder=\"contact@example.com\"\n error={errors.contactEmail}\n />\n\n <InputField\n label={tr(\"devportal.supportEmail\", \"Support Email\")}\n type=\"email\"\n value={formData.supportEmail}\n onChange={(e) =>\n handleChange(\"supportEmail\", e.target.value)\n }\n placeholder=\"support@example.com\"\n error={errors.supportEmail}\n />\n </div>\n </div>\n </PageSection>\n\n {/* Tier Selection */}\n <PageSection\n title={tr(\"devportal.selectPlan\", \"Select Plan\")}\n description={tr(\n \"devportal.selectPlanDescription\",\n \"Choose a plan that fits your needs\",\n )}\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {TIER_OPTIONS.map((tier) => {\n const info = tierInfo[tier.value as DeveloperOrgTier];\n const isSelected = formData.tier === tier.value;\n return (\n <button\n key={tier.value}\n type=\"button\"\n onClick={() => handleChange(\"tier\", tier.value)}\n className={`rounded-lg border p-4 text-left transition-colors ${\n isSelected\n ? \"border-primary bg-primary/5 ring-2 ring-primary\"\n : \"hover:border-muted-foreground/50\"\n }`}\n >\n <div className=\"flex items-start justify-between\">\n <div>\n <p className=\"font-medium\">{tier.value}</p>\n <p className=\"text-sm text-muted-foreground\">\n {info.price}\n </p>\n </div>\n <div\n className={`h-4 w-4 rounded-full border-2 ${\n isSelected\n ? \"border-primary bg-primary\"\n : \"border-muted-foreground\"\n }`}\n >\n {isSelected && (\n <div className=\"h-full w-full flex items-center justify-center\">\n <div className=\"h-1.5 w-1.5 rounded-full bg-bg-surface\" />\n </div>\n )}\n </div>\n </div>\n <div className=\"mt-3 grid grid-cols-2 gap-2 text-xs text-muted-foreground\">\n <span>\n {info.apps} {tr(\"devportal.apps\", \"apps\")}\n </span>\n <span>\n {info.apiCalls}{\" \"}\n {tr(\"devportal.apiCalls\", \"API calls\")}\n </span>\n <span>\n {info.storage} {tr(\"devportal.storage\", \"storage\")}\n </span>\n <span>\n {info.members} {tr(\"devportal.members\", \"members\")}\n </span>\n </div>\n </button>\n );\n })}\n </div>\n </PageSection>\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3 border-t pt-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors\"\n >\n <X size={16} />\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleNext}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n {tr(\"devportal.continue\", \"Continue\")}\n </button>\n </div>\n </>\n )}\n\n {currentStep === \"billing\" && (\n <>\n {/* Legal Entity */}\n <PageSection\n title={tr(\"devportal.legalInformation\", \"Legal Information\")}\n description={tr(\n \"devportal.legalInformationDescription\",\n \"Required for payouts and compliance (optional for Free tier)\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.legalEntityName\", \"Legal Entity Name\")}\n value={formData.legalEntityName}\n onChange={(e) =>\n handleChange(\"legalEntityName\", e.target.value)\n }\n placeholder={tr(\n \"devportal.legalEntityNamePlaceholder\",\n \"Acme Inc.\",\n )}\n hint={tr(\n \"devportal.legalEntityNameHint\",\n \"Your registered business name\",\n )}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <SelectField\n label={tr(\"devportal.entityType\", \"Entity Type\")}\n value={formData.legalEntityType}\n onChange={(e) =>\n handleChange(\"legalEntityType\", e.target.value)\n }\n options={LEGAL_ENTITY_TYPES}\n />\n\n <SelectField\n label={tr(\"devportal.taxCountry\", \"Tax Country\")}\n value={formData.taxCountry}\n onChange={(e) =>\n handleChange(\"taxCountry\", e.target.value)\n }\n options={COUNTRIES}\n />\n </div>\n\n <InputField\n label={tr(\n \"devportal.taxIdVatNumber\",\n \"Tax ID / VAT Number\",\n )}\n value={formData.taxId}\n onChange={(e) => handleChange(\"taxId\", e.target.value)}\n placeholder=\"XX-XXXXXXX\"\n hint={tr(\n \"devportal.taxIdHint\",\n \"Your business tax identification number\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Business Address */}\n <PageSection\n title={tr(\"devportal.businessAddress\", \"Business Address\")}\n description={tr(\n \"devportal.businessAddressDescription\",\n \"Used for invoicing and legal documents\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.streetAddress\", \"Street Address\")}\n value={formData.streetAddress}\n onChange={(e) =>\n handleChange(\"streetAddress\", e.target.value)\n }\n placeholder={tr(\n \"devportal.streetAddressPlaceholder\",\n \"123 Main Street\",\n )}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.city\", \"City\")}\n value={formData.city}\n onChange={(e) => handleChange(\"city\", e.target.value)}\n placeholder={tr(\n \"devportal.cityPlaceholder\",\n \"San Francisco\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.stateProvince\", \"State / Province\")}\n value={formData.state}\n onChange={(e) => handleChange(\"state\", e.target.value)}\n placeholder={tr(\n \"devportal.stateProvincePlaceholder\",\n \"California\",\n )}\n />\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.postalCode\", \"Postal Code\")}\n value={formData.postalCode}\n onChange={(e) =>\n handleChange(\"postalCode\", e.target.value)\n }\n placeholder={tr(\n \"devportal.postalCodePlaceholder\",\n \"94102\",\n )}\n />\n\n <SelectField\n label={tr(\"devportal.country\", \"Country\")}\n value={formData.country}\n onChange={(e) => handleChange(\"country\", e.target.value)}\n options={COUNTRIES}\n />\n </div>\n </div>\n </PageSection>\n\n {/* Info banner */}\n <div className=\"rounded-lg border border-border-subtle bg-status-info-bg-subtle p-4 flex gap-3\">\n <Info\n size={20}\n className=\"text-accent-blue flex-shrink-0 mt-0.5\"\n />\n <div className=\"text-sm text-status-info-text\">\n <p className=\"font-medium\">\n {tr(\"devportal.canAddLater\", \"You can add this later\")}\n </p>\n <p className=\"mt-1 text-status-info-text\">\n {tr(\n \"devportal.legalInfoOptionalNote\",\n \"Legal and billing information is only required when you want to receive payouts from app sales or upgrade to a paid plan.\",\n )}\n </p>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-between border-t pt-6\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors\"\n >\n {tr(\"devportal.back\", \"Back\")}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50\"\n >\n {isSubmitting ? (\n <>\n <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n {tr(\"devportal.creating\", \"Creating...\")}\n </>\n ) : (\n <>\n <Building2 size={16} />\n {tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n </>\n )}\n </button>\n </div>\n </>\n )}\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -34,41 +34,46 @@ var w = () => {
|
|
|
34
34
|
title: k("devportal.developerPortal", "Developer Portal"),
|
|
35
35
|
description: k("devportal.buildManageMonitor", "Build, manage, and monitor your applications"),
|
|
36
36
|
actions: /* @__PURE__ */ C("button", {
|
|
37
|
+
"data-tour": "devportal-dashboard-create-app-button",
|
|
37
38
|
onClick: () => F("/apps/new"),
|
|
38
39
|
className: "inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors",
|
|
39
40
|
children: [/* @__PURE__ */ S(v, { size: 16 }), k("devportal.createApp", "Create App")]
|
|
40
41
|
}),
|
|
41
42
|
children: [
|
|
42
|
-
/* @__PURE__ */ S(r, {
|
|
43
|
-
|
|
44
|
-
children:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
43
|
+
/* @__PURE__ */ S(r, {
|
|
44
|
+
"data-tour": "devportal-dashboard-stats-overview",
|
|
45
|
+
children: /* @__PURE__ */ C(a, {
|
|
46
|
+
columns: 4,
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ S(i, {
|
|
49
|
+
title: k("devportal.totalApps", "Total Apps"),
|
|
50
|
+
value: I ? /* @__PURE__ */ S(_, { className: "h-5 w-5 animate-spin" }) : P.totalApps,
|
|
51
|
+
icon: /* @__PURE__ */ S(f, { size: 20 }),
|
|
52
|
+
trend: void 0
|
|
53
|
+
}),
|
|
54
|
+
/* @__PURE__ */ S(i, {
|
|
55
|
+
title: k("devportal.organizations", "Organizations"),
|
|
56
|
+
value: I ? /* @__PURE__ */ S(_, { className: "h-5 w-5 animate-spin" }) : P.totalOrgs,
|
|
57
|
+
icon: /* @__PURE__ */ S(m, { size: 20 })
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ S(i, {
|
|
60
|
+
title: k("devportal.apiKeys", "API Keys"),
|
|
61
|
+
value: P.totalApiKeys,
|
|
62
|
+
icon: /* @__PURE__ */ S(g, { size: 20 }),
|
|
63
|
+
description: k("devportal.activeKeys", "Active keys")
|
|
64
|
+
}),
|
|
65
|
+
/* @__PURE__ */ S(i, {
|
|
66
|
+
title: k("devportal.webhooks", "Webhooks"),
|
|
67
|
+
value: P.totalWebhooks,
|
|
68
|
+
icon: /* @__PURE__ */ S(x, { size: 20 }),
|
|
69
|
+
description: k("devportal.configured", "Configured")
|
|
70
|
+
})
|
|
71
|
+
]
|
|
72
|
+
})
|
|
73
|
+
}),
|
|
70
74
|
/* @__PURE__ */ S(r, {
|
|
71
75
|
title: k("devportal.usageOverview", "Usage Overview"),
|
|
76
|
+
"data-tour": "devportal-dashboard-usage-overview",
|
|
72
77
|
children: /* @__PURE__ */ C(a, {
|
|
73
78
|
columns: 4,
|
|
74
79
|
children: [
|
|
@@ -102,6 +107,7 @@ var w = () => {
|
|
|
102
107
|
/* @__PURE__ */ C("div", {
|
|
103
108
|
className: "grid gap-6 lg:grid-cols-2",
|
|
104
109
|
children: [/* @__PURE__ */ S(r, {
|
|
110
|
+
"data-tour": "devportal-dashboard-recent-apps",
|
|
105
111
|
title: k("devportal.recentApplications", "Recent Applications"),
|
|
106
112
|
actions: /* @__PURE__ */ C("button", {
|
|
107
113
|
onClick: () => F("/apps"),
|
|
@@ -133,6 +139,7 @@ var w = () => {
|
|
|
133
139
|
})
|
|
134
140
|
}), /* @__PURE__ */ S(r, {
|
|
135
141
|
title: k("devportal.quickActions", "Quick Actions"),
|
|
142
|
+
"data-tour": "devportal-dashboard-quick-actions",
|
|
136
143
|
children: /* @__PURE__ */ C("div", {
|
|
137
144
|
className: "grid gap-3",
|
|
138
145
|
children: [
|
|
@@ -165,6 +172,7 @@ var w = () => {
|
|
|
165
172
|
})]
|
|
166
173
|
}),
|
|
167
174
|
I ? /* @__PURE__ */ S(r, {
|
|
175
|
+
"data-tour": "devportal-dashboard-organizations",
|
|
168
176
|
title: k("devportal.yourOrganizations", "Your Organizations"),
|
|
169
177
|
children: /* @__PURE__ */ S("div", {
|
|
170
178
|
className: "flex items-center justify-center py-8",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/devportal/pages/DashboardPage.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n AppWindow,\n Building2,\n Key,\n Webhook,\n TrendingUp,\n Users,\n DollarSign,\n Activity,\n Plus,\n ArrowRight,\n Loader2,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { StatCard, StatCardGrid } from \"../components/StatCard\";\nimport { AppListItem } from \"../components/AppCard\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { Application, DeveloperOrganization } from \"../types\";\nimport {\n useGetDevportalDashboardBootstrapQuery,\n type OrganizationFieldsFragment,\n type ApplicationFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Developer Portal Dashboard Page\n *\n * Shows an overview of the developer's applications, organizations,\n * and key metrics using real data from the GraphQL API.\n */\nexport const DashboardPage: FC = () => {\n const { navigate, basePath } = useDevportal();\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\n // Fetch dashboard bootstrap in a single global query\n const { data, loading } = useGetDevportalDashboardBootstrapQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const organizations: OrganizationFieldsFragment[] =\n data?.myDeveloperOrganizations ?? [];\n const applications: ApplicationFieldsFragment[] =\n data?.applications?.edges?.map(\n (e: { node: ApplicationFieldsFragment }) => e.node,\n ) ?? [];\n\n // Calculate aggregate stats\n const stats = {\n totalApps: data?.applications?.totalCount ?? 0,\n totalOrgs: organizations.length,\n totalApiKeys: 0,\n totalWebhooks: 0,\n totalApiCalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + parseInt(org.totalApiCalls || \"0\"),\n 0,\n ),\n activeInstalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.activeInstalls || 0),\n 0,\n ),\n monthlyRevenue: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.totalRevenue || 0),\n 0,\n ),\n errorRate: 0,\n };\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const isLoading = loading;\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_READ}>\n <PageLayout\n title={tr(\"devportal.developerPortal\", \"Developer Portal\")}\n description={tr(\n \"devportal.buildManageMonitor\",\n \"Build, manage, and monitor your applications\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n }\n >\n {/* Stats Overview */}\n <PageSection>\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.totalApps\", \"Total Apps\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalApps\n )\n }\n icon={<AppWindow size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.organizations\", \"Organizations\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalOrgs\n )\n }\n icon={<Building2 size={20} />}\n />\n <StatCard\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n value={stats.totalApiKeys}\n icon={<Key size={20} />}\n description={tr(\"devportal.activeKeys\", \"Active keys\")}\n />\n <StatCard\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n value={stats.totalWebhooks}\n icon={<Webhook size={20} />}\n description={tr(\"devportal.configured\", \"Configured\")}\n />\n </StatCardGrid>\n </PageSection>\n\n {/* Usage Stats */}\n <PageSection title={tr(\"devportal.usageOverview\", \"Usage Overview\")}>\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.apiCalls\", \"API Calls\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.totalApiCalls > 1000000 ? (\n `${(stats.totalApiCalls / 1000000).toFixed(1)}M`\n ) : stats.totalApiCalls > 1000 ? (\n `${(stats.totalApiCalls / 1000).toFixed(1)}K`\n ) : (\n stats.totalApiCalls.toString()\n )\n }\n icon={<Activity size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.activeInstalls\", \"Active Installs\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.activeInstalls > 1000 ? (\n `${(stats.activeInstalls / 1000).toFixed(1)}K`\n ) : (\n stats.activeInstalls.toString()\n )\n }\n icon={<Users size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.monthlyRevenue\", \"Monthly Revenue\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n `$${stats.monthlyRevenue.toLocaleString()}`\n )\n }\n icon={<DollarSign size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.errorRate\", \"Error Rate\")}\n value={`${stats.errorRate}%`}\n icon={<TrendingUp size={20} />}\n trend={undefined}\n />\n </StatCardGrid>\n </PageSection>\n\n {/* Quick Actions & Recent Items */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n {/* Recent Apps */}\n <PageSection\n title={tr(\"devportal.recentApplications\", \"Recent Applications\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/apps\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n {isLoading ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-muted-foreground\" />\n </div>\n ) : applications.length > 0 ? (\n <div className=\"space-y-2\">\n {applications\n .slice(0, 3)\n .map((app: ApplicationFieldsFragment) => (\n <AppListItem\n key={app.id}\n app={app as Application}\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<AppWindow size={24} />}\n title={tr(\"devportal.noApplicationsYet\", \"No applications yet\")}\n description={tr(\n \"devportal.createFirstApplicationToGetStarted\",\n \"Create your first application to get started\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n }\n />\n )}\n </PageSection>\n\n {/* Quick Actions */}\n <PageSection title={tr(\"devportal.quickActions\", \"Quick Actions\")}>\n <div className=\"grid gap-3\">\n <QuickActionCard\n title={tr(\"devportal.createNewApp\", \"Create New App\")}\n description={tr(\n \"devportal.startBuildingANewApplication\",\n \"Start building a new application\",\n )}\n icon={<AppWindow size={20} />}\n onClick={() => handleNavigate(\"/apps/new\")}\n />\n <QuickActionCard\n title={tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n description={tr(\n \"devportal.setUpATeamToCollaborate\",\n \"Set up a team to collaborate\",\n )}\n icon={<Building2 size={20} />}\n onClick={() => handleNavigate(\"/organizations/new\")}\n />\n <QuickActionCard\n title={tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n description={tr(\n \"devportal.createCredentialsForYourApps\",\n \"Create credentials for your apps\",\n )}\n icon={<Key size={20} />}\n onClick={() => handleNavigate(\"/api-keys\")}\n />\n <QuickActionCard\n title={tr(\"devportal.configureWebhooks\", \"Configure Webhooks\")}\n description={tr(\n \"devportal.setUpEventNotifications\",\n \"Set up event notifications\",\n )}\n icon={<Webhook size={20} />}\n onClick={() => handleNavigate(\"/webhooks\")}\n />\n </div>\n </PageSection>\n </div>\n\n {/* Organizations */}\n {isLoading ? (\n <PageSection\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n >\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-muted-foreground\" />\n </div>\n </PageSection>\n ) : organizations.length > 0 ? (\n <PageSection\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/organizations\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {organizations\n .slice(0, 3)\n .map((org: OrganizationFieldsFragment) => (\n <OrgCard\n key={org.id}\n org={org as DeveloperOrganization}\n onClick={() => handleNavigate(`/organizations/${org.id}`)}\n />\n ))}\n </div>\n </PageSection>\n ) : null}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// Quick action card component\nconst QuickActionCard: FC<{\n title: string;\n description: string;\n icon: React.ReactNode;\n onClick: () => void;\n}> = ({ title, description, icon, onClick }) => (\n <button\n onClick={onClick}\n className=\"flex items-center gap-4 rounded-lg border bg-card p-4 text-left hover:bg-accent transition-colors\"\n >\n <div className=\"rounded-lg bg-primary/10 p-2 text-primary\">{icon}</div>\n <div>\n <p className=\"font-medium\">{title}</p>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n </button>\n);\n\n// Organization card component\nconst OrgCard: FC<{\n org: DeveloperOrganization;\n onClick: () => void;\n}> = ({ org, onClick }) => {\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 return (\n <button\n onClick={onClick}\n className=\"rounded-lg border bg-card p-4 text-left hover:bg-accent transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-10 w-10 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary\">\n <Building2 size={20} />\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium truncate\">{org.name}</p>\n <p className=\"text-sm text-muted-foreground\">\n {org.publishedApps}{\" \"}\n {tr(\"devportal.appsPublished\", \"apps published\")}\n </p>\n </div>\n </div>\n <div className=\"mt-3 flex gap-4 text-sm text-muted-foreground\">\n <span>\n {(org.totalInstalls || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.installs\", \"installs\")}\n </span>\n <span>\n ${(org.totalRevenue || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.revenue\", \"revenue\")}\n </span>\n </div>\n </button>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/devportal/pages/DashboardPage.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n AppWindow,\n Building2,\n Key,\n Webhook,\n TrendingUp,\n Users,\n DollarSign,\n Activity,\n Plus,\n ArrowRight,\n Loader2,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { StatCard, StatCardGrid } from \"../components/StatCard\";\nimport { AppListItem } from \"../components/AppCard\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { Application, DeveloperOrganization } from \"../types\";\nimport {\n useGetDevportalDashboardBootstrapQuery,\n type OrganizationFieldsFragment,\n type ApplicationFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Developer Portal Dashboard Page\n *\n * Shows an overview of the developer's applications, organizations,\n * and key metrics using real data from the GraphQL API.\n */\nexport const DashboardPage: FC = () => {\n const { navigate, basePath } = useDevportal();\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\n // Fetch dashboard bootstrap in a single global query\n const { data, loading } = useGetDevportalDashboardBootstrapQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const organizations: OrganizationFieldsFragment[] =\n data?.myDeveloperOrganizations ?? [];\n const applications: ApplicationFieldsFragment[] =\n data?.applications?.edges?.map(\n (e: { node: ApplicationFieldsFragment }) => e.node,\n ) ?? [];\n\n // Calculate aggregate stats\n const stats = {\n totalApps: data?.applications?.totalCount ?? 0,\n totalOrgs: organizations.length,\n totalApiKeys: 0,\n totalWebhooks: 0,\n totalApiCalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + parseInt(org.totalApiCalls || \"0\"),\n 0,\n ),\n activeInstalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.activeInstalls || 0),\n 0,\n ),\n monthlyRevenue: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.totalRevenue || 0),\n 0,\n ),\n errorRate: 0,\n };\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const isLoading = loading;\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_READ}>\n <PageLayout\n title={tr(\"devportal.developerPortal\", \"Developer Portal\")}\n description={tr(\n \"devportal.buildManageMonitor\",\n \"Build, manage, and monitor your applications\",\n )}\n actions={\n <button\n data-tour=\"devportal-dashboard-create-app-button\"\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n }\n >\n {/* Stats Overview */}\n <PageSection data-tour=\"devportal-dashboard-stats-overview\">\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.totalApps\", \"Total Apps\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalApps\n )\n }\n icon={<AppWindow size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.organizations\", \"Organizations\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalOrgs\n )\n }\n icon={<Building2 size={20} />}\n />\n <StatCard\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n value={stats.totalApiKeys}\n icon={<Key size={20} />}\n description={tr(\"devportal.activeKeys\", \"Active keys\")}\n />\n <StatCard\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n value={stats.totalWebhooks}\n icon={<Webhook size={20} />}\n description={tr(\"devportal.configured\", \"Configured\")}\n />\n </StatCardGrid>\n </PageSection>\n\n {/* Usage Stats */}\n <PageSection\n title={tr(\"devportal.usageOverview\", \"Usage Overview\")}\n data-tour=\"devportal-dashboard-usage-overview\"\n >\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.apiCalls\", \"API Calls\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.totalApiCalls > 1000000 ? (\n `${(stats.totalApiCalls / 1000000).toFixed(1)}M`\n ) : stats.totalApiCalls > 1000 ? (\n `${(stats.totalApiCalls / 1000).toFixed(1)}K`\n ) : (\n stats.totalApiCalls.toString()\n )\n }\n icon={<Activity size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.activeInstalls\", \"Active Installs\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.activeInstalls > 1000 ? (\n `${(stats.activeInstalls / 1000).toFixed(1)}K`\n ) : (\n stats.activeInstalls.toString()\n )\n }\n icon={<Users size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.monthlyRevenue\", \"Monthly Revenue\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n `$${stats.monthlyRevenue.toLocaleString()}`\n )\n }\n icon={<DollarSign size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.errorRate\", \"Error Rate\")}\n value={`${stats.errorRate}%`}\n icon={<TrendingUp size={20} />}\n trend={undefined}\n />\n </StatCardGrid>\n </PageSection>\n\n {/* Quick Actions & Recent Items */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n {/* Recent Apps */}\n <PageSection\n data-tour=\"devportal-dashboard-recent-apps\"\n title={tr(\"devportal.recentApplications\", \"Recent Applications\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/apps\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n {isLoading ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-muted-foreground\" />\n </div>\n ) : applications.length > 0 ? (\n <div className=\"space-y-2\">\n {applications\n .slice(0, 3)\n .map((app: ApplicationFieldsFragment) => (\n <AppListItem\n key={app.id}\n app={app as Application}\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<AppWindow size={24} />}\n title={tr(\"devportal.noApplicationsYet\", \"No applications yet\")}\n description={tr(\n \"devportal.createFirstApplicationToGetStarted\",\n \"Create your first application to get started\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n }\n />\n )}\n </PageSection>\n\n {/* Quick Actions */}\n <PageSection\n title={tr(\"devportal.quickActions\", \"Quick Actions\")}\n data-tour=\"devportal-dashboard-quick-actions\"\n >\n <div className=\"grid gap-3\">\n <QuickActionCard\n title={tr(\"devportal.createNewApp\", \"Create New App\")}\n description={tr(\n \"devportal.startBuildingANewApplication\",\n \"Start building a new application\",\n )}\n icon={<AppWindow size={20} />}\n onClick={() => handleNavigate(\"/apps/new\")}\n />\n <QuickActionCard\n title={tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n description={tr(\n \"devportal.setUpATeamToCollaborate\",\n \"Set up a team to collaborate\",\n )}\n icon={<Building2 size={20} />}\n onClick={() => handleNavigate(\"/organizations/new\")}\n />\n <QuickActionCard\n title={tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n description={tr(\n \"devportal.createCredentialsForYourApps\",\n \"Create credentials for your apps\",\n )}\n icon={<Key size={20} />}\n onClick={() => handleNavigate(\"/api-keys\")}\n />\n <QuickActionCard\n title={tr(\"devportal.configureWebhooks\", \"Configure Webhooks\")}\n description={tr(\n \"devportal.setUpEventNotifications\",\n \"Set up event notifications\",\n )}\n icon={<Webhook size={20} />}\n onClick={() => handleNavigate(\"/webhooks\")}\n />\n </div>\n </PageSection>\n </div>\n\n {/* Organizations */}\n {isLoading ? (\n <PageSection\n data-tour=\"devportal-dashboard-organizations\"\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n >\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-muted-foreground\" />\n </div>\n </PageSection>\n ) : organizations.length > 0 ? (\n <PageSection\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/organizations\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {organizations\n .slice(0, 3)\n .map((org: OrganizationFieldsFragment) => (\n <OrgCard\n key={org.id}\n org={org as DeveloperOrganization}\n onClick={() => handleNavigate(`/organizations/${org.id}`)}\n />\n ))}\n </div>\n </PageSection>\n ) : null}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// Quick action card component\nconst QuickActionCard: FC<{\n title: string;\n description: string;\n icon: React.ReactNode;\n onClick: () => void;\n}> = ({ title, description, icon, onClick }) => (\n <button\n onClick={onClick}\n className=\"flex items-center gap-4 rounded-lg border bg-card p-4 text-left hover:bg-accent transition-colors\"\n >\n <div className=\"rounded-lg bg-primary/10 p-2 text-primary\">{icon}</div>\n <div>\n <p className=\"font-medium\">{title}</p>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n </button>\n);\n\n// Organization card component\nconst OrgCard: FC<{\n org: DeveloperOrganization;\n onClick: () => void;\n}> = ({ org, onClick }) => {\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 return (\n <button\n onClick={onClick}\n className=\"rounded-lg border bg-card p-4 text-left hover:bg-accent transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-10 w-10 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary\">\n <Building2 size={20} />\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium truncate\">{org.name}</p>\n <p className=\"text-sm text-muted-foreground\">\n {org.publishedApps}{\" \"}\n {tr(\"devportal.appsPublished\", \"apps published\")}\n </p>\n </div>\n </div>\n <div className=\"mt-3 flex gap-4 text-sm text-muted-foreground\">\n <span>\n {(org.totalInstalls || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.installs\", \"installs\")}\n </span>\n <span>\n ${(org.totalRevenue || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.revenue\", \"revenue\")}\n </span>\n </div>\n </button>\n );\n};\n"],"mappings":"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"}
|
|
@@ -508,13 +508,13 @@ var O = () => {
|
|
|
508
508
|
Te && /* @__PURE__ */ D("button", {
|
|
509
509
|
type: "button",
|
|
510
510
|
onClick: () => V(!0),
|
|
511
|
-
className: "inline-flex items-center gap-2 rounded-md border border-accent-orange bg-
|
|
511
|
+
className: "inline-flex items-center gap-2 rounded-md border border-accent-orange bg-bg-surface px-3 py-1.5 text-sm font-medium text-accent-orange hover:bg-accent-orange-subtle transition-colors",
|
|
512
512
|
children: [/* @__PURE__ */ E(ie, { size: 14 }), N("devportal.deprecateApp", "Deprecate App")]
|
|
513
513
|
}),
|
|
514
514
|
Ee && /* @__PURE__ */ D("button", {
|
|
515
515
|
type: "button",
|
|
516
516
|
onClick: () => H(!0),
|
|
517
|
-
className: "inline-flex items-center gap-2 rounded-md border border-accent-orange bg-
|
|
517
|
+
className: "inline-flex items-center gap-2 rounded-md border border-accent-orange bg-bg-surface px-3 py-1.5 text-sm font-medium text-accent-orange hover:bg-accent-orange-subtle transition-colors",
|
|
518
518
|
children: [/* @__PURE__ */ E(ae, { size: 14 }), N("devportal.deactivateApp", "Deactivate App")]
|
|
519
519
|
}),
|
|
520
520
|
/* @__PURE__ */ D("button", {
|
|
@@ -522,7 +522,7 @@ var O = () => {
|
|
|
522
522
|
onClick: () => z(!0),
|
|
523
523
|
disabled: !M,
|
|
524
524
|
"aria-disabled": !M,
|
|
525
|
-
className: "inline-flex items-center gap-2 rounded-md border border-status-error-bg bg-
|
|
525
|
+
className: "inline-flex items-center gap-2 rounded-md border border-status-error-bg bg-bg-surface px-3 py-1.5 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle transition-colors disabled:opacity-50",
|
|
526
526
|
children: [/* @__PURE__ */ E(ce, { size: 14 }), N("devportal.deleteApp", "Delete App")]
|
|
527
527
|
})
|
|
528
528
|
]
|