@oxy.so/services 12.7.0 → 12.9.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.
Files changed (100) hide show
  1. package/lib/commonjs/index.js +7 -0
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/client.js +19 -0
  4. package/lib/commonjs/ui/client.js.map +1 -1
  5. package/lib/commonjs/ui/components/FollowButton.js +8 -12
  6. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  7. package/lib/commonjs/ui/components/FollowTargetButton.js +1 -2
  8. package/lib/commonjs/ui/components/FollowTargetButton.js.map +1 -1
  9. package/lib/commonjs/ui/components/followLabels.js +34 -0
  10. package/lib/commonjs/ui/components/followLabels.js.map +1 -1
  11. package/lib/commonjs/ui/hooks/useAccountColor.js +75 -0
  12. package/lib/commonjs/ui/hooks/useAccountColor.js.map +1 -1
  13. package/lib/commonjs/ui/hooks/useFollow.js +5 -4
  14. package/lib/commonjs/ui/hooks/useFollow.js.map +1 -1
  15. package/lib/commonjs/ui/hooks/useFollowTarget.js +8 -8
  16. package/lib/commonjs/ui/hooks/useFollowTarget.js.map +1 -1
  17. package/lib/commonjs/ui/stores/followStore.js +10 -9
  18. package/lib/commonjs/ui/stores/followStore.js.map +1 -1
  19. package/lib/commonjs/ui/stores/followTargetStore.js.map +1 -1
  20. package/lib/commonjs/ui/utils/followErrors.js +45 -0
  21. package/lib/commonjs/ui/utils/followErrors.js.map +1 -0
  22. package/lib/module/index.js +1 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/ui/client.js +2 -1
  25. package/lib/module/ui/client.js.map +1 -1
  26. package/lib/module/ui/components/FollowButton.js +9 -13
  27. package/lib/module/ui/components/FollowButton.js.map +1 -1
  28. package/lib/module/ui/components/FollowTargetButton.js +2 -3
  29. package/lib/module/ui/components/FollowTargetButton.js.map +1 -1
  30. package/lib/module/ui/components/followLabels.js +33 -1
  31. package/lib/module/ui/components/followLabels.js.map +1 -1
  32. package/lib/module/ui/hooks/useAccountColor.js +74 -1
  33. package/lib/module/ui/hooks/useAccountColor.js.map +1 -1
  34. package/lib/module/ui/hooks/useFollow.js +5 -4
  35. package/lib/module/ui/hooks/useFollow.js.map +1 -1
  36. package/lib/module/ui/hooks/useFollowTarget.js +8 -8
  37. package/lib/module/ui/hooks/useFollowTarget.js.map +1 -1
  38. package/lib/module/ui/stores/followStore.js +10 -9
  39. package/lib/module/ui/stores/followStore.js.map +1 -1
  40. package/lib/module/ui/stores/followTargetStore.js.map +1 -1
  41. package/lib/module/ui/utils/followErrors.js +41 -0
  42. package/lib/module/ui/utils/followErrors.js.map +1 -0
  43. package/lib/typescript/commonjs/index.d.ts +2 -0
  44. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/ui/client.d.ts +3 -1
  46. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/ui/components/FollowButton.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/ui/components/FollowTargetButton.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/ui/components/followLabels.d.ts +13 -0
  50. package/lib/typescript/commonjs/ui/components/followLabels.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/ui/hooks/useAccountColor.d.ts +23 -0
  52. package/lib/typescript/commonjs/ui/hooks/useAccountColor.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/ui/hooks/useFollow.d.ts +5 -4
  54. package/lib/typescript/commonjs/ui/hooks/useFollow.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/ui/hooks/useFollow.types.d.ts +3 -2
  56. package/lib/typescript/commonjs/ui/hooks/useFollow.types.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/ui/hooks/useFollowTarget.d.ts +4 -2
  58. package/lib/typescript/commonjs/ui/hooks/useFollowTarget.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/ui/stores/followStore.d.ts +3 -1
  60. package/lib/typescript/commonjs/ui/stores/followStore.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/ui/stores/followTargetStore.d.ts +4 -2
  62. package/lib/typescript/commonjs/ui/stores/followTargetStore.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/ui/utils/followErrors.d.ts +42 -0
  64. package/lib/typescript/commonjs/ui/utils/followErrors.d.ts.map +1 -0
  65. package/lib/typescript/module/index.d.ts +2 -0
  66. package/lib/typescript/module/index.d.ts.map +1 -1
  67. package/lib/typescript/module/ui/client.d.ts +3 -1
  68. package/lib/typescript/module/ui/client.d.ts.map +1 -1
  69. package/lib/typescript/module/ui/components/FollowButton.d.ts.map +1 -1
  70. package/lib/typescript/module/ui/components/FollowTargetButton.d.ts.map +1 -1
  71. package/lib/typescript/module/ui/components/followLabels.d.ts +13 -0
  72. package/lib/typescript/module/ui/components/followLabels.d.ts.map +1 -1
  73. package/lib/typescript/module/ui/hooks/useAccountColor.d.ts +23 -0
  74. package/lib/typescript/module/ui/hooks/useAccountColor.d.ts.map +1 -1
  75. package/lib/typescript/module/ui/hooks/useFollow.d.ts +5 -4
  76. package/lib/typescript/module/ui/hooks/useFollow.d.ts.map +1 -1
  77. package/lib/typescript/module/ui/hooks/useFollow.types.d.ts +3 -2
  78. package/lib/typescript/module/ui/hooks/useFollow.types.d.ts.map +1 -1
  79. package/lib/typescript/module/ui/hooks/useFollowTarget.d.ts +4 -2
  80. package/lib/typescript/module/ui/hooks/useFollowTarget.d.ts.map +1 -1
  81. package/lib/typescript/module/ui/stores/followStore.d.ts +3 -1
  82. package/lib/typescript/module/ui/stores/followStore.d.ts.map +1 -1
  83. package/lib/typescript/module/ui/stores/followTargetStore.d.ts +4 -2
  84. package/lib/typescript/module/ui/stores/followTargetStore.d.ts.map +1 -1
  85. package/lib/typescript/module/ui/utils/followErrors.d.ts +42 -0
  86. package/lib/typescript/module/ui/utils/followErrors.d.ts.map +1 -0
  87. package/package.json +5 -5
  88. package/src/index.ts +2 -0
  89. package/src/ui/client.ts +8 -1
  90. package/src/ui/components/FollowButton.tsx +14 -12
  91. package/src/ui/components/FollowTargetButton.tsx +7 -3
  92. package/src/ui/components/followLabels.ts +36 -2
  93. package/src/ui/hooks/useAccountColor.ts +71 -1
  94. package/src/ui/hooks/useFollow.ts +22 -10
  95. package/src/ui/hooks/useFollow.types.ts +6 -2
  96. package/src/ui/hooks/useFollowTarget.ts +15 -10
  97. package/src/ui/stores/__tests__/followStore.test.ts +1 -1
  98. package/src/ui/stores/followStore.ts +12 -14
  99. package/src/ui/stores/followTargetStore.ts +4 -2
  100. package/src/ui/utils/followErrors.ts +56 -0
@@ -1 +1 @@
1
- {"version":3,"file":"useAccountColor.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useAccountColor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAS1F,2EAA2E;AAC3E,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,iBAAiB,CAAC,GAAG,IAAI,GAAG,SAAS,GAC/D,YAAY,GAAG,IAAI,CAGrB;AAED,wBAAgB,eAAe,CAAC,EAC9B,MAAM,GACP,GAAE;IACD,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC;CACrC,GAAG,IAAI,CAUZ"}
1
+ {"version":3,"file":"useAccountColor.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useAccountColor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAW1F,2EAA2E;AAC3E,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,iBAAiB,CAAC,GAAG,IAAI,GAAG,SAAS,GAC/D,YAAY,GAAG,IAAI,CAGrB;AAED,wBAAgB,eAAe,CAAC,EAC9B,MAAM,GACP,GAAE;IACD,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC;CACrC,GAAG,IAAI,CAUZ;AAED,KAAK,kBAAkB,GAAG,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;AAExF;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,kBAAkB,EACxB,GAAG,GAAE,MAAmB,GACvB,SAAS,YAAY,EAAE,CAYzB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,IAAI;IACrC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IAChC,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,EAAE,OAAO,CAAC;CACjB,CA4BA"}
@@ -1,4 +1,5 @@
1
- import type { OxyServices, BulkFollowResult, BulkUnfollowResult } from '@oxy.so/core';
1
+ import { type OxyServices, type BulkFollowResult, type BulkUnfollowResult } from '@oxy.so/core';
2
+ import type { FollowErrorCode } from '../utils/followErrors.js';
2
3
  /**
3
4
  * useFollow — Hook for follow state management.
4
5
  *
@@ -15,7 +16,7 @@ import type { OxyServices, BulkFollowResult, BulkUnfollowResult } from '@oxy.so/
15
16
  export declare const useFollow: (userId?: string | string[]) => {
16
17
  isFollowing: boolean;
17
18
  isLoading: boolean;
18
- error: string | null;
19
+ error: FollowErrorCode | null;
19
20
  toggleFollow: () => Promise<void>;
20
21
  setFollowStatus: (following: boolean) => void;
21
22
  fetchStatus: () => Promise<void>;
@@ -42,7 +43,7 @@ export declare const useFollow: (userId?: string | string[]) => {
42
43
  followData: Record<string, {
43
44
  isFollowing: boolean;
44
45
  isLoading: boolean;
45
- error: string | null;
46
+ error: FollowErrorCode | null;
46
47
  }>;
47
48
  toggleFollowForUser: (targetUserId: string) => Promise<void>;
48
49
  setFollowStatusForUser: (targetUserId: string, following: boolean) => void;
@@ -81,7 +82,7 @@ export declare const useFollowForButton: (userId: string, oxyServices: OxyServic
81
82
  isFollowing: boolean;
82
83
  isKnown: boolean;
83
84
  isLoading: boolean;
84
- error: string | null;
85
+ error: FollowErrorCode | null;
85
86
  toggleFollow: () => Promise<boolean>;
86
87
  resolveStatus: () => void;
87
88
  setFollowStatus: (following: boolean) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useFollow.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useFollow.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAKtF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,SAAS,GAAI,SAAS,MAAM,GAAG,MAAM,EAAE;;;;;iCAqHpC,OAAO;;;;;;;8BA4BX,MAAM;+BASN,MAAM;;;;;;;;;;;;;;;qBAnF4B,OAAO;mBAAa,OAAO;eAAS,MAAM,GAAG,IAAI;;wCAqHtE,MAAM;2CAW6B,MAAM,aAAa,OAAO;uCAK7D,MAAM;;0BAegB,OAAO,CAAC,gBAAgB,CAAC;4BAMvB,OAAO,CAAC,kBAAkB,CAAC;sCAKrB,MAAM;;;;;;;;;;;;;;;;;;CAkD5D,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,aAAa,WAAW,EACxB,qBAAqB,OAAO;;;;;wBA8Be,OAAO,CAAC,OAAO,CAAC;;iCAc7C,OAAO;;CAoBtB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,mBACT,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAExC,CAAC;AAGT,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM;;;;;8BAkBpC,MAAM;+BAON,MAAM;CAcjB,CAAC"}
1
+ {"version":3,"file":"useFollow.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useFollow.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAAuB,CAAC;AAK7D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,SAAS,GAAI,SAAS,MAAM,GAAG,MAAM,EAAE;;;;;iCAwHpC,OAAO;;;;;;;8BA4BX,MAAM;+BASN,MAAM;;;;;;;;;;;;;;;qBApFG,OAAO;mBAAa,OAAO;eAAS,eAAe,GAAG,IAAI;;wCAsHtD,MAAM;2CAY6B,MAAM,aAAa,OAAO;uCAK7D,MAAM;;0BAegB,OAAO,CAAC,gBAAgB,CAAC;4BAOvB,OAAO,CAAC,kBAAkB,CAAC;sCAMrB,MAAM;;;;;;;;;;;;;;;;;;CAkD5D,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,aAAa,WAAW,EACxB,qBAAqB,OAAO;;;;;wBA8Be,OAAO,CAAC,OAAO,CAAC;;iCAc7C,OAAO;;CAoBtB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,mBACT,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAExC,CAAC;AAGT,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM;;;;;8BAkBpC,MAAM;+BAON,MAAM;CAcjB,CAAC"}
@@ -1,8 +1,9 @@
1
1
  import type { BulkFollowResult } from '@oxy.so/core';
2
+ import type { FollowErrorCode } from '../utils/followErrors.js';
2
3
  export type SingleFollowResult = {
3
4
  isFollowing: boolean;
4
5
  isLoading: boolean;
5
- error: string | null;
6
+ error: FollowErrorCode | null;
6
7
  toggleFollow: () => Promise<void>;
7
8
  setFollowStatus: (following: boolean) => void;
8
9
  fetchStatus: () => Promise<void>;
@@ -18,7 +19,7 @@ export type MultiFollowResult = {
18
19
  followData: Record<string, {
19
20
  isFollowing: boolean;
20
21
  isLoading: boolean;
21
- error: string | null;
22
+ error: FollowErrorCode | null;
22
23
  }>;
23
24
  toggleFollowForUser: (userId: string) => Promise<void>;
24
25
  setFollowStatusForUser: (userId: string, following: boolean) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useFollow.types.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useFollow.types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,eAAe,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAC/F,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,sBAAsB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IACrE,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,cAAc,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAChD,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,kBAAkB,GAAG,iBAAiB,CAAC"}
1
+ {"version":3,"file":"useFollow.types.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useFollow.types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAAuB,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,eAAe,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAChB,MAAM,EACN;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAA;KAAE,CAC5E,CAAC;IACF,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,sBAAsB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IACrE,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,cAAc,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAChD,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,kBAAkB,GAAG,iBAAiB,CAAC"}
@@ -15,6 +15,7 @@
15
15
  * width of one request rather than being sent with a guessed id.
16
16
  */
17
17
  import type { FollowStatus } from '@oxy.so/contracts';
18
+ import { type FollowErrorCode } from '../utils/followErrors.js';
18
19
  export interface UseFollowTargetResult {
19
20
  status: FollowStatus;
20
21
  /**
@@ -25,11 +26,12 @@ export interface UseFollowTargetResult {
25
26
  /** True until the first read settles. Distinct from "not following". */
26
27
  isUnknown: boolean;
27
28
  isPending: boolean;
28
- error: string | undefined;
29
+ /** Why the last read or write failed — a code; `useFollowErrorMessage()` words it. */
30
+ error: FollowErrorCode | undefined;
29
31
  /**
30
32
  * Every mutation resolves to whether the server ACCEPTED it.
31
33
  *
32
- * They never reject — a refusal becomes `error` in the store, so the button
34
+ * They never reject — a refusal becomes an `error` code in the store, so the button
33
35
  * can render it — which means a caller that only awaits them cannot tell a
34
36
  * write that happened from one that did not. Returning the outcome is what
35
37
  * lets a caller mirror the change somewhere else without mirroring failures
@@ -1 +1 @@
1
- {"version":3,"file":"useFollowTarget.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useFollowTarget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAUtD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,YAAY,CAAC;IACrB;;;OAGG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B;;;;;;;;OAQG;IACH,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,wEAAwE;IACxE,WAAW,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,YAAY,CAAA;CAAE,GACzC,qBAAqB,CAmKvB"}
1
+ {"version":3,"file":"useFollowTarget.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useFollowTarget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAStD,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,0BAAuB,CAAC;AAE/B,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,YAAY,CAAC;IACrB;;;OAGG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,sFAAsF;IACtF,KAAK,EAAE,eAAe,GAAG,SAAS,CAAC;IACnC;;;;;;;;OAQG;IACH,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,wEAAwE;IACxE,WAAW,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,YAAY,CAAA;CAAE,GACzC,qBAAqB,CAkKvB"}
@@ -1,9 +1,11 @@
1
1
  import type { OxyServices, BulkFollowResult, BulkUnfollowResult } from '@oxy.so/core';
2
+ import { type FollowErrorCode } from '../utils/followErrors.js';
2
3
  interface FollowState {
3
4
  followingUsers: Record<string, boolean>;
4
5
  loadingUsers: Record<string, boolean>;
5
6
  fetchingUsers: Record<string, boolean>;
6
- errors: Record<string, string | null>;
7
+ /** Why the last read or write of a user failed, as a code — never text. */
8
+ errors: Record<string, FollowErrorCode | null>;
7
9
  followerCounts: Record<string, number>;
8
10
  followingCounts: Record<string, number>;
9
11
  loadingCounts: Record<string, boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"followStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/stores/followStore.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAEnB,MAAM,cAAc,CAAC;AAEtB,UAAU,WAAW;IAKnB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAEtC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;IAKnE,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC1D,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAI7B,qBAAqB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7E,gBAAgB,EAAE,CAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,oBAAoB,EAAE,OAAO,KAC1B,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtB,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAE5F,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAEhG,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,4BAA4B,EAAE,CAC5B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,GAAG,UAAU,EAC7B,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAChD,aAAa,CAAC,EAAE,MAAM,KACnB,IAAI,CAAC;IACV,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9E;AA8BD,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,QAAO,IAMxC,CAAC;AAEF,eAAO,MAAM,cAAc,0EA0YxB,CAAC"}
1
+ {"version":3,"file":"followStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/stores/followStore.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAEnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,eAAe,EAAqB,MAAM,0BAAuB,CAAC;AAEhF,UAAU,WAAW;IAKnB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC,CAAC;IAE/C,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;IAKnE,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC1D,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAI7B,qBAAqB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7E,gBAAgB,EAAE,CAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,oBAAoB,EAAE,OAAO,KAC1B,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtB,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAE5F,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAEhG,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,4BAA4B,EAAE,CAC5B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,GAAG,UAAU,EAC7B,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAChD,aAAa,CAAC,EAAE,MAAM,KACnB,IAAI,CAAC;IACV,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9E;AA8BD,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,QAAO,IAMxC,CAAC;AAEF,eAAO,MAAM,cAAc,0EAsYxB,CAAC"}
@@ -22,13 +22,15 @@
22
22
  * why a follow the user can see in their list does nothing in this app.
23
23
  */
24
24
  import type { FollowApplicationMode, FollowRecord, FollowStatus } from '@oxy.so/contracts';
25
+ import type { FollowErrorCode } from '../utils/followErrors.js';
25
26
  /** The status of one target, or `undefined` when it has never been read. */
26
27
  type StatusMap = Record<string, FollowStatus | undefined>;
27
28
  interface FollowTargetState {
28
29
  statuses: StatusMap;
29
30
  /** In-flight writes, per target. Reads do not set this — only mutations do. */
30
31
  pending: Record<string, boolean>;
31
- errors: Record<string, string | undefined>;
32
+ /** Why the last read or write of a target failed, as a code — never text. */
33
+ errors: Record<string, FollowErrorCode | undefined>;
32
34
  setStatus: (targetId: string, status: FollowStatus) => void;
33
35
  /**
34
36
  * Seed many statuses at once — from a follow list, a feed payload, anything
@@ -37,7 +39,7 @@ interface FollowTargetState {
37
39
  */
38
40
  seed: (entries: Record<string, FollowStatus>) => void;
39
41
  setPending: (targetId: string, pending: boolean) => void;
40
- setError: (targetId: string, error: string | undefined) => void;
42
+ setError: (targetId: string, error: FollowErrorCode | undefined) => void;
41
43
  /** Drop everything. Called on identity change — see the note below. */
42
44
  reset: () => void;
43
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"followTargetStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/stores/followTargetStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE3F,4EAA4E;AAC5E,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAAC,CAAC;AAE1D,UAAU,iBAAiB;IACzB,QAAQ,EAAE,SAAS,CAAC;IACpB,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAE3C,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IAC5D;;;;OAIG;IACH,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,KAAK,IAAI,CAAC;IACtD,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACzD,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAChE,uEAAuE;IACvE,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAInC,CAAC;AAEF,eAAO,MAAM,oBAAoB,gFAoB9B,CAAC;AAEJ;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAGpE;AAED,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAQvE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAM9F;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,qBAAqB,GAC1B,YAAY,CAad"}
1
+ {"version":3,"file":"followTargetStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/stores/followTargetStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAAuB,CAAC;AAE7D,4EAA4E;AAC5E,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAAC,CAAC;AAE1D,UAAU,iBAAiB;IACzB,QAAQ,EAAE,SAAS,CAAC;IACpB,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC,CAAC;IAEpD,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IAC5D;;;;OAIG;IACH,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,KAAK,IAAI,CAAC;IACtD,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACzD,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,SAAS,KAAK,IAAI,CAAC;IACzE,uEAAuE;IACvE,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAInC,CAAC;AAEF,eAAO,MAAM,oBAAoB,gFAoB9B,CAAC;AAEJ;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAGpE;AAED,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAQvE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAM9F;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,qBAAqB,GAC1B,YAAY,CAad"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * What a failed follow read or write leaves behind: a stable CODE, never text.
3
+ *
4
+ * The follow stores and hooks have no translator and must not pretend to — an
5
+ * English sentence written into a store reaches every locale's toast verbatim,
6
+ * and so does a server's `message`. They keep one of these codes instead, and
7
+ * the UI turns it into words in the user's language (`describeFollowError` in
8
+ * `components/followLabels.ts`).
9
+ *
10
+ * A code says either what the caller was doing (`read_failed`, `update_failed`,
11
+ * `mode_failed`) or — when the failure says more than that — why it failed,
12
+ * taken from the API's machine code and HTTP status rather than its message.
13
+ */
14
+ export type FollowErrorCode =
15
+ /** The status could not be read. */
16
+ 'read_failed'
17
+ /** A follow or unfollow was refused. */
18
+ | 'update_failed'
19
+ /** Switching a follow off or back on in this application was refused. */
20
+ | 'mode_failed'
21
+ /** No response arrived: the device is offline, or the request timed out. */
22
+ | 'offline'
23
+ /** The session is gone; signing in again is the only fix. */
24
+ | 'signed_out'
25
+ /** This application lacks the permission the write needs. */
26
+ | 'forbidden'
27
+ /** The target or the relationship no longer exists. */
28
+ | 'not_found'
29
+ /** Too many writes in a short window. */
30
+ | 'rate_limited';
31
+ /** The codes that name the caller's action rather than a cause. */
32
+ export type FollowActionErrorCode = 'read_failed' | 'update_failed' | 'mode_failed';
33
+ /**
34
+ * The code for whatever a follow request rejected with.
35
+ *
36
+ * Read structurally — every `OxyServices` call rejects with an `OxyApiError`
37
+ * (or an `OxyAuthenticationError`), and both carry `status` and `code` — so a
38
+ * second copy of `@oxy.so/core` in an app cannot defeat an `instanceof`.
39
+ * Anything the cause does not explain falls back to the caller's `action`.
40
+ */
41
+ export declare function toFollowErrorCode(error: unknown, action: FollowActionErrorCode): FollowErrorCode;
42
+ //# sourceMappingURL=followErrors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"followErrors.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/followErrors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe;AACzB,oCAAoC;AAClC,aAAa;AACf,wCAAwC;GACtC,eAAe;AACjB,yEAAyE;GACvE,aAAa;AACf,4EAA4E;GAC1E,SAAS;AACX,6DAA6D;GAC3D,YAAY;AACd,6DAA6D;GAC3D,WAAW;AACb,uDAAuD;GACrD,WAAW;AACb,yCAAyC;GACvC,cAAc,CAAC;AAEnB,mEAAmE;AACnE,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG,eAAe,GAAG,aAAa,CAAC;AAIpF;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAWhG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/services",
3
- "version": "12.7.0",
3
+ "version": "12.9.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",
@@ -151,7 +151,7 @@
151
151
  "prepublishOnly": "node ../../scripts/assert-bun-publish.mjs && bun run typescript && bun run test && bun run build"
152
152
  },
153
153
  "dependencies": {
154
- "@oxy.so/contracts": "^5.4.0",
154
+ "@oxy.so/contracts": "^5.6.0",
155
155
  "color": "^5.0.3",
156
156
  "@peable.to/sdk": "0.4.1"
157
157
  },
@@ -160,7 +160,7 @@
160
160
  "@commitlint/config-conventional": "^21.2.0",
161
161
  "@expo/app-integrity": "~58.0.2",
162
162
  "@expo/vector-icons": "^15.0.3",
163
- "@oxy.so/core": "6.9.3",
163
+ "@oxy.so/core": "6.11.1",
164
164
  "@react-native-async-storage/async-storage": "^2.0.0",
165
165
  "@react-native-community/netinfo": "^12.0.1",
166
166
  "@react-native/babel-preset": "^0.88.0",
@@ -210,12 +210,12 @@
210
210
  "expo-video": "~58.0.11"
211
211
  },
212
212
  "//@oxy.so/bloom": "Bloom 11.1.0 adds the shared grouped sections, usage card, subscription plans, frame appearance and overlay navigation APIs used by this SDK. First-party Bloom peers remain open above this tested minimum.",
213
- "//@oxy.so/core": "Core 6.4.0 supplies the `follow.*` strings the follow buttons read for every verb and language; 6.1.0 the `oxy.auth.email.{startChange,change,remove}` and `oxy.auth.password.remove` the sign-in & security panels call. Keep one Core instance per app; raise this floor with the root catalogue when new runtime APIs are required.",
213
+ "//@oxy.so/core": "Core 6.11.0 supplies the `follow.errors.*` strings the follow error codes are worded with; 6.4.0 the `follow.*` strings the follow buttons read for every verb and language; 6.1.0 the `oxy.auth.email.{startChange,change,remove}` and `oxy.auth.password.remove` the sign-in & security panels call. Keep one Core instance per app; raise this floor with the root catalogue when new runtime APIs are required.",
214
214
  "peerDependencies": {
215
215
  "@expo/app-integrity": ">=56.0.0 <59.0.0",
216
216
  "@expo/vector-icons": "^15.0.3",
217
217
  "@oxy.so/bloom": ">=11.1.0",
218
- "@oxy.so/core": "^6.4.0",
218
+ "@oxy.so/core": "^6.11.0",
219
219
  "@react-native-async-storage/async-storage": "^2.0.0",
220
220
  "@react-native-community/netinfo": ">=11.4.1 <13.0.0",
221
221
  "@tanstack/query-async-storage-persister": "^5.101",
package/src/index.ts CHANGED
@@ -346,6 +346,8 @@ export {
346
346
  export type { FollowDuration, FollowMenuItem } from './ui/components/followRules';
347
347
  export type { FollowTargetButtonProps } from './ui/components/FollowTargetButton';
348
348
  export type { FollowVerb, FollowLabels } from './ui/components/followLabels';
349
+ export { useFollowErrorMessage } from './ui/components/followLabels';
350
+ export type { FollowErrorCode } from './ui/utils/followErrors';
349
351
  export { useFollowTarget } from './ui/hooks/useFollowTarget';
350
352
  export type { UseFollowTargetResult } from './ui/hooks/useFollowTarget';
351
353
  export {
package/src/ui/client.ts CHANGED
@@ -31,6 +31,8 @@ export type {
31
31
  export { FollowTargetButton } from './components/FollowTargetButton';
32
32
  export type { FollowTargetButtonProps } from './components/FollowTargetButton';
33
33
  export type { FollowVerb, FollowLabels } from './components/followLabels';
34
+ export { useFollowErrorMessage } from './components/followLabels';
35
+ export type { FollowErrorCode } from './utils/followErrors';
34
36
  export { default as ProfileButton } from './components/ProfileButton';
35
37
  export type { ProfileButtonProps } from './components/ProfileButton';
36
38
  export { default as PeableButton } from './components/PeableButton';
@@ -45,7 +47,12 @@ export { useOxy, useOptionalOxy, OxyProviderMissingError } from './context/OxyCo
45
47
 
46
48
  // Hooks
47
49
  export { useAuth } from './hooks/useAuth';
48
- export { useAccountColor, resolveAccountColor } from './hooks/useAccountColor';
50
+ export {
51
+ useAccountColor,
52
+ useAccountColorSave,
53
+ resolveAccountColor,
54
+ entitledAccountColors,
55
+ } from './hooks/useAccountColor';
49
56
  // This app's Oxy-signed assertion of which app it is (ADR 0045), for an SDK
50
57
  // that attaches it to its own requests without pulling in the account screens.
51
58
  export {
@@ -9,8 +9,14 @@ import { useFollowStore } from '../stores/followStore';
9
9
  import type { OxyServices, BulkFollowResult, BulkUnfollowResult } from '@oxy.so/core';
10
10
  import { useShallow } from 'zustand/react/shallow';
11
11
  import { useI18n } from '../hooks/useI18n';
12
+ import { toFollowErrorCode } from '../utils/followErrors';
12
13
  import { describeFollowAllButton, describeFollowButton } from './followButtonAccessibility';
13
- import { type FollowLabels, type FollowVerb, useFollowLabels } from './followLabels';
14
+ import {
15
+ describeFollowError,
16
+ type FollowLabels,
17
+ type FollowVerb,
18
+ useFollowLabels,
19
+ } from './followLabels';
14
20
 
15
21
  /** Props shared by both single- and multi-user follow modes. */
16
22
  interface FollowButtonBaseProps {
@@ -101,13 +107,10 @@ const FollowButtonInner = memo(function FollowButtonInner({
101
107
  // Ignore presses while a mutation is in flight or the status is still unknown.
102
108
  if (disabled || isLoading || !isKnown) return;
103
109
 
104
- try {
105
- const accepted = await toggleFollow();
106
- if (accepted) onFollowChange?.(!isFollowing);
107
- } catch (err: unknown) {
108
- const error = err instanceof Error ? err : new Error(String(err));
109
- toast.error(error.message || t('follow.errors.update'));
110
- }
110
+ // `toggleFollow` never rejects: a refusal is rolled back and its code
111
+ // recorded in the store, which is what the toast words.
112
+ if (await toggleFollow()) onFollowChange?.(!isFollowing);
113
+ else toast.error(describeFollowError(useFollowStore.getState().errors[userId], t));
111
114
  },
112
115
  [
113
116
  disabled,
@@ -117,6 +120,7 @@ const FollowButtonInner = memo(function FollowButtonInner({
117
120
  onFollowChange,
118
121
  isFollowing,
119
122
  preventParentActions,
123
+ userId,
120
124
  t,
121
125
  ],
122
126
  );
@@ -234,8 +238,7 @@ const FollowButtonMultiInner = memo(function FollowButtonMultiInner({
234
238
  onFollowChange?.(false);
235
239
  onBulkUnfollow?.(result);
236
240
  } catch (err: unknown) {
237
- const error = err instanceof Error ? err : new Error(String(err));
238
- toast.error(error.message || t('follow.errors.update'));
241
+ toast.error(describeFollowError(toFollowErrorCode(err, 'update_failed'), t));
239
242
  } finally {
240
243
  setIsSubmitting(false);
241
244
  }
@@ -261,8 +264,7 @@ const FollowButtonMultiInner = memo(function FollowButtonMultiInner({
261
264
  }
262
265
  onBulkFollow?.(result);
263
266
  } catch (err: unknown) {
264
- const error = err instanceof Error ? err : new Error(String(err));
265
- toast.error(error.message || t('follow.errors.update'));
267
+ toast.error(describeFollowError(toFollowErrorCode(err, 'update_failed'), t));
266
268
  } finally {
267
269
  setIsSubmitting(false);
268
270
  }
@@ -60,7 +60,12 @@ import {
60
60
  type FollowDuration,
61
61
  type FollowMenuItem,
62
62
  } from './followRules';
63
- import { type FollowLabels, type FollowVerb, useFollowLabels } from './followLabels';
63
+ import {
64
+ describeFollowError,
65
+ type FollowLabels,
66
+ type FollowVerb,
67
+ useFollowLabels,
68
+ } from './followLabels';
64
69
  import { useI18n } from '../hooks/useI18n';
65
70
 
66
71
  const HOUR = 60 * 60;
@@ -149,8 +154,7 @@ export const FollowTargetButton = memo(function FollowTargetButton({
149
154
  );
150
155
 
151
156
  const reportMutationFailure = useCallback(() => {
152
- const message = useFollowTargetStore.getState().errors[targetId] ?? t('follow.errors.update');
153
- toast.error(message);
157
+ toast.error(describeFollowError(useFollowTargetStore.getState().errors[targetId], t));
154
158
  }, [targetId, t]);
155
159
 
156
160
  const label = useMemo(() => {
@@ -8,8 +8,9 @@
8
8
  * drawn.
9
9
  */
10
10
 
11
- import { useMemo } from 'react';
12
- import { useI18n } from '../hooks/useI18n';
11
+ import { useCallback, useMemo } from 'react';
12
+ import { type Translate, useI18n } from '../hooks/useI18n';
13
+ import type { FollowErrorCode } from '../utils/followErrors';
13
14
 
14
15
  /**
15
16
  * The small vocabulary of verbs the ecosystem actually uses. An application
@@ -46,3 +47,36 @@ export function useFollowLabels(
46
47
  [t, verb, overrides],
47
48
  );
48
49
  }
50
+
51
+ /** The `follow.errors.*` string for each code — the one place a code becomes words. */
52
+ const FOLLOW_ERROR_KEYS: Record<FollowErrorCode, string> = {
53
+ read_failed: 'follow.errors.read',
54
+ update_failed: 'follow.errors.update',
55
+ mode_failed: 'follow.errors.mode',
56
+ offline: 'follow.errors.offline',
57
+ signed_out: 'follow.errors.signedOut',
58
+ forbidden: 'follow.errors.forbidden',
59
+ not_found: 'follow.errors.notFound',
60
+ rate_limited: 'follow.errors.rateLimited',
61
+ };
62
+
63
+ /**
64
+ * What a follow error says, in the current language. Every component that
65
+ * shows a follow failure reads it here; a missing code (a failure the store
66
+ * did not record) reads as the generic "could not update".
67
+ */
68
+ export function describeFollowError(
69
+ code: FollowErrorCode | null | undefined,
70
+ t: Translate,
71
+ ): string {
72
+ return t(FOLLOW_ERROR_KEYS[code ?? 'update_failed']);
73
+ }
74
+
75
+ /**
76
+ * `describeFollowError` bound to the current language, for an application that
77
+ * renders `useFollowTarget().error` (or `useFollow().error`) itself.
78
+ */
79
+ export function useFollowErrorMessage(): (code: FollowErrorCode | null | undefined) => string {
80
+ const { t } = useI18n();
81
+ return useCallback((code) => describeFollowError(code, t), [t]);
82
+ }
@@ -16,10 +16,12 @@
16
16
  * default instead of a colour the viewer is not entitled to.
17
17
  */
18
18
 
19
- import { useEffect } from 'react';
19
+ import { useCallback, useEffect, useMemo } from 'react';
20
20
  import type { User } from '@oxy.so/core';
21
21
  import { APP_COLOR_PRESETS, useBloomTheme, type AppColorName } from '@oxy.so/bloom/theme';
22
+ import { USER_PROFILE_COLORS, USER_PROFILE_COLOR_PRESETS } from '@oxy.so/contracts/client';
22
23
  import { useAuth } from './useAuth';
24
+ import { useUpdateProfile } from './mutations/useAccountMutations';
23
25
 
24
26
  function isAppColorName(value: unknown): value is AppColorName {
25
27
  return (
@@ -50,3 +52,71 @@ export function useAccountColor({
50
52
  setColorPreset(accountColor);
51
53
  }, [isAuthenticated, accountColor, allowed, colorPreset, setColorPreset]);
52
54
  }
55
+
56
+ type EntitlementSubject = Pick<User, 'username' | 'personalization'> | null | undefined;
57
+
58
+ /**
59
+ * The presets this account may choose, from the contract's own gate table: a
60
+ * free preset, the one named after the account's handle (`oxy`, `faircoin`), and
61
+ * `mono` while the account holds the benefit that grants it. The API enforces
62
+ * the same rule when it stores the value; this only keeps a picker from offering
63
+ * what it would refuse.
64
+ */
65
+ export function entitledAccountColors(
66
+ user: EntitlementSubject,
67
+ now: number = Date.now(),
68
+ ): readonly AppColorName[] {
69
+ const handle = user?.username?.trim().toLowerCase();
70
+ const grant = user?.personalization?.mentionMono;
71
+ const expires = grant?.expiresAt === null ? Infinity : Date.parse(grant?.expiresAt ?? '');
72
+ const hasMono = grant?.allowed === true && expires > now;
73
+ const supported = new Set<string>(USER_PROFILE_COLOR_PRESETS);
74
+ return USER_PROFILE_COLORS.filter(({ name, gate }) => {
75
+ if (!supported.has(name) || !isAppColorName(name)) return false;
76
+ if (gate === null) return true;
77
+ if (gate === 'handle') return name === handle;
78
+ return name === 'mono' && hasMono;
79
+ }).map(({ name }) => name as AppColorName);
80
+ }
81
+
82
+ /**
83
+ * Choose the account's colour: one `updateMe` writes `color` and the portable
84
+ * `themePreference.colorPreset` together (the mode already on the account is
85
+ * kept), the Bloom theme follows at once, and the session user is updated
86
+ * optimistically so `useAccountColor` agrees with it. `colors` is what the
87
+ * picker should list.
88
+ */
89
+ export function useAccountColorSave(): {
90
+ colors: readonly AppColorName[];
91
+ value: AppColorName;
92
+ saveColor: (name: AppColorName) => Promise<void>;
93
+ saving: boolean;
94
+ } {
95
+ const { user } = useAuth();
96
+ const { mode, colorPreset, setColorPreset } = useBloomTheme();
97
+ const { mutateAsync, isPending } = useUpdateProfile();
98
+ const colors = useMemo(() => entitledAccountColors(user), [user]);
99
+ const saveColor = useCallback(
100
+ async (name: AppColorName) => {
101
+ if (!colors.includes(name)) throw new Error('Color is not available for this account');
102
+ const previous = colorPreset;
103
+ setColorPreset(name);
104
+ try {
105
+ await mutateAsync({
106
+ color: name,
107
+ themePreference: {
108
+ mode:
109
+ user?.themePreference?.mode ??
110
+ (mode === 'light' || mode === 'dark' ? mode : 'system'),
111
+ colorPreset: name,
112
+ },
113
+ });
114
+ } catch (error) {
115
+ setColorPreset(previous);
116
+ throw error;
117
+ }
118
+ },
119
+ [colors, colorPreset, setColorPreset, mutateAsync, user, mode],
120
+ );
121
+ return { colors, value: colorPreset, saveColor, saving: isPending };
122
+ }
@@ -2,7 +2,13 @@ import { useCallback, useMemo, useState } from 'react';
2
2
  import { useQuery, useQueryClient } from '@tanstack/react-query';
3
3
  import { useFollowStore } from '../stores/followStore';
4
4
  import { useOxy } from '../context/oxyRuntimeContext';
5
- import type { OxyServices, BulkFollowResult, BulkUnfollowResult } from '@oxy.so/core';
5
+ import {
6
+ OxyAuthenticationError,
7
+ type OxyServices,
8
+ type BulkFollowResult,
9
+ type BulkUnfollowResult,
10
+ } from '@oxy.so/core';
11
+ import type { FollowErrorCode } from '../utils/followErrors';
6
12
  import { useShallow } from 'zustand/react/shallow';
7
13
  import { queryKeys } from './queries/queryKeys';
8
14
  import { patchCachedUserRelationship } from './queries/userCacheRelationship';
@@ -79,8 +85,8 @@ export const useFollow = (userId?: string | string[]) => {
79
85
  // nested `followData` shape is then assembled in a `useMemo` below.
80
86
  const followFlat = useFollowStore(
81
87
  useShallow((s) => {
82
- if (isSingleUser) return {} as Record<string, boolean | string | null>;
83
- const flat: Record<string, boolean | string | null> = {};
88
+ if (isSingleUser) return {} as Record<string, boolean | FollowErrorCode | null>;
89
+ const flat: Record<string, boolean | FollowErrorCode | null> = {};
84
90
  for (const uid of userIds) {
85
91
  flat[`${uid}:isFollowing`] = s.followingUsers[uid] ?? false;
86
92
  flat[`${uid}:isLoading`] = s.loadingUsers[uid] ?? false;
@@ -91,14 +97,16 @@ export const useFollow = (userId?: string | string[]) => {
91
97
  );
92
98
 
93
99
  const followData = useMemo(() => {
94
- const data: Record<string, { isFollowing: boolean; isLoading: boolean; error: string | null }> =
95
- {};
100
+ const data: Record<
101
+ string,
102
+ { isFollowing: boolean; isLoading: boolean; error: FollowErrorCode | null }
103
+ > = {};
96
104
  if (isSingleUser) return data;
97
105
  for (const uid of userIds) {
98
106
  data[uid] = {
99
107
  isFollowing: Boolean(followFlat[`${uid}:isFollowing`]),
100
108
  isLoading: Boolean(followFlat[`${uid}:isLoading`]),
101
- error: (followFlat[`${uid}:error`] as string | null) ?? null,
109
+ error: (followFlat[`${uid}:error`] as FollowErrorCode | null) ?? null,
102
110
  };
103
111
  }
104
112
  return data;
@@ -128,7 +136,8 @@ export const useFollow = (userId?: string | string[]) => {
128
136
  const toggleFollow = useCallback(async () => {
129
137
  if (!isSingleUser || !userId)
130
138
  throw new Error('toggleFollow is only available for single user mode');
131
- if (!canUsePrivateApi) throw new Error('Authentication is required to follow users');
139
+ if (!canUsePrivateApi)
140
+ throw new OxyAuthenticationError('Authentication is required to follow users');
132
141
  const currentlyFollowing = useFollowStore.getState().followingUsers[userId] ?? false;
133
142
  const accepted = await useFollowStore
134
143
  .getState()
@@ -209,7 +218,8 @@ export const useFollow = (userId?: string | string[]) => {
209
218
  // Multi-user callbacks
210
219
  const toggleFollowForUser = useCallback(
211
220
  async (targetUserId: string) => {
212
- if (!canUsePrivateApi) throw new Error('Authentication is required to follow users');
221
+ if (!canUsePrivateApi)
222
+ throw new OxyAuthenticationError('Authentication is required to follow users');
213
223
  const currentState = useFollowStore.getState().followingUsers[targetUserId] ?? false;
214
224
  const accepted = await useFollowStore
215
225
  .getState()
@@ -240,13 +250,15 @@ export const useFollow = (userId?: string | string[]) => {
240
250
 
241
251
  // Bulk follow — follows ALL users in ONE network call (never unfollows).
242
252
  const followAllUsers = useCallback(async (): Promise<BulkFollowResult> => {
243
- if (!canUsePrivateApi) throw new Error('Authentication is required to follow users');
253
+ if (!canUsePrivateApi)
254
+ throw new OxyAuthenticationError('Authentication is required to follow users');
244
255
  return useFollowStore.getState().followManyUsers(userIds, oxyServices);
245
256
  }, [canUsePrivateApi, userIds, oxyServices]);
246
257
 
247
258
  // Bulk unfollow — unfollows ALL users in ONE network call (idempotent; never follows).
248
259
  const unfollowAllUsers = useCallback(async (): Promise<BulkUnfollowResult> => {
249
- if (!canUsePrivateApi) throw new Error('Authentication is required to unfollow users');
260
+ if (!canUsePrivateApi)
261
+ throw new OxyAuthenticationError('Authentication is required to unfollow users');
250
262
  return useFollowStore.getState().unfollowManyUsers(userIds, oxyServices);
251
263
  }, [canUsePrivateApi, userIds, oxyServices]);
252
264
 
@@ -2,11 +2,12 @@
2
2
  // Expand this as needed to better reflect the real return type.
3
3
 
4
4
  import type { BulkFollowResult } from '@oxy.so/core';
5
+ import type { FollowErrorCode } from '../utils/followErrors';
5
6
 
6
7
  export type SingleFollowResult = {
7
8
  isFollowing: boolean;
8
9
  isLoading: boolean;
9
- error: string | null;
10
+ error: FollowErrorCode | null;
10
11
  toggleFollow: () => Promise<void>;
11
12
  setFollowStatus: (following: boolean) => void;
12
13
  fetchStatus: () => Promise<void>;
@@ -20,7 +21,10 @@ export type SingleFollowResult = {
20
21
  };
21
22
 
22
23
  export type MultiFollowResult = {
23
- followData: Record<string, { isFollowing: boolean; isLoading: boolean; error: string | null }>;
24
+ followData: Record<
25
+ string,
26
+ { isFollowing: boolean; isLoading: boolean; error: FollowErrorCode | null }
27
+ >;
24
28
  toggleFollowForUser: (userId: string) => Promise<void>;
25
29
  setFollowStatusForUser: (userId: string, following: boolean) => void;
26
30
  fetchStatusForUser: (userId: string) => Promise<void>;