portal-mojo 0.1.0
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/LICENSE +201 -0
- package/README.md +55 -0
- package/docs/DetailView.md +173 -0
- package/docs/ModelTable.md +128 -0
- package/docs/README.md +98 -0
- package/docs/admin-assistant.md +72 -0
- package/docs/admin-bouncer.md +120 -0
- package/docs/admin-cloudwatch.md +38 -0
- package/docs/admin-credentials.md +116 -0
- package/docs/admin-dashboard.md +15 -0
- package/docs/admin-devices-geoip.md +284 -0
- package/docs/admin-dns-certificates.md +122 -0
- package/docs/admin-dns-records.md +65 -0
- package/docs/admin-dns-registrar.md +33 -0
- package/docs/admin-dns.md +80 -0
- package/docs/admin-identity-users.md +103 -0
- package/docs/admin-incidents.md +71 -0
- package/docs/admin-jobs.md +426 -0
- package/docs/admin-members.md +112 -0
- package/docs/admin-messaging.md +56 -0
- package/docs/admin-metrics-explorer.md +63 -0
- package/docs/admin-monitoring.md +99 -0
- package/docs/admin-network-security.md +393 -0
- package/docs/admin-phonehub.md +27 -0
- package/docs/admin-push.md +36 -0
- package/docs/admin-rules.md +87 -0
- package/docs/admin-security-tickets.md +40 -0
- package/docs/admin-settings.md +78 -0
- package/docs/admin-shortlinks.md +32 -0
- package/docs/admin-storage.md +141 -0
- package/docs/auth-pages.md +212 -0
- package/docs/auth.md +58 -0
- package/docs/calendar.md +122 -0
- package/docs/charts.md +303 -0
- package/docs/client.md +206 -0
- package/docs/collection-multiselect.md +117 -0
- package/docs/collection-select.md +132 -0
- package/docs/combobox.md +105 -0
- package/docs/dataview.md +226 -0
- package/docs/datepicker.md +151 -0
- package/docs/daterange.md +170 -0
- package/docs/datetimepicker.md +249 -0
- package/docs/defineModel.md +72 -0
- package/docs/detail-primitives.md +174 -0
- package/docs/feedback.md +85 -0
- package/docs/file-upload-ux.md +146 -0
- package/docs/forms.md +435 -0
- package/docs/grouping-and-fmt.md +98 -0
- package/docs/idioms.md +112 -0
- package/docs/image-editor.md +101 -0
- package/docs/loading.md +242 -0
- package/docs/location-address.md +76 -0
- package/docs/markdown.md +203 -0
- package/docs/menus-and-access.md +89 -0
- package/docs/multiselect-dropdown.md +153 -0
- package/docs/params.md +73 -0
- package/docs/popover.md +130 -0
- package/docs/realtime.md +61 -0
- package/docs/record-feed.md +238 -0
- package/docs/right-panel.md +42 -0
- package/docs/tabs-and-form-wizard.md +60 -0
- package/docs/taginput.md +120 -0
- package/docs/timepicker.md +173 -0
- package/docs/timezone-select.md +147 -0
- package/docs/worldmap.md +302 -0
- package/package.json +55 -0
- package/src/admin/assistant/AssistantFeed.tsx +29 -0
- package/src/admin/assistant/AssistantPanel.tsx +65 -0
- package/src/admin/assistant/api.ts +17 -0
- package/src/admin/assistant/data.ts +63 -0
- package/src/admin/assistant/index.ts +21 -0
- package/src/admin/assistant/launchers.tsx +33 -0
- package/src/admin/assistant/pages.tsx +78 -0
- package/src/admin/assistant/streaming.ts +238 -0
- package/src/admin/assistant/types.ts +30 -0
- package/src/admin/bouncer/devices.tsx +230 -0
- package/src/admin/bouncer/index.ts +34 -0
- package/src/admin/bouncer/models.ts +199 -0
- package/src/admin/bouncer/signals.tsx +182 -0
- package/src/admin/bouncer/signatures.tsx +220 -0
- package/src/admin/cloudwatch/CloudWatchChart.tsx +61 -0
- package/src/admin/cloudwatch/CloudWatchDashboardPage.tsx +52 -0
- package/src/admin/cloudwatch/CloudWatchResourceDetail.tsx +33 -0
- package/src/admin/cloudwatch/data.ts +163 -0
- package/src/admin/cloudwatch/index.ts +16 -0
- package/src/admin/credentials/group-api-keys.tsx +433 -0
- package/src/admin/credentials/index.ts +4 -0
- package/src/admin/credentials/models.ts +244 -0
- package/src/admin/credentials/secret-dialog.tsx +77 -0
- package/src/admin/credentials/webhook-subscriptions.tsx +423 -0
- package/src/admin/dashboard/AdminDashboardPage.tsx +101 -0
- package/src/admin/dashboard/data.ts +60 -0
- package/src/admin/dashboard/index.ts +2 -0
- package/src/admin/dns/CertificateDetail.tsx +115 -0
- package/src/admin/dns/CertificateLifecyclePoller.tsx +141 -0
- package/src/admin/dns/CertificateRequestDialog.tsx +103 -0
- package/src/admin/dns/CertificatesPage.tsx +85 -0
- package/src/admin/dns/DnsRecordEditor.tsx +110 -0
- package/src/admin/dns/DnsRecordsPage.tsx +36 -0
- package/src/admin/dns/DnsRecordsPanel.tsx +71 -0
- package/src/admin/dns/DomainCertificatesSection.tsx +40 -0
- package/src/admin/dns/DomainDetail.tsx +41 -0
- package/src/admin/dns/DomainOnboardingDialog.tsx +27 -0
- package/src/admin/dns/DomainPurchaseDetail.tsx +11 -0
- package/src/admin/dns/DomainPurchaseWizard.tsx +112 -0
- package/src/admin/dns/DomainPurchasesPage.tsx +16 -0
- package/src/admin/dns/DomainsPage.tsx +27 -0
- package/src/admin/dns/ProviderCredentialsPage.tsx +304 -0
- package/src/admin/dns/RegistrantContactPage.tsx +42 -0
- package/src/admin/dns/api.ts +515 -0
- package/src/admin/dns/certificate-data.ts +221 -0
- package/src/admin/dns/data.ts +32 -0
- package/src/admin/dns/dns-data.ts +266 -0
- package/src/admin/dns/dns-integration.ts +24 -0
- package/src/admin/dns/domain-links.ts +37 -0
- package/src/admin/dns/index.ts +86 -0
- package/src/admin/dns/models.ts +418 -0
- package/src/admin/dns/purchase-data.ts +67 -0
- package/src/admin/dns/registrant-data.ts +52 -0
- package/src/admin/identity/index.ts +5 -0
- package/src/admin/identity/members/GroupMembersPanel.tsx +146 -0
- package/src/admin/identity/members/MemberDetail.tsx +226 -0
- package/src/admin/identity/members/MembersPage.tsx +144 -0
- package/src/admin/identity/members/index.ts +24 -0
- package/src/admin/identity/members/member-flows.tsx +132 -0
- package/src/admin/identity/members/models.ts +204 -0
- package/src/admin/identity/users/UserDetail.tsx +257 -0
- package/src/admin/identity/users/UsersPage.tsx +163 -0
- package/src/admin/identity/users/index.ts +27 -0
- package/src/admin/identity/users/models.ts +232 -0
- package/src/admin/identity/users/sections/ApiKeysSection.tsx +119 -0
- package/src/admin/identity/users/sections/AuditSection.tsx +190 -0
- package/src/admin/identity/users/sections/DevicesSection.tsx +140 -0
- package/src/admin/identity/users/sections/GroupsSection.tsx +64 -0
- package/src/admin/identity/users/sections/LoginsSection.tsx +106 -0
- package/src/admin/identity/users/sections/MetadataPanel.tsx +36 -0
- package/src/admin/identity/users/sections/NotificationsSection.tsx +117 -0
- package/src/admin/identity/users/sections/OAuthSection.tsx +72 -0
- package/src/admin/identity/users/sections/OverviewSection.tsx +109 -0
- package/src/admin/identity/users/sections/PermissionsSection.tsx +35 -0
- package/src/admin/identity/users/sections/PersonalSection.tsx +73 -0
- package/src/admin/identity/users/sections/ProfileSection.tsx +175 -0
- package/src/admin/identity/users/sections/SecuritySection.tsx +123 -0
- package/src/admin/identity/users/sections/actions.tsx +752 -0
- package/src/admin/identity/users/sections/permission-catalog.ts +138 -0
- package/src/admin/identity/users/sections/queries.ts +74 -0
- package/src/admin/identity/users/sections/shared.tsx +299 -0
- package/src/admin/incidents/EventDetail.tsx +43 -0
- package/src/admin/incidents/EventsPage.tsx +52 -0
- package/src/admin/incidents/IncidentDetail.tsx +68 -0
- package/src/admin/incidents/IncidentsPage.tsx +102 -0
- package/src/admin/incidents/forensics.tsx +73 -0
- package/src/admin/incidents/index.ts +16 -0
- package/src/admin/incidents/models.ts +113 -0
- package/src/admin/incidents/sanitize.ts +71 -0
- package/src/admin/index.ts +383 -0
- package/src/admin/jobs/JobDashboardPage.tsx +111 -0
- package/src/admin/jobs/JobDetail.tsx +721 -0
- package/src/admin/jobs/JobRunnersPage.tsx +216 -0
- package/src/admin/jobs/JobsTablePage.tsx +122 -0
- package/src/admin/jobs/RunnerDetail.tsx +576 -0
- package/src/admin/jobs/ScheduledTaskDetail.tsx +421 -0
- package/src/admin/jobs/ScheduledTasksPage.tsx +167 -0
- package/src/admin/jobs/columns.tsx +297 -0
- package/src/admin/jobs/control.ts +488 -0
- package/src/admin/jobs/index.ts +85 -0
- package/src/admin/jobs/models.ts +599 -0
- package/src/admin/jobs/queries.ts +272 -0
- package/src/admin/jobs/scheduled-task-form.tsx +493 -0
- package/src/admin/jobs/sections/JobChannelsPanel.tsx +142 -0
- package/src/admin/jobs/sections/JobOperationsSection.tsx +423 -0
- package/src/admin/jobs/sections/JobRunnersStrip.tsx +82 -0
- package/src/admin/jobs/sections/JobThroughputSection.tsx +118 -0
- package/src/admin/messaging/EmailDomainWizard.tsx +24 -0
- package/src/admin/messaging/EmailDomainsPage.tsx +17 -0
- package/src/admin/messaging/EmailTemplatesPage.tsx +13 -0
- package/src/admin/messaging/MailboxesPage.tsx +15 -0
- package/src/admin/messaging/PublicMessagesPage.tsx +13 -0
- package/src/admin/messaging/SandboxedEmailPreview.tsx +23 -0
- package/src/admin/messaging/SentMessagesPage.tsx +11 -0
- package/src/admin/messaging/api.ts +17 -0
- package/src/admin/messaging/data.ts +10 -0
- package/src/admin/messaging/index.ts +23 -0
- package/src/admin/messaging/models.ts +76 -0
- package/src/admin/messaging/push/PushPage.tsx +44 -0
- package/src/admin/messaging/push/api.ts +17 -0
- package/src/admin/messaging/push/index.ts +9 -0
- package/src/admin/messaging/push/models.ts +43 -0
- package/src/admin/messaging/sanitize.ts +44 -0
- package/src/admin/monitoring/LogInspector.tsx +160 -0
- package/src/admin/monitoring/LogsPage.tsx +135 -0
- package/src/admin/monitoring/MetricsExplorerPage.tsx +360 -0
- package/src/admin/monitoring/MetricsPermissionsPage.tsx +166 -0
- package/src/admin/monitoring/MetricsSourcePicker.tsx +136 -0
- package/src/admin/monitoring/index.ts +55 -0
- package/src/admin/monitoring/metrics-explorer-client.ts +229 -0
- package/src/admin/monitoring/metrics-explorer-data.ts +158 -0
- package/src/admin/monitoring/models.ts +155 -0
- package/src/admin/network/BlockedIPsPage.tsx +194 -0
- package/src/admin/network/BlockedIpDetail.tsx +262 -0
- package/src/admin/network/FirewallLogPage.tsx +115 -0
- package/src/admin/network/IPSetDetail.tsx +280 -0
- package/src/admin/network/IPSetEditor.tsx +191 -0
- package/src/admin/network/IPSetsPage.tsx +177 -0
- package/src/admin/network/geofence/BlocksTab.tsx +289 -0
- package/src/admin/network/geofence/ExemptionsTab.tsx +349 -0
- package/src/admin/network/geofence/GeofencingPage.tsx +119 -0
- package/src/admin/network/geofence/PostureHeader.tsx +184 -0
- package/src/admin/network/geofence/RuleEditor.tsx +176 -0
- package/src/admin/network/geofence/RulesTab.tsx +254 -0
- package/src/admin/network/geofence/SimulatorTab.tsx +281 -0
- package/src/admin/network/geofence/geofence-data.ts +674 -0
- package/src/admin/network/index.ts +68 -0
- package/src/admin/network/models.ts +607 -0
- package/src/admin/phonehub/PhoneHubPage.tsx +57 -0
- package/src/admin/phonehub/api.ts +47 -0
- package/src/admin/phonehub/data.ts +6 -0
- package/src/admin/phonehub/index.ts +10 -0
- package/src/admin/phonehub/models.ts +68 -0
- package/src/admin/rules/HandlerChainBuilder.tsx +51 -0
- package/src/admin/rules/RuleSetDetailPage.tsx +33 -0
- package/src/admin/rules/RuleSetsPage.tsx +33 -0
- package/src/admin/rules/editors.tsx +54 -0
- package/src/admin/rules/handler-dsl.ts +213 -0
- package/src/admin/rules/index.ts +13 -0
- package/src/admin/rules/models.ts +112 -0
- package/src/admin/security/devices/LoginEventDetail.tsx +123 -0
- package/src/admin/security/devices/LoginLocationMap.tsx +321 -0
- package/src/admin/security/devices/LoginLocationsPage.tsx +162 -0
- package/src/admin/security/devices/UserDeviceDetail.tsx +546 -0
- package/src/admin/security/devices/UserDeviceLocationDetail.tsx +272 -0
- package/src/admin/security/devices/UserDevicesPage.tsx +84 -0
- package/src/admin/security/devices/index.ts +40 -0
- package/src/admin/security/devices/models.ts +470 -0
- package/src/admin/security/geoip/GeoIpCachePage.tsx +135 -0
- package/src/admin/security/geoip/GeoIpDossier.tsx +804 -0
- package/src/admin/security/geoip/geoip-forms.ts +102 -0
- package/src/admin/security/geoip/index.ts +27 -0
- package/src/admin/security/geoip/models.ts +397 -0
- package/src/admin/security/index.ts +31 -0
- package/src/admin/security/models.ts +137 -0
- package/src/admin/security/tickets.tsx +562 -0
- package/src/admin/security-permissions.ts +5 -0
- package/src/admin/settings/SettingDetail.tsx +57 -0
- package/src/admin/settings/SettingEditor.tsx +157 -0
- package/src/admin/settings/SettingsPage.tsx +72 -0
- package/src/admin/settings/index.ts +19 -0
- package/src/admin/settings/model.ts +143 -0
- package/src/admin/shortlinks/ShortlinkDetail.tsx +55 -0
- package/src/admin/shortlinks/ShortlinkHistoryPage.tsx +18 -0
- package/src/admin/shortlinks/ShortlinksPage.tsx +59 -0
- package/src/admin/shortlinks/index.ts +18 -0
- package/src/admin/shortlinks/models.ts +246 -0
- package/src/admin/storage/BackendsPage.tsx +43 -0
- package/src/admin/storage/BucketDetail.tsx +77 -0
- package/src/admin/storage/BucketsPage.tsx +64 -0
- package/src/admin/storage/FileManagerDetail.tsx +36 -0
- package/src/admin/storage/FilePreview.tsx +54 -0
- package/src/admin/storage/FileUploadSurface.tsx +250 -0
- package/src/admin/storage/FileView.tsx +109 -0
- package/src/admin/storage/FilesPage.tsx +53 -0
- package/src/admin/storage/file-renditions.ts +99 -0
- package/src/admin/storage/index.ts +31 -0
- package/src/admin/storage/models.ts +585 -0
- package/src/admin/storage/storage-dialogs.tsx +80 -0
- package/src/charts/CircularProgress.tsx +281 -0
- package/src/charts/KPIStrip.tsx +162 -0
- package/src/charts/KPITile.tsx +125 -0
- package/src/charts/MetricsChart.tsx +459 -0
- package/src/charts/MetricsMiniWidget.tsx +588 -0
- package/src/charts/MiniChart.tsx +283 -0
- package/src/charts/PieChart.tsx +354 -0
- package/src/charts/SeriesChart.tsx +378 -0
- package/src/charts/chart-dialogs.tsx +149 -0
- package/src/charts/exportChart.ts +144 -0
- package/src/charts/index.ts +28 -0
- package/src/charts/pie-math.ts +127 -0
- package/src/charts/stats.ts +265 -0
- package/src/charts/worldmap/WorldMap.tsx +649 -0
- package/src/charts/worldmap/countryCentroids.ts +304 -0
- package/src/charts/worldmap/geo.ts +293 -0
- package/src/charts/worldmap/use-world-land.ts +47 -0
- package/src/charts/worldmap/world-land.ts +18 -0
- package/src/charts/worldmap/worldmap-data.ts +285 -0
- package/src/client/auth.ts +737 -0
- package/src/client/client.ts +268 -0
- package/src/client/duid.ts +31 -0
- package/src/client/errors.ts +33 -0
- package/src/client/group-context.ts +28 -0
- package/src/client/group.tsx +126 -0
- package/src/client/hooks.ts +50 -0
- package/src/client/index.ts +37 -0
- package/src/client/jwt.ts +75 -0
- package/src/client/location.ts +285 -0
- package/src/client/lookups.ts +41 -0
- package/src/client/markdown-fallback.tsx +115 -0
- package/src/client/markdown-parse.ts +386 -0
- package/src/client/markdown-sanitize.ts +217 -0
- package/src/client/markdown.ts +122 -0
- package/src/client/me.ts +188 -0
- package/src/client/mock.ts +9984 -0
- package/src/client/model.ts +227 -0
- package/src/client/params.ts +292 -0
- package/src/client/realtime-mock.ts +221 -0
- package/src/client/realtime.ts +561 -0
- package/src/client/record-feed.ts +329 -0
- package/src/client/safe-export.ts +71 -0
- package/src/client/types.ts +278 -0
- package/src/client/upload.ts +421 -0
- package/src/ui/AddressField.tsx +273 -0
- package/src/ui/AttachmentQueue.tsx +92 -0
- package/src/ui/CollectionMultiSelect.tsx +622 -0
- package/src/ui/CollectionSelect.tsx +517 -0
- package/src/ui/ComboBox.tsx +336 -0
- package/src/ui/DetailView.tsx +438 -0
- package/src/ui/FileDrop.tsx +194 -0
- package/src/ui/FileField.tsx +369 -0
- package/src/ui/FilterBar.tsx +269 -0
- package/src/ui/FormFields.tsx +77 -0
- package/src/ui/FormView.tsx +360 -0
- package/src/ui/FormWizard.tsx +336 -0
- package/src/ui/GroupSwitcher.tsx +202 -0
- package/src/ui/Guarded.tsx +16 -0
- package/src/ui/MarkdownView.tsx +89 -0
- package/src/ui/ModelTable.tsx +848 -0
- package/src/ui/MultiSelectDropdown.tsx +390 -0
- package/src/ui/Popover.tsx +204 -0
- package/src/ui/RecordFeed.tsx +627 -0
- package/src/ui/RequiresGroup.tsx +32 -0
- package/src/ui/RightPanel.tsx +107 -0
- package/src/ui/SidebarNav.tsx +330 -0
- package/src/ui/Tabs.tsx +161 -0
- package/src/ui/TagInput.tsx +381 -0
- package/src/ui/ThemeProvider.tsx +52 -0
- package/src/ui/UploadQueue.tsx +603 -0
- package/src/ui/armed-button.tsx +91 -0
- package/src/ui/date/Calendar.tsx +498 -0
- package/src/ui/date/DatePicker.tsx +289 -0
- package/src/ui/date/DateRangePicker.tsx +280 -0
- package/src/ui/date/DateTimePicker.tsx +587 -0
- package/src/ui/date/PresetRail.tsx +165 -0
- package/src/ui/date/TimePicker.tsx +624 -0
- package/src/ui/date/TimezoneSelect.tsx +267 -0
- package/src/ui/date/fns.ts +679 -0
- package/src/ui/detail/DataView.tsx +692 -0
- package/src/ui/detail/FlowStrip.tsx +80 -0
- package/src/ui/detail/KnownFieldsCard.tsx +121 -0
- package/src/ui/detail/MetadataSection.tsx +219 -0
- package/src/ui/detail/StackTraceView.tsx +150 -0
- package/src/ui/detail/StatusPanel.tsx +73 -0
- package/src/ui/detail/Timeline.tsx +52 -0
- package/src/ui/detail/index.ts +12 -0
- package/src/ui/field-registry.tsx +428 -0
- package/src/ui/field-wire.ts +322 -0
- package/src/ui/form-autosave.ts +583 -0
- package/src/ui/format.ts +444 -0
- package/src/ui/grouping.ts +150 -0
- package/src/ui/image-editor/ImageEditor.tsx +484 -0
- package/src/ui/image-editor/canvas.ts +254 -0
- package/src/ui/image-editor/index.ts +3 -0
- package/src/ui/image-editor/math.ts +416 -0
- package/src/ui/index.ts +58 -0
- package/src/ui/loading.tsx +604 -0
- package/src/ui/menu-registry.ts +213 -0
- package/src/ui/modal.tsx +297 -0
- package/src/ui/password.tsx +302 -0
- package/src/ui/schema-form-core.tsx +315 -0
- package/src/ui/toast.tsx +219 -0
- package/src/ui/ui.tsx +45 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// LocationClient — the six public django-mojo location endpoints behind one
|
|
2
|
+
// typed, non-Query transport. The provider session token is deliberately a
|
|
3
|
+
// private field: it never enters URL state, form values, storage or results.
|
|
4
|
+
import { mojoCall, type FetchOpts } from './client';
|
|
5
|
+
|
|
6
|
+
export type LocationTransport = (path: string, opts: FetchOpts) => Promise<unknown>;
|
|
7
|
+
|
|
8
|
+
export interface LocationEndpoints {
|
|
9
|
+
validate: string;
|
|
10
|
+
suggestions: string;
|
|
11
|
+
details: string;
|
|
12
|
+
geocode: string;
|
|
13
|
+
reverseGeocode: string;
|
|
14
|
+
timezone: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DEFAULT_ENDPOINTS: LocationEndpoints = {
|
|
18
|
+
validate: '/api/location/address/validate',
|
|
19
|
+
suggestions: '/api/location/address/suggestions',
|
|
20
|
+
details: '/api/location/address/place-details',
|
|
21
|
+
geocode: '/api/location/address/geocode',
|
|
22
|
+
reverseGeocode: '/api/location/address/reverse-geocode',
|
|
23
|
+
timezone: '/api/location/timezone',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface AddressSuggestion {
|
|
27
|
+
id: string | null;
|
|
28
|
+
place_id: string;
|
|
29
|
+
description: string;
|
|
30
|
+
main_text: string;
|
|
31
|
+
secondary_text: string;
|
|
32
|
+
types: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AddressSuggestions {
|
|
36
|
+
success: true;
|
|
37
|
+
data: AddressSuggestion[];
|
|
38
|
+
size: number;
|
|
39
|
+
count: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface AddressDetails {
|
|
43
|
+
address1?: string;
|
|
44
|
+
address2?: string;
|
|
45
|
+
city?: string;
|
|
46
|
+
state?: string;
|
|
47
|
+
state_code?: string;
|
|
48
|
+
postal_code?: string;
|
|
49
|
+
country?: string;
|
|
50
|
+
country_code?: string;
|
|
51
|
+
latitude?: number;
|
|
52
|
+
longitude?: number;
|
|
53
|
+
formatted_address?: string;
|
|
54
|
+
place_id?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ValidateAddressInput {
|
|
58
|
+
address1: string;
|
|
59
|
+
address2?: string;
|
|
60
|
+
city?: string;
|
|
61
|
+
state: string;
|
|
62
|
+
postal_code?: string;
|
|
63
|
+
provider?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface AddressValidation {
|
|
67
|
+
valid?: boolean;
|
|
68
|
+
source?: string;
|
|
69
|
+
standardized_address?: Record<string, unknown>;
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
71
|
+
[key: string]: unknown;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface GeocodeResult {
|
|
75
|
+
success: true;
|
|
76
|
+
latitude: number;
|
|
77
|
+
longitude: number;
|
|
78
|
+
formatted_address: string;
|
|
79
|
+
place_id: string;
|
|
80
|
+
address_components: Record<string, unknown>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ReverseGeocodeResult {
|
|
84
|
+
success: true;
|
|
85
|
+
formatted_address: string;
|
|
86
|
+
place_id: string;
|
|
87
|
+
address_components: Record<string, unknown>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface TimezoneResult {
|
|
91
|
+
success: true;
|
|
92
|
+
timezone_id: string;
|
|
93
|
+
timezone_name: string;
|
|
94
|
+
raw_offset: number;
|
|
95
|
+
dst_offset: number;
|
|
96
|
+
total_offset: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class StaleLocationRequestError extends Error {
|
|
100
|
+
constructor() {
|
|
101
|
+
super('Location request was superseded');
|
|
102
|
+
this.name = 'StaleLocationRequestError';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function isStaleLocationRequest(error: unknown): error is StaleLocationRequestError {
|
|
107
|
+
return error instanceof StaleLocationRequestError;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function record(value: unknown, label: string): Record<string, unknown> {
|
|
111
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
112
|
+
throw new TypeError(`Invalid ${label} response`);
|
|
113
|
+
}
|
|
114
|
+
return value as Record<string, unknown>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function requiredString(body: Record<string, unknown>, key: string, label: string): string {
|
|
118
|
+
if (typeof body[key] !== 'string') throw new TypeError(`Invalid ${label} response`);
|
|
119
|
+
return body[key] as string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function requiredNumber(body: Record<string, unknown>, key: string, label: string): number {
|
|
123
|
+
if (typeof body[key] !== 'number' || !Number.isFinite(body[key])) {
|
|
124
|
+
throw new TypeError(`Invalid ${label} response`);
|
|
125
|
+
}
|
|
126
|
+
return body[key] as number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function requireSuccess(body: Record<string, unknown>, label: string): void {
|
|
130
|
+
if (body.success !== true) throw new Error(typeof body.error === 'string' ? body.error : `${label} failed`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export class LocationClient {
|
|
134
|
+
#transport: LocationTransport;
|
|
135
|
+
#endpoints: LocationEndpoints;
|
|
136
|
+
#sessionToken: string | null = null;
|
|
137
|
+
#generation = 0;
|
|
138
|
+
#disposed = false;
|
|
139
|
+
|
|
140
|
+
constructor(options: { transport?: LocationTransport; endpoints?: Partial<LocationEndpoints> } = {}) {
|
|
141
|
+
this.#transport = options.transport ?? ((path, opts) => mojoCall(path, opts));
|
|
142
|
+
this.#endpoints = { ...DEFAULT_ENDPOINTS, ...options.endpoints };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Invalidate late completions without ending the provider session. */
|
|
146
|
+
cancelPending(): void {
|
|
147
|
+
this.#generation += 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Begin a new provider session and invalidate every pending completion. */
|
|
151
|
+
reset(): void {
|
|
152
|
+
this.#sessionToken = null;
|
|
153
|
+
this.#generation += 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Upstream changes are a hard boundary: old responses cannot repopulate. */
|
|
157
|
+
replaceTransport(transport: LocationTransport, endpoints: Partial<LocationEndpoints> = {}): void {
|
|
158
|
+
this.reset();
|
|
159
|
+
this.#transport = transport;
|
|
160
|
+
this.#endpoints = { ...DEFAULT_ENDPOINTS, ...endpoints };
|
|
161
|
+
this.#disposed = false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
dispose(): void {
|
|
165
|
+
this.#disposed = true;
|
|
166
|
+
this.reset();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async #request(path: string, opts: FetchOpts): Promise<Record<string, unknown>> {
|
|
170
|
+
if (this.#disposed) throw new StaleLocationRequestError();
|
|
171
|
+
const generation = ++this.#generation;
|
|
172
|
+
const result = await this.#transport(path, opts);
|
|
173
|
+
if (this.#disposed || generation !== this.#generation) throw new StaleLocationRequestError();
|
|
174
|
+
return record(result, 'location');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async validateAddress(input: ValidateAddressInput): Promise<AddressValidation> {
|
|
178
|
+
const body = await this.#request(this.#endpoints.validate, { method: 'POST', body: { ...input } });
|
|
179
|
+
return record(body.data, 'address validation') as AddressValidation;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async autocomplete(input: string, options: {
|
|
183
|
+
country?: string;
|
|
184
|
+
lat?: number;
|
|
185
|
+
lng?: number;
|
|
186
|
+
radius?: number;
|
|
187
|
+
} = {}): Promise<AddressSuggestions> {
|
|
188
|
+
const query = input.trim();
|
|
189
|
+
if (!query) {
|
|
190
|
+
this.cancelPending();
|
|
191
|
+
return { success: true, data: [], size: 0, count: 0 };
|
|
192
|
+
}
|
|
193
|
+
const params: Record<string, string | number> = {
|
|
194
|
+
input: query,
|
|
195
|
+
country: options.country ?? 'US',
|
|
196
|
+
};
|
|
197
|
+
if (options.lat !== undefined) params.lat = options.lat;
|
|
198
|
+
if (options.lng !== undefined) params.lng = options.lng;
|
|
199
|
+
if (options.radius !== undefined) params.radius = options.radius;
|
|
200
|
+
// Only the backend creates the first token. A returned token is
|
|
201
|
+
// accepted privately and necessarily rides subsequent GETs.
|
|
202
|
+
if (this.#sessionToken) params.session_token = this.#sessionToken;
|
|
203
|
+
|
|
204
|
+
const body = await this.#request(this.#endpoints.suggestions, { params });
|
|
205
|
+
requireSuccess(body, 'Address suggestions');
|
|
206
|
+
const token = requiredString(body, 'session_token', 'address suggestions');
|
|
207
|
+
if (!Array.isArray(body.data)) throw new TypeError('Invalid address suggestions response');
|
|
208
|
+
const data = body.data.map((raw) => {
|
|
209
|
+
const item = record(raw, 'address suggestion');
|
|
210
|
+
const placeId = typeof item.place_id === 'string'
|
|
211
|
+
? item.place_id
|
|
212
|
+
: typeof item.id === 'string' ? item.id : '';
|
|
213
|
+
if (!placeId) throw new TypeError('Invalid address suggestion response');
|
|
214
|
+
return {
|
|
215
|
+
id: typeof item.id === 'string' ? item.id : null,
|
|
216
|
+
place_id: placeId,
|
|
217
|
+
description: typeof item.description === 'string' ? item.description : '',
|
|
218
|
+
main_text: typeof item.main_text === 'string' ? item.main_text : '',
|
|
219
|
+
secondary_text: typeof item.secondary_text === 'string' ? item.secondary_text : '',
|
|
220
|
+
types: Array.isArray(item.types) ? item.types.filter((v): v is string => typeof v === 'string') : [],
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
this.#sessionToken = token;
|
|
224
|
+
return {
|
|
225
|
+
success: true,
|
|
226
|
+
data,
|
|
227
|
+
size: typeof body.size === 'number' ? body.size : data.length,
|
|
228
|
+
count: typeof body.count === 'number' ? body.count : data.length,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async placeDetails(placeId: string): Promise<AddressDetails> {
|
|
233
|
+
const params: Record<string, string> = { place_id: placeId };
|
|
234
|
+
if (this.#sessionToken) params.session_token = this.#sessionToken;
|
|
235
|
+
try {
|
|
236
|
+
const body = await this.#request(this.#endpoints.details, { params });
|
|
237
|
+
requireSuccess(body, 'Place details');
|
|
238
|
+
const details = record(body.address, 'place details') as AddressDetails;
|
|
239
|
+
this.#sessionToken = null;
|
|
240
|
+
return details;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
// A current provider failure ends the session. A stale completion
|
|
243
|
+
// must not erase the token installed by a newer generation.
|
|
244
|
+
if (!isStaleLocationRequest(error)) this.#sessionToken = null;
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async geocode(address: string | Record<string, unknown>): Promise<GeocodeResult> {
|
|
250
|
+
const body = await this.#request(this.#endpoints.geocode, { method: 'POST', body: { address } });
|
|
251
|
+
requireSuccess(body, 'Geocode');
|
|
252
|
+
return {
|
|
253
|
+
success: true,
|
|
254
|
+
latitude: requiredNumber(body, 'latitude', 'geocode'),
|
|
255
|
+
longitude: requiredNumber(body, 'longitude', 'geocode'),
|
|
256
|
+
formatted_address: requiredString(body, 'formatted_address', 'geocode'),
|
|
257
|
+
place_id: requiredString(body, 'place_id', 'geocode'),
|
|
258
|
+
address_components: record(body.address_components, 'geocode'),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async reverseGeocode(coords: { lat: number; lng: number }): Promise<ReverseGeocodeResult> {
|
|
263
|
+
const body = await this.#request(this.#endpoints.reverseGeocode, { params: coords });
|
|
264
|
+
requireSuccess(body, 'Reverse geocode');
|
|
265
|
+
return {
|
|
266
|
+
success: true,
|
|
267
|
+
formatted_address: requiredString(body, 'formatted_address', 'reverse geocode'),
|
|
268
|
+
place_id: requiredString(body, 'place_id', 'reverse geocode'),
|
|
269
|
+
address_components: record(body.address_components, 'reverse geocode'),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async timezone(coords: { lat: number; lng: number; timestamp?: number }): Promise<TimezoneResult> {
|
|
274
|
+
const body = await this.#request(this.#endpoints.timezone, { params: coords });
|
|
275
|
+
requireSuccess(body, 'Timezone');
|
|
276
|
+
return {
|
|
277
|
+
success: true,
|
|
278
|
+
timezone_id: requiredString(body, 'timezone_id', 'timezone'),
|
|
279
|
+
timezone_name: requiredString(body, 'timezone_name', 'timezone'),
|
|
280
|
+
raw_offset: requiredNumber(body, 'raw_offset', 'timezone'),
|
|
281
|
+
dst_offset: requiredNumber(body, 'dst_offset', 'timezone'),
|
|
282
|
+
total_offset: requiredNumber(body, 'total_offset', 'timezone'),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// DjangoLookups — direct port of web-mojo's pure module (PORT-WHOLE tier).
|
|
2
|
+
// Parses filter keys like 'role__in' and renders human pill text.
|
|
3
|
+
|
|
4
|
+
export const LOOKUPS: Record<string, string> = {
|
|
5
|
+
exact: 'is',
|
|
6
|
+
in: 'is one of',
|
|
7
|
+
not: 'is not',
|
|
8
|
+
not_in: 'is not one of',
|
|
9
|
+
gt: '>',
|
|
10
|
+
gte: '≥',
|
|
11
|
+
lt: '<',
|
|
12
|
+
lte: '≤',
|
|
13
|
+
contains: 'contains',
|
|
14
|
+
icontains: 'contains',
|
|
15
|
+
startswith: 'starts with',
|
|
16
|
+
istartswith: 'starts with',
|
|
17
|
+
endswith: 'ends with',
|
|
18
|
+
iendswith: 'ends with',
|
|
19
|
+
isnull: 'is empty',
|
|
20
|
+
range: 'between',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function parseFilterKey(key: string): { field: string; lookup: string } {
|
|
24
|
+
const parts = key.split('__');
|
|
25
|
+
const last = parts[parts.length - 1]!;
|
|
26
|
+
if (parts.length > 1 && last in LOOKUPS) {
|
|
27
|
+
return { field: parts.slice(0, -1).join('__'), lookup: last };
|
|
28
|
+
}
|
|
29
|
+
return { field: key, lookup: 'exact' };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function formatFilterDisplay(key: string, value: string, label?: string): string {
|
|
33
|
+
const { field, lookup } = parseFilterKey(key);
|
|
34
|
+
const name = label ?? field.replace(/_/g, ' ');
|
|
35
|
+
if (lookup === 'isnull') return value === 'true' ? `${name} is empty` : `${name} is set`;
|
|
36
|
+
if (lookup === 'in' || lookup === 'not_in') {
|
|
37
|
+
const items = value.split(',').map((s) => `'${s.trim()}'`).join(', ');
|
|
38
|
+
return `${name} ${LOOKUPS[lookup]} ${items}`;
|
|
39
|
+
}
|
|
40
|
+
return `${name} ${LOOKUPS[lookup]} '${value}'`;
|
|
41
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// The client fallback renderer: markdown → REACT ELEMENTS.
|
|
2
|
+
//
|
|
3
|
+
// This path never produces an HTML string and never touches innerHTML, so
|
|
4
|
+
// there is nothing for a sanitizer to do on it — the only way a `<script>` in
|
|
5
|
+
// the source could become a script is if React rendered one, and React only
|
|
6
|
+
// renders elements this file names. Text is escaped by React itself.
|
|
7
|
+
//
|
|
8
|
+
// URLs are the one exception to "React makes this safe": React happily renders
|
|
9
|
+
// `href="javascript:..."`. Both link and image URLs go through the SAME vetting
|
|
10
|
+
// the sanitizer uses; a rejected link degrades to its plain text.
|
|
11
|
+
//
|
|
12
|
+
// Used whenever the server render is unavailable — offline, /api/docit/render
|
|
13
|
+
// missing on an older backend, an auth-gated 401, a body over the server's
|
|
14
|
+
// 400 KB cap, or simply not back yet (it is what MarkdownView paints on the
|
|
15
|
+
// first frame so the layout never jumps).
|
|
16
|
+
import type { ReactNode } from 'react';
|
|
17
|
+
import { parseMarkdown, type MdBlock, type MdInline, type MdListItem } from './markdown-parse';
|
|
18
|
+
import { safeImgUrl, safeLinkUrl } from './markdown-sanitize';
|
|
19
|
+
|
|
20
|
+
function inlineNodes(nodes: MdInline[], keyPrefix: string): ReactNode[] {
|
|
21
|
+
return nodes.map((n, i) => {
|
|
22
|
+
const key = `${keyPrefix}.${i}`;
|
|
23
|
+
switch (n.t) {
|
|
24
|
+
case 'text': return n.v;
|
|
25
|
+
case 'br': return <br key={key} />;
|
|
26
|
+
case 'code': return <code key={key}>{n.v}</code>;
|
|
27
|
+
case 'strong': return <strong key={key}>{inlineNodes(n.c, key)}</strong>;
|
|
28
|
+
case 'em': return <em key={key}>{inlineNodes(n.c, key)}</em>;
|
|
29
|
+
case 'del': return <del key={key}>{inlineNodes(n.c, key)}</del>;
|
|
30
|
+
case 'img': {
|
|
31
|
+
const src = safeImgUrl(n.src);
|
|
32
|
+
// Never render nothing: a rejected image keeps its alt text.
|
|
33
|
+
return src ? <img key={key} src={src} alt={n.alt} /> : <span key={key}>{n.alt}</span>;
|
|
34
|
+
}
|
|
35
|
+
case 'link': {
|
|
36
|
+
const href = safeLinkUrl(n.href);
|
|
37
|
+
const children = inlineNodes(n.c, key);
|
|
38
|
+
if (!href) return <span key={key}>{children}</span>;
|
|
39
|
+
const external = /^https?:/i.test(href);
|
|
40
|
+
return (
|
|
41
|
+
<a key={key} href={href} {...(external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}>
|
|
42
|
+
{children}
|
|
43
|
+
</a>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function listItemNode(item: MdListItem, key: string): ReactNode {
|
|
51
|
+
const body = item.tight
|
|
52
|
+
? item.blocks.map((b, i) => (b.t === 'para' ? inlineNodes(b.c, `${key}.${i}`) : blockNodes([b], `${key}.${i}`)))
|
|
53
|
+
: blockNodes(item.blocks, key);
|
|
54
|
+
return (
|
|
55
|
+
<li key={key}>
|
|
56
|
+
{item.task !== null && (
|
|
57
|
+
// Rendered markdown is never interactive: disabled + readOnly.
|
|
58
|
+
<input type="checkbox" checked={item.task} disabled readOnly />
|
|
59
|
+
)}
|
|
60
|
+
{item.task !== null ? ' ' : null}
|
|
61
|
+
{body}
|
|
62
|
+
</li>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function blockNodes(blocks: MdBlock[], keyPrefix: string): ReactNode[] {
|
|
67
|
+
return blocks.map((b, i) => {
|
|
68
|
+
const key = `${keyPrefix}.${i}`;
|
|
69
|
+
switch (b.t) {
|
|
70
|
+
case 'heading': {
|
|
71
|
+
const Tag = `h${b.level}` as 'h1';
|
|
72
|
+
return <Tag key={key}>{inlineNodes(b.c, key)}</Tag>;
|
|
73
|
+
}
|
|
74
|
+
case 'para': return <p key={key}>{inlineNodes(b.c, key)}</p>;
|
|
75
|
+
case 'hr': return <hr key={key} />;
|
|
76
|
+
case 'code': return <pre key={key}><code>{b.v}</code></pre>;
|
|
77
|
+
case 'quote': return <blockquote key={key}>{blockNodes(b.blocks, key)}</blockquote>;
|
|
78
|
+
case 'list': {
|
|
79
|
+
const items = b.items.map((item, k) => listItemNode(item, `${key}.${k}`));
|
|
80
|
+
return b.ordered
|
|
81
|
+
? <ol key={key} start={b.start !== 1 ? b.start : undefined}>{items}</ol>
|
|
82
|
+
: <ul key={key}>{items}</ul>;
|
|
83
|
+
}
|
|
84
|
+
case 'table':
|
|
85
|
+
return (
|
|
86
|
+
<table key={key}>
|
|
87
|
+
<thead>
|
|
88
|
+
<tr>{b.head.map((cell, c) => <th key={`${key}.h${c}`}>{inlineNodes(cell, `${key}.h${c}`)}</th>)}</tr>
|
|
89
|
+
</thead>
|
|
90
|
+
<tbody>
|
|
91
|
+
{b.rows.map((row, r) => (
|
|
92
|
+
<tr key={`${key}.r${r}`}>
|
|
93
|
+
{row.map((cell, c) => <td key={`${key}.r${r}c${c}`}>{inlineNodes(cell, `${key}.r${r}c${c}`)}</td>)}
|
|
94
|
+
</tr>
|
|
95
|
+
))}
|
|
96
|
+
</tbody>
|
|
97
|
+
</table>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Markdown → React elements. No HTML string is produced anywhere on this path. */
|
|
104
|
+
export function markdownToNodes(source: string): ReactNode[] {
|
|
105
|
+
return blockNodes(parseMarkdown(source), 'b');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The fallback renderer as a component. Renders a fragment — the caller owns
|
|
110
|
+
* the wrapper element (and its `md` class), so swapping in the server HTML
|
|
111
|
+
* later replaces children in place rather than the container.
|
|
112
|
+
*/
|
|
113
|
+
export function MarkdownFallback({ source }: { source: string }) {
|
|
114
|
+
return <>{markdownToNodes(source)}</>;
|
|
115
|
+
}
|