@hyvor/design 1.1.24-beta.4 → 1.1.25-beta-org-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.
@@ -3,11 +3,19 @@
3
3
  import { onMount } from 'svelte';
4
4
  import BarProducts, { PRODUCTS } from './BarProducts.svelte';
5
5
  import BarSupport from './BarSupport.svelte';
6
- import { barUser, initBar, setInstanceAndProduct, type BarConfig, type BarUser as BarUserType } from './bar.js';
6
+ import {
7
+ barUser,
8
+ initBar,
9
+ setInstanceAndProduct,
10
+ type BarConfig,
11
+ type BarUser as BarUserType
12
+ } from './bar.js';
7
13
  import BarUpdates from './BarUpdates.svelte';
8
14
  import IconCaretDownFill from '@hyvor/icons/IconCaretDownFill';
9
15
  import BarNotice from './Notice/BarNotice.svelte';
10
16
  import BarLicense from './Notice/BarLicense.svelte';
17
+ import BarOrganization from './Organization/BarOrganization.svelte';
18
+ import { type BarOrganization as BarOrganizationType } from './bar.js';
11
19
 
12
20
  interface Props {
13
21
  instance?: string;
@@ -27,16 +35,18 @@
27
35
  authOverride?: {
28
36
  user: BarUserType | null;
29
37
  logoutUrl: string;
30
- }
38
+ };
39
+ onOrganizationSwitch?: (org: BarOrganizationType) => void;
31
40
  }
32
41
 
33
42
  let {
34
- instance = 'https://hyvor.com',
35
- product,
43
+ instance = 'https://hyvor.com',
44
+ product,
36
45
  logo = `${instance}/api/public/logo/${product}.svg`,
37
46
  config = {},
38
47
  cloud = true,
39
- authOverride = undefined
48
+ authOverride = undefined,
49
+ onOrganizationSwitch = () => {}
40
50
  }: Props = $props();
41
51
 
42
52
  let mobileShow = $state(false);
@@ -61,15 +71,15 @@
61
71
  }
62
72
  }
63
73
 
74
+ if (authOverride) {
75
+ barUser.set(authOverride.user);
76
+ }
77
+
64
78
  onMount(() => {
65
79
  setInstanceAndProduct(instance, product);
66
80
 
67
81
  if (cloud) {
68
82
  initBar();
69
- } else {
70
- if (authOverride) {
71
- barUser.set(authOverride.user);
72
- }
73
83
  }
74
84
  });
75
85
 
@@ -87,16 +97,12 @@
87
97
  <div class="inner hds-box">
88
98
  <div class="left">
89
99
  <a class="logo" href="/">
90
- <img
91
- src={logo}
92
- alt={product}
93
- width="20"
94
- height="20"
95
- />
100
+ <img src={logo} alt={product} width="20" height="20" />
96
101
  <span class="name">
97
102
  {getName()}
98
103
  </span>
99
104
  </a>
105
+ <BarOrganization onSwitch={onOrganizationSwitch} />
100
106
  <BarLicense name={getName()} />
101
107
  </div>
102
108
  <div class="right">
@@ -132,7 +138,7 @@
132
138
  z-index: 100;
133
139
  }
134
140
  .inner {
135
- padding: 10px 29px;
141
+ padding: 0px 29px;
136
142
  display: flex;
137
143
  align-items: center;
138
144
  border-top-left-radius: 0;
@@ -143,6 +149,7 @@
143
149
  display: flex;
144
150
  align-items: center;
145
151
  flex: 1;
152
+ height: 100%;
146
153
  }
147
154
  .logo {
148
155
  display: flex;
@@ -1,4 +1,5 @@
1
1
  import { type BarConfig, type BarUser as BarUserType } from './bar.js';
2
+ import { type BarOrganization as BarOrganizationType } from './bar.js';
2
3
  interface Props {
3
4
  instance?: string;
4
5
  product: string;
@@ -13,6 +14,7 @@ interface Props {
13
14
  user: BarUserType | null;
14
15
  logoutUrl: string;
15
16
  };
17
+ onOrganizationSwitch?: (org: BarOrganizationType) => void;
16
18
  }
17
19
  declare const HyvorBar: import("svelte").Component<Props, {}, "">;
18
20
  type HyvorBar = ReturnType<typeof HyvorBar>;
@@ -22,7 +22,7 @@
22
22
  return daysDiff(endsAt);
23
23
  }
24
24
 
25
- function remainingCancelAtDAys(cancelAt: number | undefined | null) : null | number {
25
+ function remainingCancelAtDAys(cancelAt: number | undefined | null): null | number {
26
26
  if (!cancelAt) {
27
27
  return null;
28
28
  }
@@ -36,9 +36,7 @@
36
36
  {#if $barLicense}
37
37
  <a class="wrap" href="/console/billing">
38
38
  {#if $barLicense.type === 'subscription'}
39
- <Tooltip
40
- position="bottom"
41
- >
39
+ <Tooltip position="bottom">
42
40
  {#snippet tooltip()}
43
41
  Your current subscription plan for {name}. Click to manage it.
44
42
  {/snippet}
@@ -0,0 +1,43 @@
1
+ <script lang="ts">
2
+ import Tooltip from '../../Tooltip/Tooltip.svelte';
3
+ import { barOrganizationDropdownOpen, barUser, type BarOrganization } from '../bar.js';
4
+ import OrganizationButton from './OrganizationButton.svelte';
5
+
6
+ let disableTooltip = $state(false);
7
+
8
+ let props: {
9
+ onSwitch: (org: BarOrganization) => void;
10
+ } = $props();
11
+
12
+ barOrganizationDropdownOpen.subscribe((value) => {
13
+ if (value) disableTooltip = true;
14
+ });
15
+ </script>
16
+
17
+ {#if $barUser}
18
+ <Tooltip position="bottom" text="Switch Organization" disabled={disableTooltip}>
19
+ <div class="wrap">
20
+ <OrganizationButton
21
+ bind:show={$barOrganizationDropdownOpen}
22
+ onSwitch={props.onSwitch}
23
+ style="bordered"
24
+ />
25
+ </div>
26
+ </Tooltip>
27
+ {/if}
28
+
29
+ <style>
30
+ .wrap {
31
+ height: 100%;
32
+ margin-left: 20px;
33
+ margin-right: 20px;
34
+ display: inline-flex;
35
+ align-items: center;
36
+ }
37
+
38
+ .wrap :global(.dropdown),
39
+ .wrap :global(.trigger) {
40
+ display: inline-block;
41
+ height: 100%;
42
+ }
43
+ </style>
@@ -0,0 +1,7 @@
1
+ import { type BarOrganization } from '../bar.js';
2
+ type $$ComponentProps = {
3
+ onSwitch: (org: BarOrganization) => void;
4
+ };
5
+ declare const BarOrganization: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type BarOrganization = ReturnType<typeof BarOrganization>;
7
+ export default BarOrganization;
@@ -0,0 +1,90 @@
1
+ <script lang="ts">
2
+ import Dropdown from '../../Dropdown/Dropdown.svelte';
3
+ import IconChevronExpand from '@hyvor/icons/IconChevronExpand';
4
+ import OrgsList from './OrgsList.svelte';
5
+ import { barUser, switchOrganization, type BarOrganization } from '../bar.js';
6
+ import toast from '../../Toast/toast.js';
7
+ import type { DropdownAlign } from '../../Dropdown/dropdown.types.js';
8
+
9
+ interface Props {
10
+ show?: boolean;
11
+ onSwitch?: (org: BarOrganization) => void;
12
+ style?: 'bordered' | 'plain';
13
+ dropdownAlign?: DropdownAlign;
14
+ }
15
+
16
+ let {
17
+ show = $bindable(false),
18
+ onSwitch,
19
+ style = 'plain',
20
+ dropdownAlign = 'center'
21
+ }: Props = $props();
22
+ let switching = $state(false);
23
+
24
+ async function handleSwitch(org: BarOrganization) {
25
+ show = false;
26
+ switching = true;
27
+
28
+ try {
29
+ await switchOrganization(org);
30
+ } catch (e) {
31
+ toast.error('Failed to switch organization.');
32
+ switching = false;
33
+ return;
34
+ }
35
+
36
+ switching = false;
37
+ onSwitch?.(org);
38
+ }
39
+ </script>
40
+
41
+ {#if $barUser}
42
+ <Dropdown bind:show align={dropdownAlign} width={275} contentPadding={0}>
43
+ {#snippet trigger()}
44
+ <button class:bordered={style === 'bordered'}>
45
+ {#if switching}
46
+ <span class="switching">Switching...</span>
47
+ {:else}
48
+ {$barUser.current_organization_name}
49
+ {/if}
50
+ <IconChevronExpand size={12} />
51
+ </button>
52
+ {/snippet}
53
+ {#snippet content()}
54
+ <OrgsList onSwitch={handleSwitch} />
55
+ {/snippet}
56
+ </Dropdown>
57
+ {/if}
58
+
59
+ <style>
60
+ button {
61
+ height: 100%;
62
+ font-size: 14px;
63
+ height: 24px;
64
+ font-weight: 600;
65
+ transition: 0.2s background-color;
66
+ }
67
+ button.bordered {
68
+ border: 1px solid var(--border);
69
+ border-top: none;
70
+ border-bottom: none;
71
+ padding: 0 15px;
72
+ }
73
+ button:hover {
74
+ background-color: var(--hover);
75
+ }
76
+
77
+ .switching {
78
+ opacity: 0.4;
79
+ animation: switching-pulse 1.5s infinite;
80
+ }
81
+ @keyframes switching-pulse {
82
+ 0%,
83
+ 100% {
84
+ opacity: 0.4;
85
+ }
86
+ 50% {
87
+ opacity: 0.8;
88
+ }
89
+ }
90
+ </style>
@@ -0,0 +1,11 @@
1
+ import { type BarOrganization } from '../bar.js';
2
+ import type { DropdownAlign } from '../../Dropdown/dropdown.types.js';
3
+ interface Props {
4
+ show?: boolean;
5
+ onSwitch?: (org: BarOrganization) => void;
6
+ style?: 'bordered' | 'plain';
7
+ dropdownAlign?: DropdownAlign;
8
+ }
9
+ declare const OrganizationButton: import("svelte").Component<Props, {}, "show">;
10
+ type OrganizationButton = ReturnType<typeof OrganizationButton>;
11
+ export default OrganizationButton;
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ import IconMessage from '../../IconMessage/IconMessage.svelte';
3
+ import Loader from '../../Loader/Loader.svelte';
4
+ import { onMount } from 'svelte';
5
+ import { barOrganizations, getMyOrganizations, type BarOrganization } from '../bar.js';
6
+ import ActionList from '../../ActionList/ActionList.svelte';
7
+ import ActionListItem from '../../ActionList/ActionListItem.svelte';
8
+
9
+ let loading = $state(true);
10
+ let error = $state('');
11
+
12
+ let {
13
+ onSwitch
14
+ }: {
15
+ onSwitch: (org: BarOrganization) => void;
16
+ } = $props();
17
+
18
+ onMount(() => {
19
+ if ($barOrganizations.length > 0) {
20
+ loading = false;
21
+ return;
22
+ }
23
+ getMyOrganizations()
24
+ .then((data) => {
25
+ barOrganizations.set(data);
26
+ })
27
+ .catch((err) => {
28
+ error = 'Failed to load organizations.';
29
+ })
30
+ .finally(() => {
31
+ loading = false;
32
+ });
33
+ });
34
+ </script>
35
+
36
+ {#if loading}
37
+ <Loader padding={40} block size="small" />
38
+ {:else if error}
39
+ <IconMessage error iconSize={30} padding={40}>
40
+ {error}
41
+ </IconMessage>
42
+ {:else}
43
+ <div class="list-wrap">
44
+ <ActionList>
45
+ {#each $barOrganizations as org}
46
+ <ActionListItem on:select={() => onSwitch(org)}>
47
+ {org.name}
48
+ {#snippet end()}
49
+ <span class="role">
50
+ {org.role.toUpperCase()}
51
+ </span>
52
+ {/snippet}
53
+ </ActionListItem>
54
+ {/each}
55
+ </ActionList>
56
+ </div>
57
+ {/if}
58
+
59
+ <style>
60
+ .role {
61
+ font-size: 10px;
62
+ color: var(--text-light);
63
+ font-weight: 600;
64
+ }
65
+ .list-wrap {
66
+ max-height: 300px;
67
+ overflow-y: auto;
68
+ padding: 10px;
69
+ }
70
+ </style>
@@ -0,0 +1,7 @@
1
+ import { type BarOrganization } from '../bar.js';
2
+ type $$ComponentProps = {
3
+ onSwitch: (org: BarOrganization) => void;
4
+ };
5
+ declare const OrgsList: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type OrgsList = ReturnType<typeof OrgsList>;
7
+ export default OrgsList;
@@ -10,6 +10,12 @@ export interface BarUser {
10
10
  username?: string | null;
11
11
  email: string;
12
12
  picture_url: string | null;
13
+ current_organization_name: string;
14
+ }
15
+ export interface BarOrganization {
16
+ id: number;
17
+ name: string;
18
+ role: 'admin' | 'billing' | 'manager' | 'member';
13
19
  }
14
20
  export interface BarUpdate {
15
21
  id: number;
@@ -19,7 +25,7 @@ export interface BarUpdate {
19
25
  content: string;
20
26
  url?: string;
21
27
  }
22
- export type BarUpdateType = 'company' | 'core' | 'talk' | 'blogs' | 'post' | 'relay' | 'fortguard';
28
+ export type BarUpdateType = 'company' | 'core' | 'talk' | 'blogs' | 'fortguard';
23
29
  export interface BarResolvedLicense {
24
30
  type: 'subscription' | 'trial' | 'custom' | 'expired';
25
31
  license: Record<string, number | boolean> | null;
@@ -33,8 +39,15 @@ export declare const barUser: import("svelte/store").Writable<BarUser | null>;
33
39
  export declare const barUnreadUpdates: import("svelte/store").Writable<number>;
34
40
  export declare const barLicense: import("svelte/store").Writable<BarResolvedLicense | null>;
35
41
  export declare const barHasFailedInvoices: import("svelte/store").Writable<boolean>;
42
+ export declare const barOrganizationDropdownOpen: import("svelte/store").Writable<boolean>;
43
+ export declare const barOrganizations: import("svelte/store").Writable<BarOrganization[]>;
36
44
  export declare function setInstanceAndProduct(instance_: string, product_: string): void;
37
- export declare function initBar(): void;
45
+ /**
46
+ * @throws Error if initialization fails
47
+ */
48
+ export declare function initBar(): Promise<void>;
49
+ export declare function getMyOrganizations(): Promise<BarOrganization[]>;
50
+ export declare function switchOrganization(org: BarOrganization): Promise<void>;
38
51
  export declare class UnreadUpdatesTimeLocalStorage {
39
52
  static KEY: string;
40
53
  static get(): number | null;
@@ -48,4 +61,8 @@ export declare const bar: {
48
61
  * This is useful to create after, for example, a user creates a new blog
49
62
  */
50
63
  reload: () => void;
64
+ /**
65
+ * Open the org selector dropdown from the outside world
66
+ */
67
+ openOrganizationDropdown: () => void;
51
68
  };
@@ -6,39 +6,69 @@ export const barUser = writable(null);
6
6
  export const barUnreadUpdates = writable(0);
7
7
  export const barLicense = writable(null);
8
8
  export const barHasFailedInvoices = writable(false);
9
+ export const barOrganizationDropdownOpen = writable(false);
10
+ export const barOrganizations = writable([]);
9
11
  export function setInstanceAndProduct(instance_, product_) {
10
12
  instance = instance_;
11
13
  product = product_;
12
14
  }
13
- export function initBar() {
15
+ /**
16
+ * @throws Error if initialization fails
17
+ */
18
+ export async function initBar() {
14
19
  const query = new URLSearchParams();
15
20
  query.set('product', product);
16
21
  const lastUnreadTime = UnreadUpdatesTimeLocalStorage.get();
17
22
  if (lastUnreadTime) {
18
23
  query.set('last_read_updates_at', lastUnreadTime.toString());
19
24
  }
20
- fetch(instance + '/api/public/bar?' + query.toString(), {
25
+ const response = await fetch(instance + '/api/public/bar?' + query.toString(), {
21
26
  credentials: 'include'
22
- })
23
- .then((response) => response.json())
24
- .then((data) => {
25
- barUser.set(data.user);
26
- barUnreadUpdates.set(data.updates.unread);
27
- barLicense.set(data.billing.license);
28
- barHasFailedInvoices.set(data.billing.has_failed_invoices);
29
- if (lastUnreadTime === null) {
30
- UnreadUpdatesTimeLocalStorage.setNow();
31
- }
32
- if (data.user && track.ready()) {
33
- track.identify(data.user.id.toString(), {
34
- name: data.user.name ?? undefined,
35
- avatar: data.user.picture_url ?? undefined
36
- });
37
- }
38
- })
39
- .catch((error) => {
40
- console.error('Error:', error);
41
27
  });
28
+ if (!response.ok) {
29
+ throw new Error('Failed to initialize bar');
30
+ }
31
+ const data = await response.json();
32
+ barUser.set(data.user);
33
+ barUnreadUpdates.set(data.updates.unread);
34
+ barLicense.set(data.billing.license);
35
+ barHasFailedInvoices.set(data.billing.has_failed_invoices);
36
+ if (lastUnreadTime === null) {
37
+ UnreadUpdatesTimeLocalStorage.setNow();
38
+ }
39
+ if (data.user && track.ready()) {
40
+ track.identify(data.user.id.toString(), {
41
+ name: data.user.name ?? undefined,
42
+ avatar: data.user.picture_url ?? undefined,
43
+ });
44
+ }
45
+ }
46
+ export async function getMyOrganizations() {
47
+ /* return [
48
+ { id: 1, name: 'Org 1', role: 'admin' },
49
+ { id: 2, name: 'Org 2', role: 'member' },
50
+ { id: 3, name: 'Org 3', role: 'billing' },
51
+ { id: 4, name: 'Org 4', role: 'manager' },
52
+ ] */
53
+ const response = await fetch(instance + '/api/public/bar/myorgs', {
54
+ credentials: 'include'
55
+ });
56
+ const data = await response.json();
57
+ return data.organizations;
58
+ }
59
+ export async function switchOrganization(org) {
60
+ const response = await fetch(instance + '/api/public/bar/switch-org', {
61
+ method: 'POST',
62
+ credentials: 'include',
63
+ headers: {
64
+ 'Content-Type': 'application/json'
65
+ },
66
+ body: JSON.stringify({ organization_id: org.id })
67
+ });
68
+ if (!response.ok) {
69
+ throw new Error('Failed to switch organization');
70
+ }
71
+ await initBar();
42
72
  }
43
73
  export class UnreadUpdatesTimeLocalStorage {
44
74
  static KEY = 'unread_updates';
@@ -98,5 +128,9 @@ export const bar = {
98
128
  */
99
129
  reload: () => {
100
130
  initBar();
101
- }
131
+ },
132
+ /**
133
+ * Open the org selector dropdown from the outside world
134
+ */
135
+ openOrganizationDropdown: () => barOrganizationDropdownOpen.set(true)
102
136
  };
@@ -26,7 +26,7 @@ export { default as FormControl } from './FormControl/FormControl.svelte';
26
26
  export { default as InputGroup } from './FormControl/InputGroup.svelte';
27
27
  export { default as Label } from './FormControl/Label.svelte';
28
28
  export { default as Validation } from './FormControl/Validation.svelte';
29
- export { uploadFile, type FileUploaderConfig, type UploadedFile as FileUploaderUploadedFile, type SelectedFile as FileUploaderSelectedFile, } from './FileUploader/file-uploader.js';
29
+ export { uploadFile, type FileUploaderConfig, type UploadedFile as FileUploaderUploadedFile, type SelectedFile as FileUploaderSelectedFile } from './FileUploader/file-uploader.js';
30
30
  export { default as HyvorBar } from './HyvorBar/HyvorBar.svelte';
31
31
  export { bar as hyvorBar } from './HyvorBar/bar.js';
32
32
  export { default as IconButton } from './IconButton/IconButton.svelte';
@@ -36,6 +36,7 @@ export { default as LoadButton } from './Loader/LoadButton.svelte';
36
36
  export { default as Modal } from './Modal/Modal.svelte';
37
37
  export { confirm } from './Modal/confirm.js';
38
38
  export { default as NavLink } from './NavLink/NavLink.svelte';
39
+ export { default as OrganizationButton } from './HyvorBar/Organization/OrganizationButton.svelte';
39
40
  export { default as Radio } from './Radio/Radio.svelte';
40
41
  export { default as Slider } from './Slider/Slider.svelte';
41
42
  export { default as SplitControl } from './SplitControl/SplitControl.svelte';
@@ -26,7 +26,7 @@ export { default as FormControl } from './FormControl/FormControl.svelte';
26
26
  export { default as InputGroup } from './FormControl/InputGroup.svelte';
27
27
  export { default as Label } from './FormControl/Label.svelte';
28
28
  export { default as Validation } from './FormControl/Validation.svelte';
29
- export { uploadFile, } from './FileUploader/file-uploader.js';
29
+ export { uploadFile } from './FileUploader/file-uploader.js';
30
30
  export { default as HyvorBar } from './HyvorBar/HyvorBar.svelte';
31
31
  export { bar as hyvorBar } from './HyvorBar/bar.js';
32
32
  export { default as IconButton } from './IconButton/IconButton.svelte';
@@ -36,6 +36,7 @@ export { default as LoadButton } from './Loader/LoadButton.svelte';
36
36
  export { default as Modal } from './Modal/Modal.svelte';
37
37
  export { confirm } from './Modal/confirm.js';
38
38
  export { default as NavLink } from './NavLink/NavLink.svelte';
39
+ export { default as OrganizationButton } from './HyvorBar/Organization/OrganizationButton.svelte';
39
40
  export { default as Radio } from './Radio/Radio.svelte';
40
41
  export { default as Slider } from './Slider/Slider.svelte';
41
42
  export { default as SplitControl } from './SplitControl/SplitControl.svelte';
@@ -4,46 +4,35 @@
4
4
  import IconButton from '../../components/IconButton/IconButton.svelte';
5
5
  import Dropdown from '../../components/Dropdown/Dropdown.svelte';
6
6
  import IconList from '@hyvor/icons/IconList';
7
- import HeaderNotification from './HeaderNotification.svelte';
8
7
 
9
8
  interface Props {
10
- product: string;
11
- instance?: string;
12
- logo?: string;
9
+ logo: string;
13
10
  name?: string;
14
11
  href?: string;
15
12
  subName?: undefined | string;
16
13
  darkToggle?: boolean;
17
14
  center?: import('svelte').Snippet;
18
15
  end?: import('svelte').Snippet;
19
- max?: boolean;
16
+ max?: boolean;
20
17
  }
21
18
 
22
19
  let {
23
- product,
24
20
  logo,
25
- instance = 'https://hyvor.com',
26
21
  name = 'HYVOR',
27
22
  href = '/',
28
23
  subName = undefined,
29
24
  darkToggle = true,
30
25
  center,
31
26
  end,
32
- max = false
27
+ max = false,
33
28
  }: Props = $props();
34
29
  </script>
35
30
 
36
31
  <header>
37
- <HeaderNotification {instance} {product} />
38
32
  <Container as="nav" {max}>
39
33
  <div class="nav-start">
40
34
  <a class="nav-brand" {href}>
41
- <img
42
- src={logo || `${instance}/api/public/logo/${product}.svg`}
43
- alt="{name + (subName ? ' ' + subName : '')} Logo"
44
- width="30"
45
- height="30"
46
- />
35
+ <img src={logo} alt="Hyvor Logo" width="30" height="30" />
47
36
  <span class="brand-product">
48
37
  <span class="brand">{name}</span>
49
38
  {#if subName}
@@ -67,6 +56,25 @@
67
56
  <DarkToggle />
68
57
  </span>
69
58
  {/if}
59
+
60
+ <!-- <span class="mobile-nav-wrap">
61
+ <Dropdown align="end" width={300}>
62
+ <IconButton
63
+ color="invisible"
64
+ slot="trigger"
65
+ >
66
+ <IconList size={18} />
67
+ </IconButton>
68
+ <div slot="content" class="mobile-content">
69
+ <div class="mobile-inner center">
70
+ <slot name="center" />
71
+ </div>
72
+ <div class="mobile-inner end">
73
+ <slot name="end" />
74
+ </div>
75
+ </div>
76
+ </Dropdown>
77
+ </span> -->
70
78
  </div>
71
79
 
72
80
  <span class="mobile-nav-wrap">
@@ -93,119 +101,120 @@
93
101
 
94
102
  <div class="header-space"></div>
95
103
 
96
- <style>
97
- .header-space {
98
- height: var(--header-height);
99
- }
100
-
101
- header {
102
- position: fixed;
103
- top: 0;
104
- left: 0;
105
- width: 100%;
106
- z-index: 100;
107
- background-color: var(--background, var(--accent-lightest));
108
- border-bottom: 1px solid var(--border);
109
- height: var(--header-height);
110
- display: flex;
111
- flex-direction: column;
112
- }
113
-
114
- header :global(> nav) {
115
- display: flex;
116
- align-items: center;
117
- flex: 1;
118
- }
119
- .nav-brand {
120
- display: inline-block;
121
- line-height: inherit;
122
- white-space: nowrap;
123
- color: inherit;
124
- text-decoration: none;
125
- font-weight: 600;
126
- }
127
- .nav-brand img {
128
- vertical-align: middle;
129
- }
130
-
131
- .brand-product {
132
- vertical-align: middle;
133
- display: inline-flex;
134
- flex-direction: column;
135
- justify-content: center;
136
- line-height: 14px;
137
- }
138
- .brand-product .brand {
139
- font-size: 14px;
140
- }
141
- .brand-product .product {
142
- font-size: 12px;
143
- font-weight: normal;
144
- color: var(--text-light);
145
- }
146
-
147
- .nav-center {
148
- flex: 1;
149
- display: flex;
150
- align-items: center;
151
- gap: 6px;
152
- justify-content: center;
153
- }
154
-
155
- .nav-end {
156
- display: flex;
157
- align-items: center;
158
- gap: 6px;
159
- }
160
-
161
- .mobile-nav-wrap {
162
- display: none;
163
- }
164
-
165
- .dark-mobile {
166
- display: inline-flex;
167
- align-items: center;
168
- }
169
-
170
- .dark-toggle-wrap {
171
- margin-left: 8px;
172
- }
173
-
174
- @media screen and (max-width: 992px) {
175
- .nav-center {
176
- display: none;
177
- }
178
- .nav-end {
179
- display: none;
180
- }
181
- .mobile-nav-wrap {
182
- display: inline-block;
183
- }
184
- .dark-mobile {
185
- flex: 1;
186
- text-align: right;
187
- display: inline-block;
188
- }
189
- }
190
-
191
- .mobile-content,
192
- .mobile-inner {
193
- display: flex;
194
- flex-direction: column;
195
- }
196
- .mobile-content {
197
- gap: 10px;
198
- }
199
-
200
- .mobile-content :global(.button) {
201
- display: flex;
202
- }
203
-
204
- /*
205
- Scroll padding top is used to prevent the content from being hidden behind the header
206
- https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-padding-top
207
- */
208
- :global(html) {
209
- scroll-padding-top: calc(var(--header-height) + 20px);
210
- }
211
- </style>
104
+ <style>.header-space {
105
+ height: var(--header-height);
106
+ }
107
+
108
+ header {
109
+ position: fixed;
110
+ top: 0;
111
+ left: 0;
112
+ width: 100%;
113
+ z-index: 100;
114
+ background-color: var(--background, var(--accent-lightest));
115
+ border-bottom: 1px solid var(--border);
116
+ height: var(--header-height);
117
+ display: flex;
118
+ align-items: center;
119
+ }
120
+
121
+ header :global(nav) {
122
+ display: flex;
123
+ align-items: center;
124
+ }
125
+
126
+ .nav-brand {
127
+ display: inline-block;
128
+ line-height: inherit;
129
+ white-space: nowrap;
130
+ color: inherit;
131
+ text-decoration: none;
132
+ font-weight: 600;
133
+ }
134
+
135
+ .nav-brand img {
136
+ vertical-align: middle;
137
+ }
138
+
139
+ .brand-product {
140
+ vertical-align: middle;
141
+ display: inline-flex;
142
+ flex-direction: column;
143
+ justify-content: center;
144
+ line-height: 14px;
145
+ }
146
+
147
+ .brand-product .brand {
148
+ font-size: 14px;
149
+ }
150
+
151
+ .brand-product .product {
152
+ font-size: 12px;
153
+ font-weight: normal;
154
+ color: var(--text-light);
155
+ }
156
+
157
+ .nav-center {
158
+ flex: 1;
159
+ display: flex;
160
+ align-items: center;
161
+ gap: 6px;
162
+ justify-content: center;
163
+ }
164
+
165
+ .nav-end {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 6px;
169
+ }
170
+
171
+ .mobile-nav-wrap {
172
+ display: none;
173
+ }
174
+
175
+ .dark-mobile {
176
+ display: inline-flex;
177
+ align-items: center;
178
+ }
179
+
180
+ .dark-toggle-wrap {
181
+ margin-left: 8px;
182
+ }
183
+
184
+ @media screen and (max-width: 992px) {
185
+ .nav-center {
186
+ display: none;
187
+ }
188
+ .nav-end {
189
+ display: none;
190
+ }
191
+ .mobile-nav-wrap {
192
+ display: inline-block;
193
+ }
194
+ .dark-mobile {
195
+ flex: 1;
196
+ text-align: right;
197
+ display: inline-block;
198
+ }
199
+ }
200
+ .mobile-content,
201
+ .mobile-inner {
202
+ display: flex;
203
+ flex-direction: column;
204
+ }
205
+
206
+ .mobile-content {
207
+ gap: 10px;
208
+ }
209
+
210
+ .mobile-content :global(.button) {
211
+ display: flex;
212
+ }
213
+
214
+ /*
215
+ Scroll padding top is used to prevent the content from being hidden behind the header
216
+ https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-padding-top
217
+ */
218
+ :global(html) {
219
+ scroll-padding-top: calc(var(--header-height) + 20px);
220
+ }</style>
@@ -1,7 +1,5 @@
1
1
  interface Props {
2
- product: string;
3
- instance?: string;
4
- logo?: string;
2
+ logo: string;
5
3
  name?: string;
6
4
  href?: string;
7
5
  subName?: undefined | string;
@@ -6,6 +6,7 @@ interface InitOptions {
6
6
  }
7
7
  declare class Track {
8
8
  private op;
9
+ private isProductionDomain;
9
10
  init({ forceTrack, openPanelApiUrl, openPanelClientId, context }?: InitOptions): void;
10
11
  ready(): boolean;
11
12
  private warnNoOp;
@@ -1,9 +1,12 @@
1
1
  import { OpenPanel } from '@openpanel/web';
2
- import { isCloud } from '../cloud.js';
3
2
  class Track {
4
3
  op;
4
+ isProductionDomain() {
5
+ const hostname = window.location.hostname;
6
+ return hostname === 'hyvor.com' || hostname.endsWith('.hyvor.com');
7
+ }
5
8
  init({ forceTrack = false, openPanelApiUrl = 'https://op.hyvor.com/api', openPanelClientId = 'b11f6143-a6b0-4fa4-a86c-3969c01dbb1d', context = {} } = {}) {
6
- if (!forceTrack && !isCloud()) {
9
+ if (!forceTrack && !this.isProductionDomain()) {
7
10
  console.log('Tracking is disabled in non-production domains.');
8
11
  return;
9
12
  }
@@ -12,7 +15,7 @@ class Track {
12
15
  clientId: openPanelClientId,
13
16
  trackScreenViews: true,
14
17
  trackOutgoingLinks: true,
15
- trackAttributes: true
18
+ trackAttributes: true,
16
19
  });
17
20
  if (Object.keys(context).length > 0) {
18
21
  this.op.setGlobalProperties(context);
package/package.json CHANGED
@@ -62,5 +62,5 @@
62
62
  "publishConfig": {
63
63
  "access": "public"
64
64
  },
65
- "version": "1.1.24-beta.4"
65
+ "version": "1.1.25-beta-org-1"
66
66
  }
@@ -1,64 +0,0 @@
1
- <script lang="ts">
2
- import type { BarUpdate } from '../../components/HyvorBar/bar.js';
3
- import { onMount } from 'svelte';
4
- import { slide } from 'svelte/transition';
5
- import { isCloud } from '../cloud.js';
6
- import IconMegaphone from '@hyvor/icons/IconMegaphone';
7
-
8
- let notificationUpdate: BarUpdate | null = $state(null);
9
-
10
- interface Props {
11
- instance: string;
12
- product: string;
13
- }
14
-
15
- let { instance, product }: Props = $props();
16
-
17
- function set(u: BarUpdate) {
18
- notificationUpdate = u;
19
- document.documentElement.style.setProperty('--header-height', '85px');
20
- }
21
-
22
- onMount(() => {
23
- if (!isCloud(false)) return;
24
-
25
- fetch(instance + '/api/public/updates/notification?type=' + product)
26
- .then((response) => response.json())
27
- .then((data) => {
28
- if (data.update) {
29
- set(data.update);
30
- }
31
- });
32
- });
33
- </script>
34
-
35
- {#if notificationUpdate}
36
- <a
37
- class="header-notification"
38
- href={notificationUpdate.url}
39
- target="_blank"
40
- transition:slide={{ duration: 300 }}
41
- >
42
- <IconMegaphone size={12} />&nbsp;&nbsp;
43
- {notificationUpdate.title}&nbsp;&nbsp;&rarr;
44
- </a>
45
- {/if}
46
-
47
- <style>
48
- .header-notification {
49
- display: block;
50
- background-color: var(--accent);
51
- color: var(--accent-text);
52
- height: 30px;
53
- display: flex;
54
- align-items: center;
55
- justify-content: center;
56
- font-size: 14px;
57
- cursor: pointer;
58
- font-weight: 600;
59
- transition: opacity 0.2s;
60
- }
61
- .header-notification:hover {
62
- opacity: 0.8;
63
- }
64
- </style>
@@ -1,7 +0,0 @@
1
- interface Props {
2
- instance: string;
3
- product: string;
4
- }
5
- declare const HeaderNotification: import("svelte").Component<Props, {}, "">;
6
- type HeaderNotification = ReturnType<typeof HeaderNotification>;
7
- export default HeaderNotification;
@@ -1 +0,0 @@
1
- export declare function isCloud(forceProd?: boolean): boolean;
@@ -1,15 +0,0 @@
1
- export function isCloud(forceProd = true) {
2
- if (typeof window === 'undefined') {
3
- return false;
4
- }
5
- const hostname = window.location.hostname;
6
- function isDomain(domain) {
7
- return hostname === domain || hostname.endsWith(`.${domain}`);
8
- }
9
- let domains = ['hyvor.com'];
10
- if (forceProd === false) {
11
- domains.push('hyvor.localhost');
12
- domains.push('hyvorstaging.com');
13
- }
14
- return domains.some(isDomain);
15
- }