From 356be6cc0a269754728e0216a8c524af0140d003 Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Wed, 3 Jun 2026 16:20:56 +0530 Subject: [PATCH 1/2] fix: pat errors --- web/apps/admin/package.json | 2 +- web/apps/admin/src/utils/transform-query.ts | 64 +++++++++++++--- web/apps/client-demo/package.json | 2 +- web/pnpm-lock.yaml | 18 ++--- web/sdk/package.json | 2 +- web/sdk/react/utils/transform-query.ts | 64 +++++++++++++--- .../views-new/pat/pat-details-view.module.css | 4 + .../react/views-new/pat/pat-details-view.tsx | 74 ++++++++++++++----- .../projects/project-details-view.tsx | 2 +- web/sdk/utils/transform-query.ts | 71 ++++++++++++++---- 10 files changed, 238 insertions(+), 65 deletions(-) diff --git a/web/apps/admin/package.json b/web/apps/admin/package.json index 8d94ed72c..c14ad262f 100644 --- a/web/apps/admin/package.json +++ b/web/apps/admin/package.json @@ -17,7 +17,7 @@ "@hookform/resolvers": "^3.0.1", "@radix-ui/react-form": "^0.1.8", "@radix-ui/react-icons": "^1.3.0", - "@raystack/apsara": "1.0.0-rc.9", + "@raystack/apsara": "1.0.0-rc.12", "@raystack/frontier": "workspace:^", "@raystack/proton": "0.1.0-859ba765e6cfd44736ddcf42664b742fe7fd916e", "@stitches/react": "^1.2.8", diff --git a/web/apps/admin/src/utils/transform-query.ts b/web/apps/admin/src/utils/transform-query.ts index e701e9c62..b2485f91e 100644 --- a/web/apps/admin/src/utils/transform-query.ts +++ b/web/apps/admin/src/utils/transform-query.ts @@ -6,6 +6,7 @@ import { RQLSortSchema } from '@raystack/proton/frontier'; import { create } from '@bufbuild/protobuf'; +import dayjs from 'dayjs'; // Extract DataTableFilter type from DataTableQuery since it's not exported type DataTableFilter = NonNullable[number]; @@ -39,10 +40,55 @@ function convertFilterValue(value: unknown): RQLFilter['value'] { /** * Transforms a DataTableFilter to RQLFilter */ +/** + * Expands a date filter into RQLFilter(s) anchored to the user's local day. + * The date picker emits local midnight, and `eq` on a timestamp column can + * never match a calendar day — so expand it to a [start of day, next day) + * range. Other operators compare against the local start of day. + */ +function transformDateFilter( + filter: DataTableFilter, + fieldName: string +): RQLFilter[] { + const startOfDay = dayjs(filter.value as Date).startOf('day'); + + if (filter.operator === 'eq') { + return [ + create(RQLFilterSchema, { + name: fieldName, + operator: 'gte', + value: { case: 'stringValue', value: startOfDay.toISOString() } + }), + create(RQLFilterSchema, { + name: fieldName, + operator: 'lt', + value: { + case: 'stringValue', + value: startOfDay.add(1, 'day').toISOString() + } + }) + ]; + } + + return [ + create(RQLFilterSchema, { + name: fieldName, + operator: filter.operator, + value: { case: 'stringValue', value: startOfDay.toISOString() } + }) + ]; +} + function transformFilter( filter: DataTableFilter, fieldNameMapping?: Record -): RQLFilter { +): RQLFilter[] { + const fieldName = fieldNameMapping?.[filter.name] ?? filter.name; + + if (filter.value instanceof Date) { + return transformDateFilter(filter, fieldName); + } + // Priority: typed values > generic value field let value: RQLFilter['value']; @@ -56,13 +102,13 @@ function transformFilter( value = convertFilterValue(filter.value); } - const fieldName = fieldNameMapping?.[filter.name] ?? filter.name; - - return create(RQLFilterSchema, { - name: fieldName, - operator: filter.operator, - value - }); + return [ + create(RQLFilterSchema, { + name: fieldName, + operator: filter.operator, + value + }) + ]; } /** @@ -92,7 +138,7 @@ export function transformDataTableQueryToRQLRequest( // Transform DataTable filters const filters: RQLFilter[] = query.filters?.length - ? query.filters.map(filter => transformFilter(filter, fieldNameMapping)) + ? query.filters.flatMap(filter => transformFilter(filter, fieldNameMapping)) : []; // Build the RQLRequest with snake_case properties diff --git a/web/apps/client-demo/package.json b/web/apps/client-demo/package.json index f9ad2b697..6e75128d0 100644 --- a/web/apps/client-demo/package.json +++ b/web/apps/client-demo/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "@radix-ui/react-icons": "^1.3.0", - "@raystack/apsara": "1.0.0-rc.9", + "@raystack/apsara": "1.0.0-rc.12", "@raystack/frontier": "workspace:^", "react": "^19.2.1", "react-dom": "^19.2.1", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index b7cf73960..38362ac74 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -55,8 +55,8 @@ importers: specifier: ^1.3.0 version: 1.3.2(react@19.2.4) '@raystack/apsara': - specifier: 1.0.0-rc.9 - version: 1.0.0-rc.9(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 1.0.0-rc.12 + version: 1.0.0-rc.12(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@raystack/frontier': specifier: workspace:^ version: link:../../sdk @@ -161,8 +161,8 @@ importers: specifier: ^1.3.0 version: 1.3.2(react@19.2.4) '@raystack/apsara': - specifier: 1.0.0-rc.9 - version: 1.0.0-rc.9(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 1.0.0-rc.12 + version: 1.0.0-rc.12(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@raystack/frontier': specifier: workspace:^ version: link:../../sdk @@ -222,8 +222,8 @@ importers: specifier: ^3.10.0 version: 3.10.0(react-hook-form@7.71.2(react@19.2.4)) '@raystack/apsara-v1': - specifier: npm:@raystack/apsara@1.0.0-rc.9 - version: '@raystack/apsara@1.0.0-rc.9(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)' + specifier: npm:@raystack/apsara@1.0.0-rc.12 + version: '@raystack/apsara@1.0.0-rc.12(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)' '@raystack/proton': specifier: 0.1.0-859ba765e6cfd44736ddcf42664b742fe7fd916e version: 0.1.0-859ba765e6cfd44736ddcf42664b742fe7fd916e(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.21(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -2230,8 +2230,8 @@ packages: '@types/react': optional: true - '@raystack/apsara@1.0.0-rc.9': - resolution: {integrity: sha512-RucfY0H0eoVmP594gYWltvmqUicqNPpiH2VPL4EWWhXIsj69NuZXu3i2lvUK7pvgGpRJmkzIg6AdiEhwdxj0pQ==} + '@raystack/apsara@1.0.0-rc.12': + resolution: {integrity: sha512-WzL7HD8YMyyhs7zM80V3wm/bxxHTmhNYsi563nPddMtASi51KyyeT0G2CLLTl0mw89XppHp9fjeEM5WsHSOE/g==} engines: {node: '>=22'} peerDependencies: '@types/react': ^19 @@ -9535,7 +9535,7 @@ snapshots: transitivePeerDependencies: - '@types/react-dom' - '@raystack/apsara@1.0.0-rc.9(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@raystack/apsara@1.0.0-rc.12(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@base-ui/react': 1.4.1(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@base-ui/utils': 0.2.8(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) diff --git a/web/sdk/package.json b/web/sdk/package.json index 51882564a..6993d7d24 100644 --- a/web/sdk/package.json +++ b/web/sdk/package.json @@ -107,7 +107,7 @@ "@connectrpc/connect-query": "2.1.1", "@connectrpc/connect-web": "2.1.1", "@hookform/resolvers": "^3.10.0", - "@raystack/apsara-v1": "npm:@raystack/apsara@1.0.0-rc.9", + "@raystack/apsara-v1": "npm:@raystack/apsara@1.0.0-rc.12", "@raystack/proton": "0.1.0-859ba765e6cfd44736ddcf42664b742fe7fd916e", "@tanstack/react-query": "^5.90.2", "@tanstack/react-router": "^1.168.3", diff --git a/web/sdk/react/utils/transform-query.ts b/web/sdk/react/utils/transform-query.ts index 99fb51763..55a03a4fb 100644 --- a/web/sdk/react/utils/transform-query.ts +++ b/web/sdk/react/utils/transform-query.ts @@ -6,6 +6,7 @@ import { RQLSortSchema } from '@raystack/proton/frontier'; import { create } from '@bufbuild/protobuf'; +import dayjs from 'dayjs'; // Extract DataTableFilter type from DataTableQuery since it's not exported type DataTableFilter = NonNullable[number]; @@ -42,10 +43,55 @@ function convertFilterValue(value: unknown): RQLFilter['value'] { /** * Transforms a DataTableFilter to RQLFilter */ +/** + * Expands a date filter into RQLFilter(s) anchored to the user's local day. + * The date picker emits local midnight, and `eq` on a timestamp column can + * never match a calendar day — so expand it to a [start of day, next day) + * range. Other operators compare against the local start of day. + */ +function transformDateFilter( + filter: DataTableFilter, + fieldName: string +): RQLFilter[] { + const startOfDay = dayjs(filter.value as Date).startOf('day'); + + if (filter.operator === 'eq') { + return [ + create(RQLFilterSchema, { + name: fieldName, + operator: 'gte', + value: { case: 'stringValue', value: startOfDay.toISOString() } + }), + create(RQLFilterSchema, { + name: fieldName, + operator: 'lt', + value: { + case: 'stringValue', + value: startOfDay.add(1, 'day').toISOString() + } + }) + ]; + } + + return [ + create(RQLFilterSchema, { + name: fieldName, + operator: filter.operator, + value: { case: 'stringValue', value: startOfDay.toISOString() } + }) + ]; +} + function transformFilter( filter: DataTableFilter, fieldNameMapping?: Record -): RQLFilter { +): RQLFilter[] { + const fieldName = fieldNameMapping?.[filter.name] ?? filter.name; + + if (filter.value instanceof Date) { + return transformDateFilter(filter, fieldName); + } + // Priority: typed values > generic value field let value: RQLFilter['value']; @@ -59,13 +105,13 @@ function transformFilter( value = convertFilterValue(filter.value); } - const fieldName = fieldNameMapping?.[filter.name] ?? filter.name; - - return create(RQLFilterSchema, { - name: fieldName, - operator: filter.operator, - value - }); + return [ + create(RQLFilterSchema, { + name: fieldName, + operator: filter.operator, + value + }) + ]; } /** @@ -99,7 +145,7 @@ export function transformDataTableQueryToRQLRequest( // Transform DataTable filters const filters: RQLFilter[] = query.filters?.length - ? query.filters.map(filter => transformFilter(filter, fieldNameMapping)) + ? query.filters.flatMap(filter => transformFilter(filter, fieldNameMapping)) : []; // Build the RQLRequest with snake_case properties diff --git a/web/sdk/react/views-new/pat/pat-details-view.module.css b/web/sdk/react/views-new/pat/pat-details-view.module.css index 4a4427eac..e8609ab58 100644 --- a/web/sdk/react/views-new/pat/pat-details-view.module.css +++ b/web/sdk/react/views-new/pat/pat-details-view.module.css @@ -21,3 +21,7 @@ .callout { width: 100%; } + +.expiredTooltip { + max-width: 220px; +} diff --git a/web/sdk/react/views-new/pat/pat-details-view.tsx b/web/sdk/react/views-new/pat/pat-details-view.tsx index 5f50771f6..a61ea2870 100644 --- a/web/sdk/react/views-new/pat/pat-details-view.tsx +++ b/web/sdk/react/views-new/pat/pat-details-view.tsx @@ -17,7 +17,8 @@ import { Menu, Skeleton, Text, - toastManager + toastManager, + Tooltip } from '@raystack/apsara-v1'; import deleteIcon from '../../assets/delete.svg'; import { useQuery } from '@connectrpc/connect-query'; @@ -52,6 +53,9 @@ import styles from './pat-details-view.module.css'; dayjs.extend(relativeTime); +const EXPIRED_TOOLTIP_MESSAGE = + 'This token has expired. Please regenerate it before proceeding.'; + const updatePATDialogHandle = Dialog.createHandle(); const regenerateDialogHandle = Dialog.createHandle(); const patCreatedDialogHandle = Dialog.createHandle(); @@ -203,14 +207,18 @@ export function PATDetailsView({ return d ? d.format(dateFormat) : ''; }, [pat, dateFormat]); + const isExpired = useMemo(() => { + const expires = timestampToDayjs(pat?.expiresAt); + return expires ? expires.isBefore(dayjs()) : false; + }, [pat]); + const { expiryInfo, currentExpiryValue } = useMemo(() => { const reference = getExpiryReferenceDayjs(pat); const expires = timestampToDayjs(pat?.expiresAt); if (!reference || !expires) return { expiryInfo: '', currentExpiryValue: '' }; - const days = expires.diff(reference, 'day'); return { - expiryInfo: `${expires.format(dateFormat)} (${days} Days)`, + expiryInfo: `${expires.format(dateFormat)} (${expires.fromNow()})`, currentExpiryValue: getExpiryOptionValue(reference, expires) }; }, [pat, dateFormat]); @@ -263,13 +271,27 @@ export function PATDetailsView({ - } - onClick={() => updatePATDialogHandle.open(null)} - data-test-id="frontier-sdk-pat-update-menu-btn" - > - Update - + + }> + } + onClick={() => updatePATDialogHandle.open(null)} + disabled={isExpired} + data-test-id="frontier-sdk-pat-update-menu-btn" + > + Update + + + {isExpired && ( + + {EXPIRED_TOOLTIP_MESSAGE} + + )} + } onClick={() => @@ -313,15 +335,29 @@ export function PATDetailsView({ General - + + }> + + + {isExpired && ( + + {EXPIRED_TOOLTIP_MESSAGE} + + )} + {createdOn && {createdOn}} diff --git a/web/sdk/react/views-new/projects/project-details-view.tsx b/web/sdk/react/views-new/projects/project-details-view.tsx index b80556f88..178d567e1 100644 --- a/web/sdk/react/views-new/projects/project-details-view.tsx +++ b/web/sdk/react/views-new/projects/project-details-view.tsx @@ -421,7 +421,7 @@ export function ProjectDetailsView({ data-test-id="frontier-sdk-remove-member-btn" style={{ color: 'var(--rs-color-foreground-danger-primary)' }} > - Remove from project + Remove ); diff --git a/web/sdk/utils/transform-query.ts b/web/sdk/utils/transform-query.ts index dece22ac9..8c38389ea 100644 --- a/web/sdk/utils/transform-query.ts +++ b/web/sdk/utils/transform-query.ts @@ -1,4 +1,4 @@ -import type { DataTableQuery, DataTableSort } from '@raystack/apsara'; +import type { DataTableQuery, DataTableSort } from '@raystack/apsara-v1'; import type { RQLRequest, RQLFilter, RQLSort } from '@raystack/proton/frontier'; import { RQLRequestSchema, @@ -6,6 +6,7 @@ import { RQLSortSchema } from '@raystack/proton/frontier'; import { create } from '@bufbuild/protobuf'; +import dayjs from 'dayjs'; // Extract DataTableFilter type from DataTableQuery since it's not exported type DataTableFilter = NonNullable[number]; @@ -26,9 +27,7 @@ export interface TransformOptions { /** * Converts a filter value to the appropriate RQLFilter value format */ -function convertFilterValue( - value: string | number | boolean | null | undefined -): RQLFilter['value'] { +function convertFilterValue(value: unknown): RQLFilter['value'] { switch (typeof value) { case 'boolean': return { case: 'boolValue', value }; @@ -37,17 +36,59 @@ function convertFilterValue( case 'string': return { case: 'stringValue', value }; default: - return { case: 'stringValue', value: value ?? '' }; + return { case: 'stringValue', value: value == null ? '' : String(value) }; } } /** - * Transforms a DataTableFilter to RQLFilter + * Expands a date filter into RQLFilter(s) anchored to the user's local day. + * The date picker emits local midnight, and `eq` on a timestamp column can + * never match a calendar day — so expand it to a [start of day, next day) + * range. Other operators compare against the local start of day. */ +function transformDateFilter( + filter: DataTableFilter, + fieldName: string +): RQLFilter[] { + const startOfDay = dayjs(filter.value as Date).startOf('day'); + + if (filter.operator === 'eq') { + return [ + create(RQLFilterSchema, { + name: fieldName, + operator: 'gte', + value: { case: 'stringValue', value: startOfDay.toISOString() } + }), + create(RQLFilterSchema, { + name: fieldName, + operator: 'lt', + value: { + case: 'stringValue', + value: startOfDay.add(1, 'day').toISOString() + } + }) + ]; + } + + return [ + create(RQLFilterSchema, { + name: fieldName, + operator: filter.operator, + value: { case: 'stringValue', value: startOfDay.toISOString() } + }) + ]; +} + function transformFilter( filter: DataTableFilter, fieldNameMapping?: Record -): RQLFilter { +): RQLFilter[] { + const fieldName = fieldNameMapping?.[filter.name] ?? filter.name; + + if (filter.value instanceof Date) { + return transformDateFilter(filter, fieldName); + } + // Priority: typed values > generic value field let value: RQLFilter['value']; @@ -61,13 +102,13 @@ function transformFilter( value = convertFilterValue(filter.value); } - const fieldName = fieldNameMapping?.[filter.name] ?? filter.name; - - return create(RQLFilterSchema, { - name: fieldName, - operator: filter.operator, - value - }); + return [ + create(RQLFilterSchema, { + name: fieldName, + operator: filter.operator, + value + }) + ]; } /** @@ -101,7 +142,7 @@ export function transformDataTableQueryToRQLRequest( // Transform DataTable filters const filters: RQLFilter[] = query.filters?.length - ? query.filters.map(filter => transformFilter(filter, fieldNameMapping)) + ? query.filters.flatMap(filter => transformFilter(filter, fieldNameMapping)) : []; // Build the RQLRequest with snake_case properties From ec1a21faa7efe9e55bc1fb79a328e636a1103de1 Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Wed, 3 Jun 2026 16:29:02 +0530 Subject: [PATCH 2/2] fix: border issues --- web/sdk/react/views-new/general/general-view.module.css | 6 +++++- .../preferences/components/preferences-row.module.css | 6 +++++- web/sdk/react/views-new/profile/profile-view.module.css | 6 +++++- web/sdk/react/views-new/security/security-view.module.css | 2 +- web/sdk/react/views-new/sessions/sessions-view.module.css | 6 +++++- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/web/sdk/react/views-new/general/general-view.module.css b/web/sdk/react/views-new/general/general-view.module.css index 07ee09b27..7a62f2d2d 100644 --- a/web/sdk/react/views-new/general/general-view.module.css +++ b/web/sdk/react/views-new/general/general-view.module.css @@ -1,6 +1,10 @@ .section { padding: var(--rs-space-9) 0; - border-bottom: 1px solid var(--rs-color-border-base-primary); + border-bottom: 0.5px solid var(--rs-color-border-base-primary); +} + +.section:last-child { + border-bottom: none; } .formFields { diff --git a/web/sdk/react/views-new/preferences/components/preferences-row.module.css b/web/sdk/react/views-new/preferences/components/preferences-row.module.css index 78f0feb59..ad4add6ca 100644 --- a/web/sdk/react/views-new/preferences/components/preferences-row.module.css +++ b/web/sdk/react/views-new/preferences/components/preferences-row.module.css @@ -1,6 +1,10 @@ .row { padding: var(--rs-space-7) 0; - border-bottom: 1px dashed var(--rs-color-border-base-primary); + border-bottom: 0.5px solid var(--rs-color-border-base-primary); +} + +.row:last-child { + border-bottom: none; } .content { flex: 1; diff --git a/web/sdk/react/views-new/profile/profile-view.module.css b/web/sdk/react/views-new/profile/profile-view.module.css index 77cebe778..3c0e09ec4 100644 --- a/web/sdk/react/views-new/profile/profile-view.module.css +++ b/web/sdk/react/views-new/profile/profile-view.module.css @@ -1,6 +1,10 @@ .section { padding: var(--rs-space-9) 0; - border-bottom: 1px solid var(--rs-color-border-base-primary); + border-bottom: 0.5px solid var(--rs-color-border-base-primary); +} + +.section:last-child { + border-bottom: none; } .formFields { diff --git a/web/sdk/react/views-new/security/security-view.module.css b/web/sdk/react/views-new/security/security-view.module.css index 349912d4f..56a79a814 100644 --- a/web/sdk/react/views-new/security/security-view.module.css +++ b/web/sdk/react/views-new/security/security-view.module.css @@ -1,6 +1,6 @@ .section { padding: var(--rs-space-9) 0; - border-bottom: 1px solid var(--rs-color-border-base-primary); + border-bottom: 0.5px solid var(--rs-color-border-base-primary); } .section:last-child { diff --git a/web/sdk/react/views-new/sessions/sessions-view.module.css b/web/sdk/react/views-new/sessions/sessions-view.module.css index deff3fbe5..8b31357c5 100644 --- a/web/sdk/react/views-new/sessions/sessions-view.module.css +++ b/web/sdk/react/views-new/sessions/sessions-view.module.css @@ -1,6 +1,10 @@ .sessionRow { padding: var(--rs-space-7) 0; - border-bottom: 1px solid var(--rs-color-border-base-primary); + border-bottom: 0.5px solid var(--rs-color-border-base-primary); +} + +.sessionRow:last-child { + border-bottom: none; } .dot {