@fluid-app/portal-sdk 0.1.159 → 0.1.161
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/README.md +2 -196
- package/dist/{FluidProvider-Cqf2kmUc.mjs → FluidProvider-B00jTGTH.mjs} +1510 -1624
- package/dist/FluidProvider-B00jTGTH.mjs.map +1 -0
- package/dist/{FluidProvider-Bc-3uN7M.cjs → FluidProvider-BtGi2jJt.cjs} +1458 -1614
- package/dist/FluidProvider-BtGi2jJt.cjs.map +1 -0
- package/dist/{MessagingScreen-BGqIn-c2.cjs → MessagingScreen-BRCUJDDZ.cjs} +2 -2
- package/dist/{MessagingScreen-CbmuvlH6.mjs → MessagingScreen-BWpSXB8Q.mjs} +2 -2
- package/dist/{MessagingScreen-CbmuvlH6.mjs.map → MessagingScreen-BWpSXB8Q.mjs.map} +1 -1
- package/dist/{MessagingScreen-CBPjP4du.cjs → MessagingScreen-C_OLIWCC.cjs} +2 -2
- package/dist/{MessagingScreen-CBPjP4du.cjs.map → MessagingScreen-C_OLIWCC.cjs.map} +1 -1
- package/dist/{MySiteScreen-DSDLDnCN.cjs → MySiteScreen-B90qzZPe.cjs} +2 -2
- package/dist/{MySiteScreen-Cl6nuU99.cjs → MySiteScreen-DLuHDXB1.cjs} +2 -2
- package/dist/{MySiteScreen-Cl6nuU99.cjs.map → MySiteScreen-DLuHDXB1.cjs.map} +1 -1
- package/dist/{MySiteScreen-BT1PBPsH.mjs → MySiteScreen-VaOB-vWk.mjs} +2 -2
- package/dist/{MySiteScreen-BT1PBPsH.mjs.map → MySiteScreen-VaOB-vWk.mjs.map} +1 -1
- package/dist/ProductsScreen-B8OynxlP.cjs +13 -0
- package/dist/{ProductsScreen-4WRrJcvw.mjs → ProductsScreen-CbVSNv1l.mjs} +3 -5
- package/dist/ProductsScreen-CbVSNv1l.mjs.map +1 -0
- package/dist/{ProductsScreen-BCs3YKVk.cjs → ProductsScreen-D1bw4ZIH.cjs} +3 -5
- package/dist/ProductsScreen-D1bw4ZIH.cjs.map +1 -0
- package/dist/ProductsScreen-Pq3j09nI.mjs +11 -0
- package/dist/{ProfileScreen-BgyrIQdL.mjs → ProfileScreen-BCHljkWD.mjs} +2 -2
- package/dist/{ProfileScreen-BgyrIQdL.mjs.map → ProfileScreen-BCHljkWD.mjs.map} +1 -1
- package/dist/{ProfileScreen-B0KNWXpV.cjs → ProfileScreen-Bf-lYNzz.cjs} +2 -2
- package/dist/{ProfileScreen-DLLLRNB2.cjs → ProfileScreen-ksdtbydb.cjs} +2 -2
- package/dist/{ProfileScreen-DLLLRNB2.cjs.map → ProfileScreen-ksdtbydb.cjs.map} +1 -1
- package/dist/{ShareablesScreen-ySSwCVSI.cjs → ShareablesScreen-6wgiwi_9.cjs} +3 -5
- package/dist/ShareablesScreen-6wgiwi_9.cjs.map +1 -0
- package/dist/{ShareablesScreen-B8cmh8JC.mjs → ShareablesScreen-BLCukNTk.mjs} +3 -5
- package/dist/{ShareablesScreen-B8cmh8JC.mjs.map → ShareablesScreen-BLCukNTk.mjs.map} +1 -1
- package/dist/ShareablesScreen-CM9OH-Nx.mjs +11 -0
- package/dist/ShareablesScreen-rmLcUhbL.cjs +13 -0
- package/dist/{ShopScreen-DhMo8bvP.cjs → ShopScreen-8KKwwjka.cjs} +3 -3
- package/dist/{ShopScreen-DhMo8bvP.cjs.map → ShopScreen-8KKwwjka.cjs.map} +1 -1
- package/dist/{ShopScreen-BkvyW0pE.cjs → ShopScreen-CjEbB3Q7.cjs} +2 -2
- package/dist/{ShopScreen-BFFGYwdV.mjs → ShopScreen-Dn5LwwYD.mjs} +3 -3
- package/dist/{ShopScreen-BFFGYwdV.mjs.map → ShopScreen-Dn5LwwYD.mjs.map} +1 -1
- package/dist/{SubscriptionsScreen-RScBCcY0.cjs → SubscriptionsScreen-B16wPAoA.cjs} +2 -2
- package/dist/SubscriptionsScreen-B16wPAoA.cjs.map +1 -0
- package/dist/{SubscriptionsScreen-CiNR7JUC.mjs → SubscriptionsScreen-B4cTlgDU.mjs} +2 -2
- package/dist/SubscriptionsScreen-B4cTlgDU.mjs.map +1 -0
- package/dist/{SubscriptionsScreen-DMh-GE6n.cjs → SubscriptionsScreen-nRUMdnx7.cjs} +1 -1
- package/dist/{dist-Bg8UyHyM.cjs → dist-lO2OG0T5.cjs} +1 -1
- package/dist/{dist-Bg8UyHyM.cjs.map → dist-lO2OG0T5.cjs.map} +1 -1
- package/dist/index.cjs +134 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +444 -655
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +444 -655
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +127 -130
- package/dist/index.mjs.map +1 -1
- package/dist/{sortable.esm-Cz-CP2N8.mjs → sortable.esm-DSrWP4x9.mjs} +1 -1
- package/dist/{sortable.esm-Cz-CP2N8.mjs.map → sortable.esm-DSrWP4x9.mjs.map} +1 -1
- package/dist/{use-portal-products-client-BmlUixy4.cjs → use-portal-products-client-BHSBT64s.cjs} +2 -2
- package/dist/use-portal-products-client-BHSBT64s.cjs.map +1 -0
- package/dist/{use-portal-products-client-DQK9nFxT.mjs → use-portal-products-client-tbqk6XUq.mjs} +2 -2
- package/dist/use-portal-products-client-tbqk6XUq.mjs.map +1 -0
- package/dist/{use-portal-shareables-api-KVPj0Jfr.mjs → use-portal-shareables-api-B9B4XTjw.mjs} +107 -201
- package/dist/use-portal-shareables-api-B9B4XTjw.mjs.map +1 -0
- package/dist/{use-portal-shareables-api-D5D6uIJy.cjs → use-portal-shareables-api-MBl0d0eQ.cjs} +106 -206
- package/dist/use-portal-shareables-api-MBl0d0eQ.cjs.map +1 -0
- package/package.json +13 -13
- package/dist/FluidProvider-Bc-3uN7M.cjs.map +0 -1
- package/dist/FluidProvider-Cqf2kmUc.mjs.map +0 -1
- package/dist/ProductsScreen-4WRrJcvw.mjs.map +0 -1
- package/dist/ProductsScreen-BCs3YKVk.cjs.map +0 -1
- package/dist/ProductsScreen-BR9TN4So.cjs +0 -48
- package/dist/ProductsScreen-Da6eWJ_c.mjs +0 -46
- package/dist/ShareablesScreen-DW0wCYOj.mjs +0 -46
- package/dist/ShareablesScreen-DbPJOtCF.cjs +0 -48
- package/dist/ShareablesScreen-ySSwCVSI.cjs.map +0 -1
- package/dist/SubscriptionsScreen-CiNR7JUC.mjs.map +0 -1
- package/dist/SubscriptionsScreen-RScBCcY0.cjs.map +0 -1
- package/dist/use-portal-products-client-BmlUixy4.cjs.map +0 -1
- package/dist/use-portal-products-client-DQK9nFxT.mjs.map +0 -1
- package/dist/use-portal-shareables-api-D5D6uIJy.cjs.map +0 -1
- package/dist/use-portal-shareables-api-KVPj0Jfr.mjs.map +0 -1
|
@@ -489,7 +489,7 @@ function createPersister() {
|
|
|
489
489
|
});
|
|
490
490
|
}
|
|
491
491
|
//#endregion
|
|
492
|
-
//#region ../core/src/
|
|
492
|
+
//#region ../core/src/fluidos-api-context.ts
|
|
493
493
|
const FluidOsReadApiContext = (0, react.createContext)(null);
|
|
494
494
|
const FluidOsBuilderApiContext = (0, react.createContext)(null);
|
|
495
495
|
/** Provider for read-only manifest fetching (used by SDK). */
|
|
@@ -710,18 +710,6 @@ function createFetchClient(config) {
|
|
|
710
710
|
};
|
|
711
711
|
}
|
|
712
712
|
//#endregion
|
|
713
|
-
//#region ../../api-clients/fluidos/src/namespaces/fluid_os.ts
|
|
714
|
-
/**
|
|
715
|
-
* Get active Fluid OS definition
|
|
716
|
-
* Retrieve the active Fluid OS definition manifest for a specific platform
|
|
717
|
-
*
|
|
718
|
-
* @param client - Fetch client instance
|
|
719
|
-
* @param params - params
|
|
720
|
-
*/
|
|
721
|
-
async function getFluidOSManifest(client, params) {
|
|
722
|
-
return client.get(`/api/fluid_os/definitions/active`, params);
|
|
723
|
-
}
|
|
724
|
-
//#endregion
|
|
725
713
|
//#region src/client/types.ts
|
|
726
714
|
/**
|
|
727
715
|
* HTTP methods supported by the API client.
|
|
@@ -735,1807 +723,1705 @@ const HTTP_METHODS = {
|
|
|
735
723
|
DELETE: "DELETE"
|
|
736
724
|
};
|
|
737
725
|
//#endregion
|
|
738
|
-
//#region
|
|
739
|
-
const SEMANTIC_COLOR_NAMES = [
|
|
740
|
-
"background",
|
|
741
|
-
"foreground",
|
|
742
|
-
"primary",
|
|
743
|
-
"secondary",
|
|
744
|
-
"accent",
|
|
745
|
-
"muted",
|
|
746
|
-
"destructive"
|
|
747
|
-
];
|
|
748
|
-
const SHADE_STEPS = [
|
|
749
|
-
100,
|
|
750
|
-
200,
|
|
751
|
-
300,
|
|
752
|
-
400,
|
|
753
|
-
500,
|
|
754
|
-
600,
|
|
755
|
-
700,
|
|
756
|
-
800,
|
|
757
|
-
900
|
|
758
|
-
];
|
|
759
|
-
const FONT_SIZE_KEYS = [
|
|
760
|
-
"extraSmall",
|
|
761
|
-
"small",
|
|
762
|
-
"regular",
|
|
763
|
-
"large",
|
|
764
|
-
"extraLarge",
|
|
765
|
-
"giant"
|
|
766
|
-
];
|
|
767
|
-
const FONT_FAMILY_KEYS = ["header", "body"];
|
|
768
|
-
const RADIUS_KEYS = [
|
|
769
|
-
"small",
|
|
770
|
-
"medium",
|
|
771
|
-
"large",
|
|
772
|
-
"extraLarge"
|
|
773
|
-
];
|
|
774
|
-
//#endregion
|
|
775
|
-
//#region ../core/src/theme/color-engine.ts
|
|
726
|
+
//#region src/client/fluid-client.ts
|
|
776
727
|
/**
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
* @returns the parsed Color, or a neutral gray (`oklch(0.5 0 0)`) on failure
|
|
728
|
+
* Fluid API Client
|
|
729
|
+
* Adapted from: packages/fluidos-api-client/src/lib/fetch-client.ts
|
|
730
|
+
* Provides authenticated API access with domain-specific methods
|
|
782
731
|
*/
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
732
|
+
/**
|
|
733
|
+
* API Error class for structured error handling
|
|
734
|
+
*/
|
|
735
|
+
var ApiError = class ApiError extends Error {
|
|
736
|
+
status;
|
|
737
|
+
data;
|
|
738
|
+
constructor(message, status, data) {
|
|
739
|
+
super(message);
|
|
740
|
+
this.name = "ApiError";
|
|
741
|
+
this.status = status;
|
|
742
|
+
this.data = data;
|
|
743
|
+
const errorWithCapture = Error;
|
|
744
|
+
if (errorWithCapture.captureStackTrace) errorWithCapture.captureStackTrace(this, ApiError);
|
|
794
745
|
}
|
|
795
|
-
|
|
746
|
+
toJSON() {
|
|
747
|
+
return {
|
|
748
|
+
name: this.name,
|
|
749
|
+
message: this.message,
|
|
750
|
+
status: this.status,
|
|
751
|
+
data: this.data
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
};
|
|
796
755
|
/**
|
|
797
|
-
*
|
|
798
|
-
* whichever provides better contrast against `color`.
|
|
799
|
-
* Inversion triggers when the APCA contrast is below 50.
|
|
756
|
+
* Type guard for ApiError
|
|
800
757
|
*/
|
|
801
|
-
function
|
|
802
|
-
|
|
803
|
-
if (color.contrastAPCA(foreground) < 50) return new colorjs_io.default("oklch", [
|
|
804
|
-
color.oklch.l < .7 ? .95 : .15,
|
|
805
|
-
foreground.oklch.c || 0,
|
|
806
|
-
foreground.oklch.h || 0
|
|
807
|
-
]);
|
|
808
|
-
return foreground;
|
|
758
|
+
function isApiError(error) {
|
|
759
|
+
return error instanceof ApiError;
|
|
809
760
|
}
|
|
810
761
|
/**
|
|
811
|
-
*
|
|
812
|
-
* Base anchors at 500. Light shades (100–400) step toward white,
|
|
813
|
-
* dark shades (600–900) step toward black. Dark steps use an asymmetric
|
|
814
|
-
* multiplier (1.6×, 1.875×, 3×, 4× of `darkStep`) for a more gradual
|
|
815
|
-
* initial descent. Chroma is nudged per step for perceptually natural ramps.
|
|
762
|
+
* Type guard to check if a value is a non-null string
|
|
816
763
|
*/
|
|
817
|
-
function
|
|
818
|
-
|
|
819
|
-
const c = base.oklch.c ?? 0;
|
|
820
|
-
const h = base.oklch.h ?? 0;
|
|
821
|
-
const safeMax = l >= .885 ? .995 : .97;
|
|
822
|
-
const safeMin = l <= .33 ? 0 : .21;
|
|
823
|
-
const lightStep = (safeMax - l) / 5;
|
|
824
|
-
const darkStep = -(l - safeMin) / 8;
|
|
825
|
-
const shade = (lDelta, cDelta) => {
|
|
826
|
-
return new colorjs_io.default("oklch", [
|
|
827
|
-
Math.max(0, Math.min(1, l + lDelta)),
|
|
828
|
-
c <= .001 ? c : Math.max(0, c + cDelta),
|
|
829
|
-
h
|
|
830
|
-
]);
|
|
831
|
-
};
|
|
832
|
-
return {
|
|
833
|
-
100: shade(5 * lightStep, -.00375),
|
|
834
|
-
200: shade(4 * lightStep, -.00375),
|
|
835
|
-
300: shade(3 * lightStep, -.00375),
|
|
836
|
-
400: shade(2 * lightStep, -.00375),
|
|
837
|
-
500: new colorjs_io.default("oklch", [
|
|
838
|
-
l,
|
|
839
|
-
c,
|
|
840
|
-
h
|
|
841
|
-
]),
|
|
842
|
-
600: shade(1.6 * darkStep, .025),
|
|
843
|
-
700: shade(1.875 * 2 * darkStep, .05),
|
|
844
|
-
800: shade(6 * darkStep, .075),
|
|
845
|
-
900: shade(8 * darkStep, .1)
|
|
846
|
-
};
|
|
847
|
-
}
|
|
848
|
-
const DARK_DERIVATION_CONFIG = {
|
|
849
|
-
background: {
|
|
850
|
-
baseLightness: .15,
|
|
851
|
-
fgLightness: .93
|
|
852
|
-
},
|
|
853
|
-
foreground: {
|
|
854
|
-
baseLightness: .93,
|
|
855
|
-
fgLightness: .15
|
|
856
|
-
},
|
|
857
|
-
muted: {
|
|
858
|
-
baseLightness: .22,
|
|
859
|
-
fgLightness: .75
|
|
860
|
-
},
|
|
861
|
-
primary: {
|
|
862
|
-
baseLightness: "invert",
|
|
863
|
-
fgLightness: .95,
|
|
864
|
-
chromaScale: .9
|
|
865
|
-
},
|
|
866
|
-
secondary: {
|
|
867
|
-
baseLightness: "invert",
|
|
868
|
-
fgLightness: .93,
|
|
869
|
-
chromaScale: .85
|
|
870
|
-
},
|
|
871
|
-
accent: {
|
|
872
|
-
baseLightness: "invert",
|
|
873
|
-
fgLightness: .95,
|
|
874
|
-
chromaScale: .9
|
|
875
|
-
},
|
|
876
|
-
destructive: {
|
|
877
|
-
baseLightness: "invert",
|
|
878
|
-
fgLightness: .95,
|
|
879
|
-
chromaScale: .95
|
|
880
|
-
}
|
|
881
|
-
};
|
|
882
|
-
/** Invert OKLCH lightness (1 - l), clamped to [0.35, 0.75] to avoid extremes. */
|
|
883
|
-
function invertLightness(l) {
|
|
884
|
-
const inverted = 1 - l;
|
|
885
|
-
return Math.max(.35, Math.min(.75, inverted));
|
|
764
|
+
function isString(value) {
|
|
765
|
+
return typeof value === "string";
|
|
886
766
|
}
|
|
887
767
|
/**
|
|
888
|
-
*
|
|
768
|
+
* Extract error message from API response data using `in` operator narrowing.
|
|
769
|
+
* Checks common error message field names in order of precedence.
|
|
889
770
|
*/
|
|
890
|
-
function
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
return {
|
|
896
|
-
base: new colorjs_io.default("oklch", [
|
|
897
|
-
baseLightness,
|
|
898
|
-
(light.base.oklch.c || 0) * chromaScale,
|
|
899
|
-
light.base.oklch.h || 0
|
|
900
|
-
]),
|
|
901
|
-
foreground: new colorjs_io.default("oklch", [
|
|
902
|
-
fgLightness,
|
|
903
|
-
(light.foreground.oklch.c || 0) * chromaScale,
|
|
904
|
-
light.foreground.oklch.h || 0
|
|
905
|
-
])
|
|
906
|
-
};
|
|
771
|
+
function extractErrorMessage(data, fallback) {
|
|
772
|
+
if ("message" in data && isString(data.message)) return data.message;
|
|
773
|
+
if ("error_message" in data && isString(data.error_message)) return data.error_message;
|
|
774
|
+
if ("error" in data && isString(data.error)) return data.error;
|
|
775
|
+
return fallback;
|
|
907
776
|
}
|
|
908
777
|
/**
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
* foreground those are used; otherwise the missing channels are derived.
|
|
778
|
+
* Type guard to detect whether a parsed JSON value is an API envelope.
|
|
779
|
+
* Envelopes always have numeric `status` and a `data` key.
|
|
912
780
|
*/
|
|
913
|
-
function
|
|
914
|
-
|
|
915
|
-
for (const name of SEMANTIC_COLOR_NAMES) {
|
|
916
|
-
const lightInput = def.light[name];
|
|
917
|
-
const darkOverride = def.dark[name];
|
|
918
|
-
if (darkOverride?.base && darkOverride?.foreground) darkColors[name] = darkOverride;
|
|
919
|
-
else if (darkOverride) {
|
|
920
|
-
const base = darkOverride.base ?? deriveDarkVariant(name, lightInput).base;
|
|
921
|
-
darkColors[name] = {
|
|
922
|
-
base,
|
|
923
|
-
foreground: darkOverride.foreground ?? getForegroundColor(def.light.foreground.base, base)
|
|
924
|
-
};
|
|
925
|
-
} else darkColors[name] = deriveDarkVariant(name, lightInput);
|
|
926
|
-
}
|
|
927
|
-
return darkColors;
|
|
928
|
-
}
|
|
929
|
-
function resolveColorSet(colors) {
|
|
930
|
-
const resolved = {};
|
|
931
|
-
for (const name of SEMANTIC_COLOR_NAMES) {
|
|
932
|
-
const input = colors[name];
|
|
933
|
-
const shades = generateShades(input.base);
|
|
934
|
-
const resolvedShades = {};
|
|
935
|
-
for (const step of SHADE_STEPS) resolvedShades[step] = shades[step];
|
|
936
|
-
resolved[name] = {
|
|
937
|
-
base: input.base.clone(),
|
|
938
|
-
foreground: input.foreground.clone(),
|
|
939
|
-
shades: resolvedShades
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
return resolved;
|
|
781
|
+
function isApiEnvelope(value) {
|
|
782
|
+
return typeof value === "object" && value !== null && "status" in value && typeof value.status === "number" && "data" in value;
|
|
943
783
|
}
|
|
944
784
|
/**
|
|
945
|
-
*
|
|
946
|
-
* Dark mode colors are derived from light where not overridden.
|
|
785
|
+
* Creates a configured Fluid API client instance
|
|
947
786
|
*/
|
|
948
|
-
function
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
const TAILWIND_COLOR_MAP = {
|
|
983
|
-
gray: "foreground",
|
|
984
|
-
red: "destructive",
|
|
985
|
-
blue: "primary",
|
|
986
|
-
green: "accent"
|
|
987
|
-
};
|
|
988
|
-
const TAILWIND_SHADES = [
|
|
989
|
-
50,
|
|
990
|
-
100,
|
|
991
|
-
200,
|
|
992
|
-
300,
|
|
993
|
-
400,
|
|
994
|
-
500,
|
|
995
|
-
600,
|
|
996
|
-
700,
|
|
997
|
-
800,
|
|
998
|
-
900,
|
|
999
|
-
950
|
|
1000
|
-
];
|
|
1001
|
-
const SHADE_REMAP = {
|
|
1002
|
-
50: 100,
|
|
1003
|
-
950: 900
|
|
1004
|
-
};
|
|
1005
|
-
const lines = [];
|
|
1006
|
-
for (const [twName, semantic] of Object.entries(TAILWIND_COLOR_MAP)) for (const shade of TAILWIND_SHADES) {
|
|
1007
|
-
const step = SHADE_REMAP[shade] ?? shade;
|
|
1008
|
-
const override = OVERRIDES[`--color-${twName}-${shade}`];
|
|
1009
|
-
lines.push(`--color-${twName}-${shade}: ${override ? override : `var(--color-${semantic}-${semantic === "foreground" && darkMode === true ? getInvertedStep(step) : step})`};`);
|
|
1010
|
-
}
|
|
1011
|
-
lines.push("--color-white: var(--color-background);");
|
|
1012
|
-
lines.push("--color-black: var(--color-foreground);");
|
|
1013
|
-
return lines;
|
|
1014
|
-
}
|
|
1015
|
-
//#endregion
|
|
1016
|
-
//#region ../core/src/theme/css-generator.ts
|
|
1017
|
-
function colorToCSS(color) {
|
|
1018
|
-
const result = color.toString({ format: "oklch" });
|
|
1019
|
-
if (result.includes("NaN")) {
|
|
1020
|
-
console.warn("[theme] colorToCSS produced NaN, using neutral fallback:", result);
|
|
1021
|
-
return "oklch(0.5 0 0)";
|
|
1022
|
-
}
|
|
1023
|
-
return result;
|
|
1024
|
-
}
|
|
1025
|
-
function camelToKebab(str) {
|
|
1026
|
-
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Emit --color-{name}, --color-{name}-foreground, --color-{name}-{shade} vars.
|
|
1030
|
-
* Uses --color- prefix to match portal-widgets/tailwind.config.ts.
|
|
1031
|
-
*/
|
|
1032
|
-
function emitColorVars(colors) {
|
|
1033
|
-
const lines = [];
|
|
1034
|
-
for (const name of SEMANTIC_COLOR_NAMES) {
|
|
1035
|
-
const color = colors[name];
|
|
1036
|
-
lines.push(`--color-${name}: ${colorToCSS(color.base)};`);
|
|
1037
|
-
lines.push(`--color-${name}-foreground: ${colorToCSS(color.foreground)};`);
|
|
1038
|
-
for (const step of SHADE_STEPS) lines.push(`--color-${name}-${step}: ${colorToCSS(color.shades[step])};`);
|
|
1039
|
-
}
|
|
1040
|
-
return lines;
|
|
1041
|
-
}
|
|
1042
|
-
/**
|
|
1043
|
-
* Format a font family value for CSS output.
|
|
1044
|
-
* - If the value starts with "var(" (legacy), pass through as-is
|
|
1045
|
-
* - If the value already contains a comma (has fallback), pass through as-is
|
|
1046
|
-
* - Otherwise, wrap in quotes and append a generic sans-serif fallback
|
|
1047
|
-
*/
|
|
1048
|
-
function formatFontFamily(value) {
|
|
1049
|
-
if (value.startsWith("var(")) return value;
|
|
1050
|
-
if (value.includes(",")) return value;
|
|
1051
|
-
return `'${value}', sans-serif`;
|
|
1052
|
-
}
|
|
1053
|
-
/**
|
|
1054
|
-
* Emit non-color CSS variables (font sizes, families, spacing, radii).
|
|
1055
|
-
*/
|
|
1056
|
-
function emitNonColorVars(theme) {
|
|
1057
|
-
const lines = [];
|
|
1058
|
-
for (const key of FONT_SIZE_KEYS) lines.push(`--font-size-${camelToKebab(key)}: ${theme.fontSizes[key]};`);
|
|
1059
|
-
for (const key of FONT_FAMILY_KEYS) lines.push(`--font-${key}: ${formatFontFamily(theme.fontFamilies[key])};`);
|
|
1060
|
-
lines.push(`--spacing: ${theme.spacing};`);
|
|
1061
|
-
for (const key of RADIUS_KEYS) lines.push(`--radius-${camelToKebab(key)}: ${theme.radii[key]};`);
|
|
1062
|
-
return lines;
|
|
1063
|
-
}
|
|
1064
|
-
/**
|
|
1065
|
-
* Static CSS alias variables that bridge theme var names to Tailwind/component conventions.
|
|
1066
|
-
* These are always emitted and not mode-dependent.
|
|
1067
|
-
*/
|
|
1068
|
-
const globalCSSOverride = [
|
|
1069
|
-
"--color-background-foreground: var(--color-foreground);",
|
|
1070
|
-
"--color-foreground-foreground: var(--color-background);",
|
|
1071
|
-
"--color-contrast: var(--color-foreground);",
|
|
1072
|
-
...SEMANTIC_COLOR_NAMES.map((value) => `--${value}: var(--color-${value});`),
|
|
1073
|
-
...SEMANTIC_COLOR_NAMES.map((value) => `--${value}-foreground: var(--color-${value}-foreground);`),
|
|
1074
|
-
"--sidebar-ring: var(--color-primary);",
|
|
1075
|
-
"--sidebar-border: var(--color-border);",
|
|
1076
|
-
"--sidebar-accent-foreground: var(--color-accent-foreground);",
|
|
1077
|
-
"--sidebar-accent: var(--color-accent);",
|
|
1078
|
-
"--sidebar-primary-foreground: var(--color-primary-foreground);",
|
|
1079
|
-
"--sidebar-primary: var(--color-primary);",
|
|
1080
|
-
"--sidebar-foreground: var(--color-muted-foreground);",
|
|
1081
|
-
"--sidebar: var(--color-muted);",
|
|
1082
|
-
"--border: var(--color-background-600);",
|
|
1083
|
-
"--ring: var(--color-primary);",
|
|
1084
|
-
"--popover: var(--color-background);",
|
|
1085
|
-
"--popover-foreground: var(--color-foreground);",
|
|
1086
|
-
"--card: var(--color-muted);",
|
|
1087
|
-
"--card-foreground: var(--color-muted-foreground);",
|
|
1088
|
-
"--radius-sm: var(--radius-small);",
|
|
1089
|
-
"--radius-md: var(--radius-medium);",
|
|
1090
|
-
"--radius-lg: var(--radius-large);",
|
|
1091
|
-
"--radius-xl: var(--radius-extra-large);",
|
|
1092
|
-
"--text-xs: var(--font-size-extra-small);",
|
|
1093
|
-
"--text-sm: var(--font-size-small);",
|
|
1094
|
-
"--text-base: var(--font-size-regular);",
|
|
1095
|
-
"--text-lg: var(--font-size-large);",
|
|
1096
|
-
"--text-xl: var(--font-size-extra-large);",
|
|
1097
|
-
"--text-2xl: var(--font-size-giant);"
|
|
1098
|
-
];
|
|
1099
|
-
/**
|
|
1100
|
-
* Overrides for global tailwindcss for specifically dark mode.
|
|
1101
|
-
*/
|
|
1102
|
-
const globalDarkCSSOverride = ["--border: var(--color-background-400);"];
|
|
1103
|
-
/**
|
|
1104
|
-
* Generate a complete CSS string for a resolved theme.
|
|
1105
|
-
* Outputs 2–3 blocks: light default, dark explicit via `[data-theme-mode="dark"]`,
|
|
1106
|
-
* and (unless `disableAutoTheme`) a `prefers-color-scheme: dark` media query block.
|
|
1107
|
-
*/
|
|
1108
|
-
function generateThemeCSS(theme, options = {}) {
|
|
1109
|
-
const sel = `[data-theme="${theme.id}"]`;
|
|
1110
|
-
const tw = options.mapTailwindColors ?? true;
|
|
1111
|
-
const blocks = [];
|
|
1112
|
-
blocks.push(`${sel} {`);
|
|
1113
|
-
blocks.push(...globalCSSOverride);
|
|
1114
|
-
blocks.push(...emitNonColorVars(theme));
|
|
1115
|
-
blocks.push(...emitColorVars(theme.light));
|
|
1116
|
-
if (tw) blocks.push(...emitTailwindOverrides());
|
|
1117
|
-
blocks.push(`}`);
|
|
1118
|
-
blocks.push(`${sel}[data-theme-mode="dark"] {`);
|
|
1119
|
-
blocks.push(...globalDarkCSSOverride);
|
|
1120
|
-
blocks.push(...emitColorVars(theme.dark));
|
|
1121
|
-
if (tw) blocks.push(...emitTailwindOverrides(true));
|
|
1122
|
-
blocks.push(`}`);
|
|
1123
|
-
if (!options.disableAutoTheme) {
|
|
1124
|
-
blocks.push(`@media (prefers-color-scheme: dark) {`);
|
|
1125
|
-
blocks.push(`${sel}:not([data-theme-mode]) {`);
|
|
1126
|
-
blocks.push(...globalDarkCSSOverride);
|
|
1127
|
-
blocks.push(...emitColorVars(theme.dark).map((l) => `${l}`));
|
|
1128
|
-
if (tw) blocks.push(...emitTailwindOverrides(true).map((l) => `${l}`));
|
|
1129
|
-
blocks.push(`}`);
|
|
1130
|
-
blocks.push(`}`);
|
|
1131
|
-
}
|
|
1132
|
-
return blocks.join("\n");
|
|
1133
|
-
}
|
|
1134
|
-
//#endregion
|
|
1135
|
-
//#region ../core/src/theme/defaults.ts
|
|
1136
|
-
const DEFAULT_FONT_SIZES = {
|
|
1137
|
-
extraSmall: "0.75rem",
|
|
1138
|
-
small: "0.875rem",
|
|
1139
|
-
regular: "1rem",
|
|
1140
|
-
large: "1.125rem",
|
|
1141
|
-
extraLarge: "1.25rem",
|
|
1142
|
-
giant: "1.5rem"
|
|
1143
|
-
};
|
|
1144
|
-
const DEFAULT_FONT_FAMILIES = {
|
|
1145
|
-
header: "Inter",
|
|
1146
|
-
body: "Inter"
|
|
1147
|
-
};
|
|
1148
|
-
const DEFAULT_SPACING = "0.25rem";
|
|
1149
|
-
const DEFAULT_RADII = {
|
|
1150
|
-
small: "0.25rem",
|
|
1151
|
-
medium: "0.5rem",
|
|
1152
|
-
large: "0.75rem",
|
|
1153
|
-
extraLarge: "1rem"
|
|
1154
|
-
};
|
|
1155
|
-
const DEFAULT_COLORS = {
|
|
1156
|
-
background: "#ffffff",
|
|
1157
|
-
foreground: "#1a1a1a",
|
|
1158
|
-
primary: "#3b82f6",
|
|
1159
|
-
secondary: "#6b7280",
|
|
1160
|
-
accent: "#10b981",
|
|
1161
|
-
muted: "#f3f4f6",
|
|
1162
|
-
destructive: "#ef4444",
|
|
1163
|
-
mutedForeground: "#6b7280"
|
|
1164
|
-
};
|
|
1165
|
-
const DEFAULT_THEME_ID = "default";
|
|
1166
|
-
const DEFAULT_THEME_NAME = "Default Theme";
|
|
1167
|
-
/**
|
|
1168
|
-
* Build a fresh ThemeDefinition populated with all defaults.
|
|
1169
|
-
* Returns a new object each call because Color instances are mutable — do not cache the result.
|
|
1170
|
-
*/
|
|
1171
|
-
function getDefaultThemeDefinition() {
|
|
1172
|
-
const bg = new colorjs_io.default(DEFAULT_COLORS.background);
|
|
1173
|
-
const fg = new colorjs_io.default(DEFAULT_COLORS.foreground);
|
|
1174
|
-
const primary = new colorjs_io.default(DEFAULT_COLORS.primary);
|
|
1175
|
-
const secondary = new colorjs_io.default(DEFAULT_COLORS.secondary);
|
|
1176
|
-
const accent = new colorjs_io.default(DEFAULT_COLORS.accent);
|
|
1177
|
-
const muted = new colorjs_io.default(DEFAULT_COLORS.muted);
|
|
1178
|
-
const destructive = new colorjs_io.default(DEFAULT_COLORS.destructive);
|
|
1179
|
-
const mutedFg = new colorjs_io.default(DEFAULT_COLORS.mutedForeground);
|
|
1180
|
-
const darkBg = new colorjs_io.default("#0a0a0a");
|
|
1181
|
-
const darkFg = new colorjs_io.default("#fafafa");
|
|
1182
|
-
const darkMuted = new colorjs_io.default("#171717");
|
|
1183
|
-
const darkMutedForeground = new colorjs_io.default("#dddddd");
|
|
1184
|
-
return {
|
|
1185
|
-
id: DEFAULT_THEME_ID,
|
|
1186
|
-
name: DEFAULT_THEME_NAME,
|
|
1187
|
-
light: {
|
|
1188
|
-
background: {
|
|
1189
|
-
base: bg,
|
|
1190
|
-
foreground: fg
|
|
1191
|
-
},
|
|
1192
|
-
foreground: {
|
|
1193
|
-
base: fg,
|
|
1194
|
-
foreground: bg
|
|
1195
|
-
},
|
|
1196
|
-
primary: {
|
|
1197
|
-
base: primary,
|
|
1198
|
-
foreground: getForegroundColor(fg, primary)
|
|
1199
|
-
},
|
|
1200
|
-
secondary: {
|
|
1201
|
-
base: secondary,
|
|
1202
|
-
foreground: getForegroundColor(fg, secondary)
|
|
1203
|
-
},
|
|
1204
|
-
accent: {
|
|
1205
|
-
base: accent,
|
|
1206
|
-
foreground: getForegroundColor(fg, accent)
|
|
1207
|
-
},
|
|
1208
|
-
muted: {
|
|
1209
|
-
base: muted,
|
|
1210
|
-
foreground: mutedFg
|
|
1211
|
-
},
|
|
1212
|
-
destructive: {
|
|
1213
|
-
base: destructive,
|
|
1214
|
-
foreground: getForegroundColor(fg, destructive)
|
|
1215
|
-
}
|
|
1216
|
-
},
|
|
1217
|
-
dark: {
|
|
1218
|
-
background: {
|
|
1219
|
-
base: darkBg,
|
|
1220
|
-
foreground: darkFg
|
|
1221
|
-
},
|
|
1222
|
-
foreground: {
|
|
1223
|
-
base: darkFg,
|
|
1224
|
-
foreground: darkBg
|
|
1225
|
-
},
|
|
1226
|
-
muted: {
|
|
1227
|
-
base: darkMuted,
|
|
1228
|
-
foreground: darkMutedForeground
|
|
1229
|
-
}
|
|
1230
|
-
},
|
|
1231
|
-
fontSizes: { ...DEFAULT_FONT_SIZES },
|
|
1232
|
-
fontFamilies: { ...DEFAULT_FONT_FAMILIES },
|
|
1233
|
-
spacing: DEFAULT_SPACING,
|
|
1234
|
-
radii: { ...DEFAULT_RADII }
|
|
1235
|
-
};
|
|
1236
|
-
}
|
|
1237
|
-
//#endregion
|
|
1238
|
-
//#region ../core/src/theme/serialisation.ts
|
|
1239
|
-
function colorToPlain(color) {
|
|
1240
|
-
return {
|
|
1241
|
-
l: color.oklch.l ?? 0,
|
|
1242
|
-
c: color.oklch.c ?? 0,
|
|
1243
|
-
h: color.oklch.h ?? 0
|
|
1244
|
-
};
|
|
1245
|
-
}
|
|
1246
|
-
function plainToColor(plain) {
|
|
1247
|
-
return new colorjs_io.default("oklch", [
|
|
1248
|
-
plain.l,
|
|
1249
|
-
plain.c,
|
|
1250
|
-
plain.h
|
|
1251
|
-
]);
|
|
1252
|
-
}
|
|
1253
|
-
/**
|
|
1254
|
-
* Serialise a ThemeDefinition (with Color objects) to a plain JSON payload
|
|
1255
|
-
* suitable for backend storage.
|
|
1256
|
-
*/
|
|
1257
|
-
function serialiseTheme(def) {
|
|
1258
|
-
const light = {};
|
|
1259
|
-
for (const name of SEMANTIC_COLOR_NAMES) light[name] = {
|
|
1260
|
-
base: colorToPlain(def.light[name].base),
|
|
1261
|
-
foreground: colorToPlain(def.light[name].foreground)
|
|
1262
|
-
};
|
|
1263
|
-
const dark = {};
|
|
1264
|
-
for (const [name, value] of Object.entries(def.dark)) {
|
|
1265
|
-
if (!value) continue;
|
|
1266
|
-
dark[name] = {
|
|
1267
|
-
...value.base ? { base: colorToPlain(value.base) } : {},
|
|
1268
|
-
...value.foreground ? { foreground: colorToPlain(value.foreground) } : {}
|
|
1269
|
-
};
|
|
1270
|
-
}
|
|
1271
|
-
return {
|
|
1272
|
-
id: def.id,
|
|
1273
|
-
name: def.name,
|
|
1274
|
-
light,
|
|
1275
|
-
dark,
|
|
1276
|
-
fontSizes: { ...def.fontSizes },
|
|
1277
|
-
fontFamilies: { ...def.fontFamilies },
|
|
1278
|
-
spacing: def.spacing,
|
|
1279
|
-
radii: { ...def.radii },
|
|
1280
|
-
...def.syncWithBrandColors ? { syncWithBrandColors: true } : {}
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
/**
|
|
1284
|
-
* Deserialise a backend payload into a ThemeDefinition with Color objects.
|
|
1285
|
-
* Accepts `Record<string, unknown>` because API data is untyped at the boundary.
|
|
1286
|
-
* Falls back to default colors for any missing light-mode entries.
|
|
1287
|
-
*/
|
|
1288
|
-
function deserialiseTheme(payload) {
|
|
1289
|
-
const lightRaw = payload.light ?? {};
|
|
1290
|
-
const darkRaw = payload.dark ?? {};
|
|
1291
|
-
const defaults = getDefaultThemeDefinition();
|
|
1292
|
-
const light = {};
|
|
1293
|
-
for (const name of SEMANTIC_COLOR_NAMES) {
|
|
1294
|
-
const entry = lightRaw[name];
|
|
1295
|
-
if (entry) light[name] = {
|
|
1296
|
-
base: plainToColor(entry.base),
|
|
1297
|
-
foreground: plainToColor(entry.foreground)
|
|
1298
|
-
};
|
|
1299
|
-
else {
|
|
1300
|
-
console.warn(`[theme] deserialiseTheme: missing light color "${name}", using default`);
|
|
1301
|
-
light[name] = defaults.light[name];
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
const dark = {};
|
|
1305
|
-
for (const [name, value] of Object.entries(darkRaw)) {
|
|
1306
|
-
if (!value) continue;
|
|
1307
|
-
dark[name] = {
|
|
1308
|
-
...value.base ? { base: plainToColor(value.base) } : {},
|
|
1309
|
-
...value.foreground ? { foreground: plainToColor(value.foreground) } : {}
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1312
|
-
return {
|
|
1313
|
-
id: payload.id,
|
|
1314
|
-
name: payload.name,
|
|
1315
|
-
light,
|
|
1316
|
-
dark,
|
|
1317
|
-
fontSizes: payload.fontSizes ?? DEFAULT_FONT_SIZES,
|
|
1318
|
-
fontFamilies: payload.fontFamilies ?? DEFAULT_FONT_FAMILIES,
|
|
1319
|
-
spacing: payload.spacing ?? "0.25rem",
|
|
1320
|
-
radii: payload.radii ?? DEFAULT_RADII,
|
|
1321
|
-
...payload.syncWithBrandColors === true ? { syncWithBrandColors: true } : {}
|
|
1322
|
-
};
|
|
1323
|
-
}
|
|
1324
|
-
//#endregion
|
|
1325
|
-
//#region ../core/src/theme/transforms.ts
|
|
1326
|
-
/**
|
|
1327
|
-
* Check if a theme config uses the new structured format (has a `light` key
|
|
1328
|
-
* that is an object) vs the legacy flat format.
|
|
1329
|
-
*/
|
|
1330
|
-
function isNewThemeFormat(config) {
|
|
1331
|
-
return config.light != null && typeof config.light === "object";
|
|
1332
|
-
}
|
|
1333
|
-
/**
|
|
1334
|
-
* Convert a legacy flat config to a ThemeDefinition.
|
|
1335
|
-
* Legacy format: { base: "#fff", text: "#000", primary: "oklch(0.6 0.2 250)", ... }
|
|
1336
|
-
*/
|
|
1337
|
-
function legacyConfigToDefinition(id, name, config) {
|
|
1338
|
-
const bg = parseColor(config.base ?? config.background ?? DEFAULT_COLORS.background);
|
|
1339
|
-
const fg = parseColor(config.text ?? config.foreground ?? DEFAULT_COLORS.foreground);
|
|
1340
|
-
const primary = parseColor(config.primary ?? DEFAULT_COLORS.primary);
|
|
1341
|
-
const secondary = parseColor(config.secondary ?? DEFAULT_COLORS.secondary);
|
|
1342
|
-
const accent = parseColor(config.accent ?? DEFAULT_COLORS.accent);
|
|
1343
|
-
const muted = parseColor(config.muted ?? DEFAULT_COLORS.muted);
|
|
1344
|
-
const destructive = parseColor(config.destructive ?? DEFAULT_COLORS.destructive);
|
|
1345
|
-
const mutedFg = parseColor(config.mutedForeground ?? DEFAULT_COLORS.mutedForeground);
|
|
1346
|
-
return {
|
|
1347
|
-
id: String(id),
|
|
1348
|
-
name,
|
|
1349
|
-
light: {
|
|
1350
|
-
background: {
|
|
1351
|
-
base: bg,
|
|
1352
|
-
foreground: fg
|
|
1353
|
-
},
|
|
1354
|
-
foreground: {
|
|
1355
|
-
base: fg,
|
|
1356
|
-
foreground: bg
|
|
1357
|
-
},
|
|
1358
|
-
primary: {
|
|
1359
|
-
base: primary,
|
|
1360
|
-
foreground: getForegroundColor(fg, primary)
|
|
1361
|
-
},
|
|
1362
|
-
secondary: {
|
|
1363
|
-
base: secondary,
|
|
1364
|
-
foreground: getForegroundColor(fg, secondary)
|
|
1365
|
-
},
|
|
1366
|
-
accent: {
|
|
1367
|
-
base: accent,
|
|
1368
|
-
foreground: getForegroundColor(fg, accent)
|
|
1369
|
-
},
|
|
1370
|
-
muted: {
|
|
1371
|
-
base: muted,
|
|
1372
|
-
foreground: mutedFg
|
|
1373
|
-
},
|
|
1374
|
-
destructive: {
|
|
1375
|
-
base: destructive,
|
|
1376
|
-
foreground: getForegroundColor(fg, destructive)
|
|
787
|
+
function createFluidClient(config) {
|
|
788
|
+
const { baseUrl, getAuthToken, onAuthError, defaultHeaders = {} } = config;
|
|
789
|
+
const fetchClient = createFetchClient({
|
|
790
|
+
baseUrl,
|
|
791
|
+
...getAuthToken ? { getAuthToken } : {},
|
|
792
|
+
onAuthError,
|
|
793
|
+
defaultHeaders,
|
|
794
|
+
credentials: "include"
|
|
795
|
+
});
|
|
796
|
+
/**
|
|
797
|
+
* Build headers for a request.
|
|
798
|
+
* Auth is handled by session cookies via `credentials: 'include'` on fetch calls.
|
|
799
|
+
*/
|
|
800
|
+
function buildHeaders(customHeaders) {
|
|
801
|
+
return {
|
|
802
|
+
"Content-Type": "application/json",
|
|
803
|
+
...defaultHeaders,
|
|
804
|
+
...customHeaders
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Build URL with query parameters (Rails-compatible)
|
|
809
|
+
*/
|
|
810
|
+
function buildUrl(endpoint, params) {
|
|
811
|
+
const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
812
|
+
const normalizedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
|
|
813
|
+
const url = normalizedBase ? new URL(normalizedBase + normalizedEndpoint) : new URL(normalizedEndpoint, typeof window !== "undefined" ? window.location.origin : "http://localhost");
|
|
814
|
+
if (params) for (const [key, value] of Object.entries(params)) {
|
|
815
|
+
if (value === void 0 || value === null) continue;
|
|
816
|
+
if (Array.isArray(value)) for (const item of value) url.searchParams.append(`${key}[]`, String(item));
|
|
817
|
+
else if (typeof value === "object") for (const [subKey, subValue] of Object.entries(value)) {
|
|
818
|
+
if (subValue === void 0 || subValue === null) continue;
|
|
819
|
+
if (Array.isArray(subValue)) for (const item of subValue) url.searchParams.append(`${key}[${subKey}][]`, String(item));
|
|
820
|
+
else url.searchParams.append(`${key}[${subKey}]`, String(subValue));
|
|
1377
821
|
}
|
|
1378
|
-
|
|
1379
|
-
dark: {},
|
|
1380
|
-
fontSizes: {
|
|
1381
|
-
extraSmall: config.extraSmall ?? DEFAULT_FONT_SIZES.extraSmall,
|
|
1382
|
-
small: config.small ?? DEFAULT_FONT_SIZES.small,
|
|
1383
|
-
regular: config.regular ?? DEFAULT_FONT_SIZES.regular,
|
|
1384
|
-
large: config.large ?? DEFAULT_FONT_SIZES.large,
|
|
1385
|
-
extraLarge: config.extraLarge ?? DEFAULT_FONT_SIZES.extraLarge,
|
|
1386
|
-
giant: config.giant ?? DEFAULT_FONT_SIZES.giant
|
|
1387
|
-
},
|
|
1388
|
-
fontFamilies: {
|
|
1389
|
-
header: config.headerFont ?? DEFAULT_FONT_FAMILIES.header,
|
|
1390
|
-
body: config.bodyFont ?? DEFAULT_FONT_FAMILIES.body
|
|
1391
|
-
},
|
|
1392
|
-
spacing: config.globalSpacing ?? "0.25rem",
|
|
1393
|
-
radii: {
|
|
1394
|
-
small: config.radiusSmall ?? DEFAULT_RADII.small,
|
|
1395
|
-
medium: config.radiusMedium ?? DEFAULT_RADII.medium,
|
|
1396
|
-
large: config.radiusLarge ?? DEFAULT_RADII.large,
|
|
1397
|
-
extraLarge: config.radiusExtraLarge ?? DEFAULT_RADII.extraLarge
|
|
822
|
+
else url.searchParams.append(key, String(value));
|
|
1398
823
|
}
|
|
1399
|
-
|
|
1400
|
-
}
|
|
1401
|
-
/**
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
1404
|
-
*/
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
/**
|
|
1415
|
-
* Transform raw API themes to ThemeDefinition[].
|
|
1416
|
-
* Catches and logs errors per theme (graceful degradation).
|
|
1417
|
-
*/
|
|
1418
|
-
function transformThemes(themes) {
|
|
1419
|
-
return themes.flatMap((theme) => {
|
|
824
|
+
return url.toString();
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Default request options for type-safe defaults.
|
|
828
|
+
* Uses `satisfies` to validate against RequestOptions while preserving literal types.
|
|
829
|
+
*/
|
|
830
|
+
const defaultRequestOptions = { method: HTTP_METHODS.GET };
|
|
831
|
+
/**
|
|
832
|
+
* Main request function
|
|
833
|
+
*/
|
|
834
|
+
async function request(endpoint, options = {}) {
|
|
835
|
+
const { method = defaultRequestOptions.method, headers: customHeaders, params, body, signal } = options;
|
|
836
|
+
const url = buildUrl(endpoint, method === HTTP_METHODS.GET ? params : void 0);
|
|
837
|
+
const headers = buildHeaders(customHeaders);
|
|
838
|
+
let response;
|
|
1420
839
|
try {
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
840
|
+
const fetchOptions = {
|
|
841
|
+
method,
|
|
842
|
+
headers,
|
|
843
|
+
credentials: "include"
|
|
844
|
+
};
|
|
845
|
+
if (signal !== void 0) fetchOptions.signal = signal;
|
|
846
|
+
if (body && method !== HTTP_METHODS.GET) fetchOptions.body = JSON.stringify(body);
|
|
847
|
+
response = await fetch(url, fetchOptions);
|
|
848
|
+
} catch (networkError) {
|
|
849
|
+
throw new ApiError(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
|
|
1425
850
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
* Get the active theme ID from a list of raw API themes.
|
|
1430
|
-
* Falls back to the first theme if none is marked active.
|
|
1431
|
-
*/
|
|
1432
|
-
function getActiveThemeId(themes) {
|
|
1433
|
-
const active = themes.find((t) => t.active) ?? themes[0];
|
|
1434
|
-
return active ? String(active.id) : void 0;
|
|
1435
|
-
}
|
|
1436
|
-
//#endregion
|
|
1437
|
-
//#region ../core/src/theme/theme-applicator.ts
|
|
1438
|
-
const STYLE_PREFIX = "theme-style-";
|
|
1439
|
-
const FONT_LINK_PREFIX = "theme-font-";
|
|
1440
|
-
const SYSTEM_FONTS = new Set([
|
|
1441
|
-
"sans-serif",
|
|
1442
|
-
"serif",
|
|
1443
|
-
"monospace",
|
|
1444
|
-
"cursive",
|
|
1445
|
-
"fantasy",
|
|
1446
|
-
"system-ui",
|
|
1447
|
-
"ui-sans-serif",
|
|
1448
|
-
"ui-serif",
|
|
1449
|
-
"ui-monospace"
|
|
1450
|
-
]);
|
|
1451
|
-
/** Build a Google Fonts CSS2 URL for a given font family with all weights. */
|
|
1452
|
-
function buildGoogleFontUrl(family) {
|
|
1453
|
-
return `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family).replace(/%20/g, "+")}:wght@100;200;300;400;500;600;700;800;900&display=swap`;
|
|
1454
|
-
}
|
|
1455
|
-
/** Check if a font family value needs to be loaded (i.e. is not a CSS var or system font). */
|
|
1456
|
-
function isLoadableFont(value) {
|
|
1457
|
-
if (!value) return false;
|
|
1458
|
-
if (value.startsWith("var(")) return false;
|
|
1459
|
-
return !SYSTEM_FONTS.has(value.toLowerCase());
|
|
1460
|
-
}
|
|
1461
|
-
/** Deterministic link element ID for a font family. */
|
|
1462
|
-
function fontLinkId(family) {
|
|
1463
|
-
return `${FONT_LINK_PREFIX}${family.replace(/\s+/g, "-").toLowerCase()}`;
|
|
1464
|
-
}
|
|
1465
|
-
/**
|
|
1466
|
-
* Inject or update `<link>` elements for Google Fonts used by the theme.
|
|
1467
|
-
* Removes links for fonts that are no longer referenced.
|
|
1468
|
-
*/
|
|
1469
|
-
function loadThemeFonts(theme) {
|
|
1470
|
-
if (typeof document === "undefined") return;
|
|
1471
|
-
const fontsToLoad = /* @__PURE__ */ new Set();
|
|
1472
|
-
for (const key of FONT_FAMILY_KEYS) {
|
|
1473
|
-
const value = theme.fontFamilies[key];
|
|
1474
|
-
if (isLoadableFont(value)) fontsToLoad.add(value);
|
|
1475
|
-
}
|
|
1476
|
-
document.querySelectorAll(`link[id^="${FONT_LINK_PREFIX}"]`).forEach((link) => {
|
|
1477
|
-
const owners = link.getAttribute("data-font-theme-ids")?.split(",") ?? [];
|
|
1478
|
-
if (!owners.includes(theme.id)) return;
|
|
1479
|
-
const fontName = link.getAttribute("data-font-family");
|
|
1480
|
-
if (fontName && !fontsToLoad.has(fontName)) {
|
|
1481
|
-
const remaining = owners.filter((id) => id !== theme.id);
|
|
1482
|
-
if (remaining.length === 0) link.remove();
|
|
1483
|
-
else link.setAttribute("data-font-theme-ids", remaining.join(","));
|
|
851
|
+
if (response.status === 401) {
|
|
852
|
+
onAuthError?.();
|
|
853
|
+
throw new ApiError("Authentication required", 401, null);
|
|
1484
854
|
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
if (
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
855
|
+
if (!response.ok) try {
|
|
856
|
+
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
857
|
+
const data = await response.json();
|
|
858
|
+
throw new ApiError(extractErrorMessage(data, `${method} request failed`), response.status, "errors" in data ? data.errors : data);
|
|
859
|
+
} else throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null);
|
|
860
|
+
} catch (error) {
|
|
861
|
+
if (isApiError(error)) throw error;
|
|
862
|
+
throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null);
|
|
863
|
+
}
|
|
864
|
+
if (response.status === 204 || response.headers.get("content-length") === "0") return null;
|
|
865
|
+
try {
|
|
866
|
+
const raw = await response.json();
|
|
867
|
+
if (raw === null || raw === void 0) throw new ApiError("Unexpected null/undefined in JSON response", response.status, null);
|
|
868
|
+
return isApiEnvelope(raw) ? raw.data : raw;
|
|
869
|
+
} catch (parseError) {
|
|
870
|
+
if (isApiError(parseError)) throw parseError;
|
|
871
|
+
throw new ApiError("Failed to parse response as JSON", response.status, null);
|
|
1500
872
|
}
|
|
1501
873
|
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
*
|
|
1511
|
-
*
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
874
|
+
/**
|
|
875
|
+
* Request function for endpoints that may return null (204 No Content).
|
|
876
|
+
* Properly types the return as T | null.
|
|
877
|
+
*/
|
|
878
|
+
async function requestNullable(endpoint, options = {}) {
|
|
879
|
+
return request(endpoint, options);
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Safe request wrapper that returns a discriminated union instead of throwing.
|
|
883
|
+
* Use `isApiSuccess` or `isApiFailure` to narrow the result.
|
|
884
|
+
*/
|
|
885
|
+
async function safeRequest(endpoint, options = {}) {
|
|
886
|
+
try {
|
|
887
|
+
return {
|
|
888
|
+
success: true,
|
|
889
|
+
data: await request(endpoint, options)
|
|
890
|
+
};
|
|
891
|
+
} catch (error) {
|
|
892
|
+
if (isApiError(error)) return {
|
|
893
|
+
success: false,
|
|
894
|
+
error
|
|
895
|
+
};
|
|
896
|
+
return {
|
|
897
|
+
success: false,
|
|
898
|
+
error: new ApiError(error instanceof Error ? error.message : "Unknown error", 0, null)
|
|
899
|
+
};
|
|
1525
900
|
}
|
|
1526
|
-
el.textContent = generateThemeCSS(theme, options);
|
|
1527
|
-
} catch (error) {
|
|
1528
|
-
console.error(`[theme] applyTheme failed for "${theme.id}":`, error);
|
|
1529
901
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
902
|
+
/**
|
|
903
|
+
* Helper to safely convert typed params to Record<string, unknown>.
|
|
904
|
+
* Type assertion required: TypeScript's structural typing allows any object
|
|
905
|
+
* to be treated as Record<string, unknown> when we only need to iterate
|
|
906
|
+
* over its entries. This is safe because buildUrl only reads properties.
|
|
907
|
+
*/
|
|
908
|
+
function toParams(params) {
|
|
909
|
+
return params;
|
|
910
|
+
}
|
|
911
|
+
const get = (endpoint, params, options) => {
|
|
912
|
+
const baseOptions = {
|
|
913
|
+
...options,
|
|
914
|
+
method: HTTP_METHODS.GET
|
|
915
|
+
};
|
|
916
|
+
const convertedParams = toParams(params);
|
|
917
|
+
return request(endpoint, convertedParams !== void 0 ? {
|
|
918
|
+
...baseOptions,
|
|
919
|
+
params: convertedParams
|
|
920
|
+
} : baseOptions);
|
|
921
|
+
};
|
|
922
|
+
const post = (endpoint, body, options) => request(endpoint, {
|
|
923
|
+
...options,
|
|
924
|
+
method: HTTP_METHODS.POST,
|
|
925
|
+
body
|
|
1539
926
|
});
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
927
|
+
const put = (endpoint, body, options) => request(endpoint, {
|
|
928
|
+
...options,
|
|
929
|
+
method: HTTP_METHODS.PUT,
|
|
930
|
+
body
|
|
931
|
+
});
|
|
932
|
+
const patch = (endpoint, body, options) => request(endpoint, {
|
|
933
|
+
...options,
|
|
934
|
+
method: HTTP_METHODS.PATCH,
|
|
935
|
+
body
|
|
936
|
+
});
|
|
937
|
+
const del = (endpoint, options) => request(endpoint, {
|
|
938
|
+
...options,
|
|
939
|
+
method: HTTP_METHODS.DELETE
|
|
940
|
+
});
|
|
941
|
+
return {
|
|
942
|
+
fetchClient,
|
|
943
|
+
request,
|
|
944
|
+
requestNullable,
|
|
945
|
+
safeRequest,
|
|
946
|
+
get,
|
|
947
|
+
post,
|
|
948
|
+
put,
|
|
949
|
+
patch,
|
|
950
|
+
delete: del
|
|
951
|
+
};
|
|
1546
952
|
}
|
|
1547
953
|
//#endregion
|
|
1548
|
-
//#region src/
|
|
954
|
+
//#region ../../api-clients/fluidos/src/namespaces/fluid_os.ts
|
|
1549
955
|
/**
|
|
1550
|
-
*
|
|
1551
|
-
*
|
|
956
|
+
* Get active Fluid OS definition
|
|
957
|
+
* Retrieve the active Fluid OS definition manifest for a specific platform
|
|
958
|
+
*
|
|
959
|
+
* @param client - Fetch client instance
|
|
960
|
+
* @param params - params
|
|
1552
961
|
*/
|
|
1553
|
-
function
|
|
1554
|
-
|
|
1555
|
-
if (Array.isArray(componentTree)) return componentTree;
|
|
1556
|
-
if (typeof componentTree === "object") return [componentTree];
|
|
1557
|
-
return [];
|
|
962
|
+
async function getFluidOSManifest(client, params) {
|
|
963
|
+
return client.get(`/api/fluid_os/definitions/active`, params);
|
|
1558
964
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
965
|
+
//#endregion
|
|
966
|
+
//#region src/adapters/fluidos-api-adapter.ts
|
|
967
|
+
/** Create a FluidOsReadApi adapter backed by a FetchClient. */
|
|
968
|
+
function createFluidOsReadAdapter(client) {
|
|
969
|
+
return { getManifest: (params) => getFluidOSManifest(client, params) };
|
|
970
|
+
}
|
|
971
|
+
//#endregion
|
|
972
|
+
//#region src/adapters/app-definition-api-adapter.ts
|
|
973
|
+
function mapDefinition(raw) {
|
|
1564
974
|
return {
|
|
1565
|
-
id:
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
975
|
+
id: raw.id ?? 0,
|
|
976
|
+
name: raw.name ?? "",
|
|
977
|
+
version: raw.version ?? null,
|
|
978
|
+
components: raw.components ?? [],
|
|
979
|
+
active: raw.active ?? false
|
|
1569
980
|
};
|
|
1570
981
|
}
|
|
1571
|
-
//#endregion
|
|
1572
|
-
//#region src/transforms/navigation-transforms.ts
|
|
1573
982
|
/**
|
|
1574
|
-
*
|
|
1575
|
-
*
|
|
983
|
+
* Creates an AppDefinitionApi adapter backed by the portal-tenant BFF client.
|
|
984
|
+
*
|
|
985
|
+
* Maps the generated portal-tenant `app_definition_show` response to the
|
|
986
|
+
* `AppDefinitionApi` port, applying runtime defaults for optional BFF fields
|
|
987
|
+
* so TypeScript catches schema drift at compile time via `satisfies`.
|
|
1576
988
|
*/
|
|
1577
|
-
function
|
|
1578
|
-
|
|
989
|
+
function createAppDefinitionApiAdapter(client) {
|
|
990
|
+
return { fetchDefinition: async () => {
|
|
991
|
+
const response = await require_portal_tenant.app_definition_show(client);
|
|
992
|
+
return {
|
|
993
|
+
definition: mapDefinition(response.definition ?? {}),
|
|
994
|
+
meta: {
|
|
995
|
+
request_id: response.meta?.request_id ?? "",
|
|
996
|
+
timestamp: response.meta?.timestamp ?? ""
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
} };
|
|
1000
|
+
}
|
|
1001
|
+
//#endregion
|
|
1002
|
+
//#region src/adapters/account-api-adapter.ts
|
|
1003
|
+
function mapAccount(raw) {
|
|
1579
1004
|
return {
|
|
1580
|
-
id:
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1005
|
+
id: raw.id ?? 0,
|
|
1006
|
+
member_type: raw.member_type ?? "rep",
|
|
1007
|
+
first_name: raw.first_name ?? "",
|
|
1008
|
+
last_name: raw.last_name ?? "",
|
|
1009
|
+
email: raw.email ?? "",
|
|
1010
|
+
phone: raw.phone ?? null,
|
|
1011
|
+
bio: raw.bio ?? null,
|
|
1012
|
+
avatar_url: raw.avatar_url ?? null,
|
|
1013
|
+
slug: raw.slug ?? "",
|
|
1014
|
+
social_links: raw.social_links ?? null
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
function createAccountApiAdapter(client) {
|
|
1018
|
+
return {
|
|
1019
|
+
fetchAccount: async () => {
|
|
1020
|
+
const response = await require_portal_tenant.account_show(client);
|
|
1021
|
+
return {
|
|
1022
|
+
account: mapAccount(response.account ?? {}),
|
|
1023
|
+
meta: {
|
|
1024
|
+
request_id: response.meta?.request_id ?? "",
|
|
1025
|
+
timestamp: response.meta?.timestamp ?? ""
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
},
|
|
1029
|
+
updateAccount: async (body) => {
|
|
1030
|
+
const response = await require_portal_tenant.account_update(client, body);
|
|
1031
|
+
return {
|
|
1032
|
+
account: mapAccount(response.account ?? {}),
|
|
1033
|
+
meta: {
|
|
1034
|
+
request_id: response.meta?.request_id ?? "",
|
|
1035
|
+
timestamp: response.meta?.timestamp ?? ""
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1589
1039
|
};
|
|
1590
1040
|
}
|
|
1591
1041
|
//#endregion
|
|
1592
|
-
//#region src/
|
|
1042
|
+
//#region ../../api-clients/portal-tenant-pay/src/namespaces/portal_tenant_pay.ts
|
|
1593
1043
|
/**
|
|
1594
|
-
*
|
|
1044
|
+
* List addresses
|
|
1045
|
+
* Returns addresses associated with the member's customer record in this tenant.
|
|
1595
1046
|
*
|
|
1596
|
-
*
|
|
1597
|
-
*
|
|
1598
|
-
* This function bridges that gap so callers avoid `as unknown as` casts.
|
|
1047
|
+
* @param client - Fetch client instance
|
|
1048
|
+
* @param [params] - params
|
|
1599
1049
|
*/
|
|
1600
|
-
function
|
|
1601
|
-
|
|
1602
|
-
return raw;
|
|
1050
|
+
async function addresses_list(client, params) {
|
|
1051
|
+
return client.get(`/api/pay/addresses`, params);
|
|
1603
1052
|
}
|
|
1604
1053
|
/**
|
|
1605
|
-
*
|
|
1054
|
+
* Create an address
|
|
1055
|
+
* Adds a new address to the member's customer record. If an identical address already exists it is returned instead of creating a duplicate.
|
|
1606
1056
|
*
|
|
1607
|
-
*
|
|
1608
|
-
*
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
*/
|
|
1613
|
-
function transformManifestToRepAppData(response) {
|
|
1614
|
-
const manifest = response.manifest;
|
|
1615
|
-
const rawProfile = manifest.profile;
|
|
1616
|
-
const rawThemes = Array.isArray(rawProfile?.themes) ? rawProfile.themes : [];
|
|
1617
|
-
const screens = (manifest.screens ?? []).map((screen) => toScreenDefinition(screen));
|
|
1618
|
-
const navigationItems = (rawProfile?.navigation?.navigation_items ?? []).map(toNavigationItem);
|
|
1619
|
-
const nav = rawProfile?.navigation;
|
|
1620
|
-
const mobileNav = rawProfile?.mobile_navigation;
|
|
1621
|
-
const mobileNavigationItems = (mobileNav?.navigation_items ?? []).map(toNavigationItem);
|
|
1622
|
-
const activeThemeId = getActiveThemeId(rawThemes);
|
|
1623
|
-
return {
|
|
1624
|
-
definition_id: manifest.definition_id,
|
|
1625
|
-
published_version: manifest.published_version ?? 0,
|
|
1626
|
-
screens,
|
|
1627
|
-
profile: {
|
|
1628
|
-
name: rawProfile?.name ?? "Default",
|
|
1629
|
-
definition_id: rawProfile?.definition_id ?? manifest.definition_id,
|
|
1630
|
-
themes: transformThemes(rawThemes),
|
|
1631
|
-
...activeThemeId !== void 0 ? { activeThemeId } : {},
|
|
1632
|
-
navigation: {
|
|
1633
|
-
definition_id: nav?.definition_id ?? manifest.definition_id,
|
|
1634
|
-
id: nav?.id ?? 0,
|
|
1635
|
-
name: nav?.name ?? "Main Navigation",
|
|
1636
|
-
navigation_items: navigationItems,
|
|
1637
|
-
screens
|
|
1638
|
-
},
|
|
1639
|
-
...mobileNav ? { mobile_navigation: {
|
|
1640
|
-
definition_id: mobileNav.definition_id ?? manifest.definition_id,
|
|
1641
|
-
id: mobileNav.id ?? 0,
|
|
1642
|
-
name: mobileNav.name ?? "Mobile Navigation",
|
|
1643
|
-
navigation_items: mobileNavigationItems,
|
|
1644
|
-
screens
|
|
1645
|
-
} } : {}
|
|
1646
|
-
}
|
|
1647
|
-
};
|
|
1057
|
+
* @param client - Fetch client instance
|
|
1058
|
+
* @param body - body
|
|
1059
|
+
*/
|
|
1060
|
+
async function addresses_create(client, body) {
|
|
1061
|
+
return client.post(`/api/pay/addresses`, body);
|
|
1648
1062
|
}
|
|
1649
|
-
//#endregion
|
|
1650
|
-
//#region src/client/fluid-client.ts
|
|
1651
1063
|
/**
|
|
1652
|
-
*
|
|
1064
|
+
* Update an address
|
|
1065
|
+
* Creates a new address with the merged attributes and discards the old one, preserving references from existing orders.
|
|
1066
|
+
*
|
|
1067
|
+
* @param client - Fetch client instance
|
|
1068
|
+
* @param id - id
|
|
1069
|
+
* @param body - body
|
|
1653
1070
|
*/
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
constructor(message, status, data) {
|
|
1658
|
-
super(message);
|
|
1659
|
-
this.name = "ApiError";
|
|
1660
|
-
this.status = status;
|
|
1661
|
-
this.data = data;
|
|
1662
|
-
const errorWithCapture = Error;
|
|
1663
|
-
if (errorWithCapture.captureStackTrace) errorWithCapture.captureStackTrace(this, ApiError);
|
|
1664
|
-
}
|
|
1665
|
-
toJSON() {
|
|
1666
|
-
return {
|
|
1667
|
-
name: this.name,
|
|
1668
|
-
message: this.message,
|
|
1669
|
-
status: this.status,
|
|
1670
|
-
data: this.data
|
|
1671
|
-
};
|
|
1672
|
-
}
|
|
1673
|
-
};
|
|
1071
|
+
async function addresses_update(client, id, body) {
|
|
1072
|
+
return client.patch(`/api/pay/addresses/${id}`, body);
|
|
1073
|
+
}
|
|
1674
1074
|
/**
|
|
1675
|
-
*
|
|
1075
|
+
* Delete an address
|
|
1076
|
+
* Removes an address from the member's customer record. The default address cannot be removed.
|
|
1077
|
+
*
|
|
1078
|
+
* @param client - Fetch client instance
|
|
1079
|
+
* @param id - id
|
|
1676
1080
|
*/
|
|
1677
|
-
function
|
|
1678
|
-
return
|
|
1081
|
+
async function addresses_destroy(client, id) {
|
|
1082
|
+
return client.delete(`/api/pay/addresses/${id}`);
|
|
1679
1083
|
}
|
|
1680
1084
|
/**
|
|
1681
|
-
*
|
|
1085
|
+
* List payment methods
|
|
1086
|
+
* Returns displayable payment methods on the member's customer record, excluding Apple Pay sources.
|
|
1087
|
+
*
|
|
1088
|
+
* @param client - Fetch client instance
|
|
1089
|
+
* @param [params] - params
|
|
1682
1090
|
*/
|
|
1683
|
-
function
|
|
1684
|
-
return
|
|
1091
|
+
async function payment_methods_list(client, params) {
|
|
1092
|
+
return client.get(`/api/pay/payment_methods`, params);
|
|
1685
1093
|
}
|
|
1686
1094
|
/**
|
|
1687
|
-
*
|
|
1688
|
-
*
|
|
1095
|
+
* Create a payment method
|
|
1096
|
+
* Tokenizes and stores a new payment method via the vault provider. Requires a vault token obtained from the vault credentials endpoint.
|
|
1097
|
+
*
|
|
1098
|
+
* @param client - Fetch client instance
|
|
1099
|
+
* @param body - body
|
|
1689
1100
|
*/
|
|
1690
|
-
function
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1101
|
+
async function payment_methods_create(client, body) {
|
|
1102
|
+
return client.post(`/api/pay/payment_methods`, body);
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Update a payment method
|
|
1106
|
+
* Updates a payment method's attributes. Currently supports setting a payment method as the default.
|
|
1107
|
+
*
|
|
1108
|
+
* @param client - Fetch client instance
|
|
1109
|
+
* @param id - id
|
|
1110
|
+
* @param body - body
|
|
1111
|
+
*/
|
|
1112
|
+
async function payment_methods_update(client, id, body) {
|
|
1113
|
+
return client.patch(`/api/pay/payment_methods/${id}`, body);
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Delete a payment method
|
|
1117
|
+
* Removes a payment method from the member's customer record. If the removed method was the default, the default is cleared.
|
|
1118
|
+
*
|
|
1119
|
+
* @param client - Fetch client instance
|
|
1120
|
+
* @param id - id
|
|
1121
|
+
*/
|
|
1122
|
+
async function payment_methods_destroy(client, id) {
|
|
1123
|
+
return client.delete(`/api/pay/payment_methods/${id}`);
|
|
1695
1124
|
}
|
|
1696
1125
|
/**
|
|
1697
|
-
*
|
|
1698
|
-
*
|
|
1126
|
+
* Get vault credentials
|
|
1127
|
+
* Returns a short-lived vault token and environment identifier for initializing the client-side payment vault SDK.
|
|
1128
|
+
*
|
|
1129
|
+
* @param client - Fetch client instance
|
|
1130
|
+
|
|
1699
1131
|
*/
|
|
1700
|
-
function
|
|
1701
|
-
return
|
|
1132
|
+
async function payment_methods_vault_show(client) {
|
|
1133
|
+
return client.get(`/api/pay/payment_methods/vault`);
|
|
1702
1134
|
}
|
|
1703
1135
|
/**
|
|
1704
|
-
*
|
|
1136
|
+
* List points ledger entries
|
|
1137
|
+
* Returns loyalty points ledger entries for the member's customer record in this tenant, ordered by most recent first.
|
|
1138
|
+
*
|
|
1139
|
+
* @param client - Fetch client instance
|
|
1140
|
+
* @param [params] - params
|
|
1705
1141
|
*/
|
|
1706
|
-
function
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
}
|
|
1743
|
-
return url.toString();
|
|
1744
|
-
}
|
|
1745
|
-
/**
|
|
1746
|
-
* Default request options for type-safe defaults.
|
|
1747
|
-
* Uses `satisfies` to validate against RequestOptions while preserving literal types.
|
|
1748
|
-
*/
|
|
1749
|
-
const defaultRequestOptions = { method: HTTP_METHODS.GET };
|
|
1750
|
-
/**
|
|
1751
|
-
* Main request function
|
|
1752
|
-
*/
|
|
1753
|
-
async function request(endpoint, options = {}) {
|
|
1754
|
-
const { method = defaultRequestOptions.method, headers: customHeaders, params, body, signal } = options;
|
|
1755
|
-
const url = buildUrl(endpoint, method === HTTP_METHODS.GET ? params : void 0);
|
|
1756
|
-
const headers = buildHeaders(customHeaders);
|
|
1757
|
-
let response;
|
|
1758
|
-
try {
|
|
1759
|
-
const fetchOptions = {
|
|
1760
|
-
method,
|
|
1761
|
-
headers,
|
|
1762
|
-
credentials: "include"
|
|
1763
|
-
};
|
|
1764
|
-
if (signal !== void 0) fetchOptions.signal = signal;
|
|
1765
|
-
if (body && method !== HTTP_METHODS.GET) fetchOptions.body = JSON.stringify(body);
|
|
1766
|
-
response = await fetch(url, fetchOptions);
|
|
1767
|
-
} catch (networkError) {
|
|
1768
|
-
throw new ApiError(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
|
|
1769
|
-
}
|
|
1770
|
-
if (response.status === 401) {
|
|
1771
|
-
onAuthError?.();
|
|
1772
|
-
throw new ApiError("Authentication required", 401, null);
|
|
1773
|
-
}
|
|
1774
|
-
if (!response.ok) try {
|
|
1775
|
-
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
1776
|
-
const data = await response.json();
|
|
1777
|
-
throw new ApiError(extractErrorMessage(data, `${method} request failed`), response.status, "errors" in data ? data.errors : data);
|
|
1778
|
-
} else throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null);
|
|
1779
|
-
} catch (error) {
|
|
1780
|
-
if (isApiError(error)) throw error;
|
|
1781
|
-
throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null);
|
|
1782
|
-
}
|
|
1783
|
-
if (response.status === 204 || response.headers.get("content-length") === "0") return null;
|
|
1784
|
-
try {
|
|
1785
|
-
const raw = await response.json();
|
|
1786
|
-
if (raw === null || raw === void 0) throw new ApiError("Unexpected null/undefined in JSON response", response.status, null);
|
|
1787
|
-
return isApiEnvelope(raw) ? raw.data : raw;
|
|
1788
|
-
} catch (parseError) {
|
|
1789
|
-
if (isApiError(parseError)) throw parseError;
|
|
1790
|
-
throw new ApiError("Failed to parse response as JSON", response.status, null);
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
/**
|
|
1794
|
-
* Request function for endpoints that may return null (204 No Content).
|
|
1795
|
-
* Properly types the return as T | null.
|
|
1796
|
-
*/
|
|
1797
|
-
async function requestNullable(endpoint, options = {}) {
|
|
1798
|
-
return request(endpoint, options);
|
|
1799
|
-
}
|
|
1800
|
-
/**
|
|
1801
|
-
* Safe request wrapper that returns a discriminated union instead of throwing.
|
|
1802
|
-
* Use `isApiSuccess` or `isApiFailure` to narrow the result.
|
|
1803
|
-
*/
|
|
1804
|
-
async function safeRequest(endpoint, options = {}) {
|
|
1805
|
-
try {
|
|
1142
|
+
async function points_ledgers_list(client, params) {
|
|
1143
|
+
return client.get(`/api/pay/points_ledgers`, params);
|
|
1144
|
+
}
|
|
1145
|
+
//#endregion
|
|
1146
|
+
//#region src/adapters/pay-api-adapter.ts
|
|
1147
|
+
function mapAddress(raw) {
|
|
1148
|
+
return {
|
|
1149
|
+
id: raw.id ?? 0,
|
|
1150
|
+
street1: raw.street1 ?? "",
|
|
1151
|
+
street2: raw.street2 ?? null,
|
|
1152
|
+
city: raw.city ?? "",
|
|
1153
|
+
state: raw.state ?? "",
|
|
1154
|
+
zip: raw.zip ?? "",
|
|
1155
|
+
country: raw.country ?? "",
|
|
1156
|
+
default: raw.default ?? false,
|
|
1157
|
+
created_at: raw.created_at ?? null,
|
|
1158
|
+
updated_at: raw.updated_at ?? null
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function mapPaymentMethod(raw) {
|
|
1162
|
+
return {
|
|
1163
|
+
id: raw.id ?? 0,
|
|
1164
|
+
type: raw.type ?? "card",
|
|
1165
|
+
brand: raw.brand ?? null,
|
|
1166
|
+
last_four: raw.last_four ?? "",
|
|
1167
|
+
exp_month: raw.exp_month ?? null,
|
|
1168
|
+
exp_year: raw.exp_year ?? null,
|
|
1169
|
+
default: raw.default ?? false,
|
|
1170
|
+
created_at: raw.created_at ?? null,
|
|
1171
|
+
updated_at: raw.updated_at ?? null
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
function createPortalTenantPayAdapter(client) {
|
|
1175
|
+
return {
|
|
1176
|
+
fetchAddresses: async () => {
|
|
1177
|
+
const response = await addresses_list(client);
|
|
1806
1178
|
return {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
success: false,
|
|
1813
|
-
error
|
|
1179
|
+
addresses: (response.addresses ?? []).map(mapAddress),
|
|
1180
|
+
meta: {
|
|
1181
|
+
request_id: response.meta?.request_id ?? "",
|
|
1182
|
+
timestamp: response.meta?.timestamp ?? ""
|
|
1183
|
+
}
|
|
1814
1184
|
};
|
|
1185
|
+
},
|
|
1186
|
+
createAddress: async (body) => {
|
|
1187
|
+
await addresses_create(client, body);
|
|
1188
|
+
},
|
|
1189
|
+
updateAddress: async (addressId, body) => {
|
|
1190
|
+
await addresses_update(client, addressId, body);
|
|
1191
|
+
},
|
|
1192
|
+
deleteAddress: async (addressId) => {
|
|
1193
|
+
await addresses_destroy(client, addressId);
|
|
1194
|
+
},
|
|
1195
|
+
fetchPaymentMethods: async () => {
|
|
1196
|
+
const response = await payment_methods_list(client);
|
|
1815
1197
|
return {
|
|
1816
|
-
|
|
1817
|
-
|
|
1198
|
+
payment_methods: (response.payment_methods ?? []).map(mapPaymentMethod),
|
|
1199
|
+
meta: {
|
|
1200
|
+
request_id: response.meta?.request_id ?? "",
|
|
1201
|
+
timestamp: response.meta?.timestamp ?? ""
|
|
1202
|
+
}
|
|
1818
1203
|
};
|
|
1819
|
-
}
|
|
1820
|
-
}
|
|
1821
|
-
/**
|
|
1822
|
-
* Helper to safely convert typed params to Record<string, unknown>.
|
|
1823
|
-
* Type assertion required: TypeScript's structural typing allows any object
|
|
1824
|
-
* to be treated as Record<string, unknown> when we only need to iterate
|
|
1825
|
-
* over its entries. This is safe because buildUrl only reads properties.
|
|
1826
|
-
*/
|
|
1827
|
-
function toParams(params) {
|
|
1828
|
-
return params;
|
|
1829
|
-
}
|
|
1830
|
-
const get = (endpoint, params, options) => {
|
|
1831
|
-
const baseOptions = {
|
|
1832
|
-
...options,
|
|
1833
|
-
method: HTTP_METHODS.GET
|
|
1834
|
-
};
|
|
1835
|
-
const convertedParams = toParams(params);
|
|
1836
|
-
return request(endpoint, convertedParams !== void 0 ? {
|
|
1837
|
-
...baseOptions,
|
|
1838
|
-
params: convertedParams
|
|
1839
|
-
} : baseOptions);
|
|
1840
|
-
};
|
|
1841
|
-
const post = (endpoint, body, options) => request(endpoint, {
|
|
1842
|
-
...options,
|
|
1843
|
-
method: HTTP_METHODS.POST,
|
|
1844
|
-
body
|
|
1845
|
-
});
|
|
1846
|
-
const put = (endpoint, body, options) => request(endpoint, {
|
|
1847
|
-
...options,
|
|
1848
|
-
method: HTTP_METHODS.PUT,
|
|
1849
|
-
body
|
|
1850
|
-
});
|
|
1851
|
-
const patch = (endpoint, body, options) => request(endpoint, {
|
|
1852
|
-
...options,
|
|
1853
|
-
method: HTTP_METHODS.PATCH,
|
|
1854
|
-
body
|
|
1855
|
-
});
|
|
1856
|
-
const del = (endpoint, options) => request(endpoint, {
|
|
1857
|
-
...options,
|
|
1858
|
-
method: HTTP_METHODS.DELETE
|
|
1859
|
-
});
|
|
1860
|
-
return {
|
|
1861
|
-
fetchClient,
|
|
1862
|
-
request,
|
|
1863
|
-
requestNullable,
|
|
1864
|
-
safeRequest,
|
|
1865
|
-
get,
|
|
1866
|
-
post,
|
|
1867
|
-
put,
|
|
1868
|
-
patch,
|
|
1869
|
-
delete: del,
|
|
1870
|
-
reps: {
|
|
1871
|
-
current: () => get("/api/reps/me"),
|
|
1872
|
-
updateProfile: (data) => patch("/api/reps/me", data)
|
|
1873
1204
|
},
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1205
|
+
createPaymentMethod: async (body) => {
|
|
1206
|
+
await payment_methods_create(client, body);
|
|
1207
|
+
},
|
|
1208
|
+
updatePaymentMethod: async (paymentMethodId, body) => {
|
|
1209
|
+
await payment_methods_update(client, paymentMethodId, body);
|
|
1210
|
+
},
|
|
1211
|
+
deletePaymentMethod: async (paymentMethodId) => {
|
|
1212
|
+
await payment_methods_destroy(client, paymentMethodId);
|
|
1213
|
+
},
|
|
1214
|
+
fetchVaultCredentials: async () => {
|
|
1215
|
+
const response = await payment_methods_vault_show(client);
|
|
1216
|
+
return {
|
|
1217
|
+
vault: {
|
|
1218
|
+
token: response.vault?.token ?? null,
|
|
1219
|
+
environment: response.vault?.environment ?? "production"
|
|
1220
|
+
},
|
|
1221
|
+
meta: {
|
|
1222
|
+
request_id: response.meta?.request_id ?? "",
|
|
1223
|
+
timestamp: response.meta?.timestamp ?? ""
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1881
1226
|
},
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1227
|
+
fetchPointsLedgers: async () => {
|
|
1228
|
+
const response = await points_ledgers_list(client);
|
|
1229
|
+
return {
|
|
1230
|
+
points_ledgers: (response.points_ledgers ?? []).map((entry) => ({
|
|
1231
|
+
id: entry.id ?? 0,
|
|
1232
|
+
amount: entry.amount ?? 0,
|
|
1233
|
+
total_balance: entry.total_balance ?? 0,
|
|
1234
|
+
metadata: entry.metadata ?? null,
|
|
1235
|
+
created_at: entry.created_at ?? ""
|
|
1236
|
+
})),
|
|
1237
|
+
meta: {
|
|
1238
|
+
request_id: response.meta?.request_id ?? "",
|
|
1239
|
+
timestamp: response.meta?.timestamp ?? ""
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1886
1242
|
}
|
|
1887
1243
|
};
|
|
1888
1244
|
}
|
|
1889
1245
|
//#endregion
|
|
1890
|
-
//#region src/
|
|
1891
|
-
/**
|
|
1892
|
-
|
|
1893
|
-
|
|
1246
|
+
//#region ../../api-clients/portal-tenant-store/src/namespaces/portal_tenant_store.ts
|
|
1247
|
+
/**
|
|
1248
|
+
* List available countries
|
|
1249
|
+
* Returns countries enabled for the tenant store, each with its ISO 3166 subdivisions (states/provinces).
|
|
1250
|
+
*
|
|
1251
|
+
* @param client - Fetch client instance
|
|
1252
|
+
* @param [params] - params
|
|
1253
|
+
*/
|
|
1254
|
+
async function countries_list(client, params) {
|
|
1255
|
+
return client.get(`/api/store/countries`, params);
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* List available languages
|
|
1259
|
+
* Returns languages enabled for the tenant store, sorted by name.
|
|
1260
|
+
*
|
|
1261
|
+
* @param client - Fetch client instance
|
|
1262
|
+
* @param [params] - params
|
|
1263
|
+
*/
|
|
1264
|
+
async function languages_list(client, params) {
|
|
1265
|
+
return client.get(`/api/store/languages`, params);
|
|
1894
1266
|
}
|
|
1895
1267
|
//#endregion
|
|
1896
|
-
//#region src/
|
|
1897
|
-
|
|
1268
|
+
//#region ../../store/api-client/src/portal-tenant-countries-adapter.ts
|
|
1269
|
+
/**
|
|
1270
|
+
* Maps a BFF state to the port's State shape.
|
|
1271
|
+
*/
|
|
1272
|
+
function mapState(raw) {
|
|
1898
1273
|
return {
|
|
1899
|
-
|
|
1274
|
+
code: raw.code ?? "",
|
|
1275
|
+
name: raw.name ?? ""
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Maps a BFF country to the port's Country shape.
|
|
1280
|
+
*/
|
|
1281
|
+
function mapCountry(raw) {
|
|
1282
|
+
return {
|
|
1283
|
+
code: raw.code ?? "",
|
|
1900
1284
|
name: raw.name ?? "",
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
active: raw.active ?? false
|
|
1285
|
+
currency_code: raw.currency_code ?? "",
|
|
1286
|
+
states: (raw.states ?? []).map(mapState)
|
|
1904
1287
|
};
|
|
1905
1288
|
}
|
|
1906
1289
|
/**
|
|
1907
|
-
*
|
|
1290
|
+
* Maps the BFF meta envelope to the port's ApiMeta shape.
|
|
1291
|
+
*/
|
|
1292
|
+
function mapMeta$1(raw) {
|
|
1293
|
+
return {
|
|
1294
|
+
request_id: raw?.request_id ?? null,
|
|
1295
|
+
timestamp: raw?.timestamp ?? "",
|
|
1296
|
+
pagination: raw?.pagination ? {
|
|
1297
|
+
cursor: raw.pagination.cursor ?? null,
|
|
1298
|
+
limit: raw.pagination.limit,
|
|
1299
|
+
next_cursor: raw.pagination.next_cursor ?? null,
|
|
1300
|
+
prev_cursor: raw.pagination.prev_cursor ?? null
|
|
1301
|
+
} : void 0
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Creates a CountriesApi adapter backed by the portal-tenant store BFF.
|
|
1908
1306
|
*
|
|
1909
|
-
* Maps the generated portal-tenant
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
1307
|
+
* Maps the generated portal-tenant-store namespace functions to the abstract
|
|
1308
|
+
* CountriesApi port, closing over the FetchClient so consumers don't need
|
|
1309
|
+
* to pass it per-call.
|
|
1912
1310
|
*/
|
|
1913
|
-
function
|
|
1914
|
-
return {
|
|
1915
|
-
const response = await
|
|
1311
|
+
function createPortalTenantCountriesAdapter(client) {
|
|
1312
|
+
return { listCountries: async (params) => {
|
|
1313
|
+
const response = await countries_list(client, {
|
|
1314
|
+
"page[cursor]": params?.cursor,
|
|
1315
|
+
"page[limit]": params?.limit
|
|
1316
|
+
});
|
|
1916
1317
|
return {
|
|
1917
|
-
|
|
1918
|
-
meta:
|
|
1919
|
-
request_id: response.meta?.request_id ?? "",
|
|
1920
|
-
timestamp: response.meta?.timestamp ?? ""
|
|
1921
|
-
}
|
|
1318
|
+
countries: (response.countries ?? []).map(mapCountry),
|
|
1319
|
+
meta: mapMeta$1(response.meta)
|
|
1922
1320
|
};
|
|
1923
1321
|
} };
|
|
1924
1322
|
}
|
|
1925
1323
|
//#endregion
|
|
1926
|
-
//#region src/
|
|
1927
|
-
|
|
1324
|
+
//#region ../../store/api-client/src/portal-tenant-languages-adapter.ts
|
|
1325
|
+
/**
|
|
1326
|
+
* Maps a BFF language to the port's Language shape.
|
|
1327
|
+
*/
|
|
1328
|
+
function mapLanguage(raw) {
|
|
1928
1329
|
return {
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
first_name: raw.first_name ?? "",
|
|
1932
|
-
last_name: raw.last_name ?? "",
|
|
1933
|
-
email: raw.email ?? "",
|
|
1934
|
-
phone: raw.phone ?? null,
|
|
1935
|
-
bio: raw.bio ?? null,
|
|
1936
|
-
avatar_url: raw.avatar_url ?? null,
|
|
1937
|
-
slug: raw.slug ?? "",
|
|
1938
|
-
social_links: raw.social_links ?? null
|
|
1330
|
+
code: raw.code ?? "",
|
|
1331
|
+
name: raw.name ?? ""
|
|
1939
1332
|
};
|
|
1940
1333
|
}
|
|
1941
|
-
|
|
1334
|
+
/**
|
|
1335
|
+
* Maps the BFF meta envelope to the port's ApiMeta shape.
|
|
1336
|
+
*/
|
|
1337
|
+
function mapMeta(raw) {
|
|
1942
1338
|
return {
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
};
|
|
1952
|
-
},
|
|
1953
|
-
updateAccount: async (body) => {
|
|
1954
|
-
const response = await require_portal_tenant.account_update(client, body);
|
|
1955
|
-
return {
|
|
1956
|
-
account: mapAccount(response.account ?? {}),
|
|
1957
|
-
meta: {
|
|
1958
|
-
request_id: response.meta?.request_id ?? "",
|
|
1959
|
-
timestamp: response.meta?.timestamp ?? ""
|
|
1960
|
-
}
|
|
1961
|
-
};
|
|
1962
|
-
}
|
|
1339
|
+
request_id: raw?.request_id ?? null,
|
|
1340
|
+
timestamp: raw?.timestamp ?? "",
|
|
1341
|
+
pagination: raw?.pagination ? {
|
|
1342
|
+
cursor: raw.pagination.cursor ?? null,
|
|
1343
|
+
limit: raw.pagination.limit,
|
|
1344
|
+
next_cursor: raw.pagination.next_cursor ?? null,
|
|
1345
|
+
prev_cursor: raw.pagination.prev_cursor ?? null
|
|
1346
|
+
} : void 0
|
|
1963
1347
|
};
|
|
1964
1348
|
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Creates a LanguagesApi adapter backed by the portal-tenant store BFF.
|
|
1351
|
+
*
|
|
1352
|
+
* Maps the generated portal-tenant-store namespace functions to the abstract
|
|
1353
|
+
* LanguagesApi port, closing over the FetchClient so consumers don't need
|
|
1354
|
+
* to pass it per-call.
|
|
1355
|
+
*/
|
|
1356
|
+
function createPortalTenantLanguagesAdapter(client) {
|
|
1357
|
+
return { listLanguages: async (params) => {
|
|
1358
|
+
const response = await languages_list(client, {
|
|
1359
|
+
"page[cursor]": params?.cursor,
|
|
1360
|
+
"page[limit]": params?.limit
|
|
1361
|
+
});
|
|
1362
|
+
return {
|
|
1363
|
+
languages: (response.languages ?? []).map(mapLanguage),
|
|
1364
|
+
meta: mapMeta(response.meta)
|
|
1365
|
+
};
|
|
1366
|
+
} };
|
|
1367
|
+
}
|
|
1965
1368
|
//#endregion
|
|
1966
|
-
//#region
|
|
1369
|
+
//#region src/adapters/countries-api-adapter.ts
|
|
1967
1370
|
/**
|
|
1968
|
-
*
|
|
1969
|
-
*
|
|
1371
|
+
* Creates a CountriesApi adapter backed by the portal-tenant store BFF.
|
|
1372
|
+
*
|
|
1373
|
+
* Delegates to the adapter factory in `@fluid-app/store-api-client` which
|
|
1374
|
+
* maps the generated portal-tenant-store namespace functions to the abstract
|
|
1375
|
+
* CountriesApi port.
|
|
1376
|
+
*/
|
|
1377
|
+
function createCountriesApiAdapter(client) {
|
|
1378
|
+
return createPortalTenantCountriesAdapter(client);
|
|
1379
|
+
}
|
|
1380
|
+
//#endregion
|
|
1381
|
+
//#region src/adapters/languages-api-adapter.ts
|
|
1382
|
+
/**
|
|
1383
|
+
* Creates a LanguagesApi adapter backed by the portal-tenant store BFF.
|
|
1384
|
+
*
|
|
1385
|
+
* Delegates to the adapter factory in `@fluid-app/store-api-client` which
|
|
1386
|
+
* maps the generated portal-tenant-store namespace functions to the abstract
|
|
1387
|
+
* LanguagesApi port.
|
|
1388
|
+
*/
|
|
1389
|
+
function createLanguagesApiAdapter(client) {
|
|
1390
|
+
return createPortalTenantLanguagesAdapter(client);
|
|
1391
|
+
}
|
|
1392
|
+
//#endregion
|
|
1393
|
+
//#region ../../api-clients/portal-tenant-mysite/src/namespaces/portal_tenant_mysite.ts
|
|
1394
|
+
/**
|
|
1395
|
+
* Get MySite profile for the current user
|
|
1396
|
+
* Returns the member's MySite profile.
|
|
1397
|
+
*
|
|
1398
|
+
* @param client - Fetch client instance
|
|
1399
|
+
|
|
1400
|
+
*/
|
|
1401
|
+
async function mysite_profile_show(client) {
|
|
1402
|
+
return client.get(`/api/mysite/profile`);
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Update MySite profile
|
|
1406
|
+
* Updates the member's MySite profile fields.
|
|
1407
|
+
*
|
|
1408
|
+
* @param client - Fetch client instance
|
|
1409
|
+
* @param body - body
|
|
1410
|
+
*/
|
|
1411
|
+
async function mysite_profile_update(client, body) {
|
|
1412
|
+
return client.put(`/api/mysite/profile`, body);
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* List available MySite themes
|
|
1416
|
+
* Returns available MySite themes.
|
|
1970
1417
|
*
|
|
1971
1418
|
* @param client - Fetch client instance
|
|
1972
1419
|
* @param [params] - params
|
|
1973
1420
|
*/
|
|
1974
|
-
async function
|
|
1975
|
-
return client.get(`/api/
|
|
1421
|
+
async function mysite_themes_list(client, params) {
|
|
1422
|
+
return client.get(`/api/mysite/themes`, params);
|
|
1976
1423
|
}
|
|
1977
1424
|
/**
|
|
1978
|
-
*
|
|
1979
|
-
*
|
|
1425
|
+
* Update MySite settings
|
|
1426
|
+
* Updates the member's MySite settings.
|
|
1980
1427
|
*
|
|
1981
1428
|
* @param client - Fetch client instance
|
|
1982
1429
|
* @param body - body
|
|
1983
1430
|
*/
|
|
1984
|
-
async function
|
|
1985
|
-
return client.
|
|
1431
|
+
async function mysite_settings_update(client, body) {
|
|
1432
|
+
return client.put(`/api/mysite/settings`, body);
|
|
1986
1433
|
}
|
|
1987
1434
|
/**
|
|
1988
|
-
*
|
|
1989
|
-
*
|
|
1435
|
+
* List MySite links for the current user
|
|
1436
|
+
* Returns the member's MySite links, ordered by position.
|
|
1437
|
+
*
|
|
1438
|
+
* @param client - Fetch client instance
|
|
1439
|
+
* @param [params] - params
|
|
1440
|
+
*/
|
|
1441
|
+
async function mysite_links_list(client, params) {
|
|
1442
|
+
return client.get(`/api/mysite/links`, params);
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Create a MySite link
|
|
1446
|
+
* Adds a new link to the member's MySite.
|
|
1990
1447
|
*
|
|
1991
1448
|
* @param client - Fetch client instance
|
|
1992
|
-
* @param id - id
|
|
1993
1449
|
* @param body - body
|
|
1994
1450
|
*/
|
|
1995
|
-
async function
|
|
1996
|
-
return client.
|
|
1451
|
+
async function mysite_links_create(client, body) {
|
|
1452
|
+
return client.post(`/api/mysite/links`, body);
|
|
1997
1453
|
}
|
|
1998
1454
|
/**
|
|
1999
|
-
*
|
|
2000
|
-
*
|
|
1455
|
+
* Update a MySite link
|
|
1456
|
+
* Updates an existing MySite link.
|
|
2001
1457
|
*
|
|
2002
1458
|
* @param client - Fetch client instance
|
|
2003
1459
|
* @param id - id
|
|
1460
|
+
* @param body - body
|
|
2004
1461
|
*/
|
|
2005
|
-
async function
|
|
2006
|
-
return client.
|
|
1462
|
+
async function mysite_links_update(client, id, body) {
|
|
1463
|
+
return client.put(`/api/mysite/links/${id}`, body);
|
|
2007
1464
|
}
|
|
2008
1465
|
/**
|
|
2009
|
-
*
|
|
2010
|
-
*
|
|
1466
|
+
* Delete a MySite link
|
|
1467
|
+
* Removes a link from the member's MySite.
|
|
2011
1468
|
*
|
|
2012
1469
|
* @param client - Fetch client instance
|
|
2013
|
-
* @param
|
|
1470
|
+
* @param id - id
|
|
2014
1471
|
*/
|
|
2015
|
-
async function
|
|
2016
|
-
return client.
|
|
1472
|
+
async function mysite_links_destroy(client, id) {
|
|
1473
|
+
return client.delete(`/api/mysite/links/${id}`);
|
|
2017
1474
|
}
|
|
2018
1475
|
/**
|
|
2019
|
-
*
|
|
2020
|
-
*
|
|
1476
|
+
* Reorder MySite links
|
|
1477
|
+
* Reorders MySite links by providing an ordered list of IDs.
|
|
2021
1478
|
*
|
|
2022
1479
|
* @param client - Fetch client instance
|
|
2023
1480
|
* @param body - body
|
|
2024
1481
|
*/
|
|
2025
|
-
async function
|
|
2026
|
-
return client.
|
|
1482
|
+
async function mysite_links_bulk_reorder(client, body) {
|
|
1483
|
+
return client.patch(`/api/mysite/links/bulk`, body);
|
|
2027
1484
|
}
|
|
2028
1485
|
/**
|
|
2029
|
-
*
|
|
2030
|
-
*
|
|
1486
|
+
* List MySite favorite products
|
|
1487
|
+
* Returns the member's MySite favorite products.
|
|
2031
1488
|
*
|
|
2032
1489
|
* @param client - Fetch client instance
|
|
2033
|
-
* @param
|
|
2034
|
-
* @param body - body
|
|
1490
|
+
* @param [params] - params
|
|
2035
1491
|
*/
|
|
2036
|
-
async function
|
|
2037
|
-
return client.
|
|
1492
|
+
async function mysite_favorites_list(client, params) {
|
|
1493
|
+
return client.get(`/api/mysite/favorites`, params);
|
|
2038
1494
|
}
|
|
2039
1495
|
/**
|
|
2040
|
-
*
|
|
2041
|
-
*
|
|
1496
|
+
* Add a product to MySite favorites
|
|
1497
|
+
* Adds a product to the member's MySite favorites.
|
|
2042
1498
|
*
|
|
2043
1499
|
* @param client - Fetch client instance
|
|
2044
|
-
* @param
|
|
1500
|
+
* @param body - body
|
|
2045
1501
|
*/
|
|
2046
|
-
async function
|
|
2047
|
-
return client.
|
|
1502
|
+
async function mysite_favorites_create(client, body) {
|
|
1503
|
+
return client.post(`/api/mysite/favorites`, body);
|
|
2048
1504
|
}
|
|
2049
1505
|
/**
|
|
2050
|
-
*
|
|
2051
|
-
*
|
|
1506
|
+
* Remove a product from MySite favorites
|
|
1507
|
+
* Removes a product from the member's MySite favorites.
|
|
2052
1508
|
*
|
|
2053
1509
|
* @param client - Fetch client instance
|
|
2054
|
-
|
|
1510
|
+
* @param id - id
|
|
2055
1511
|
*/
|
|
2056
|
-
async function
|
|
2057
|
-
return client.
|
|
1512
|
+
async function mysite_favorites_destroy(client, id) {
|
|
1513
|
+
return client.delete(`/api/mysite/favorites/${id}`);
|
|
2058
1514
|
}
|
|
2059
1515
|
/**
|
|
2060
|
-
*
|
|
2061
|
-
*
|
|
1516
|
+
* Reorder MySite favorites
|
|
1517
|
+
* Reorders MySite favorites by providing an ordered list of IDs.
|
|
2062
1518
|
*
|
|
2063
1519
|
* @param client - Fetch client instance
|
|
2064
|
-
* @param
|
|
1520
|
+
* @param body - body
|
|
2065
1521
|
*/
|
|
2066
|
-
async function
|
|
2067
|
-
return client.
|
|
1522
|
+
async function mysite_favorites_bulk_reorder(client, body) {
|
|
1523
|
+
return client.patch(`/api/mysite/favorites/bulk`, body);
|
|
2068
1524
|
}
|
|
2069
1525
|
//#endregion
|
|
2070
|
-
//#region src/adapters/
|
|
2071
|
-
function
|
|
1526
|
+
//#region src/adapters/mysite-api-adapter.ts
|
|
1527
|
+
function mapProfile(raw) {
|
|
2072
1528
|
return {
|
|
2073
1529
|
id: raw.id ?? 0,
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
default: raw.default ?? false,
|
|
2081
|
-
created_at: raw.created_at ?? null,
|
|
2082
|
-
updated_at: raw.updated_at ?? null
|
|
1530
|
+
mysite_url: raw.mysite_url ?? null,
|
|
1531
|
+
theme_id: raw.theme_id ?? null,
|
|
1532
|
+
bio: raw.bio ?? null,
|
|
1533
|
+
avatar_url: raw.avatar_url ?? null,
|
|
1534
|
+
display_name: raw.display_name ?? null,
|
|
1535
|
+
slug: raw.slug ?? null
|
|
2083
1536
|
};
|
|
2084
1537
|
}
|
|
2085
|
-
function
|
|
1538
|
+
function mapLink(raw) {
|
|
2086
1539
|
return {
|
|
2087
1540
|
id: raw.id ?? 0,
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
exp_month: raw.exp_month ?? null,
|
|
2092
|
-
exp_year: raw.exp_year ?? null,
|
|
2093
|
-
default: raw.default ?? false,
|
|
2094
|
-
created_at: raw.created_at ?? null,
|
|
2095
|
-
updated_at: raw.updated_at ?? null
|
|
1541
|
+
url: raw.url ?? "",
|
|
1542
|
+
title: raw.title ?? "",
|
|
1543
|
+
position: raw.position ?? 0
|
|
2096
1544
|
};
|
|
2097
1545
|
}
|
|
2098
|
-
function
|
|
1546
|
+
function mapFavorite(raw) {
|
|
2099
1547
|
return {
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
1548
|
+
id: raw.id ?? 0,
|
|
1549
|
+
product_id: raw.product_id ?? 0,
|
|
1550
|
+
product_name: raw.product_name ?? null,
|
|
1551
|
+
product_image_url: raw.product_image_url ?? null,
|
|
1552
|
+
position: raw.position ?? 0,
|
|
1553
|
+
created_at: raw.created_at ?? null
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
function mapTheme(raw) {
|
|
1557
|
+
return {
|
|
1558
|
+
id: raw.id ?? 0,
|
|
1559
|
+
name: raw.name ?? "",
|
|
1560
|
+
preview_url: raw.preview_url ?? null
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
function createMySiteApiAdapter(client) {
|
|
1564
|
+
return {
|
|
1565
|
+
fetchProfile: async () => {
|
|
1566
|
+
return mapProfile((await mysite_profile_show(client)).profile ?? {});
|
|
2109
1567
|
},
|
|
2110
|
-
|
|
2111
|
-
await
|
|
1568
|
+
updateProfile: async (body) => {
|
|
1569
|
+
return mapProfile((await mysite_profile_update(client, { profile: {
|
|
1570
|
+
display_name: body.display_name,
|
|
1571
|
+
bio: body.bio,
|
|
1572
|
+
avatar_url: body.avatar_url
|
|
1573
|
+
} })).profile ?? {});
|
|
2112
1574
|
},
|
|
2113
|
-
|
|
2114
|
-
await
|
|
1575
|
+
updateSettings: async (body) => {
|
|
1576
|
+
await Promise.all([body.theme_id !== void 0 ? mysite_settings_update(client, { settings: { theme_id: body.theme_id } }) : Promise.resolve(), body.slug !== void 0 ? mysite_profile_update(client, { profile: { slug: body.slug } }) : Promise.resolve()]);
|
|
2115
1577
|
},
|
|
2116
|
-
|
|
2117
|
-
await
|
|
1578
|
+
listLinks: async () => {
|
|
1579
|
+
return ((await mysite_links_list(client)).links ?? []).map(mapLink);
|
|
2118
1580
|
},
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
request_id: response.meta?.request_id ?? "",
|
|
2125
|
-
timestamp: response.meta?.timestamp ?? ""
|
|
2126
|
-
}
|
|
2127
|
-
};
|
|
1581
|
+
createLink: async (body) => {
|
|
1582
|
+
return mapLink((await mysite_links_create(client, { link: {
|
|
1583
|
+
title: body.title,
|
|
1584
|
+
url: body.url
|
|
1585
|
+
} })).link ?? {});
|
|
2128
1586
|
},
|
|
2129
|
-
|
|
2130
|
-
await
|
|
1587
|
+
updateLink: async (linkId, body) => {
|
|
1588
|
+
return mapLink((await mysite_links_update(client, linkId, { link: {
|
|
1589
|
+
title: body.title,
|
|
1590
|
+
url: body.url
|
|
1591
|
+
} })).link ?? {});
|
|
2131
1592
|
},
|
|
2132
|
-
|
|
2133
|
-
await
|
|
1593
|
+
deleteLink: async (linkId) => {
|
|
1594
|
+
await mysite_links_destroy(client, linkId);
|
|
2134
1595
|
},
|
|
2135
|
-
|
|
2136
|
-
await
|
|
1596
|
+
reorderLinks: async (orderedIds) => {
|
|
1597
|
+
return ((await mysite_links_bulk_reorder(client, { ordered_ids: orderedIds })).links ?? []).map(mapLink);
|
|
2137
1598
|
},
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
return {
|
|
2141
|
-
vault: {
|
|
2142
|
-
token: response.vault?.token ?? null,
|
|
2143
|
-
environment: response.vault?.environment ?? "production"
|
|
2144
|
-
},
|
|
2145
|
-
meta: {
|
|
2146
|
-
request_id: response.meta?.request_id ?? "",
|
|
2147
|
-
timestamp: response.meta?.timestamp ?? ""
|
|
2148
|
-
}
|
|
2149
|
-
};
|
|
1599
|
+
listFavorites: async () => {
|
|
1600
|
+
return ((await mysite_favorites_list(client)).favorites ?? []).map(mapFavorite);
|
|
2150
1601
|
},
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
request_id: response.meta?.request_id ?? "",
|
|
2163
|
-
timestamp: response.meta?.timestamp ?? ""
|
|
2164
|
-
}
|
|
2165
|
-
};
|
|
1602
|
+
addFavorite: async (body) => {
|
|
1603
|
+
return mapFavorite((await mysite_favorites_create(client, { favorite: { product_id: body.product_id } })).favorite ?? {});
|
|
1604
|
+
},
|
|
1605
|
+
deleteFavorite: async (favoriteId) => {
|
|
1606
|
+
await mysite_favorites_destroy(client, favoriteId);
|
|
1607
|
+
},
|
|
1608
|
+
reorderFavorites: async (orderedIds) => {
|
|
1609
|
+
return ((await mysite_favorites_bulk_reorder(client, { ordered_ids: orderedIds })).favorites ?? []).map(mapFavorite);
|
|
1610
|
+
},
|
|
1611
|
+
listThemes: async () => {
|
|
1612
|
+
return ((await mysite_themes_list(client)).themes ?? []).map(mapTheme);
|
|
2166
1613
|
}
|
|
2167
1614
|
};
|
|
2168
1615
|
}
|
|
2169
1616
|
//#endregion
|
|
2170
|
-
//#region
|
|
1617
|
+
//#region ../core/src/theme/types.ts
|
|
1618
|
+
const SEMANTIC_COLOR_NAMES = [
|
|
1619
|
+
"background",
|
|
1620
|
+
"foreground",
|
|
1621
|
+
"primary",
|
|
1622
|
+
"secondary",
|
|
1623
|
+
"accent",
|
|
1624
|
+
"muted",
|
|
1625
|
+
"destructive"
|
|
1626
|
+
];
|
|
1627
|
+
const SHADE_STEPS = [
|
|
1628
|
+
100,
|
|
1629
|
+
200,
|
|
1630
|
+
300,
|
|
1631
|
+
400,
|
|
1632
|
+
500,
|
|
1633
|
+
600,
|
|
1634
|
+
700,
|
|
1635
|
+
800,
|
|
1636
|
+
900
|
|
1637
|
+
];
|
|
1638
|
+
const FONT_SIZE_KEYS = [
|
|
1639
|
+
"extraSmall",
|
|
1640
|
+
"small",
|
|
1641
|
+
"regular",
|
|
1642
|
+
"large",
|
|
1643
|
+
"extraLarge",
|
|
1644
|
+
"giant"
|
|
1645
|
+
];
|
|
1646
|
+
const FONT_FAMILY_KEYS = ["header", "body"];
|
|
1647
|
+
const RADIUS_KEYS = [
|
|
1648
|
+
"small",
|
|
1649
|
+
"medium",
|
|
1650
|
+
"large",
|
|
1651
|
+
"extraLarge"
|
|
1652
|
+
];
|
|
1653
|
+
//#endregion
|
|
1654
|
+
//#region ../core/src/theme/color-engine.ts
|
|
2171
1655
|
/**
|
|
2172
|
-
*
|
|
2173
|
-
*
|
|
1656
|
+
* Attempt to convert any string into a Color using colorjs.io.
|
|
1657
|
+
* If the string is exactly 6 characters it is assumed to be a bare hex value
|
|
1658
|
+
* (e.g. "3b82f6") and a "#" prefix is added before parsing.
|
|
2174
1659
|
*
|
|
2175
|
-
* @
|
|
2176
|
-
* @param [params] - params
|
|
1660
|
+
* @returns the parsed Color, or a neutral gray (`oklch(0.5 0 0)`) on failure
|
|
2177
1661
|
*/
|
|
2178
|
-
|
|
2179
|
-
|
|
1662
|
+
function parseColor(value) {
|
|
1663
|
+
if (value.length === 6) value = `#${value}`;
|
|
1664
|
+
try {
|
|
1665
|
+
return new colorjs_io.default(value);
|
|
1666
|
+
} catch (error) {
|
|
1667
|
+
console.warn("[theme] Failed to parse color:", value, error);
|
|
1668
|
+
return new colorjs_io.default("oklch", [
|
|
1669
|
+
.5,
|
|
1670
|
+
0,
|
|
1671
|
+
0
|
|
1672
|
+
]);
|
|
1673
|
+
}
|
|
2180
1674
|
}
|
|
2181
1675
|
/**
|
|
2182
|
-
*
|
|
2183
|
-
*
|
|
2184
|
-
*
|
|
2185
|
-
* @param client - Fetch client instance
|
|
2186
|
-
* @param [params] - params
|
|
1676
|
+
* Returns either the original foreground or a corrected lightness variant,
|
|
1677
|
+
* whichever provides better contrast against `color`.
|
|
1678
|
+
* Inversion triggers when the APCA contrast is below 50.
|
|
2187
1679
|
*/
|
|
2188
|
-
|
|
2189
|
-
|
|
1680
|
+
function getForegroundColor(foreground, color) {
|
|
1681
|
+
if (foreground.oklch.l == null || color.oklch.l == null) return foreground;
|
|
1682
|
+
if (color.contrastAPCA(foreground) < 50) return new colorjs_io.default("oklch", [
|
|
1683
|
+
color.oklch.l < .7 ? .95 : .15,
|
|
1684
|
+
foreground.oklch.c || 0,
|
|
1685
|
+
foreground.oklch.h || 0
|
|
1686
|
+
]);
|
|
1687
|
+
return foreground;
|
|
2190
1688
|
}
|
|
2191
|
-
//#endregion
|
|
2192
|
-
//#region ../../store/api-client/src/portal-tenant-countries-adapter.ts
|
|
2193
1689
|
/**
|
|
2194
|
-
*
|
|
1690
|
+
* Generate a 100–900 shade ramp from a base color.
|
|
1691
|
+
* Base anchors at 500. Light shades (100–400) step toward white,
|
|
1692
|
+
* dark shades (600–900) step toward black. Dark steps use an asymmetric
|
|
1693
|
+
* multiplier (1.6×, 1.875×, 3×, 4× of `darkStep`) for a more gradual
|
|
1694
|
+
* initial descent. Chroma is nudged per step for perceptually natural ramps.
|
|
2195
1695
|
*/
|
|
2196
|
-
function
|
|
1696
|
+
function generateShades(base) {
|
|
1697
|
+
const l = base.oklch.l ?? 0;
|
|
1698
|
+
const c = base.oklch.c ?? 0;
|
|
1699
|
+
const h = base.oklch.h ?? 0;
|
|
1700
|
+
const safeMax = l >= .885 ? .995 : .97;
|
|
1701
|
+
const safeMin = l <= .33 ? 0 : .21;
|
|
1702
|
+
const lightStep = (safeMax - l) / 5;
|
|
1703
|
+
const darkStep = -(l - safeMin) / 8;
|
|
1704
|
+
const shade = (lDelta, cDelta) => {
|
|
1705
|
+
return new colorjs_io.default("oklch", [
|
|
1706
|
+
Math.max(0, Math.min(1, l + lDelta)),
|
|
1707
|
+
c <= .001 ? c : Math.max(0, c + cDelta),
|
|
1708
|
+
h
|
|
1709
|
+
]);
|
|
1710
|
+
};
|
|
2197
1711
|
return {
|
|
2198
|
-
|
|
2199
|
-
|
|
1712
|
+
100: shade(5 * lightStep, -.00375),
|
|
1713
|
+
200: shade(4 * lightStep, -.00375),
|
|
1714
|
+
300: shade(3 * lightStep, -.00375),
|
|
1715
|
+
400: shade(2 * lightStep, -.00375),
|
|
1716
|
+
500: new colorjs_io.default("oklch", [
|
|
1717
|
+
l,
|
|
1718
|
+
c,
|
|
1719
|
+
h
|
|
1720
|
+
]),
|
|
1721
|
+
600: shade(1.6 * darkStep, .025),
|
|
1722
|
+
700: shade(1.875 * 2 * darkStep, .05),
|
|
1723
|
+
800: shade(6 * darkStep, .075),
|
|
1724
|
+
900: shade(8 * darkStep, .1)
|
|
2200
1725
|
};
|
|
2201
1726
|
}
|
|
1727
|
+
const DARK_DERIVATION_CONFIG = {
|
|
1728
|
+
background: {
|
|
1729
|
+
baseLightness: .15,
|
|
1730
|
+
fgLightness: .93
|
|
1731
|
+
},
|
|
1732
|
+
foreground: {
|
|
1733
|
+
baseLightness: .93,
|
|
1734
|
+
fgLightness: .15
|
|
1735
|
+
},
|
|
1736
|
+
muted: {
|
|
1737
|
+
baseLightness: .22,
|
|
1738
|
+
fgLightness: .75
|
|
1739
|
+
},
|
|
1740
|
+
primary: {
|
|
1741
|
+
baseLightness: "invert",
|
|
1742
|
+
fgLightness: .95,
|
|
1743
|
+
chromaScale: .9
|
|
1744
|
+
},
|
|
1745
|
+
secondary: {
|
|
1746
|
+
baseLightness: "invert",
|
|
1747
|
+
fgLightness: .93,
|
|
1748
|
+
chromaScale: .85
|
|
1749
|
+
},
|
|
1750
|
+
accent: {
|
|
1751
|
+
baseLightness: "invert",
|
|
1752
|
+
fgLightness: .95,
|
|
1753
|
+
chromaScale: .9
|
|
1754
|
+
},
|
|
1755
|
+
destructive: {
|
|
1756
|
+
baseLightness: "invert",
|
|
1757
|
+
fgLightness: .95,
|
|
1758
|
+
chromaScale: .95
|
|
1759
|
+
}
|
|
1760
|
+
};
|
|
1761
|
+
/** Invert OKLCH lightness (1 - l), clamped to [0.35, 0.75] to avoid extremes. */
|
|
1762
|
+
function invertLightness(l) {
|
|
1763
|
+
const inverted = 1 - l;
|
|
1764
|
+
return Math.max(.35, Math.min(.75, inverted));
|
|
1765
|
+
}
|
|
2202
1766
|
/**
|
|
2203
|
-
*
|
|
1767
|
+
* Derive a dark-mode ThemeColorInput from its light-mode counterpart.
|
|
2204
1768
|
*/
|
|
2205
|
-
function
|
|
1769
|
+
function deriveDarkVariant(name, light) {
|
|
1770
|
+
const config = DARK_DERIVATION_CONFIG[name];
|
|
1771
|
+
const chromaScale = config.chromaScale ?? 1;
|
|
1772
|
+
const baseLightness = config.baseLightness === "invert" ? invertLightness(light.base.oklch.l ?? 0) : config.baseLightness;
|
|
1773
|
+
const fgLightness = config.fgLightness === "invert" ? invertLightness(light.foreground.oklch.l ?? 0) : config.fgLightness;
|
|
2206
1774
|
return {
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
1775
|
+
base: new colorjs_io.default("oklch", [
|
|
1776
|
+
baseLightness,
|
|
1777
|
+
(light.base.oklch.c || 0) * chromaScale,
|
|
1778
|
+
light.base.oklch.h || 0
|
|
1779
|
+
]),
|
|
1780
|
+
foreground: new colorjs_io.default("oklch", [
|
|
1781
|
+
fgLightness,
|
|
1782
|
+
(light.foreground.oklch.c || 0) * chromaScale,
|
|
1783
|
+
light.foreground.oklch.h || 0
|
|
1784
|
+
])
|
|
2211
1785
|
};
|
|
2212
1786
|
}
|
|
2213
1787
|
/**
|
|
2214
|
-
*
|
|
1788
|
+
* Merge auto-derived dark colors with any user-specified overrides.
|
|
1789
|
+
* For each semantic color, if the user has fully overridden both base and
|
|
1790
|
+
* foreground those are used; otherwise the missing channels are derived.
|
|
2215
1791
|
*/
|
|
2216
|
-
function
|
|
1792
|
+
function mergeDarkOverrides(def) {
|
|
1793
|
+
const darkColors = {};
|
|
1794
|
+
for (const name of SEMANTIC_COLOR_NAMES) {
|
|
1795
|
+
const lightInput = def.light[name];
|
|
1796
|
+
const darkOverride = def.dark[name];
|
|
1797
|
+
if (darkOverride?.base && darkOverride?.foreground) darkColors[name] = darkOverride;
|
|
1798
|
+
else if (darkOverride) {
|
|
1799
|
+
const base = darkOverride.base ?? deriveDarkVariant(name, lightInput).base;
|
|
1800
|
+
darkColors[name] = {
|
|
1801
|
+
base,
|
|
1802
|
+
foreground: darkOverride.foreground ?? getForegroundColor(def.light.foreground.base, base)
|
|
1803
|
+
};
|
|
1804
|
+
} else darkColors[name] = deriveDarkVariant(name, lightInput);
|
|
1805
|
+
}
|
|
1806
|
+
return darkColors;
|
|
1807
|
+
}
|
|
1808
|
+
function resolveColorSet(colors) {
|
|
1809
|
+
const resolved = {};
|
|
1810
|
+
for (const name of SEMANTIC_COLOR_NAMES) {
|
|
1811
|
+
const input = colors[name];
|
|
1812
|
+
const shades = generateShades(input.base);
|
|
1813
|
+
const resolvedShades = {};
|
|
1814
|
+
for (const step of SHADE_STEPS) resolvedShades[step] = shades[step];
|
|
1815
|
+
resolved[name] = {
|
|
1816
|
+
base: input.base.clone(),
|
|
1817
|
+
foreground: input.foreground.clone(),
|
|
1818
|
+
shades: resolvedShades
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
return resolved;
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Resolve a ThemeDefinition into a complete ResolvedTheme.
|
|
1825
|
+
* Dark mode colors are derived from light where not overridden.
|
|
1826
|
+
*/
|
|
1827
|
+
function resolveTheme(def) {
|
|
2217
1828
|
return {
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
1829
|
+
id: def.id,
|
|
1830
|
+
name: def.name,
|
|
1831
|
+
light: resolveColorSet(def.light),
|
|
1832
|
+
dark: resolveColorSet(mergeDarkOverrides(def)),
|
|
1833
|
+
fontSizes: { ...def.fontSizes },
|
|
1834
|
+
fontFamilies: { ...def.fontFamilies },
|
|
1835
|
+
spacing: def.spacing,
|
|
1836
|
+
radii: { ...def.radii }
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
//#endregion
|
|
1840
|
+
//#region ../core/src/theme/tailwind-overrides.ts
|
|
1841
|
+
/**
|
|
1842
|
+
* Specific overrides, otherwise all the overrides are generated using emitTailwindOverrides
|
|
1843
|
+
*/
|
|
1844
|
+
const OVERRIDES = {
|
|
1845
|
+
"--color-gray-50": "var(--color-muted)",
|
|
1846
|
+
"--color-gray-100": "var(--color-muted-600)",
|
|
1847
|
+
"--color-gray-200": "var(--color-border)"
|
|
1848
|
+
};
|
|
1849
|
+
/**
|
|
1850
|
+
* Returns the inverted shade for dark mode foreground colors.
|
|
1851
|
+
* In dark mode, light shades (50, 100) should map to dark values (950, 900) and vice versa.
|
|
1852
|
+
*/
|
|
1853
|
+
function getInvertedStep(shade) {
|
|
1854
|
+
const shadeIndex = SHADE_STEPS.indexOf(shade);
|
|
1855
|
+
return SHADE_STEPS[SHADE_STEPS.length - 1 - shadeIndex] || 500;
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Map semantic colors to Tailwind built-in color names.
|
|
1859
|
+
*/
|
|
1860
|
+
function emitTailwindOverrides(darkMode = false) {
|
|
1861
|
+
const TAILWIND_COLOR_MAP = {
|
|
1862
|
+
gray: "foreground",
|
|
1863
|
+
red: "destructive",
|
|
1864
|
+
blue: "primary",
|
|
1865
|
+
green: "accent"
|
|
2226
1866
|
};
|
|
1867
|
+
const TAILWIND_SHADES = [
|
|
1868
|
+
50,
|
|
1869
|
+
100,
|
|
1870
|
+
200,
|
|
1871
|
+
300,
|
|
1872
|
+
400,
|
|
1873
|
+
500,
|
|
1874
|
+
600,
|
|
1875
|
+
700,
|
|
1876
|
+
800,
|
|
1877
|
+
900,
|
|
1878
|
+
950
|
|
1879
|
+
];
|
|
1880
|
+
const SHADE_REMAP = {
|
|
1881
|
+
50: 100,
|
|
1882
|
+
950: 900
|
|
1883
|
+
};
|
|
1884
|
+
const lines = [];
|
|
1885
|
+
for (const [twName, semantic] of Object.entries(TAILWIND_COLOR_MAP)) for (const shade of TAILWIND_SHADES) {
|
|
1886
|
+
const step = SHADE_REMAP[shade] ?? shade;
|
|
1887
|
+
const override = OVERRIDES[`--color-${twName}-${shade}`];
|
|
1888
|
+
lines.push(`--color-${twName}-${shade}: ${override ? override : `var(--color-${semantic}-${semantic === "foreground" && darkMode === true ? getInvertedStep(step) : step})`};`);
|
|
1889
|
+
}
|
|
1890
|
+
lines.push("--color-white: var(--color-background);");
|
|
1891
|
+
lines.push("--color-black: var(--color-foreground);");
|
|
1892
|
+
return lines;
|
|
1893
|
+
}
|
|
1894
|
+
//#endregion
|
|
1895
|
+
//#region ../core/src/theme/css-generator.ts
|
|
1896
|
+
function colorToCSS(color) {
|
|
1897
|
+
const result = color.toString({ format: "oklch" });
|
|
1898
|
+
if (result.includes("NaN")) {
|
|
1899
|
+
console.warn("[theme] colorToCSS produced NaN, using neutral fallback:", result);
|
|
1900
|
+
return "oklch(0.5 0 0)";
|
|
1901
|
+
}
|
|
1902
|
+
return result;
|
|
1903
|
+
}
|
|
1904
|
+
function camelToKebab(str) {
|
|
1905
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
2227
1906
|
}
|
|
2228
1907
|
/**
|
|
2229
|
-
*
|
|
2230
|
-
*
|
|
2231
|
-
* Maps the generated portal-tenant-store namespace functions to the abstract
|
|
2232
|
-
* CountriesApi port, closing over the FetchClient so consumers don't need
|
|
2233
|
-
* to pass it per-call.
|
|
1908
|
+
* Emit --color-{name}, --color-{name}-foreground, --color-{name}-{shade} vars.
|
|
1909
|
+
* Uses --color- prefix to match portal-widgets/tailwind.config.ts.
|
|
2234
1910
|
*/
|
|
2235
|
-
function
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
});
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
};
|
|
2245
|
-
} };
|
|
1911
|
+
function emitColorVars(colors) {
|
|
1912
|
+
const lines = [];
|
|
1913
|
+
for (const name of SEMANTIC_COLOR_NAMES) {
|
|
1914
|
+
const color = colors[name];
|
|
1915
|
+
lines.push(`--color-${name}: ${colorToCSS(color.base)};`);
|
|
1916
|
+
lines.push(`--color-${name}-foreground: ${colorToCSS(color.foreground)};`);
|
|
1917
|
+
for (const step of SHADE_STEPS) lines.push(`--color-${name}-${step}: ${colorToCSS(color.shades[step])};`);
|
|
1918
|
+
}
|
|
1919
|
+
return lines;
|
|
2246
1920
|
}
|
|
2247
|
-
//#endregion
|
|
2248
|
-
//#region ../../store/api-client/src/portal-tenant-languages-adapter.ts
|
|
2249
1921
|
/**
|
|
2250
|
-
*
|
|
1922
|
+
* Format a font family value for CSS output.
|
|
1923
|
+
* - If the value starts with "var(" (legacy), pass through as-is
|
|
1924
|
+
* - If the value already contains a comma (has fallback), pass through as-is
|
|
1925
|
+
* - Otherwise, wrap in quotes and append a generic sans-serif fallback
|
|
2251
1926
|
*/
|
|
2252
|
-
function
|
|
2253
|
-
return
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
};
|
|
1927
|
+
function formatFontFamily(value) {
|
|
1928
|
+
if (value.startsWith("var(")) return value;
|
|
1929
|
+
if (value.includes(",")) return value;
|
|
1930
|
+
return `'${value}', sans-serif`;
|
|
2257
1931
|
}
|
|
2258
1932
|
/**
|
|
2259
|
-
*
|
|
1933
|
+
* Emit non-color CSS variables (font sizes, families, spacing, radii).
|
|
2260
1934
|
*/
|
|
2261
|
-
function
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
next_cursor: raw.pagination.next_cursor ?? null,
|
|
2269
|
-
prev_cursor: raw.pagination.prev_cursor ?? null
|
|
2270
|
-
} : void 0
|
|
2271
|
-
};
|
|
1935
|
+
function emitNonColorVars(theme) {
|
|
1936
|
+
const lines = [];
|
|
1937
|
+
for (const key of FONT_SIZE_KEYS) lines.push(`--font-size-${camelToKebab(key)}: ${theme.fontSizes[key]};`);
|
|
1938
|
+
for (const key of FONT_FAMILY_KEYS) lines.push(`--font-${key}: ${formatFontFamily(theme.fontFamilies[key])};`);
|
|
1939
|
+
lines.push(`--spacing: ${theme.spacing};`);
|
|
1940
|
+
for (const key of RADIUS_KEYS) lines.push(`--radius-${camelToKebab(key)}: ${theme.radii[key]};`);
|
|
1941
|
+
return lines;
|
|
2272
1942
|
}
|
|
2273
1943
|
/**
|
|
2274
|
-
*
|
|
2275
|
-
*
|
|
2276
|
-
* Maps the generated portal-tenant-store namespace functions to the abstract
|
|
2277
|
-
* LanguagesApi port, closing over the FetchClient so consumers don't need
|
|
2278
|
-
* to pass it per-call.
|
|
1944
|
+
* Static CSS alias variables that bridge theme var names to Tailwind/component conventions.
|
|
1945
|
+
* These are always emitted and not mode-dependent.
|
|
2279
1946
|
*/
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
1947
|
+
const globalCSSOverride = [
|
|
1948
|
+
"--color-background-foreground: var(--color-foreground);",
|
|
1949
|
+
"--color-foreground-foreground: var(--color-background);",
|
|
1950
|
+
"--color-contrast: var(--color-foreground);",
|
|
1951
|
+
...SEMANTIC_COLOR_NAMES.map((value) => `--${value}: var(--color-${value});`),
|
|
1952
|
+
...SEMANTIC_COLOR_NAMES.map((value) => `--${value}-foreground: var(--color-${value}-foreground);`),
|
|
1953
|
+
"--sidebar-ring: var(--color-primary);",
|
|
1954
|
+
"--sidebar-border: var(--color-border);",
|
|
1955
|
+
"--sidebar-accent-foreground: var(--color-accent-foreground);",
|
|
1956
|
+
"--sidebar-accent: var(--color-accent);",
|
|
1957
|
+
"--sidebar-primary-foreground: var(--color-primary-foreground);",
|
|
1958
|
+
"--sidebar-primary: var(--color-primary);",
|
|
1959
|
+
"--sidebar-foreground: var(--color-muted-foreground);",
|
|
1960
|
+
"--sidebar: var(--color-muted);",
|
|
1961
|
+
"--border: var(--color-background-600);",
|
|
1962
|
+
"--ring: var(--color-primary);",
|
|
1963
|
+
"--popover: var(--color-background);",
|
|
1964
|
+
"--popover-foreground: var(--color-foreground);",
|
|
1965
|
+
"--card: var(--color-muted);",
|
|
1966
|
+
"--card-foreground: var(--color-muted-foreground);",
|
|
1967
|
+
"--radius-sm: var(--radius-small);",
|
|
1968
|
+
"--radius-md: var(--radius-medium);",
|
|
1969
|
+
"--radius-lg: var(--radius-large);",
|
|
1970
|
+
"--radius-xl: var(--radius-extra-large);",
|
|
1971
|
+
"--text-xs: var(--font-size-extra-small);",
|
|
1972
|
+
"--text-sm: var(--font-size-small);",
|
|
1973
|
+
"--text-base: var(--font-size-regular);",
|
|
1974
|
+
"--text-lg: var(--font-size-large);",
|
|
1975
|
+
"--text-xl: var(--font-size-extra-large);",
|
|
1976
|
+
"--text-2xl: var(--font-size-giant);"
|
|
1977
|
+
];
|
|
2294
1978
|
/**
|
|
2295
|
-
*
|
|
2296
|
-
*
|
|
2297
|
-
* Delegates to the adapter factory in `@fluid-app/store-api-client` which
|
|
2298
|
-
* maps the generated portal-tenant-store namespace functions to the abstract
|
|
2299
|
-
* CountriesApi port.
|
|
1979
|
+
* Overrides for global tailwindcss for specifically dark mode.
|
|
2300
1980
|
*/
|
|
2301
|
-
|
|
2302
|
-
|
|
1981
|
+
const globalDarkCSSOverride = ["--border: var(--color-background-400);"];
|
|
1982
|
+
/**
|
|
1983
|
+
* Generate a complete CSS string for a resolved theme.
|
|
1984
|
+
* Outputs 2–3 blocks: light default, dark explicit via `[data-theme-mode="dark"]`,
|
|
1985
|
+
* and (unless `disableAutoTheme`) a `prefers-color-scheme: dark` media query block.
|
|
1986
|
+
*/
|
|
1987
|
+
function generateThemeCSS(theme, options = {}) {
|
|
1988
|
+
const sel = `[data-theme="${theme.id}"]`;
|
|
1989
|
+
const tw = options.mapTailwindColors ?? true;
|
|
1990
|
+
const blocks = [];
|
|
1991
|
+
blocks.push(`${sel} {`);
|
|
1992
|
+
blocks.push(...globalCSSOverride);
|
|
1993
|
+
blocks.push(...emitNonColorVars(theme));
|
|
1994
|
+
blocks.push(...emitColorVars(theme.light));
|
|
1995
|
+
if (tw) blocks.push(...emitTailwindOverrides());
|
|
1996
|
+
blocks.push(`}`);
|
|
1997
|
+
blocks.push(`${sel}[data-theme-mode="dark"] {`);
|
|
1998
|
+
blocks.push(...globalDarkCSSOverride);
|
|
1999
|
+
blocks.push(...emitColorVars(theme.dark));
|
|
2000
|
+
if (tw) blocks.push(...emitTailwindOverrides(true));
|
|
2001
|
+
blocks.push(`}`);
|
|
2002
|
+
if (!options.disableAutoTheme) {
|
|
2003
|
+
blocks.push(`@media (prefers-color-scheme: dark) {`);
|
|
2004
|
+
blocks.push(`${sel}:not([data-theme-mode]) {`);
|
|
2005
|
+
blocks.push(...globalDarkCSSOverride);
|
|
2006
|
+
blocks.push(...emitColorVars(theme.dark).map((l) => `${l}`));
|
|
2007
|
+
if (tw) blocks.push(...emitTailwindOverrides(true).map((l) => `${l}`));
|
|
2008
|
+
blocks.push(`}`);
|
|
2009
|
+
blocks.push(`}`);
|
|
2010
|
+
}
|
|
2011
|
+
return blocks.join("\n");
|
|
2303
2012
|
}
|
|
2304
2013
|
//#endregion
|
|
2305
|
-
//#region src/
|
|
2014
|
+
//#region ../core/src/theme/defaults.ts
|
|
2015
|
+
const DEFAULT_FONT_SIZES = {
|
|
2016
|
+
extraSmall: "0.75rem",
|
|
2017
|
+
small: "0.875rem",
|
|
2018
|
+
regular: "1rem",
|
|
2019
|
+
large: "1.125rem",
|
|
2020
|
+
extraLarge: "1.25rem",
|
|
2021
|
+
giant: "1.5rem"
|
|
2022
|
+
};
|
|
2023
|
+
const DEFAULT_FONT_FAMILIES = {
|
|
2024
|
+
header: "Inter",
|
|
2025
|
+
body: "Inter"
|
|
2026
|
+
};
|
|
2027
|
+
const DEFAULT_SPACING = "0.25rem";
|
|
2028
|
+
const DEFAULT_RADII = {
|
|
2029
|
+
small: "0.25rem",
|
|
2030
|
+
medium: "0.5rem",
|
|
2031
|
+
large: "0.75rem",
|
|
2032
|
+
extraLarge: "1rem"
|
|
2033
|
+
};
|
|
2034
|
+
const DEFAULT_COLORS = {
|
|
2035
|
+
background: "#ffffff",
|
|
2036
|
+
foreground: "#1a1a1a",
|
|
2037
|
+
primary: "#3b82f6",
|
|
2038
|
+
secondary: "#6b7280",
|
|
2039
|
+
accent: "#10b981",
|
|
2040
|
+
muted: "#f3f4f6",
|
|
2041
|
+
destructive: "#ef4444",
|
|
2042
|
+
mutedForeground: "#6b7280"
|
|
2043
|
+
};
|
|
2044
|
+
const DEFAULT_THEME_ID = "default";
|
|
2045
|
+
const DEFAULT_THEME_NAME = "Default Theme";
|
|
2306
2046
|
/**
|
|
2307
|
-
*
|
|
2308
|
-
*
|
|
2309
|
-
* Delegates to the adapter factory in `@fluid-app/store-api-client` which
|
|
2310
|
-
* maps the generated portal-tenant-store namespace functions to the abstract
|
|
2311
|
-
* LanguagesApi port.
|
|
2047
|
+
* Build a fresh ThemeDefinition populated with all defaults.
|
|
2048
|
+
* Returns a new object each call because Color instances are mutable — do not cache the result.
|
|
2312
2049
|
*/
|
|
2313
|
-
function
|
|
2314
|
-
|
|
2050
|
+
function getDefaultThemeDefinition() {
|
|
2051
|
+
const bg = new colorjs_io.default(DEFAULT_COLORS.background);
|
|
2052
|
+
const fg = new colorjs_io.default(DEFAULT_COLORS.foreground);
|
|
2053
|
+
const primary = new colorjs_io.default(DEFAULT_COLORS.primary);
|
|
2054
|
+
const secondary = new colorjs_io.default(DEFAULT_COLORS.secondary);
|
|
2055
|
+
const accent = new colorjs_io.default(DEFAULT_COLORS.accent);
|
|
2056
|
+
const muted = new colorjs_io.default(DEFAULT_COLORS.muted);
|
|
2057
|
+
const destructive = new colorjs_io.default(DEFAULT_COLORS.destructive);
|
|
2058
|
+
const mutedFg = new colorjs_io.default(DEFAULT_COLORS.mutedForeground);
|
|
2059
|
+
const darkBg = new colorjs_io.default("#0a0a0a");
|
|
2060
|
+
const darkFg = new colorjs_io.default("#fafafa");
|
|
2061
|
+
const darkMuted = new colorjs_io.default("#171717");
|
|
2062
|
+
const darkMutedForeground = new colorjs_io.default("#dddddd");
|
|
2063
|
+
return {
|
|
2064
|
+
id: DEFAULT_THEME_ID,
|
|
2065
|
+
name: DEFAULT_THEME_NAME,
|
|
2066
|
+
light: {
|
|
2067
|
+
background: {
|
|
2068
|
+
base: bg,
|
|
2069
|
+
foreground: fg
|
|
2070
|
+
},
|
|
2071
|
+
foreground: {
|
|
2072
|
+
base: fg,
|
|
2073
|
+
foreground: bg
|
|
2074
|
+
},
|
|
2075
|
+
primary: {
|
|
2076
|
+
base: primary,
|
|
2077
|
+
foreground: getForegroundColor(fg, primary)
|
|
2078
|
+
},
|
|
2079
|
+
secondary: {
|
|
2080
|
+
base: secondary,
|
|
2081
|
+
foreground: getForegroundColor(fg, secondary)
|
|
2082
|
+
},
|
|
2083
|
+
accent: {
|
|
2084
|
+
base: accent,
|
|
2085
|
+
foreground: getForegroundColor(fg, accent)
|
|
2086
|
+
},
|
|
2087
|
+
muted: {
|
|
2088
|
+
base: muted,
|
|
2089
|
+
foreground: mutedFg
|
|
2090
|
+
},
|
|
2091
|
+
destructive: {
|
|
2092
|
+
base: destructive,
|
|
2093
|
+
foreground: getForegroundColor(fg, destructive)
|
|
2094
|
+
}
|
|
2095
|
+
},
|
|
2096
|
+
dark: {
|
|
2097
|
+
background: {
|
|
2098
|
+
base: darkBg,
|
|
2099
|
+
foreground: darkFg
|
|
2100
|
+
},
|
|
2101
|
+
foreground: {
|
|
2102
|
+
base: darkFg,
|
|
2103
|
+
foreground: darkBg
|
|
2104
|
+
},
|
|
2105
|
+
muted: {
|
|
2106
|
+
base: darkMuted,
|
|
2107
|
+
foreground: darkMutedForeground
|
|
2108
|
+
}
|
|
2109
|
+
},
|
|
2110
|
+
fontSizes: { ...DEFAULT_FONT_SIZES },
|
|
2111
|
+
fontFamilies: { ...DEFAULT_FONT_FAMILIES },
|
|
2112
|
+
spacing: DEFAULT_SPACING,
|
|
2113
|
+
radii: { ...DEFAULT_RADII }
|
|
2114
|
+
};
|
|
2315
2115
|
}
|
|
2316
2116
|
//#endregion
|
|
2317
|
-
//#region
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
*/
|
|
2325
|
-
async function mysite_profile_show(client) {
|
|
2326
|
-
return client.get(`/api/mysite/profile`);
|
|
2327
|
-
}
|
|
2328
|
-
/**
|
|
2329
|
-
* Update MySite profile
|
|
2330
|
-
* Updates the member's MySite profile fields.
|
|
2331
|
-
*
|
|
2332
|
-
* @param client - Fetch client instance
|
|
2333
|
-
* @param body - body
|
|
2334
|
-
*/
|
|
2335
|
-
async function mysite_profile_update(client, body) {
|
|
2336
|
-
return client.put(`/api/mysite/profile`, body);
|
|
2337
|
-
}
|
|
2338
|
-
/**
|
|
2339
|
-
* List available MySite themes
|
|
2340
|
-
* Returns available MySite themes.
|
|
2341
|
-
*
|
|
2342
|
-
* @param client - Fetch client instance
|
|
2343
|
-
* @param [params] - params
|
|
2344
|
-
*/
|
|
2345
|
-
async function mysite_themes_list(client, params) {
|
|
2346
|
-
return client.get(`/api/mysite/themes`, params);
|
|
2347
|
-
}
|
|
2348
|
-
/**
|
|
2349
|
-
* Update MySite settings
|
|
2350
|
-
* Updates the member's MySite settings.
|
|
2351
|
-
*
|
|
2352
|
-
* @param client - Fetch client instance
|
|
2353
|
-
* @param body - body
|
|
2354
|
-
*/
|
|
2355
|
-
async function mysite_settings_update(client, body) {
|
|
2356
|
-
return client.put(`/api/mysite/settings`, body);
|
|
2357
|
-
}
|
|
2358
|
-
/**
|
|
2359
|
-
* List MySite links for the current user
|
|
2360
|
-
* Returns the member's MySite links, ordered by position.
|
|
2361
|
-
*
|
|
2362
|
-
* @param client - Fetch client instance
|
|
2363
|
-
* @param [params] - params
|
|
2364
|
-
*/
|
|
2365
|
-
async function mysite_links_list(client, params) {
|
|
2366
|
-
return client.get(`/api/mysite/links`, params);
|
|
2117
|
+
//#region ../core/src/theme/serialisation.ts
|
|
2118
|
+
function colorToPlain(color) {
|
|
2119
|
+
return {
|
|
2120
|
+
l: color.oklch.l ?? 0,
|
|
2121
|
+
c: color.oklch.c ?? 0,
|
|
2122
|
+
h: color.oklch.h ?? 0
|
|
2123
|
+
};
|
|
2367
2124
|
}
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
*/
|
|
2375
|
-
async function mysite_links_create(client, body) {
|
|
2376
|
-
return client.post(`/api/mysite/links`, body);
|
|
2125
|
+
function plainToColor(plain) {
|
|
2126
|
+
return new colorjs_io.default("oklch", [
|
|
2127
|
+
plain.l,
|
|
2128
|
+
plain.c,
|
|
2129
|
+
plain.h
|
|
2130
|
+
]);
|
|
2377
2131
|
}
|
|
2378
2132
|
/**
|
|
2379
|
-
*
|
|
2380
|
-
*
|
|
2381
|
-
*
|
|
2382
|
-
* @param client - Fetch client instance
|
|
2383
|
-
* @param id - id
|
|
2384
|
-
* @param body - body
|
|
2133
|
+
* Serialise a ThemeDefinition (with Color objects) to a plain JSON payload
|
|
2134
|
+
* suitable for backend storage.
|
|
2385
2135
|
*/
|
|
2386
|
-
|
|
2387
|
-
|
|
2136
|
+
function serialiseTheme(def) {
|
|
2137
|
+
const light = {};
|
|
2138
|
+
for (const name of SEMANTIC_COLOR_NAMES) light[name] = {
|
|
2139
|
+
base: colorToPlain(def.light[name].base),
|
|
2140
|
+
foreground: colorToPlain(def.light[name].foreground)
|
|
2141
|
+
};
|
|
2142
|
+
const dark = {};
|
|
2143
|
+
for (const [name, value] of Object.entries(def.dark)) {
|
|
2144
|
+
if (!value) continue;
|
|
2145
|
+
dark[name] = {
|
|
2146
|
+
...value.base ? { base: colorToPlain(value.base) } : {},
|
|
2147
|
+
...value.foreground ? { foreground: colorToPlain(value.foreground) } : {}
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
return {
|
|
2151
|
+
id: def.id,
|
|
2152
|
+
name: def.name,
|
|
2153
|
+
light,
|
|
2154
|
+
dark,
|
|
2155
|
+
fontSizes: { ...def.fontSizes },
|
|
2156
|
+
fontFamilies: { ...def.fontFamilies },
|
|
2157
|
+
spacing: def.spacing,
|
|
2158
|
+
radii: { ...def.radii },
|
|
2159
|
+
...def.syncWithBrandColors ? { syncWithBrandColors: true } : {}
|
|
2160
|
+
};
|
|
2388
2161
|
}
|
|
2389
2162
|
/**
|
|
2390
|
-
*
|
|
2391
|
-
*
|
|
2392
|
-
*
|
|
2393
|
-
* @param client - Fetch client instance
|
|
2394
|
-
* @param id - id
|
|
2163
|
+
* Deserialise a backend payload into a ThemeDefinition with Color objects.
|
|
2164
|
+
* Accepts `Record<string, unknown>` because API data is untyped at the boundary.
|
|
2165
|
+
* Falls back to default colors for any missing light-mode entries.
|
|
2395
2166
|
*/
|
|
2396
|
-
|
|
2397
|
-
|
|
2167
|
+
function deserialiseTheme(payload) {
|
|
2168
|
+
const lightRaw = payload.light ?? {};
|
|
2169
|
+
const darkRaw = payload.dark ?? {};
|
|
2170
|
+
const defaults = getDefaultThemeDefinition();
|
|
2171
|
+
const light = {};
|
|
2172
|
+
for (const name of SEMANTIC_COLOR_NAMES) {
|
|
2173
|
+
const entry = lightRaw[name];
|
|
2174
|
+
if (entry) light[name] = {
|
|
2175
|
+
base: plainToColor(entry.base),
|
|
2176
|
+
foreground: plainToColor(entry.foreground)
|
|
2177
|
+
};
|
|
2178
|
+
else {
|
|
2179
|
+
console.warn(`[theme] deserialiseTheme: missing light color "${name}", using default`);
|
|
2180
|
+
light[name] = defaults.light[name];
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
const dark = {};
|
|
2184
|
+
for (const [name, value] of Object.entries(darkRaw)) {
|
|
2185
|
+
if (!value) continue;
|
|
2186
|
+
dark[name] = {
|
|
2187
|
+
...value.base ? { base: plainToColor(value.base) } : {},
|
|
2188
|
+
...value.foreground ? { foreground: plainToColor(value.foreground) } : {}
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
return {
|
|
2192
|
+
id: payload.id,
|
|
2193
|
+
name: payload.name,
|
|
2194
|
+
light,
|
|
2195
|
+
dark,
|
|
2196
|
+
fontSizes: payload.fontSizes ?? DEFAULT_FONT_SIZES,
|
|
2197
|
+
fontFamilies: payload.fontFamilies ?? DEFAULT_FONT_FAMILIES,
|
|
2198
|
+
spacing: payload.spacing ?? "0.25rem",
|
|
2199
|
+
radii: payload.radii ?? DEFAULT_RADII,
|
|
2200
|
+
...payload.syncWithBrandColors === true ? { syncWithBrandColors: true } : {}
|
|
2201
|
+
};
|
|
2398
2202
|
}
|
|
2203
|
+
//#endregion
|
|
2204
|
+
//#region ../core/src/theme/transforms.ts
|
|
2399
2205
|
/**
|
|
2400
|
-
*
|
|
2401
|
-
*
|
|
2402
|
-
*
|
|
2403
|
-
* @param client - Fetch client instance
|
|
2404
|
-
* @param body - body
|
|
2206
|
+
* Check if a theme config uses the new structured format (has a `light` key
|
|
2207
|
+
* that is an object) vs the legacy flat format.
|
|
2405
2208
|
*/
|
|
2406
|
-
|
|
2407
|
-
return
|
|
2209
|
+
function isNewThemeFormat(config) {
|
|
2210
|
+
return config.light != null && typeof config.light === "object";
|
|
2408
2211
|
}
|
|
2409
2212
|
/**
|
|
2410
|
-
*
|
|
2411
|
-
*
|
|
2412
|
-
*
|
|
2413
|
-
* @param client - Fetch client instance
|
|
2414
|
-
* @param [params] - params
|
|
2213
|
+
* Convert a legacy flat config to a ThemeDefinition.
|
|
2214
|
+
* Legacy format: { base: "#fff", text: "#000", primary: "oklch(0.6 0.2 250)", ... }
|
|
2415
2215
|
*/
|
|
2416
|
-
|
|
2417
|
-
|
|
2216
|
+
function legacyConfigToDefinition(id, name, config) {
|
|
2217
|
+
const bg = parseColor(config.base ?? config.background ?? DEFAULT_COLORS.background);
|
|
2218
|
+
const fg = parseColor(config.text ?? config.foreground ?? DEFAULT_COLORS.foreground);
|
|
2219
|
+
const primary = parseColor(config.primary ?? DEFAULT_COLORS.primary);
|
|
2220
|
+
const secondary = parseColor(config.secondary ?? DEFAULT_COLORS.secondary);
|
|
2221
|
+
const accent = parseColor(config.accent ?? DEFAULT_COLORS.accent);
|
|
2222
|
+
const muted = parseColor(config.muted ?? DEFAULT_COLORS.muted);
|
|
2223
|
+
const destructive = parseColor(config.destructive ?? DEFAULT_COLORS.destructive);
|
|
2224
|
+
const mutedFg = parseColor(config.mutedForeground ?? DEFAULT_COLORS.mutedForeground);
|
|
2225
|
+
return {
|
|
2226
|
+
id: String(id),
|
|
2227
|
+
name,
|
|
2228
|
+
light: {
|
|
2229
|
+
background: {
|
|
2230
|
+
base: bg,
|
|
2231
|
+
foreground: fg
|
|
2232
|
+
},
|
|
2233
|
+
foreground: {
|
|
2234
|
+
base: fg,
|
|
2235
|
+
foreground: bg
|
|
2236
|
+
},
|
|
2237
|
+
primary: {
|
|
2238
|
+
base: primary,
|
|
2239
|
+
foreground: getForegroundColor(fg, primary)
|
|
2240
|
+
},
|
|
2241
|
+
secondary: {
|
|
2242
|
+
base: secondary,
|
|
2243
|
+
foreground: getForegroundColor(fg, secondary)
|
|
2244
|
+
},
|
|
2245
|
+
accent: {
|
|
2246
|
+
base: accent,
|
|
2247
|
+
foreground: getForegroundColor(fg, accent)
|
|
2248
|
+
},
|
|
2249
|
+
muted: {
|
|
2250
|
+
base: muted,
|
|
2251
|
+
foreground: mutedFg
|
|
2252
|
+
},
|
|
2253
|
+
destructive: {
|
|
2254
|
+
base: destructive,
|
|
2255
|
+
foreground: getForegroundColor(fg, destructive)
|
|
2256
|
+
}
|
|
2257
|
+
},
|
|
2258
|
+
dark: {},
|
|
2259
|
+
fontSizes: {
|
|
2260
|
+
extraSmall: config.extraSmall ?? DEFAULT_FONT_SIZES.extraSmall,
|
|
2261
|
+
small: config.small ?? DEFAULT_FONT_SIZES.small,
|
|
2262
|
+
regular: config.regular ?? DEFAULT_FONT_SIZES.regular,
|
|
2263
|
+
large: config.large ?? DEFAULT_FONT_SIZES.large,
|
|
2264
|
+
extraLarge: config.extraLarge ?? DEFAULT_FONT_SIZES.extraLarge,
|
|
2265
|
+
giant: config.giant ?? DEFAULT_FONT_SIZES.giant
|
|
2266
|
+
},
|
|
2267
|
+
fontFamilies: {
|
|
2268
|
+
header: config.headerFont ?? DEFAULT_FONT_FAMILIES.header,
|
|
2269
|
+
body: config.bodyFont ?? DEFAULT_FONT_FAMILIES.body
|
|
2270
|
+
},
|
|
2271
|
+
spacing: config.globalSpacing ?? "0.25rem",
|
|
2272
|
+
radii: {
|
|
2273
|
+
small: config.radiusSmall ?? DEFAULT_RADII.small,
|
|
2274
|
+
medium: config.radiusMedium ?? DEFAULT_RADII.medium,
|
|
2275
|
+
large: config.radiusLarge ?? DEFAULT_RADII.large,
|
|
2276
|
+
extraLarge: config.radiusExtraLarge ?? DEFAULT_RADII.extraLarge
|
|
2277
|
+
}
|
|
2278
|
+
};
|
|
2418
2279
|
}
|
|
2419
2280
|
/**
|
|
2420
|
-
*
|
|
2421
|
-
*
|
|
2422
|
-
*
|
|
2423
|
-
* @param client - Fetch client instance
|
|
2424
|
-
* @param body - body
|
|
2281
|
+
* Build a ThemeDefinition from a single API theme object.
|
|
2282
|
+
* Handles both new structured format and legacy flat format.
|
|
2425
2283
|
*/
|
|
2426
|
-
|
|
2427
|
-
|
|
2284
|
+
function buildThemeDefinition(theme) {
|
|
2285
|
+
const config = theme.config ?? {};
|
|
2286
|
+
if (isNewThemeFormat(config)) return deserialiseTheme({
|
|
2287
|
+
...config,
|
|
2288
|
+
id: String(theme.id),
|
|
2289
|
+
name: theme.name ?? "Untitled Theme"
|
|
2290
|
+
});
|
|
2291
|
+
return legacyConfigToDefinition(theme.id, theme.name ?? "Untitled Theme", config);
|
|
2428
2292
|
}
|
|
2429
2293
|
/**
|
|
2430
|
-
*
|
|
2431
|
-
*
|
|
2432
|
-
*
|
|
2433
|
-
* @param client - Fetch client instance
|
|
2434
|
-
* @param id - id
|
|
2294
|
+
* Transform raw API themes to ThemeDefinition[].
|
|
2295
|
+
* Catches and logs errors per theme (graceful degradation).
|
|
2435
2296
|
*/
|
|
2436
|
-
|
|
2437
|
-
return
|
|
2297
|
+
function transformThemes(themes) {
|
|
2298
|
+
return themes.flatMap((theme) => {
|
|
2299
|
+
try {
|
|
2300
|
+
return [buildThemeDefinition(theme)];
|
|
2301
|
+
} catch (error) {
|
|
2302
|
+
console.error(`[theme] Failed to build theme id=${theme.id}:`, error);
|
|
2303
|
+
return [];
|
|
2304
|
+
}
|
|
2305
|
+
});
|
|
2438
2306
|
}
|
|
2439
2307
|
/**
|
|
2440
|
-
*
|
|
2441
|
-
*
|
|
2442
|
-
*
|
|
2443
|
-
* @param client - Fetch client instance
|
|
2444
|
-
* @param body - body
|
|
2308
|
+
* Get the active theme ID from a list of raw API themes.
|
|
2309
|
+
* Falls back to the first theme if none is marked active.
|
|
2445
2310
|
*/
|
|
2446
|
-
|
|
2447
|
-
|
|
2311
|
+
function getActiveThemeId(themes) {
|
|
2312
|
+
const active = themes.find((t) => t.active) ?? themes[0];
|
|
2313
|
+
return active ? String(active.id) : void 0;
|
|
2448
2314
|
}
|
|
2449
2315
|
//#endregion
|
|
2450
|
-
//#region src/
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2316
|
+
//#region ../core/src/theme/theme-applicator.ts
|
|
2317
|
+
const STYLE_PREFIX = "theme-style-";
|
|
2318
|
+
const FONT_LINK_PREFIX = "theme-font-";
|
|
2319
|
+
const SYSTEM_FONTS = new Set([
|
|
2320
|
+
"sans-serif",
|
|
2321
|
+
"serif",
|
|
2322
|
+
"monospace",
|
|
2323
|
+
"cursive",
|
|
2324
|
+
"fantasy",
|
|
2325
|
+
"system-ui",
|
|
2326
|
+
"ui-sans-serif",
|
|
2327
|
+
"ui-serif",
|
|
2328
|
+
"ui-monospace"
|
|
2329
|
+
]);
|
|
2330
|
+
/** Build a Google Fonts CSS2 URL for a given font family with all weights. */
|
|
2331
|
+
function buildGoogleFontUrl(family) {
|
|
2332
|
+
return `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family).replace(/%20/g, "+")}:wght@100;200;300;400;500;600;700;800;900&display=swap`;
|
|
2461
2333
|
}
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
position: raw.position ?? 0
|
|
2468
|
-
};
|
|
2334
|
+
/** Check if a font family value needs to be loaded (i.e. is not a CSS var or system font). */
|
|
2335
|
+
function isLoadableFont(value) {
|
|
2336
|
+
if (!value) return false;
|
|
2337
|
+
if (value.startsWith("var(")) return false;
|
|
2338
|
+
return !SYSTEM_FONTS.has(value.toLowerCase());
|
|
2469
2339
|
}
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
product_id: raw.product_id ?? 0,
|
|
2474
|
-
product_name: raw.product_name ?? null,
|
|
2475
|
-
product_image_url: raw.product_image_url ?? null,
|
|
2476
|
-
position: raw.position ?? 0,
|
|
2477
|
-
created_at: raw.created_at ?? null
|
|
2478
|
-
};
|
|
2340
|
+
/** Deterministic link element ID for a font family. */
|
|
2341
|
+
function fontLinkId(family) {
|
|
2342
|
+
return `${FONT_LINK_PREFIX}${family.replace(/\s+/g, "-").toLowerCase()}`;
|
|
2479
2343
|
}
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2344
|
+
/**
|
|
2345
|
+
* Inject or update `<link>` elements for Google Fonts used by the theme.
|
|
2346
|
+
* Removes links for fonts that are no longer referenced.
|
|
2347
|
+
*/
|
|
2348
|
+
function loadThemeFonts(theme) {
|
|
2349
|
+
if (typeof document === "undefined") return;
|
|
2350
|
+
const fontsToLoad = /* @__PURE__ */ new Set();
|
|
2351
|
+
for (const key of FONT_FAMILY_KEYS) {
|
|
2352
|
+
const value = theme.fontFamilies[key];
|
|
2353
|
+
if (isLoadableFont(value)) fontsToLoad.add(value);
|
|
2354
|
+
}
|
|
2355
|
+
document.querySelectorAll(`link[id^="${FONT_LINK_PREFIX}"]`).forEach((link) => {
|
|
2356
|
+
const owners = link.getAttribute("data-font-theme-ids")?.split(",") ?? [];
|
|
2357
|
+
if (!owners.includes(theme.id)) return;
|
|
2358
|
+
const fontName = link.getAttribute("data-font-family");
|
|
2359
|
+
if (fontName && !fontsToLoad.has(fontName)) {
|
|
2360
|
+
const remaining = owners.filter((id) => id !== theme.id);
|
|
2361
|
+
if (remaining.length === 0) link.remove();
|
|
2362
|
+
else link.setAttribute("data-font-theme-ids", remaining.join(","));
|
|
2363
|
+
}
|
|
2364
|
+
});
|
|
2365
|
+
for (const family of fontsToLoad) {
|
|
2366
|
+
const id = fontLinkId(family);
|
|
2367
|
+
const existing = document.getElementById(id);
|
|
2368
|
+
if (existing) {
|
|
2369
|
+
const owners = existing.getAttribute("data-font-theme-ids")?.split(",") ?? [];
|
|
2370
|
+
if (!owners.includes(theme.id)) existing.setAttribute("data-font-theme-ids", [...owners, theme.id].join(","));
|
|
2371
|
+
} else {
|
|
2372
|
+
const link = document.createElement("link");
|
|
2373
|
+
link.id = id;
|
|
2374
|
+
link.rel = "stylesheet";
|
|
2375
|
+
link.href = buildGoogleFontUrl(family);
|
|
2376
|
+
link.setAttribute("data-font-family", family);
|
|
2377
|
+
link.setAttribute("data-font-theme-ids", theme.id);
|
|
2378
|
+
document.head.appendChild(link);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2486
2381
|
}
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
} })).link ?? {});
|
|
2510
|
-
},
|
|
2511
|
-
updateLink: async (linkId, body) => {
|
|
2512
|
-
return mapLink((await mysite_links_update(client, linkId, { link: {
|
|
2513
|
-
title: body.title,
|
|
2514
|
-
url: body.url
|
|
2515
|
-
} })).link ?? {});
|
|
2516
|
-
},
|
|
2517
|
-
deleteLink: async (linkId) => {
|
|
2518
|
-
await mysite_links_destroy(client, linkId);
|
|
2519
|
-
},
|
|
2520
|
-
reorderLinks: async (orderedIds) => {
|
|
2521
|
-
return ((await mysite_links_bulk_reorder(client, { ordered_ids: orderedIds })).links ?? []).map(mapLink);
|
|
2522
|
-
},
|
|
2523
|
-
listFavorites: async () => {
|
|
2524
|
-
return ((await mysite_favorites_list(client)).favorites ?? []).map(mapFavorite);
|
|
2525
|
-
},
|
|
2526
|
-
addFavorite: async (body) => {
|
|
2527
|
-
return mapFavorite((await mysite_favorites_create(client, { favorite: { product_id: body.product_id } })).favorite ?? {});
|
|
2528
|
-
},
|
|
2529
|
-
deleteFavorite: async (favoriteId) => {
|
|
2530
|
-
await mysite_favorites_destroy(client, favoriteId);
|
|
2531
|
-
},
|
|
2532
|
-
reorderFavorites: async (orderedIds) => {
|
|
2533
|
-
return ((await mysite_favorites_bulk_reorder(client, { ordered_ids: orderedIds })).favorites ?? []).map(mapFavorite);
|
|
2534
|
-
},
|
|
2535
|
-
listThemes: async () => {
|
|
2536
|
-
return ((await mysite_themes_list(client)).themes ?? []).map(mapTheme);
|
|
2382
|
+
/** Remove all font `<link>` elements injected by the theme system. */
|
|
2383
|
+
function removeAllFontLinks() {
|
|
2384
|
+
if (typeof document === "undefined") return;
|
|
2385
|
+
document.querySelectorAll(`link[id^="${FONT_LINK_PREFIX}"]`).forEach((el) => el.remove());
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Inject or update a `<style>` element in `<head>` for the given theme.
|
|
2389
|
+
* The element ID is deterministic (`theme-style-{themeId}`) so repeated calls
|
|
2390
|
+
* for the same theme are idempotent — the existing element is updated in place.
|
|
2391
|
+
* Also loads Google Fonts referenced by the theme's font families.
|
|
2392
|
+
* No-op when `document` is unavailable (SSR).
|
|
2393
|
+
*/
|
|
2394
|
+
function applyTheme(theme, options) {
|
|
2395
|
+
if (typeof document === "undefined") return;
|
|
2396
|
+
try {
|
|
2397
|
+
loadThemeFonts(theme);
|
|
2398
|
+
const styleId = `${STYLE_PREFIX}${theme.id}`;
|
|
2399
|
+
let el = document.getElementById(styleId);
|
|
2400
|
+
if (!el) {
|
|
2401
|
+
el = document.createElement("style");
|
|
2402
|
+
el.id = styleId;
|
|
2403
|
+
document.head.appendChild(el);
|
|
2537
2404
|
}
|
|
2538
|
-
|
|
2405
|
+
el.textContent = generateThemeCSS(theme, options);
|
|
2406
|
+
} catch (error) {
|
|
2407
|
+
console.error(`[theme] applyTheme failed for "${theme.id}":`, error);
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
/** Remove an injected theme stylesheet and clean up font link ownership. No-op during SSR. */
|
|
2411
|
+
function removeTheme(themeId) {
|
|
2412
|
+
if (typeof document === "undefined") return;
|
|
2413
|
+
document.getElementById(`${STYLE_PREFIX}${themeId}`)?.remove();
|
|
2414
|
+
document.querySelectorAll(`link[id^="${FONT_LINK_PREFIX}"]`).forEach((link) => {
|
|
2415
|
+
const remaining = (link.getAttribute("data-font-theme-ids")?.split(",") ?? []).filter((id) => id !== themeId);
|
|
2416
|
+
if (remaining.length === 0) link.remove();
|
|
2417
|
+
else link.setAttribute("data-font-theme-ids", remaining.join(","));
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
/** Remove all injected theme stylesheets and font links. No-op during SSR. */
|
|
2421
|
+
function removeAllThemes() {
|
|
2422
|
+
if (typeof document === "undefined") return;
|
|
2423
|
+
document.querySelectorAll(`style[id^="${STYLE_PREFIX}"]`).forEach((el) => el.remove());
|
|
2424
|
+
removeAllFontLinks();
|
|
2539
2425
|
}
|
|
2540
2426
|
//#endregion
|
|
2541
2427
|
//#region src/providers/FluidThemeProvider.tsx
|
|
@@ -2942,18 +2828,6 @@ Object.defineProperty(exports, "buildThemeDefinition", {
|
|
|
2942
2828
|
return buildThemeDefinition;
|
|
2943
2829
|
}
|
|
2944
2830
|
});
|
|
2945
|
-
Object.defineProperty(exports, "createFetchClient", {
|
|
2946
|
-
enumerable: true,
|
|
2947
|
-
get: function() {
|
|
2948
|
-
return createFetchClient;
|
|
2949
|
-
}
|
|
2950
|
-
});
|
|
2951
|
-
Object.defineProperty(exports, "createFluidClient", {
|
|
2952
|
-
enumerable: true,
|
|
2953
|
-
get: function() {
|
|
2954
|
-
return createFluidClient;
|
|
2955
|
-
}
|
|
2956
|
-
});
|
|
2957
2831
|
Object.defineProperty(exports, "createPersister", {
|
|
2958
2832
|
enumerable: true,
|
|
2959
2833
|
get: function() {
|
|
@@ -3038,12 +2912,6 @@ Object.defineProperty(exports, "mergeDarkOverrides", {
|
|
|
3038
2912
|
return mergeDarkOverrides;
|
|
3039
2913
|
}
|
|
3040
2914
|
});
|
|
3041
|
-
Object.defineProperty(exports, "normalizeComponentTree", {
|
|
3042
|
-
enumerable: true,
|
|
3043
|
-
get: function() {
|
|
3044
|
-
return normalizeComponentTree;
|
|
3045
|
-
}
|
|
3046
|
-
});
|
|
3047
2915
|
Object.defineProperty(exports, "parseColor", {
|
|
3048
2916
|
enumerable: true,
|
|
3049
2917
|
get: function() {
|
|
@@ -3074,30 +2942,6 @@ Object.defineProperty(exports, "serialiseTheme", {
|
|
|
3074
2942
|
return serialiseTheme;
|
|
3075
2943
|
}
|
|
3076
2944
|
});
|
|
3077
|
-
Object.defineProperty(exports, "toNavigationItem", {
|
|
3078
|
-
enumerable: true,
|
|
3079
|
-
get: function() {
|
|
3080
|
-
return toNavigationItem;
|
|
3081
|
-
}
|
|
3082
|
-
});
|
|
3083
|
-
Object.defineProperty(exports, "toRawManifest", {
|
|
3084
|
-
enumerable: true,
|
|
3085
|
-
get: function() {
|
|
3086
|
-
return toRawManifest;
|
|
3087
|
-
}
|
|
3088
|
-
});
|
|
3089
|
-
Object.defineProperty(exports, "toScreenDefinition", {
|
|
3090
|
-
enumerable: true,
|
|
3091
|
-
get: function() {
|
|
3092
|
-
return toScreenDefinition;
|
|
3093
|
-
}
|
|
3094
|
-
});
|
|
3095
|
-
Object.defineProperty(exports, "transformManifestToRepAppData", {
|
|
3096
|
-
enumerable: true,
|
|
3097
|
-
get: function() {
|
|
3098
|
-
return transformManifestToRepAppData;
|
|
3099
|
-
}
|
|
3100
|
-
});
|
|
3101
2945
|
Object.defineProperty(exports, "transformThemes", {
|
|
3102
2946
|
enumerable: true,
|
|
3103
2947
|
get: function() {
|
|
@@ -3147,4 +2991,4 @@ Object.defineProperty(exports, "widgetPropertySchemas", {
|
|
|
3147
2991
|
}
|
|
3148
2992
|
});
|
|
3149
2993
|
|
|
3150
|
-
//# sourceMappingURL=FluidProvider-
|
|
2994
|
+
//# sourceMappingURL=FluidProvider-BtGi2jJt.cjs.map
|