diff --git a/apps/console/relay.core.json b/apps/console/relay.core.json index cb236f35d..8353e0da6 100644 --- a/apps/console/relay.core.json +++ b/apps/console/relay.core.json @@ -4,5 +4,5 @@ "language": "typescript", "eagerEsModules": true, "noFutureProofEnums": true, - "excludes": ["**/pages/iam/**/*", "**/node_modules/**"] + "excludes": ["**/iam/**/*", "**/node_modules/**", "**/__generated__/**"] } diff --git a/apps/console/relay.iam.json b/apps/console/relay.iam.json index 5483c0c4f..eed3f19a9 100644 --- a/apps/console/relay.iam.json +++ b/apps/console/relay.iam.json @@ -4,5 +4,5 @@ "language": "typescript", "eagerEsModules": true, "noFutureProofEnums": true, - "excludes": ["**/node_modules/**"] + "excludes": ["**/node_modules/**", "**/__generated__/**"] } diff --git a/apps/console/src/fields/iam/__generated__/sessionIsExpiredFragment.graphql.ts b/apps/console/src/fields/iam/__generated__/sessionIsExpiredFragment.graphql.ts new file mode 100644 index 000000000..db071e59f --- /dev/null +++ b/apps/console/src/fields/iam/__generated__/sessionIsExpiredFragment.graphql.ts @@ -0,0 +1,42 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type sessionIsExpiredFragment$data = { + readonly expiresAt: any; + readonly " $fragmentType": "sessionIsExpiredFragment"; +}; +export type sessionIsExpiredFragment$key = { + readonly " $data"?: sessionIsExpiredFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"sessionIsExpiredFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "sessionIsExpiredFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "expiresAt", + "storageKey": null + } + ], + "type": "Session", + "abstractKey": null +}; + +(node as any).hash = "ef7bc8bbfa6d2573b0df09b5b13b00b2"; + +export default node; diff --git a/apps/console/src/pages/iam/organizations/OrganizationsPage.tsx b/apps/console/src/pages/iam/organizations/OrganizationsPage.tsx index 5f503d983..7ef6b83e0 100644 --- a/apps/console/src/pages/iam/organizations/OrganizationsPage.tsx +++ b/apps/console/src/pages/iam/organizations/OrganizationsPage.tsx @@ -8,13 +8,13 @@ import { Input, } from "@probo/ui"; import { useMemo, useState } from "react"; -import { OrganizationCard } from "./_components/OrganizationCard"; import { InvitationCard } from "./_components/InvitationCard"; import type { OrganizationsQuery, OrganizationsQuery$data, } from "./__generated__/OrganizationsQuery.graphql"; import { organizationsQuery } from "./OrganizationsQuery"; +import { MembershipCard } from "./_components/MembershipCard"; interface PageProps { queryRef: PreloadedQuery; @@ -87,8 +87,8 @@ export default function Page(props: PageProps) { {__("No organizations found")} ) : ( - memberships.map(({ node: { id, organization } }) => ( - + memberships.map(({ node }) => ( + )) )} diff --git a/apps/console/src/pages/iam/organizations/OrganizationsQuery.tsx b/apps/console/src/pages/iam/organizations/OrganizationsQuery.tsx index 9a169b6de..68b3208cf 100644 --- a/apps/console/src/pages/iam/organizations/OrganizationsQuery.tsx +++ b/apps/console/src/pages/iam/organizations/OrganizationsQuery.tsx @@ -17,10 +17,9 @@ export const organizationsQuery = graphql` edges { node { id - createdAt + ...MembershipCardFragment organization { name - ...OrganizationCardFragment } } } diff --git a/apps/console/src/pages/iam/organizations/__generated__/OrganizationsQuery.graphql.ts b/apps/console/src/pages/iam/organizations/__generated__/OrganizationsQuery.graphql.ts index 077e84e14..c20ec6b6a 100644 --- a/apps/console/src/pages/iam/organizations/__generated__/OrganizationsQuery.graphql.ts +++ b/apps/console/src/pages/iam/organizations/__generated__/OrganizationsQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<97119b2ea90f368155a13a32941b99d3>> * @lightSyntaxTransform * @nogrep */ @@ -16,12 +16,11 @@ export type OrganizationsQuery$data = { readonly memberships: { readonly edges: ReadonlyArray<{ readonly node: { - readonly createdAt: any; readonly id: string; readonly organization: { readonly name: string; - readonly " $fragmentSpreads": FragmentRefs<"OrganizationCardFragment">; }; + readonly " $fragmentSpreads": FragmentRefs<"MembershipCardFragment">; }; }>; }; @@ -64,13 +63,6 @@ v1 = { "storageKey": null }, v2 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "createdAt", - "storageKey": null -}, -v3 = { "alias": null, "args": null, "kind": "ScalarField", @@ -119,7 +111,11 @@ return { "plural": false, "selections": [ (v1/*: any*/), - (v2/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "MembershipCardFragment" + }, { "alias": null, "args": null, @@ -128,12 +124,7 @@ return { "name": "organization", "plural": false, "selections": [ - (v3/*: any*/), - { - "args": null, - "kind": "FragmentSpread", - "name": "OrganizationCardFragment" - } + (v2/*: any*/) ], "storageKey": null } @@ -233,7 +224,25 @@ return { "plural": false, "selections": [ (v1/*: any*/), - (v2/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Session", + "kind": "LinkedField", + "name": "activeSession", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "expiresAt", + "storageKey": null + } + ], + "storageKey": null + }, { "alias": null, "args": null, @@ -242,8 +251,8 @@ return { "name": "organization", "plural": false, "selections": [ - (v3/*: any*/), (v1/*: any*/), + (v2/*: any*/), { "alias": null, "args": null, @@ -295,7 +304,13 @@ return { "name": "role", "storageKey": null }, - (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, { "alias": null, "args": null, @@ -305,7 +320,7 @@ return { "plural": false, "selections": [ (v1/*: any*/), - (v3/*: any*/) + (v2/*: any*/) ], "storageKey": null } @@ -325,16 +340,16 @@ return { ] }, "params": { - "cacheID": "145dddcd59c5c55a1c61913d9f43b38b", + "cacheID": "5dad37a0c652055f81de5bd2c09995c2", "id": null, "metadata": {}, "name": "OrganizationsQuery", "operationKind": "query", - "text": "query OrganizationsQuery {\n viewer {\n memberships(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n createdAt\n organization {\n name\n ...OrganizationCardFragment\n id\n }\n }\n }\n }\n pendingInvitations(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...InvitationCardFragment\n }\n }\n }\n id\n }\n}\n\nfragment InvitationCardFragment on Invitation {\n id\n role\n createdAt\n organization {\n id\n name\n }\n}\n\nfragment OrganizationCardFragment on Organization {\n id\n name\n logoUrl\n}\n" + "text": "query OrganizationsQuery {\n viewer {\n memberships(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...MembershipCardFragment\n organization {\n name\n id\n }\n }\n }\n }\n pendingInvitations(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...InvitationCardFragment\n }\n }\n }\n id\n }\n}\n\nfragment InvitationCardFragment on Invitation {\n id\n role\n createdAt\n organization {\n id\n name\n }\n}\n\nfragment MembershipCardFragment on Membership {\n activeSession {\n id\n expiresAt\n }\n organization {\n id\n name\n logoUrl\n }\n}\n" } }; })(); -(node as any).hash = "f85219c0fcd69be1b5cfb0a5b9c9b8db"; +(node as any).hash = "dc4b2e28600292c73ea31b5cb450dba7"; export default node; diff --git a/apps/console/src/pages/iam/organizations/__generated__/sessionIsExpiredFragment.graphql.ts b/apps/console/src/pages/iam/organizations/__generated__/sessionIsExpiredFragment.graphql.ts new file mode 100644 index 000000000..db071e59f --- /dev/null +++ b/apps/console/src/pages/iam/organizations/__generated__/sessionIsExpiredFragment.graphql.ts @@ -0,0 +1,42 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type sessionIsExpiredFragment$data = { + readonly expiresAt: any; + readonly " $fragmentType": "sessionIsExpiredFragment"; +}; +export type sessionIsExpiredFragment$key = { + readonly " $data"?: sessionIsExpiredFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"sessionIsExpiredFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "sessionIsExpiredFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "expiresAt", + "storageKey": null + } + ], + "type": "Session", + "abstractKey": null +}; + +(node as any).hash = "ef7bc8bbfa6d2573b0df09b5b13b00b2"; + +export default node; diff --git a/apps/console/src/pages/iam/organizations/_components/MembershipCard.tsx b/apps/console/src/pages/iam/organizations/_components/MembershipCard.tsx new file mode 100644 index 000000000..afcdca5a6 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/_components/MembershipCard.tsx @@ -0,0 +1,133 @@ +import { useTranslate } from "@probo/i18n"; +import { + Avatar, + Badge, + Button, + Card, + IconCheckmark1, + IconClock, + IconLock, +} from "@probo/ui"; +import { Link } from "react-router"; +import { graphql } from "relay-runtime"; +import { useFragment } from "react-relay"; +import type { MembershipCardFragment$key } from "./__generated__/MembershipCardFragment.graphql"; +import { parseDate } from "@probo/helpers"; + +const fragment = graphql` + fragment MembershipCardFragment on Membership { + activeSession { + id + expiresAt + } + organization { + id + name + logoUrl + } + } +`; + +interface MembershipCardProps { + fKey: MembershipCardFragment$key; +} + +export function MembershipCard(props: MembershipCardProps) { + const { fKey } = props; + const { __ } = useTranslate(); + + const { activeSession, organization } = + useFragment(fragment, fKey); + const isAuthenticated = !!activeSession; + const isExpired = + activeSession && parseDate(activeSession.expiresAt) >= new Date(); + + // Determine target URL and button text based on auth status + // const targetUrl = isAuthenticated + // ? `/organizations/${organization.id}` + // : organization.loginUrl; + const targetUrl = `/organizations/${organization.id}`; + + const getAuthBadge = () => { + if (isAuthenticated) { + return ( + + + {__("Authenticated")} + + ); + } else if (isExpired) { + return ( + + + {__("Session expired")} + + ); + } else { + return ( + + + {__("Authentication required")} + + ); + } + }; + + // const getButtonText = () => { + // if (isAuthenticated) return __("Select"); + // if (organization.authenticationMethod === "saml") + // return __("Login with SAML"); + // return __("Login"); + // }; + + // Check if the URL is a backend SAML endpoint + const isSAMLUrl = targetUrl.includes("/connect/saml/"); + + return ( + +
+ {isSAMLUrl ? ( + + +
+

{organization.name}

+ {getAuthBadge()} +
+
+ ) : ( + + +
+

{organization.name}

+ {getAuthBadge()} +
+ + )} +
+ +
+
+
+ ); +} diff --git a/apps/console/src/pages/iam/organizations/_components/__generated__/MembershipCardFragment.graphql.ts b/apps/console/src/pages/iam/organizations/_components/__generated__/MembershipCardFragment.graphql.ts new file mode 100644 index 000000000..c3f9e3df2 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/_components/__generated__/MembershipCardFragment.graphql.ts @@ -0,0 +1,97 @@ +/** + * @generated SignedSource<<30ba0cd2d4738694d58bf346410d4d68>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type MembershipCardFragment$data = { + readonly activeSession: { + readonly expiresAt: any; + readonly id: string; + } | null | undefined; + readonly organization: { + readonly id: string; + readonly logoUrl: string | null | undefined; + readonly name: string; + }; + readonly " $fragmentType": "MembershipCardFragment"; +}; +export type MembershipCardFragment$key = { + readonly " $data"?: MembershipCardFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MembershipCardFragment">; +}; + +const node: ReaderFragment = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "MembershipCardFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Session", + "kind": "LinkedField", + "name": "activeSession", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "expiresAt", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Organization", + "kind": "LinkedField", + "name": "organization", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "logoUrl", + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Membership", + "abstractKey": null +}; +})(); + +(node as any).hash = "2145f6030b486447df7b7d9ff798ec06"; + +export default node; diff --git a/apps/console/src/routes/rightsRequestRoutes.ts b/apps/console/src/routes/rightsRequestRoutes.ts index 11aa556d6..6e7f8dcf4 100644 --- a/apps/console/src/routes/rightsRequestRoutes.ts +++ b/apps/console/src/routes/rightsRequestRoutes.ts @@ -1,5 +1,5 @@ import { loadQuery } from "react-relay"; -import { relayEnvironment } from "/providers/RelayProviders"; +import { consoleEnvironment } from "/environments"; import { PageSkeleton } from "/components/skeletons/PageSkeleton"; import { lazy } from "@probo/react-lazy"; import { rightsRequestsQuery, rightsRequestNodeQuery } from "/hooks/graph/RightsRequestGraph"; @@ -12,7 +12,7 @@ export const rightsRequestRoutes = [ path: "rights-requests", Fallback: PageSkeleton, loader: loaderFromQueryLoader(({ organizationId }) => - loadQuery(relayEnvironment, rightsRequestsQuery, { + loadQuery(consoleEnvironment, rightsRequestsQuery, { organizationId, }), ), @@ -24,7 +24,7 @@ export const rightsRequestRoutes = [ path: "rights-requests/:requestId", Fallback: PageSkeleton, loader: loaderFromQueryLoader(({ requestId }) => - loadQuery(relayEnvironment, rightsRequestNodeQuery, { + loadQuery(consoleEnvironment, rightsRequestNodeQuery, { rightsRequestId: requestId!, }), ), diff --git a/apps/console/src/routes/statesOfApplicabilityRoutes.ts b/apps/console/src/routes/statesOfApplicabilityRoutes.ts index 92ed5186e..bd998c448 100644 --- a/apps/console/src/routes/statesOfApplicabilityRoutes.ts +++ b/apps/console/src/routes/statesOfApplicabilityRoutes.ts @@ -1,6 +1,6 @@ import { lazy } from "@probo/react-lazy"; import { loadQuery } from "react-relay"; -import { relayEnvironment } from "/providers/RelayProviders"; +import { consoleEnvironment } from "/environments"; import { PageSkeleton } from "/components/skeletons/PageSkeleton.tsx"; import { paginatedStateOfApplicabilityQuery, @@ -16,7 +16,7 @@ export const statesOfApplicabilityRoutes = [ Fallback: PageSkeleton, loader: loaderFromQueryLoader(({ organizationId }) => loadQuery( - relayEnvironment, + consoleEnvironment, paginatedStateOfApplicabilityQuery, { organizationId: organizationId! }, ), @@ -30,7 +30,7 @@ export const statesOfApplicabilityRoutes = [ Fallback: PageSkeleton, loader: loaderFromQueryLoader(({ organizationId }) => loadQuery( - relayEnvironment, + consoleEnvironment, paginatedStateOfApplicabilityQuery, { organizationId: organizationId! }, ), @@ -44,7 +44,7 @@ export const statesOfApplicabilityRoutes = [ Fallback: PageSkeleton, loader: loaderFromQueryLoader(({ stateOfApplicabilityId }) => loadQuery( - relayEnvironment, + consoleEnvironment, stateOfApplicabilityNodeQuery, { stateOfApplicabilityId: stateOfApplicabilityId! }, ), @@ -58,7 +58,7 @@ export const statesOfApplicabilityRoutes = [ Fallback: PageSkeleton, loader: loaderFromQueryLoader(({ stateOfApplicabilityId }) => loadQuery( - relayEnvironment, + consoleEnvironment, stateOfApplicabilityNodeQuery, { stateOfApplicabilityId: stateOfApplicabilityId! }, ), diff --git a/packages/helpers/src/date.ts b/packages/helpers/src/date.ts index 7249e1dba..bc6dae645 100644 --- a/packages/helpers/src/date.ts +++ b/packages/helpers/src/date.ts @@ -9,21 +9,21 @@ export function toDateInput(dateString?: string | null): string { } export function formatDate(dateInput?: string | null): string { - if (!dateInput) return ''; + if (!dateInput) return ""; const date = parseDate(dateInput); return date.toLocaleDateString(); } -function parseDate(dateString: string): Date { - if (dateString.includes('T')) { +export function parseDate(dateString: string): Date { + if (dateString.includes("T")) { return new Date(dateString); } - const parts = dateString.split('-'); + const parts = dateString.split("-"); return new Date( parseInt(parts[0], 10), parts[1] ? parseInt(parts[1], 10) - 1 : 0, - parts[2] ? parseInt(parts[2], 10) : 1 + parts[2] ? parseInt(parts[2], 10) : 1, ); } diff --git a/packages/helpers/src/index.ts b/packages/helpers/src/index.ts index 5ecf19f2b..b4bb613aa 100644 --- a/packages/helpers/src/index.ts +++ b/packages/helpers/src/index.ts @@ -1,16 +1,16 @@ export { objectKeys, objectEntries, cleanFormData } from "./object"; export { sprintf, faviconUrl, slugify, domain } from "./string"; export { - getTreatment, - getRiskImpacts, - getRiskLikelihoods, - getSeverity, + getTreatment, + getRiskImpacts, + getRiskLikelihoods, + getSeverity, } from "./risk"; export { - withViewTransition, - downloadFile, - safeOpenUrl, - focusSiblingElement, + withViewTransition, + downloadFile, + safeOpenUrl, + focusSiblingElement, } from "./dom"; export { times, groupBy, isEmpty } from "./array"; export { randomInt } from "./number"; @@ -18,39 +18,39 @@ export { getMeasureStateLabel, measureStates } from "./measure"; export { getRole, getRoles, peopleRoles } from "./people"; export { certificationCategoryLabel, certifications } from "./certifications"; export { - getCountryName, - getCountryOptions, - getCountryLabel, - countries, - type CountryCode, + getCountryName, + getCountryOptions, + getCountryLabel, + countries, + type CountryCode, } from "./countries"; export { - getDocumentTypeLabel, - documentTypes, - getDocumentClassificationLabel, - documentClassifications, + getDocumentTypeLabel, + documentTypes, + getDocumentClassificationLabel, + documentClassifications, } from "./documents"; export { getAssetTypeVariant } from "./assets"; export { - getSnapshotTypeLabel, - getSnapshotTypeUrlPath, - snapshotTypes, - validateSnapshotConsistency, + getSnapshotTypeLabel, + getSnapshotTypeUrlPath, + snapshotTypes, + validateSnapshotConsistency, } from "./snapshots"; export { - getAuditStateLabel, - getAuditStateVariant, - auditStates, + getAuditStateLabel, + getAuditStateVariant, + auditStates, } from "./audits"; export { - getStatusVariant, - getStatusLabel, - getStatusOptions, + getStatusVariant, + getStatusLabel, + getStatusOptions, } from "./registryStatus"; export { - getObligationStatusVariant, - getObligationStatusLabel, - getObligationStatusOptions, + getObligationStatusVariant, + getObligationStatusLabel, + getObligationStatusOptions, } from "./obligationStatus"; export { getObligationTypeLabel, @@ -65,24 +65,30 @@ export { } from "./trustCenterVisibility"; export { promisifyMutation } from "./relay"; export { fileType, fileSize } from "./file"; -export { formatDatetime, formatDate, toDateInput, formatDuration } from "./date"; +export { + formatDatetime, + formatDate, + toDateInput, + formatDuration, + parseDate, +} from "./date"; export { getTrustCenterUrl } from "./trustCenter"; export { formatError, type GraphQLError } from "./error"; export { Role, getAssignableRoles } from "./roles"; export { - getTrustCenterDocumentAccessInfo, - getTrustCenterDocumentAccessStatusBadgeVariant, - getTrustCenterDocumentAccessStatusLabel, - type TrustCenterDocumentAccessInfo, + getTrustCenterDocumentAccessInfo, + getTrustCenterDocumentAccessStatusBadgeVariant, + getTrustCenterDocumentAccessStatusLabel, + type TrustCenterDocumentAccessInfo, } from "./trustCenterDocumentAccess"; export { - getRightsRequestTypeLabel, - getRightsRequestTypeOptions, - getRightsRequestStateVariant, - getRightsRequestStateLabel, - getRightsRequestStateOptions, - rightsRequestTypes, - rightsRequestStates, - type RightsRequestType, - type RightsRequestState, + getRightsRequestTypeLabel, + getRightsRequestTypeOptions, + getRightsRequestStateVariant, + getRightsRequestStateLabel, + getRightsRequestStateOptions, + rightsRequestTypes, + rightsRequestStates, + type RightsRequestType, + type RightsRequestState, } from "./rightsRequest";