@streamoid/settings 0.1.0 → 0.1.2

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.d.mts CHANGED
@@ -52,6 +52,10 @@ interface SettingsAuthUser {
52
52
  role?: string;
53
53
  company?: string;
54
54
  createdAt?: number | string;
55
+ /** Account-level specialization. Empty string = never answered;
56
+ "unspecified" = explicit skip; otherwise one of the host-defined ids
57
+ (e.g. "design", "merchandising"). */
58
+ userSpecialization?: string;
55
59
  }
56
60
  interface SettingsAuthAdapter {
57
61
  user: SettingsAuthUser | null;
@@ -59,6 +63,7 @@ interface SettingsAuthAdapter {
59
63
  updateProfile: (patch: {
60
64
  name?: string;
61
65
  imageUrl?: string | null;
66
+ userSpecialization?: string;
62
67
  }) => Promise<void>;
63
68
  clearAuth: () => void;
64
69
  logout: () => Promise<void>;
@@ -490,6 +495,20 @@ interface SettingsApiAdapter {
490
495
  fetchReferrals?: () => Promise<unknown[]>;
491
496
  }
492
497
  interface SettingsNavigationAdapter {
498
+ /**
499
+ * Resolve a fully-qualified URL the host wants Stripe checkout to redirect
500
+ * back to after success / cancel. Both flows typically land on the billing
501
+ * tab. The package calls this for `updateSubscription` (upgrade/downgrade)
502
+ * and `buyCredits` (topup) checkout payloads.
503
+ *
504
+ * When omitted, the package falls back to
505
+ * `${window.location.origin}${window.location.pathname}` — i.e. brings the
506
+ * user back to the page they triggered checkout from. Hosts override when
507
+ * they need a different URL shape (e.g. CXO uses
508
+ * `/dashboard/<wsId>/billing`, Photogenix uses
509
+ * `/workspace/<wsId>/settings/billing`).
510
+ */
511
+ getCheckoutReturnUrl?: (kind: "success" | "cancel") => string;
493
512
  /** Imperative navigate to another settings tab. */
494
513
  navigateToTab: (tab: SettingsTab) => void;
495
514
  /** External-link navigate (e.g. for "What's new" or referral promo links). */
package/dist/index.d.ts CHANGED
@@ -52,6 +52,10 @@ interface SettingsAuthUser {
52
52
  role?: string;
53
53
  company?: string;
54
54
  createdAt?: number | string;
55
+ /** Account-level specialization. Empty string = never answered;
56
+ "unspecified" = explicit skip; otherwise one of the host-defined ids
57
+ (e.g. "design", "merchandising"). */
58
+ userSpecialization?: string;
55
59
  }
56
60
  interface SettingsAuthAdapter {
57
61
  user: SettingsAuthUser | null;
@@ -59,6 +63,7 @@ interface SettingsAuthAdapter {
59
63
  updateProfile: (patch: {
60
64
  name?: string;
61
65
  imageUrl?: string | null;
66
+ userSpecialization?: string;
62
67
  }) => Promise<void>;
63
68
  clearAuth: () => void;
64
69
  logout: () => Promise<void>;
@@ -490,6 +495,20 @@ interface SettingsApiAdapter {
490
495
  fetchReferrals?: () => Promise<unknown[]>;
491
496
  }
492
497
  interface SettingsNavigationAdapter {
498
+ /**
499
+ * Resolve a fully-qualified URL the host wants Stripe checkout to redirect
500
+ * back to after success / cancel. Both flows typically land on the billing
501
+ * tab. The package calls this for `updateSubscription` (upgrade/downgrade)
502
+ * and `buyCredits` (topup) checkout payloads.
503
+ *
504
+ * When omitted, the package falls back to
505
+ * `${window.location.origin}${window.location.pathname}` — i.e. brings the
506
+ * user back to the page they triggered checkout from. Hosts override when
507
+ * they need a different URL shape (e.g. CXO uses
508
+ * `/dashboard/<wsId>/billing`, Photogenix uses
509
+ * `/workspace/<wsId>/settings/billing`).
510
+ */
511
+ getCheckoutReturnUrl?: (kind: "success" | "cancel") => string;
493
512
  /** Imperative navigate to another settings tab. */
494
513
  navigateToTab: (tab: SettingsTab) => void;
495
514
  /** External-link navigate (e.g. for "What's new" or referral promo links). */
package/dist/index.js CHANGED
@@ -4546,7 +4546,7 @@ function UpdateSubscriptionModal({
4546
4546
  lineHeight: "var(--line-height-line-height-xxl)",
4547
4547
  color: "var(--alias-text---icons-primary)"
4548
4548
  },
4549
- children: currentPlanName === "free" ? "Free" : "Pro"
4549
+ children: currentPlanName || "Free"
4550
4550
  }
4551
4551
  ),
4552
4552
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col items-end", children: [
@@ -5360,9 +5360,16 @@ function UsageLogsTable({
5360
5360
  );
5361
5361
  }
5362
5362
  function BillingContent({ embedded = false } = {}) {
5363
- const { auth, workspace, api, toast } = useSettingsAdapters();
5363
+ const { auth, workspace, api, toast, navigation } = useSettingsAdapters();
5364
5364
  const user = auth.user;
5365
5365
  const workspaceId = workspace.current?.id ?? null;
5366
+ const resolveReturnUrl = (kind) => {
5367
+ if (navigation.getCheckoutReturnUrl) {
5368
+ return navigation.getCheckoutReturnUrl(kind);
5369
+ }
5370
+ if (typeof window === "undefined") return "";
5371
+ return `${window.location.origin}${window.location.pathname}`;
5372
+ };
5366
5373
  const [activeTab, setActiveTab] = (0, import_react8.useState)("invoice");
5367
5374
  const [showPlans, setShowPlans] = (0, import_react8.useState)(false);
5368
5375
  const [showExportModal, setShowExportModal] = (0, import_react8.useState)(false);
@@ -5487,8 +5494,8 @@ function BillingContent({ embedded = false } = {}) {
5487
5494
  quantity: 1,
5488
5495
  customerEmail: user.email,
5489
5496
  customMetadata: { ref: "Unified dashboard" },
5490
- successUrl: `${window.location.origin}/dashboard/${workspaceId}/billing`,
5491
- cancelUrl: `${window.location.origin}/dashboard/${workspaceId}/billing`,
5497
+ successUrl: resolveReturnUrl("success"),
5498
+ cancelUrl: resolveReturnUrl("cancel"),
5492
5499
  action: "upgrade"
5493
5500
  };
5494
5501
  try {
@@ -5585,8 +5592,8 @@ function BillingContent({ embedded = false } = {}) {
5585
5592
  if (loadingBilling) return;
5586
5593
  setShowInitialLoader(false);
5587
5594
  }, [showInitialLoader, initialFetchTriggered, loadingBilling]);
5588
- const planName = subscription?.plan?.amount != null && subscription.plan.amount > 0 ? "Pro" : "Free";
5589
- const isFreePlan = subscription?.plan?.amount === 0 && planName.trim().toLowerCase() === "free";
5595
+ const planName = subscription?.plan?.name?.trim() || (subscription?.plan?.amount != null && subscription.plan.amount > 0 ? "Pro" : "Free");
5596
+ const isFreePlan = subscription?.plan?.amount == null || subscription.plan.amount === 0;
5590
5597
  const planAmount = subscription?.plan?.amount != null ? `$${(subscription.plan.amount / 100).toFixed(2)} / month` : "$0";
5591
5598
  const usedCredits = availableCredits != null && totalCredits != null ? Math.max(0, totalCredits - availableCredits) : null;
5592
5599
  const progressPct = usedCredits != null && totalCredits != null && totalCredits > 0 ? Math.min(100, Math.round(usedCredits / totalCredits * 100)) : 0;
@@ -5683,7 +5690,6 @@ function BillingContent({ embedded = false } = {}) {
5683
5690
  additionalInfo: `${credits}:${price}`
5684
5691
  }
5685
5692
  );
5686
- const returnUrl = `${window.location.origin}/dashboard/${workspaceId}/billing`;
5687
5693
  try {
5688
5694
  const res = await api.buyCredits({
5689
5695
  serviceName: DEFAULT_TOPUP_SERVICE,
@@ -5691,8 +5697,8 @@ function BillingContent({ embedded = false } = {}) {
5691
5697
  unitAmount: price,
5692
5698
  itemCount: 1,
5693
5699
  currency: "usd",
5694
- successUrl: returnUrl,
5695
- cancelUrl: returnUrl,
5700
+ successUrl: resolveReturnUrl("success"),
5701
+ cancelUrl: resolveReturnUrl("cancel"),
5696
5702
  metadata: { source: "self-serve" }
5697
5703
  });
5698
5704
  const payloadData = "data" in res && res.data ? res.data : res;
@@ -6868,11 +6874,274 @@ function SettingsNav({
6868
6874
  }
6869
6875
  );
6870
6876
  }
6877
+ var SPECIALIZATION_ICON_SIZE = 20;
6878
+ var SPECIALIZATION_OPTIONS = [
6879
+ {
6880
+ id: "design",
6881
+ label: "Design",
6882
+ icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6883
+ import_icons7.SiconSwatch,
6884
+ {
6885
+ style: {
6886
+ width: SPECIALIZATION_ICON_SIZE,
6887
+ height: SPECIALIZATION_ICON_SIZE
6888
+ }
6889
+ }
6890
+ )
6891
+ },
6892
+ {
6893
+ id: "merchandising",
6894
+ label: "Merchandising",
6895
+ icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6896
+ import_icons7.SiconBag,
6897
+ {
6898
+ style: {
6899
+ width: SPECIALIZATION_ICON_SIZE,
6900
+ height: SPECIALIZATION_ICON_SIZE
6901
+ }
6902
+ }
6903
+ )
6904
+ },
6905
+ {
6906
+ id: "planning",
6907
+ label: "Planning",
6908
+ icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6909
+ import_icons7.SiconInsights,
6910
+ {
6911
+ style: {
6912
+ width: SPECIALIZATION_ICON_SIZE,
6913
+ height: SPECIALIZATION_ICON_SIZE
6914
+ }
6915
+ }
6916
+ )
6917
+ },
6918
+ {
6919
+ id: "marketing_brand",
6920
+ label: "Marketing & brand",
6921
+ icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6922
+ import_icons7.SiconAdCreator,
6923
+ {
6924
+ style: {
6925
+ width: SPECIALIZATION_ICON_SIZE,
6926
+ height: SPECIALIZATION_ICON_SIZE
6927
+ }
6928
+ }
6929
+ )
6930
+ },
6931
+ {
6932
+ id: "founder_operator",
6933
+ label: "Founder & operator",
6934
+ icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6935
+ import_icons7.SiconCrown,
6936
+ {
6937
+ style: {
6938
+ width: SPECIALIZATION_ICON_SIZE,
6939
+ height: SPECIALIZATION_ICON_SIZE
6940
+ }
6941
+ }
6942
+ )
6943
+ }
6944
+ ];
6945
+ function SpecializationDropdown({
6946
+ value,
6947
+ onChange,
6948
+ disabled
6949
+ }) {
6950
+ const [open, setOpen] = (0, import_react10.useState)(false);
6951
+ const containerRef = (0, import_react10.useRef)(null);
6952
+ (0, import_react10.useEffect)(() => {
6953
+ if (!open) return;
6954
+ function handleMouseDown(e) {
6955
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
6956
+ setOpen(false);
6957
+ }
6958
+ }
6959
+ function handleKeyDown(e) {
6960
+ if (e.key === "Escape") setOpen(false);
6961
+ }
6962
+ document.addEventListener("mousedown", handleMouseDown);
6963
+ document.addEventListener("keydown", handleKeyDown);
6964
+ return () => {
6965
+ document.removeEventListener("mousedown", handleMouseDown);
6966
+ document.removeEventListener("keydown", handleKeyDown);
6967
+ };
6968
+ }, [open]);
6969
+ const selected = SPECIALIZATION_OPTIONS.find((o) => o.id === value);
6970
+ const labelText = selected ? selected.label : value === "unspecified" ? "Not specified" : "Select specialization";
6971
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
6972
+ "div",
6973
+ {
6974
+ ref: containerRef,
6975
+ className: "flex flex-col items-start w-full",
6976
+ style: { gap: "var(--spacing-xs)", position: "relative" },
6977
+ children: [
6978
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6979
+ "span",
6980
+ {
6981
+ style: {
6982
+ fontFamily: "var(--font-family-font-family-body)",
6983
+ fontSize: "var(--font-size-font-size-sm)",
6984
+ lineHeight: "var(--line-height-line-height-sm)",
6985
+ color: "var(--alias-text---icons-tertiary)"
6986
+ },
6987
+ children: "Specialization"
6988
+ }
6989
+ ),
6990
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
6991
+ "button",
6992
+ {
6993
+ type: "button",
6994
+ onClick: () => !disabled && setOpen((v) => !v),
6995
+ disabled,
6996
+ "aria-haspopup": "listbox",
6997
+ "aria-expanded": open,
6998
+ style: {
6999
+ width: "100%",
7000
+ height: 48,
7001
+ display: "flex",
7002
+ alignItems: "center",
7003
+ gap: "var(--spacing-md, 8px)",
7004
+ padding: "var(--spacing-xl, 12px) var(--spacing-3xl, 16px)",
7005
+ background: "var(--alias-fill-neutral-neutral, #1a1a1a)",
7006
+ borderRadius: "var(--radius-3xl, 16px)",
7007
+ border: "none",
7008
+ cursor: disabled ? "default" : "pointer",
7009
+ opacity: disabled ? 0.5 : 1,
7010
+ color: selected ? "var(--alias-text---icons-primary)" : "var(--alias-text---icons-muted)",
7011
+ fontFamily: "var(--font-family-font-family-body)",
7012
+ fontSize: "var(--font-size-font-size-sm)",
7013
+ lineHeight: "var(--line-height-line-height-sm)",
7014
+ textAlign: "left"
7015
+ },
7016
+ children: [
7017
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7018
+ "span",
7019
+ {
7020
+ style: {
7021
+ display: "flex",
7022
+ alignItems: "center",
7023
+ justifyContent: "center",
7024
+ width: 24,
7025
+ height: 24,
7026
+ color: selected ? "var(--alias-text---icons-primary)" : "var(--alias-text---icons-muted)"
7027
+ },
7028
+ children: selected ? selected.icon : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_icons7.SiconBolt, {})
7029
+ }
7030
+ ),
7031
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7032
+ "span",
7033
+ {
7034
+ style: {
7035
+ flex: 1,
7036
+ minWidth: 0,
7037
+ overflow: "hidden",
7038
+ textOverflow: "ellipsis",
7039
+ whiteSpace: "nowrap"
7040
+ },
7041
+ children: labelText
7042
+ }
7043
+ ),
7044
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7045
+ "span",
7046
+ {
7047
+ style: {
7048
+ display: "flex",
7049
+ alignItems: "center",
7050
+ justifyContent: "center",
7051
+ width: 24,
7052
+ height: 24,
7053
+ color: "var(--alias-text---icons-primary)",
7054
+ transform: open ? "rotate(180deg)" : "none",
7055
+ transition: "transform 120ms ease-out"
7056
+ },
7057
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_icons7.SiconDown, {})
7058
+ }
7059
+ )
7060
+ ]
7061
+ }
7062
+ ),
7063
+ open && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7064
+ "div",
7065
+ {
7066
+ role: "listbox",
7067
+ style: {
7068
+ position: "absolute",
7069
+ top: "calc(100% + 4px)",
7070
+ left: 0,
7071
+ right: 0,
7072
+ zIndex: 50,
7073
+ background: "var(--alias-surface-subtleraised, #1f1f1f)",
7074
+ border: "1px solid var(--alias-border-divider, #242424)",
7075
+ borderRadius: "var(--radius-3xl, 16px)",
7076
+ padding: "var(--spacing-md, 8px)",
7077
+ display: "flex",
7078
+ flexDirection: "column",
7079
+ gap: 2,
7080
+ boxShadow: "0 8px 24px rgba(0,0,0,0.4)"
7081
+ },
7082
+ children: SPECIALIZATION_OPTIONS.map((opt) => {
7083
+ const isActive = opt.id === value;
7084
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
7085
+ "button",
7086
+ {
7087
+ type: "button",
7088
+ role: "option",
7089
+ "aria-selected": isActive,
7090
+ onClick: () => {
7091
+ onChange(opt.id);
7092
+ setOpen(false);
7093
+ },
7094
+ style: {
7095
+ display: "flex",
7096
+ alignItems: "center",
7097
+ gap: "var(--spacing-md, 8px)",
7098
+ padding: "var(--spacing-md, 8px) var(--spacing-3xl, 16px)",
7099
+ background: isActive ? "var(--alias-fill-neutral-neutralhover, #242424)" : "transparent",
7100
+ border: "none",
7101
+ borderRadius: "var(--radius-md, 8px)",
7102
+ cursor: "pointer",
7103
+ color: "var(--alias-text---icons-primary)",
7104
+ fontFamily: "var(--font-family-font-family-body)",
7105
+ fontSize: "var(--font-size-font-size-sm)",
7106
+ lineHeight: "var(--line-height-line-height-sm)",
7107
+ textAlign: "left",
7108
+ height: 40
7109
+ },
7110
+ onMouseEnter: (e) => e.currentTarget.style.background = "var(--alias-fill-neutral-neutralhover, #242424)",
7111
+ onMouseLeave: (e) => e.currentTarget.style.background = isActive ? "var(--alias-fill-neutral-neutralhover, #242424)" : "transparent",
7112
+ children: [
7113
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7114
+ "span",
7115
+ {
7116
+ style: {
7117
+ display: "flex",
7118
+ alignItems: "center",
7119
+ justifyContent: "center",
7120
+ width: 24,
7121
+ height: 24
7122
+ },
7123
+ children: opt.icon
7124
+ }
7125
+ ),
7126
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { flex: 1 }, children: opt.label })
7127
+ ]
7128
+ },
7129
+ opt.id
7130
+ );
7131
+ })
7132
+ }
7133
+ )
7134
+ ]
7135
+ }
7136
+ );
7137
+ }
6871
7138
  function ProfileTab() {
6872
7139
  const { auth, imageUpload, toast } = useSettingsAdapters();
6873
7140
  const user = auth.user;
6874
7141
  const derivedName = user?.name ?? "";
7142
+ const derivedSpec = user?.userSpecialization ?? "";
6875
7143
  const [fullName, setFullName] = (0, import_react10.useState)(derivedName);
7144
+ const [selectedSpec, setSelectedSpec] = (0, import_react10.useState)(derivedSpec);
6876
7145
  const [saving, setSaving] = (0, import_react10.useState)(false);
6877
7146
  const [imageSaving, setImageSaving] = (0, import_react10.useState)(false);
6878
7147
  const [imagePreview, setImagePreview] = (0, import_react10.useState)(
@@ -6883,15 +7152,22 @@ function ProfileTab() {
6883
7152
  const profileImage = imagePreview ?? getUserProfileImage(user);
6884
7153
  const profileInitial = profileDisplayName.charAt(0).toUpperCase();
6885
7154
  const disableAllActions = saving || imageSaving;
7155
+ const dirty = fullName.trim() !== derivedName || selectedSpec !== derivedSpec;
6886
7156
  (0, import_react10.useEffect)(() => {
6887
7157
  setFullName(derivedName);
6888
7158
  setImagePreview(void 0);
6889
7159
  }, [derivedName]);
7160
+ (0, import_react10.useEffect)(() => {
7161
+ setSelectedSpec(derivedSpec);
7162
+ }, [derivedSpec]);
6890
7163
  const handleSave = async () => {
6891
- if (disableAllActions) return;
7164
+ if (disableAllActions || !dirty) return;
6892
7165
  setSaving(true);
6893
7166
  try {
6894
- await auth.updateProfile({ name: fullName.trim() });
7167
+ const patch = {};
7168
+ if (fullName.trim() !== derivedName) patch.name = fullName.trim();
7169
+ if (selectedSpec !== derivedSpec) patch.userSpecialization = selectedSpec;
7170
+ await auth.updateProfile(patch);
6895
7171
  await auth.refreshProfile();
6896
7172
  toast.success("Profile updated");
6897
7173
  } catch (err) {
@@ -7102,27 +7378,35 @@ function ProfileTab() {
7102
7378
  }
7103
7379
  ) }),
7104
7380
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex-1 min-w-0", style: { overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7105
- import_ui8.ScTextField,
7381
+ SpecializationDropdown,
7106
7382
  {
7107
- label: "Email ID",
7108
- labelGroup: "label",
7109
- state: "default",
7110
- value: user?.email ?? "",
7111
- placeholderFilled: user?.email ?? "chris@kepler.com",
7112
- tfGroup: "icon-right",
7113
- tfRightIcon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7114
- import_icons7.SiconLock,
7115
- {
7116
- className: "w-6 h-6",
7117
- color: "var(--alias-text---icons-primary)"
7118
- }
7119
- ),
7120
- className: "sc-text-field-full-width",
7121
- style: { width: "100%" },
7122
- readOnly: true
7383
+ value: selectedSpec,
7384
+ onChange: setSelectedSpec,
7385
+ disabled: disableAllActions
7123
7386
  }
7124
7387
  ) })
7125
7388
  ] }),
7389
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "w-full", style: { overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7390
+ import_ui8.ScTextField,
7391
+ {
7392
+ label: "Email ID",
7393
+ labelGroup: "label",
7394
+ state: "default",
7395
+ value: user?.email ?? "",
7396
+ placeholderFilled: user?.email ?? "chris@kepler.com",
7397
+ tfGroup: "icon-right",
7398
+ tfRightIcon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7399
+ import_icons7.SiconLock,
7400
+ {
7401
+ className: "w-6 h-6",
7402
+ color: "var(--alias-text---icons-primary)"
7403
+ }
7404
+ ),
7405
+ className: "sc-text-field-full-width",
7406
+ style: { width: "100%" },
7407
+ readOnly: true
7408
+ }
7409
+ ) }),
7126
7410
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
7127
7411
  "div",
7128
7412
  {
@@ -7135,7 +7419,7 @@ function ProfileTab() {
7135
7419
  text: saving ? "Saving..." : "Save changes",
7136
7420
  variant: "secondary",
7137
7421
  size: "md",
7138
- state: saving || fullName.trim() === derivedName ? "disabled" : "default",
7422
+ state: saving || !dirty ? "disabled" : "default",
7139
7423
  onClick: handleSave
7140
7424
  }
7141
7425
  ),
package/dist/index.mjs CHANGED
@@ -1,5 +1,10 @@
1
1
  // src/settings-content.tsx
2
- import { useState as useState9, useEffect as useEffect6, useRef as useRef3, useCallback as useCallback5 } from "react";
2
+ import {
3
+ useState as useState9,
4
+ useEffect as useEffect6,
5
+ useRef as useRef3,
6
+ useCallback as useCallback5
7
+ } from "react";
3
8
  import {
4
9
  ScSidebarMenu,
5
10
  ScVDivider,
@@ -21,7 +26,13 @@ import {
21
26
  SiconSettings,
22
27
  SiconRight as SiconRight2,
23
28
  SiconUserAdd as SiconUserAdd2,
24
- SiconLock
29
+ SiconLock,
30
+ SiconDown,
31
+ SiconSwatch,
32
+ SiconBag,
33
+ SiconInsights,
34
+ SiconAdCreator,
35
+ SiconCrown
25
36
  } from "@streamoid/icons";
26
37
 
27
38
  // src/icons/SiconKey.tsx
@@ -4554,7 +4565,7 @@ function UpdateSubscriptionModal({
4554
4565
  lineHeight: "var(--line-height-line-height-xxl)",
4555
4566
  color: "var(--alias-text---icons-primary)"
4556
4567
  },
4557
- children: currentPlanName === "free" ? "Free" : "Pro"
4568
+ children: currentPlanName || "Free"
4558
4569
  }
4559
4570
  ),
4560
4571
  /* @__PURE__ */ jsxs9("div", { className: "flex flex-col items-end", children: [
@@ -5368,9 +5379,16 @@ function UsageLogsTable({
5368
5379
  );
5369
5380
  }
5370
5381
  function BillingContent({ embedded = false } = {}) {
5371
- const { auth, workspace, api, toast } = useSettingsAdapters();
5382
+ const { auth, workspace, api, toast, navigation } = useSettingsAdapters();
5372
5383
  const user = auth.user;
5373
5384
  const workspaceId = workspace.current?.id ?? null;
5385
+ const resolveReturnUrl = (kind) => {
5386
+ if (navigation.getCheckoutReturnUrl) {
5387
+ return navigation.getCheckoutReturnUrl(kind);
5388
+ }
5389
+ if (typeof window === "undefined") return "";
5390
+ return `${window.location.origin}${window.location.pathname}`;
5391
+ };
5374
5392
  const [activeTab, setActiveTab] = useState7("invoice");
5375
5393
  const [showPlans, setShowPlans] = useState7(false);
5376
5394
  const [showExportModal, setShowExportModal] = useState7(false);
@@ -5495,8 +5513,8 @@ function BillingContent({ embedded = false } = {}) {
5495
5513
  quantity: 1,
5496
5514
  customerEmail: user.email,
5497
5515
  customMetadata: { ref: "Unified dashboard" },
5498
- successUrl: `${window.location.origin}/dashboard/${workspaceId}/billing`,
5499
- cancelUrl: `${window.location.origin}/dashboard/${workspaceId}/billing`,
5516
+ successUrl: resolveReturnUrl("success"),
5517
+ cancelUrl: resolveReturnUrl("cancel"),
5500
5518
  action: "upgrade"
5501
5519
  };
5502
5520
  try {
@@ -5593,8 +5611,8 @@ function BillingContent({ embedded = false } = {}) {
5593
5611
  if (loadingBilling) return;
5594
5612
  setShowInitialLoader(false);
5595
5613
  }, [showInitialLoader, initialFetchTriggered, loadingBilling]);
5596
- const planName = subscription?.plan?.amount != null && subscription.plan.amount > 0 ? "Pro" : "Free";
5597
- const isFreePlan = subscription?.plan?.amount === 0 && planName.trim().toLowerCase() === "free";
5614
+ const planName = subscription?.plan?.name?.trim() || (subscription?.plan?.amount != null && subscription.plan.amount > 0 ? "Pro" : "Free");
5615
+ const isFreePlan = subscription?.plan?.amount == null || subscription.plan.amount === 0;
5598
5616
  const planAmount = subscription?.plan?.amount != null ? `$${(subscription.plan.amount / 100).toFixed(2)} / month` : "$0";
5599
5617
  const usedCredits = availableCredits != null && totalCredits != null ? Math.max(0, totalCredits - availableCredits) : null;
5600
5618
  const progressPct = usedCredits != null && totalCredits != null && totalCredits > 0 ? Math.min(100, Math.round(usedCredits / totalCredits * 100)) : 0;
@@ -5691,7 +5709,6 @@ function BillingContent({ embedded = false } = {}) {
5691
5709
  additionalInfo: `${credits}:${price}`
5692
5710
  }
5693
5711
  );
5694
- const returnUrl = `${window.location.origin}/dashboard/${workspaceId}/billing`;
5695
5712
  try {
5696
5713
  const res = await api.buyCredits({
5697
5714
  serviceName: DEFAULT_TOPUP_SERVICE,
@@ -5699,8 +5716,8 @@ function BillingContent({ embedded = false } = {}) {
5699
5716
  unitAmount: price,
5700
5717
  itemCount: 1,
5701
5718
  currency: "usd",
5702
- successUrl: returnUrl,
5703
- cancelUrl: returnUrl,
5719
+ successUrl: resolveReturnUrl("success"),
5720
+ cancelUrl: resolveReturnUrl("cancel"),
5704
5721
  metadata: { source: "self-serve" }
5705
5722
  });
5706
5723
  const payloadData = "data" in res && res.data ? res.data : res;
@@ -6882,11 +6899,274 @@ function SettingsNav({
6882
6899
  }
6883
6900
  );
6884
6901
  }
6902
+ var SPECIALIZATION_ICON_SIZE = 20;
6903
+ var SPECIALIZATION_OPTIONS = [
6904
+ {
6905
+ id: "design",
6906
+ label: "Design",
6907
+ icon: /* @__PURE__ */ jsx13(
6908
+ SiconSwatch,
6909
+ {
6910
+ style: {
6911
+ width: SPECIALIZATION_ICON_SIZE,
6912
+ height: SPECIALIZATION_ICON_SIZE
6913
+ }
6914
+ }
6915
+ )
6916
+ },
6917
+ {
6918
+ id: "merchandising",
6919
+ label: "Merchandising",
6920
+ icon: /* @__PURE__ */ jsx13(
6921
+ SiconBag,
6922
+ {
6923
+ style: {
6924
+ width: SPECIALIZATION_ICON_SIZE,
6925
+ height: SPECIALIZATION_ICON_SIZE
6926
+ }
6927
+ }
6928
+ )
6929
+ },
6930
+ {
6931
+ id: "planning",
6932
+ label: "Planning",
6933
+ icon: /* @__PURE__ */ jsx13(
6934
+ SiconInsights,
6935
+ {
6936
+ style: {
6937
+ width: SPECIALIZATION_ICON_SIZE,
6938
+ height: SPECIALIZATION_ICON_SIZE
6939
+ }
6940
+ }
6941
+ )
6942
+ },
6943
+ {
6944
+ id: "marketing_brand",
6945
+ label: "Marketing & brand",
6946
+ icon: /* @__PURE__ */ jsx13(
6947
+ SiconAdCreator,
6948
+ {
6949
+ style: {
6950
+ width: SPECIALIZATION_ICON_SIZE,
6951
+ height: SPECIALIZATION_ICON_SIZE
6952
+ }
6953
+ }
6954
+ )
6955
+ },
6956
+ {
6957
+ id: "founder_operator",
6958
+ label: "Founder & operator",
6959
+ icon: /* @__PURE__ */ jsx13(
6960
+ SiconCrown,
6961
+ {
6962
+ style: {
6963
+ width: SPECIALIZATION_ICON_SIZE,
6964
+ height: SPECIALIZATION_ICON_SIZE
6965
+ }
6966
+ }
6967
+ )
6968
+ }
6969
+ ];
6970
+ function SpecializationDropdown({
6971
+ value,
6972
+ onChange,
6973
+ disabled
6974
+ }) {
6975
+ const [open, setOpen] = useState9(false);
6976
+ const containerRef = useRef3(null);
6977
+ useEffect6(() => {
6978
+ if (!open) return;
6979
+ function handleMouseDown(e) {
6980
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
6981
+ setOpen(false);
6982
+ }
6983
+ }
6984
+ function handleKeyDown(e) {
6985
+ if (e.key === "Escape") setOpen(false);
6986
+ }
6987
+ document.addEventListener("mousedown", handleMouseDown);
6988
+ document.addEventListener("keydown", handleKeyDown);
6989
+ return () => {
6990
+ document.removeEventListener("mousedown", handleMouseDown);
6991
+ document.removeEventListener("keydown", handleKeyDown);
6992
+ };
6993
+ }, [open]);
6994
+ const selected = SPECIALIZATION_OPTIONS.find((o) => o.id === value);
6995
+ const labelText = selected ? selected.label : value === "unspecified" ? "Not specified" : "Select specialization";
6996
+ return /* @__PURE__ */ jsxs11(
6997
+ "div",
6998
+ {
6999
+ ref: containerRef,
7000
+ className: "flex flex-col items-start w-full",
7001
+ style: { gap: "var(--spacing-xs)", position: "relative" },
7002
+ children: [
7003
+ /* @__PURE__ */ jsx13(
7004
+ "span",
7005
+ {
7006
+ style: {
7007
+ fontFamily: "var(--font-family-font-family-body)",
7008
+ fontSize: "var(--font-size-font-size-sm)",
7009
+ lineHeight: "var(--line-height-line-height-sm)",
7010
+ color: "var(--alias-text---icons-tertiary)"
7011
+ },
7012
+ children: "Specialization"
7013
+ }
7014
+ ),
7015
+ /* @__PURE__ */ jsxs11(
7016
+ "button",
7017
+ {
7018
+ type: "button",
7019
+ onClick: () => !disabled && setOpen((v) => !v),
7020
+ disabled,
7021
+ "aria-haspopup": "listbox",
7022
+ "aria-expanded": open,
7023
+ style: {
7024
+ width: "100%",
7025
+ height: 48,
7026
+ display: "flex",
7027
+ alignItems: "center",
7028
+ gap: "var(--spacing-md, 8px)",
7029
+ padding: "var(--spacing-xl, 12px) var(--spacing-3xl, 16px)",
7030
+ background: "var(--alias-fill-neutral-neutral, #1a1a1a)",
7031
+ borderRadius: "var(--radius-3xl, 16px)",
7032
+ border: "none",
7033
+ cursor: disabled ? "default" : "pointer",
7034
+ opacity: disabled ? 0.5 : 1,
7035
+ color: selected ? "var(--alias-text---icons-primary)" : "var(--alias-text---icons-muted)",
7036
+ fontFamily: "var(--font-family-font-family-body)",
7037
+ fontSize: "var(--font-size-font-size-sm)",
7038
+ lineHeight: "var(--line-height-line-height-sm)",
7039
+ textAlign: "left"
7040
+ },
7041
+ children: [
7042
+ /* @__PURE__ */ jsx13(
7043
+ "span",
7044
+ {
7045
+ style: {
7046
+ display: "flex",
7047
+ alignItems: "center",
7048
+ justifyContent: "center",
7049
+ width: 24,
7050
+ height: 24,
7051
+ color: selected ? "var(--alias-text---icons-primary)" : "var(--alias-text---icons-muted)"
7052
+ },
7053
+ children: selected ? selected.icon : /* @__PURE__ */ jsx13(SiconBolt, {})
7054
+ }
7055
+ ),
7056
+ /* @__PURE__ */ jsx13(
7057
+ "span",
7058
+ {
7059
+ style: {
7060
+ flex: 1,
7061
+ minWidth: 0,
7062
+ overflow: "hidden",
7063
+ textOverflow: "ellipsis",
7064
+ whiteSpace: "nowrap"
7065
+ },
7066
+ children: labelText
7067
+ }
7068
+ ),
7069
+ /* @__PURE__ */ jsx13(
7070
+ "span",
7071
+ {
7072
+ style: {
7073
+ display: "flex",
7074
+ alignItems: "center",
7075
+ justifyContent: "center",
7076
+ width: 24,
7077
+ height: 24,
7078
+ color: "var(--alias-text---icons-primary)",
7079
+ transform: open ? "rotate(180deg)" : "none",
7080
+ transition: "transform 120ms ease-out"
7081
+ },
7082
+ children: /* @__PURE__ */ jsx13(SiconDown, {})
7083
+ }
7084
+ )
7085
+ ]
7086
+ }
7087
+ ),
7088
+ open && /* @__PURE__ */ jsx13(
7089
+ "div",
7090
+ {
7091
+ role: "listbox",
7092
+ style: {
7093
+ position: "absolute",
7094
+ top: "calc(100% + 4px)",
7095
+ left: 0,
7096
+ right: 0,
7097
+ zIndex: 50,
7098
+ background: "var(--alias-surface-subtleraised, #1f1f1f)",
7099
+ border: "1px solid var(--alias-border-divider, #242424)",
7100
+ borderRadius: "var(--radius-3xl, 16px)",
7101
+ padding: "var(--spacing-md, 8px)",
7102
+ display: "flex",
7103
+ flexDirection: "column",
7104
+ gap: 2,
7105
+ boxShadow: "0 8px 24px rgba(0,0,0,0.4)"
7106
+ },
7107
+ children: SPECIALIZATION_OPTIONS.map((opt) => {
7108
+ const isActive = opt.id === value;
7109
+ return /* @__PURE__ */ jsxs11(
7110
+ "button",
7111
+ {
7112
+ type: "button",
7113
+ role: "option",
7114
+ "aria-selected": isActive,
7115
+ onClick: () => {
7116
+ onChange(opt.id);
7117
+ setOpen(false);
7118
+ },
7119
+ style: {
7120
+ display: "flex",
7121
+ alignItems: "center",
7122
+ gap: "var(--spacing-md, 8px)",
7123
+ padding: "var(--spacing-md, 8px) var(--spacing-3xl, 16px)",
7124
+ background: isActive ? "var(--alias-fill-neutral-neutralhover, #242424)" : "transparent",
7125
+ border: "none",
7126
+ borderRadius: "var(--radius-md, 8px)",
7127
+ cursor: "pointer",
7128
+ color: "var(--alias-text---icons-primary)",
7129
+ fontFamily: "var(--font-family-font-family-body)",
7130
+ fontSize: "var(--font-size-font-size-sm)",
7131
+ lineHeight: "var(--line-height-line-height-sm)",
7132
+ textAlign: "left",
7133
+ height: 40
7134
+ },
7135
+ onMouseEnter: (e) => e.currentTarget.style.background = "var(--alias-fill-neutral-neutralhover, #242424)",
7136
+ onMouseLeave: (e) => e.currentTarget.style.background = isActive ? "var(--alias-fill-neutral-neutralhover, #242424)" : "transparent",
7137
+ children: [
7138
+ /* @__PURE__ */ jsx13(
7139
+ "span",
7140
+ {
7141
+ style: {
7142
+ display: "flex",
7143
+ alignItems: "center",
7144
+ justifyContent: "center",
7145
+ width: 24,
7146
+ height: 24
7147
+ },
7148
+ children: opt.icon
7149
+ }
7150
+ ),
7151
+ /* @__PURE__ */ jsx13("span", { style: { flex: 1 }, children: opt.label })
7152
+ ]
7153
+ },
7154
+ opt.id
7155
+ );
7156
+ })
7157
+ }
7158
+ )
7159
+ ]
7160
+ }
7161
+ );
7162
+ }
6885
7163
  function ProfileTab() {
6886
7164
  const { auth, imageUpload, toast } = useSettingsAdapters();
6887
7165
  const user = auth.user;
6888
7166
  const derivedName = user?.name ?? "";
7167
+ const derivedSpec = user?.userSpecialization ?? "";
6889
7168
  const [fullName, setFullName] = useState9(derivedName);
7169
+ const [selectedSpec, setSelectedSpec] = useState9(derivedSpec);
6890
7170
  const [saving, setSaving] = useState9(false);
6891
7171
  const [imageSaving, setImageSaving] = useState9(false);
6892
7172
  const [imagePreview, setImagePreview] = useState9(
@@ -6897,15 +7177,22 @@ function ProfileTab() {
6897
7177
  const profileImage = imagePreview ?? getUserProfileImage(user);
6898
7178
  const profileInitial = profileDisplayName.charAt(0).toUpperCase();
6899
7179
  const disableAllActions = saving || imageSaving;
7180
+ const dirty = fullName.trim() !== derivedName || selectedSpec !== derivedSpec;
6900
7181
  useEffect6(() => {
6901
7182
  setFullName(derivedName);
6902
7183
  setImagePreview(void 0);
6903
7184
  }, [derivedName]);
7185
+ useEffect6(() => {
7186
+ setSelectedSpec(derivedSpec);
7187
+ }, [derivedSpec]);
6904
7188
  const handleSave = async () => {
6905
- if (disableAllActions) return;
7189
+ if (disableAllActions || !dirty) return;
6906
7190
  setSaving(true);
6907
7191
  try {
6908
- await auth.updateProfile({ name: fullName.trim() });
7192
+ const patch = {};
7193
+ if (fullName.trim() !== derivedName) patch.name = fullName.trim();
7194
+ if (selectedSpec !== derivedSpec) patch.userSpecialization = selectedSpec;
7195
+ await auth.updateProfile(patch);
6909
7196
  await auth.refreshProfile();
6910
7197
  toast.success("Profile updated");
6911
7198
  } catch (err) {
@@ -7116,27 +7403,35 @@ function ProfileTab() {
7116
7403
  }
7117
7404
  ) }),
7118
7405
  /* @__PURE__ */ jsx13("div", { className: "flex-1 min-w-0", style: { overflow: "hidden" }, children: /* @__PURE__ */ jsx13(
7119
- ScTextField,
7406
+ SpecializationDropdown,
7120
7407
  {
7121
- label: "Email ID",
7122
- labelGroup: "label",
7123
- state: "default",
7124
- value: user?.email ?? "",
7125
- placeholderFilled: user?.email ?? "chris@kepler.com",
7126
- tfGroup: "icon-right",
7127
- tfRightIcon: /* @__PURE__ */ jsx13(
7128
- SiconLock,
7129
- {
7130
- className: "w-6 h-6",
7131
- color: "var(--alias-text---icons-primary)"
7132
- }
7133
- ),
7134
- className: "sc-text-field-full-width",
7135
- style: { width: "100%" },
7136
- readOnly: true
7408
+ value: selectedSpec,
7409
+ onChange: setSelectedSpec,
7410
+ disabled: disableAllActions
7137
7411
  }
7138
7412
  ) })
7139
7413
  ] }),
7414
+ /* @__PURE__ */ jsx13("div", { className: "w-full", style: { overflow: "hidden" }, children: /* @__PURE__ */ jsx13(
7415
+ ScTextField,
7416
+ {
7417
+ label: "Email ID",
7418
+ labelGroup: "label",
7419
+ state: "default",
7420
+ value: user?.email ?? "",
7421
+ placeholderFilled: user?.email ?? "chris@kepler.com",
7422
+ tfGroup: "icon-right",
7423
+ tfRightIcon: /* @__PURE__ */ jsx13(
7424
+ SiconLock,
7425
+ {
7426
+ className: "w-6 h-6",
7427
+ color: "var(--alias-text---icons-primary)"
7428
+ }
7429
+ ),
7430
+ className: "sc-text-field-full-width",
7431
+ style: { width: "100%" },
7432
+ readOnly: true
7433
+ }
7434
+ ) }),
7140
7435
  /* @__PURE__ */ jsxs11(
7141
7436
  "div",
7142
7437
  {
@@ -7149,7 +7444,7 @@ function ProfileTab() {
7149
7444
  text: saving ? "Saving..." : "Save changes",
7150
7445
  variant: "secondary",
7151
7446
  size: "md",
7152
- state: saving || fullName.trim() === derivedName ? "disabled" : "default",
7447
+ state: saving || !dirty ? "disabled" : "default",
7153
7448
  onClick: handleSave
7154
7449
  }
7155
7450
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamoid/settings",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",