@flytedan/flytebot-design-system 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -172,6 +172,7 @@ __export(index_exports, {
172
172
  channelWeightOf: () => channelWeightOf,
173
173
  computeNeedMore: () => computeNeedMore,
174
174
  computeVirtualWindow: () => computeVirtualWindow,
175
+ createLocalStorageAppearanceStore: () => createLocalStorageAppearanceStore,
175
176
  createVersionStore: () => createVersionStore,
176
177
  eqFilter: () => eqFilter,
177
178
  extensionOf: () => extensionOf,
@@ -5624,6 +5625,43 @@ function savePrefs(prefs, opts) {
5624
5625
  } catch {
5625
5626
  }
5626
5627
  }
5628
+ function createLocalStorageAppearanceStore(opts) {
5629
+ const key = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
5630
+ const storage = resolveStorage(opts);
5631
+ let snapshot = loadPrefs(opts);
5632
+ const listeners3 = /* @__PURE__ */ new Set();
5633
+ const notify = () => listeners3.forEach((l) => l());
5634
+ const onStorageEvent = (e) => {
5635
+ if (e.key !== null && e.key !== key) return;
5636
+ snapshot = loadPrefs(opts);
5637
+ notify();
5638
+ };
5639
+ return {
5640
+ getSnapshot: () => snapshot,
5641
+ write(prefs) {
5642
+ snapshot = prefs;
5643
+ if (storage) {
5644
+ try {
5645
+ storage.setItem(key, JSON.stringify(prefs));
5646
+ } catch {
5647
+ }
5648
+ }
5649
+ notify();
5650
+ },
5651
+ subscribe(onChange) {
5652
+ listeners3.add(onChange);
5653
+ if (listeners3.size === 1 && typeof window !== "undefined") {
5654
+ window.addEventListener("storage", onStorageEvent);
5655
+ }
5656
+ return () => {
5657
+ listeners3.delete(onChange);
5658
+ if (listeners3.size === 0 && typeof window !== "undefined") {
5659
+ window.removeEventListener("storage", onStorageEvent);
5660
+ }
5661
+ };
5662
+ }
5663
+ };
5664
+ }
5627
5665
  function applyPrefs(prefs, root = document.documentElement) {
5628
5666
  root.setAttribute("data-theme", resolveTheme(prefs.theme));
5629
5667
  root.setAttribute("data-text-scale", prefs.textScale);
@@ -5632,11 +5670,10 @@ function applyPrefs(prefs, root = document.documentElement) {
5632
5670
  if (prefs.reduceMotion) root.setAttribute("data-motion", "reduce");
5633
5671
  else root.removeAttribute("data-motion");
5634
5672
  }
5635
- function useAppearancePrefs(opts) {
5636
- const [prefs, setPrefs] = React33.useState(() => loadPrefs(opts));
5673
+ function useAppearancePrefs(store) {
5674
+ const prefs = React33.useSyncExternalStore(store.subscribe, store.getSnapshot);
5637
5675
  React33.useEffect(() => {
5638
5676
  applyPrefs(prefs);
5639
- savePrefs(prefs, opts);
5640
5677
  }, [prefs]);
5641
5678
  React33.useEffect(() => {
5642
5679
  if (prefs.theme !== "system" || typeof window === "undefined" || typeof window.matchMedia !== "function") {
@@ -5647,9 +5684,12 @@ function useAppearancePrefs(opts) {
5647
5684
  mq.addEventListener("change", onChange);
5648
5685
  return () => mq.removeEventListener("change", onChange);
5649
5686
  }, [prefs]);
5650
- const update = React33.useCallback((patch) => {
5651
- setPrefs((p) => ({ ...p, ...patch }));
5652
- }, []);
5687
+ const update = React33.useCallback(
5688
+ (patch) => {
5689
+ store.write({ ...store.getSnapshot(), ...patch });
5690
+ },
5691
+ [store]
5692
+ );
5653
5693
  return [prefs, update];
5654
5694
  }
5655
5695
 
@@ -5666,8 +5706,12 @@ var TEXT_SCALE_OPTIONS = [
5666
5706
  { value: "lg", label: "L" },
5667
5707
  { value: "xl", label: "XL" }
5668
5708
  ];
5669
- function AppearanceMenu({ className = "", ...storeOptions }) {
5670
- const [prefs, update] = useAppearancePrefs(storeOptions);
5709
+ function AppearanceMenu({ className = "", store: storeProp, storageKey, storage, defaults }) {
5710
+ const store = React34.useMemo(
5711
+ () => storeProp ?? createLocalStorageAppearanceStore({ storageKey, storage, defaults }),
5712
+ [storeProp, storageKey, storage, defaults]
5713
+ );
5714
+ const [prefs, update] = useAppearancePrefs(store);
5671
5715
  const [open, setOpen] = React34.useState(false);
5672
5716
  const anchorRef = React34.useRef(null);
5673
5717
  const patch = (p) => update(p);
@@ -5904,137 +5948,172 @@ function ApiSpecBrowser({
5904
5948
  // src/components/platform/ProfilePage.tsx
5905
5949
  var React36 = __toESM(require("react"), 1);
5906
5950
  var import_jsx_runtime62 = require("react/jsx-runtime");
5907
- var TIMEZONES = ["America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "America/Anchorage", "Pacific/Honolulu", "Europe/London", "Europe/Berlin"];
5908
- var NOTIFY = [
5909
- { key: "planShared", label: "A plan is shared with me", detail: "Someone sends you a plan or a client link." },
5910
- { key: "planChanged", label: "A plan I own changes", detail: "Budget moves, campuses added or removed." },
5911
- { key: "goalMissed", label: "The reviewer flags a missed criterion", detail: "After every solve, if a criterion is short." },
5912
- { key: "flagChanged", label: "A feature flag affecting me changes", detail: "Someone toggles a feature you can see." },
5913
- { key: "weekly", label: "Weekly digest", detail: "Monday summary of plan activity." }
5914
- ];
5915
- function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSessions = true, className = "", ...rest }) {
5951
+ function fieldInitialValue(field, user) {
5952
+ if (field.value) return field.value(user);
5953
+ const raw = user[field.key];
5954
+ return raw == null ? "" : String(raw);
5955
+ }
5956
+ function groupFields(fields) {
5957
+ const order = [];
5958
+ const byGroup = /* @__PURE__ */ new Map();
5959
+ for (const field of fields) {
5960
+ const group = field.group ?? "Identity";
5961
+ if (!byGroup.has(group)) {
5962
+ byGroup.set(group, { group, description: field.groupDescription, fields: [] });
5963
+ order.push(group);
5964
+ }
5965
+ const entry = byGroup.get(group);
5966
+ if (entry.description === void 0) entry.description = field.groupDescription;
5967
+ entry.fields.push(field);
5968
+ }
5969
+ return order.map((g) => byGroup.get(g));
5970
+ }
5971
+ function ProfilePage({
5972
+ user: userProp,
5973
+ fields = [],
5974
+ notifications = [],
5975
+ extra,
5976
+ onChangePhoto,
5977
+ onSave,
5978
+ onNavigate,
5979
+ sessions,
5980
+ onSignOutEverywhere,
5981
+ className = ""
5982
+ }) {
5916
5983
  const { user: sessionUser, roles, permissions, saveProfile: saveProfile2 } = useSessionContext();
5917
5984
  const user = userProp || sessionUser;
5918
- const [draft, setDraft] = React36.useState(() => ({
5919
- name: user.name || "",
5920
- title: user.title || "",
5921
- email: user.email || "",
5922
- phone: user.phone || "",
5923
- timezone: user.timezone || TIMEZONES[2],
5924
- bio: user.bio || "",
5925
- notify: user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true }
5926
- }));
5985
+ const initialFieldValues = React36.useMemo(() => {
5986
+ const values2 = {};
5987
+ for (const field of fields) values2[field.key] = fieldInitialValue(field, user);
5988
+ return values2;
5989
+ }, [user.id, fields]);
5990
+ const initialNotify = React36.useMemo(() => {
5991
+ const values2 = {};
5992
+ for (const n of notifications) values2[n.key] = !!user.notify?.[n.key];
5993
+ return values2;
5994
+ }, [user.id, notifications]);
5995
+ const [values, setValues] = React36.useState(initialFieldValues);
5996
+ const [notify, setNotify] = React36.useState(initialNotify);
5927
5997
  const [saving, setSaving] = React36.useState(false);
5928
5998
  const [saved, setSaved] = React36.useState(false);
5929
- const set = (k, v) => {
5930
- setDraft((d) => Object.assign({}, d, { [k]: v }));
5999
+ const [prevUserId, setPrevUserId] = React36.useState(user.id);
6000
+ if (user.id !== prevUserId) {
6001
+ setPrevUserId(user.id);
6002
+ setValues(initialFieldValues);
6003
+ setNotify(initialNotify);
6004
+ setSaved(false);
6005
+ }
6006
+ const setField = (key, v) => {
6007
+ setValues((d) => ({ ...d, [key]: v }));
5931
6008
  setSaved(false);
5932
6009
  };
5933
- const setNotify = (k) => {
5934
- setDraft((d) => Object.assign({}, d, { notify: Object.assign({}, d.notify, { [k]: !d.notify[k] }) }));
6010
+ const toggleNotify = (key) => {
6011
+ setNotify((d) => ({ ...d, [key]: !d[key] }));
5935
6012
  setSaved(false);
5936
6013
  };
5937
- const dirty = draft.name !== (user.name || "") || draft.title !== (user.title || "") || draft.phone !== (user.phone || "") || draft.timezone !== (user.timezone || TIMEZONES[2]) || draft.bio !== (user.bio || "") || JSON.stringify(draft.notify) !== JSON.stringify(user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true });
5938
- const canSave = !!(onSave || saveProfile2);
6014
+ const dirty = fields.some((f) => values[f.key] !== initialFieldValues[f.key]) || notifications.some((n) => notify[n.key] !== initialNotify[n.key]);
6015
+ const canSave = !!(onSave || saveProfile2) && (fields.length > 0 || notifications.length > 0);
5939
6016
  const save = async () => {
5940
6017
  if (!canSave) return;
5941
6018
  setSaving(true);
5942
- if (onSave) await onSave(draft);
5943
- else await saveProfile2(draft);
5944
- setSaving(false);
5945
- setSaved(true);
6019
+ const draft = { ...values };
6020
+ if (notifications.length > 0) draft.notify = notify;
6021
+ try {
6022
+ if (onSave) await onSave(draft);
6023
+ else await saveProfile2(draft);
6024
+ setSaved(true);
6025
+ } finally {
6026
+ setSaving(false);
6027
+ }
5946
6028
  };
5947
- const liveSessions = sessions || [
5948
- { id: "s1", device: "MacBook Pro \xB7 Chrome", where: "Denver, CO", when: "Active now", current: true },
5949
- { id: "s2", device: "iPhone 15 \xB7 Safari", where: "Denver, CO", when: "2 hours ago" },
5950
- { id: "s3", device: "Windows \xB7 Edge", where: "Chicago, IL", when: "Aug 12" }
5951
- ];
5952
- return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 860 }, ...rest, children: [
6029
+ const discard = () => {
6030
+ setValues(initialFieldValues);
6031
+ setNotify(initialNotify);
6032
+ setSaved(false);
6033
+ };
6034
+ const displayName = fields.some((f) => f.key === "name") ? values.name || user.name : user.name;
6035
+ const fieldGroups = groupFields(fields);
6036
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 860 }, children: [
5953
6037
  /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-stack", style: { gap: 10 }, children: [
5954
6038
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-overline fd-muted", children: "Account" }),
5955
6039
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("h1", { className: "fd-h1", style: { margin: 0 }, children: "Your profile" }),
5956
6040
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("p", { className: "fd-body fd-secondary fd-prose", style: { margin: 0 }, children: "How you appear to colleagues and clients across every flytedesk app, and what we send you." })
5957
6041
  ] }),
5958
6042
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Card, { elevation: "flat", children: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-row", style: { gap: 16, flexWrap: "wrap", alignItems: "flex-start" }, children: [
5959
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Avatar, { name: draft.name || user.name, size: "lg" }),
6043
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Avatar, { name: displayName, size: "lg" }),
5960
6044
  /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-stack", style: { gap: 4, flex: 1, minWidth: 200 }, children: [
5961
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-h3", style: { margin: 0 }, children: draft.name || user.name }),
5962
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-body-sm fd-muted", children: [
5963
- draft.title || "No title set",
5964
- " \xB7 ",
5965
- user.team || "No team"
5966
- ] }),
6045
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-h3", style: { margin: 0 }, children: displayName }),
5967
6046
  /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-row", style: { gap: 6, flexWrap: "wrap", paddingTop: 4 }, children: [
5968
6047
  roles.map((r) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Badge, { tone: r.id === "owner" ? "success" : "neutral", children: r.name }, r.id)),
5969
6048
  user.sso ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Badge, { tone: "info", icon: "shield-check", children: "SSO" }) : null
5970
6049
  ] })
5971
6050
  ] }),
5972
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { variant: "secondary", size: "sm", icon: "image", children: "Change photo" })
6051
+ onChangePhoto ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { variant: "secondary", size: "sm", icon: "image", onClick: onChangePhoto, children: "Change photo" }) : null
5973
6052
  ] }) }),
5974
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6053
+ fieldGroups.map(({ group, description, fields: groupFieldDefs }) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
5975
6054
  Card,
5976
6055
  {
5977
- title: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-stack", style: { gap: 2 }, children: [
5978
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: "Identity" }),
5979
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "Name and title appear on plans you share" })
5980
- ] }),
6056
+ title: description ? /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-stack", style: { gap: 2 }, children: [
6057
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: group }),
6058
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: description })
6059
+ ] }) : group,
5981
6060
  elevation: "flat",
5982
6061
  children: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-stack", style: { gap: 14 }, children: [
5983
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
5984
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Input, { label: "Full name", value: draft.name, onChange: (e) => set("name", e.target.value) }),
5985
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Input, { label: "Job title", value: draft.title, onChange: (e) => set("title", e.target.value), placeholder: "e.g. Senior media planner" })
5986
- ] }),
5987
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
5988
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6062
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: groupFieldDefs.filter((f) => f.type !== "textarea").map((field) => {
6063
+ const help = typeof field.help === "function" ? field.help(user) : field.help;
6064
+ const readOnly = field.readOnly || field.type === "readonly";
6065
+ if (field.type === "select") {
6066
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6067
+ Select,
6068
+ {
6069
+ label: field.label,
6070
+ value: values[field.key],
6071
+ onChange: readOnly ? void 0 : (e) => setField(field.key, e.target.value),
6072
+ options: field.options ?? [],
6073
+ placeholder: field.placeholder,
6074
+ help,
6075
+ disabled: readOnly
6076
+ },
6077
+ field.key
6078
+ );
6079
+ }
6080
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
5989
6081
  Input,
5990
6082
  {
5991
- label: "Work email",
5992
- value: draft.email,
5993
- readOnly: true,
5994
- disabled: true,
5995
- help: user.sso ? "Managed by your identity provider \u2014 change it there." : "Contact an administrator to change this."
5996
- }
5997
- ),
5998
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Input, { label: "Phone", value: draft.phone, onChange: (e) => set("phone", e.target.value), placeholder: "Optional" })
5999
- ] }),
6000
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6001
- Textarea,
6002
- {
6003
- label: "Short bio",
6004
- value: draft.bio,
6005
- onChange: (e) => set("bio", e.target.value),
6006
- rows: 3,
6007
- placeholder: "Optional \u2014 shown to colleagues on shared plans.",
6008
- help: "Never shown to clients."
6009
- }
6010
- )
6083
+ label: field.label,
6084
+ value: values[field.key],
6085
+ onChange: readOnly ? void 0 : (e) => setField(field.key, e.target.value),
6086
+ readOnly,
6087
+ disabled: readOnly,
6088
+ placeholder: field.placeholder,
6089
+ help
6090
+ },
6091
+ field.key
6092
+ );
6093
+ }) }),
6094
+ groupFieldDefs.filter((f) => f.type === "textarea").map((field) => {
6095
+ const help = typeof field.help === "function" ? field.help(user) : field.help;
6096
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6097
+ Textarea,
6098
+ {
6099
+ label: field.label,
6100
+ value: values[field.key],
6101
+ onChange: field.readOnly ? void 0 : (e) => setField(field.key, e.target.value),
6102
+ readOnly: field.readOnly,
6103
+ disabled: field.readOnly,
6104
+ rows: field.rows ?? 3,
6105
+ placeholder: field.placeholder,
6106
+ help
6107
+ },
6108
+ field.key
6109
+ );
6110
+ })
6011
6111
  ] })
6012
- }
6013
- ),
6014
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Card, { title: "Working preferences", elevation: "flat", children: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
6015
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6016
- Select,
6017
- {
6018
- label: "Time zone",
6019
- value: draft.timezone,
6020
- onChange: (e) => set("timezone", e.target.value),
6021
- options: TIMEZONES,
6022
- help: "Flight dates and schedules render in this zone."
6023
- }
6024
- ),
6025
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Select, { label: "Start of week", options: ["Monday", "Sunday"], placeholder: "Monday" })
6026
- ] }) }),
6027
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6028
- Card,
6029
- {
6030
- title: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-stack", style: { gap: 2 }, children: [
6031
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: "Notifications" }),
6032
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "Email only for now \u2014 in-app notifications are on the roadmap" })
6033
- ] }),
6034
- elevation: "flat",
6035
- children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "fd-stack", style: { gap: 14 }, children: NOTIFY.map((n) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Switch, { checked: !!draft.notify[n.key], onChange: () => setNotify(n.key), label: n.label, description: n.detail }, n.key)) })
6036
- }
6037
- ),
6112
+ },
6113
+ group
6114
+ )),
6115
+ extra,
6116
+ notifications.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Card, { title: "Notifications", elevation: "flat", children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "fd-stack", style: { gap: 14 }, children: notifications.map((n) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Switch, { checked: !!notify[n.key], onChange: () => toggleNotify(n.key), label: n.label, description: n.description }, n.key)) }) }) : null,
6038
6117
  /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)(
6039
6118
  Card,
6040
6119
  {
@@ -6066,13 +6145,13 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
6066
6145
  ]
6067
6146
  }
6068
6147
  ),
6069
- showSessions ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6148
+ sessions ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6070
6149
  Card,
6071
6150
  {
6072
6151
  title: "Signed-in devices",
6073
6152
  elevation: "flat",
6074
- action: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { size: "sm", variant: "ghost", icon: "sign-out", children: "Sign out everywhere" }),
6075
- children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "fd-stack", style: { gap: 0 }, children: liveSessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-row", style: { gap: 12, padding: "10px 0", borderTop: "1px solid var(--border)", flexWrap: "wrap" }, children: [
6153
+ action: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { size: "sm", variant: "ghost", icon: "sign-out", onClick: onSignOutEverywhere, children: "Sign out everywhere" }),
6154
+ children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "fd-stack", style: { gap: 0 }, children: sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("p", { className: "fd-body-sm fd-muted", style: { margin: 0 }, children: "No other signed-in devices." }) : sessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-row", style: { gap: 12, padding: "10px 0", borderTop: "1px solid var(--border)", flexWrap: "wrap" }, children: [
6076
6155
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("i", { className: "ph ph-device-mobile", style: { fontSize: 16, color: "var(--text-muted)" }, "aria-hidden": "true" }),
6077
6156
  /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-stack", style: { gap: 1, flex: 1, minWidth: 160 }, children: [
6078
6157
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-body-sm", style: { fontWeight: 600 }, children: s.device }),
@@ -6098,19 +6177,7 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
6098
6177
  boxShadow: "0 -4px 16px rgba(11,13,17,.06)"
6099
6178
  }, children: [
6100
6179
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-body-sm", style: { fontWeight: 600, flex: 1 }, children: saved ? "Saved." : dirty ? "Unsaved changes" : "No pending changes" }),
6101
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6102
- Button,
6103
- {
6104
- size: "sm",
6105
- variant: "ghost",
6106
- disabled: !dirty || saving,
6107
- onClick: () => {
6108
- setDraft({ name: user.name || "", title: user.title || "", email: user.email || "", phone: user.phone || "", timezone: user.timezone || TIMEZONES[2], bio: user.bio || "", notify: user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true } });
6109
- setSaved(false);
6110
- },
6111
- children: "Discard"
6112
- }
6113
- ),
6180
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { size: "sm", variant: "ghost", disabled: !dirty || saving, onClick: discard, children: "Discard" }),
6114
6181
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { size: "sm", icon: "check", disabled: !dirty || !canSave, loading: saving, onClick: save, children: "Save changes" })
6115
6182
  ] })
6116
6183
  ] });
@@ -11956,6 +12023,7 @@ function createVersionStore(initialState, options) {
11956
12023
  channelWeightOf,
11957
12024
  computeNeedMore,
11958
12025
  computeVirtualWindow,
12026
+ createLocalStorageAppearanceStore,
11959
12027
  createVersionStore,
11960
12028
  eqFilter,
11961
12029
  extensionOf,