Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
f849c33
feat: add init code
paanSinghCoder Jul 24, 2025
e1eb6b5
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Jul 24, 2025
cc2d024
feat: add list items
paanSinghCoder Jul 24, 2025
61d081f
style: add list styling
paanSinghCoder Jul 24, 2025
7a01885
chore: minor changes
paanSinghCoder Jul 24, 2025
25d8e05
chore: add data-test-id
paanSinghCoder Jul 24, 2025
adc7f41
chore: use IconButton instead of Image
paanSinghCoder Jul 24, 2025
542ae9c
chore: import sort
paanSinghCoder Jul 24, 2025
bd71f02
chore: props
paanSinghCoder Jul 25, 2025
6522eb3
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Aug 18, 2025
af26d99
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Sep 3, 2025
6a6b6e4
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Sep 18, 2025
71b8edf
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Sep 26, 2025
1dd998f
feat: add api
paanSinghCoder Sep 26, 2025
ecd3cb9
fix: container scrollable
paanSinghCoder Sep 29, 2025
6df4fe9
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Sep 29, 2025
86b7fcb
chore: revert swagger files
paanSinghCoder Sep 29, 2025
81bc445
feat: add ping user session api
paanSinghCoder Sep 29, 2025
6023313
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Sep 29, 2025
95c292f
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Sep 30, 2025
03aae9a
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Oct 1, 2025
fabc34e
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Oct 3, 2025
254fdf0
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Oct 6, 2025
fc9c19b
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Oct 6, 2025
8e4f4ce
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Oct 6, 2025
2474528
feat: handle time and formatting
paanSinghCoder Oct 8, 2025
5f6da69
style: fix styling
paanSinghCoder Oct 8, 2025
e468c59
fix: refetch and css
paanSinghCoder Oct 8, 2025
474013a
chore: keep current session first
paanSinghCoder Oct 8, 2025
0996b64
chore: rename Revoke for current session
paanSinghCoder Oct 8, 2025
494b277
chore: rename copy
paanSinghCoder Oct 8, 2025
e4286a5
style: title
paanSinghCoder Oct 8, 2025
17357dd
chore: conditional show error message
paanSinghCoder Oct 8, 2025
9929b93
chore: remove console
paanSinghCoder Oct 8, 2025
89cbea0
fix: comments on PR
paanSinghCoder Oct 9, 2025
08f0bc2
fix: remove comment
paanSinghCoder Oct 9, 2025
9b1036b
fix: change useLastActivityTracker invoke strategy
paanSinghCoder Oct 10, 2025
bdbcb17
Apply suggestion from @paanSinghCoder
paanSinghCoder Oct 10, 2025
6922fd8
Apply suggestion from @paanSinghCoder
paanSinghCoder Oct 10, 2025
cf79c95
Apply suggestion from @paanSinghCoder
paanSinghCoder Oct 10, 2025
833dffb
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Oct 10, 2025
4bb8372
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Oct 13, 2025
3e9b5a7
Apply suggestion from @rsbh
paanSinghCoder Oct 13, 2025
8fde07e
Apply suggestion from @rsbh
paanSinghCoder Oct 13, 2025
197a5b8
Apply suggestion from @rsbh
paanSinghCoder Oct 13, 2025
f4b472e
fix: use unknown as type for error
paanSinghCoder Oct 13, 2025
bf73534
chore: use useQuery
paanSinghCoder Oct 13, 2025
1c1a715
fix: use dayjs
paanSinghCoder Oct 13, 2025
47e90e4
fix: pass onLogout via props from client
paanSinghCoder Oct 13, 2025
f809533
Update sdks/js/packages/core/react/components/organization/sessions/s…
paanSinghCoder Oct 14, 2025
bf96584
Update ui/src/pages/users/details/security/sessions/revoke-session-co…
paanSinghCoder Oct 14, 2025
fe0a439
Merge branch 'main' into feat/sessions-page-sdk
paanSinghCoder Oct 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export const OrganizationProfile = ({
showAPIKeys = false,
showPreferences = false,
hideToast = false,
customScreens = []
customScreens = [],
onLogout = () => {}
}: OrganizationProfileProps) => {
const memoryHistory = createMemoryHistory({
initialEntries: [defaultRoute]
Expand All @@ -50,7 +51,8 @@ export const OrganizationProfile = ({
showAPIKeys,
hideToast,
showPreferences,
customRoutes
customRoutes,
onLogout
}
});
return <RouterProvider router={memoryRouter} />;
Expand Down
20 changes: 19 additions & 1 deletion sdks/js/packages/core/react/components/organization/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import ServiceUserPage from './api-keys/service-user';
import { DeleteServiceAccount } from './api-keys/delete';
import { DeleteServiceAccountKey } from './api-keys/service-user/delete';
import ManageServiceUserProjects from './api-keys/service-user/projects';
import { SessionsPage, RevokeSessionConfirm } from './sessions';
export interface CustomScreen {
name: string;
path: string;
Expand All @@ -63,6 +64,7 @@ export interface OrganizationProfileProps {
showPreferences?: boolean;
hideToast?: boolean;
customScreens?: CustomScreen[];
onLogout?: () => void;
}

export interface CustomRoutes {
Expand All @@ -78,7 +80,7 @@ type RouterContext = Pick<
| 'showAPIKeys'
| 'hideToast'
| 'showPreferences'
> & { customRoutes: CustomRoutes };
> & { customRoutes: CustomRoutes; onLogout?: () => void };

export function getCustomRoutes(customScreens: CustomScreen[] = []) {
return (
Expand Down Expand Up @@ -349,6 +351,21 @@ const deleteServiceAccountKeyRoute = createRoute({
component: DeleteServiceAccountKey
});

const sessionsRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/sessions',
component: SessionsPage
});

const revokeSessionRoute = createRoute({
getParentRoute: () => sessionsRoute,
path: '/revoke',
component: RevokeSessionConfirm,
validateSearch: (search: Record<string, unknown>) => ({
sessionId: search.sessionId as string | undefined,
}),
});

interface getRootTreeOptions {
customScreens?: CustomScreen[];
}
Expand All @@ -357,6 +374,7 @@ export function getRootTree({ customScreens = [] }: getRootTreeOptions) {
return rootRoute.addChildren([
indexRoute.addChildren([deleteOrgRoute]),
securityRoute,
sessionsRoute.addChildren([revokeSessionRoute]),
membersRoute.addChildren([inviteMemberRoute, removeMemberRoute]),
teamsRoute.addChildren([addTeamRoute]),
domainsRoute.addChildren([
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { SessionsPage } from './sessions-page';
export { RevokeSessionConfirm } from './revoke-session-confirm';
export { RevokeSessionFinalConfirm } from './revoke-session-final-confirm';
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { useState, useMemo } from 'react';
import { useNavigate, useSearch, useRouteContext } from '@tanstack/react-router';
import {
Button,
Text,
Dialog,
Flex,
List,
Skeleton
} from '@raystack/apsara/v1';
import { useSessions } from '../../../hooks/useSessions';
import { useMutation } from '@connectrpc/connect-query';
import { FrontierServiceQueries } from '@raystack/proton/frontier';
import { RevokeSessionFinalConfirm } from './revoke-session-final-confirm';
import styles from './sessions.module.css';

export const RevokeSessionConfirm = () => {
const navigate = useNavigate({ from: '/sessions/revoke' });
const search = useSearch({ from: '/sessions/revoke' }) as { sessionId?: string };
const { sessions, revokeSession, isRevokingSession } = useSessions();
const { onLogout } = useRouteContext({ from: '__root__' }) as { onLogout?: () => void };
const [isFinalConfirmOpen, setIsFinalConfirmOpen] = useState(false);

const { mutate: logout } = useMutation(FrontierServiceQueries.authLogout, {
onSuccess: () => {
if (onLogout) {
onLogout();
}
},
onError: (error) => {
console.error('Failed to logout:', error);
// Fallback to regular session revocation
if (search.sessionId) {
revokeSession(search.sessionId);
navigate({ to: '/sessions' });
}
},
});

// Find the session data based on sessionId from URL params
const sessionData = useMemo(() => {
if (!search.sessionId || sessions.length === 0) {
return null;
}

const session = sessions.find(s => s.id === search.sessionId);
if (!session) {
console.error('Not found');
return null;
}

return session;
}, [search.sessionId, sessions]);

const handleRevokeClick = () => {
setIsFinalConfirmOpen(true);
};

const handleFinalConfirm = () => {
if (!search.sessionId) return;

if (sessionData?.isCurrent) {
logout({});
return;
}

revokeSession(search.sessionId);
navigate({ to: '/sessions' });
};


return (
<>
{sessionData ? (
<Dialog open={true} onOpenChange={() => navigate({ to: '/sessions' })}>
<Dialog.Content
style={{ padding: 0, maxWidth: '400px', width: '100%' }}
>
<Dialog.Header className={styles.revokeSessionConfirmHeader}>
<Flex justify="between" align="center" width="full">
<Text size="regular">
{sessionData.browser} on {sessionData.operatingSystem}
</Text>
<Dialog.CloseButton data-test-id="frontier-sdk-close-revoke-session-dialog" />
</Flex>
</Dialog.Header>

<Dialog.Body className={styles.revokeSessionConfirmBody}>
<List className={styles.listRoot}>
<List.Item className={styles.listItem}>
<List.Label minWidth="120px">Device</List.Label>
<List.Value>{sessionData.browser} on {sessionData.operatingSystem}</List.Value>
</List.Item>
<List.Item className={styles.listItem}>
<List.Label minWidth="120px">IP Address</List.Label>
<List.Value>{sessionData.ipAddress}</List.Value>
</List.Item>
<List.Item className={styles.listItem}>
<List.Label minWidth="120px">Last Location</List.Label>
<List.Value>{sessionData.location}</List.Value>
</List.Item>
<List.Item className={styles.listItem}>
<List.Label minWidth="120px">Last Active</List.Label>
<List.Value>{sessionData.lastActive}</List.Value>
</List.Item>
</List>
</Dialog.Body>

<Dialog.Footer>
<Flex justify="end" gap={5}>
<Button
variant="outline"
color="neutral"
onClick={() => navigate({ to: '/sessions' })}
data-test-id="frontier-sdk-cancel-revoke-session-dialog"
>
Cancel
</Button>
<Button
variant="solid"
color="danger"
onClick={handleRevokeClick}
data-test-id="frontier-sdk-confirm-revoke-session-dialog"
>
{sessionData?.isCurrent ? 'Logout' : 'Revoke'}
</Button>
</Flex>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
) : (
<Dialog open={true} onOpenChange={() => navigate({ to: '/sessions' })}>
<Dialog.Content style={{ padding: 0, maxWidth: '400px', width: '100%' }}>
<Skeleton
height="20px"
containerStyle={{ padding: '2rem' }}
count={4}
/>
</Dialog.Content>
</Dialog>
)}

<RevokeSessionFinalConfirm
isOpen={isFinalConfirmOpen}
onOpenChange={setIsFinalConfirmOpen}
onConfirm={handleFinalConfirm}
isLoading={isRevokingSession}
isCurrentSession={sessionData?.isCurrent}
/>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
Button,
toast,
Dialog,
Flex,
Text
} from '@raystack/apsara/v1';
import styles from './sessions.module.css';

const getErrorMessage = (error: unknown): string => {
if (error && typeof error === 'object' && 'status' in error && error.status === 500) {
return 'Something went wrong';
}
return error instanceof Error ? error.message : 'Something went wrong';
};

interface RevokeSessionFinalConfirmProps {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
onConfirm: () => void;
isLoading?: boolean;
isCurrentSession?: boolean;
}

export const RevokeSessionFinalConfirm = ({
isOpen,
onOpenChange,
onConfirm,
isLoading = false,
isCurrentSession = false
}: RevokeSessionFinalConfirmProps) => {
const handleConfirm = async () => {
try {
onConfirm();
onOpenChange(false);
} catch (error: any) {
toast.error('Failed to revoke session', {
description: getErrorMessage(error)
});
}
};

return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<Dialog.Content
style={{ padding: 0, maxWidth: '400px', width: '100%' }}
>
<Dialog.Header className={styles.revokeSessionConfirmHeader}>
<Dialog.Title>Revoke</Dialog.Title>
<Dialog.CloseButton data-test-id="frontier-sdk-close-final-revoke-dialog" />
</Dialog.Header>

<Dialog.Body className={styles.revokeSessionFinalConfirmBody}>
<Flex direction="column" gap={4}>
<Text size="small" variant="secondary">
Are you sure you want to revoke this session? This action cannot be undone.
</Text>
</Flex>
</Dialog.Body>

<Dialog.Footer>
<Flex justify="end" gap={5}>
<Button
variant="outline"
color="neutral"
onClick={() => onOpenChange(false)}
data-test-id="frontier-sdk-cancel-final-revoke-dialog"
disabled={isLoading}
>
Cancel
</Button>
<Button
variant="solid"
color="danger"
onClick={handleConfirm}
data-test-id="frontier-sdk-confirm-final-revoke-dialog"
disabled={isLoading}
loading={isLoading}
loaderText={isCurrentSession ? "Signing out..." : "Revoking..."}
>
{isCurrentSession ? 'Logout' : 'Revoke'}
</Button>
</Flex>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
};
Loading
Loading