@realiizlabs/admin 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -894,12 +894,149 @@ var STUDIO_CSS = `
894
894
  .rz-admin .rz-help__pager a:hover { color: var(--signal); }
895
895
  .rz-admin .rz-arrow--back { transform: none; }
896
896
  @media (prefers-reduced-motion: no-preference) { .rz-admin a:hover .rz-arrow--back { transform: translateX(-4px); } }
897
+
898
+ /* ---- Studio update strip / Settings \u2192 Studio ---------------------------- */
899
+ .rz-admin .rz-update { border: 1px solid var(--border); border-left: 3px solid var(--signal); border-radius: var(--rz-radius); background: var(--surface-1); padding: .7rem 1rem; }
900
+ .rz-admin .rz-update--strip { margin: 0 0 1.25rem; }
901
+ .rz-admin .rz-update__row { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; }
902
+ .rz-admin .rz-update__title { font-weight: 600; color: var(--text-primary); }
903
+ .rz-admin .rz-update__link { display: inline-flex; align-items: center; gap: .3rem; background: none; border: 0; padding: 0; font: inherit; font-size: .875rem; color: var(--signal); cursor: pointer; text-decoration: none; }
904
+ .rz-admin .rz-update__link:hover { color: var(--text-primary); }
905
+ .rz-admin .rz-update__later { margin-left: auto; background: none; border: 0; padding: 0; font: inherit; font-size: .8rem; color: var(--text-muted); cursor: pointer; }
906
+ .rz-admin .rz-update__later:hover { color: var(--text-primary); }
907
+ .rz-admin .rz-update__notes { margin: .6rem 0 0; font-size: .875rem; line-height: 1.55; color: var(--text-secondary); white-space: pre-line; }
908
+ .rz-admin .rz-update__problem { margin: .6rem 0 0; font-size: .875rem; color: var(--status-down); }
909
+ .rz-admin .rz-update__done { display: block; margin-top: .5rem; font-size: .875rem; color: var(--text-secondary); }
897
910
  `;
898
- function StudioShellFrame({ siteName, logoUrl, siteUrl, user, role, nav, pinnedNav, branding, children }) {
911
+ var POLL_MS = 8e3;
912
+ var LOCAL_FALLBACK_MS = 15e4;
913
+ var GIVE_UP_MS = 15 * 6e4;
914
+ function useDeployed(active, mergedSha) {
915
+ const [state, setState] = react.useState("waiting");
916
+ react.useEffect(() => {
917
+ if (!active) return;
918
+ let stop = false;
919
+ let baseline;
920
+ const started = Date.now();
921
+ const read2 = async () => {
922
+ try {
923
+ const r = await fetch("/api/version", { cache: "no-store" });
924
+ const j = await r.json();
925
+ return j.sha;
926
+ } catch {
927
+ return null;
928
+ }
929
+ };
930
+ const tick = async () => {
931
+ if (stop) return;
932
+ const sha = await read2();
933
+ if (stop) return;
934
+ if (baseline === void 0) baseline = sha;
935
+ const elapsed = Date.now() - started;
936
+ const matched = sha !== null && mergedSha !== null && sha === mergedSha;
937
+ const moved = sha !== null && baseline !== null && sha !== baseline;
938
+ const localDone = sha === null && elapsed >= LOCAL_FALLBACK_MS;
939
+ if (matched || moved || localDone) {
940
+ setState("live");
941
+ return;
942
+ }
943
+ if (elapsed >= GIVE_UP_MS) {
944
+ setState("slow");
945
+ return;
946
+ }
947
+ setTimeout(tick, POLL_MS);
948
+ };
949
+ void tick();
950
+ return () => {
951
+ stop = true;
952
+ };
953
+ }, [active, mergedSha]);
954
+ return state;
955
+ }
956
+ var HIDE_KEY = "rz-update-later";
957
+ function canSeeUpdates(role) {
958
+ return role === "owner" || role === "staff";
959
+ }
960
+ function UpdateStrip({ update, role }) {
961
+ const [hidden, setHidden] = react.useState(true);
962
+ react.useEffect(() => {
963
+ try {
964
+ setHidden(sessionStorage.getItem(HIDE_KEY) === update?.version);
965
+ } catch {
966
+ setHidden(false);
967
+ }
968
+ }, [update?.version]);
969
+ if (!update || update.state !== "ready" || !canSeeUpdates(role) || hidden) return null;
970
+ const later = () => {
971
+ try {
972
+ sessionStorage.setItem(HIDE_KEY, update.version);
973
+ } catch {
974
+ }
975
+ setHidden(true);
976
+ };
977
+ return /* @__PURE__ */ jsxRuntime.jsx(UpdateBody, { update, onLater: later, compact: true });
978
+ }
979
+ function UpdateBody({ update, onLater, compact = false }) {
980
+ const { actions } = useStudio();
981
+ const [showNotes, setShowNotes] = react.useState(!compact);
982
+ const [busy, setBusy] = react.useState(false);
983
+ const [outcome, setOutcome] = react.useState(null);
984
+ const merged = outcome?.state === "merged";
985
+ const deploy = useDeployed(merged, merged ? outcome.sha : null);
986
+ const run = async () => {
987
+ setBusy(true);
988
+ try {
989
+ setOutcome(await actions.updateStudio(update.number));
990
+ } finally {
991
+ setBusy(false);
992
+ }
993
+ };
994
+ if (merged) {
995
+ const live = deploy === "live";
996
+ const steps = [
997
+ { label: "Updated", state: "done", note: `Studio ${update.version}` },
998
+ { label: "Live", state: live ? "done" : "active", note: live ? "On the site now" : deploy === "slow" ? "Taking longer than usual" : "Rebuilding, about two minutes" }
999
+ ];
1000
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `rz-update${compact ? " rz-update--strip" : ""}`, role: "status", children: [
1001
+ /* @__PURE__ */ jsxRuntime.jsx(Steps, { steps }),
1002
+ live && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rz-update__done", children: [
1003
+ "Studio updated to ",
1004
+ update.version,
1005
+ ". Reload to see it."
1006
+ ] })
1007
+ ] });
1008
+ }
1009
+ const problem = outcome && outcome.state !== "pending" ? outcome.state === "stale" ? "The site changed since the checks ran. Try again in a minute." : outcome.state === "red" ? `The checks failed${outcome.failingCheck ? ` (${outcome.failingCheck})` : ""}. Your web team has been able to see this.` : outcome.state === "error" ? outcome.message : null : null;
1010
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `rz-update${compact ? " rz-update--strip" : ""}`, role: "status", children: [
1011
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rz-update__row", children: [
1012
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rz-update__title", children: [
1013
+ "Studio ",
1014
+ update.version,
1015
+ " is available"
1016
+ ] }),
1017
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "rz-update__link", onClick: () => setShowNotes((v) => !v), "aria-expanded": showNotes, children: "What\u2019s new" }),
1018
+ update.previewUrl && /* @__PURE__ */ jsxRuntime.jsxs("a", { href: update.previewUrl, target: "_blank", rel: "noreferrer", className: "rz-update__link", children: [
1019
+ "Preview ",
1020
+ /* @__PURE__ */ jsxRuntime.jsx(OutboundArrow, {})
1021
+ ] }),
1022
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", onClick: run, disabled: busy, children: busy ? "Updating\u2026" : "Update" }),
1023
+ onLater && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "rz-update__later", onClick: onLater, children: "Later" })
1024
+ ] }),
1025
+ showNotes && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rz-update__notes", children: update.notes ?? `Version ${update.version} of Studio, replacing ${update.currentVersion}. Nothing changes on your website until you press Update.` }),
1026
+ problem && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rz-update__problem", children: problem })
1027
+ ] });
1028
+ }
1029
+ function StudioShellFrame({ siteName, logoUrl, siteUrl = "/", user, role, nav, pinnedNav, branding, update = null, children }) {
899
1030
  const pathname = navigation.usePathname() ?? "/admin";
1031
+ react.useEffect(() => {
1032
+ if (user) document.cookie = "rz-studio=1; Path=/; Max-Age=2592000; SameSite=Lax";
1033
+ }, [user]);
900
1034
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
901
1035
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: STUDIO_CSS }),
902
- /* @__PURE__ */ jsxRuntime.jsx(AdminShell, { siteName, logoUrl: logoUrl ?? void 0, siteUrl, user, role, nav, pinnedNav, branding, accountHref: null, activeHref: pathname, children })
1036
+ /* @__PURE__ */ jsxRuntime.jsxs(AdminShell, { siteName, logoUrl: logoUrl ?? void 0, siteUrl, user, role, nav, pinnedNav, branding, accountHref: null, activeHref: pathname, children: [
1037
+ /* @__PURE__ */ jsxRuntime.jsx(UpdateStrip, { update, role }),
1038
+ children
1039
+ ] })
903
1040
  ] });
904
1041
  }
905
1042
 
@@ -2911,51 +3048,6 @@ function RemoveItem({ typeId, slug }) {
2911
3048
  error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rz-alert admin__alert", children: error })
2912
3049
  ] });
2913
3050
  }
2914
- var POLL_MS = 8e3;
2915
- var LOCAL_FALLBACK_MS = 15e4;
2916
- var GIVE_UP_MS = 15 * 6e4;
2917
- function useDeployed(active, mergedSha) {
2918
- const [state, setState] = react.useState("waiting");
2919
- react.useEffect(() => {
2920
- if (!active) return;
2921
- let stop = false;
2922
- let baseline;
2923
- const started = Date.now();
2924
- const read2 = async () => {
2925
- try {
2926
- const r = await fetch("/api/version", { cache: "no-store" });
2927
- const j = await r.json();
2928
- return j.sha;
2929
- } catch {
2930
- return null;
2931
- }
2932
- };
2933
- const tick = async () => {
2934
- if (stop) return;
2935
- const sha = await read2();
2936
- if (stop) return;
2937
- if (baseline === void 0) baseline = sha;
2938
- const elapsed = Date.now() - started;
2939
- const matched = sha !== null && mergedSha !== null && sha === mergedSha;
2940
- const moved = sha !== null && baseline !== null && sha !== baseline;
2941
- const localDone = sha === null && elapsed >= LOCAL_FALLBACK_MS;
2942
- if (matched || moved || localDone) {
2943
- setState("live");
2944
- return;
2945
- }
2946
- if (elapsed >= GIVE_UP_MS) {
2947
- setState("slow");
2948
- return;
2949
- }
2950
- setTimeout(tick, POLL_MS);
2951
- };
2952
- void tick();
2953
- return () => {
2954
- stop = true;
2955
- };
2956
- }, [active, mergedSha]);
2957
- return state;
2958
- }
2959
3051
  var REFRESH_MS = 1e4;
2960
3052
  function PublishPanel({ number, htmlUrl, merged, closed = false, branch, removal = false, mergedSha = null, status, item }) {
2961
3053
  const { mergeContentPr, discardChange } = useStudio().actions;
@@ -3854,12 +3946,46 @@ function BusinessTab({ initial }) {
3854
3946
  ] })
3855
3947
  ] });
3856
3948
  }
3857
- var LABELS = { profile: "My Profile", password: "Change Password", team: "Team", business: "Business" };
3858
- function AccountTabs({ me, manages, team, branding, myUserId, myRole }) {
3949
+ function StudioTab({ update, version, role }) {
3950
+ const { supportName } = useStudio();
3951
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "acct-stack", children: [
3952
+ /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "acct-card", children: [
3953
+ /* @__PURE__ */ jsxRuntime.jsxs("h2", { className: "acct-title", children: [
3954
+ "Studio ",
3955
+ version
3956
+ ] }),
3957
+ !update && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "acct-blurb", children: "You\u2019re up to date. When a new version comes out, it appears here and as a strip at the top of every Studio page." }),
3958
+ update?.state === "checking" && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "acct-blurb", children: [
3959
+ "Studio ",
3960
+ update.version,
3961
+ " is on its way \u2014 the checks are still running. Come back in a couple of minutes."
3962
+ ] }),
3963
+ update?.state === "failed" && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "acct-blurb", children: [
3964
+ "Studio ",
3965
+ update.version,
3966
+ " is available, but its checks failed, so it can\u2019t be installed yet.",
3967
+ role === "staff" && update.failingCheck ? ` Failing check: ${update.failingCheck}.` : ` ${supportName} has been able to see this and will sort it out.`
3968
+ ] }),
3969
+ update?.state === "ready" && /* @__PURE__ */ jsxRuntime.jsx(UpdateBody, { update })
3970
+ ] }),
3971
+ /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "acct-card", children: [
3972
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "acct-title", children: "How updates work" }),
3973
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "acct-blurb", children: [
3974
+ "Studio is a small package inside your website. When ",
3975
+ supportName,
3976
+ " releases a new version, your site prepares the update as a checked change \u2014 the same way a blog post is checked before it goes live \u2014 and builds a private preview. Nothing changes until you press ",
3977
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: "Update" }),
3978
+ ". If the update would break anything, the button never unlocks."
3979
+ ] })
3980
+ ] })
3981
+ ] });
3982
+ }
3983
+ var LABELS = { profile: "My Profile", password: "Change Password", team: "Team", business: "Business", studio: "Studio" };
3984
+ function AccountTabs({ me, manages, team, branding, myUserId, myRole, update = null, version = "" }) {
3859
3985
  const router = navigation.useRouter();
3860
3986
  const pathname = navigation.usePathname();
3861
3987
  const params = navigation.useSearchParams();
3862
- const ids = manages ? ["profile", "password", "team", "business"] : ["profile", "password"];
3988
+ const ids = manages ? ["profile", "password", "team", "business", "studio"] : ["profile", "password"];
3863
3989
  const requested = params.get("tab");
3864
3990
  const active = requested && ids.includes(requested) ? requested : "profile";
3865
3991
  const setActive = (v) => router.replace(`${pathname}?tab=${v}`, { scroll: false });
@@ -3868,14 +3994,15 @@ function AccountTabs({ me, manages, team, branding, myUserId, myRole }) {
3868
3994
  active === "profile" && /* @__PURE__ */ jsxRuntime.jsx(ProfileTab, { email: me.email, name: me.name, displayName: me.displayName, avatarUrl: me.avatarUrl }),
3869
3995
  active === "password" && /* @__PURE__ */ jsxRuntime.jsx(PasswordTab, {}),
3870
3996
  active === "team" && manages && (team?.ok ? /* @__PURE__ */ jsxRuntime.jsx(TeamTab, { members: team.members, canInvite: team.canInvite, myUserId, myRole }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rz-alert admin__alert", children: team?.error ?? "Couldn\u2019t load the team." })),
3871
- active === "business" && manages && /* @__PURE__ */ jsxRuntime.jsx(BusinessTab, { initial: branding })
3997
+ active === "business" && manages && /* @__PURE__ */ jsxRuntime.jsx(BusinessTab, { initial: branding }),
3998
+ active === "studio" && manages && /* @__PURE__ */ jsxRuntime.jsx(StudioTab, { update, version, role: myRole })
3872
3999
  ] });
3873
4000
  }
3874
- function StudioAccount({ me, manages, team, branding, myUserId, myRole }) {
4001
+ function StudioAccount({ me, manages, team, branding, myUserId, myRole, update = null, version = "" }) {
3875
4002
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "acct", children: [
3876
4003
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "acct-h1", children: "Account" }),
3877
4004
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "acct-email", children: me.email }),
3878
- /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(AccountTabs, { me, manages, team, branding, myUserId, myRole }) })
4005
+ /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(AccountTabs, { me, manages, team, branding, myUserId, myRole, update, version }) })
3879
4006
  ] });
3880
4007
  }
3881
4008
 
@@ -4013,6 +4140,28 @@ The status becomes **Needs fixing** and the message names the problem. Open the
4013
4140
  | **Removing\u2026** / **Removal ready** | You asked to take an item down; same steps as any change. | Publish to make the removal live, or discard to keep the item. |
4014
4141
 
4015
4142
  One item has at most one change waiting at a time. Saving again updates that same change rather than starting a second one.
4143
+ `
4144
+ },
4145
+ {
4146
+ id: "studio-pill",
4147
+ section: "start",
4148
+ title: "Getting back to Studio from the website",
4149
+ summary: "A small Studio pill appears bottom-left on the website while you're signed in \u2014 Edit this page, or Dashboard.",
4150
+ keywords: ["pill", "edit this page", "admin bar", "back to studio", "website"],
4151
+ body: `
4152
+ ## What it is
4153
+
4154
+ While you're signed in to Studio, your public website shows a small dark pill in the bottom-left corner: **Studio \xB7 Edit this page \xB7 Dashboard**. Visitors never see it \u2014 it only appears in a browser where you've signed in.
4155
+
4156
+ ## Why it's useful
4157
+
4158
+ Spot a typo while reading your own site? **Edit this page** opens that exact post or event in Studio. **Dashboard** takes you to the Studio home.
4159
+
4160
+ ## Good to know
4161
+
4162
+ - **Edit this page** only shows on pages Studio manages (a blog post, an event). On the home page or About, you get **Dashboard** only.
4163
+ - The \xD7 hides the pill for that browser tab; it comes back next time you open the site.
4164
+ - It disappears when you sign out of Studio. If you see it but Studio asks you to sign in again, your session simply expired \u2014 sign in and carry on.
4016
4165
  `
4017
4166
  }
4018
4167
  ];
@@ -4833,6 +4982,38 @@ Pictures save the moment you choose them (**Choose a picture**, **Replace**, **R
4833
4982
  ## Where the pictures live
4834
4983
 
4835
4984
  In Studio's own secure storage \u2014 not in your website's files, so they don't go through Save/Publish.
4985
+ `
4986
+ },
4987
+ {
4988
+ id: "updating-studio",
4989
+ section: "account",
4990
+ title: "Updating Studio",
4991
+ summary: "When a new version is out, owners see a strip with what's new, a preview and an Update button. Nothing installs until you press it.",
4992
+ keywords: ["update", "upgrade", "new version", "release", "what's new", "studio version"],
4993
+ body: `
4994
+ ## What it is
4995
+
4996
+ Studio is a small package inside your website, and it improves over time. When {supportName} releases a new version, your website prepares the update automatically as a **checked change** \u2014 exactly like a blog post is checked before it goes live \u2014 and builds a private preview of your site with the new Studio in it.
4997
+
4998
+ ## Where you see it
4999
+
5000
+ - A quiet strip at the top of every Studio page: **Studio 0.15 is available \xB7 What's new \xB7 Preview \xB7 Update \xB7 Later**. Only owners and {supportName} see it; editors don't.
5001
+ - **Settings \u2192 Studio**: the version you're running, the pending update with its notes, and the same Update button.
5002
+
5003
+ The strip appears only once the update's checks have passed. If they fail, nothing is offered \u2014 {supportName} sees the failure and fixes it.
5004
+
5005
+ ## How to update
5006
+
5007
+ 1. Read **What's new** (a few lines, plain English).
5008
+ 2. Optional: press **Preview** to open your website running the new Studio.
5009
+ 3. Press **Update**. The tracker shows *Updated \u2192 Live*; about two minutes later the website has rebuilt.
5010
+ 4. Reload Studio to use the new version.
5011
+
5012
+ ## Good to know
5013
+
5014
+ - **Later** hides the strip for this browser tab; it comes back next time.
5015
+ - Nothing on your public website changes because of a Studio update \u2014 only the dashboard does.
5016
+ - You can't skip a version or go back from here; if something looks wrong after an update, tell {supportName}.
4836
5017
  `
4837
5018
  }
4838
5019
  ];
@@ -5271,8 +5452,12 @@ function BackArrow() {
5271
5452
  ] });
5272
5453
  }
5273
5454
 
5455
+ // src/version.ts
5456
+ var ADMIN_VERSION = "0.15.0" ;
5457
+
5274
5458
  // src/studio-ui/types.ts
5275
5459
  var ACTION_NAMES = [
5460
+ "updateStudio",
5276
5461
  "publishContent",
5277
5462
  "removeContent",
5278
5463
  "mergeContentPr",
@@ -5293,6 +5478,7 @@ var ACTION_NAMES = [
5293
5478
  ];
5294
5479
 
5295
5480
  exports.ACTION_NAMES = ACTION_NAMES;
5481
+ exports.ADMIN_VERSION = ADMIN_VERSION;
5296
5482
  exports.ContentEditor = ContentEditor;
5297
5483
  exports.GLOSSARY = GLOSSARY;
5298
5484
  exports.HELP_ARTICLES = HELP_ARTICLES;
@@ -5316,6 +5502,9 @@ exports.StudioPullRequest = StudioPullRequest;
5316
5502
  exports.StudioShellFrame = StudioShellFrame;
5317
5503
  exports.StudioSignIn = StudioSignIn;
5318
5504
  exports.StudioTerm = StudioTerm;
5505
+ exports.UpdateBody = UpdateBody;
5506
+ exports.UpdateStrip = UpdateStrip;
5507
+ exports.canSeeUpdates = canSeeUpdates;
5319
5508
  exports.copyFor = copyFor;
5320
5509
  exports.fillVars = fillVars;
5321
5510
  exports.helpAsText = helpAsText;