@tuturuuu/utils 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.18.0](https://github.com/tutur3u/platform/compare/utils-v0.17.0...utils-v0.18.0) (2026-07-25)
4
+
5
+
6
+ ### Features
7
+
8
+ * **apps:** unify and redesign app catalog ([b679068](https://github.com/tutur3u/platform/commit/b679068339143a83b38610f363bec7f53ddc3706))
9
+ * **forms:** merge satellite migration ([e739f1b](https://github.com/tutur3u/platform/commit/e739f1bead568905458a42373ae24d13cd778907))
10
+ * **forms:** migrate product to satellite app ([51b9392](https://github.com/tutur3u/platform/commit/51b93928f1a12ebd4f4c753595fb33902ebfa66c))
11
+ * **inventory:** secure Square POS event checkout ([532b463](https://github.com/tutur3u/platform/commit/532b46372116ee0bebfd83ba2af762cc9f668c3c))
12
+ * **offline:** own service worker runtime and refresh dependencies ([ae44477](https://github.com/tutur3u/platform/commit/ae44477603c39f0513244514771653287338a89f))
13
+ * **reports:** add periodic reporting automation ([ec7bd5e](https://github.com/tutur3u/platform/commit/ec7bd5e10abb137e217d1dcf143624530276392f))
14
+
15
+ ## [0.17.0](https://github.com/tutur3u/platform/compare/utils-v0.16.0...utils-v0.17.0) (2026-07-21)
16
+
17
+
18
+ ### Features
19
+
20
+ * **infrastructure:** manage internal accounts ([02fd9f3](https://github.com/tutur3u/platform/commit/02fd9f3d1b4edb23c881e2dbb04bf244b36e6ed0))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * **chat:** harden personal Zalo integration ([f1d12c6](https://github.com/tutur3u/platform/commit/f1d12c60fe12ab3b01a1be3f0381573d44a32226))
26
+ * **ci:** keep task helper collection source-only ([7355a05](https://github.com/tutur3u/platform/commit/7355a05385ccd8d1e2d1bcf6ac15ae5297e057ad))
27
+ * **e2e:** run tasks lifecycle in CI ([bed2cd5](https://github.com/tutur3u/platform/commit/bed2cd590572748f65e75fcb15b7e4b714b9034c))
28
+ * **inventory:** harden sales analytics and access ([53968d3](https://github.com/tutur3u/platform/commit/53968d38a0c5425a3c1065ab2f645e30a0145c8f))
29
+ * resolve code quality findings ([63f10b5](https://github.com/tutur3u/platform/commit/63f10b5ec22a4194f48f448ee2b1b088b5da8f08))
30
+ * **security:** remediate code scanning findings ([023db2e](https://github.com/tutur3u/platform/commit/023db2edf4b0557be108a9d772cbc7e2223af947))
31
+
32
+
33
+ ### Performance Improvements
34
+
35
+ * **storefront:** share customer navigation shell ([21cb78d](https://github.com/tutur3u/platform/commit/21cb78db484d1e1fdb9f2469fcac4e3a948bd176))
36
+
3
37
  ## [0.16.0](https://github.com/tutur3u/platform/compare/utils-v0.15.1...utils-v0.16.0) (2026-07-18)
4
38
 
5
39
 
package/biome.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "root": false,
3
- "$schema": "https://biomejs.dev/schemas/2.5.4/schema.json",
3
+ "$schema": "https://biomejs.dev/schemas/2.5.5/schema.json",
4
4
  "extends": "//"
5
5
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tuturuuu/utils",
3
3
  "license": "MIT",
4
- "version": "0.16.0",
4
+ "version": "0.18.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/tutur3u/platform",
@@ -19,25 +19,25 @@
19
19
  "@dnd-kit/core": "^6.3.1",
20
20
  "@noble/ciphers": "^2.2.0",
21
21
  "@noble/hashes": "^2.2.0",
22
- "@tanstack/react-query": "^5.101.2",
23
- "@tiptap/core": "3.27.4",
24
- "@tiptap/react": "3.27.4",
25
- "@tuturuuu/google": "0.0.3",
26
- "@tuturuuu/icons": "0.0.6",
27
- "@tuturuuu/internal-api": "0.19.0",
28
- "@tuturuuu/supabase": "0.4.0",
22
+ "@tanstack/react-query": "^5.101.4",
23
+ "@tiptap/core": "3.28.0",
24
+ "@tiptap/react": "3.28.0",
25
+ "@tuturuuu/google": "0.1.0",
26
+ "@tuturuuu/icons": "0.1.0",
27
+ "@tuturuuu/internal-api": "0.21.0",
28
+ "@tuturuuu/supabase": "0.5.0",
29
29
  "@upstash/ratelimit": "^2.0.8",
30
30
  "@upstash/redis": "^1.38.0",
31
31
  "clsx": "^2.1.1",
32
32
  "date-fns": "^4.4.0",
33
- "dayjs": "^1.11.20",
33
+ "dayjs": "^1.11.21",
34
34
  "diff": "^9.0.0",
35
35
  "effect": "^3.22.0",
36
36
  "moment": "^2.30.1",
37
- "next": "^16.3.0-preview.6",
37
+ "next": "^16.3.0-preview.9",
38
38
  "prosemirror-model": "^1.25.11",
39
- "react": "^19.2.7",
40
- "react-dom": "^19.2.7",
39
+ "react": "^19.2.8",
40
+ "react-dom": "^19.2.8",
41
41
  "tailwind-merge": "^3.6.0",
42
42
  "uuid": "^14.0.1",
43
43
  "y-prosemirror": "^1.3.7",
@@ -45,14 +45,14 @@
45
45
  "zod": "^4.4.3"
46
46
  },
47
47
  "devDependencies": {
48
- "@tuturuuu/types": "0.18.0",
48
+ "@tuturuuu/types": "0.20.0",
49
49
  "@tuturuuu/typescript-config": "0.1.1",
50
50
  "@types/diff": "^8.0.0",
51
51
  "@types/node": "^26.1.1",
52
52
  "@types/react": "^19.2.17",
53
- "@vitejs/plugin-react": "^6.0.3",
53
+ "@vitejs/plugin-react": "^6.0.4",
54
54
  "typescript": "7.0.2",
55
- "vite": "^8.1.4",
55
+ "vite": "^8.1.5",
56
56
  "vitest": "^4.1.10"
57
57
  },
58
58
  "exports": {
@@ -60,6 +60,7 @@
60
60
  "./effect": "./src/effect.ts",
61
61
  "./email/validation": "./src/email/validation.ts",
62
62
  "./hooks/*": "./src/hooks/*.ts",
63
+ "./task-helper/personal-external-staging": "./src/task-helper/personal-external-staging.ts",
63
64
  "./*": "./src/*.ts",
64
65
  "./common/scan": "./src/common/scan.tsx",
65
66
  "./common/metadata": "./src/common/metadata.ts",
@@ -296,6 +296,7 @@ describe('Portless app origin registry', () => {
296
296
  'https://drive.tuturuuu.localhost',
297
297
  'https://external.tuturuuu.localhost',
298
298
  'https://finance.tuturuuu.localhost',
299
+ 'https://forms.tuturuuu.localhost',
299
300
  'https://hive.tuturuuu.localhost',
300
301
  'https://realtime.hive.tuturuuu.localhost',
301
302
  'https://infra.tuturuuu.localhost',
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
  import { describe, expect, it } from 'vitest';
4
+ import { ROOT_WORKSPACE_ID } from '../constants';
4
5
  import { permissions } from '../permissions';
5
6
 
6
7
  describe('workspace permission catalog', () => {
@@ -30,9 +31,39 @@ describe('workspace permission catalog', () => {
30
31
  expect(permissionIds).toContain('admin');
31
32
  expect(permissionIds).not.toContain('view_infrastructure');
32
33
  expect(permissionIds).not.toContain('manage_infrastructure_stress_tests');
34
+ expect(permissionIds).not.toContain('manage_internal_accounts');
33
35
  expect(permissionIds).not.toContain('manage_workspace_secrets');
34
36
  });
35
37
 
38
+ it('exposes internal account management only in the root workspace', () => {
39
+ const rootPermissionIds = permissions({
40
+ wsId: ROOT_WORKSPACE_ID,
41
+ user: null,
42
+ }).map((permission) => permission.id);
43
+
44
+ expect(rootPermissionIds).toContain('manage_internal_accounts');
45
+ });
46
+
47
+ it('keeps internal account permission translations in shared role editors', () => {
48
+ for (const app of ['cms', 'infrastructure', 'inventory', 'web']) {
49
+ for (const locale of ['en', 'vi']) {
50
+ const messages = JSON.parse(
51
+ readFileSync(
52
+ resolve(repoRoot, `apps/${app}/messages/${locale}.json`),
53
+ 'utf8'
54
+ )
55
+ ) as { 'ws-roles': Record<string, string> };
56
+
57
+ expect(
58
+ messages['ws-roles'].manage_internal_accounts?.length
59
+ ).toBeGreaterThan(0);
60
+ expect(
61
+ messages['ws-roles'].manage_internal_accounts_description?.length
62
+ ).toBeGreaterThan(0);
63
+ }
64
+ }
65
+ });
66
+
36
67
  it('exposes the full shared catalog for typed defaults', () => {
37
68
  const permissionIds = permissions({
38
69
  catalog: 'full',
@@ -43,6 +74,7 @@ describe('workspace permission catalog', () => {
43
74
  expect(permissionIds).toContain('admin');
44
75
  expect(permissionIds).toContain('view_infrastructure');
45
76
  expect(permissionIds).toContain('manage_infrastructure_stress_tests');
77
+ expect(permissionIds).toContain('manage_internal_accounts');
46
78
  expect(permissionIds).toContain('manage_external_migrations');
47
79
  expect(permissionIds).toContain('manage_workspace_secrets');
48
80
  expect(new Set(permissionIds).size).toBe(permissionIds.length);
@@ -578,7 +578,7 @@ describe('getPermissions membership type gate', () => {
578
578
  );
579
579
  });
580
580
 
581
- it('returns null when caller membership is GUEST without enabled guest defaults', async () => {
581
+ it('returns a read-only permission context for a guest without defaults', async () => {
582
582
  const membershipQuery = {
583
583
  select: vi.fn(),
584
584
  eq: vi.fn(),
@@ -656,7 +656,14 @@ describe('getPermissions membership type gate', () => {
656
656
  wsId: '11111111-1111-4111-8111-111111111111',
657
657
  });
658
658
 
659
- expect(permissions).toBeNull();
659
+ expect(permissions).toMatchObject({
660
+ membershipType: 'GUEST',
661
+ permissions: [],
662
+ wsId: '11111111-1111-4111-8111-111111111111',
663
+ });
664
+ expect(permissions?.containsPermission('manage_workspace_settings')).toBe(
665
+ false
666
+ );
660
667
  expect(defaultPermissionsQuery.eq).toHaveBeenCalledWith(
661
668
  'member_type',
662
669
  'GUEST'
@@ -183,6 +183,21 @@ describe('createPageMetadata', () => {
183
183
  });
184
184
  });
185
185
 
186
+ it('normalizes long runs of boundary slashes in linear time', () => {
187
+ const boundary = '/'.repeat(20_000);
188
+ const metadata = createPageMetadata({
189
+ baseUrl: 'https://test.tuturuuu.com',
190
+ description: 'Stable metadata path normalization.',
191
+ locale: 'en',
192
+ pathname: `${boundary}products/workflows${boundary}`,
193
+ title: 'Workflow Automation',
194
+ });
195
+
196
+ expect(metadata.alternates?.canonical).toBe(
197
+ 'https://test.tuturuuu.com/products/workflows'
198
+ );
199
+ });
200
+
186
201
  it('supports apps that never expose locale prefixes', () => {
187
202
  const metadata = createPageMetadata({
188
203
  baseUrl: 'https://tools.tuturuuu.com',
@@ -282,8 +297,8 @@ describe('application metadata coverage', () => {
282
297
  'apps/tools/src/app/[locale]/page.tsx',
283
298
  'apps/tools/src/app/[locale]/qr/page.tsx',
284
299
  'apps/tools/src/app/[locale]/random/page.tsx',
285
- 'apps/storefront/src/app/[locale]/[storeSlug]/page.tsx',
286
- 'apps/storefront/src/app/[locale]/[storeSlug]/products/[listingId]/page.tsx',
300
+ 'apps/storefront/src/app/[locale]/[storeSlug]/(shop)/page.tsx',
301
+ 'apps/storefront/src/app/[locale]/[storeSlug]/(shop)/products/[listingId]/page.tsx',
287
302
  'apps/nova/src/app/[locale]/(marketing)/layout.tsx',
288
303
  'apps/nova/src/app/[locale]/(marketing)/learn/layout.tsx',
289
304
  ];
@@ -70,6 +70,16 @@ function getOpenGraphLocale(locale: string) {
70
70
  return locale === 'vi' ? 'vi_VN' : 'en_US';
71
71
  }
72
72
 
73
+ function trimSlashes(value: string) {
74
+ let start = 0;
75
+ let end = value.length;
76
+
77
+ while (start < end && value[start] === '/') start += 1;
78
+ while (end > start && value[end - 1] === '/') end -= 1;
79
+
80
+ return value.slice(start, end);
81
+ }
82
+
73
83
  function getLocalizedUrl(
74
84
  baseUrl: string,
75
85
  locale: string,
@@ -77,7 +87,7 @@ function getLocalizedUrl(
77
87
  localePrefix: NonNullable<PageMetadataConfig['localePrefix']>
78
88
  ) {
79
89
  const normalizedPathname =
80
- pathname === '/' ? '' : `/${pathname.replace(/^\/+|\/+$/g, '')}`;
90
+ pathname === '/' ? '' : `/${trimSlashes(pathname)}`;
81
91
  const localizedPrefix =
82
92
  localePrefix === 'never' ||
83
93
  (localePrefix === 'as-needed' && locale === 'en')
@@ -108,6 +108,10 @@ export const PRODUCTION_INTERNAL_APP_DOMAINS = [
108
108
  name: 'contacts',
109
109
  url: 'https://contacts.tuturuuu.com',
110
110
  },
111
+ {
112
+ name: 'forms',
113
+ url: 'https://forms.tuturuuu.com',
114
+ },
111
115
  {
112
116
  name: 'hive',
113
117
  url: 'https://hive.tuturuuu.com',
@@ -215,6 +219,10 @@ export const PORTLESS_INTERNAL_APP_DOMAINS = [
215
219
  name: 'contacts',
216
220
  url: getTuturuuuPortlessAppOrigin('contacts'),
217
221
  },
222
+ {
223
+ name: 'forms',
224
+ url: getTuturuuuPortlessAppOrigin('forms'),
225
+ },
218
226
  {
219
227
  name: 'hive',
220
228
  url: getTuturuuuPortlessAppOrigin('hive'),
@@ -318,6 +326,10 @@ export const LOCALHOST_INTERNAL_APP_DOMAINS = [
318
326
  name: 'contacts',
319
327
  url: 'http://localhost:7827',
320
328
  },
329
+ {
330
+ name: 'forms',
331
+ url: 'http://localhost:7828',
332
+ },
321
333
  {
322
334
  name: 'hive',
323
335
  url: 'http://localhost:7814',
@@ -25,7 +25,13 @@ const teamWorkspace: LaunchableWorkspace = {
25
25
  describe('launchable apps', () => {
26
26
  it('resolves apps by slug, title, and aliases', () => {
27
27
  expect(getLaunchableApp('calendar')?.title).toBe('Calendar');
28
+ expect(getLaunchableApp('forms')).toMatchObject({
29
+ appRoot: 'apps/forms',
30
+ productionUrl: 'https://forms.tuturuuu.com',
31
+ title: 'Forms',
32
+ });
28
33
  expect(getLaunchableAppByTitle('Tuturuuu')?.slug).toBe('platform');
34
+ expect(getLaunchableAppByTitle('questionnaires')?.slug).toBe('forms');
29
35
  expect(getLaunchableAppByTitle('apps gateway')?.slug).toBe('apps');
30
36
  });
31
37
 
@@ -101,6 +107,28 @@ describe('launchable apps', () => {
101
107
  currentOrigin: 'https://calendar.tuturuuu.localhost',
102
108
  })
103
109
  ).toBe('https://tasks.tuturuuu.localhost');
110
+
111
+ expect(
112
+ getLaunchableAppOrigin(app!, {
113
+ currentOrigin: 'https://tuturuuu.localhost:1355',
114
+ })
115
+ ).toBe('https://tasks.tuturuuu.localhost:1355');
116
+ });
117
+
118
+ it('preserves the Portless proxy port in full app URLs', () => {
119
+ const app = getLaunchableApp('tasks');
120
+
121
+ expect(app).not.toBeNull();
122
+ expect(
123
+ resolveLaunchableAppUrl({
124
+ app: app!,
125
+ currentOrigin: 'https://tuturuuu.localhost:1355',
126
+ searchParams: { source: 'sidebar-apps' },
127
+ workspace: teamWorkspace,
128
+ })
129
+ ).toBe(
130
+ 'https://tasks.tuturuuu.localhost:1355/team-workspace/tasks?source=sidebar-apps'
131
+ );
104
132
  });
105
133
 
106
134
  it('uses default paths when no workspace is provided', () => {
@@ -336,6 +336,19 @@ export const LAUNCHABLE_APPS = [
336
336
  title: 'Contacts',
337
337
  workspacePathResolver: workspaceRootPath,
338
338
  },
339
+ {
340
+ aliases: ['Surveys', 'Questionnaires', 'Responses', 'Form Builder'],
341
+ appRoot: 'apps/forms',
342
+ category: 'operations',
343
+ defaultPath: '/personal',
344
+ localhostOrigin: 'http://localhost:7828',
345
+ packageName: '@tuturuuu/forms',
346
+ portlessApp: 'forms',
347
+ productionUrl: 'https://forms.tuturuuu.com',
348
+ slug: 'forms',
349
+ title: 'Forms',
350
+ workspacePathResolver: workspaceRootPath,
351
+ },
339
352
  {
340
353
  aliases: ['Time Tracking', 'Timer'],
341
354
  appRoot: 'apps/track',
@@ -395,6 +408,32 @@ function getAutoEnvironment(currentOrigin?: string): LaunchableAppEnvironment {
395
408
  return process.env.NODE_ENV === 'production' ? 'production' : 'portless';
396
409
  }
397
410
 
411
+ function preservePortlessProxyPort(
412
+ targetOrigin: string,
413
+ currentOrigin?: string
414
+ ): string {
415
+ if (!currentOrigin) return targetOrigin;
416
+
417
+ try {
418
+ const currentUrl = new URL(currentOrigin);
419
+ const targetUrl = new URL(targetOrigin);
420
+ const isCurrentPortlessHost =
421
+ currentUrl.hostname === 'tuturuuu.localhost' ||
422
+ currentUrl.hostname.endsWith('.tuturuuu.localhost');
423
+ const isTargetPortlessHost =
424
+ targetUrl.hostname === 'tuturuuu.localhost' ||
425
+ targetUrl.hostname.endsWith('.tuturuuu.localhost');
426
+
427
+ if (isCurrentPortlessHost && isTargetPortlessHost && currentUrl.port) {
428
+ targetUrl.port = currentUrl.port;
429
+ }
430
+
431
+ return targetUrl.origin;
432
+ } catch {
433
+ return targetOrigin;
434
+ }
435
+ }
436
+
398
437
  export function getLaunchableAppOrigin(
399
438
  app: LaunchableApp,
400
439
  {
@@ -413,7 +452,10 @@ export function getLaunchableAppOrigin(
413
452
  return app.localhostOrigin ?? getTuturuuuPortlessAppOrigin(app.portlessApp);
414
453
  }
415
454
 
416
- return getTuturuuuPortlessAppOrigin(app.portlessApp);
455
+ return preservePortlessProxyPort(
456
+ getTuturuuuPortlessAppOrigin(app.portlessApp),
457
+ currentOrigin
458
+ );
417
459
  }
418
460
 
419
461
  function trimTrailingSlashes(value: string) {
package/src/money.ts CHANGED
@@ -40,7 +40,7 @@ export function getCurrencyFractionDigits(currency = 'USD'): number {
40
40
  const cached = fractionDigitsCache.get(code);
41
41
  if (cached !== undefined) return cached;
42
42
 
43
- let digits = DEFAULT_FRACTION_DIGITS;
43
+ let digits: number;
44
44
  try {
45
45
  digits =
46
46
  new Intl.NumberFormat(getCurrencyLocale(code), {
@@ -4,6 +4,7 @@ import {
4
4
  createTuturuuuNextConfig,
5
5
  isTuturuuuNextCacheComponentsEnabled,
6
6
  isTuturuuuNextReactCompilerEnabled,
7
+ resolveTuturuuuInfrastructureAppUrl,
7
8
  resolveTuturuuuWebAppUrl,
8
9
  TUTURUUU_NEXT_IMAGE_REMOTE_PATTERNS,
9
10
  TUTURUUU_NEXT_OPTIMIZE_PACKAGE_IMPORTS,
@@ -372,6 +373,46 @@ describe('resolveTuturuuuWebAppUrl', () => {
372
373
  });
373
374
  });
374
375
 
376
+ describe('resolveTuturuuuInfrastructureAppUrl', () => {
377
+ it('trims configured infrastructure origins', () => {
378
+ expect(
379
+ resolveTuturuuuInfrastructureAppUrl({
380
+ env: {
381
+ INTERNAL_INFRASTRUCTURE_API_ORIGIN: 'https://example.com///',
382
+ NODE_ENV: 'development',
383
+ },
384
+ })
385
+ ).toBe('https://example.com');
386
+ });
387
+
388
+ it('uses the local Portless infrastructure app in development', () => {
389
+ expect(
390
+ resolveTuturuuuInfrastructureAppUrl({
391
+ env: { NODE_ENV: 'development' },
392
+ })
393
+ ).toBe('https://infra.tuturuuu.localhost');
394
+ });
395
+
396
+ it('uses the canonical production infrastructure app when deployed', () => {
397
+ expect(
398
+ resolveTuturuuuInfrastructureAppUrl({
399
+ env: { VERCEL_ENV: 'production' },
400
+ })
401
+ ).toBe('https://infrastructure.tuturuuu.com');
402
+ });
403
+
404
+ it('ignores origins registered to another Tuturuuu app', () => {
405
+ expect(
406
+ resolveTuturuuuInfrastructureAppUrl({
407
+ env: {
408
+ INTERNAL_INFRASTRUCTURE_API_ORIGIN: 'https://chat.tuturuuu.com',
409
+ VERCEL_ENV: 'production',
410
+ },
411
+ })
412
+ ).toBe('https://infrastructure.tuturuuu.com');
413
+ });
414
+ });
415
+
375
416
  describe('trimTrailingSlashes', () => {
376
417
  it('removes only trailing slash characters', () => {
377
418
  expect(trimTrailingSlashes('https://example.com/path///')).toBe(
@@ -193,3 +193,31 @@ export function resolveTuturuuuWebAppUrl({
193
193
  (isTuturuuuNextDeployedEnvironment(env) ? productionUrl : localUrl)
194
194
  );
195
195
  }
196
+
197
+ export function resolveTuturuuuInfrastructureAppUrl({
198
+ env = process.env,
199
+ localFallbackUrl,
200
+ productionUrl = 'https://infrastructure.tuturuuu.com',
201
+ }: {
202
+ env?: Environment;
203
+ localFallbackUrl?: string;
204
+ productionUrl?: string;
205
+ } = {}) {
206
+ const localUrl =
207
+ localFallbackUrl ??
208
+ getLocalInternalAppUrl('infra', 'http://localhost:7823');
209
+
210
+ return trimTrailingSlashes(
211
+ resolveInternalAppUrl({
212
+ appName: 'infra',
213
+ candidates: [
214
+ env.INTERNAL_INFRASTRUCTURE_API_ORIGIN,
215
+ env.INFRASTRUCTURE_APP_URL,
216
+ env.NEXT_PUBLIC_INFRASTRUCTURE_APP_URL,
217
+ ],
218
+ fallback: isTuturuuuNextDeployedEnvironment(env)
219
+ ? productionUrl
220
+ : localUrl,
221
+ })
222
+ );
223
+ }
@@ -121,6 +121,16 @@ export const permissionGroups = ({
121
121
  disableOnProduction: false,
122
122
  disabled: false,
123
123
  },
124
+ {
125
+ id: 'manage_internal_accounts' as PermissionId,
126
+ icon: <UserCog />,
127
+ title: t('ws-roles.manage_internal_accounts'),
128
+ description: t(
129
+ 'ws-roles.manage_internal_accounts_description'
130
+ ),
131
+ disableOnProduction: false,
132
+ disabled: false,
133
+ },
124
134
  {
125
135
  id: 'manage_mobile_deployment_vault' as PermissionId,
126
136
  icon: <FileKey2 />,
@@ -709,6 +719,26 @@ export const permissionGroups = ({
709
719
  disableOnProduction: false,
710
720
  disabled: false,
711
721
  },
722
+ {
723
+ id: 'manage_user_report_automation',
724
+ icon: <Settings2 />,
725
+ title: t('ws-roles.manage_user_report_automation'),
726
+ description: t(
727
+ 'ws-roles.manage_user_report_automation_description'
728
+ ),
729
+ disableOnProduction: false,
730
+ disabled: false,
731
+ },
732
+ {
733
+ id: 'send_user_group_report_emails',
734
+ icon: <Send />,
735
+ title: t('ws-roles.send_user_group_report_emails'),
736
+ description: t(
737
+ 'ws-roles.send_user_group_report_emails_description'
738
+ ),
739
+ disableOnProduction: false,
740
+ disabled: false,
741
+ },
712
742
  {
713
743
  id: 'approve_reports',
714
744
  icon: <ShieldCheck />,
@@ -867,6 +897,14 @@ export const permissionGroups = ({
867
897
  disableOnProduction: false,
868
898
  disabled: false,
869
899
  },
900
+ {
901
+ id: 'initiate_pos_checkout',
902
+ icon: <CreditCard />,
903
+ title: t('ws-roles.initiate_pos_checkout'),
904
+ description: t('ws-roles.initiate_pos_checkout_description'),
905
+ disableOnProduction: false,
906
+ disabled: false,
907
+ },
870
908
  {
871
909
  id: 'view_inventory_analytics',
872
910
  icon: <ChartBar />,
@@ -7,7 +7,7 @@ import {
7
7
 
8
8
  describe('platform release metadata', () => {
9
9
  it('uses the centralized shared browser app version', () => {
10
- expect(TUTURUUU_PLATFORM_VERSION).toBe('0.23.0'); // x-release-please-version
10
+ expect(TUTURUUU_PLATFORM_VERSION).toBe('0.26.0'); // x-release-please-version
11
11
  });
12
12
 
13
13
  it('preserves the source timestamp in builtAt and derives a short hash', () => {
@@ -1,6 +1,6 @@
1
1
  import { PLATFORM_BUILD_METADATA } from './generated/platform-build-metadata';
2
2
 
3
- export const TUTURUUU_PLATFORM_VERSION = '0.23.0'; // x-release-please-version
3
+ export const TUTURUUU_PLATFORM_VERSION = '0.26.0'; // x-release-please-version
4
4
 
5
5
  export type PlatformBuildMetadataInput = {
6
6
  /** ISO timestamp of the source commit used for the build, not deployment time. */
package/src/portless.ts CHANGED
@@ -15,6 +15,7 @@ export const TUTURUUU_PORTLESS_APP_ORIGINS = {
15
15
  drive: `https://drive.${TUTURUUU_PORTLESS_ROOT_HOST}`,
16
16
  external: `https://external.${TUTURUUU_PORTLESS_ROOT_HOST}`,
17
17
  finance: `https://finance.${TUTURUUU_PORTLESS_ROOT_HOST}`,
18
+ forms: `https://forms.${TUTURUUU_PORTLESS_ROOT_HOST}`,
18
19
  hive: `https://hive.${TUTURUUU_PORTLESS_ROOT_HOST}`,
19
20
  'hive-realtime': `https://realtime.hive.${TUTURUUU_PORTLESS_ROOT_HOST}`,
20
21
  infra: `https://infra.${TUTURUUU_PORTLESS_ROOT_HOST}`,
@@ -153,9 +153,11 @@ export function useWorkspaceLabels(wsId: string | null | undefined) {
153
153
 
154
154
  export interface BoardConfig {
155
155
  id: string;
156
+ name?: string | null;
156
157
  estimation_type: string | null;
157
158
  extended_estimation: boolean;
158
159
  allow_zero_estimates: boolean;
160
+ count_unestimated_issues?: boolean;
159
161
  ws_id: string;
160
162
  ticket_prefix: string | null;
161
163
  }
@@ -179,9 +181,11 @@ export function useBoardConfig(
179
181
 
180
182
  return {
181
183
  id: board.id,
184
+ name: board.name ?? null,
182
185
  estimation_type: board.estimation_type ?? null,
183
186
  extended_estimation: board.extended_estimation ?? false,
184
187
  allow_zero_estimates: board.allow_zero_estimates ?? false,
188
+ count_unestimated_issues: board.count_unestimated_issues ?? false,
185
189
  ws_id: board.ws_id,
186
190
  ticket_prefix: board.ticket_prefix ?? null,
187
191
  } as BoardConfig;
@@ -828,22 +828,14 @@ async function getPermissionsImpl({
828
828
 
829
829
  const isCreator =
830
830
  membershipType === 'MEMBER' && workspaceData.creator_id === userId;
831
- const hasPermissions =
832
- permissionsData.length > 0 || defaultData.length > 0 || isCreator;
833
-
834
831
  // if (DEV_MODE) {
835
832
  // console.log('--------------------');
836
833
  // console.log('Is creator', isCreator);
837
834
  // console.log('Workspace permissions', permissionsData);
838
835
  // console.log('Default permissions', defaultData);
839
- // console.log('Has permissions', hasPermissions);
840
836
  // console.log('--------------------');
841
837
  // }
842
838
 
843
- if (!isCreator && !hasPermissions) {
844
- return null;
845
- }
846
-
847
839
  const permissions = isCreator
848
840
  ? rolePermissions({
849
841
  wsId: resolvedWorkspaceId,