@camunda/camunda-composite-components 0.1.4 → 0.1.5-rc.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.
@@ -4,7 +4,7 @@ export declare class HttpError extends Error {
4
4
  constructor(message: string, status: number);
5
5
  }
6
6
  export interface RequestPayload {
7
- base?: "notifications";
7
+ base?: "notifications" | "accounts";
8
8
  stage?: Stage;
9
9
  endpoints?: Endpoints;
10
10
  url: string;
@@ -18,6 +18,8 @@ export interface RequestPayload {
18
18
  token: string;
19
19
  refreshTokenMethod: () => Promise<string>;
20
20
  };
21
- body?: any;
21
+ body?: {
22
+ [key: string]: unknown;
23
+ };
22
24
  }
23
25
  export declare function request(payload: RequestPayload): Promise<any>;
@@ -1,4 +1,4 @@
1
- import { getEndpoint, NOTIFICATIONS } from "./endpoints.const";
1
+ import { getEndpoint, NOTIFICATIONS, ACCOUNTS, } from "./endpoints.const";
2
2
  import { JWTUtils } from "./jwt.utils";
3
3
  export class HttpError extends Error {
4
4
  status;
@@ -41,6 +41,13 @@ export async function request(payload) {
41
41
  base = getEndpoint(payload.stage, NOTIFICATIONS);
42
42
  }
43
43
  break;
44
+ case "accounts":
45
+ if (payload.endpoints?.accounts) {
46
+ base = payload.endpoints.accounts;
47
+ }
48
+ else if (payload.stage) {
49
+ base = getEndpoint(payload.stage, ACCOUNTS);
50
+ }
44
51
  }
45
52
  }
46
53
  const url = base ? `${base}/${payload.url}` : payload.url;
@@ -62,7 +69,7 @@ export async function request(payload) {
62
69
  const responseType = payload.responseType ?? type;
63
70
  switch (responseType) {
64
71
  case "json":
65
- return response.json();
72
+ return response.text().then((text) => (text ? JSON.parse(text) : {}));
66
73
  case "text":
67
74
  return response.text();
68
75
  default:
@@ -6,8 +6,10 @@ export interface Endpoint {
6
6
  }
7
7
  export interface Endpoints {
8
8
  notifications?: string;
9
+ accounts?: string;
9
10
  }
10
11
  export declare const NOTIFICATIONS: Endpoint;
12
+ export declare const ACCOUNTS: Endpoint;
11
13
  export type Stage = "dev" | "int" | "prod";
12
14
  export declare function getEndpoint(stage: Stage, endpoint: Endpoint): string;
13
15
  export declare function getEndpointByOptions(options: {
@@ -4,6 +4,12 @@ export const NOTIFICATIONS = {
4
4
  int: "https://notifications.cloud.ultrawombat.com",
5
5
  prod: "https://notifications.cloud.camunda.io",
6
6
  };
7
+ export const ACCOUNTS = {
8
+ id: "accounts",
9
+ dev: "https://accounts.cloud.dev.ultrawombat.com",
10
+ int: "https://accounts.cloud.ultrawombat.com",
11
+ prod: "https://accounts.cloud.camunda.io",
12
+ };
7
13
  export function getEndpoint(stage, endpoint) {
8
14
  switch (stage) {
9
15
  case "dev":
@@ -25,6 +31,14 @@ export function getEndpointByOptions(options) {
25
31
  else if (options.stage) {
26
32
  return getEndpoint(options.stage, options.endpoint);
27
33
  }
34
+ break;
35
+ case "accounts":
36
+ if (options.endpoints?.accounts) {
37
+ return options.endpoints.accounts;
38
+ }
39
+ else if (options.stage) {
40
+ return getEndpoint(options.stage, options.endpoint);
41
+ }
28
42
  }
29
43
  throw new Error(`Missing stage or notifications endpoint`);
30
44
  }
@@ -0,0 +1,16 @@
1
+ import { C3NotificationsProps } from "../components/c3-navigation/c3-navigation.types";
2
+ import { Theme } from "../components/c3-user-configuration/c3-profile-provider/c3-profile-provider";
3
+ export type Profile = {
4
+ theme: Theme;
5
+ };
6
+ export type Token = {
7
+ sub?: string;
8
+ "https://camunda.com/settings"?: {
9
+ theme?: Theme;
10
+ };
11
+ };
12
+ export declare const getUserTheme: (userToken: string) => Theme | null;
13
+ export type UpdateThemeOptions = C3NotificationsProps & {
14
+ theme: Profile["theme"];
15
+ };
16
+ export declare const updateTheme: ({ endpoints, stage, userToken, getNewUserToken, theme, }: UpdateThemeOptions) => Promise<void>;
@@ -0,0 +1,29 @@
1
+ import jwt from "jwt-decode";
2
+ import { request } from "./api";
3
+ const decodeJWT = (userToken) => {
4
+ let decodedToken = {};
5
+ try {
6
+ decodedToken = jwt(userToken);
7
+ }
8
+ catch (error) {
9
+ console.error("User token invalid");
10
+ }
11
+ return decodedToken;
12
+ };
13
+ export const getUserTheme = (userToken) => decodeJWT(userToken)["https://camunda.com/settings"]?.theme || null;
14
+ export const updateTheme = async ({ endpoints, stage, userToken, getNewUserToken, theme, }) => {
15
+ try {
16
+ await request({
17
+ method: "patch",
18
+ base: "accounts",
19
+ stage,
20
+ endpoints,
21
+ camundaAuth: { token: userToken, refreshTokenMethod: getNewUserToken },
22
+ url: `external/user/theme`,
23
+ body: { theme },
24
+ });
25
+ }
26
+ catch (error) {
27
+ console.error(error);
28
+ }
29
+ };
@@ -4,6 +4,8 @@ import { FormLabel, RadioButton, RadioButtonGroup, Stack, SwitcherDivider, Toggl
4
4
  import { UserAvatar } from "@carbon/react/icons";
5
5
  import styled from "styled-components";
6
6
  import { useUserSidebarState } from "./c3-sidebar-state-provider";
7
+ import { useC3Profile, } from "../../c3-user-configuration/c3-profile-provider/c3-profile-provider";
8
+ import { useC3UserConfiguration } from "../../c3-user-configuration/c3-user-configuration-provider";
7
9
  const VersionWrapper = styled.div `
8
10
  align-self: end;
9
11
  padding: 0.5rem 1rem;
@@ -28,6 +30,8 @@ const C3UserSidebar = ({ sideBar }) => {
28
30
  const themeSelector = customElements?.themeSelector;
29
31
  const stageToggle = customElements?.stageToggle;
30
32
  const customSection = customElements?.customSection;
33
+ const { handleTheme } = useC3UserConfiguration();
34
+ const { theme, onThemeChange } = useC3Profile();
31
35
  const { isOpen } = useUserSidebarState();
32
36
  const itemTabIndex = isOpen ? undefined : -1;
33
37
  return (React.createElement(C3NavigationSideBar, { sideBar: {
@@ -51,14 +55,16 @@ const C3UserSidebar = ({ sideBar }) => {
51
55
  React.createElement(Stack, null,
52
56
  React.createElement("div", { className: "textPrimary", style: { fontSize: "14px" } }, profile.user.name),
53
57
  React.createElement("div", { className: "textPrimary", style: { fontSize: "12px" } }, profile.user.email))))),
54
- themeSelector && (React.createElement(React.Fragment, null,
58
+ (themeSelector || handleTheme) && (React.createElement(React.Fragment, null,
55
59
  React.createElement(SwitcherDivider, null),
56
60
  React.createElement("div", { style: {
57
61
  padding: ".5rem 1rem",
58
62
  } },
59
- React.createElement(RadioButtonGroup, { name: "theme-radio-group", defaultSelected: themeSelector.currentTheme, legendText: "Theme", orientation: "vertical", onChange: (newValue) => {
60
- themeSelector.onChange(newValue);
61
- } },
63
+ React.createElement(RadioButtonGroup, { name: "theme-radio-group", legendText: "Theme", orientation: "vertical", onChange: (newValue) => {
64
+ handleTheme
65
+ ? onThemeChange(newValue)
66
+ : themeSelector?.onChange(newValue);
67
+ }, valueSelected: handleTheme ? theme : themeSelector?.currentTheme },
62
68
  React.createElement(RadioButton, { id: "light", labelText: "Light", value: "light", tabIndex: itemTabIndex }),
63
69
  React.createElement(RadioButton, { id: "system", labelText: "System", value: "system", tabIndex: itemTabIndex }),
64
70
  React.createElement(RadioButton, { id: "dark", labelText: "Dark", value: "dark", tabIndex: itemTabIndex }))))),
@@ -16,7 +16,7 @@ const C3NotificationProvider = ({ children, }) => {
16
16
  const [activeOrganizationId, setActiveOrganizationId] = useState("");
17
17
  const [isEventStreamAvailable, setEventStreamAvailable] = useState(false);
18
18
  const config = useContext(C3UserConfigurationContext);
19
- const enabled = !!config;
19
+ const enabled = !!config && !!config.activeOrganizationId && !!config.userToken;
20
20
  // if the organization changes, we need to reset the state
21
21
  if (enabled && config.activeOrganizationId !== activeOrganizationId) {
22
22
  setActiveOrganizationId(config.activeOrganizationId);
@@ -19,6 +19,7 @@ export declare function createOrgSideBarProps(options: {
19
19
  export declare function createUserSideBarProps(options: {
20
20
  isOpen: boolean;
21
21
  customSection?: React.JSX.Element;
22
+ hideThemeSelector?: boolean;
22
23
  }): C3NavigationProps["userSideBar"];
23
24
  export declare function createNotificationSideBarProps(options: {
24
25
  isOpen: boolean;
@@ -169,10 +169,12 @@ export function createUserSideBarProps(options) {
169
169
  name: "Team Cloud",
170
170
  },
171
171
  },
172
- themeSelector: {
173
- currentTheme: "dark",
174
- onChange: () => console.log("Selected dark theme"),
175
- },
172
+ themeSelector: options.hideThemeSelector
173
+ ? undefined
174
+ : {
175
+ currentTheme: "dark",
176
+ onChange: (newTheme) => console.log(`Selected ${newTheme} theme`),
177
+ },
176
178
  stageToggle: {
177
179
  prodFeaturesEnabled: true,
178
180
  toggle: () => console.log("Toggle stage"),
@@ -0,0 +1,10 @@
1
+ import React, { FC, PropsWithChildren } from "react";
2
+ export type Theme = "light" | "dark" | "system";
3
+ export type C3ProfileContextValue = {
4
+ isEnabled: boolean;
5
+ theme: Theme;
6
+ onThemeChange: (newTheme: Theme) => void;
7
+ };
8
+ export declare const C3ProfileContext: React.Context<C3ProfileContextValue>;
9
+ export declare const C3ProfileProvider: FC<PropsWithChildren>;
10
+ export declare const useC3Profile: () => C3ProfileContextValue;
@@ -0,0 +1,33 @@
1
+ import React, { createContext, useContext, useEffect, useState, } from "react";
2
+ import { C3UserConfigurationContext } from "../c3-user-configuration-provider";
3
+ import { getUserTheme, updateTheme } from "../../../api/profile";
4
+ import { CarbonThemeProvider } from "./carbon-theme-provider";
5
+ const defaultTheme = "light";
6
+ export const C3ProfileContext = createContext({
7
+ isEnabled: false,
8
+ theme: defaultTheme,
9
+ onThemeChange: () => undefined,
10
+ });
11
+ export const C3ProfileProvider = ({ children }) => {
12
+ const config = useContext(C3UserConfigurationContext);
13
+ const isEnabled = !!config;
14
+ const [theme, setTheme] = useState(getUserTheme(config.userToken) || defaultTheme);
15
+ useEffect(() => {
16
+ if (isEnabled && config.userToken && config.handleTheme) {
17
+ const currentTheme = getUserTheme(config.userToken);
18
+ if (currentTheme)
19
+ setTheme(currentTheme);
20
+ }
21
+ }, [config?.userToken]);
22
+ const onThemeChange = (newTheme) => {
23
+ if (isEnabled) {
24
+ setTheme(newTheme);
25
+ updateTheme({ ...config, theme: newTheme });
26
+ }
27
+ };
28
+ if (!isEnabled)
29
+ return children;
30
+ return config.handleTheme ? (React.createElement(C3ProfileContext.Provider, { value: { isEnabled, theme, onThemeChange } },
31
+ React.createElement(CarbonThemeProvider, null, children))) : (children);
32
+ };
33
+ export const useC3Profile = () => useContext(C3ProfileContext);
@@ -0,0 +1,3 @@
1
+ import { FC, PropsWithChildren } from "react";
2
+ export type CarbonTheme = "white" | "g10" | "g90" | "g100";
3
+ export declare const CarbonThemeProvider: FC<PropsWithChildren>;
@@ -0,0 +1,23 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { useC3Profile } from "./c3-profile-provider";
3
+ import { GlobalTheme } from "@carbon/react";
4
+ const THEMES = { light: "g10", dark: "g100" };
5
+ const getCarbonTheme = (theme) => theme === "light" ||
6
+ window.matchMedia("(prefers-color-scheme: light)").matches
7
+ ? THEMES.light
8
+ : THEMES.dark;
9
+ const setThemeClass = (newTheme, currentTheme) => {
10
+ if (currentTheme) {
11
+ document.documentElement.classList.remove(`cds--${getCarbonTheme(currentTheme)}`);
12
+ }
13
+ document.documentElement.classList.add(`cds--${getCarbonTheme(newTheme)}`);
14
+ };
15
+ export const CarbonThemeProvider = ({ children }) => {
16
+ const { theme } = useC3Profile();
17
+ const [previousTheme, setPreviousTheme] = useState(theme);
18
+ useEffect(() => {
19
+ setThemeClass(theme, previousTheme);
20
+ setPreviousTheme(theme);
21
+ }, [theme]);
22
+ return React.createElement(GlobalTheme, { theme: getCarbonTheme(theme) }, children);
23
+ };
@@ -7,13 +7,16 @@ type C3UserConfigurationBase = {
7
7
  };
8
8
  type C3UserConfigurationWithEndpoints = C3UserConfigurationBase & {
9
9
  endpoints: Endpoints;
10
+ handleTheme?: never;
10
11
  };
11
12
  type C3UserConfigurationWithStage = C3UserConfigurationBase & {
12
13
  stage: Stage;
14
+ handleTheme?: boolean;
13
15
  };
14
16
  export type C3UserConfiguration = C3UserConfigurationWithEndpoints | C3UserConfigurationWithStage;
15
- export declare const C3UserConfigurationContext: React.Context<C3UserConfiguration | null>;
17
+ export declare const C3UserConfigurationContext: React.Context<C3UserConfiguration>;
16
18
  declare const C3UserConfigurationProvider: FC<C3UserConfiguration & {
17
19
  children?: ReactNode;
18
20
  }>;
21
+ export declare const useC3UserConfiguration: () => C3UserConfiguration;
19
22
  export default C3UserConfigurationProvider;
@@ -1,4 +1,12 @@
1
- import React from "react";
2
- export const C3UserConfigurationContext = React.createContext(null);
3
- const C3UserConfigurationProvider = ({ children, ...config }) => (React.createElement(C3UserConfigurationContext.Provider, { value: config }, children));
1
+ import React, { useContext } from "react";
2
+ import { C3ProfileProvider } from "./c3-profile-provider/c3-profile-provider";
3
+ export const C3UserConfigurationContext = React.createContext({
4
+ stage: "dev",
5
+ activeOrganizationId: "",
6
+ userToken: "",
7
+ getNewUserToken: () => Promise.resolve(""),
8
+ });
9
+ const C3UserConfigurationProvider = ({ children, ...config }) => (React.createElement(C3UserConfigurationContext.Provider, { value: config },
10
+ React.createElement(C3ProfileProvider, null, children)));
11
+ export const useC3UserConfiguration = () => useContext(C3UserConfigurationContext);
4
12
  export default C3UserConfigurationProvider;
@@ -7,3 +7,4 @@ export { C3AppMenuIcon } from "./icons/c3-icons";
7
7
  export { C3IconProps } from "./icons/c3-icons.types";
8
8
  export { default as C3UserConfigurationProvider } from "./components/c3-user-configuration/c3-user-configuration-provider";
9
9
  export { C3UserConfiguration } from "./components/c3-user-configuration/c3-user-configuration-provider";
10
+ export { useC3Profile } from "./components/c3-user-configuration/c3-profile-provider/c3-profile-provider";
package/lib/esm/index.js CHANGED
@@ -2,3 +2,4 @@ export { C3EmptyState } from "./components/c3-empty-state/c3-empty-state";
2
2
  export { default as C3Navigation } from "./components/c3-navigation";
3
3
  export { C3AppMenuIcon } from "./icons/c3-icons";
4
4
  export { default as C3UserConfigurationProvider } from "./components/c3-user-configuration/c3-user-configuration-provider";
5
+ export { useC3Profile } from "./components/c3-user-configuration/c3-profile-provider/c3-profile-provider";
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@camunda/camunda-composite-components",
3
- "version": "0.1.4",
3
+ "version": "0.1.5-rc.0",
4
4
  "scripts": {
5
5
  "clean": "rimraf lib/",
6
6
  "build": "yarn clean && tsc",
7
7
  "storybook": "storybook dev -p 6006",
8
8
  "start": "yarn storybook",
9
9
  "build:storybook": "storybook build",
10
+ "build:all": "yarn build && yarn build:storybook",
10
11
  "serve:storybook": "serve ./storybook-static -p 8081 -n -L",
11
12
  "prepare": "husky install",
12
13
  "format": "prettier --write .",
@@ -80,6 +81,9 @@
80
81
  "wait-on": "7.0.1",
81
82
  "webpack": "5.88.2"
82
83
  },
84
+ "dependencies": {
85
+ "jwt-decode": "3.1.2"
86
+ },
83
87
  "peerDependencies": {
84
88
  "@carbon/react": "1.x",
85
89
  "event-source-polyfill": "1.0.x",