@oxy.so/services 11.2.0 → 11.4.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/lib/commonjs/index.js +28 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/AccountInvitationAcceptance.js +97 -0
- package/lib/commonjs/ui/components/AccountInvitationAcceptance.js.map +1 -0
- package/lib/commonjs/ui/components/DisplayCurrencySelector.js +49 -0
- package/lib/commonjs/ui/components/DisplayCurrencySelector.js.map +1 -0
- package/lib/commonjs/ui/components/organizations/OrganizationClaimReview.js +208 -0
- package/lib/commonjs/ui/components/organizations/OrganizationClaimReview.js.map +1 -0
- package/lib/commonjs/ui/components/organizations/OrganizationClaims.js +77 -0
- package/lib/commonjs/ui/components/organizations/OrganizationClaims.js.map +1 -0
- package/lib/commonjs/ui/components/organizations/OrganizationDirectorySearch.js +218 -0
- package/lib/commonjs/ui/components/organizations/OrganizationDirectorySearch.js.map +1 -0
- package/lib/commonjs/ui/components/organizations/OrganizationSettings.js +147 -0
- package/lib/commonjs/ui/components/organizations/OrganizationSettings.js.map +1 -0
- package/lib/commonjs/ui/components/organizations/OrganizationTypePicker.js +34 -0
- package/lib/commonjs/ui/components/organizations/OrganizationTypePicker.js.map +1 -0
- package/lib/commonjs/ui/hooks/useDisplayCurrency.js +77 -0
- package/lib/commonjs/ui/hooks/useDisplayCurrency.js.map +1 -0
- package/lib/commonjs/ui/hooks/useDisplayPrice.js +59 -0
- package/lib/commonjs/ui/hooks/useDisplayPrice.js.map +1 -0
- package/lib/commonjs/ui/screens/AccountMembersScreen.js +63 -20
- package/lib/commonjs/ui/screens/AccountMembersScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +14 -5
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/CreateAccountScreen.js +148 -115
- package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +13 -1
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PreferencesScreen.js +4 -3
- package/lib/commonjs/ui/screens/PreferencesScreen.js.map +1 -1
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/AccountInvitationAcceptance.js +94 -0
- package/lib/module/ui/components/AccountInvitationAcceptance.js.map +1 -0
- package/lib/module/ui/components/DisplayCurrencySelector.js +44 -0
- package/lib/module/ui/components/DisplayCurrencySelector.js.map +1 -0
- package/lib/module/ui/components/organizations/OrganizationClaimReview.js +205 -0
- package/lib/module/ui/components/organizations/OrganizationClaimReview.js.map +1 -0
- package/lib/module/ui/components/organizations/OrganizationClaims.js +73 -0
- package/lib/module/ui/components/organizations/OrganizationClaims.js.map +1 -0
- package/lib/module/ui/components/organizations/OrganizationDirectorySearch.js +215 -0
- package/lib/module/ui/components/organizations/OrganizationDirectorySearch.js.map +1 -0
- package/lib/module/ui/components/organizations/OrganizationSettings.js +143 -0
- package/lib/module/ui/components/organizations/OrganizationSettings.js.map +1 -0
- package/lib/module/ui/components/organizations/OrganizationTypePicker.js +30 -0
- package/lib/module/ui/components/organizations/OrganizationTypePicker.js.map +1 -0
- package/lib/module/ui/hooks/useDisplayCurrency.js +72 -0
- package/lib/module/ui/hooks/useDisplayCurrency.js.map +1 -0
- package/lib/module/ui/hooks/useDisplayPrice.js +56 -0
- package/lib/module/ui/hooks/useDisplayPrice.js.map +1 -0
- package/lib/module/ui/screens/AccountMembersScreen.js +63 -20
- package/lib/module/ui/screens/AccountMembersScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +14 -5
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/CreateAccountScreen.js +149 -115
- package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/ManageAccountScreen.js +13 -1
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/PreferencesScreen.js +4 -3
- package/lib/module/ui/screens/PreferencesScreen.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AccountInvitationAcceptance.d.ts +5 -0
- package/lib/typescript/commonjs/ui/components/AccountInvitationAcceptance.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/DisplayCurrencySelector.d.ts +3 -0
- package/lib/typescript/commonjs/ui/components/DisplayCurrencySelector.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/organizations/OrganizationClaimReview.d.ts +3 -0
- package/lib/typescript/commonjs/ui/components/organizations/OrganizationClaimReview.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/organizations/OrganizationClaims.d.ts +2 -0
- package/lib/typescript/commonjs/ui/components/organizations/OrganizationClaims.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/organizations/OrganizationDirectorySearch.d.ts +6 -0
- package/lib/typescript/commonjs/ui/components/organizations/OrganizationDirectorySearch.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/organizations/OrganizationSettings.d.ts +5 -0
- package/lib/typescript/commonjs/ui/components/organizations/OrganizationSettings.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/organizations/OrganizationTypePicker.d.ts +7 -0
- package/lib/typescript/commonjs/ui/components/organizations/OrganizationTypePicker.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +10 -3
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/usePersonalPlans.d.ts +4 -3
- package/lib/typescript/commonjs/ui/hooks/queries/usePersonalPlans.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useDisplayCurrency.d.ts +11 -0
- package/lib/typescript/commonjs/ui/hooks/useDisplayCurrency.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useDisplayPrice.d.ts +6 -0
- package/lib/typescript/commonjs/ui/hooks/useDisplayPrice.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/icons/Ionicons.d.ts +1 -1
- package/lib/typescript/commonjs/ui/icons/MaterialCommunityIcons.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/AccountMembersScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/AccountMembersScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/PreferencesScreen.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AccountInvitationAcceptance.d.ts +5 -0
- package/lib/typescript/module/ui/components/AccountInvitationAcceptance.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/DisplayCurrencySelector.d.ts +3 -0
- package/lib/typescript/module/ui/components/DisplayCurrencySelector.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/organizations/OrganizationClaimReview.d.ts +3 -0
- package/lib/typescript/module/ui/components/organizations/OrganizationClaimReview.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/organizations/OrganizationClaims.d.ts +2 -0
- package/lib/typescript/module/ui/components/organizations/OrganizationClaims.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/organizations/OrganizationDirectorySearch.d.ts +6 -0
- package/lib/typescript/module/ui/components/organizations/OrganizationDirectorySearch.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/organizations/OrganizationSettings.d.ts +5 -0
- package/lib/typescript/module/ui/components/organizations/OrganizationSettings.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/organizations/OrganizationTypePicker.d.ts +7 -0
- package/lib/typescript/module/ui/components/organizations/OrganizationTypePicker.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +10 -3
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/usePersonalPlans.d.ts +4 -3
- package/lib/typescript/module/ui/hooks/queries/usePersonalPlans.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useDisplayCurrency.d.ts +11 -0
- package/lib/typescript/module/ui/hooks/useDisplayCurrency.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useDisplayPrice.d.ts +6 -0
- package/lib/typescript/module/ui/hooks/useDisplayPrice.d.ts.map +1 -0
- package/lib/typescript/module/ui/icons/Ionicons.d.ts +1 -1
- package/lib/typescript/module/ui/icons/MaterialCommunityIcons.d.ts +1 -1
- package/lib/typescript/module/ui/screens/AccountMembersScreen.d.ts +1 -1
- package/lib/typescript/module/ui/screens/AccountMembersScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/PreferencesScreen.d.ts.map +1 -1
- package/package.json +9 -8
- package/src/index.ts +5 -0
- package/src/ui/components/AccountInvitationAcceptance.tsx +90 -0
- package/src/ui/components/DisplayCurrencySelector.tsx +38 -0
- package/src/ui/components/__tests__/AccountInvitationAcceptance.test.tsx +61 -0
- package/src/ui/components/organizations/OrganizationClaimReview.tsx +218 -0
- package/src/ui/components/organizations/OrganizationClaims.tsx +67 -0
- package/src/ui/components/organizations/OrganizationDirectorySearch.tsx +228 -0
- package/src/ui/components/organizations/OrganizationSettings.tsx +131 -0
- package/src/ui/components/organizations/OrganizationTypePicker.tsx +31 -0
- package/src/ui/hooks/useDisplayCurrency.ts +64 -0
- package/src/ui/hooks/useDisplayPrice.ts +40 -0
- package/src/ui/screens/AccountMembersScreen.tsx +71 -27
- package/src/ui/screens/AccountSettingsScreen.tsx +22 -4
- package/src/ui/screens/CreateAccountScreen.tsx +177 -124
- package/src/ui/screens/ManageAccountScreen.tsx +18 -0
- package/src/ui/screens/PreferencesScreen.tsx +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePersonalPlans.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/queries/usePersonalPlans.ts"],"names":[],"mappings":"AAIA,iFAAiF;AACjF,wBAAgB,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"usePersonalPlans.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/queries/usePersonalPlans.ts"],"names":[],"mappings":"AAIA,iFAAiF;AACjF,wBAAgB,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;aA0Bk1O,CAAC;;;;;;;;;UAnBl3O;AAED,kFAAkF;AAClF,wBAAgB,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;YAe3C"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type UserPreferences } from '@oxy.so/core';
|
|
2
|
+
export type DisplayCurrency = NonNullable<UserPreferences['displayCurrency']>;
|
|
3
|
+
export declare const DISPLAY_CURRENCIES: readonly DisplayCurrency[];
|
|
4
|
+
/** Account-scoped server preference. A late save can never replace another account. */
|
|
5
|
+
export declare function useDisplayCurrency(): {
|
|
6
|
+
currency: DisplayCurrency;
|
|
7
|
+
selectCurrency: (next: DisplayCurrency) => void;
|
|
8
|
+
isSaving: boolean;
|
|
9
|
+
error: Error | null;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=useDisplayCurrency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDisplayCurrency.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useDisplayCurrency.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAO1E,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAC9E,eAAO,MAAM,kBAAkB,EAAE,SAAS,eAAe,EAA2B,CAAC;AAYrF,uFAAuF;AACvF,wBAAgB,kBAAkB;;2BA6BF,eAAe;;;EAW9C"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Display conversion only. Pricing and checkout request bodies retain their source units. */
|
|
2
|
+
export declare function useDisplayPrice(): {
|
|
3
|
+
currency: import("./useDisplayCurrency.js").DisplayCurrency;
|
|
4
|
+
formatUsd: (minorUnits: number) => string;
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=useDisplayPrice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDisplayPrice.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useDisplayPrice.ts"],"names":[],"mappings":"AAMA,8FAA8F;AAC9F,wBAAgB,eAAe;;4BAkBE,MAAM;EActC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Glyphs are decorative: hidden from assistive technology (see `decorativeIconSet`). */
|
|
2
|
-
declare const Ionicons: import("@expo/vector-icons/build/createIconSet").Icon<"push" | "at" | "code" | "
|
|
2
|
+
declare const Ionicons: import("@expo/vector-icons/build/createIconSet").Icon<"push" | "at" | "code" | "business" | "open" | "add" | "desktop" | "search" | "navigate" | "warning" | "text" | "scale" | "link" | "image" | "key" | "resize" | "ellipse" | "alert" | "radio" | "list" | "infinite" | "alert-circle" | "alert-circle-outline" | "apps" | "archive" | "arrow-down" | "arrow-up" | "briefcase-outline" | "bug" | "calendar" | "calendar-outline" | "camera" | "camera-outline" | "card" | "card-outline" | "cart-outline" | "chevron-down" | "chevron-up" | "close" | "close-circle" | "cloud-outline" | "cloud-upload" | "cloud-upload-outline" | "cog" | "cog-outline" | "cube-outline" | "download" | "download-outline" | "earth" | "ellipse-outline" | "eye" | "eye-off-outline" | "filter-outline" | "flash" | "folder" | "folder-open-outline" | "folder-outline" | "gift" | "gift-outline" | "grid" | "heart" | "heart-outline" | "help-circle" | "help-circle-outline" | "image-outline" | "information" | "information-outline" | "leaf" | "library" | "pencil-outline" | "play" | "pulse" | "receipt-outline" | "rocket" | "school-outline" | "server" | "share-outline" | "shield" | "star" | "sync" | "trophy" | "trophy-outline" | "volume-off" | "wallet" | "wallet-outline" | "watch" | "wifi" | "images" | "settings" | "add-circle-outline" | "arrow-back" | "arrow-forward" | "bulb" | "business-outline" | "cash-outline" | "chatbubble" | "checkmark" | "checkmark-circle" | "chevron-back" | "chevron-forward" | "cloud-offline-outline" | "construct-outline" | "copy-outline" | "document" | "document-outline" | "document-text" | "documents" | "ellipsis-horizontal" | "flame" | "footsteps" | "globe-outline" | "hand-left" | "hardware-chip-outline" | "hourglass-outline" | "images-outline" | "information-circle" | "link-outline" | "location" | "location-outline" | "lock-closed" | "log-out-outline" | "musical-notes" | "people" | "people-outline" | "pricetag-outline" | "qr-code-outline" | "refresh-outline" | "remove" | "repeat-outline" | "search-outline" | "shield-checkmark" | "shield-checkmark-outline" | "swap-horizontal-outline" | "swap-vertical-outline" | "time-outline" | "trash" | "trash-outline" | "videocam", "OxyServicesIonicons">;
|
|
3
3
|
export default Ionicons;
|
|
4
4
|
//# sourceMappingURL=Ionicons.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Glyphs are decorative: hidden from assistive technology (see `decorativeIconSet`). */
|
|
2
|
-
declare const MaterialCommunityIcons: import("@expo/vector-icons/build/createIconSet").Icon<"function" | "devices" | "sort" | "at" | "
|
|
2
|
+
declare const MaterialCommunityIcons: import("@expo/vector-icons/build/createIconSet").Icon<"function" | "devices" | "sort" | "at" | "security" | "account" | "message" | "network" | "email" | "bio" | "phone" | "web" | "read" | "android" | "logout" | "store" | "label" | "pan" | "text" | "solid" | "scale" | "link" | "loading" | "image" | "key" | "resize" | "ellipse" | "switch" | "opacity" | "wrap" | "alert" | "radio" | "contain" | "account-circle" | "account-cog-outline" | "account-eye-outline" | "account-group" | "account-multiple" | "account-multiple-outline" | "account-outline" | "account-plus" | "account-search" | "account-switch" | "alert-circle" | "alert-circle-outline" | "apps" | "archive" | "archive-arrow-down" | "arrow-down" | "arrow-left" | "arrow-up" | "bell" | "bell-outline" | "briefcase-outline" | "bug" | "bullhorn" | "cake-variant" | "cake-variant-outline" | "calendar" | "calendar-outline" | "camera" | "camera-outline" | "cancel" | "card" | "card-outline" | "cart-outline" | "cellphone" | "cellphone-basic" | "cellphone-message" | "cellphone-screenshot" | "chart-line" | "check-all" | "check-circle" | "chevron-down" | "chevron-up" | "chip" | "circle-outline" | "clock" | "clock-outline" | "close" | "close-circle" | "cloud-outline" | "cloud-upload" | "cloud-upload-outline" | "code-tags" | "cog" | "cog-outline" | "content-copy" | "controller" | "cookie" | "credit-card" | "cube-outline" | "database-outline" | "delete" | "delete-outline" | "dots-horizontal" | "download" | "download-outline" | "earth" | "ellipse-outline" | "email-outline" | "eye" | "eye-off-outline" | "file" | "file-document" | "file-document-edit-outline" | "file-document-outline" | "file-excel-outline" | "file-outline" | "file-pdf-box" | "filter-outline" | "flash" | "folder" | "folder-image" | "folder-open-outline" | "folder-outline" | "folder-zip-outline" | "ghost" | "gift" | "gift-outline" | "grid" | "heart" | "heart-outline" | "help-circle" | "help-circle-outline" | "history" | "image-multiple-outline" | "image-outline" | "information" | "information-outline" | "leaf" | "library" | "link-variant" | "lock-outline" | "magnify" | "map-marker" | "map-marker-multiple" | "map-marker-outline" | "message-outline" | "minus-circle" | "motion" | "music-note-outline" | "null" | "numeric" | "open-in-app" | "openid" | "palette" | "palette-outline" | "pencil-outline" | "phone-outline" | "play" | "plus" | "plus-circle" | "post" | "presentation" | "pulse" | "react" | "receipt-outline" | "rocket" | "school-outline" | "server" | "share-outline" | "shield" | "shield-account-outline" | "shield-check" | "shield-search" | "signature" | "sort-alphabetical-variant" | "sort-numeric-variant" | "star" | "sync" | "tag" | "tag-outline" | "text-box-outline" | "theme-light-dark" | "transfer" | "translate" | "trash-can-outline" | "tray-arrow-down" | "trophy" | "trophy-outline" | "update" | "video-outline" | "volume-off" | "wallet" | "wallet-outline" | "watch" | "wifi" | "wifi-off" | "wrench" | "xml", "OxyServicesMaterialCommunityIcons">;
|
|
3
3
|
export default MaterialCommunityIcons;
|
|
4
4
|
//# sourceMappingURL=MaterialCommunityIcons.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import type { BaseScreenProps } from '../types/navigation.js';
|
|
3
3
|
/**
|
|
4
|
-
* Manage the members of an account (invite by
|
|
4
|
+
* Manage the members of an account (invite by email, change roles,
|
|
5
5
|
* remove, transfer ownership). Gated on the caller's `callerMembership`
|
|
6
6
|
* permissions. Receives the target `accountId` via navigation props.
|
|
7
7
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountMembersScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/AccountMembersScreen.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAc/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"AccountMembersScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/AccountMembersScreen.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAc/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAa3D;;;;GAIG;AACH,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAufnD,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountSettingsScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/AccountSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"AccountSettingsScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/AccountSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAoB/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAa3D;;;;;;GAMG;AACH,QAAA,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAiRpD,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateAccountScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/CreateAccountScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"CreateAccountScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/CreateAccountScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA6B/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAsF3D;;;;;GAKG;AACH,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA+clD,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ManageAccountScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/ManageAccountScreen.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ManageAccountScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/ManageAccountScreen.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAiB/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AA+C3D;;;;;;;GAOG;AACH,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA8nBlD,CAAC;AAYF,eAAe,mBAAmB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PreferencesScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/PreferencesScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"PreferencesScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/PreferencesScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoD,MAAM,OAAO,CAAC;AAQzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;;AAyL3D,wBAA6C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxy.so/services",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.4.0",
|
|
4
4
|
"description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -155,18 +155,19 @@
|
|
|
155
155
|
},
|
|
156
156
|
"dependencies": {
|
|
157
157
|
"@expo/config-plugins": "57.0.9",
|
|
158
|
-
"@oxy.so/contracts": "4.
|
|
159
|
-
"color": "^
|
|
158
|
+
"@oxy.so/contracts": "4.15.0",
|
|
159
|
+
"color": "^5.0.3",
|
|
160
|
+
"@peable.to/sdk": "0.2.7-oxy-one.1"
|
|
160
161
|
},
|
|
161
162
|
"devDependencies": {
|
|
162
163
|
"@commitlint/cli": "^21.2.1",
|
|
163
164
|
"@commitlint/config-conventional": "^21.2.0",
|
|
164
165
|
"@expo/vector-icons": "^15.0.3",
|
|
165
|
-
"@oxy.so/core": "4.
|
|
166
|
+
"@oxy.so/core": "4.9.0",
|
|
166
167
|
"@react-native-async-storage/async-storage": "^2.0.0",
|
|
167
168
|
"@react-native-community/netinfo": "^12.0.1",
|
|
168
169
|
"@react-native/babel-preset": "^0.86.2",
|
|
169
|
-
"@release-it/conventional-changelog": "^
|
|
170
|
+
"@release-it/conventional-changelog": "^12.0.2",
|
|
170
171
|
"@tanstack/query-async-storage-persister": "^5.101",
|
|
171
172
|
"@tanstack/query-sync-storage-persister": "^5.101",
|
|
172
173
|
"@tanstack/react-query": "^5.101",
|
|
@@ -177,7 +178,7 @@
|
|
|
177
178
|
"@types/jest": "^30.0.0",
|
|
178
179
|
"@types/node": "^20.19.43",
|
|
179
180
|
"@types/react": "*",
|
|
180
|
-
"@oxy.so/bloom": "
|
|
181
|
+
"@oxy.so/bloom": "^10.4.0",
|
|
181
182
|
"copyfiles": "^2.4.1",
|
|
182
183
|
"expo-clipboard": "~57.0.2",
|
|
183
184
|
"expo-constants": "~57.0.5",
|
|
@@ -188,7 +189,7 @@
|
|
|
188
189
|
"expo-linear-gradient": "~57.0.1",
|
|
189
190
|
"expo-notifications": "~57.0.5",
|
|
190
191
|
"expo-modules-core": "~57.0.17",
|
|
191
|
-
"expo-secure-store": "~57.0.
|
|
192
|
+
"expo-secure-store": "~57.0.4",
|
|
192
193
|
"expo-web-browser": "~57.0.1",
|
|
193
194
|
"husky": "^4.3.8",
|
|
194
195
|
"jest": "^30.5.1",
|
|
@@ -215,7 +216,7 @@
|
|
|
215
216
|
"peerDependencies": {
|
|
216
217
|
"@expo/vector-icons": "^15.0.3",
|
|
217
218
|
"@oxy.so/bloom": ">=6.2.1 <6.3.0 || >=7.1.2 <7.2.0 || >=7.7.0 <7.8.0 || >=7.13.1 <7.34.0 || >=8.0.0 <8.2.0 || >=9.0.0 <9.1.0 || >=10.0.0",
|
|
218
|
-
"@oxy.so/core": "4.
|
|
219
|
+
"@oxy.so/core": "4.9.0",
|
|
219
220
|
"@react-native-async-storage/async-storage": "^2.0.0",
|
|
220
221
|
"@react-native-community/netinfo": ">=11.4.1 <13.0.0",
|
|
221
222
|
"@tanstack/query-async-storage-persister": "^5.101",
|
package/src/index.ts
CHANGED
|
@@ -453,3 +453,8 @@ export {
|
|
|
453
453
|
usePersonalPlans,
|
|
454
454
|
usePersonalPlanSubscriptions,
|
|
455
455
|
} from './ui/hooks/queries/usePersonalPlans';
|
|
456
|
+
export { AccountInvitationAcceptance } from './ui/components/AccountInvitationAcceptance';
|
|
457
|
+
|
|
458
|
+
export { DisplayCurrencySelector } from './ui/components/DisplayCurrencySelector';
|
|
459
|
+
export { useDisplayCurrency } from './ui/hooks/useDisplayCurrency';
|
|
460
|
+
export { useDisplayPrice } from './ui/hooks/useDisplayPrice';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { Text } from '@oxy.so/bloom/typography';
|
|
4
|
+
import { Button } from '@oxy.so/bloom/button';
|
|
5
|
+
import type { AccountInvitationPreview } from '@oxy.so/core';
|
|
6
|
+
import { getNormalizedUserHandle } from '@oxy.so/core';
|
|
7
|
+
import { useOxy } from '../context/OxyContext';
|
|
8
|
+
|
|
9
|
+
/** The link never grants access on navigation, preview, sign-in or account switching. */
|
|
10
|
+
export function AccountInvitationAcceptance({ token }: { token: string }) {
|
|
11
|
+
return <InvitationContent key={token} token={token} />;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function InvitationContent({ token }: { token: string }) {
|
|
15
|
+
const { oxyServices, user, canUsePrivateApi, openAccountDialog } = useOxy();
|
|
16
|
+
const [preview, setPreview] = useState<AccountInvitationPreview | null>(null);
|
|
17
|
+
const [error, setError] = useState('');
|
|
18
|
+
const [busy, setBusy] = useState(false);
|
|
19
|
+
const [accepted, setAccepted] = useState(false);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
let alive = true;
|
|
22
|
+
setPreview(null);
|
|
23
|
+
setError('');
|
|
24
|
+
if (canUsePrivateApi && token) {
|
|
25
|
+
oxyServices.accounts.invitations
|
|
26
|
+
.preview(token)
|
|
27
|
+
.then((value) => {
|
|
28
|
+
if (alive) setPreview(value);
|
|
29
|
+
})
|
|
30
|
+
.catch((reason) => {
|
|
31
|
+
if (alive) setError(reason instanceof Error ? reason.message : 'Invitation unavailable');
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return () => {
|
|
35
|
+
alive = false;
|
|
36
|
+
};
|
|
37
|
+
}, [oxyServices, canUsePrivateApi, token]);
|
|
38
|
+
|
|
39
|
+
const accept = async () => {
|
|
40
|
+
if (!user || !preview) return;
|
|
41
|
+
setBusy(true);
|
|
42
|
+
setError('');
|
|
43
|
+
try {
|
|
44
|
+
await oxyServices.accounts.invitations.accept(token, user.id);
|
|
45
|
+
setAccepted(true);
|
|
46
|
+
} catch (reason) {
|
|
47
|
+
setError(reason instanceof Error ? reason.message : 'Could not accept invitation');
|
|
48
|
+
} finally {
|
|
49
|
+
setBusy(false);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
return (
|
|
53
|
+
<View className="gap-space-16 p-space-24">
|
|
54
|
+
<Text className="text-title font-semibold text-text">Account invitation</Text>
|
|
55
|
+
{canUsePrivateApi && token && !preview && !error ? (
|
|
56
|
+
<Text accessibilityRole="progressbar">Loading invitation…</Text>
|
|
57
|
+
) : null}
|
|
58
|
+
{accepted ? (
|
|
59
|
+
<Text>Invitation accepted. You can now access this account.</Text>
|
|
60
|
+
) : (
|
|
61
|
+
<>
|
|
62
|
+
{preview ? (
|
|
63
|
+
<Text>
|
|
64
|
+
Join {preview.account.displayName ?? preview.account.username} as{' '}
|
|
65
|
+
{preview.invitation.role}.
|
|
66
|
+
</Text>
|
|
67
|
+
) : null}
|
|
68
|
+
<Text>
|
|
69
|
+
Selected account:{' '}
|
|
70
|
+
{user
|
|
71
|
+
? `${user.name?.displayName ?? getNormalizedUserHandle(user) ?? user.id} (${user.email ?? user.id})`
|
|
72
|
+
: 'Not signed in'}
|
|
73
|
+
</Text>
|
|
74
|
+
<Text>
|
|
75
|
+
The selected account receives access. Its email can differ from the invitation address.
|
|
76
|
+
No access is granted until you confirm.
|
|
77
|
+
</Text>
|
|
78
|
+
<Button disabled={busy} onPress={() => openAccountDialog()}>
|
|
79
|
+
{user ? 'Change account' : 'Sign in or create account'}
|
|
80
|
+
</Button>
|
|
81
|
+
<Button disabled={busy || !user || !preview || user.kind !== 'personal'} onPress={accept}>
|
|
82
|
+
Confirm and accept
|
|
83
|
+
</Button>
|
|
84
|
+
</>
|
|
85
|
+
)}
|
|
86
|
+
{error ? <Text accessibilityRole="alert">{error}</Text> : null}
|
|
87
|
+
{!token ? <Text>This invitation link is incomplete.</Text> : null}
|
|
88
|
+
</View>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
SegmentedControl,
|
|
5
|
+
SegmentedControlItem,
|
|
6
|
+
SegmentedControlItemText,
|
|
7
|
+
} from '@oxy.so/bloom/segmented-control';
|
|
8
|
+
import { Text } from '@oxy.so/bloom/typography';
|
|
9
|
+
import { DISPLAY_CURRENCIES, useDisplayCurrency } from '../hooks/useDisplayCurrency';
|
|
10
|
+
import { useI18n } from '../hooks/useI18n';
|
|
11
|
+
|
|
12
|
+
export function DisplayCurrencySelector() {
|
|
13
|
+
const { currency, selectCurrency, isSaving, error } = useDisplayCurrency();
|
|
14
|
+
const { t } = useI18n();
|
|
15
|
+
return (
|
|
16
|
+
<View>
|
|
17
|
+
<Text>{t('preferences.displayCurrency.label')}</Text>
|
|
18
|
+
<SegmentedControl
|
|
19
|
+
type="radio"
|
|
20
|
+
label={t('preferences.displayCurrency.label')}
|
|
21
|
+
value={currency}
|
|
22
|
+
onValueChange={(value) => {
|
|
23
|
+
const selected = DISPLAY_CURRENCIES.find((item) => item === value);
|
|
24
|
+
if (selected && !isSaving) selectCurrency(selected);
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
{DISPLAY_CURRENCIES.map((value) => (
|
|
28
|
+
<SegmentedControlItem key={value} value={value} disabled={isSaving}>
|
|
29
|
+
<SegmentedControlItemText>{value}</SegmentedControlItemText>
|
|
30
|
+
</SegmentedControlItem>
|
|
31
|
+
))}
|
|
32
|
+
</SegmentedControl>
|
|
33
|
+
{error && (
|
|
34
|
+
<Text accessibilityLiveRegion="polite">{t('preferences.displayCurrency.saveFailed')}</Text>
|
|
35
|
+
)}
|
|
36
|
+
</View>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { AccountInvitationAcceptance } from '../AccountInvitationAcceptance';
|
|
4
|
+
const mockPreview = jest.fn();
|
|
5
|
+
const mockAccept = jest.fn();
|
|
6
|
+
const mockSwitch = jest.fn();
|
|
7
|
+
let mockUser: { id: string; kind: string; email: string; username: string } | null = null;
|
|
8
|
+
const mockServices = { accounts: { invitations: { preview: mockPreview, accept: mockAccept } } };
|
|
9
|
+
jest.mock('../../context/OxyContext', () => ({
|
|
10
|
+
useOxy: () => ({
|
|
11
|
+
oxyServices: mockServices,
|
|
12
|
+
user: mockUser,
|
|
13
|
+
canUsePrivateApi: !!mockUser,
|
|
14
|
+
openAccountDialog: mockSwitch,
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
mockUser = null;
|
|
20
|
+
mockPreview.mockResolvedValue({
|
|
21
|
+
invitation: { role: 'editor' },
|
|
22
|
+
account: { displayName: 'Community' },
|
|
23
|
+
});
|
|
24
|
+
mockAccept.mockResolvedValue({ success: true });
|
|
25
|
+
});
|
|
26
|
+
test('sign-in and switching never accept; confirmation names the displayed selected account', async () => {
|
|
27
|
+
const { rerender } = render(<AccountInvitationAcceptance token="secret" />);
|
|
28
|
+
fireEvent.click(screen.getByText('Sign in or create account'));
|
|
29
|
+
expect(mockSwitch).toHaveBeenCalled();
|
|
30
|
+
expect(mockAccept).not.toHaveBeenCalled();
|
|
31
|
+
mockUser = { id: 'first', kind: 'personal', username: 'first', email: 'different@example.com' };
|
|
32
|
+
rerender(<AccountInvitationAcceptance token="secret" />);
|
|
33
|
+
await screen.findByText('Join Community as editor.');
|
|
34
|
+
fireEvent.click(screen.getByText('Change account'));
|
|
35
|
+
expect(mockAccept).not.toHaveBeenCalled();
|
|
36
|
+
mockUser = { id: 'second', kind: 'personal', username: 'second', email: 'second@example.com' };
|
|
37
|
+
rerender(<AccountInvitationAcceptance token="secret" />);
|
|
38
|
+
expect(screen.getByText(/Selected account: second/)).toBeTruthy();
|
|
39
|
+
fireEvent.click(screen.getByText('Confirm and accept'));
|
|
40
|
+
await waitFor(() => expect(mockAccept).toHaveBeenCalledWith('secret', 'second'));
|
|
41
|
+
await screen.findByText('Invitation accepted. You can now access this account.');
|
|
42
|
+
});
|
|
43
|
+
test('a new link resets previous acceptance and does not auto-accept', async () => {
|
|
44
|
+
mockUser = { id: 'first', kind: 'personal', username: 'first', email: 'first@example.com' };
|
|
45
|
+
const { rerender } = render(<AccountInvitationAcceptance token="old" />);
|
|
46
|
+
await screen.findByText('Join Community as editor.');
|
|
47
|
+
fireEvent.click(screen.getByText('Confirm and accept'));
|
|
48
|
+
await screen.findByText('Invitation accepted. You can now access this account.');
|
|
49
|
+
rerender(<AccountInvitationAcceptance token="new" />);
|
|
50
|
+
await screen.findByText('Join Community as editor.');
|
|
51
|
+
expect(screen.queryByText('Invitation accepted. You can now access this account.')).toBeNull();
|
|
52
|
+
expect(mockAccept).toHaveBeenCalledTimes(1);
|
|
53
|
+
});
|
|
54
|
+
test('preview failure leaves confirmation disabled', async () => {
|
|
55
|
+
mockUser = { id: 'first', kind: 'personal', username: 'first', email: 'first@example.com' };
|
|
56
|
+
mockPreview.mockRejectedValue(new Error('Invitation expired'));
|
|
57
|
+
render(<AccountInvitationAcceptance token="expired" />);
|
|
58
|
+
await screen.findByText('Invitation expired');
|
|
59
|
+
fireEvent.click(screen.getByText('Confirm and accept'));
|
|
60
|
+
expect(mockAccept).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { ActivityIndicator, View } from 'react-native';
|
|
3
|
+
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
|
+
import { isHttpRequestError } from '@oxy.so/core';
|
|
5
|
+
import {
|
|
6
|
+
organizationClaimReviewSchema,
|
|
7
|
+
type OrganizationClaimReview as OrganizationClaimReviewInput,
|
|
8
|
+
} from '@oxy.so/contracts';
|
|
9
|
+
import { Button } from '@oxy.so/bloom/button';
|
|
10
|
+
import { SettingsListGroup, SettingsListItem } from '@oxy.so/bloom/settings-list';
|
|
11
|
+
import { TextField, TextFieldInput } from '@oxy.so/bloom/text-field';
|
|
12
|
+
import { Text } from '@oxy.so/bloom/typography';
|
|
13
|
+
import { useOxy } from '../../context/OxyContext';
|
|
14
|
+
import { useI18n } from '../../hooks/useI18n';
|
|
15
|
+
|
|
16
|
+
/** The queue is requested only after an explicit action; the server rechecks staff authority. */
|
|
17
|
+
export function OrganizationClaimReview() {
|
|
18
|
+
const { t } = useI18n();
|
|
19
|
+
const { user, canUsePrivateApi } = useOxy();
|
|
20
|
+
const [open, setOpen] = useState(false);
|
|
21
|
+
return (
|
|
22
|
+
<SettingsListGroup>
|
|
23
|
+
<SettingsListItem
|
|
24
|
+
title={t('organizationDirectory.review.title')}
|
|
25
|
+
disabled={!canUsePrivateApi}
|
|
26
|
+
onPress={() => setOpen((value) => !value)}
|
|
27
|
+
/>
|
|
28
|
+
{open && user?.kind === 'personal' && canUsePrivateApi ? <ReviewQueue key={user.id} /> : null}
|
|
29
|
+
</SettingsListGroup>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
function ReviewQueue() {
|
|
33
|
+
const { t } = useI18n();
|
|
34
|
+
const { oxyServices, user, canUsePrivateApi } = useOxy();
|
|
35
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
36
|
+
const requests = useInfiniteQuery({
|
|
37
|
+
queryKey: ['oxy', 'organizations', 'review', user?.id, canUsePrivateApi],
|
|
38
|
+
queryFn: ({ pageParam }) =>
|
|
39
|
+
oxyServices.organizations.reviewClaims({
|
|
40
|
+
status: 'pending',
|
|
41
|
+
limit: 20,
|
|
42
|
+
...(pageParam ? { cursor: pageParam } : {}),
|
|
43
|
+
}),
|
|
44
|
+
initialPageParam: undefined as string | undefined,
|
|
45
|
+
getNextPageParam: (page) => page.nextCursor ?? undefined,
|
|
46
|
+
enabled: canUsePrivateApi,
|
|
47
|
+
retry: false,
|
|
48
|
+
gcTime: 0,
|
|
49
|
+
});
|
|
50
|
+
const claims = requests.data?.pages.flatMap((page) => page.items) ?? [];
|
|
51
|
+
const ids = [...new Set(claims.map((claim) => claim.accountId))].sort();
|
|
52
|
+
const organizations = useQuery({
|
|
53
|
+
queryKey: ['oxy', 'organizations', 'review-organizations', ids],
|
|
54
|
+
queryFn: async () => {
|
|
55
|
+
const batches = [];
|
|
56
|
+
for (let offset = 0; offset < ids.length; offset += 100)
|
|
57
|
+
batches.push(oxyServices.organizations.batch({ ids: ids.slice(offset, offset + 100) }));
|
|
58
|
+
return (await Promise.all(batches)).flatMap((batch) => batch.items);
|
|
59
|
+
},
|
|
60
|
+
enabled: canUsePrivateApi && requests.isSuccess && ids.length > 0,
|
|
61
|
+
retry: false,
|
|
62
|
+
});
|
|
63
|
+
if (requests.isPending) return <ActivityIndicator />;
|
|
64
|
+
if (requests.isError)
|
|
65
|
+
return (
|
|
66
|
+
<View className="gap-space-16 p-space-16">
|
|
67
|
+
<Text accessibilityRole="alert">
|
|
68
|
+
{t(
|
|
69
|
+
isHttpRequestError(requests.error) && requests.error.status === 403
|
|
70
|
+
? 'organizationDirectory.review.forbidden'
|
|
71
|
+
: 'organizationDirectory.requestError',
|
|
72
|
+
)}
|
|
73
|
+
</Text>
|
|
74
|
+
<Button onPress={() => requests.refetch()}>{t('organizationDirectory.retry')}</Button>
|
|
75
|
+
</View>
|
|
76
|
+
);
|
|
77
|
+
if (selected)
|
|
78
|
+
return <ReviewDetail claimId={selected} key={selected} onClose={() => setSelected(null)} />;
|
|
79
|
+
return (
|
|
80
|
+
<View className="gap-space-16 p-space-16">
|
|
81
|
+
{claims.length === 0 ? <Text>{t('organizationDirectory.review.empty')}</Text> : null}
|
|
82
|
+
{claims.map((claim) => (
|
|
83
|
+
<SettingsListItem
|
|
84
|
+
key={claim.id}
|
|
85
|
+
title={
|
|
86
|
+
organizations.data?.find((item) => item.requestedAccountId === claim.accountId)
|
|
87
|
+
?.organization.name ?? t('organizationDirectory.view')
|
|
88
|
+
}
|
|
89
|
+
description={t('organizationDirectory.pending')}
|
|
90
|
+
onPress={() => setSelected(claim.id)}
|
|
91
|
+
/>
|
|
92
|
+
))}
|
|
93
|
+
{requests.hasNextPage ? (
|
|
94
|
+
<Button onPress={() => requests.fetchNextPage()} disabled={requests.isFetchingNextPage}>
|
|
95
|
+
{t('organizationDirectory.loadMore')}
|
|
96
|
+
</Button>
|
|
97
|
+
) : null}
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
function ReviewDetail({ claimId, onClose }: { claimId: string; onClose: () => void }) {
|
|
102
|
+
const { t } = useI18n();
|
|
103
|
+
const { oxyServices, user, canUsePrivateApi } = useOxy();
|
|
104
|
+
const queryClient = useQueryClient();
|
|
105
|
+
const [evidence, setEvidence] = useState('');
|
|
106
|
+
const [authorizationRevoked, setAuthorizationRevoked] = useState(false);
|
|
107
|
+
const detail = useQuery({
|
|
108
|
+
queryKey: ['oxy', 'organizations', 'review-detail', user?.id, canUsePrivateApi, claimId],
|
|
109
|
+
queryFn: () => oxyServices.organizations.reviewClaimDetail(claimId),
|
|
110
|
+
enabled: canUsePrivateApi,
|
|
111
|
+
retry: false,
|
|
112
|
+
gcTime: 0,
|
|
113
|
+
});
|
|
114
|
+
const review = useMutation({
|
|
115
|
+
mutationFn: (decision: OrganizationClaimReviewInput['decision']) => {
|
|
116
|
+
if (
|
|
117
|
+
!canUsePrivateApi ||
|
|
118
|
+
authorizationRevoked ||
|
|
119
|
+
!detail.isSuccess ||
|
|
120
|
+
!detail.data ||
|
|
121
|
+
detail.data.claimantUserId === user?.id
|
|
122
|
+
)
|
|
123
|
+
throw new Error('Independent staff review is required');
|
|
124
|
+
return oxyServices.organizations.reviewClaim(
|
|
125
|
+
claimId,
|
|
126
|
+
organizationClaimReviewSchema.parse({ decision, evidenceReference: evidence }),
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
onError: (error) => {
|
|
130
|
+
if (isHttpRequestError(error) && error.status === 403) setAuthorizationRevoked(true);
|
|
131
|
+
},
|
|
132
|
+
onSuccess: async () => {
|
|
133
|
+
await queryClient.invalidateQueries({ queryKey: ['oxy', 'organizations'] });
|
|
134
|
+
await queryClient.invalidateQueries({ queryKey: ['accounts'] });
|
|
135
|
+
onClose();
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
const conflict = isHttpRequestError(review.error) && review.error.status === 409;
|
|
139
|
+
const forbidden = isHttpRequestError(review.error) && review.error.status === 403;
|
|
140
|
+
const selfReview = detail.data?.claimantUserId === user?.id;
|
|
141
|
+
return (
|
|
142
|
+
<View className="gap-space-16 p-space-16">
|
|
143
|
+
{detail.isPending ? <ActivityIndicator /> : null}
|
|
144
|
+
{detail.isError ? (
|
|
145
|
+
<>
|
|
146
|
+
<Text accessibilityRole="alert">
|
|
147
|
+
{t(
|
|
148
|
+
isHttpRequestError(detail.error) && detail.error.status === 403
|
|
149
|
+
? 'organizationDirectory.review.forbidden'
|
|
150
|
+
: 'organizationDirectory.requestError',
|
|
151
|
+
)}
|
|
152
|
+
</Text>
|
|
153
|
+
<Button onPress={() => detail.refetch()}>{t('organizationDirectory.retry')}</Button>
|
|
154
|
+
</>
|
|
155
|
+
) : null}
|
|
156
|
+
{authorizationRevoked ? (
|
|
157
|
+
<Text accessibilityRole="alert">{t('organizationDirectory.review.forbidden')}</Text>
|
|
158
|
+
) : null}
|
|
159
|
+
{detail.isSuccess && canUsePrivateApi && !authorizationRevoked ? (
|
|
160
|
+
<>
|
|
161
|
+
<Text>{t('organizationDirectory.evidence')}</Text>
|
|
162
|
+
<Text selectable>{detail.data.evidenceReference}</Text>
|
|
163
|
+
<Text>{t('organizationDirectory.review.hint')}</Text>
|
|
164
|
+
{selfReview ? <Text>{t('organizationDirectory.review.independent')}</Text> : null}
|
|
165
|
+
<TextField>
|
|
166
|
+
<TextFieldInput
|
|
167
|
+
label={t('organizationDirectory.review.evidence')}
|
|
168
|
+
value={evidence}
|
|
169
|
+
onChangeText={setEvidence}
|
|
170
|
+
maxLength={1000}
|
|
171
|
+
/>
|
|
172
|
+
</TextField>
|
|
173
|
+
{review.isError ? (
|
|
174
|
+
<Text accessibilityRole="alert">
|
|
175
|
+
{t(
|
|
176
|
+
conflict
|
|
177
|
+
? 'organizationDirectory.review.conflict'
|
|
178
|
+
: forbidden
|
|
179
|
+
? 'organizationDirectory.review.forbidden'
|
|
180
|
+
: 'organizationDirectory.review.failed',
|
|
181
|
+
)}
|
|
182
|
+
</Text>
|
|
183
|
+
) : null}
|
|
184
|
+
{(['approved', 'rejected'] as const).map((decision) => (
|
|
185
|
+
<Button
|
|
186
|
+
key={decision}
|
|
187
|
+
disabled={
|
|
188
|
+
!canUsePrivateApi ||
|
|
189
|
+
selfReview ||
|
|
190
|
+
detail.data.status !== 'pending' ||
|
|
191
|
+
!evidence.trim() ||
|
|
192
|
+
review.isPending ||
|
|
193
|
+
conflict ||
|
|
194
|
+
forbidden
|
|
195
|
+
}
|
|
196
|
+
onPress={() => review.mutate(decision)}
|
|
197
|
+
>
|
|
198
|
+
{t(`organizationDirectory.review.${decision}`)}
|
|
199
|
+
</Button>
|
|
200
|
+
))}
|
|
201
|
+
{conflict ? (
|
|
202
|
+
<Button
|
|
203
|
+
onPress={async () => {
|
|
204
|
+
await queryClient.invalidateQueries({ queryKey: ['oxy', 'organizations'] });
|
|
205
|
+
onClose();
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
{t('organizationDirectory.retry')}
|
|
209
|
+
</Button>
|
|
210
|
+
) : null}
|
|
211
|
+
</>
|
|
212
|
+
) : null}
|
|
213
|
+
<Button appearance="outline" onPress={onClose} disabled={review.isPending}>
|
|
214
|
+
{t('organizationDirectory.back')}
|
|
215
|
+
</Button>
|
|
216
|
+
</View>
|
|
217
|
+
);
|
|
218
|
+
}
|