@fluid-app/portal-sdk 0.1.64 → 0.1.65

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.
@@ -24,7 +24,7 @@ function GooglePlayIcon() {
24
24
  }
25
25
  function AppStoreButtons({ appstoreUrl, playstoreUrl }) {
26
26
  return /* @__PURE__ */ jsxs("div", {
27
- className: `grid gap-4 ${playstoreUrl ? "grid-cols-2" : "grid-cols-1"}`,
27
+ className: `grid gap-4 ${playstoreUrl ? "grid-cols-1 sm:grid-cols-2" : "grid-cols-1"}`,
28
28
  children: [/* @__PURE__ */ jsx("a", {
29
29
  href: appstoreUrl,
30
30
  target: "_blank",
@@ -208,4 +208,4 @@ function AppDownloadScreen() {
208
208
  //#endregion
209
209
  export { appDownloadScreenPropertySchema as n, AppDownloadScreen as t };
210
210
 
211
- //# sourceMappingURL=AppDownloadScreen-pm8fLxZX.mjs.map
211
+ //# sourceMappingURL=AppDownloadScreen-BXS02LRb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppDownloadScreen-BXS02LRb.mjs","names":["AppDownloadScreen","AppDownloadScreenUI"],"sources":["../../app-download-ui/src/components/AppStoreButtons.tsx","../../app-download-ui/src/components/QrCodeSection.tsx","../../app-download-ui/src/components/ImageSection.tsx","../../app-download-ui/src/lib/cn.ts","../../app-download-ui/src/components/AppDownloadScreen.tsx","../src/screens/AppDownloadScreen.tsx"],"sourcesContent":["import { ExternalLink } from \"lucide-react\";\n\nexport interface AppStoreButtonsProps {\n appstoreUrl: string;\n playstoreUrl?: string;\n}\n\nfunction AppleIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 384 512\"\n fill=\"currentColor\"\n className=\"size-5\"\n >\n <path d=\"M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-62.1 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z\" />\n </svg>\n );\n}\n\nfunction GooglePlayIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 512 512\"\n fill=\"currentColor\"\n className=\"size-5\"\n >\n <path d=\"M325.3 234.3L104.6 13l280.8 161.2-60.1 60.1zM47 0C34 6.8 25.3 19.2 25.3 35.3v441.3c0 16.1 8.7 28.5 21.7 35.3l256.6-256L47 0zm425.2 225.6l-58.9-34.1-65.7 64.5 65.7 64.5 60.1-34.1c18-14.3 18-46.5-1.2-60.8zM104.6 499l280.8-161.2-60.1-60.1L104.6 499z\" />\n </svg>\n );\n}\n\nexport function AppStoreButtons({\n appstoreUrl,\n playstoreUrl,\n}: AppStoreButtonsProps) {\n return (\n <div\n className={`grid gap-4 ${playstoreUrl ? \"grid-cols-1 sm:grid-cols-2\" : \"grid-cols-1\"}`}\n >\n <a\n href={appstoreUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <AppleIcon />\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Open\n <br />\n App Store\n </div>\n </div>\n <div className=\"flex items-start\">\n <ExternalLink className=\"size-4\" />\n </div>\n </div>\n </a>\n\n {playstoreUrl && (\n <a\n href={playstoreUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <GooglePlayIcon />\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Open\n <br />\n Play Store\n </div>\n </div>\n <div className=\"flex items-start\">\n <ExternalLink className=\"size-4\" />\n </div>\n </div>\n </a>\n )}\n </div>\n );\n}\n","export interface QrCodeSectionProps {\n companyId: number;\n}\n\nexport function QrCodeSection({ companyId }: QrCodeSectionProps) {\n const redirectUrl = `https://api.fluid.app/mobile_app_download/${companyId}`;\n const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(redirectUrl)}`;\n\n return (\n <a\n href={redirectUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex items-end justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Scan to\n <br />\n Download\n </div>\n </div>\n <div className=\"flex\">\n <img\n src={qrCodeUrl}\n alt=\"QR code for mobile app download\"\n className=\"h-16 w-16\"\n loading=\"lazy\"\n />\n </div>\n </div>\n </a>\n );\n}\n","const DEFAULT_IMAGE =\n \"https://assets.fluid.app/fluid-admin/images/we-commerce/iphone_product.png\";\n\nconst DEFAULT_LOGO =\n \"https://assets.fluid.app/fluid-admin/images/we-commerce/we-commerce.png\";\n\nexport interface ImageSectionProps {\n logoUrl?: string | null;\n screenshotUrl?: string | null;\n appName?: string;\n}\n\nexport function ImageSection({\n logoUrl,\n screenshotUrl,\n appName = \"App\",\n}: ImageSectionProps) {\n return (\n <div className=\"flex flex-col overflow-hidden\">\n <div className=\"-mb-px flex flex-1 flex-col\">\n <div className=\"flex h-20 px-3 pt-8\">\n <img\n src={logoUrl || DEFAULT_LOGO}\n alt={appName}\n width={150}\n height={150}\n className=\"object-contain\"\n />\n </div>\n <div\n className={\n screenshotUrl\n ? \"mt-8 flex flex-1 items-end justify-start\"\n : \"-ml-20 flex flex-1 items-end justify-start\"\n }\n >\n <img\n src={screenshotUrl || DEFAULT_IMAGE}\n alt=\"Mobile app interface\"\n width={480}\n height={500}\n className=\"h-full w-auto object-cover\"\n />\n </div>\n </div>\n </div>\n );\n}\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import { AppStoreButtons } from \"./AppStoreButtons\";\nimport { QrCodeSection } from \"./QrCodeSection\";\nimport { ImageSection } from \"./ImageSection\";\nimport { cn } from \"../lib/cn\";\n\nconst DEFAULT_APPSTORE_URL =\n \"https://apps.apple.com/app/wecommerce/id1564463740\";\nconst DEFAULT_PLAYSTORE_URL =\n \"https://play.google.com/store/apps/details?id=com.fluid.crm\";\n\nexport interface AppDownloadScreenProps {\n companyId: number;\n appstoreUrl?: string | null;\n playstoreUrl?: string | null;\n logoUrl?: string | null;\n screenshotUrl?: string | null;\n appName?: string;\n className?: string;\n}\n\nexport function AppDownloadScreen({\n companyId,\n appstoreUrl,\n playstoreUrl,\n logoUrl,\n screenshotUrl,\n appName,\n className,\n}: AppDownloadScreenProps) {\n const resolvedAppstoreUrl = appstoreUrl?.trim() || DEFAULT_APPSTORE_URL;\n const resolvedPlaystoreUrl = playstoreUrl?.trim() || DEFAULT_PLAYSTORE_URL;\n\n return (\n <div\n className={cn(\"flex h-full items-center justify-center p-4\", className)}\n >\n <div className=\"bg-muted relative mx-4 w-full max-w-6xl overflow-hidden rounded-xl px-6 shadow-xl\">\n <div className=\"grid h-150 w-full grid-cols-1 lg:grid-cols-2\">\n {/* Left side - Images */}\n <ImageSection\n logoUrl={logoUrl}\n screenshotUrl={screenshotUrl}\n appName={appName}\n />\n\n {/* Right side - Content */}\n <div className=\"space-y-2 pt-8 pb-8\">\n <h1 className=\"mt-12 text-left text-[32px] leading-tight font-black text-gray-900\">\n EVERYTHING YOU NEED\n </h1>\n <h1 className=\"-mt-2 text-left text-[32px] leading-tight font-black text-gray-900\">\n AT YOUR FINGERTIPS\n </h1>\n <p className=\"-mt-1 text-left text-[14px] leading-relaxed text-gray-600\">\n Everything you need at your fingertips. Run your business from one\n place while on the go. Share, message, sell, and manage from the\n mobile app.\n </p>\n\n <h3 className=\"mt-16 text-left text-[12px] font-bold text-gray-900\">\n Download the App\n </h3>\n\n <AppStoreButtons\n appstoreUrl={resolvedAppstoreUrl}\n playstoreUrl={resolvedPlaystoreUrl}\n />\n\n <QrCodeSection companyId={companyId} />\n </div>\n </div>\n </div>\n </div>\n );\n}\n","import type { WidgetPropertySchema } from \"../registries/property-schema-types\";\nimport { AppDownloadScreen as AppDownloadScreenUI } from \"@fluid-app/portal-app-download-ui\";\nimport { useCurrentUser } from \"../hooks/use-current-user\";\n\nexport const appDownloadScreenPropertySchema = {\n widgetType: \"AppDownloadScreen\",\n displayName: \"App Download Screen\",\n fields: [],\n} as const satisfies WidgetPropertySchema;\n\nexport function AppDownloadScreen(): React.JSX.Element {\n const { data: user } = useCurrentUser();\n\n return (\n <AppDownloadScreenUI\n companyId={user?.company?.id ?? 0}\n appstoreUrl={user?.company?.appstore_url}\n playstoreUrl={user?.company?.playstore_url}\n logoUrl={user?.company?.logo_url}\n />\n );\n}\n"],"mappings":";;;;;;AAOA,SAAS,YAAY;AACnB,QACE,oBAAC,OAAD;EACE,OAAM;EACN,SAAQ;EACR,MAAK;EACL,WAAU;YAEV,oBAAC,QAAD,EAAM,GAAE,ubAAwb,CAAA;EAC5b,CAAA;;AAIV,SAAS,iBAAiB;AACxB,QACE,oBAAC,OAAD;EACE,OAAM;EACN,SAAQ;EACR,MAAK;EACL,WAAU;YAEV,oBAAC,QAAD,EAAM,GAAE,0PAA2P,CAAA;EAC/P,CAAA;;AAIV,SAAgB,gBAAgB,EAC9B,aACA,gBACuB;AACvB,QACE,qBAAC,OAAD;EACE,WAAW,cAAc,eAAe,+BAA+B;YADzE,CAGE,oBAAC,KAAD;GACE,MAAM;GACN,QAAO;GACP,KAAI;GACJ,WAAU;aAEV,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,oBAAC,WAAD,EAAa,CAAA,EACb,qBAAC,OAAD;MAAK,WAAU;gBAAf;OAAsD;OAEpD,oBAAC,MAAD,EAAM,CAAA;;OAEF;QACF;QACN,oBAAC,OAAD;KAAK,WAAU;eACb,oBAAC,cAAD,EAAc,WAAU,UAAW,CAAA;KAC/B,CAAA,CACF;;GACJ,CAAA,EAEH,gBACC,oBAAC,KAAD;GACE,MAAM;GACN,QAAO;GACP,KAAI;GACJ,WAAU;aAEV,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,oBAAC,gBAAD,EAAkB,CAAA,EAClB,qBAAC,OAAD;MAAK,WAAU;gBAAf;OAAsD;OAEpD,oBAAC,MAAD,EAAM,CAAA;;OAEF;QACF;QACN,oBAAC,OAAD;KAAK,WAAU;eACb,oBAAC,cAAD,EAAc,WAAU,UAAW,CAAA;KAC/B,CAAA,CACF;;GACJ,CAAA,CAEF;;;;;AChFV,SAAgB,cAAc,EAAE,aAAiC;CAC/D,MAAM,cAAc,6CAA6C;CACjE,MAAM,YAAY,iEAAiE,mBAAmB,YAAY;AAElH,QACE,oBAAC,KAAD;EACE,MAAM;EACN,QAAO;EACP,KAAI;EACJ,WAAU;YAEV,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,OAAD;IAAK,WAAU;cACb,qBAAC,OAAD;KAAK,WAAU;eAAf;MAAsD;MAEpD,oBAAC,MAAD,EAAM,CAAA;;MAEF;;IACF,CAAA,EACN,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,OAAD;KACE,KAAK;KACL,KAAI;KACJ,WAAU;KACV,SAAQ;KACR,CAAA;IACE,CAAA,CACF;;EACJ,CAAA;;;;AChCR,MAAM,gBACJ;AAEF,MAAM,eACJ;AAQF,SAAgB,aAAa,EAC3B,SACA,eACA,UAAU,SACU;AACpB,QACE,oBAAC,OAAD;EAAK,WAAU;YACb,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,OAAD;KACE,KAAK,WAAW;KAChB,KAAK;KACL,OAAO;KACP,QAAQ;KACR,WAAU;KACV,CAAA;IACE,CAAA,EACN,oBAAC,OAAD;IACE,WACE,gBACI,6CACA;cAGN,oBAAC,OAAD;KACE,KAAK,iBAAiB;KACtB,KAAI;KACJ,OAAO;KACP,QAAQ;KACR,WAAU;KACV,CAAA;IACE,CAAA,CACF;;EACF,CAAA;;;;AC1CV,SAAgB,GAAG,GAAG,QAAsB;AAC1C,QAAO,QAAQ,KAAK,OAAO,CAAC;;;;ACC9B,MAAM,uBACJ;AACF,MAAM,wBACJ;AAYF,SAAgBA,oBAAkB,EAChC,WACA,aACA,cACA,SACA,eACA,SACA,aACyB;CACzB,MAAM,sBAAsB,aAAa,MAAM,IAAI;CACnD,MAAM,uBAAuB,cAAc,MAAM,IAAI;AAErD,QACE,oBAAC,OAAD;EACE,WAAW,GAAG,+CAA+C,UAAU;YAEvE,oBAAC,OAAD;GAAK,WAAU;aACb,qBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,oBAAC,cAAD;KACW;KACM;KACN;KACT,CAAA,EAGF,qBAAC,OAAD;KAAK,WAAU;eAAf;MACE,oBAAC,MAAD;OAAI,WAAU;iBAAqE;OAE9E,CAAA;MACL,oBAAC,MAAD;OAAI,WAAU;iBAAqE;OAE9E,CAAA;MACL,oBAAC,KAAD;OAAG,WAAU;iBAA4D;OAIrE,CAAA;MAEJ,oBAAC,MAAD;OAAI,WAAU;iBAAsD;OAE/D,CAAA;MAEL,oBAAC,iBAAD;OACE,aAAa;OACb,cAAc;OACd,CAAA;MAEF,oBAAC,eAAD,EAA0B,WAAa,CAAA;MACnC;OACF;;GACF,CAAA;EACF,CAAA;;;;ACpEV,MAAa,kCAAkC;CAC7C,YAAY;CACZ,aAAa;CACb,QAAQ,EAAE;CACX;AAED,SAAgB,oBAAuC;CACrD,MAAM,EAAE,MAAM,SAAS,gBAAgB;AAEvC,QACE,oBAACC,qBAAD;EACE,WAAW,MAAM,SAAS,MAAM;EAChC,aAAa,MAAM,SAAS;EAC5B,cAAc,MAAM,SAAS;EAC7B,SAAS,MAAM,SAAS;EACxB,CAAA"}
@@ -25,7 +25,7 @@ function GooglePlayIcon() {
25
25
  }
26
26
  function AppStoreButtons({ appstoreUrl, playstoreUrl }) {
27
27
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
28
- className: `grid gap-4 ${playstoreUrl ? "grid-cols-2" : "grid-cols-1"}`,
28
+ className: `grid gap-4 ${playstoreUrl ? "grid-cols-1 sm:grid-cols-2" : "grid-cols-1"}`,
29
29
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
30
30
  href: appstoreUrl,
31
31
  target: "_blank",
@@ -220,4 +220,4 @@ Object.defineProperty(exports, "appDownloadScreenPropertySchema", {
220
220
  }
221
221
  });
222
222
 
223
- //# sourceMappingURL=AppDownloadScreen-oMzxBETr.cjs.map
223
+ //# sourceMappingURL=AppDownloadScreen-BXWBVnQM.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppDownloadScreen-BXWBVnQM.cjs","names":["ExternalLink","AppDownloadScreen","useCurrentUser","AppDownloadScreenUI"],"sources":["../../app-download-ui/src/components/AppStoreButtons.tsx","../../app-download-ui/src/components/QrCodeSection.tsx","../../app-download-ui/src/components/ImageSection.tsx","../../app-download-ui/src/lib/cn.ts","../../app-download-ui/src/components/AppDownloadScreen.tsx","../src/screens/AppDownloadScreen.tsx"],"sourcesContent":["import { ExternalLink } from \"lucide-react\";\n\nexport interface AppStoreButtonsProps {\n appstoreUrl: string;\n playstoreUrl?: string;\n}\n\nfunction AppleIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 384 512\"\n fill=\"currentColor\"\n className=\"size-5\"\n >\n <path d=\"M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-62.1 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z\" />\n </svg>\n );\n}\n\nfunction GooglePlayIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 512 512\"\n fill=\"currentColor\"\n className=\"size-5\"\n >\n <path d=\"M325.3 234.3L104.6 13l280.8 161.2-60.1 60.1zM47 0C34 6.8 25.3 19.2 25.3 35.3v441.3c0 16.1 8.7 28.5 21.7 35.3l256.6-256L47 0zm425.2 225.6l-58.9-34.1-65.7 64.5 65.7 64.5 60.1-34.1c18-14.3 18-46.5-1.2-60.8zM104.6 499l280.8-161.2-60.1-60.1L104.6 499z\" />\n </svg>\n );\n}\n\nexport function AppStoreButtons({\n appstoreUrl,\n playstoreUrl,\n}: AppStoreButtonsProps) {\n return (\n <div\n className={`grid gap-4 ${playstoreUrl ? \"grid-cols-1 sm:grid-cols-2\" : \"grid-cols-1\"}`}\n >\n <a\n href={appstoreUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <AppleIcon />\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Open\n <br />\n App Store\n </div>\n </div>\n <div className=\"flex items-start\">\n <ExternalLink className=\"size-4\" />\n </div>\n </div>\n </a>\n\n {playstoreUrl && (\n <a\n href={playstoreUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <GooglePlayIcon />\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Open\n <br />\n Play Store\n </div>\n </div>\n <div className=\"flex items-start\">\n <ExternalLink className=\"size-4\" />\n </div>\n </div>\n </a>\n )}\n </div>\n );\n}\n","export interface QrCodeSectionProps {\n companyId: number;\n}\n\nexport function QrCodeSection({ companyId }: QrCodeSectionProps) {\n const redirectUrl = `https://api.fluid.app/mobile_app_download/${companyId}`;\n const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(redirectUrl)}`;\n\n return (\n <a\n href={redirectUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex items-end justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Scan to\n <br />\n Download\n </div>\n </div>\n <div className=\"flex\">\n <img\n src={qrCodeUrl}\n alt=\"QR code for mobile app download\"\n className=\"h-16 w-16\"\n loading=\"lazy\"\n />\n </div>\n </div>\n </a>\n );\n}\n","const DEFAULT_IMAGE =\n \"https://assets.fluid.app/fluid-admin/images/we-commerce/iphone_product.png\";\n\nconst DEFAULT_LOGO =\n \"https://assets.fluid.app/fluid-admin/images/we-commerce/we-commerce.png\";\n\nexport interface ImageSectionProps {\n logoUrl?: string | null;\n screenshotUrl?: string | null;\n appName?: string;\n}\n\nexport function ImageSection({\n logoUrl,\n screenshotUrl,\n appName = \"App\",\n}: ImageSectionProps) {\n return (\n <div className=\"flex flex-col overflow-hidden\">\n <div className=\"-mb-px flex flex-1 flex-col\">\n <div className=\"flex h-20 px-3 pt-8\">\n <img\n src={logoUrl || DEFAULT_LOGO}\n alt={appName}\n width={150}\n height={150}\n className=\"object-contain\"\n />\n </div>\n <div\n className={\n screenshotUrl\n ? \"mt-8 flex flex-1 items-end justify-start\"\n : \"-ml-20 flex flex-1 items-end justify-start\"\n }\n >\n <img\n src={screenshotUrl || DEFAULT_IMAGE}\n alt=\"Mobile app interface\"\n width={480}\n height={500}\n className=\"h-full w-auto object-cover\"\n />\n </div>\n </div>\n </div>\n );\n}\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import { AppStoreButtons } from \"./AppStoreButtons\";\nimport { QrCodeSection } from \"./QrCodeSection\";\nimport { ImageSection } from \"./ImageSection\";\nimport { cn } from \"../lib/cn\";\n\nconst DEFAULT_APPSTORE_URL =\n \"https://apps.apple.com/app/wecommerce/id1564463740\";\nconst DEFAULT_PLAYSTORE_URL =\n \"https://play.google.com/store/apps/details?id=com.fluid.crm\";\n\nexport interface AppDownloadScreenProps {\n companyId: number;\n appstoreUrl?: string | null;\n playstoreUrl?: string | null;\n logoUrl?: string | null;\n screenshotUrl?: string | null;\n appName?: string;\n className?: string;\n}\n\nexport function AppDownloadScreen({\n companyId,\n appstoreUrl,\n playstoreUrl,\n logoUrl,\n screenshotUrl,\n appName,\n className,\n}: AppDownloadScreenProps) {\n const resolvedAppstoreUrl = appstoreUrl?.trim() || DEFAULT_APPSTORE_URL;\n const resolvedPlaystoreUrl = playstoreUrl?.trim() || DEFAULT_PLAYSTORE_URL;\n\n return (\n <div\n className={cn(\"flex h-full items-center justify-center p-4\", className)}\n >\n <div className=\"bg-muted relative mx-4 w-full max-w-6xl overflow-hidden rounded-xl px-6 shadow-xl\">\n <div className=\"grid h-150 w-full grid-cols-1 lg:grid-cols-2\">\n {/* Left side - Images */}\n <ImageSection\n logoUrl={logoUrl}\n screenshotUrl={screenshotUrl}\n appName={appName}\n />\n\n {/* Right side - Content */}\n <div className=\"space-y-2 pt-8 pb-8\">\n <h1 className=\"mt-12 text-left text-[32px] leading-tight font-black text-gray-900\">\n EVERYTHING YOU NEED\n </h1>\n <h1 className=\"-mt-2 text-left text-[32px] leading-tight font-black text-gray-900\">\n AT YOUR FINGERTIPS\n </h1>\n <p className=\"-mt-1 text-left text-[14px] leading-relaxed text-gray-600\">\n Everything you need at your fingertips. Run your business from one\n place while on the go. Share, message, sell, and manage from the\n mobile app.\n </p>\n\n <h3 className=\"mt-16 text-left text-[12px] font-bold text-gray-900\">\n Download the App\n </h3>\n\n <AppStoreButtons\n appstoreUrl={resolvedAppstoreUrl}\n playstoreUrl={resolvedPlaystoreUrl}\n />\n\n <QrCodeSection companyId={companyId} />\n </div>\n </div>\n </div>\n </div>\n );\n}\n","import type { WidgetPropertySchema } from \"../registries/property-schema-types\";\nimport { AppDownloadScreen as AppDownloadScreenUI } from \"@fluid-app/portal-app-download-ui\";\nimport { useCurrentUser } from \"../hooks/use-current-user\";\n\nexport const appDownloadScreenPropertySchema = {\n widgetType: \"AppDownloadScreen\",\n displayName: \"App Download Screen\",\n fields: [],\n} as const satisfies WidgetPropertySchema;\n\nexport function AppDownloadScreen(): React.JSX.Element {\n const { data: user } = useCurrentUser();\n\n return (\n <AppDownloadScreenUI\n companyId={user?.company?.id ?? 0}\n appstoreUrl={user?.company?.appstore_url}\n playstoreUrl={user?.company?.playstore_url}\n logoUrl={user?.company?.logo_url}\n />\n );\n}\n"],"mappings":"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"}
@@ -32,6 +32,6 @@ require("./SpacerWidget-Br2IZFFv.cjs");
32
32
  require("./TableWidget-Cri6r5Yr.cjs");
33
33
  require("./ToDoWidget-Bv6POc5R.cjs");
34
34
  require("./VideoWidget-DnnuIvW1.cjs");
35
- const require_AppDownloadScreen = require("./AppDownloadScreen-oMzxBETr.cjs");
35
+ const require_AppDownloadScreen = require("./AppDownloadScreen-BXWBVnQM.cjs");
36
36
  exports.AppDownloadScreen = require_AppDownloadScreen.AppDownloadScreen;
37
37
  exports.appDownloadScreenPropertySchema = require_AppDownloadScreen.appDownloadScreenPropertySchema;
@@ -31,5 +31,6 @@ import "./SpacerWidget-DgVU58BC.mjs";
31
31
  import "./TableWidget-DjYabx1e.mjs";
32
32
  import "./ToDoWidget-Cmvf7I6c.mjs";
33
33
  import "./VideoWidget-51MxnPRZ.mjs";
34
- import { n as appDownloadScreenPropertySchema, t as AppDownloadScreen } from "./AppDownloadScreen-pm8fLxZX.mjs";
34
+ import "./use-current-user-DnBpWIw6.mjs";
35
+ import { n as appDownloadScreenPropertySchema, t as AppDownloadScreen } from "./AppDownloadScreen-BXS02LRb.mjs";
35
36
  export { AppDownloadScreen, appDownloadScreenPropertySchema };
@@ -32,6 +32,7 @@ import "./TableWidget-DjYabx1e.mjs";
32
32
  import "./ToDoWidget-Cmvf7I6c.mjs";
33
33
  import "./VideoWidget-51MxnPRZ.mjs";
34
34
  import "./ScreenHeaderContext-CrdfLGKk.mjs";
35
+ import "./AppNavigationContext-DJeNcP4Y.mjs";
35
36
  import "./sortable.esm-DreCqRxJ.mjs";
36
37
  import { n as mySiteScreenPropertySchema, t as MySiteScreen } from "./MySiteScreen-DR_xY95c.mjs";
37
38
  export { MySiteScreen, mySiteScreenPropertySchema };
@@ -32,6 +32,7 @@ import "./TableWidget-DjYabx1e.mjs";
32
32
  import "./ToDoWidget-Cmvf7I6c.mjs";
33
33
  import "./VideoWidget-51MxnPRZ.mjs";
34
34
  import "./ScreenHeaderContext-CrdfLGKk.mjs";
35
+ import "./AppNavigationContext-DJeNcP4Y.mjs";
35
36
  import "./order-detail-DkMYJvzl.mjs";
36
37
  import { n as ordersScreenPropertySchema, t as OrdersScreen } from "./OrdersScreen-CQzgIEa4.mjs";
37
38
  export { OrdersScreen, ordersScreenPropertySchema };
@@ -32,6 +32,8 @@ import "./TableWidget-DjYabx1e.mjs";
32
32
  import "./ToDoWidget-Cmvf7I6c.mjs";
33
33
  import "./VideoWidget-51MxnPRZ.mjs";
34
34
  import "./ScreenHeaderContext-CrdfLGKk.mjs";
35
+ import "./use-current-user-DnBpWIw6.mjs";
36
+ import "./AppNavigationContext-DJeNcP4Y.mjs";
35
37
  import "./dist-CMGXkSgZ.mjs";
36
38
  import "./es-CrIkZTQ3.mjs";
37
39
  import "./dist-Cl4FsM3V.mjs";
@@ -32,6 +32,8 @@ import "./TableWidget-DjYabx1e.mjs";
32
32
  import "./ToDoWidget-Cmvf7I6c.mjs";
33
33
  import "./VideoWidget-51MxnPRZ.mjs";
34
34
  import "./ScreenHeaderContext-CrdfLGKk.mjs";
35
+ import "./use-current-user-DnBpWIw6.mjs";
36
+ import "./AppNavigationContext-DJeNcP4Y.mjs";
35
37
  import "./dist-CMGXkSgZ.mjs";
36
38
  import "./es-CrIkZTQ3.mjs";
37
39
  import "./dist-Cl4FsM3V.mjs";
@@ -32,5 +32,7 @@ import "./TableWidget-DjYabx1e.mjs";
32
32
  import "./ToDoWidget-Cmvf7I6c.mjs";
33
33
  import "./VideoWidget-51MxnPRZ.mjs";
34
34
  import "./ScreenHeaderContext-CrdfLGKk.mjs";
35
+ import "./use-current-user-DnBpWIw6.mjs";
36
+ import "./AppNavigationContext-DJeNcP4Y.mjs";
35
37
  import { n as shopScreenPropertySchema, t as ShopScreen } from "./ShopScreen-DFWQY8hT.mjs";
36
38
  export { ShopScreen, shopScreenPropertySchema };
@@ -32,6 +32,7 @@ import "./TableWidget-DjYabx1e.mjs";
32
32
  import "./ToDoWidget-Cmvf7I6c.mjs";
33
33
  import "./VideoWidget-51MxnPRZ.mjs";
34
34
  import "./ScreenHeaderContext-CrdfLGKk.mjs";
35
+ import "./AppNavigationContext-DJeNcP4Y.mjs";
35
36
  import "./order-detail-DkMYJvzl.mjs";
36
37
  import { n as subscriptionsScreenPropertySchema, t as SubscriptionsScreen } from "./SubscriptionsScreen-50w7SgWX.mjs";
37
38
  import "./src-BakNjVTk.mjs";
package/dist/index.cjs CHANGED
@@ -56,7 +56,7 @@ require("./dist-oTn1xy1Z.cjs");
56
56
  const require_ProductsScreen = require("./ProductsScreen-BgVl8GAm.cjs");
57
57
  const require_MySiteScreen = require("./MySiteScreen-C-cS0_qZ.cjs");
58
58
  require("./UpgradeScreen-BbP-0Pcg.cjs");
59
- require("./AppDownloadScreen-oMzxBETr.cjs");
59
+ require("./AppDownloadScreen-BXWBVnQM.cjs");
60
60
  let react = require("react");
61
61
  react = require_chunk.__toESM(react);
62
62
  let _tanstack_react_query = require("@tanstack/react-query");
@@ -2414,7 +2414,7 @@ const ProductsScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => requ
2414
2414
  const ShareablesScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => require("./ShareablesScreen-8rSANBth.cjs")).then((m) => ({ default: m.ShareablesScreen })));
2415
2415
  const MySiteScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => require("./MySiteScreen-B_UnnWDX.cjs")).then((m) => ({ default: m.MySiteScreen })));
2416
2416
  const UpgradeScreen = (0, react.lazy)(() => Promise.resolve().then(() => require("./UpgradeScreen-DJKNVL-O.cjs")).then((m) => ({ default: m.UpgradeScreen })));
2417
- const AppDownloadScreen = (0, react.lazy)(() => Promise.resolve().then(() => require("./AppDownloadScreen-DW3cBnbI.cjs")).then((m) => ({ default: m.AppDownloadScreen })));
2417
+ const AppDownloadScreen = (0, react.lazy)(() => Promise.resolve().then(() => require("./AppDownloadScreen-BtwySuUY.cjs")).then((m) => ({ default: m.AppDownloadScreen })));
2418
2418
  const SYSTEM_SLUG_SCREEN_MAP = {
2419
2419
  profile: ProfileScreen$1,
2420
2420
  orders: OrdersScreen$1,
@@ -3825,7 +3825,7 @@ const screenPropertySchemas = {
3825
3825
  ShareablesScreen: () => Promise.resolve().then(() => require("./ShareablesScreen-8rSANBth.cjs")).then((m) => m.shareablesScreenPropertySchema),
3826
3826
  ShopScreen: () => Promise.resolve().then(() => require("./ShopScreen-DCKr0cbB.cjs")).then((m) => m.shopScreenPropertySchema),
3827
3827
  UpgradeScreen: () => Promise.resolve().then(() => require("./UpgradeScreen-DJKNVL-O.cjs")).then((m) => m.upgradeScreenPropertySchema),
3828
- AppDownloadScreen: () => Promise.resolve().then(() => require("./AppDownloadScreen-DW3cBnbI.cjs")).then((m) => m.appDownloadScreenPropertySchema)
3828
+ AppDownloadScreen: () => Promise.resolve().then(() => require("./AppDownloadScreen-BtwySuUY.cjs")).then((m) => m.appDownloadScreenPropertySchema)
3829
3829
  };
3830
3830
  /**
3831
3831
  * Core page template IDs
package/dist/index.mjs CHANGED
@@ -54,7 +54,7 @@ import "./sortable.esm-DreCqRxJ.mjs";
54
54
  import { n as productsScreenPropertySchema, t as ProductsScreen } from "./ProductsScreen-B0BsPJth.mjs";
55
55
  import { n as mySiteScreenPropertySchema, t as MySiteScreen } from "./MySiteScreen-DR_xY95c.mjs";
56
56
  import "./UpgradeScreen-D7LfdVSJ.mjs";
57
- import "./AppDownloadScreen-pm8fLxZX.mjs";
57
+ import "./AppDownloadScreen-BXS02LRb.mjs";
58
58
  import * as React$1 from "react";
59
59
  import { StrictMode, Suspense, createContext, forwardRef, lazy, memo, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
60
60
  import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -2402,17 +2402,17 @@ function AccountManageLayout({ children }) {
2402
2402
  //#endregion
2403
2403
  //#region src/shell/system-screen-map.ts
2404
2404
  const ProfileScreen$1 = lazy(() => import("./ProfileScreen-Tcqc5WfU.mjs").then((m) => ({ default: m.ProfileScreen })));
2405
- const OrdersScreen$1 = lazy(() => import("./OrdersScreen-CO9vm0Wj.mjs").then((m) => ({ default: m.OrdersScreen })));
2406
- const SubscriptionsScreen$1 = lazy(() => import("./SubscriptionsScreen-BVLHc7-p.mjs").then((m) => ({ default: m.SubscriptionsScreen })));
2405
+ const OrdersScreen$1 = lazy(() => import("./OrdersScreen-h7KX2dQo.mjs").then((m) => ({ default: m.OrdersScreen })));
2406
+ const SubscriptionsScreen$1 = lazy(() => import("./SubscriptionsScreen-DjG0cmvn.mjs").then((m) => ({ default: m.SubscriptionsScreen })));
2407
2407
  const MessagingScreen$1 = lazy(() => import("./MessagingScreen-CGS474IP.mjs").then((m) => ({ default: m.MessagingScreen })));
2408
2408
  const ContactsScreen$1 = lazy(() => import("./ContactsScreen-ruymx9Pi.mjs").then((m) => ({ default: m.ContactsScreen })));
2409
- const ShopScreen$1 = lazy(() => import("./ShopScreen-Bg696rqv.mjs").then((m) => ({ default: m.ShopScreen })));
2409
+ const ShopScreen$1 = lazy(() => import("./ShopScreen-CSpdAmLo.mjs").then((m) => ({ default: m.ShopScreen })));
2410
2410
  const CustomersScreen$1 = lazy(() => import("./CustomersScreen-D22G27ru.mjs").then((n) => n.n).then((m) => ({ default: m.CustomersScreen })));
2411
- const ProductsScreen$1 = lazy(() => import("./ProductsScreen-GOW8lxiN.mjs").then((m) => ({ default: m.ProductsScreen })));
2412
- const ShareablesScreen$1 = lazy(() => import("./ShareablesScreen-KFwyduqD.mjs").then((m) => ({ default: m.ShareablesScreen })));
2413
- const MySiteScreen$1 = lazy(() => import("./MySiteScreen-bUfo9RDR.mjs").then((m) => ({ default: m.MySiteScreen })));
2411
+ const ProductsScreen$1 = lazy(() => import("./ProductsScreen-D90OAeDM.mjs").then((m) => ({ default: m.ProductsScreen })));
2412
+ const ShareablesScreen$1 = lazy(() => import("./ShareablesScreen-CQbMDEkb.mjs").then((m) => ({ default: m.ShareablesScreen })));
2413
+ const MySiteScreen$1 = lazy(() => import("./MySiteScreen-DlbGFMlk.mjs").then((m) => ({ default: m.MySiteScreen })));
2414
2414
  const UpgradeScreen = lazy(() => import("./UpgradeScreen-D7LfdVSJ.mjs").then((n) => n.t).then((m) => ({ default: m.UpgradeScreen })));
2415
- const AppDownloadScreen = lazy(() => import("./AppDownloadScreen-DUGGDlr4.mjs").then((m) => ({ default: m.AppDownloadScreen })));
2415
+ const AppDownloadScreen = lazy(() => import("./AppDownloadScreen-CC3PVGnN.mjs").then((m) => ({ default: m.AppDownloadScreen })));
2416
2416
  const SYSTEM_SLUG_SCREEN_MAP = {
2417
2417
  profile: ProfileScreen$1,
2418
2418
  orders: OrdersScreen$1,
@@ -3815,15 +3815,15 @@ const screenPropertySchemas = {
3815
3815
  ProfileScreen: () => import("./ProfileScreen-Tcqc5WfU.mjs").then((m) => m.profileScreenPropertySchema),
3816
3816
  MessagingScreen: () => import("./MessagingScreen-CGS474IP.mjs").then((m) => m.messagingScreenPropertySchema),
3817
3817
  ContactsScreen: () => import("./ContactsScreen-ruymx9Pi.mjs").then((m) => m.contactsScreenPropertySchema),
3818
- OrdersScreen: () => import("./OrdersScreen-CO9vm0Wj.mjs").then((m) => m.ordersScreenPropertySchema),
3819
- SubscriptionsScreen: () => import("./SubscriptionsScreen-BVLHc7-p.mjs").then((m) => m.subscriptionsScreenPropertySchema),
3818
+ OrdersScreen: () => import("./OrdersScreen-h7KX2dQo.mjs").then((m) => m.ordersScreenPropertySchema),
3819
+ SubscriptionsScreen: () => import("./SubscriptionsScreen-DjG0cmvn.mjs").then((m) => m.subscriptionsScreenPropertySchema),
3820
3820
  CustomersScreen: () => import("./CustomersScreen-D22G27ru.mjs").then((n) => n.n).then((m) => m.customersScreenPropertySchema),
3821
- ProductsScreen: () => import("./ProductsScreen-GOW8lxiN.mjs").then((m) => m.productsScreenPropertySchema),
3822
- MySiteScreen: () => import("./MySiteScreen-bUfo9RDR.mjs").then((m) => m.mySiteScreenPropertySchema),
3823
- ShareablesScreen: () => import("./ShareablesScreen-KFwyduqD.mjs").then((m) => m.shareablesScreenPropertySchema),
3824
- ShopScreen: () => import("./ShopScreen-Bg696rqv.mjs").then((m) => m.shopScreenPropertySchema),
3821
+ ProductsScreen: () => import("./ProductsScreen-D90OAeDM.mjs").then((m) => m.productsScreenPropertySchema),
3822
+ MySiteScreen: () => import("./MySiteScreen-DlbGFMlk.mjs").then((m) => m.mySiteScreenPropertySchema),
3823
+ ShareablesScreen: () => import("./ShareablesScreen-CQbMDEkb.mjs").then((m) => m.shareablesScreenPropertySchema),
3824
+ ShopScreen: () => import("./ShopScreen-CSpdAmLo.mjs").then((m) => m.shopScreenPropertySchema),
3825
3825
  UpgradeScreen: () => import("./UpgradeScreen-D7LfdVSJ.mjs").then((n) => n.t).then((m) => m.upgradeScreenPropertySchema),
3826
- AppDownloadScreen: () => import("./AppDownloadScreen-DUGGDlr4.mjs").then((m) => m.appDownloadScreenPropertySchema)
3826
+ AppDownloadScreen: () => import("./AppDownloadScreen-CC3PVGnN.mjs").then((m) => m.appDownloadScreenPropertySchema)
3827
3827
  };
3828
3828
  /**
3829
3829
  * Core page template IDs
@@ -1,5 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../chunk-DAgNkxik.cjs");
3
+ let node_path = require("node:path");
4
+ let node_fs = require("node:fs");
3
5
  //#region src/vite/validate-manifest.ts
4
6
  /**
5
7
  * Lightweight manifest validation for the SDK vite plugin.
@@ -89,76 +91,6 @@ function validateManifest(input) {
89
91
  };
90
92
  }
91
93
  //#endregion
92
- //#region src/vite/manifest-plugin.ts
93
- /**
94
- * Vite plugin that serves widget manifest metadata.
95
- *
96
- * Dev mode: middleware serves /__manifests__ dynamically via ssrLoadModule.
97
- * Re-reads portal.config.ts on every request (HMR-aware).
98
- *
99
- * Build mode: emits an empty __manifests__.json as a static asset in dist/.
100
- * The CLI extraction utility (extract-manifests.ts) handles build-time
101
- * extraction via tsx subprocess for `fluid build` and `fluid deploy`.
102
- *
103
- * The builder fetches this endpoint/file to discover custom widgets.
104
- */
105
- function fluidManifestPlugin() {
106
- return {
107
- name: "fluid-manifest-plugin",
108
- configureServer(server) {
109
- server.middlewares.use("/__manifests__", async (_req, res) => {
110
- try {
111
- const serializable = await loadManifests(server, server.config.logger);
112
- res.setHeader("Content-Type", "application/json");
113
- res.setHeader("Access-Control-Allow-Origin", "*");
114
- res.end(JSON.stringify(serializable));
115
- } catch (err) {
116
- server.config.logger.error(`[fluid] Failed to load manifests: ${err}`);
117
- res.statusCode = 500;
118
- res.end(JSON.stringify({ error: String(err) }));
119
- }
120
- });
121
- },
122
- generateBundle() {
123
- this.warn("[fluid] fluidManifestPlugin: emitting empty __manifests__.json. Run `fluid build` instead of `vite build` to include widget manifests.");
124
- this.emitFile({
125
- type: "asset",
126
- fileName: "__manifests__.json",
127
- source: JSON.stringify([])
128
- });
129
- }
130
- };
131
- }
132
- /**
133
- * Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.
134
- * Validates each manifest before stripping the `component` field.
135
- * Returns an empty array if the config module or export is missing/invalid.
136
- */
137
- async function loadManifests(server, logger) {
138
- let mod;
139
- try {
140
- mod = await server.ssrLoadModule("/src/portal.config.ts");
141
- } catch (err) {
142
- logger?.warn(`[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`);
143
- return [];
144
- }
145
- const rawWidgets = mod.customWidgets;
146
- if (!rawWidgets) return [];
147
- if (!Array.isArray(rawWidgets)) {
148
- logger?.warn(`[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`);
149
- return [];
150
- }
151
- const manifests = rawWidgets;
152
- if (logger) for (const manifest of manifests) {
153
- const result = validateManifest(manifest);
154
- if (!result.success) {
155
- const type = manifest.type ?? "unknown";
156
- logger.warn(`[fluid] Invalid manifest for "${type}":\n` + result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n"));
157
- }
158
- }
159
- return manifests.map(({ component: _component, ...rest }) => rest);
160
- }
161
- //#endregion
162
94
  //#region src/vite/builder-preview-plugin.ts
163
95
  const VIRTUAL_ENTRY_ID = "virtual:builder-preview-entry";
164
96
  const RESOLVED_VIRTUAL_ID = "\0" + VIRTUAL_ENTRY_ID;
@@ -174,8 +106,7 @@ const RAW_HTML = `<!doctype html>
174
106
  </head>
175
107
  <body>
176
108
  <div id="builder-preview-root"></div>
177
- <script type="module" src="/@id/__x00__virtual:builder-preview-entry"><\/script>
178
- <script type="module">import "/src/index.css";<\/script>
109
+ <script type="module" src="/@id/virtual:builder-preview-entry"><\/script>
179
110
  </body>
180
111
  </html>`;
181
112
  /**
@@ -183,19 +114,29 @@ const RAW_HTML = `<!doctype html>
183
114
  *
184
115
  * Dev mode only. Renders all customWidgets from portal.config.ts with
185
116
  * a live preview and property editor — no auth, no iframe, no fluid-admin needed.
186
- *
187
- * Uses a virtual module to bridge the user's portal.config.ts into the preview app.
188
117
  */
189
118
  function fluidBuilderPreviewPlugin() {
119
+ let configPath;
120
+ let cssPath;
190
121
  return {
191
- name: "fluid-builder-preview-plugin",
122
+ name: "fluid-builder-preview",
192
123
  apply: "serve",
124
+ configResolved(config) {
125
+ const root = config.root;
126
+ configPath = ["src/portal.config.ts", "portal.config.ts"].find((c) => (0, node_fs.existsSync)((0, node_path.join)(root, c))) ?? "src/portal.config.ts";
127
+ cssPath = [
128
+ "src/index.css",
129
+ "src/styles/index.css",
130
+ "index.css"
131
+ ].find((c) => (0, node_fs.existsSync)((0, node_path.join)(root, c))) ?? "src/index.css";
132
+ },
193
133
  resolveId(id) {
194
134
  if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;
195
135
  },
196
136
  load(id) {
197
137
  if (id === RESOLVED_VIRTUAL_ID) return `
198
- import * as portalConfig from "/src/portal.config.ts";
138
+ import "/${cssPath}";
139
+ import * as portalConfig from "/${configPath}";
199
140
  import { createRoot } from "react-dom/client";
200
141
  import { createElement } from "react";
201
142
  import { BuilderPreviewApp } from "@fluid-app/portal-preview";
@@ -227,6 +168,81 @@ if (root) {
227
168
  };
228
169
  }
229
170
  //#endregion
171
+ //#region src/vite/manifest-plugin.ts
172
+ /**
173
+ * Vite plugin bundle that serves widget manifest metadata and the builder preview.
174
+ *
175
+ * Returns an array of plugins:
176
+ * 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)
177
+ * 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)
178
+ *
179
+ * Every portal using `fluidManifestPlugin()` automatically gets the builder preview.
180
+ */
181
+ function fluidManifestPlugin() {
182
+ return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];
183
+ }
184
+ function fluidManifestPluginInternal() {
185
+ let configPath;
186
+ return {
187
+ name: "fluid-manifest-plugin",
188
+ configResolved(config) {
189
+ const root = config.root;
190
+ configPath = "/" + (["src/portal.config.ts", "portal.config.ts"].find((c) => (0, node_fs.existsSync)((0, node_path.join)(root, c))) ?? "src/portal.config.ts");
191
+ },
192
+ configureServer(server) {
193
+ server.middlewares.use("/__manifests__", async (_req, res) => {
194
+ try {
195
+ const serializable = await loadManifests(server, server.config.logger, configPath);
196
+ res.setHeader("Content-Type", "application/json");
197
+ res.setHeader("Access-Control-Allow-Origin", "*");
198
+ res.end(JSON.stringify(serializable));
199
+ } catch (err) {
200
+ server.config.logger.error(`[fluid] Failed to load manifests: ${err}`);
201
+ res.statusCode = 500;
202
+ res.end(JSON.stringify({ error: String(err) }));
203
+ }
204
+ });
205
+ },
206
+ generateBundle() {
207
+ this.warn("[fluid] fluidManifestPlugin: emitting empty __manifests__.json. Run `fluid build` instead of `vite build` to include widget manifests.");
208
+ this.emitFile({
209
+ type: "asset",
210
+ fileName: "__manifests__.json",
211
+ source: JSON.stringify([])
212
+ });
213
+ }
214
+ };
215
+ }
216
+ /**
217
+ * Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.
218
+ * Validates each manifest before stripping the `component` field.
219
+ * Returns an empty array if the config module or export is missing/invalid.
220
+ */
221
+ async function loadManifests(server, logger, portalConfigPath) {
222
+ let mod;
223
+ try {
224
+ mod = await server.ssrLoadModule(portalConfigPath ?? "/src/portal.config.ts");
225
+ } catch (err) {
226
+ logger?.warn(`[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`);
227
+ return [];
228
+ }
229
+ const rawWidgets = mod.customWidgets;
230
+ if (!rawWidgets) return [];
231
+ if (!Array.isArray(rawWidgets)) {
232
+ logger?.warn(`[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`);
233
+ return [];
234
+ }
235
+ const manifests = rawWidgets;
236
+ if (logger) for (const manifest of manifests) {
237
+ const result = validateManifest(manifest);
238
+ if (!result.success) {
239
+ const type = manifest.type ?? "unknown";
240
+ logger.warn(`[fluid] Invalid manifest for "${type}":\n` + result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n"));
241
+ }
242
+ }
243
+ return manifests.map(({ component: _component, ...rest }) => rest);
244
+ }
245
+ //#endregion
230
246
  exports.fluidBuilderPreviewPlugin = fluidBuilderPreviewPlugin;
231
247
  exports.fluidManifestPlugin = fluidManifestPlugin;
232
248
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/vite/validate-manifest.ts","../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts"],"sourcesContent":["/**\n * Lightweight manifest validation for the SDK vite plugin.\n *\n * Inlined here (rather than imported from @fluid-app/portal-core) because\n * portal-core is private and not published to npm. This avoids a runtime\n * ERR_MODULE_NOT_FOUND for portals installed from npm.\n */\n\nconst VALID_FIELD_TYPES = [\n \"text\",\n \"textarea\",\n \"number\",\n \"boolean\",\n \"select\",\n \"color\",\n \"range\",\n \"dataSource\",\n \"resource\",\n \"image\",\n \"alignment\",\n \"slider\",\n \"colorPicker\",\n \"sectionHeader\",\n \"separator\",\n \"buttonGroup\",\n \"colorSelect\",\n \"sectionLayoutSelect\",\n \"background\",\n \"contentPosition\",\n \"textSizeSelect\",\n \"cssUnit\",\n \"fontPicker\",\n \"stringArray\",\n \"borderRadius\",\n \"screenPicker\",\n] as const;\n\ninterface ValidationError {\n path: string;\n message: string;\n}\n\ntype ValidationResult =\n | { success: true }\n | { success: false; errors: ValidationError[] };\n\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: ValidationError[] = [];\n const m = input as Record<string, unknown>;\n\n if (!m || typeof m !== \"object\") {\n return {\n success: false,\n errors: [{ path: \"\", message: \"Manifest must be an object\" }],\n };\n }\n\n // Required string fields\n for (const key of [\n \"type\",\n \"displayName\",\n \"description\",\n \"icon\",\n \"category\",\n ]) {\n if (typeof m[key] !== \"string\" || (m[key] as string).length === 0) {\n errors.push({\n path: key,\n message: `${key} is required and must be a non-empty string`,\n });\n }\n }\n\n if (typeof m.manifestVersion !== \"number\" || m.manifestVersion < 1) {\n errors.push({\n path: \"manifestVersion\",\n message: \"manifestVersion must be a positive integer\",\n });\n }\n\n if (typeof m.component !== \"function\") {\n errors.push({\n path: \"component\",\n message: \"component must be a React component (function)\",\n });\n }\n\n // Property schema validation\n const schema = m.propertySchema as Record<string, unknown> | undefined;\n if (schema && typeof schema === \"object\") {\n if (typeof schema.widgetType !== \"string\" || !schema.widgetType) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"widgetType is required\",\n });\n }\n if (typeof m.type === \"string\" && schema.widgetType !== m.type) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"manifest.type must match manifest.propertySchema.widgetType\",\n });\n }\n if (Array.isArray(schema.fields)) {\n for (let i = 0; i < schema.fields.length; i++) {\n const field = schema.fields[i] as Record<string, unknown>;\n if (!field || typeof field.type !== \"string\") continue;\n if (\n !VALID_FIELD_TYPES.includes(\n field.type as (typeof VALID_FIELD_TYPES)[number],\n )\n ) {\n errors.push({\n path: `propertySchema.fields.${i}.type`,\n message: `Invalid field type \"${field.type}\". Valid types: ${VALID_FIELD_TYPES.join(\", \")}`,\n });\n }\n }\n }\n }\n\n return errors.length === 0 ? { success: true } : { success: false, errors };\n}\n","import type { Plugin, ViteDevServer, Logger } from \"vite\";\nimport { validateManifest } from \"./validate-manifest\";\n\n/**\n * Vite plugin that serves widget manifest metadata.\n *\n * Dev mode: middleware serves /__manifests__ dynamically via ssrLoadModule.\n * Re-reads portal.config.ts on every request (HMR-aware).\n *\n * Build mode: emits an empty __manifests__.json as a static asset in dist/.\n * The CLI extraction utility (extract-manifests.ts) handles build-time\n * extraction via tsx subprocess for `fluid build` and `fluid deploy`.\n *\n * The builder fetches this endpoint/file to discover custom widgets.\n */\nexport function fluidManifestPlugin(): Plugin {\n return {\n name: \"fluid-manifest-plugin\",\n\n configureServer(server) {\n server.middlewares.use(\"/__manifests__\", async (_req, res) => {\n try {\n const serializable = await loadManifests(\n server,\n server.config.logger,\n );\n\n res.setHeader(\"Content-Type\", \"application/json\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.end(JSON.stringify(serializable));\n } catch (err) {\n server.config.logger.error(\n `[fluid] Failed to load manifests: ${err}`,\n );\n res.statusCode = 500;\n res.end(JSON.stringify({ error: String(err) }));\n }\n });\n },\n\n generateBundle() {\n // Build mode: emit placeholder. The CLI extraction utility handles\n // actual build-time manifest extraction via tsx subprocess.\n this.warn(\n \"[fluid] fluidManifestPlugin: emitting empty __manifests__.json. \" +\n \"Run `fluid build` instead of `vite build` to include widget manifests.\",\n );\n this.emitFile({\n type: \"asset\",\n fileName: \"__manifests__.json\",\n source: JSON.stringify([]),\n });\n },\n };\n}\n\n/**\n * Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.\n * Validates each manifest before stripping the `component` field.\n * Returns an empty array if the config module or export is missing/invalid.\n */\nasync function loadManifests(\n server: ViteDevServer,\n logger?: Logger,\n): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(\"/src/portal.config.ts\");\n } catch (err) {\n logger?.warn(\n `[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`,\n );\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) return [];\n\n if (!Array.isArray(rawWidgets)) {\n logger?.warn(\n `[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`,\n );\n return [];\n }\n\n const manifests = rawWidgets as Record<string, unknown>[];\n\n // Validate full manifests (with component) before stripping\n if (logger) {\n for (const manifest of manifests) {\n const result = validateManifest(manifest);\n if (!result.success) {\n const type = (manifest as { type?: string }).type ?? \"unknown\";\n logger.warn(\n `[fluid] Invalid manifest for \"${type}\":\\n` +\n result.errors.map((e) => ` - ${e.path}: ${e.message}`).join(\"\\n\"),\n );\n }\n }\n }\n\n return manifests.map(\n ({ component: _component, ...rest }: Record<string, unknown>) => rest,\n );\n}\n","import type { Plugin } from \"vite\";\n\nconst VIRTUAL_ENTRY_ID = \"virtual:builder-preview-entry\";\nconst RESOLVED_VIRTUAL_ID = \"\\0\" + VIRTUAL_ENTRY_ID;\nconst VIRTUAL_MODULE_URL = \"/@id/__x00__virtual:builder-preview-entry\";\n\nconst RAW_HTML = `<!doctype html>\n<html lang=\"en\" data-theme-mode=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Custom Widget Preview</title>\n <style>\n body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }\n </style>\n </head>\n <body>\n <div id=\"builder-preview-root\"></div>\n <script type=\"module\" src=\"${VIRTUAL_MODULE_URL}\"></script>\n <script type=\"module\">import \"/src/index.css\";</script>\n </body>\n</html>`;\n\n/**\n * Vite plugin that serves a standalone widget preview page at /builder-preview.\n *\n * Dev mode only. Renders all customWidgets from portal.config.ts with\n * a live preview and property editor — no auth, no iframe, no fluid-admin needed.\n *\n * Uses a virtual module to bridge the user's portal.config.ts into the preview app.\n */\nexport function fluidBuilderPreviewPlugin(): Plugin {\n return {\n name: \"fluid-builder-preview-plugin\",\n apply: \"serve\",\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_ID) {\n return `\nimport * as portalConfig from \"/src/portal.config.ts\";\nimport { createRoot } from \"react-dom/client\";\nimport { createElement } from \"react\";\nimport { BuilderPreviewApp } from \"@fluid-app/portal-preview\";\n\nconst widgets = portalConfig.customWidgets || [];\nconst root = document.getElementById(\"builder-preview-root\");\nif (root) {\n createRoot(root).render(\n createElement(BuilderPreviewApp, { widgets })\n );\n}\n`;\n }\n },\n\n configureServer(server) {\n server.middlewares.use(async (req, res, next) => {\n const pathname = (req.url ?? \"\").split(\"?\")[0];\n if (pathname !== \"/builder-preview\" && pathname !== \"/builder-preview/\")\n return next();\n try {\n const transformed = await server.transformIndexHtml(\n \"/builder-preview\",\n RAW_HTML,\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(transformed);\n } catch (e) {\n server.config.logger.error(\n `[fluid] Failed to serve builder preview: ${e}`,\n );\n res.statusCode = 500;\n res.end(\"Builder preview failed to load\");\n }\n });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/vite/validate-manifest.ts","../../src/vite/builder-preview-plugin.ts","../../src/vite/manifest-plugin.ts"],"sourcesContent":["/**\n * Lightweight manifest validation for the SDK vite plugin.\n *\n * Inlined here (rather than imported from @fluid-app/portal-core) because\n * portal-core is private and not published to npm. This avoids a runtime\n * ERR_MODULE_NOT_FOUND for portals installed from npm.\n */\n\nconst VALID_FIELD_TYPES = [\n \"text\",\n \"textarea\",\n \"number\",\n \"boolean\",\n \"select\",\n \"color\",\n \"range\",\n \"dataSource\",\n \"resource\",\n \"image\",\n \"alignment\",\n \"slider\",\n \"colorPicker\",\n \"sectionHeader\",\n \"separator\",\n \"buttonGroup\",\n \"colorSelect\",\n \"sectionLayoutSelect\",\n \"background\",\n \"contentPosition\",\n \"textSizeSelect\",\n \"cssUnit\",\n \"fontPicker\",\n \"stringArray\",\n \"borderRadius\",\n \"screenPicker\",\n] as const;\n\ninterface ValidationError {\n path: string;\n message: string;\n}\n\ntype ValidationResult =\n | { success: true }\n | { success: false; errors: ValidationError[] };\n\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: ValidationError[] = [];\n const m = input as Record<string, unknown>;\n\n if (!m || typeof m !== \"object\") {\n return {\n success: false,\n errors: [{ path: \"\", message: \"Manifest must be an object\" }],\n };\n }\n\n // Required string fields\n for (const key of [\n \"type\",\n \"displayName\",\n \"description\",\n \"icon\",\n \"category\",\n ]) {\n if (typeof m[key] !== \"string\" || (m[key] as string).length === 0) {\n errors.push({\n path: key,\n message: `${key} is required and must be a non-empty string`,\n });\n }\n }\n\n if (typeof m.manifestVersion !== \"number\" || m.manifestVersion < 1) {\n errors.push({\n path: \"manifestVersion\",\n message: \"manifestVersion must be a positive integer\",\n });\n }\n\n if (typeof m.component !== \"function\") {\n errors.push({\n path: \"component\",\n message: \"component must be a React component (function)\",\n });\n }\n\n // Property schema validation\n const schema = m.propertySchema as Record<string, unknown> | undefined;\n if (schema && typeof schema === \"object\") {\n if (typeof schema.widgetType !== \"string\" || !schema.widgetType) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"widgetType is required\",\n });\n }\n if (typeof m.type === \"string\" && schema.widgetType !== m.type) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"manifest.type must match manifest.propertySchema.widgetType\",\n });\n }\n if (Array.isArray(schema.fields)) {\n for (let i = 0; i < schema.fields.length; i++) {\n const field = schema.fields[i] as Record<string, unknown>;\n if (!field || typeof field.type !== \"string\") continue;\n if (\n !VALID_FIELD_TYPES.includes(\n field.type as (typeof VALID_FIELD_TYPES)[number],\n )\n ) {\n errors.push({\n path: `propertySchema.fields.${i}.type`,\n message: `Invalid field type \"${field.type}\". Valid types: ${VALID_FIELD_TYPES.join(\", \")}`,\n });\n }\n }\n }\n }\n\n return errors.length === 0 ? { success: true } : { success: false, errors };\n}\n","import type { Plugin, ResolvedConfig } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst VIRTUAL_ENTRY_ID = \"virtual:builder-preview-entry\";\nconst RESOLVED_VIRTUAL_ID = \"\\0\" + VIRTUAL_ENTRY_ID;\n\nconst RAW_HTML = `<!doctype html>\n<html lang=\"en\" data-theme-mode=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Custom Widget Preview</title>\n <style>\n body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }\n </style>\n </head>\n <body>\n <div id=\"builder-preview-root\"></div>\n <script type=\"module\" src=\"/@id/virtual:builder-preview-entry\"></script>\n </body>\n</html>`;\n\n/**\n * Vite plugin that serves a standalone widget preview page at /builder-preview.\n *\n * Dev mode only. Renders all customWidgets from portal.config.ts with\n * a live preview and property editor — no auth, no iframe, no fluid-admin needed.\n */\nexport function fluidBuilderPreviewPlugin(): Plugin {\n let configPath: string;\n let cssPath: string;\n\n return {\n name: \"fluid-builder-preview\",\n apply: \"serve\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\"src/portal.config.ts\", \"portal.config.ts\"];\n configPath =\n candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\";\n const cssCandidates = [\n \"src/index.css\",\n \"src/styles/index.css\",\n \"index.css\",\n ];\n cssPath =\n cssCandidates.find((c) => existsSync(join(root, c))) ?? \"src/index.css\";\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_ID) {\n return `\nimport \"/${cssPath}\";\nimport * as portalConfig from \"/${configPath}\";\nimport { createRoot } from \"react-dom/client\";\nimport { createElement } from \"react\";\nimport { BuilderPreviewApp } from \"@fluid-app/portal-preview\";\n\nconst widgets = portalConfig.customWidgets || [];\nconst root = document.getElementById(\"builder-preview-root\");\nif (root) {\n createRoot(root).render(\n createElement(BuilderPreviewApp, { widgets })\n );\n}\n`;\n }\n },\n\n configureServer(server) {\n server.middlewares.use(async (req, res, next) => {\n const pathname = (req.url ?? \"\").split(\"?\")[0];\n if (pathname !== \"/builder-preview\" && pathname !== \"/builder-preview/\")\n return next();\n try {\n const transformed = await server.transformIndexHtml(\n \"/builder-preview\",\n RAW_HTML,\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(transformed);\n } catch (e) {\n server.config.logger.error(\n `[fluid] Failed to serve builder preview: ${e}`,\n );\n res.statusCode = 500;\n res.end(\"Builder preview failed to load\");\n }\n });\n },\n };\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer, Logger } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { validateManifest } from \"./validate-manifest\";\nimport { fluidBuilderPreviewPlugin } from \"./builder-preview-plugin\";\n\n/**\n * Vite plugin bundle that serves widget manifest metadata and the builder preview.\n *\n * Returns an array of plugins:\n * 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)\n * 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)\n *\n * Every portal using `fluidManifestPlugin()` automatically gets the builder preview.\n */\nexport function fluidManifestPlugin(): Plugin[] {\n return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];\n}\n\nfunction fluidManifestPluginInternal(): Plugin {\n let configPath: string;\n\n return {\n name: \"fluid-manifest-plugin\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\"src/portal.config.ts\", \"portal.config.ts\"];\n configPath =\n \"/\" +\n (candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\");\n },\n\n configureServer(server) {\n server.middlewares.use(\"/__manifests__\", async (_req, res) => {\n try {\n const serializable = await loadManifests(\n server,\n server.config.logger,\n configPath,\n );\n\n res.setHeader(\"Content-Type\", \"application/json\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.end(JSON.stringify(serializable));\n } catch (err) {\n server.config.logger.error(\n `[fluid] Failed to load manifests: ${err}`,\n );\n res.statusCode = 500;\n res.end(JSON.stringify({ error: String(err) }));\n }\n });\n },\n\n generateBundle() {\n // Build mode: emit placeholder. The CLI extraction utility handles\n // actual build-time manifest extraction via tsx subprocess.\n this.warn(\n \"[fluid] fluidManifestPlugin: emitting empty __manifests__.json. \" +\n \"Run `fluid build` instead of `vite build` to include widget manifests.\",\n );\n this.emitFile({\n type: \"asset\",\n fileName: \"__manifests__.json\",\n source: JSON.stringify([]),\n });\n },\n };\n}\n\n/**\n * Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.\n * Validates each manifest before stripping the `component` field.\n * Returns an empty array if the config module or export is missing/invalid.\n */\nasync function loadManifests(\n server: ViteDevServer,\n logger?: Logger,\n portalConfigPath?: string,\n): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(\n portalConfigPath ?? \"/src/portal.config.ts\",\n );\n } catch (err) {\n logger?.warn(\n `[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`,\n );\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) return [];\n\n if (!Array.isArray(rawWidgets)) {\n logger?.warn(\n `[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`,\n );\n return [];\n }\n\n const manifests = rawWidgets as Record<string, unknown>[];\n\n // Validate full manifests (with component) before stripping\n if (logger) {\n for (const manifest of manifests) {\n const result = validateManifest(manifest);\n if (!result.success) {\n const type = (manifest as { type?: string }).type ?? \"unknown\";\n logger.warn(\n `[fluid] Invalid manifest for \"${type}\":\\n` +\n result.errors.map((e) => ` - ${e.path}: ${e.message}`).join(\"\\n\"),\n );\n }\n }\n }\n\n return manifests.map(\n ({ component: _component, ...rest }: Record<string, unknown>) => rest,\n );\n}\n"],"mappings":";;;;;;;;;;;;AAQA,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAWD,SAAgB,iBAAiB,OAAkC;CACjE,MAAM,SAA4B,EAAE;CACpC,MAAM,IAAI;AAEV,KAAI,CAAC,KAAK,OAAO,MAAM,SACrB,QAAO;EACL,SAAS;EACT,QAAQ,CAAC;GAAE,MAAM;GAAI,SAAS;GAA8B,CAAC;EAC9D;AAIH,MAAK,MAAM,OAAO;EAChB;EACA;EACA;EACA;EACA;EACD,CACC,KAAI,OAAO,EAAE,SAAS,YAAa,EAAE,KAAgB,WAAW,EAC9D,QAAO,KAAK;EACV,MAAM;EACN,SAAS,GAAG,IAAI;EACjB,CAAC;AAIN,KAAI,OAAO,EAAE,oBAAoB,YAAY,EAAE,kBAAkB,EAC/D,QAAO,KAAK;EACV,MAAM;EACN,SAAS;EACV,CAAC;AAGJ,KAAI,OAAO,EAAE,cAAc,WACzB,QAAO,KAAK;EACV,MAAM;EACN,SAAS;EACV,CAAC;CAIJ,MAAM,SAAS,EAAE;AACjB,KAAI,UAAU,OAAO,WAAW,UAAU;AACxC,MAAI,OAAO,OAAO,eAAe,YAAY,CAAC,OAAO,WACnD,QAAO,KAAK;GACV,MAAM;GACN,SAAS;GACV,CAAC;AAEJ,MAAI,OAAO,EAAE,SAAS,YAAY,OAAO,eAAe,EAAE,KACxD,QAAO,KAAK;GACV,MAAM;GACN,SAAS;GACV,CAAC;AAEJ,MAAI,MAAM,QAAQ,OAAO,OAAO,CAC9B,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,OAAO,QAAQ,KAAK;GAC7C,MAAM,QAAQ,OAAO,OAAO;AAC5B,OAAI,CAAC,SAAS,OAAO,MAAM,SAAS,SAAU;AAC9C,OACE,CAAC,kBAAkB,SACjB,MAAM,KACP,CAED,QAAO,KAAK;IACV,MAAM,yBAAyB,EAAE;IACjC,SAAS,uBAAuB,MAAM,KAAK,kBAAkB,kBAAkB,KAAK,KAAK;IAC1F,CAAC;;;AAMV,QAAO,OAAO,WAAW,IAAI,EAAE,SAAS,MAAM,GAAG;EAAE,SAAS;EAAO;EAAQ;;;;ACpH7E,MAAM,mBAAmB;AACzB,MAAM,sBAAsB,OAAO;AAEnC,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;AAsBjB,SAAgB,4BAAoC;CAClD,IAAI;CACJ,IAAI;AAEJ,QAAO;EACL,MAAM;EACN,OAAO;EAEP,eAAe,QAAwB;GACrC,MAAM,OAAO,OAAO;AAEpB,gBADmB,CAAC,wBAAwB,mBAAmB,CAElD,MAAM,OAAA,GAAA,QAAA,aAAA,GAAA,UAAA,MAAsB,MAAM,EAAE,CAAC,CAAC,IACjD;AAMF,aALsB;IACpB;IACA;IACA;IACD,CAEe,MAAM,OAAA,GAAA,QAAA,aAAA,GAAA,UAAA,MAAsB,MAAM,EAAE,CAAC,CAAC,IAAI;;EAG5D,UAAU,IAAI;AACZ,OAAI,OAAO,iBAAkB,QAAO;;EAGtC,KAAK,IAAI;AACP,OAAI,OAAO,oBACT,QAAO;WACJ,QAAQ;kCACe,WAAW;;;;;;;;;;;;;;EAgBzC,gBAAgB,QAAQ;AACtB,UAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;IAC/C,MAAM,YAAY,IAAI,OAAO,IAAI,MAAM,IAAI,CAAC;AAC5C,QAAI,aAAa,sBAAsB,aAAa,oBAClD,QAAO,MAAM;AACf,QAAI;KACF,MAAM,cAAc,MAAM,OAAO,mBAC/B,oBACA,SACD;AACD,SAAI,UAAU,gBAAgB,YAAY;AAC1C,SAAI,IAAI,YAAY;aACb,GAAG;AACV,YAAO,OAAO,OAAO,MACnB,4CAA4C,IAC7C;AACD,SAAI,aAAa;AACjB,SAAI,IAAI,iCAAiC;;KAE3C;;EAEL;;;;;;;;;;;;;AClFH,SAAgB,sBAAgC;AAC9C,QAAO,CAAC,6BAA6B,EAAE,2BAA2B,CAAC;;AAGrE,SAAS,8BAAsC;CAC7C,IAAI;AAEJ,QAAO;EACL,MAAM;EAEN,eAAe,QAAwB;GACrC,MAAM,OAAO,OAAO;AAEpB,gBACE,OAFiB,CAAC,wBAAwB,mBAAmB,CAGjD,MAAM,OAAA,GAAA,QAAA,aAAA,GAAA,UAAA,MAAsB,MAAM,EAAE,CAAC,CAAC,IAChD;;EAGN,gBAAgB,QAAQ;AACtB,UAAO,YAAY,IAAI,kBAAkB,OAAO,MAAM,QAAQ;AAC5D,QAAI;KACF,MAAM,eAAe,MAAM,cACzB,QACA,OAAO,OAAO,QACd,WACD;AAED,SAAI,UAAU,gBAAgB,mBAAmB;AACjD,SAAI,UAAU,+BAA+B,IAAI;AACjD,SAAI,IAAI,KAAK,UAAU,aAAa,CAAC;aAC9B,KAAK;AACZ,YAAO,OAAO,OAAO,MACnB,qCAAqC,MACtC;AACD,SAAI,aAAa;AACjB,SAAI,IAAI,KAAK,UAAU,EAAE,OAAO,OAAO,IAAI,EAAE,CAAC,CAAC;;KAEjD;;EAGJ,iBAAiB;AAGf,QAAK,KACH,yIAED;AACD,QAAK,SAAS;IACZ,MAAM;IACN,UAAU;IACV,QAAQ,KAAK,UAAU,EAAE,CAAC;IAC3B,CAAC;;EAEL;;;;;;;AAQH,eAAe,cACb,QACA,QACA,kBACoB;CACpB,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,OAAO,cACjB,oBAAoB,wBACrB;UACM,KAAK;AACZ,UAAQ,KACN,4CAA4C,eAAe,QAAQ,IAAI,UAAU,MAClF;AACD,SAAO,EAAE;;CAGX,MAAM,aAAa,IAAI;AACvB,KAAI,CAAC,WAAY,QAAO,EAAE;AAE1B,KAAI,CAAC,MAAM,QAAQ,WAAW,EAAE;AAC9B,UAAQ,KACN,qDAAqD,OAAO,WAAW,+BACxE;AACD,SAAO,EAAE;;CAGX,MAAM,YAAY;AAGlB,KAAI,OACF,MAAK,MAAM,YAAY,WAAW;EAChC,MAAM,SAAS,iBAAiB,SAAS;AACzC,MAAI,CAAC,OAAO,SAAS;GACnB,MAAM,OAAQ,SAA+B,QAAQ;AACrD,UAAO,KACL,iCAAiC,KAAK,QACpC,OAAO,OAAO,KAAK,MAAM,OAAO,EAAE,KAAK,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK,CACrE;;;AAKP,QAAO,UAAU,KACd,EAAE,WAAW,YAAY,GAAG,WAAoC,KAClE"}
@@ -2,18 +2,15 @@ import { Plugin } from "vite";
2
2
 
3
3
  //#region src/vite/manifest-plugin.d.ts
4
4
  /**
5
- * Vite plugin that serves widget manifest metadata.
5
+ * Vite plugin bundle that serves widget manifest metadata and the builder preview.
6
6
  *
7
- * Dev mode: middleware serves /__manifests__ dynamically via ssrLoadModule.
8
- * Re-reads portal.config.ts on every request (HMR-aware).
7
+ * Returns an array of plugins:
8
+ * 1. Manifest plugin serves /__manifests__ (dev) and emits __manifests__.json (build)
9
+ * 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)
9
10
  *
10
- * Build mode: emits an empty __manifests__.json as a static asset in dist/.
11
- * The CLI extraction utility (extract-manifests.ts) handles build-time
12
- * extraction via tsx subprocess for `fluid build` and `fluid deploy`.
13
- *
14
- * The builder fetches this endpoint/file to discover custom widgets.
11
+ * Every portal using `fluidManifestPlugin()` automatically gets the builder preview.
15
12
  */
16
- declare function fluidManifestPlugin(): Plugin;
13
+ declare function fluidManifestPlugin(): Plugin[];
17
14
  //#endregion
18
15
  //#region src/vite/builder-preview-plugin.d.ts
19
16
  /**
@@ -21,8 +18,6 @@ declare function fluidManifestPlugin(): Plugin;
21
18
  *
22
19
  * Dev mode only. Renders all customWidgets from portal.config.ts with
23
20
  * a live preview and property editor — no auth, no iframe, no fluid-admin needed.
24
- *
25
- * Uses a virtual module to bridge the user's portal.config.ts into the preview app.
26
21
  */
27
22
  declare function fluidBuilderPreviewPlugin(): Plugin;
28
23
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts"],"mappings":";;;;;AAeA;;;;;;;;ACgBA;;iBDhBgB,mBAAA,CAAA,GAAuB,MAAA;;;;;AAAvC;;;;;;iBCgBgB,yBAAA,CAAA,GAA6B,MAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts"],"mappings":";;;;;AAeA;;;;;;;iBAAgB,mBAAA,CAAA,GAAuB,MAAA;;;;;AAAvC;;;;iBCcgB,yBAAA,CAAA,GAA6B,MAAA"}
@@ -2,18 +2,15 @@ import { Plugin } from "vite";
2
2
 
3
3
  //#region src/vite/manifest-plugin.d.ts
4
4
  /**
5
- * Vite plugin that serves widget manifest metadata.
5
+ * Vite plugin bundle that serves widget manifest metadata and the builder preview.
6
6
  *
7
- * Dev mode: middleware serves /__manifests__ dynamically via ssrLoadModule.
8
- * Re-reads portal.config.ts on every request (HMR-aware).
7
+ * Returns an array of plugins:
8
+ * 1. Manifest plugin serves /__manifests__ (dev) and emits __manifests__.json (build)
9
+ * 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)
9
10
  *
10
- * Build mode: emits an empty __manifests__.json as a static asset in dist/.
11
- * The CLI extraction utility (extract-manifests.ts) handles build-time
12
- * extraction via tsx subprocess for `fluid build` and `fluid deploy`.
13
- *
14
- * The builder fetches this endpoint/file to discover custom widgets.
11
+ * Every portal using `fluidManifestPlugin()` automatically gets the builder preview.
15
12
  */
16
- declare function fluidManifestPlugin(): Plugin;
13
+ declare function fluidManifestPlugin(): Plugin[];
17
14
  //#endregion
18
15
  //#region src/vite/builder-preview-plugin.d.ts
19
16
  /**
@@ -21,8 +18,6 @@ declare function fluidManifestPlugin(): Plugin;
21
18
  *
22
19
  * Dev mode only. Renders all customWidgets from portal.config.ts with
23
20
  * a live preview and property editor — no auth, no iframe, no fluid-admin needed.
24
- *
25
- * Uses a virtual module to bridge the user's portal.config.ts into the preview app.
26
21
  */
27
22
  declare function fluidBuilderPreviewPlugin(): Plugin;
28
23
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts"],"mappings":";;;;;AAeA;;;;;;;;ACgBA;;iBDhBgB,mBAAA,CAAA,GAAuB,MAAA;;;;;AAAvC;;;;;;iBCgBgB,yBAAA,CAAA,GAA6B,MAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts"],"mappings":";;;;;AAeA;;;;;;;iBAAgB,mBAAA,CAAA,GAAuB,MAAA;;;;;AAAvC;;;;iBCcgB,yBAAA,CAAA,GAA6B,MAAA"}
@@ -1,3 +1,5 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
1
3
  //#region src/vite/validate-manifest.ts
2
4
  /**
3
5
  * Lightweight manifest validation for the SDK vite plugin.
@@ -87,76 +89,6 @@ function validateManifest(input) {
87
89
  };
88
90
  }
89
91
  //#endregion
90
- //#region src/vite/manifest-plugin.ts
91
- /**
92
- * Vite plugin that serves widget manifest metadata.
93
- *
94
- * Dev mode: middleware serves /__manifests__ dynamically via ssrLoadModule.
95
- * Re-reads portal.config.ts on every request (HMR-aware).
96
- *
97
- * Build mode: emits an empty __manifests__.json as a static asset in dist/.
98
- * The CLI extraction utility (extract-manifests.ts) handles build-time
99
- * extraction via tsx subprocess for `fluid build` and `fluid deploy`.
100
- *
101
- * The builder fetches this endpoint/file to discover custom widgets.
102
- */
103
- function fluidManifestPlugin() {
104
- return {
105
- name: "fluid-manifest-plugin",
106
- configureServer(server) {
107
- server.middlewares.use("/__manifests__", async (_req, res) => {
108
- try {
109
- const serializable = await loadManifests(server, server.config.logger);
110
- res.setHeader("Content-Type", "application/json");
111
- res.setHeader("Access-Control-Allow-Origin", "*");
112
- res.end(JSON.stringify(serializable));
113
- } catch (err) {
114
- server.config.logger.error(`[fluid] Failed to load manifests: ${err}`);
115
- res.statusCode = 500;
116
- res.end(JSON.stringify({ error: String(err) }));
117
- }
118
- });
119
- },
120
- generateBundle() {
121
- this.warn("[fluid] fluidManifestPlugin: emitting empty __manifests__.json. Run `fluid build` instead of `vite build` to include widget manifests.");
122
- this.emitFile({
123
- type: "asset",
124
- fileName: "__manifests__.json",
125
- source: JSON.stringify([])
126
- });
127
- }
128
- };
129
- }
130
- /**
131
- * Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.
132
- * Validates each manifest before stripping the `component` field.
133
- * Returns an empty array if the config module or export is missing/invalid.
134
- */
135
- async function loadManifests(server, logger) {
136
- let mod;
137
- try {
138
- mod = await server.ssrLoadModule("/src/portal.config.ts");
139
- } catch (err) {
140
- logger?.warn(`[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`);
141
- return [];
142
- }
143
- const rawWidgets = mod.customWidgets;
144
- if (!rawWidgets) return [];
145
- if (!Array.isArray(rawWidgets)) {
146
- logger?.warn(`[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`);
147
- return [];
148
- }
149
- const manifests = rawWidgets;
150
- if (logger) for (const manifest of manifests) {
151
- const result = validateManifest(manifest);
152
- if (!result.success) {
153
- const type = manifest.type ?? "unknown";
154
- logger.warn(`[fluid] Invalid manifest for "${type}":\n` + result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n"));
155
- }
156
- }
157
- return manifests.map(({ component: _component, ...rest }) => rest);
158
- }
159
- //#endregion
160
92
  //#region src/vite/builder-preview-plugin.ts
161
93
  const VIRTUAL_ENTRY_ID = "virtual:builder-preview-entry";
162
94
  const RESOLVED_VIRTUAL_ID = "\0" + VIRTUAL_ENTRY_ID;
@@ -172,8 +104,7 @@ const RAW_HTML = `<!doctype html>
172
104
  </head>
173
105
  <body>
174
106
  <div id="builder-preview-root"></div>
175
- <script type="module" src="/@id/__x00__virtual:builder-preview-entry"><\/script>
176
- <script type="module">import "/src/index.css";<\/script>
107
+ <script type="module" src="/@id/virtual:builder-preview-entry"><\/script>
177
108
  </body>
178
109
  </html>`;
179
110
  /**
@@ -181,19 +112,29 @@ const RAW_HTML = `<!doctype html>
181
112
  *
182
113
  * Dev mode only. Renders all customWidgets from portal.config.ts with
183
114
  * a live preview and property editor — no auth, no iframe, no fluid-admin needed.
184
- *
185
- * Uses a virtual module to bridge the user's portal.config.ts into the preview app.
186
115
  */
187
116
  function fluidBuilderPreviewPlugin() {
117
+ let configPath;
118
+ let cssPath;
188
119
  return {
189
- name: "fluid-builder-preview-plugin",
120
+ name: "fluid-builder-preview",
190
121
  apply: "serve",
122
+ configResolved(config) {
123
+ const root = config.root;
124
+ configPath = ["src/portal.config.ts", "portal.config.ts"].find((c) => existsSync(join(root, c))) ?? "src/portal.config.ts";
125
+ cssPath = [
126
+ "src/index.css",
127
+ "src/styles/index.css",
128
+ "index.css"
129
+ ].find((c) => existsSync(join(root, c))) ?? "src/index.css";
130
+ },
191
131
  resolveId(id) {
192
132
  if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;
193
133
  },
194
134
  load(id) {
195
135
  if (id === RESOLVED_VIRTUAL_ID) return `
196
- import * as portalConfig from "/src/portal.config.ts";
136
+ import "/${cssPath}";
137
+ import * as portalConfig from "/${configPath}";
197
138
  import { createRoot } from "react-dom/client";
198
139
  import { createElement } from "react";
199
140
  import { BuilderPreviewApp } from "@fluid-app/portal-preview";
@@ -225,6 +166,81 @@ if (root) {
225
166
  };
226
167
  }
227
168
  //#endregion
169
+ //#region src/vite/manifest-plugin.ts
170
+ /**
171
+ * Vite plugin bundle that serves widget manifest metadata and the builder preview.
172
+ *
173
+ * Returns an array of plugins:
174
+ * 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)
175
+ * 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)
176
+ *
177
+ * Every portal using `fluidManifestPlugin()` automatically gets the builder preview.
178
+ */
179
+ function fluidManifestPlugin() {
180
+ return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];
181
+ }
182
+ function fluidManifestPluginInternal() {
183
+ let configPath;
184
+ return {
185
+ name: "fluid-manifest-plugin",
186
+ configResolved(config) {
187
+ const root = config.root;
188
+ configPath = "/" + (["src/portal.config.ts", "portal.config.ts"].find((c) => existsSync(join(root, c))) ?? "src/portal.config.ts");
189
+ },
190
+ configureServer(server) {
191
+ server.middlewares.use("/__manifests__", async (_req, res) => {
192
+ try {
193
+ const serializable = await loadManifests(server, server.config.logger, configPath);
194
+ res.setHeader("Content-Type", "application/json");
195
+ res.setHeader("Access-Control-Allow-Origin", "*");
196
+ res.end(JSON.stringify(serializable));
197
+ } catch (err) {
198
+ server.config.logger.error(`[fluid] Failed to load manifests: ${err}`);
199
+ res.statusCode = 500;
200
+ res.end(JSON.stringify({ error: String(err) }));
201
+ }
202
+ });
203
+ },
204
+ generateBundle() {
205
+ this.warn("[fluid] fluidManifestPlugin: emitting empty __manifests__.json. Run `fluid build` instead of `vite build` to include widget manifests.");
206
+ this.emitFile({
207
+ type: "asset",
208
+ fileName: "__manifests__.json",
209
+ source: JSON.stringify([])
210
+ });
211
+ }
212
+ };
213
+ }
214
+ /**
215
+ * Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.
216
+ * Validates each manifest before stripping the `component` field.
217
+ * Returns an empty array if the config module or export is missing/invalid.
218
+ */
219
+ async function loadManifests(server, logger, portalConfigPath) {
220
+ let mod;
221
+ try {
222
+ mod = await server.ssrLoadModule(portalConfigPath ?? "/src/portal.config.ts");
223
+ } catch (err) {
224
+ logger?.warn(`[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`);
225
+ return [];
226
+ }
227
+ const rawWidgets = mod.customWidgets;
228
+ if (!rawWidgets) return [];
229
+ if (!Array.isArray(rawWidgets)) {
230
+ logger?.warn(`[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`);
231
+ return [];
232
+ }
233
+ const manifests = rawWidgets;
234
+ if (logger) for (const manifest of manifests) {
235
+ const result = validateManifest(manifest);
236
+ if (!result.success) {
237
+ const type = manifest.type ?? "unknown";
238
+ logger.warn(`[fluid] Invalid manifest for "${type}":\n` + result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n"));
239
+ }
240
+ }
241
+ return manifests.map(({ component: _component, ...rest }) => rest);
242
+ }
243
+ //#endregion
228
244
  export { fluidBuilderPreviewPlugin, fluidManifestPlugin };
229
245
 
230
246
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/vite/validate-manifest.ts","../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts"],"sourcesContent":["/**\n * Lightweight manifest validation for the SDK vite plugin.\n *\n * Inlined here (rather than imported from @fluid-app/portal-core) because\n * portal-core is private and not published to npm. This avoids a runtime\n * ERR_MODULE_NOT_FOUND for portals installed from npm.\n */\n\nconst VALID_FIELD_TYPES = [\n \"text\",\n \"textarea\",\n \"number\",\n \"boolean\",\n \"select\",\n \"color\",\n \"range\",\n \"dataSource\",\n \"resource\",\n \"image\",\n \"alignment\",\n \"slider\",\n \"colorPicker\",\n \"sectionHeader\",\n \"separator\",\n \"buttonGroup\",\n \"colorSelect\",\n \"sectionLayoutSelect\",\n \"background\",\n \"contentPosition\",\n \"textSizeSelect\",\n \"cssUnit\",\n \"fontPicker\",\n \"stringArray\",\n \"borderRadius\",\n \"screenPicker\",\n] as const;\n\ninterface ValidationError {\n path: string;\n message: string;\n}\n\ntype ValidationResult =\n | { success: true }\n | { success: false; errors: ValidationError[] };\n\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: ValidationError[] = [];\n const m = input as Record<string, unknown>;\n\n if (!m || typeof m !== \"object\") {\n return {\n success: false,\n errors: [{ path: \"\", message: \"Manifest must be an object\" }],\n };\n }\n\n // Required string fields\n for (const key of [\n \"type\",\n \"displayName\",\n \"description\",\n \"icon\",\n \"category\",\n ]) {\n if (typeof m[key] !== \"string\" || (m[key] as string).length === 0) {\n errors.push({\n path: key,\n message: `${key} is required and must be a non-empty string`,\n });\n }\n }\n\n if (typeof m.manifestVersion !== \"number\" || m.manifestVersion < 1) {\n errors.push({\n path: \"manifestVersion\",\n message: \"manifestVersion must be a positive integer\",\n });\n }\n\n if (typeof m.component !== \"function\") {\n errors.push({\n path: \"component\",\n message: \"component must be a React component (function)\",\n });\n }\n\n // Property schema validation\n const schema = m.propertySchema as Record<string, unknown> | undefined;\n if (schema && typeof schema === \"object\") {\n if (typeof schema.widgetType !== \"string\" || !schema.widgetType) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"widgetType is required\",\n });\n }\n if (typeof m.type === \"string\" && schema.widgetType !== m.type) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"manifest.type must match manifest.propertySchema.widgetType\",\n });\n }\n if (Array.isArray(schema.fields)) {\n for (let i = 0; i < schema.fields.length; i++) {\n const field = schema.fields[i] as Record<string, unknown>;\n if (!field || typeof field.type !== \"string\") continue;\n if (\n !VALID_FIELD_TYPES.includes(\n field.type as (typeof VALID_FIELD_TYPES)[number],\n )\n ) {\n errors.push({\n path: `propertySchema.fields.${i}.type`,\n message: `Invalid field type \"${field.type}\". Valid types: ${VALID_FIELD_TYPES.join(\", \")}`,\n });\n }\n }\n }\n }\n\n return errors.length === 0 ? { success: true } : { success: false, errors };\n}\n","import type { Plugin, ViteDevServer, Logger } from \"vite\";\nimport { validateManifest } from \"./validate-manifest\";\n\n/**\n * Vite plugin that serves widget manifest metadata.\n *\n * Dev mode: middleware serves /__manifests__ dynamically via ssrLoadModule.\n * Re-reads portal.config.ts on every request (HMR-aware).\n *\n * Build mode: emits an empty __manifests__.json as a static asset in dist/.\n * The CLI extraction utility (extract-manifests.ts) handles build-time\n * extraction via tsx subprocess for `fluid build` and `fluid deploy`.\n *\n * The builder fetches this endpoint/file to discover custom widgets.\n */\nexport function fluidManifestPlugin(): Plugin {\n return {\n name: \"fluid-manifest-plugin\",\n\n configureServer(server) {\n server.middlewares.use(\"/__manifests__\", async (_req, res) => {\n try {\n const serializable = await loadManifests(\n server,\n server.config.logger,\n );\n\n res.setHeader(\"Content-Type\", \"application/json\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.end(JSON.stringify(serializable));\n } catch (err) {\n server.config.logger.error(\n `[fluid] Failed to load manifests: ${err}`,\n );\n res.statusCode = 500;\n res.end(JSON.stringify({ error: String(err) }));\n }\n });\n },\n\n generateBundle() {\n // Build mode: emit placeholder. The CLI extraction utility handles\n // actual build-time manifest extraction via tsx subprocess.\n this.warn(\n \"[fluid] fluidManifestPlugin: emitting empty __manifests__.json. \" +\n \"Run `fluid build` instead of `vite build` to include widget manifests.\",\n );\n this.emitFile({\n type: \"asset\",\n fileName: \"__manifests__.json\",\n source: JSON.stringify([]),\n });\n },\n };\n}\n\n/**\n * Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.\n * Validates each manifest before stripping the `component` field.\n * Returns an empty array if the config module or export is missing/invalid.\n */\nasync function loadManifests(\n server: ViteDevServer,\n logger?: Logger,\n): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(\"/src/portal.config.ts\");\n } catch (err) {\n logger?.warn(\n `[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`,\n );\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) return [];\n\n if (!Array.isArray(rawWidgets)) {\n logger?.warn(\n `[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`,\n );\n return [];\n }\n\n const manifests = rawWidgets as Record<string, unknown>[];\n\n // Validate full manifests (with component) before stripping\n if (logger) {\n for (const manifest of manifests) {\n const result = validateManifest(manifest);\n if (!result.success) {\n const type = (manifest as { type?: string }).type ?? \"unknown\";\n logger.warn(\n `[fluid] Invalid manifest for \"${type}\":\\n` +\n result.errors.map((e) => ` - ${e.path}: ${e.message}`).join(\"\\n\"),\n );\n }\n }\n }\n\n return manifests.map(\n ({ component: _component, ...rest }: Record<string, unknown>) => rest,\n );\n}\n","import type { Plugin } from \"vite\";\n\nconst VIRTUAL_ENTRY_ID = \"virtual:builder-preview-entry\";\nconst RESOLVED_VIRTUAL_ID = \"\\0\" + VIRTUAL_ENTRY_ID;\nconst VIRTUAL_MODULE_URL = \"/@id/__x00__virtual:builder-preview-entry\";\n\nconst RAW_HTML = `<!doctype html>\n<html lang=\"en\" data-theme-mode=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Custom Widget Preview</title>\n <style>\n body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }\n </style>\n </head>\n <body>\n <div id=\"builder-preview-root\"></div>\n <script type=\"module\" src=\"${VIRTUAL_MODULE_URL}\"></script>\n <script type=\"module\">import \"/src/index.css\";</script>\n </body>\n</html>`;\n\n/**\n * Vite plugin that serves a standalone widget preview page at /builder-preview.\n *\n * Dev mode only. Renders all customWidgets from portal.config.ts with\n * a live preview and property editor — no auth, no iframe, no fluid-admin needed.\n *\n * Uses a virtual module to bridge the user's portal.config.ts into the preview app.\n */\nexport function fluidBuilderPreviewPlugin(): Plugin {\n return {\n name: \"fluid-builder-preview-plugin\",\n apply: \"serve\",\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_ID) {\n return `\nimport * as portalConfig from \"/src/portal.config.ts\";\nimport { createRoot } from \"react-dom/client\";\nimport { createElement } from \"react\";\nimport { BuilderPreviewApp } from \"@fluid-app/portal-preview\";\n\nconst widgets = portalConfig.customWidgets || [];\nconst root = document.getElementById(\"builder-preview-root\");\nif (root) {\n createRoot(root).render(\n createElement(BuilderPreviewApp, { widgets })\n );\n}\n`;\n }\n },\n\n configureServer(server) {\n server.middlewares.use(async (req, res, next) => {\n const pathname = (req.url ?? \"\").split(\"?\")[0];\n if (pathname !== \"/builder-preview\" && pathname !== \"/builder-preview/\")\n return next();\n try {\n const transformed = await server.transformIndexHtml(\n \"/builder-preview\",\n RAW_HTML,\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(transformed);\n } catch (e) {\n server.config.logger.error(\n `[fluid] Failed to serve builder preview: ${e}`,\n );\n res.statusCode = 500;\n res.end(\"Builder preview failed to load\");\n }\n });\n },\n };\n}\n"],"mappings":";;;;;;;;AAQA,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAWD,SAAgB,iBAAiB,OAAkC;CACjE,MAAM,SAA4B,EAAE;CACpC,MAAM,IAAI;AAEV,KAAI,CAAC,KAAK,OAAO,MAAM,SACrB,QAAO;EACL,SAAS;EACT,QAAQ,CAAC;GAAE,MAAM;GAAI,SAAS;GAA8B,CAAC;EAC9D;AAIH,MAAK,MAAM,OAAO;EAChB;EACA;EACA;EACA;EACA;EACD,CACC,KAAI,OAAO,EAAE,SAAS,YAAa,EAAE,KAAgB,WAAW,EAC9D,QAAO,KAAK;EACV,MAAM;EACN,SAAS,GAAG,IAAI;EACjB,CAAC;AAIN,KAAI,OAAO,EAAE,oBAAoB,YAAY,EAAE,kBAAkB,EAC/D,QAAO,KAAK;EACV,MAAM;EACN,SAAS;EACV,CAAC;AAGJ,KAAI,OAAO,EAAE,cAAc,WACzB,QAAO,KAAK;EACV,MAAM;EACN,SAAS;EACV,CAAC;CAIJ,MAAM,SAAS,EAAE;AACjB,KAAI,UAAU,OAAO,WAAW,UAAU;AACxC,MAAI,OAAO,OAAO,eAAe,YAAY,CAAC,OAAO,WACnD,QAAO,KAAK;GACV,MAAM;GACN,SAAS;GACV,CAAC;AAEJ,MAAI,OAAO,EAAE,SAAS,YAAY,OAAO,eAAe,EAAE,KACxD,QAAO,KAAK;GACV,MAAM;GACN,SAAS;GACV,CAAC;AAEJ,MAAI,MAAM,QAAQ,OAAO,OAAO,CAC9B,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,OAAO,QAAQ,KAAK;GAC7C,MAAM,QAAQ,OAAO,OAAO;AAC5B,OAAI,CAAC,SAAS,OAAO,MAAM,SAAS,SAAU;AAC9C,OACE,CAAC,kBAAkB,SACjB,MAAM,KACP,CAED,QAAO,KAAK;IACV,MAAM,yBAAyB,EAAE;IACjC,SAAS,uBAAuB,MAAM,KAAK,kBAAkB,kBAAkB,KAAK,KAAK;IAC1F,CAAC;;;AAMV,QAAO,OAAO,WAAW,IAAI,EAAE,SAAS,MAAM,GAAG;EAAE,SAAS;EAAO;EAAQ;;;;;;;;;;;;;;;;ACzG7E,SAAgB,sBAA8B;AAC5C,QAAO;EACL,MAAM;EAEN,gBAAgB,QAAQ;AACtB,UAAO,YAAY,IAAI,kBAAkB,OAAO,MAAM,QAAQ;AAC5D,QAAI;KACF,MAAM,eAAe,MAAM,cACzB,QACA,OAAO,OAAO,OACf;AAED,SAAI,UAAU,gBAAgB,mBAAmB;AACjD,SAAI,UAAU,+BAA+B,IAAI;AACjD,SAAI,IAAI,KAAK,UAAU,aAAa,CAAC;aAC9B,KAAK;AACZ,YAAO,OAAO,OAAO,MACnB,qCAAqC,MACtC;AACD,SAAI,aAAa;AACjB,SAAI,IAAI,KAAK,UAAU,EAAE,OAAO,OAAO,IAAI,EAAE,CAAC,CAAC;;KAEjD;;EAGJ,iBAAiB;AAGf,QAAK,KACH,yIAED;AACD,QAAK,SAAS;IACZ,MAAM;IACN,UAAU;IACV,QAAQ,KAAK,UAAU,EAAE,CAAC;IAC3B,CAAC;;EAEL;;;;;;;AAQH,eAAe,cACb,QACA,QACoB;CACpB,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,OAAO,cAAc,wBAAwB;UAClD,KAAK;AACZ,UAAQ,KACN,4CAA4C,eAAe,QAAQ,IAAI,UAAU,MAClF;AACD,SAAO,EAAE;;CAGX,MAAM,aAAa,IAAI;AACvB,KAAI,CAAC,WAAY,QAAO,EAAE;AAE1B,KAAI,CAAC,MAAM,QAAQ,WAAW,EAAE;AAC9B,UAAQ,KACN,qDAAqD,OAAO,WAAW,+BACxE;AACD,SAAO,EAAE;;CAGX,MAAM,YAAY;AAGlB,KAAI,OACF,MAAK,MAAM,YAAY,WAAW;EAChC,MAAM,SAAS,iBAAiB,SAAS;AACzC,MAAI,CAAC,OAAO,SAAS;GACnB,MAAM,OAAQ,SAA+B,QAAQ;AACrD,UAAO,KACL,iCAAiC,KAAK,QACpC,OAAO,OAAO,KAAK,MAAM,OAAO,EAAE,KAAK,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK,CACrE;;;AAKP,QAAO,UAAU,KACd,EAAE,WAAW,YAAY,GAAG,WAAoC,KAClE;;;;ACrGH,MAAM,mBAAmB;AACzB,MAAM,sBAAsB,OAAO;AAGnC,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;AAyBjB,SAAgB,4BAAoC;AAClD,QAAO;EACL,MAAM;EACN,OAAO;EAEP,UAAU,IAAI;AACZ,OAAI,OAAO,iBAAkB,QAAO;;EAGtC,KAAK,IAAI;AACP,OAAI,OAAO,oBACT,QAAO;;;;;;;;;;;;;;;EAiBX,gBAAgB,QAAQ;AACtB,UAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;IAC/C,MAAM,YAAY,IAAI,OAAO,IAAI,MAAM,IAAI,CAAC;AAC5C,QAAI,aAAa,sBAAsB,aAAa,oBAClD,QAAO,MAAM;AACf,QAAI;KACF,MAAM,cAAc,MAAM,OAAO,mBAC/B,oBACA,SACD;AACD,SAAI,UAAU,gBAAgB,YAAY;AAC1C,SAAI,IAAI,YAAY;aACb,GAAG;AACV,YAAO,OAAO,OAAO,MACnB,4CAA4C,IAC7C;AACD,SAAI,aAAa;AACjB,SAAI,IAAI,iCAAiC;;KAE3C;;EAEL"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/vite/validate-manifest.ts","../../src/vite/builder-preview-plugin.ts","../../src/vite/manifest-plugin.ts"],"sourcesContent":["/**\n * Lightweight manifest validation for the SDK vite plugin.\n *\n * Inlined here (rather than imported from @fluid-app/portal-core) because\n * portal-core is private and not published to npm. This avoids a runtime\n * ERR_MODULE_NOT_FOUND for portals installed from npm.\n */\n\nconst VALID_FIELD_TYPES = [\n \"text\",\n \"textarea\",\n \"number\",\n \"boolean\",\n \"select\",\n \"color\",\n \"range\",\n \"dataSource\",\n \"resource\",\n \"image\",\n \"alignment\",\n \"slider\",\n \"colorPicker\",\n \"sectionHeader\",\n \"separator\",\n \"buttonGroup\",\n \"colorSelect\",\n \"sectionLayoutSelect\",\n \"background\",\n \"contentPosition\",\n \"textSizeSelect\",\n \"cssUnit\",\n \"fontPicker\",\n \"stringArray\",\n \"borderRadius\",\n \"screenPicker\",\n] as const;\n\ninterface ValidationError {\n path: string;\n message: string;\n}\n\ntype ValidationResult =\n | { success: true }\n | { success: false; errors: ValidationError[] };\n\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: ValidationError[] = [];\n const m = input as Record<string, unknown>;\n\n if (!m || typeof m !== \"object\") {\n return {\n success: false,\n errors: [{ path: \"\", message: \"Manifest must be an object\" }],\n };\n }\n\n // Required string fields\n for (const key of [\n \"type\",\n \"displayName\",\n \"description\",\n \"icon\",\n \"category\",\n ]) {\n if (typeof m[key] !== \"string\" || (m[key] as string).length === 0) {\n errors.push({\n path: key,\n message: `${key} is required and must be a non-empty string`,\n });\n }\n }\n\n if (typeof m.manifestVersion !== \"number\" || m.manifestVersion < 1) {\n errors.push({\n path: \"manifestVersion\",\n message: \"manifestVersion must be a positive integer\",\n });\n }\n\n if (typeof m.component !== \"function\") {\n errors.push({\n path: \"component\",\n message: \"component must be a React component (function)\",\n });\n }\n\n // Property schema validation\n const schema = m.propertySchema as Record<string, unknown> | undefined;\n if (schema && typeof schema === \"object\") {\n if (typeof schema.widgetType !== \"string\" || !schema.widgetType) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"widgetType is required\",\n });\n }\n if (typeof m.type === \"string\" && schema.widgetType !== m.type) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"manifest.type must match manifest.propertySchema.widgetType\",\n });\n }\n if (Array.isArray(schema.fields)) {\n for (let i = 0; i < schema.fields.length; i++) {\n const field = schema.fields[i] as Record<string, unknown>;\n if (!field || typeof field.type !== \"string\") continue;\n if (\n !VALID_FIELD_TYPES.includes(\n field.type as (typeof VALID_FIELD_TYPES)[number],\n )\n ) {\n errors.push({\n path: `propertySchema.fields.${i}.type`,\n message: `Invalid field type \"${field.type}\". Valid types: ${VALID_FIELD_TYPES.join(\", \")}`,\n });\n }\n }\n }\n }\n\n return errors.length === 0 ? { success: true } : { success: false, errors };\n}\n","import type { Plugin, ResolvedConfig } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst VIRTUAL_ENTRY_ID = \"virtual:builder-preview-entry\";\nconst RESOLVED_VIRTUAL_ID = \"\\0\" + VIRTUAL_ENTRY_ID;\n\nconst RAW_HTML = `<!doctype html>\n<html lang=\"en\" data-theme-mode=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Custom Widget Preview</title>\n <style>\n body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }\n </style>\n </head>\n <body>\n <div id=\"builder-preview-root\"></div>\n <script type=\"module\" src=\"/@id/virtual:builder-preview-entry\"></script>\n </body>\n</html>`;\n\n/**\n * Vite plugin that serves a standalone widget preview page at /builder-preview.\n *\n * Dev mode only. Renders all customWidgets from portal.config.ts with\n * a live preview and property editor — no auth, no iframe, no fluid-admin needed.\n */\nexport function fluidBuilderPreviewPlugin(): Plugin {\n let configPath: string;\n let cssPath: string;\n\n return {\n name: \"fluid-builder-preview\",\n apply: \"serve\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\"src/portal.config.ts\", \"portal.config.ts\"];\n configPath =\n candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\";\n const cssCandidates = [\n \"src/index.css\",\n \"src/styles/index.css\",\n \"index.css\",\n ];\n cssPath =\n cssCandidates.find((c) => existsSync(join(root, c))) ?? \"src/index.css\";\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_ID) {\n return `\nimport \"/${cssPath}\";\nimport * as portalConfig from \"/${configPath}\";\nimport { createRoot } from \"react-dom/client\";\nimport { createElement } from \"react\";\nimport { BuilderPreviewApp } from \"@fluid-app/portal-preview\";\n\nconst widgets = portalConfig.customWidgets || [];\nconst root = document.getElementById(\"builder-preview-root\");\nif (root) {\n createRoot(root).render(\n createElement(BuilderPreviewApp, { widgets })\n );\n}\n`;\n }\n },\n\n configureServer(server) {\n server.middlewares.use(async (req, res, next) => {\n const pathname = (req.url ?? \"\").split(\"?\")[0];\n if (pathname !== \"/builder-preview\" && pathname !== \"/builder-preview/\")\n return next();\n try {\n const transformed = await server.transformIndexHtml(\n \"/builder-preview\",\n RAW_HTML,\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(transformed);\n } catch (e) {\n server.config.logger.error(\n `[fluid] Failed to serve builder preview: ${e}`,\n );\n res.statusCode = 500;\n res.end(\"Builder preview failed to load\");\n }\n });\n },\n };\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer, Logger } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { validateManifest } from \"./validate-manifest\";\nimport { fluidBuilderPreviewPlugin } from \"./builder-preview-plugin\";\n\n/**\n * Vite plugin bundle that serves widget manifest metadata and the builder preview.\n *\n * Returns an array of plugins:\n * 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)\n * 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)\n *\n * Every portal using `fluidManifestPlugin()` automatically gets the builder preview.\n */\nexport function fluidManifestPlugin(): Plugin[] {\n return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];\n}\n\nfunction fluidManifestPluginInternal(): Plugin {\n let configPath: string;\n\n return {\n name: \"fluid-manifest-plugin\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\"src/portal.config.ts\", \"portal.config.ts\"];\n configPath =\n \"/\" +\n (candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\");\n },\n\n configureServer(server) {\n server.middlewares.use(\"/__manifests__\", async (_req, res) => {\n try {\n const serializable = await loadManifests(\n server,\n server.config.logger,\n configPath,\n );\n\n res.setHeader(\"Content-Type\", \"application/json\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.end(JSON.stringify(serializable));\n } catch (err) {\n server.config.logger.error(\n `[fluid] Failed to load manifests: ${err}`,\n );\n res.statusCode = 500;\n res.end(JSON.stringify({ error: String(err) }));\n }\n });\n },\n\n generateBundle() {\n // Build mode: emit placeholder. The CLI extraction utility handles\n // actual build-time manifest extraction via tsx subprocess.\n this.warn(\n \"[fluid] fluidManifestPlugin: emitting empty __manifests__.json. \" +\n \"Run `fluid build` instead of `vite build` to include widget manifests.\",\n );\n this.emitFile({\n type: \"asset\",\n fileName: \"__manifests__.json\",\n source: JSON.stringify([]),\n });\n },\n };\n}\n\n/**\n * Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.\n * Validates each manifest before stripping the `component` field.\n * Returns an empty array if the config module or export is missing/invalid.\n */\nasync function loadManifests(\n server: ViteDevServer,\n logger?: Logger,\n portalConfigPath?: string,\n): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(\n portalConfigPath ?? \"/src/portal.config.ts\",\n );\n } catch (err) {\n logger?.warn(\n `[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`,\n );\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) return [];\n\n if (!Array.isArray(rawWidgets)) {\n logger?.warn(\n `[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`,\n );\n return [];\n }\n\n const manifests = rawWidgets as Record<string, unknown>[];\n\n // Validate full manifests (with component) before stripping\n if (logger) {\n for (const manifest of manifests) {\n const result = validateManifest(manifest);\n if (!result.success) {\n const type = (manifest as { type?: string }).type ?? \"unknown\";\n logger.warn(\n `[fluid] Invalid manifest for \"${type}\":\\n` +\n result.errors.map((e) => ` - ${e.path}: ${e.message}`).join(\"\\n\"),\n );\n }\n }\n }\n\n return manifests.map(\n ({ component: _component, ...rest }: Record<string, unknown>) => rest,\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluid-app/portal-sdk",
3
- "version": "0.1.64",
3
+ "version": "0.1.65",
4
4
  "description": "SDK for building custom Fluid portals",
5
5
  "files": [
6
6
  "dist",
@@ -44,6 +44,7 @@
44
44
  "tailwind-merge": "^3.0.2",
45
45
  "tw-animate-css": "^1.4.0",
46
46
  "use-sync-external-store": "^1.4.0",
47
+ "@fluid-app/portal-preview": "0.1.0",
47
48
  "@fluid-app/portal-core": "0.1.23"
48
49
  },
49
50
  "devDependencies": {
@@ -65,9 +66,9 @@
65
66
  "tsdown": "^0.21.0",
66
67
  "typescript": "^5",
67
68
  "zod": "4.3.5",
68
- "@fluid-app/auth": "0.1.0",
69
69
  "@fluid-app/api-client-core": "0.1.0",
70
70
  "@fluid-app/company-switcher-core": "0.1.0",
71
+ "@fluid-app/auth": "0.1.0",
71
72
  "@fluid-app/company-switcher-ui": "0.1.0",
72
73
  "@fluid-app/contacts-ui": "0.1.0",
73
74
  "@fluid-app/file-picker-api-client": "0.1.0",
@@ -75,34 +76,34 @@
75
76
  "@fluid-app/fluidos-api-client": "0.1.0",
76
77
  "@fluid-app/messaging-api-client": "0.1.0",
77
78
  "@fluid-app/messaging-core": "0.1.0",
79
+ "@fluid-app/messaging-ui": "0.1.0",
78
80
  "@fluid-app/mysite-ui": "0.1.0",
79
81
  "@fluid-app/orders-api-client": "0.1.0",
80
- "@fluid-app/messaging-ui": "0.1.0",
81
82
  "@fluid-app/orders-core": "0.1.0",
82
83
  "@fluid-app/orders-ui": "0.1.0",
83
- "@fluid-app/permissions": "0.1.0",
84
84
  "@fluid-app/portal-app-download-ui": "0.1.0",
85
+ "@fluid-app/permissions": "0.1.0",
85
86
  "@fluid-app/portal-preview": "0.1.0",
86
87
  "@fluid-app/portal-pro-upgrade-ui": "0.1.0",
87
- "@fluid-app/portal-react": "0.1.0",
88
88
  "@fluid-app/portal-widgets": "0.1.22",
89
+ "@fluid-app/portal-react": "0.1.0",
89
90
  "@fluid-app/products-api-client": "0.1.0",
90
91
  "@fluid-app/products-core": "0.1.0",
91
92
  "@fluid-app/profile-core": "0.1.0",
92
- "@fluid-app/query-persister": "0.1.0",
93
93
  "@fluid-app/profile-ui": "0.1.0",
94
+ "@fluid-app/query-persister": "0.1.0",
94
95
  "@fluid-app/shareables-api-client": "0.1.0",
95
96
  "@fluid-app/shareables-core": "0.1.0",
96
97
  "@fluid-app/shareables-ui": "0.1.0",
97
- "@fluid-app/shop-ui": "0.1.0",
98
98
  "@fluid-app/subscriptions-api-client": "0.1.0",
99
- "@fluid-app/subscriptions-ui": "0.1.0",
99
+ "@fluid-app/shop-ui": "0.1.0",
100
100
  "@fluid-app/subscriptions-core": "0.1.0",
101
- "@fluid-app/ui-primitives": "0.1.13",
101
+ "@fluid-app/subscriptions-ui": "0.1.0",
102
102
  "@fluid-app/typescript-config": "0.0.0",
103
+ "@fluid-app/ui-primitives": "0.1.13",
103
104
  "@fluid-app/user-contacts-api-client": "0.1.0",
104
- "@fluid-app/user-notes-api-client": "0.1.0",
105
- "@fluid-app/user-tasks-api-client": "0.1.0"
105
+ "@fluid-app/user-tasks-api-client": "0.1.0",
106
+ "@fluid-app/user-notes-api-client": "0.1.0"
106
107
  },
107
108
  "peerDependencies": {
108
109
  "@hookform/resolvers": "^5.2.2",
@@ -1 +0,0 @@
1
- {"version":3,"file":"AppDownloadScreen-oMzxBETr.cjs","names":["ExternalLink","AppDownloadScreen","useCurrentUser","AppDownloadScreenUI"],"sources":["../../app-download-ui/src/components/AppStoreButtons.tsx","../../app-download-ui/src/components/QrCodeSection.tsx","../../app-download-ui/src/components/ImageSection.tsx","../../app-download-ui/src/lib/cn.ts","../../app-download-ui/src/components/AppDownloadScreen.tsx","../src/screens/AppDownloadScreen.tsx"],"sourcesContent":["import { ExternalLink } from \"lucide-react\";\n\nexport interface AppStoreButtonsProps {\n appstoreUrl: string;\n playstoreUrl?: string;\n}\n\nfunction AppleIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 384 512\"\n fill=\"currentColor\"\n className=\"size-5\"\n >\n <path d=\"M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-62.1 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z\" />\n </svg>\n );\n}\n\nfunction GooglePlayIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 512 512\"\n fill=\"currentColor\"\n className=\"size-5\"\n >\n <path d=\"M325.3 234.3L104.6 13l280.8 161.2-60.1 60.1zM47 0C34 6.8 25.3 19.2 25.3 35.3v441.3c0 16.1 8.7 28.5 21.7 35.3l256.6-256L47 0zm425.2 225.6l-58.9-34.1-65.7 64.5 65.7 64.5 60.1-34.1c18-14.3 18-46.5-1.2-60.8zM104.6 499l280.8-161.2-60.1-60.1L104.6 499z\" />\n </svg>\n );\n}\n\nexport function AppStoreButtons({\n appstoreUrl,\n playstoreUrl,\n}: AppStoreButtonsProps) {\n return (\n <div\n className={`grid gap-4 ${playstoreUrl ? \"grid-cols-2\" : \"grid-cols-1\"}`}\n >\n <a\n href={appstoreUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <AppleIcon />\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Open\n <br />\n App Store\n </div>\n </div>\n <div className=\"flex items-start\">\n <ExternalLink className=\"size-4\" />\n </div>\n </div>\n </a>\n\n {playstoreUrl && (\n <a\n href={playstoreUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <GooglePlayIcon />\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Open\n <br />\n Play Store\n </div>\n </div>\n <div className=\"flex items-start\">\n <ExternalLink className=\"size-4\" />\n </div>\n </div>\n </a>\n )}\n </div>\n );\n}\n","export interface QrCodeSectionProps {\n companyId: number;\n}\n\nexport function QrCodeSection({ companyId }: QrCodeSectionProps) {\n const redirectUrl = `https://api.fluid.app/mobile_app_download/${companyId}`;\n const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(redirectUrl)}`;\n\n return (\n <a\n href={redirectUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex items-end justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Scan to\n <br />\n Download\n </div>\n </div>\n <div className=\"flex\">\n <img\n src={qrCodeUrl}\n alt=\"QR code for mobile app download\"\n className=\"h-16 w-16\"\n loading=\"lazy\"\n />\n </div>\n </div>\n </a>\n );\n}\n","const DEFAULT_IMAGE =\n \"https://assets.fluid.app/fluid-admin/images/we-commerce/iphone_product.png\";\n\nconst DEFAULT_LOGO =\n \"https://assets.fluid.app/fluid-admin/images/we-commerce/we-commerce.png\";\n\nexport interface ImageSectionProps {\n logoUrl?: string | null;\n screenshotUrl?: string | null;\n appName?: string;\n}\n\nexport function ImageSection({\n logoUrl,\n screenshotUrl,\n appName = \"App\",\n}: ImageSectionProps) {\n return (\n <div className=\"flex flex-col overflow-hidden\">\n <div className=\"-mb-px flex flex-1 flex-col\">\n <div className=\"flex h-20 px-3 pt-8\">\n <img\n src={logoUrl || DEFAULT_LOGO}\n alt={appName}\n width={150}\n height={150}\n className=\"object-contain\"\n />\n </div>\n <div\n className={\n screenshotUrl\n ? \"mt-8 flex flex-1 items-end justify-start\"\n : \"-ml-20 flex flex-1 items-end justify-start\"\n }\n >\n <img\n src={screenshotUrl || DEFAULT_IMAGE}\n alt=\"Mobile app interface\"\n width={480}\n height={500}\n className=\"h-full w-auto object-cover\"\n />\n </div>\n </div>\n </div>\n );\n}\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import { AppStoreButtons } from \"./AppStoreButtons\";\nimport { QrCodeSection } from \"./QrCodeSection\";\nimport { ImageSection } from \"./ImageSection\";\nimport { cn } from \"../lib/cn\";\n\nconst DEFAULT_APPSTORE_URL =\n \"https://apps.apple.com/app/wecommerce/id1564463740\";\nconst DEFAULT_PLAYSTORE_URL =\n \"https://play.google.com/store/apps/details?id=com.fluid.crm\";\n\nexport interface AppDownloadScreenProps {\n companyId: number;\n appstoreUrl?: string | null;\n playstoreUrl?: string | null;\n logoUrl?: string | null;\n screenshotUrl?: string | null;\n appName?: string;\n className?: string;\n}\n\nexport function AppDownloadScreen({\n companyId,\n appstoreUrl,\n playstoreUrl,\n logoUrl,\n screenshotUrl,\n appName,\n className,\n}: AppDownloadScreenProps) {\n const resolvedAppstoreUrl = appstoreUrl?.trim() || DEFAULT_APPSTORE_URL;\n const resolvedPlaystoreUrl = playstoreUrl?.trim() || DEFAULT_PLAYSTORE_URL;\n\n return (\n <div\n className={cn(\"flex h-full items-center justify-center p-4\", className)}\n >\n <div className=\"bg-muted relative mx-4 w-full max-w-6xl overflow-hidden rounded-xl px-6 shadow-xl\">\n <div className=\"grid h-150 w-full grid-cols-1 lg:grid-cols-2\">\n {/* Left side - Images */}\n <ImageSection\n logoUrl={logoUrl}\n screenshotUrl={screenshotUrl}\n appName={appName}\n />\n\n {/* Right side - Content */}\n <div className=\"space-y-2 pt-8 pb-8\">\n <h1 className=\"mt-12 text-left text-[32px] leading-tight font-black text-gray-900\">\n EVERYTHING YOU NEED\n </h1>\n <h1 className=\"-mt-2 text-left text-[32px] leading-tight font-black text-gray-900\">\n AT YOUR FINGERTIPS\n </h1>\n <p className=\"-mt-1 text-left text-[14px] leading-relaxed text-gray-600\">\n Everything you need at your fingertips. Run your business from one\n place while on the go. Share, message, sell, and manage from the\n mobile app.\n </p>\n\n <h3 className=\"mt-16 text-left text-[12px] font-bold text-gray-900\">\n Download the App\n </h3>\n\n <AppStoreButtons\n appstoreUrl={resolvedAppstoreUrl}\n playstoreUrl={resolvedPlaystoreUrl}\n />\n\n <QrCodeSection companyId={companyId} />\n </div>\n </div>\n </div>\n </div>\n );\n}\n","import type { WidgetPropertySchema } from \"../registries/property-schema-types\";\nimport { AppDownloadScreen as AppDownloadScreenUI } from \"@fluid-app/portal-app-download-ui\";\nimport { useCurrentUser } from \"../hooks/use-current-user\";\n\nexport const appDownloadScreenPropertySchema = {\n widgetType: \"AppDownloadScreen\",\n displayName: \"App Download Screen\",\n fields: [],\n} as const satisfies WidgetPropertySchema;\n\nexport function AppDownloadScreen(): React.JSX.Element {\n const { data: user } = useCurrentUser();\n\n return (\n <AppDownloadScreenUI\n companyId={user?.company?.id ?? 0}\n appstoreUrl={user?.company?.appstore_url}\n playstoreUrl={user?.company?.playstore_url}\n logoUrl={user?.company?.logo_url}\n />\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"AppDownloadScreen-pm8fLxZX.mjs","names":["AppDownloadScreen","AppDownloadScreenUI"],"sources":["../../app-download-ui/src/components/AppStoreButtons.tsx","../../app-download-ui/src/components/QrCodeSection.tsx","../../app-download-ui/src/components/ImageSection.tsx","../../app-download-ui/src/lib/cn.ts","../../app-download-ui/src/components/AppDownloadScreen.tsx","../src/screens/AppDownloadScreen.tsx"],"sourcesContent":["import { ExternalLink } from \"lucide-react\";\n\nexport interface AppStoreButtonsProps {\n appstoreUrl: string;\n playstoreUrl?: string;\n}\n\nfunction AppleIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 384 512\"\n fill=\"currentColor\"\n className=\"size-5\"\n >\n <path d=\"M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-62.1 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z\" />\n </svg>\n );\n}\n\nfunction GooglePlayIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 512 512\"\n fill=\"currentColor\"\n className=\"size-5\"\n >\n <path d=\"M325.3 234.3L104.6 13l280.8 161.2-60.1 60.1zM47 0C34 6.8 25.3 19.2 25.3 35.3v441.3c0 16.1 8.7 28.5 21.7 35.3l256.6-256L47 0zm425.2 225.6l-58.9-34.1-65.7 64.5 65.7 64.5 60.1-34.1c18-14.3 18-46.5-1.2-60.8zM104.6 499l280.8-161.2-60.1-60.1L104.6 499z\" />\n </svg>\n );\n}\n\nexport function AppStoreButtons({\n appstoreUrl,\n playstoreUrl,\n}: AppStoreButtonsProps) {\n return (\n <div\n className={`grid gap-4 ${playstoreUrl ? \"grid-cols-2\" : \"grid-cols-1\"}`}\n >\n <a\n href={appstoreUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <AppleIcon />\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Open\n <br />\n App Store\n </div>\n </div>\n <div className=\"flex items-start\">\n <ExternalLink className=\"size-4\" />\n </div>\n </div>\n </a>\n\n {playstoreUrl && (\n <a\n href={playstoreUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <GooglePlayIcon />\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Open\n <br />\n Play Store\n </div>\n </div>\n <div className=\"flex items-start\">\n <ExternalLink className=\"size-4\" />\n </div>\n </div>\n </a>\n )}\n </div>\n );\n}\n","export interface QrCodeSectionProps {\n companyId: number;\n}\n\nexport function QrCodeSection({ companyId }: QrCodeSectionProps) {\n const redirectUrl = `https://api.fluid.app/mobile_app_download/${companyId}`;\n const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(redirectUrl)}`;\n\n return (\n <a\n href={redirectUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block cursor-pointer rounded-sm border bg-white px-4 py-4 text-gray-900 transition-colors hover:bg-gray-50\"\n >\n <div className=\"flex items-end justify-between\">\n <div className=\"flex flex-col items-baseline\">\n <div className=\"mt-1 text-left text-xs font-semibold\">\n Scan to\n <br />\n Download\n </div>\n </div>\n <div className=\"flex\">\n <img\n src={qrCodeUrl}\n alt=\"QR code for mobile app download\"\n className=\"h-16 w-16\"\n loading=\"lazy\"\n />\n </div>\n </div>\n </a>\n );\n}\n","const DEFAULT_IMAGE =\n \"https://assets.fluid.app/fluid-admin/images/we-commerce/iphone_product.png\";\n\nconst DEFAULT_LOGO =\n \"https://assets.fluid.app/fluid-admin/images/we-commerce/we-commerce.png\";\n\nexport interface ImageSectionProps {\n logoUrl?: string | null;\n screenshotUrl?: string | null;\n appName?: string;\n}\n\nexport function ImageSection({\n logoUrl,\n screenshotUrl,\n appName = \"App\",\n}: ImageSectionProps) {\n return (\n <div className=\"flex flex-col overflow-hidden\">\n <div className=\"-mb-px flex flex-1 flex-col\">\n <div className=\"flex h-20 px-3 pt-8\">\n <img\n src={logoUrl || DEFAULT_LOGO}\n alt={appName}\n width={150}\n height={150}\n className=\"object-contain\"\n />\n </div>\n <div\n className={\n screenshotUrl\n ? \"mt-8 flex flex-1 items-end justify-start\"\n : \"-ml-20 flex flex-1 items-end justify-start\"\n }\n >\n <img\n src={screenshotUrl || DEFAULT_IMAGE}\n alt=\"Mobile app interface\"\n width={480}\n height={500}\n className=\"h-full w-auto object-cover\"\n />\n </div>\n </div>\n </div>\n );\n}\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import { AppStoreButtons } from \"./AppStoreButtons\";\nimport { QrCodeSection } from \"./QrCodeSection\";\nimport { ImageSection } from \"./ImageSection\";\nimport { cn } from \"../lib/cn\";\n\nconst DEFAULT_APPSTORE_URL =\n \"https://apps.apple.com/app/wecommerce/id1564463740\";\nconst DEFAULT_PLAYSTORE_URL =\n \"https://play.google.com/store/apps/details?id=com.fluid.crm\";\n\nexport interface AppDownloadScreenProps {\n companyId: number;\n appstoreUrl?: string | null;\n playstoreUrl?: string | null;\n logoUrl?: string | null;\n screenshotUrl?: string | null;\n appName?: string;\n className?: string;\n}\n\nexport function AppDownloadScreen({\n companyId,\n appstoreUrl,\n playstoreUrl,\n logoUrl,\n screenshotUrl,\n appName,\n className,\n}: AppDownloadScreenProps) {\n const resolvedAppstoreUrl = appstoreUrl?.trim() || DEFAULT_APPSTORE_URL;\n const resolvedPlaystoreUrl = playstoreUrl?.trim() || DEFAULT_PLAYSTORE_URL;\n\n return (\n <div\n className={cn(\"flex h-full items-center justify-center p-4\", className)}\n >\n <div className=\"bg-muted relative mx-4 w-full max-w-6xl overflow-hidden rounded-xl px-6 shadow-xl\">\n <div className=\"grid h-150 w-full grid-cols-1 lg:grid-cols-2\">\n {/* Left side - Images */}\n <ImageSection\n logoUrl={logoUrl}\n screenshotUrl={screenshotUrl}\n appName={appName}\n />\n\n {/* Right side - Content */}\n <div className=\"space-y-2 pt-8 pb-8\">\n <h1 className=\"mt-12 text-left text-[32px] leading-tight font-black text-gray-900\">\n EVERYTHING YOU NEED\n </h1>\n <h1 className=\"-mt-2 text-left text-[32px] leading-tight font-black text-gray-900\">\n AT YOUR FINGERTIPS\n </h1>\n <p className=\"-mt-1 text-left text-[14px] leading-relaxed text-gray-600\">\n Everything you need at your fingertips. Run your business from one\n place while on the go. Share, message, sell, and manage from the\n mobile app.\n </p>\n\n <h3 className=\"mt-16 text-left text-[12px] font-bold text-gray-900\">\n Download the App\n </h3>\n\n <AppStoreButtons\n appstoreUrl={resolvedAppstoreUrl}\n playstoreUrl={resolvedPlaystoreUrl}\n />\n\n <QrCodeSection companyId={companyId} />\n </div>\n </div>\n </div>\n </div>\n );\n}\n","import type { WidgetPropertySchema } from \"../registries/property-schema-types\";\nimport { AppDownloadScreen as AppDownloadScreenUI } from \"@fluid-app/portal-app-download-ui\";\nimport { useCurrentUser } from \"../hooks/use-current-user\";\n\nexport const appDownloadScreenPropertySchema = {\n widgetType: \"AppDownloadScreen\",\n displayName: \"App Download Screen\",\n fields: [],\n} as const satisfies WidgetPropertySchema;\n\nexport function AppDownloadScreen(): React.JSX.Element {\n const { data: user } = useCurrentUser();\n\n return (\n <AppDownloadScreenUI\n companyId={user?.company?.id ?? 0}\n appstoreUrl={user?.company?.appstore_url}\n playstoreUrl={user?.company?.playstore_url}\n logoUrl={user?.company?.logo_url}\n />\n );\n}\n"],"mappings":";;;;;;AAOA,SAAS,YAAY;AACnB,QACE,oBAAC,OAAD;EACE,OAAM;EACN,SAAQ;EACR,MAAK;EACL,WAAU;YAEV,oBAAC,QAAD,EAAM,GAAE,ubAAwb,CAAA;EAC5b,CAAA;;AAIV,SAAS,iBAAiB;AACxB,QACE,oBAAC,OAAD;EACE,OAAM;EACN,SAAQ;EACR,MAAK;EACL,WAAU;YAEV,oBAAC,QAAD,EAAM,GAAE,0PAA2P,CAAA;EAC/P,CAAA;;AAIV,SAAgB,gBAAgB,EAC9B,aACA,gBACuB;AACvB,QACE,qBAAC,OAAD;EACE,WAAW,cAAc,eAAe,gBAAgB;YAD1D,CAGE,oBAAC,KAAD;GACE,MAAM;GACN,QAAO;GACP,KAAI;GACJ,WAAU;aAEV,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,oBAAC,WAAD,EAAa,CAAA,EACb,qBAAC,OAAD;MAAK,WAAU;gBAAf;OAAsD;OAEpD,oBAAC,MAAD,EAAM,CAAA;;OAEF;QACF;QACN,oBAAC,OAAD;KAAK,WAAU;eACb,oBAAC,cAAD,EAAc,WAAU,UAAW,CAAA;KAC/B,CAAA,CACF;;GACJ,CAAA,EAEH,gBACC,oBAAC,KAAD;GACE,MAAM;GACN,QAAO;GACP,KAAI;GACJ,WAAU;aAEV,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,oBAAC,gBAAD,EAAkB,CAAA,EAClB,qBAAC,OAAD;MAAK,WAAU;gBAAf;OAAsD;OAEpD,oBAAC,MAAD,EAAM,CAAA;;OAEF;QACF;QACN,oBAAC,OAAD;KAAK,WAAU;eACb,oBAAC,cAAD,EAAc,WAAU,UAAW,CAAA;KAC/B,CAAA,CACF;;GACJ,CAAA,CAEF;;;;;AChFV,SAAgB,cAAc,EAAE,aAAiC;CAC/D,MAAM,cAAc,6CAA6C;CACjE,MAAM,YAAY,iEAAiE,mBAAmB,YAAY;AAElH,QACE,oBAAC,KAAD;EACE,MAAM;EACN,QAAO;EACP,KAAI;EACJ,WAAU;YAEV,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,OAAD;IAAK,WAAU;cACb,qBAAC,OAAD;KAAK,WAAU;eAAf;MAAsD;MAEpD,oBAAC,MAAD,EAAM,CAAA;;MAEF;;IACF,CAAA,EACN,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,OAAD;KACE,KAAK;KACL,KAAI;KACJ,WAAU;KACV,SAAQ;KACR,CAAA;IACE,CAAA,CACF;;EACJ,CAAA;;;;AChCR,MAAM,gBACJ;AAEF,MAAM,eACJ;AAQF,SAAgB,aAAa,EAC3B,SACA,eACA,UAAU,SACU;AACpB,QACE,oBAAC,OAAD;EAAK,WAAU;YACb,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,OAAD;KACE,KAAK,WAAW;KAChB,KAAK;KACL,OAAO;KACP,QAAQ;KACR,WAAU;KACV,CAAA;IACE,CAAA,EACN,oBAAC,OAAD;IACE,WACE,gBACI,6CACA;cAGN,oBAAC,OAAD;KACE,KAAK,iBAAiB;KACtB,KAAI;KACJ,OAAO;KACP,QAAQ;KACR,WAAU;KACV,CAAA;IACE,CAAA,CACF;;EACF,CAAA;;;;AC1CV,SAAgB,GAAG,GAAG,QAAsB;AAC1C,QAAO,QAAQ,KAAK,OAAO,CAAC;;;;ACC9B,MAAM,uBACJ;AACF,MAAM,wBACJ;AAYF,SAAgBA,oBAAkB,EAChC,WACA,aACA,cACA,SACA,eACA,SACA,aACyB;CACzB,MAAM,sBAAsB,aAAa,MAAM,IAAI;CACnD,MAAM,uBAAuB,cAAc,MAAM,IAAI;AAErD,QACE,oBAAC,OAAD;EACE,WAAW,GAAG,+CAA+C,UAAU;YAEvE,oBAAC,OAAD;GAAK,WAAU;aACb,qBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,oBAAC,cAAD;KACW;KACM;KACN;KACT,CAAA,EAGF,qBAAC,OAAD;KAAK,WAAU;eAAf;MACE,oBAAC,MAAD;OAAI,WAAU;iBAAqE;OAE9E,CAAA;MACL,oBAAC,MAAD;OAAI,WAAU;iBAAqE;OAE9E,CAAA;MACL,oBAAC,KAAD;OAAG,WAAU;iBAA4D;OAIrE,CAAA;MAEJ,oBAAC,MAAD;OAAI,WAAU;iBAAsD;OAE/D,CAAA;MAEL,oBAAC,iBAAD;OACE,aAAa;OACb,cAAc;OACd,CAAA;MAEF,oBAAC,eAAD,EAA0B,WAAa,CAAA;MACnC;OACF;;GACF,CAAA;EACF,CAAA;;;;ACpEV,MAAa,kCAAkC;CAC7C,YAAY;CACZ,aAAa;CACb,QAAQ,EAAE;CACX;AAED,SAAgB,oBAAuC;CACrD,MAAM,EAAE,MAAM,SAAS,gBAAgB;AAEvC,QACE,oBAACC,qBAAD;EACE,WAAW,MAAM,SAAS,MAAM;EAChC,aAAa,MAAM,SAAS;EAC5B,cAAc,MAAM,SAAS;EAC7B,SAAS,MAAM,SAAS;EACxB,CAAA"}