@planningcenter/topbar 11.2.0 → 11.2.1-rc.1

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.
@@ -2,7 +2,7 @@
2
2
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@planningcenter/tapestry'), require('react'), require('@planningcenter/url'), require('@planningcenter/icons/paths/general'), require('@planningcenter/jolt-client'), require('use-sync-external-store/shim'), require('@planningcenter/tapestry-react'), require('@planningcenter/add-ons'), require('@planningcenter/icons/paths/church-center'), require('@planningcenter/icons/paths/people'), require('@planningcenter/icons/paths/toolbar'), require('react-calendly'), require('algoliasearch'), require('react-instantsearch-core'), require('react-transition-group'), require('@planningcenter/datetime-fmt')) :
3
3
  typeof define === 'function' && define.amd ? define(['exports', '@planningcenter/tapestry', 'react', '@planningcenter/url', '@planningcenter/icons/paths/general', '@planningcenter/jolt-client', 'use-sync-external-store/shim', '@planningcenter/tapestry-react', '@planningcenter/add-ons', '@planningcenter/icons/paths/church-center', '@planningcenter/icons/paths/people', '@planningcenter/icons/paths/toolbar', 'react-calendly', 'algoliasearch', 'react-instantsearch-core', 'react-transition-group', '@planningcenter/datetime-fmt'], factory) :
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.PLANNINGCENTER = global.PLANNINGCENTER || {}, global.PLANNINGCENTER.Topbar = {}), global.tapestry, global.react, global.pcoUrl, global.general, global.Jolt, global.shim, global.tapestryReact, global.addOns, global.churchCenter, global.people, global.toolbar, global.reactCalendly, global.algoliasearch, global.reactInstantsearchCore, global.reactTransitionGroup, global.datetimeFmt));
5
- })(this, (function (exports, tapestry, React, pcoUrl, general, Jolt, shim, tapestryReact, addOns, churchCenter, people, toolbar, reactCalendly, algoliasearch, reactInstantsearchCore, reactTransitionGroup, datetimeFmt) { 'use strict';
5
+ })(this, (function (exports, tapestry, React, pcoUrl$1, general, Jolt, shim, tapestryReact, addOns, churchCenter, people, toolbar, reactCalendly, algoliasearch, reactInstantsearchCore, reactTransitionGroup, datetimeFmt) { 'use strict';
6
6
 
7
7
  function _interopNamespaceDefault(e) {
8
8
  var n = Object.create(null);
@@ -461,7 +461,7 @@
461
461
  announcements: newAnnouncements
462
462
  };
463
463
  });
464
- return apiRequest(this.props.configuredFetch || defaultFetch, `${pcoUrl(this.props.env)("api")}/people/v2/me/platform_notifications/${id}/dismiss`, {
464
+ return apiRequest(this.props.configuredFetch || defaultFetch, `${pcoUrl$1(this.props.env)("api")}/people/v2/me/platform_notifications/${id}/dismiss`, {
465
465
  method: "POST"
466
466
  });
467
467
  }
@@ -706,7 +706,7 @@
706
706
  const channel = joltIsV2(jolt) ? buildJoltChannel(jolt, channelName, fetchSubscribeTokenFn) : buildLegacyJoltChannel(jolt, channelName, fetchSubscribeTokenFn);
707
707
  setJoltChannel(channel);
708
708
  setJoltInstance(jolt);
709
- }, [apiRequest, channelName, env, pcoUrl, railsCsrfFetch]);
709
+ }, [apiRequest, channelName, env, pcoUrl$1, railsCsrfFetch]);
710
710
  React.useEffect(() => {
711
711
  if (enabled) {
712
712
  subscribeToJoltChannel();
@@ -911,7 +911,7 @@
911
911
  fetch();
912
912
  }, []);
913
913
  const fetch = () => {
914
- apiRequest(configuredFetch, `${pcoUrl(env)("api")}/people/v2/me/topbar`).then(({
914
+ apiRequest(configuredFetch, `${pcoUrl$1(env)("api")}/people/v2/me/topbar`).then(({
915
915
  json,
916
916
  ok
917
917
  }) => {
@@ -1058,7 +1058,7 @@
1058
1058
  });
1059
1059
  }
1060
1060
  function switchConnectedPerson(env, toId, returnPath) {
1061
- apiRequest(railsCsrfFetch, `${pcoUrl(env)("api")}/login/v2/me/switch_connected_person`, {
1061
+ apiRequest(railsCsrfFetch, `${pcoUrl$1(env)("api")}/login/v2/me/switch_connected_person`, {
1062
1062
  data: {
1063
1063
  data: {
1064
1064
  attributes: {
@@ -1076,7 +1076,7 @@
1076
1076
  });
1077
1077
  }
1078
1078
  function unlinkConnectedPeople(env) {
1079
- apiRequest(railsCsrfFetch, `${pcoUrl(env)("api")}/login/v2/me/unlink`, {
1079
+ apiRequest(railsCsrfFetch, `${pcoUrl$1(env)("api")}/login/v2/me/unlink`, {
1080
1080
  method: "POST"
1081
1081
  }).then(({
1082
1082
  json
@@ -1093,7 +1093,7 @@
1093
1093
  };
1094
1094
  }
1095
1095
  fetch() {
1096
- apiRequest(this.props.configuredFetch, `${pcoUrl(this.props.env)("api")}/login/v2/me/connected_people`).then(({
1096
+ apiRequest(this.props.configuredFetch, `${pcoUrl$1(this.props.env)("api")}/login/v2/me/connected_people`).then(({
1097
1097
  json,
1098
1098
  ok
1099
1099
  }) => {
@@ -2152,7 +2152,7 @@
2152
2152
  component: "a",
2153
2153
  "data-pendo": `app-switcher--change-app--${homeProduct.name}`,
2154
2154
  "data-turbolinks": false,
2155
- href: appName === homeProduct.name ? `${pcoUrl(env)("accounts")}/apps/${appName}` : pcoUrl(env)("accounts") + getAppsSwitchProductPath(homeProduct.name),
2155
+ href: appName === homeProduct.name ? `${pcoUrl$1(env)("accounts")}/apps/${appName}` : pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(homeProduct.name),
2156
2156
  style: {
2157
2157
  ...listItemProps,
2158
2158
  borderBottom: "1px solid var(--t-border-color)"
@@ -2178,7 +2178,7 @@
2178
2178
  component: "a",
2179
2179
  "data-pendo": `app-switcher--change-app--${name}`,
2180
2180
  "data-turbolinks": false,
2181
- href: name === appName ? `${pcoUrl(env)("accounts")}/apps/${name.toLowerCase()}` : pcoUrl(env)("accounts") + getAppsSwitchProductPath(name),
2181
+ href: name === appName ? `${pcoUrl$1(env)("accounts")}/apps/${name.toLowerCase()}` : pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(name),
2182
2182
  key: name,
2183
2183
  style: {
2184
2184
  ...listItemProps,
@@ -2203,7 +2203,7 @@
2203
2203
  component: "a",
2204
2204
  "data-pendo": `app-switcher--change-app--${accountsProduct.name}`,
2205
2205
  "data-turbolinks": false,
2206
- href: pcoUrl(env)("accounts") + getAppsSwitchProductPath(accountsProduct.name),
2206
+ href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(accountsProduct.name),
2207
2207
  style: {
2208
2208
  ...listItemProps,
2209
2209
  borderTop: "1px solid var(--t-border-color)",
@@ -2310,7 +2310,7 @@
2310
2310
  setUserMenuVisible,
2311
2311
  userMenuVisible
2312
2312
  }) => {
2313
- const logoutUrl = `${pcoUrl(env)("login")}/logout`;
2313
+ const logoutUrl = `${pcoUrl$1(env)("login")}/logout`;
2314
2314
  return /*#__PURE__*/React.createElement(PopupRoot, null, /*#__PURE__*/React.createElement(Popup, {
2315
2315
  component: Outsider,
2316
2316
  onOutsideClick: () => setUserMenuVisible(false),
@@ -2583,7 +2583,7 @@
2583
2583
  style,
2584
2584
  ...nativeProps
2585
2585
  }) {
2586
- const url = incomingUrl || `${pcoUrl(env)("people")}/static/no_photo_thumbnail_gray.svg`;
2586
+ const url = incomingUrl || `${pcoUrl$1(env)("people")}/static/no_photo_thumbnail_gray.svg`;
2587
2587
  return /*#__PURE__*/React.createElement("div", {
2588
2588
  className: styles$4.avatar,
2589
2589
  style: {
@@ -2617,7 +2617,7 @@
2617
2617
  className: classnames(styles$3["appmenu-list-a"], styles$3["appmenu-list-a--bottom"]),
2618
2618
  "data-pendo": `app-switcher--change-app--${accountsName}`,
2619
2619
  "data-turbolinks": false,
2620
- href: pcoUrl(env)(accountsName) + getAppsSwitchProductPath(accountsName)
2620
+ href: pcoUrl$1(env)(accountsName) + getAppsSwitchProductPath(accountsName)
2621
2621
  }, /*#__PURE__*/React.createElement("svg", {
2622
2622
  "aria-hidden": "true",
2623
2623
  height: 24,
@@ -2759,7 +2759,7 @@
2759
2759
  "data-pendo": `app-switcher--change-app--${homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name}`,
2760
2760
  "data-turbolinks": false,
2761
2761
  className: classnames(styles$3["appmenu-list-a"], styles$3["appmenu-list-a--top"]),
2762
- href: homeProduct ? pcoUrl(props.env)("accounts") + getAppsSwitchProductPath(homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name) : undefined
2762
+ href: homeProduct ? pcoUrl$1(props.env)("accounts") + getAppsSwitchProductPath(homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name) : undefined
2763
2763
  }, /*#__PURE__*/React.createElement(UserMenuAppLockup, {
2764
2764
  appName: homeProduct ? homeProduct.name : props.appName
2765
2765
  })), /*#__PURE__*/React.createElement("div", {
@@ -2778,7 +2778,7 @@
2778
2778
  "data-pendo": `app-switcher--change-app--${name}`,
2779
2779
  "data-turbolinks": false,
2780
2780
  className: styles$3["appmenu-list-a"],
2781
- href: pcoUrl(props.env)("accounts") + getAppsSwitchProductPath(name)
2781
+ href: pcoUrl$1(props.env)("accounts") + getAppsSwitchProductPath(name)
2782
2782
  }, /*#__PURE__*/React.createElement(UserMenuAppLockup, {
2783
2783
  appName: name
2784
2784
  }))))), accountsProduct && (/*#__PURE__*/React.createElement("li", {
@@ -2823,7 +2823,7 @@
2823
2823
  userId
2824
2824
  }) {
2825
2825
  var _a, _b;
2826
- const basePath = pcoUrl(env)("login") + `/people/AC${userId}`;
2826
+ const basePath = pcoUrl$1(env)("login") + `/people/AC${userId}`;
2827
2827
  const pathname = (_b = (_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.pathname) === null || _b === void 0 ? void 0 : _b.toString();
2828
2828
  if (!pathname) return basePath;
2829
2829
  const params = new URLSearchParams();
@@ -2898,7 +2898,7 @@
2898
2898
  const key = "toolbar";
2899
2899
  const [toolbarFragment, updateFragment] = useFragmentKey(key);
2900
2900
  const onChangeRoute = React.useCallback((tool, path = "") => {
2901
- const nextHash = `#${key}=${tool}/${path}`;
2901
+ const nextHash = `#${key}=${tool}/${encodeQueryString(path)}`;
2902
2902
  history.replaceState(undefined, "", nextHash);
2903
2903
  updateFragment(nextHash);
2904
2904
  }, [updateFragment]);
@@ -2906,7 +2906,7 @@
2906
2906
  if (toolbarFragment) {
2907
2907
  const [tool, ...rest] = toolbarFragment.split("/");
2908
2908
  return {
2909
- path: rest.join("/"),
2909
+ path: decodeQueryString(rest.join("/")),
2910
2910
  tool
2911
2911
  };
2912
2912
  } else {
@@ -2919,6 +2919,18 @@
2919
2919
  }, []);
2920
2920
  return [route, onChangeRoute, clearRoute];
2921
2921
  }
2922
+ // The fragment value is parsed with /toolbar=([^&]+)/, so any "&" in a
2923
+ // tool-reported path would silently truncate the route. We encode just the
2924
+ // query portion on the way into the hash and decode on the way out, so
2925
+ // callers can pass and receive raw paths.
2926
+ function encodeQueryString(path) {
2927
+ const [pathPart, queryPart] = path.split("?", 2);
2928
+ return queryPart ? `${pathPart}?${encodeURIComponent(queryPart)}` : pathPart;
2929
+ }
2930
+ function decodeQueryString(path) {
2931
+ const [pathPart, queryPart] = path.split("?", 2);
2932
+ return queryPart ? `${pathPart}?${decodeURIComponent(queryPart)}` : pathPart;
2933
+ }
2922
2934
 
2923
2935
  const {
2924
2936
  generateColorScale
@@ -3439,6 +3451,16 @@
3439
3451
 
3440
3452
  var styles = {"toolbar":"toolbar-KHJR5","toolbar-button":"toolbar-button-Ye4h5","is-current":"is-current-DZK47","toolbar-button--avatar":"toolbar-button--avatar-5FUik","toolbar-button--home":"toolbar-button--home-Pthd0"};
3441
3453
 
3454
+ const pcoUrl = pcoUrl$1;
3455
+
3456
+ function buildPicobotSrc(origin, path) {
3457
+ const [rawPath, queryPart] = path.replace(/^\/+/, "").split("?", 2);
3458
+ const safePath = rawPath.split("/").filter(segment => segment && segment !== "." && segment !== "..").join("/");
3459
+ const pathWithPrefix = ["topbar", safePath].filter(Boolean).join("/");
3460
+ const relativePath = queryPart !== undefined ? `${pathWithPrefix}?${queryPart}` : pathWithPrefix;
3461
+ return new URL(relativePath, origin).toString();
3462
+ }
3463
+
3442
3464
  const handleSearchSelect = (hit, indexName) => {
3443
3465
  const appName = indexName.split("_")[1];
3444
3466
  const zendeskUrl = `https://${appName}.zendesk.com/hc/${hit.locale.locale}/articles/${hit.id}`;
@@ -3591,9 +3613,62 @@
3591
3613
  function HelpdeskComponent({
3592
3614
  appName,
3593
3615
  closeDrawer,
3616
+ currentPath,
3594
3617
  env,
3595
- helpdeskConfig
3618
+ helpdeskConfig,
3619
+ onChangePath
3596
3620
  }) {
3621
+ const PicobotIframe = React.useMemo(() => {
3622
+ const picobotOrigin = pcoUrl(env)("picobot");
3623
+ return function PicobotIframe({
3624
+ currentPath: iframePath,
3625
+ onChangePath: onIframeChangePath,
3626
+ onMessage
3627
+ }) {
3628
+ const ref = React.useRef(null);
3629
+ const initialPath = iframePath || "";
3630
+ // Tracks the path picobot last reported via postMessage so an
3631
+ // incoming currentPath that matches it doesn't trigger a reload.
3632
+ // Picobot sends a bare relative path (e.g. "conversations/abc") with
3633
+ // no leading slash or /topbar prefix—buildPicobotSrc adds those.
3634
+ const lastReportedPathRef = React.useRef(initialPath);
3635
+ const [loadedPath, setLoadedPath] = React.useState(initialPath);
3636
+ React.useEffect(() => {
3637
+ if (!onIframeChangePath && !onMessage) return undefined;
3638
+ const handler = e => {
3639
+ var _a, _b;
3640
+ if (e.origin !== picobotOrigin) return;
3641
+ if (!e.source || !ref.current) return;
3642
+ if (e.source !== ref.current.contentWindow) return;
3643
+ if (((_a = e.data) === null || _a === void 0 ? void 0 : _a.type) === "NEW_ROUTE_RENDERED" && onIframeChangePath) {
3644
+ const reportedPath = String((_b = e.data.payload) !== null && _b !== void 0 ? _b : "");
3645
+ lastReportedPathRef.current = reportedPath;
3646
+ onIframeChangePath(reportedPath);
3647
+ }
3648
+ if (onMessage) onMessage(e.data);
3649
+ };
3650
+ window.addEventListener("message", handler);
3651
+ return () => window.removeEventListener("message", handler);
3652
+ }, [onIframeChangePath, onMessage]);
3653
+ React.useEffect(() => {
3654
+ const incomingPath = iframePath || "";
3655
+ if (lastReportedPathRef.current === incomingPath) return;
3656
+ setLoadedPath(incomingPath);
3657
+ }, [iframePath]);
3658
+ const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath);
3659
+ return /*#__PURE__*/React.createElement("iframe", {
3660
+ ref: ref,
3661
+ src: iframeSrc,
3662
+ style: {
3663
+ border: 0,
3664
+ flex: 1,
3665
+ height: "100%",
3666
+ width: "100%"
3667
+ },
3668
+ title: "Picobot"
3669
+ });
3670
+ };
3671
+ }, [env]);
3597
3672
  return /*#__PURE__*/React.createElement(addOns.UiExtensionFirstPartyComponent, {
3598
3673
  grow: 1,
3599
3674
  display: "flex",
@@ -3613,6 +3688,7 @@
3613
3688
  DocumentationSearch,
3614
3689
  DrawerHeader,
3615
3690
  DrawerHeading,
3691
+ PicobotIframe,
3616
3692
  ScrollContainer,
3617
3693
  SwitchView
3618
3694
  },
@@ -3620,7 +3696,9 @@
3620
3696
  appName,
3621
3697
  closeDrawer,
3622
3698
  currentPageParams: location.search,
3623
- helpdeskConfig
3699
+ currentPath,
3700
+ helpdeskConfig,
3701
+ onChangePath
3624
3702
  }
3625
3703
  });
3626
3704
  }
@@ -3929,7 +4007,7 @@
3929
4007
  id,
3930
4008
  type: "graph_notification"
3931
4009
  }], []);
3932
- return apiRequest(railsCsrfFetch, `${pcoUrl(env)("api")}/notifications/v2/me/bulk_update`, {
4010
+ return apiRequest(railsCsrfFetch, `${pcoUrl$1(env)("api")}/notifications/v2/me/bulk_update`, {
3933
4011
  data: {
3934
4012
  data: {
3935
4013
  attributes: {},
@@ -3959,7 +4037,7 @@
3959
4037
  });
3960
4038
  };
3961
4039
  const handleCreateTask = React.useCallback(async notification => {
3962
- return apiRequest(railsCsrfFetch, `${pcoUrl(env)("api")}/home/v2/me/task_lists/inbox/tasks`, {
4040
+ return apiRequest(railsCsrfFetch, `${pcoUrl$1(env)("api")}/home/v2/me/task_lists/inbox/tasks`, {
3963
4041
  data: {
3964
4042
  data: {
3965
4043
  attributes: {
@@ -4415,38 +4493,29 @@
4415
4493
  setFragmentRoute,
4416
4494
  env
4417
4495
  }) {
4418
- const unencodedCurrentPath = decodeQueryString(currentPath || "");
4419
- const [iframeSrcPath, setIframeSrcPath] = React.useState(unencodedCurrentPath);
4420
- const internalPathOfChatRef = React.useRef(unencodedCurrentPath);
4421
- const chatBaseURL = pcoUrl(env)("chat");
4496
+ const initialPath = currentPath || "";
4497
+ const [iframeSrcPath, setIframeSrcPath] = React.useState(initialPath);
4498
+ const internalPathOfChatRef = React.useRef(initialPath);
4499
+ const chatBaseURL = pcoUrl$1(env)("chat");
4422
4500
  const updateFragmentRouteAndInternalPath = React.useCallback(event => {
4423
- const unencodedPath = event.data.payload.replace(/^\/?topbar/, "") // Maybe this should happen on the child side of things?
4501
+ const reportedPath = event.data.payload.replace(/^\/?topbar/, "") // Maybe this should happen on the child side of things?
4424
4502
  .replace(/^\/+/, "");
4425
- internalPathOfChatRef.current = unencodedPath;
4426
- // URL encode the payload's query string for the fragment route
4427
- // so possible query params don't get dropped by the fragment router
4428
- setFragmentRoute(encodeQueryString(unencodedPath));
4503
+ internalPathOfChatRef.current = reportedPath;
4504
+ setFragmentRoute(reportedPath);
4429
4505
  }, [setFragmentRoute]);
4430
4506
  useHandleMessageFromChildFrame(updateFragmentRouteAndInternalPath, {
4431
4507
  allowedOrigin: chatBaseURL,
4432
4508
  messageType: "NEW_ROUTE_RENDERED"
4433
4509
  });
4434
4510
  React.useEffect(() => {
4435
- if (internalPathOfChatRef.current !== unencodedCurrentPath) {
4436
- setIframeSrcPath(unencodedCurrentPath);
4511
+ const incomingPath = currentPath || "";
4512
+ if (internalPathOfChatRef.current !== incomingPath) {
4513
+ setIframeSrcPath(incomingPath);
4437
4514
  }
4438
4515
  }, [currentPath]);
4439
4516
  const iframeSrc = [chatBaseURL, "topbar", iframeSrcPath].filter(Boolean).join("/");
4440
4517
  return iframeSrc;
4441
4518
  }
4442
- function encodeQueryString(path) {
4443
- const [pathPart, queryPart] = path.split("?", 2);
4444
- return queryPart ? `${pathPart}?${encodeURIComponent(queryPart)}` : pathPart;
4445
- }
4446
- function decodeQueryString(path) {
4447
- const [pathPart, queryPart] = path.split("?", 2);
4448
- return queryPart ? `${pathPart}?${decodeURIComponent(queryPart)}` : pathPart;
4449
- }
4450
4519
 
4451
4520
  function ChatTool({
4452
4521
  env,
@@ -4461,7 +4530,7 @@
4461
4530
  setFragmentRoute: onChangePath
4462
4531
  });
4463
4532
  useHandleMessageFromChildFrame(closeDrawer, {
4464
- allowedOrigin: pcoUrl(env)("chat"),
4533
+ allowedOrigin: pcoUrl$1(env)("chat"),
4465
4534
  messageType: "CLOSE_DRAWER"
4466
4535
  });
4467
4536
  if (currentPath === "blankstate") {
@@ -4572,6 +4641,7 @@
4572
4641
  }
4573
4642
  const TOOLS_MAP = {
4574
4643
  Chat,
4644
+ Helpdesk,
4575
4645
  Notifications,
4576
4646
  Profile,
4577
4647
  Tasks
@@ -4654,7 +4724,7 @@
4654
4724
  env: env,
4655
4725
  userId: userId
4656
4726
  });
4657
- const logoutUrl = `${pcoUrl(env)("login")}/logout`;
4727
+ const logoutUrl = `${pcoUrl$1(env)("login")}/logout`;
4658
4728
  const profileUrl = `/people/AC${userId}`;
4659
4729
  const HelpDeskTool = Helpdesk;
4660
4730
  const setToolAndOpenDrawer = React.useCallback(content => {