Fix apps/trust lint issues
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -153,7 +153,6 @@ const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
// Component: () => "hello world",
|
|
||||||
Component: lazy(
|
Component: lazy(
|
||||||
() =>
|
() =>
|
||||||
import("./pages/organizations/employee/EmployeeDocumentsPageLoader"),
|
import("./pages/organizations/employee/EmployeeDocumentsPageLoader"),
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
import { browser } from "@probo/eslint-config";
|
import { defineConfig } from "eslint/config";
|
||||||
|
import { configs } from "@probo/eslint-config";
|
||||||
|
|
||||||
export default [
|
export default defineConfig([
|
||||||
{ ignores: ["dist", "eslint.config.mjs", "*.config.{js,mjs,ts}"] },
|
...configs.base,
|
||||||
...browser(["./tsconfig.app.json", "./tsconfig.node.json"], import.meta.dirname),
|
...configs.ts,
|
||||||
];
|
...configs.react,
|
||||||
|
configs.languageOptions.browser,
|
||||||
|
...configs.stylistic,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
tsConfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ export function AuditRow(props: { audit: AuditRowFragment$key }) {
|
|||||||
audit.report?.hasUserRequestedAccess,
|
audit.report?.hasUserRequestedAccess,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [requestAccess, isRequestingAccess] =
|
const [requestAccess, isRequestingAccess]
|
||||||
useMutation<AuditRow_requestAccessMutation>(requestAccessMutation);
|
= useMutation<AuditRow_requestAccessMutation>(requestAccessMutation);
|
||||||
const [commitDownload, downloading] =
|
const [commitDownload, downloading]
|
||||||
useMutationWithToasts<AuditRowDownloadMutation>(downloadMutation);
|
= useMutationWithToasts<AuditRowDownloadMutation>(downloadMutation);
|
||||||
|
|
||||||
const handleRequestAccess = () => {
|
const handleRequestAccess = () => {
|
||||||
requestAccess({
|
requestAccess({
|
||||||
@@ -106,11 +106,11 @@ export function AuditRow(props: { audit: AuditRowFragment$key }) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = () => {
|
const handleDownload = async () => {
|
||||||
if (!audit.report?.id) {
|
if (!audit.report?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
commitDownload({
|
await commitDownload({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
reportId: audit.report.id,
|
reportId: audit.report.id,
|
||||||
@@ -128,36 +128,40 @@ export function AuditRow(props: { audit: AuditRowFragment$key }) {
|
|||||||
<IconMedal size={16} className="flex-none text-txt-tertiary" />
|
<IconMedal size={16} className="flex-none text-txt-tertiary" />
|
||||||
{audit.framework.name}
|
{audit.framework.name}
|
||||||
</div>
|
</div>
|
||||||
{audit.report && audit.report.isUserAuthorized ? (
|
{audit.report && audit.report.isUserAuthorized
|
||||||
<Button
|
? (
|
||||||
className="w-full md:w-max"
|
<Button
|
||||||
variant="secondary"
|
className="w-full md:w-max"
|
||||||
disabled={downloading}
|
variant="secondary"
|
||||||
icon={downloading ? Spinner : IconArrowInbox}
|
disabled={downloading}
|
||||||
onClick={handleDownload}
|
icon={downloading ? Spinner : IconArrowInbox}
|
||||||
>
|
onClick={() => void handleDownload()}
|
||||||
{__("Download")}
|
>
|
||||||
</Button>
|
{__("Download")}
|
||||||
) : viewer ? (
|
</Button>
|
||||||
<Button
|
)
|
||||||
disabled={hasRequested || isRequestingAccess}
|
: viewer
|
||||||
className="w-full md:w-max"
|
? (
|
||||||
variant="secondary"
|
<Button
|
||||||
icon={IconLock}
|
disabled={hasRequested || isRequestingAccess}
|
||||||
onClick={handleRequestAccess}
|
className="w-full md:w-max"
|
||||||
>
|
variant="secondary"
|
||||||
{hasRequested ? __("Access requested") : __("Request access")}
|
icon={IconLock}
|
||||||
</Button>
|
onClick={handleRequestAccess}
|
||||||
) : (
|
>
|
||||||
<Button
|
{hasRequested ? __("Access requested") : __("Request access")}
|
||||||
className="w-full md:w-max"
|
</Button>
|
||||||
variant="secondary"
|
)
|
||||||
icon={IconLock}
|
: (
|
||||||
to="/connect"
|
<Button
|
||||||
>
|
className="w-full md:w-max"
|
||||||
{hasRequested ? __("Access requested") : __("Request access")}
|
variant="secondary"
|
||||||
</Button>
|
icon={IconLock}
|
||||||
)}
|
to="/connect"
|
||||||
|
>
|
||||||
|
{hasRequested ? __("Access requested") : __("Request access")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,10 +56,10 @@ export function DocumentRow(props: { document: DocumentRowFragment$key }) {
|
|||||||
document.hasUserRequestedAccess,
|
document.hasUserRequestedAccess,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [requestAccess, isRequestingAccess] =
|
const [requestAccess, isRequestingAccess]
|
||||||
useMutation<DocumentRow_requestAccessMutation>(requestAccessMutation);
|
= useMutation<DocumentRow_requestAccessMutation>(requestAccessMutation);
|
||||||
const [commitDownload, downloading] =
|
const [commitDownload, downloading]
|
||||||
useMutationWithToasts<DocumentRowDownloadMutation>(downloadMutation);
|
= useMutationWithToasts<DocumentRowDownloadMutation>(downloadMutation);
|
||||||
|
|
||||||
const handleRequestAccess = () => {
|
const handleRequestAccess = () => {
|
||||||
requestAccess({
|
requestAccess({
|
||||||
@@ -94,8 +94,8 @@ export function DocumentRow(props: { document: DocumentRowFragment$key }) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = () => {
|
const handleDownload = async () => {
|
||||||
commitDownload({
|
await commitDownload({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
@@ -113,36 +113,40 @@ export function DocumentRow(props: { document: DocumentRowFragment$key }) {
|
|||||||
<IconPageTextLine size={16} className=" flex-none text-txt-tertiary" />
|
<IconPageTextLine size={16} className=" flex-none text-txt-tertiary" />
|
||||||
{document.title}
|
{document.title}
|
||||||
</div>
|
</div>
|
||||||
{document.isUserAuthorized ? (
|
{document.isUserAuthorized
|
||||||
<Button
|
? (
|
||||||
className="w-full md:w-max"
|
<Button
|
||||||
variant="secondary"
|
className="w-full md:w-max"
|
||||||
disabled={downloading}
|
variant="secondary"
|
||||||
icon={downloading ? Spinner : IconArrowInbox}
|
disabled={downloading}
|
||||||
onClick={handleDownload}
|
icon={downloading ? Spinner : IconArrowInbox}
|
||||||
>
|
onClick={() => void handleDownload()}
|
||||||
{__("Download")}
|
>
|
||||||
</Button>
|
{__("Download")}
|
||||||
) : viewer ? (
|
</Button>
|
||||||
<Button
|
)
|
||||||
disabled={hasRequested || isRequestingAccess}
|
: viewer
|
||||||
className="w-full md:w-max"
|
? (
|
||||||
variant="secondary"
|
<Button
|
||||||
icon={IconLock}
|
disabled={hasRequested || isRequestingAccess}
|
||||||
onClick={handleRequestAccess}
|
className="w-full md:w-max"
|
||||||
>
|
variant="secondary"
|
||||||
{hasRequested ? __("Access requested") : __("Request access")}
|
icon={IconLock}
|
||||||
</Button>
|
onClick={handleRequestAccess}
|
||||||
) : (
|
>
|
||||||
<Button
|
{hasRequested ? __("Access requested") : __("Request access")}
|
||||||
className="w-full md:w-max"
|
</Button>
|
||||||
variant="secondary"
|
)
|
||||||
icon={IconLock}
|
: (
|
||||||
to="/connect"
|
<Button
|
||||||
>
|
className="w-full md:w-max"
|
||||||
{hasRequested ? __("Access requested") : __("Request access")}
|
variant="secondary"
|
||||||
</Button>
|
icon={IconLock}
|
||||||
)}
|
to="/connect"
|
||||||
|
>
|
||||||
|
{hasRequested ? __("Access requested") : __("Request access")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ export function NDADialog({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = handleSubmitWrapper(({ fullName }) => {
|
const handleSubmit = handleSubmitWrapper(async ({ fullName }) => {
|
||||||
commitSigning({
|
await commitSigning({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
fullName,
|
fullName,
|
||||||
@@ -98,7 +98,7 @@ export function NDADialog({
|
|||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={e => void handleSubmit(e)}>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<Field
|
<Field
|
||||||
required
|
required
|
||||||
@@ -129,7 +129,9 @@ export function NDADialog({
|
|||||||
isMobile ? "mt-15" : "mt-30",
|
isMobile ? "mt-15" : "mt-30",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Powered by <Logo withPicto className="h-6" />
|
Powered by
|
||||||
|
{" "}
|
||||||
|
<Logo withPicto className="h-6" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{isDesktop && (
|
{isDesktop && (
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ export function OrganizationSidebar({
|
|||||||
const isAuthenticated = !!use(Viewer);
|
const isAuthenticated = !!use(Viewer);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const [requestAllAccesses, isRequestingAccess] =
|
const [requestAllAccesses, isRequestingAccess]
|
||||||
useMutation<OrganizationSidebar_requestAllAccessesMutation>(
|
= useMutation<OrganizationSidebar_requestAllAccessesMutation>(
|
||||||
requestAllAccessesMutation,
|
requestAllAccessesMutation,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -75,15 +75,17 @@ export function OrganizationSidebar({
|
|||||||
return (
|
return (
|
||||||
<Card className="p-6 relative overflow-hidden border-b border-border-low isolate">
|
<Card className="p-6 relative overflow-hidden border-b border-border-low isolate">
|
||||||
<div className="h-21 bg-[#044E4114] absolute top-0 left-0 right-0 -z-1"></div>
|
<div className="h-21 bg-[#044E4114] absolute top-0 left-0 right-0 -z-1"></div>
|
||||||
{trustCenter.organization.logoUrl ? (
|
{trustCenter.organization.logoUrl
|
||||||
<img
|
? (
|
||||||
alt=""
|
<img
|
||||||
src={trustCenter.organization.logoUrl}
|
alt=""
|
||||||
className="size-24 rounded-2xl border border-border-mid shadow-mid bg-level-1"
|
src={trustCenter.organization.logoUrl}
|
||||||
/>
|
className="size-24 rounded-2xl border border-border-mid shadow-mid bg-level-1"
|
||||||
) : (
|
/>
|
||||||
<div className="size-24 rounded-2xl border border-border-mid shadow-mid bg-level-1" />
|
)
|
||||||
)}
|
: (
|
||||||
|
<div className="size-24 rounded-2xl border border-border-mid shadow-mid bg-level-1" />
|
||||||
|
)}
|
||||||
<h1 className="text-2xl mt-6">{trustCenter.organization.name}</h1>
|
<h1 className="text-2xl mt-6">{trustCenter.organization.name}</h1>
|
||||||
<p className="text-sm text-txt-secondary mt-1">
|
<p className="text-sm text-txt-secondary mt-1">
|
||||||
{trustCenter.organization.description}
|
{trustCenter.organization.description}
|
||||||
@@ -137,7 +139,7 @@ export function OrganizationSidebar({
|
|||||||
gridTemplateColumns: "repeat(auto-fit, 75px",
|
gridTemplateColumns: "repeat(auto-fit, 75px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{trustCenter.audits.edges.map((audit) => (
|
{trustCenter.audits.edges.map(audit => (
|
||||||
<AuditRowAvatar key={audit.node.id} audit={audit.node} />
|
<AuditRowAvatar key={audit.node.id} audit={audit.node} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -148,26 +150,28 @@ export function OrganizationSidebar({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
{isAuthenticated ? (
|
{isAuthenticated
|
||||||
<Button
|
? (
|
||||||
disabled={isRequestingAccess}
|
<Button
|
||||||
variant="primary"
|
disabled={isRequestingAccess}
|
||||||
icon={IconLock}
|
variant="primary"
|
||||||
className="w-full h-10"
|
icon={IconLock}
|
||||||
onClick={handleRequestAllAccesses}
|
className="w-full h-10"
|
||||||
>
|
onClick={handleRequestAllAccesses}
|
||||||
{__("Request access")}
|
>
|
||||||
</Button>
|
{__("Request access")}
|
||||||
) : (
|
</Button>
|
||||||
<Button
|
)
|
||||||
variant="primary"
|
: (
|
||||||
icon={IconLock}
|
<Button
|
||||||
className="w-full h-10"
|
variant="primary"
|
||||||
to="/connect"
|
icon={IconLock}
|
||||||
>
|
className="w-full h-10"
|
||||||
{__("Request access")}
|
to="/connect"
|
||||||
</Button>
|
>
|
||||||
)}
|
{__("Request access")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import { IconMinusLarge } from "@probo/ui/src/Atoms/Icons/IconMinusLarge.tsx";
|
|||||||
// Worker for PDF.js
|
// Worker for PDF.js
|
||||||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
|
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
|
||||||
|
|
||||||
const btnClass =
|
const btnClass
|
||||||
"size-8 grid place-items-center hover:bg-secondary-hover cursor-pointer rounded-sm disabled:opacity-30 transition-all";
|
= "size-8 grid place-items-center hover:bg-secondary-hover cursor-pointer rounded-sm disabled:opacity-30 transition-all";
|
||||||
|
|
||||||
export function PDFPreview({ src, name }: { src: string; name?: string }) {
|
export function PDFPreview({ src, name }: { src: string; name?: string }) {
|
||||||
const [numPages, setNumPages] = useState(0);
|
const [numPages, setNumPages] = useState(0);
|
||||||
@@ -92,7 +92,10 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
|
|||||||
<IconChevronLeft size={16} />
|
<IconChevronLeft size={16} />
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
{currentPage} / {numPages}
|
{currentPage}
|
||||||
|
{" "}
|
||||||
|
/
|
||||||
|
{numPages}
|
||||||
</div>
|
</div>
|
||||||
<button onClick={movePage(1)} className={btnClass}>
|
<button onClick={movePage(1)} className={btnClass}>
|
||||||
<IconChevronRight size={16} />
|
<IconChevronRight size={16} />
|
||||||
@@ -122,7 +125,7 @@ export function PDFPreview({ src, name }: { src: string; name?: string }) {
|
|||||||
ref={documentRef}
|
ref={documentRef}
|
||||||
>
|
>
|
||||||
{numPages === 0 && <Spinner className="mx-auto" />}
|
{numPages === 0 && <Spinner className="mx-auto" />}
|
||||||
{times(numPages, (index) => (
|
{times(numPages, index => (
|
||||||
<Page
|
<Page
|
||||||
className="w-max h-max mx-auto shadow-mid"
|
className="w-max h-max mx-auto shadow-mid"
|
||||||
key={index.toString()}
|
key={index.toString()}
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ export function PageError({ resetErrorBoundary, error: propsError }: Props) {
|
|||||||
// Reset error boundary on page change
|
// Reset error boundary on page change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
location.pathname !== baseLocation.current.pathname &&
|
location.pathname !== baseLocation.current.pathname
|
||||||
resetErrorBoundary
|
&& resetErrorBoundary
|
||||||
) {
|
) {
|
||||||
resetErrorBoundary();
|
resetErrorBoundary();
|
||||||
}
|
}
|
||||||
}, [location, resetErrorBoundary]);
|
}, [location, resetErrorBoundary]);
|
||||||
|
|
||||||
if (!error || (error && error.toString().includes("PAGE_NOT_FOUND"))) {
|
if (!error || (error instanceof Error && error.message.includes("PAGE_NOT_FOUND"))) {
|
||||||
return (
|
return (
|
||||||
<div className={classNames.wrapper}>
|
<div className={classNames.wrapper}>
|
||||||
<h1 className={classNames.title}>
|
<h1 className={classNames.title}>
|
||||||
@@ -46,22 +46,21 @@ export function PageError({ resetErrorBoundary, error: propsError }: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (error instanceof Error
|
||||||
error
|
&& error.message
|
||||||
.toString()
|
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.match(/(token|expired|invalid|401|unauthorized)/)
|
.match(/(token|expired|invalid|401|unauthorized)/)
|
||||||
) {
|
) {
|
||||||
const isExpiredToken = error.toString().toLowerCase().includes("expired");
|
const isExpiredToken = error.message.toLowerCase().includes("expired");
|
||||||
const title = isExpiredToken
|
const title = isExpiredToken
|
||||||
? __("Expired token")
|
? __("Expired token")
|
||||||
: __("Invalid Access Link");
|
: __("Invalid Access Link");
|
||||||
const description = isExpiredToken
|
const description = isExpiredToken
|
||||||
? __(
|
? __(
|
||||||
"This access link has expired. Trust center access links are valid for 7 days for security reasons."
|
"This access link has expired. Trust center access links are valid for 7 days for security reasons.",
|
||||||
)
|
)
|
||||||
: __(
|
: __(
|
||||||
"This access link is not valid. It may have been revoked or the link might be incorrect."
|
"This access link is not valid. It may have been revoked or the link might be incorrect.",
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className={classNames.wrapper}>
|
<div className={classNames.wrapper}>
|
||||||
@@ -81,7 +80,8 @@ export function PageError({ resetErrorBoundary, error: propsError }: Props) {
|
|||||||
<summary className={classNames.description}>
|
<summary className={classNames.description}>
|
||||||
{__("Something went wrong")}
|
{__("Something went wrong")}
|
||||||
</summary>
|
</summary>
|
||||||
<p className={classNames.detail}>{error.toString()}</p>
|
{error instanceof Error
|
||||||
|
&& <p className={classNames.detail}>{error.message}</p>}
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ export function TrustCenterFileRow(props: {
|
|||||||
const file = useFragment(trustCenterFileRowFragment, props.file);
|
const file = useFragment(trustCenterFileRowFragment, props.file);
|
||||||
const [hasRequested, setHasRequested] = useState(file.hasUserRequestedAccess);
|
const [hasRequested, setHasRequested] = useState(file.hasUserRequestedAccess);
|
||||||
|
|
||||||
const [requestAccess, isRequestingAccess] =
|
const [requestAccess, isRequestingAccess]
|
||||||
useMutation<TrustCenterFileRow_requestAccessMutation>(
|
= useMutation<TrustCenterFileRow_requestAccessMutation>(
|
||||||
requestAccessMutation,
|
requestAccessMutation,
|
||||||
);
|
);
|
||||||
const [commitDownload, downloading] =
|
const [commitDownload, downloading]
|
||||||
useMutationWithToasts<TrustCenterFileRowDownloadMutation>(downloadMutation);
|
= useMutationWithToasts<TrustCenterFileRowDownloadMutation>(downloadMutation);
|
||||||
|
|
||||||
const handleRequestAccess = () => {
|
const handleRequestAccess = () => {
|
||||||
requestAccess({
|
requestAccess({
|
||||||
@@ -98,8 +98,8 @@ export function TrustCenterFileRow(props: {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = () => {
|
const handleDownload = async () => {
|
||||||
commitDownload({
|
await commitDownload({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
trustCenterFileId: file.id,
|
trustCenterFileId: file.id,
|
||||||
@@ -117,36 +117,40 @@ export function TrustCenterFileRow(props: {
|
|||||||
<IconPageTextLine size={16} className=" flex-none text-txt-tertiary" />
|
<IconPageTextLine size={16} className=" flex-none text-txt-tertiary" />
|
||||||
{file.name}
|
{file.name}
|
||||||
</div>
|
</div>
|
||||||
{file.isUserAuthorized ? (
|
{file.isUserAuthorized
|
||||||
<Button
|
? (
|
||||||
className="w-full md:w-max"
|
<Button
|
||||||
variant="secondary"
|
className="w-full md:w-max"
|
||||||
disabled={downloading}
|
variant="secondary"
|
||||||
icon={downloading ? Spinner : IconArrowInbox}
|
disabled={downloading}
|
||||||
onClick={handleDownload}
|
icon={downloading ? Spinner : IconArrowInbox}
|
||||||
>
|
onClick={() => void handleDownload()}
|
||||||
{__("Download")}
|
>
|
||||||
</Button>
|
{__("Download")}
|
||||||
) : viewer ? (
|
</Button>
|
||||||
<Button
|
)
|
||||||
disabled={hasRequested || isRequestingAccess}
|
: viewer
|
||||||
className="w-full md:w-max"
|
? (
|
||||||
variant="secondary"
|
<Button
|
||||||
icon={IconLock}
|
disabled={hasRequested || isRequestingAccess}
|
||||||
onClick={handleRequestAccess}
|
className="w-full md:w-max"
|
||||||
>
|
variant="secondary"
|
||||||
{hasRequested ? __("Access requested") : __("Request access")}
|
icon={IconLock}
|
||||||
</Button>
|
onClick={handleRequestAccess}
|
||||||
) : (
|
>
|
||||||
<Button
|
{hasRequested ? __("Access requested") : __("Request access")}
|
||||||
className="w-full md:w-max"
|
</Button>
|
||||||
variant="secondary"
|
)
|
||||||
icon={IconLock}
|
: (
|
||||||
to="/connect"
|
<Button
|
||||||
>
|
className="w-full md:w-max"
|
||||||
{hasRequested ? __("Access requested") : __("Request access")}
|
variant="secondary"
|
||||||
</Button>
|
icon={IconLock}
|
||||||
)}
|
to="/connect"
|
||||||
|
>
|
||||||
|
{hasRequested ? __("Access requested") : __("Request access")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,35 +25,39 @@ export function VendorRow(props: { vendor: VendorRowFragment$key; hasAnyCountrie
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex text-sm leading-tight gap-3 md:items-center">
|
<div className="flex text-sm leading-tight gap-3 md:items-center">
|
||||||
{logo ? (
|
{logo
|
||||||
<img
|
? (
|
||||||
src={logo}
|
<img
|
||||||
className="size-8 md:size-6 flex-none rounded-lg"
|
src={logo}
|
||||||
alt=""
|
className="size-8 md:size-6 flex-none rounded-lg"
|
||||||
/>
|
alt=""
|
||||||
) : (
|
/>
|
||||||
<div className="size-8 md:size-6 flex-none rounded-lg" />
|
)
|
||||||
)}
|
: (
|
||||||
|
<div className="size-8 md:size-6 flex-none rounded-lg" />
|
||||||
|
)}
|
||||||
<div className={`flex flex-col md:grid ${gridCols} flex-1 gap-0.5`}>
|
<div className={`flex flex-col md:grid ${gridCols} flex-1 gap-0.5`}>
|
||||||
<div>{vendor.name}</div>
|
<div>{vendor.name}</div>
|
||||||
{vendor.privacyPolicyUrl ? (
|
{vendor.privacyPolicyUrl
|
||||||
<a
|
? (
|
||||||
href={vendor.privacyPolicyUrl}
|
<a
|
||||||
target="_blank"
|
href={vendor.privacyPolicyUrl}
|
||||||
className={`flex gap-1 text-txt-info items-center hover:underline ${!props.hasAnyCountries ? 'md:justify-end' : ''}`}
|
target="_blank"
|
||||||
>
|
className={`flex gap-1 text-txt-info items-center hover:underline ${!props.hasAnyCountries ? "md:justify-end" : ""}`}
|
||||||
<IconShield size={16} className="flex-none" />
|
>
|
||||||
<span>{__("Privacy")}</span>
|
<IconShield size={16} className="flex-none" />
|
||||||
</a>
|
<span>{__("Privacy")}</span>
|
||||||
) : (
|
</a>
|
||||||
<div></div>
|
)
|
||||||
)}
|
: (
|
||||||
|
<div></div>
|
||||||
|
)}
|
||||||
{vendor.countries.length > 0 && (
|
{vendor.countries.length > 0 && (
|
||||||
<div className={`flex gap-1 text-txt-secondary items-center ${props.hasAnyCountries ? 'md:justify-end' : ''}`}>
|
<div className={`flex gap-1 text-txt-secondary items-center ${props.hasAnyCountries ? "md:justify-end" : ""}`}>
|
||||||
<IconPin size={16} className="flex-none" />
|
<IconPin size={16} className="flex-none" />
|
||||||
<span>
|
<span>
|
||||||
{vendor.countries
|
{vendor.countries
|
||||||
.map((country) => getCountryName(__, country))
|
.map(country => getCountryName(__, country))
|
||||||
.join(", ")}
|
.join(", ")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function useMutationWithToasts<T extends MutationParameters>(
|
|||||||
baseOptions?: {
|
baseOptions?: {
|
||||||
onSuccess?: (response: T["response"]) => void;
|
onSuccess?: (response: T["response"]) => void;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
const [mutate, isLoading] = useMutation<T>(query);
|
const [mutate, isLoading] = useMutation<T>(query);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@@ -22,7 +22,7 @@ export function useMutationWithToasts<T extends MutationParameters>(
|
|||||||
queryOptions: UseMutationConfig<T> & {
|
queryOptions: UseMutationConfig<T> & {
|
||||||
onSuccess?: (response: T["response"]) => void;
|
onSuccess?: (response: T["response"]) => void;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
}
|
},
|
||||||
) => {
|
) => {
|
||||||
const options = { ...baseOptions, ...queryOptions };
|
const options = { ...baseOptions, ...queryOptions };
|
||||||
return new Promise<void>((resolve, reject) =>
|
return new Promise<void>((resolve, reject) =>
|
||||||
@@ -34,11 +34,11 @@ export function useMutationWithToasts<T extends MutationParameters>(
|
|||||||
toast({
|
toast({
|
||||||
title: __("Error"),
|
title: __("Error"),
|
||||||
description:
|
description:
|
||||||
options.errorMessage ??
|
options.errorMessage
|
||||||
__("Failed to commit this operation."),
|
?? __("Failed to commit this operation."),
|
||||||
variant: "error",
|
variant: "error",
|
||||||
});
|
});
|
||||||
reject(error);
|
reject(error instanceof Error ? error : new Error(__("Failed to commit this operation.")));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
options.onSuccess?.(response);
|
options.onSuccess?.(response);
|
||||||
@@ -53,10 +53,10 @@ export function useMutationWithToasts<T extends MutationParameters>(
|
|||||||
});
|
});
|
||||||
reject(error);
|
reject(error);
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[mutate, toast, __, baseOptions]
|
[mutate, toast, __, baseOptions],
|
||||||
);
|
);
|
||||||
|
|
||||||
return [mutateWithToast, isLoading] as const;
|
return [mutateWithToast, isLoading] as const;
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ export function MainLayout(props: Props) {
|
|||||||
if (!trustCenter) {
|
if (!trustCenter) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const showNDADialog =
|
const showNDADialog
|
||||||
trustCenter.isViewerMember &&
|
= trustCenter.isViewerMember
|
||||||
!trustCenter.hasAcceptedNonDisclosureAgreement &&
|
&& !trustCenter.hasAcceptedNonDisclosureAgreement
|
||||||
trustCenter.ndaFileUrl;
|
&& trustCenter.ndaFileUrl;
|
||||||
return (
|
return (
|
||||||
<Viewer value={data.viewer}>
|
<Viewer value={data.viewer}>
|
||||||
<TrustCenterProvider trustCenter={trustCenter}>
|
<TrustCenterProvider trustCenter={trustCenter}>
|
||||||
@@ -51,7 +51,9 @@ export function MainLayout(props: Props) {
|
|||||||
href="https://www.getprobo.com/"
|
href="https://www.getprobo.com/"
|
||||||
className="flex gap-2 text-sm font-medium text-txt-tertiary items-center w-max mx-auto my-10"
|
className="flex gap-2 text-sm font-medium text-txt-tertiary items-center w-max mx-auto my-10"
|
||||||
>
|
>
|
||||||
{__("Powered by")} <Logo withPicto className="h-6" />
|
{__("Powered by")}
|
||||||
|
{" "}
|
||||||
|
<Logo withPicto className="h-6" />
|
||||||
</a>
|
</a>
|
||||||
</TrustCenterProvider>
|
</TrustCenterProvider>
|
||||||
</Viewer>
|
</Viewer>
|
||||||
|
|||||||
@@ -23,5 +23,5 @@ createRoot(document.getElementById("root")!).render(
|
|||||||
</TranslatorProvider>
|
</TranslatorProvider>
|
||||||
</RelayProvider>
|
</RelayProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</StrictMode>
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,16 +18,16 @@ export function DocumentsPage({ queryRef }: Props) {
|
|||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const data = usePreloadedQuery<TrustGraphCurrentDocumentsQuery>(
|
const data = usePreloadedQuery<TrustGraphCurrentDocumentsQuery>(
|
||||||
currentTrustDocumentsQuery,
|
currentTrustDocumentsQuery,
|
||||||
queryRef
|
queryRef,
|
||||||
);
|
);
|
||||||
const documents =
|
const documents
|
||||||
data.currentTrustCenter?.documents.edges.map((edge) => edge.node) ?? [];
|
= data.currentTrustCenter?.documents.edges.map(edge => edge.node) ?? [];
|
||||||
const files =
|
const files
|
||||||
data.currentTrustCenter?.trustCenterFiles.edges.map((edge) => edge.node) ?? [];
|
= data.currentTrustCenter?.trustCenterFiles.edges.map(edge => edge.node) ?? [];
|
||||||
const documentsPerType = groupBy(documents, (document) =>
|
const documentsPerType = groupBy(documents, document =>
|
||||||
documentTypeLabel(document.documentType, __)
|
documentTypeLabel(document.documentType, __),
|
||||||
);
|
);
|
||||||
const filesPerCategory = groupBy(files, (file) => file.category);
|
const filesPerCategory = groupBy(files, file => file.category);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 className="font-medium mb-1">{__("Documents")}</h2>
|
<h2 className="font-medium mb-1">{__("Documents")}</h2>
|
||||||
@@ -38,7 +38,7 @@ export function DocumentsPage({ queryRef }: Props) {
|
|||||||
{objectEntries(documentsPerType).map(([label, documents]) => (
|
{objectEntries(documentsPerType).map(([label, documents]) => (
|
||||||
<Fragment key={label}>
|
<Fragment key={label}>
|
||||||
<RowHeader>{label}</RowHeader>
|
<RowHeader>{label}</RowHeader>
|
||||||
{documents.map((document) => (
|
{documents.map(document => (
|
||||||
<DocumentRow key={document.id} document={document} />
|
<DocumentRow key={document.id} document={document} />
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
@@ -46,7 +46,7 @@ export function DocumentsPage({ queryRef }: Props) {
|
|||||||
{objectEntries(filesPerCategory).map(([category, files]) => (
|
{objectEntries(filesPerCategory).map(([category, files]) => (
|
||||||
<Fragment key={category}>
|
<Fragment key={category}>
|
||||||
<RowHeader>{category}</RowHeader>
|
<RowHeader>{category}</RowHeader>
|
||||||
{files.map((file) => (
|
{files.map(file => (
|
||||||
<TrustCenterFileRow key={file.id} file={file} />
|
<TrustCenterFileRow key={file.id} file={file} />
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@@ -67,14 +67,14 @@ const overviewFragment = graphql`
|
|||||||
|
|
||||||
export function OverviewPage() {
|
export function OverviewPage() {
|
||||||
const { trustCenter } = useOutletContext<{
|
const { trustCenter } = useOutletContext<{
|
||||||
trustCenter: OverviewPageFragment$key &
|
trustCenter: OverviewPageFragment$key
|
||||||
TrustGraphCurrentQuery$data["currentTrustCenter"];
|
& TrustGraphCurrentQuery$data["currentTrustCenter"];
|
||||||
}>();
|
}>();
|
||||||
const fragment = useFragment(overviewFragment, trustCenter);
|
const fragment = useFragment(overviewFragment, trustCenter);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<References
|
<References
|
||||||
references={fragment.references.edges.map((edge) => edge.node)}
|
references={fragment.references.edges.map(edge => edge.node)}
|
||||||
/>
|
/>
|
||||||
<Documents
|
<Documents
|
||||||
audits={trustCenter.audits.edges}
|
audits={trustCenter.audits.edges}
|
||||||
@@ -106,12 +106,12 @@ function Documents({
|
|||||||
}) {
|
}) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const documentsPerType = groupBy(
|
const documentsPerType = groupBy(
|
||||||
documents.map((edge) => edge.node),
|
documents.map(edge => edge.node),
|
||||||
(node) => documentTypeLabel(node.documentType, __),
|
node => documentTypeLabel(node.documentType, __),
|
||||||
);
|
);
|
||||||
const filesPerCategory = groupBy(
|
const filesPerCategory = groupBy(
|
||||||
files.map((edge) => edge.node),
|
files.map(edge => edge.node),
|
||||||
(node) => node.category,
|
node => node.category,
|
||||||
);
|
);
|
||||||
const hasAudits = audits.length > 0;
|
const hasAudits = audits.length > 0;
|
||||||
const hasDocuments = hasAudits || documents.length > 0 || files.length > 0;
|
const hasDocuments = hasAudits || documents.length > 0 || files.length > 0;
|
||||||
@@ -130,7 +130,7 @@ function Documents({
|
|||||||
{audits.length > 0 && (
|
{audits.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<RowHeader>{__("Compliance")}</RowHeader>
|
<RowHeader>{__("Compliance")}</RowHeader>
|
||||||
{audits.map((audit) => (
|
{audits.map(audit => (
|
||||||
<AuditRow key={audit.node.id} audit={audit.node} />
|
<AuditRow key={audit.node.id} audit={audit.node} />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
@@ -138,7 +138,7 @@ function Documents({
|
|||||||
{objectEntries(documentsPerType).map(([label, documents]) => (
|
{objectEntries(documentsPerType).map(([label, documents]) => (
|
||||||
<Fragment key={label}>
|
<Fragment key={label}>
|
||||||
<RowHeader>{label}</RowHeader>
|
<RowHeader>{label}</RowHeader>
|
||||||
{documents.map((document) => (
|
{documents.map(document => (
|
||||||
<DocumentRow key={document.id} document={document} />
|
<DocumentRow key={document.id} document={document} />
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
@@ -146,7 +146,7 @@ function Documents({
|
|||||||
{objectEntries(filesPerCategory).map(([category, files]) => (
|
{objectEntries(filesPerCategory).map(([category, files]) => (
|
||||||
<Fragment key={category}>
|
<Fragment key={category}>
|
||||||
<RowHeader>{category}</RowHeader>
|
<RowHeader>{category}</RowHeader>
|
||||||
{files.map((file) => (
|
{files.map(file => (
|
||||||
<TrustCenterFileRow key={file.id} file={file} />
|
<TrustCenterFileRow key={file.id} file={file} />
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
@@ -189,7 +189,7 @@ function Subprocessors({
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<Rows className="mb-8 *:py-5">
|
<Rows className="mb-8 *:py-5">
|
||||||
{vendors.map((vendor) => (
|
{vendors.map(vendor => (
|
||||||
<VendorRow
|
<VendorRow
|
||||||
key={vendor.node.id}
|
key={vendor.node.id}
|
||||||
vendor={vendor.node}
|
vendor={vendor.node}
|
||||||
@@ -223,7 +223,7 @@ function References({ references }: { references: Reference[] }) {
|
|||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h2 className="font-medium mb-4">{__("Trusted by")}</h2>
|
<h2 className="font-medium mb-4">{__("Trusted by")}</h2>
|
||||||
<Card className="grid grid-cols-2 flex-wrap p-6 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-7">
|
<Card className="grid grid-cols-2 flex-wrap p-6 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-7">
|
||||||
{references.map((reference) => (
|
{references.map(reference => (
|
||||||
<a
|
<a
|
||||||
key={reference.id}
|
key={reference.id}
|
||||||
href={reference.websiteUrl}
|
href={reference.websiteUrl}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ type Props = {
|
|||||||
export function SubprocessorsPage({ queryRef }: Props) {
|
export function SubprocessorsPage({ queryRef }: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const data = usePreloadedQuery(currentTrustVendorsQuery, queryRef);
|
const data = usePreloadedQuery(currentTrustVendorsQuery, queryRef);
|
||||||
const vendors =
|
const vendors
|
||||||
data.currentTrustCenter?.vendors.edges.map((edge) => edge.node) ?? [];
|
= data.currentTrustCenter?.vendors.edges.map(edge => edge.node) ?? [];
|
||||||
|
|
||||||
const hasAnyCountries = vendors.some(vendor => vendor.countries.length > 0);
|
const hasAnyCountries = vendors.some(vendor => vendor.countries.length > 0);
|
||||||
|
|
||||||
@@ -24,11 +24,11 @@ export function SubprocessorsPage({ queryRef }: Props) {
|
|||||||
<p className="text-sm text-txt-secondary mb-4">
|
<p className="text-sm text-txt-secondary mb-4">
|
||||||
{sprintf(
|
{sprintf(
|
||||||
__("Third-party subprocessors %s work with:"),
|
__("Third-party subprocessors %s work with:"),
|
||||||
data.currentTrustCenter?.organization.name ?? ""
|
data.currentTrustCenter?.organization.name ?? "",
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<Rows>
|
<Rows>
|
||||||
{vendors.map((vendor) => (
|
{vendors.map(vendor => (
|
||||||
<VendorRow key={vendor.id} vendor={vendor} hasAnyCountries={hasAnyCountries} />
|
<VendorRow key={vendor.id} vendor={vendor} hasAnyCountries={hasAnyCountries} />
|
||||||
))}
|
))}
|
||||||
</Rows>
|
</Rows>
|
||||||
|
|||||||
@@ -59,12 +59,11 @@ export function ConnectPage(props: {
|
|||||||
if (!magicLinkSent && interval.current) {
|
if (!magicLinkSent && interval.current) {
|
||||||
clearInterval(interval.current);
|
clearInterval(interval.current);
|
||||||
interval.current = undefined;
|
interval.current = undefined;
|
||||||
setTimer(timerDurationSeconds);
|
|
||||||
}
|
}
|
||||||
if (magicLinkSent) {
|
if (magicLinkSent) {
|
||||||
clearInterval(interval.current);
|
clearInterval(interval.current);
|
||||||
interval.current = setInterval(() => {
|
interval.current = setInterval(() => {
|
||||||
setTimer((timer) => Math.max(timer - 1, 0));
|
setTimer(timer => Math.max(timer - 1, 0));
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +137,7 @@ export function ConnectPage(props: {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={e => void handleSubmit(e)} className="space-y-4">
|
||||||
<Field
|
<Field
|
||||||
label={__("Email")}
|
label={__("Email")}
|
||||||
placeholder="john.doe@acme.com"
|
placeholder="john.doe@acme.com"
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { RelayProvider } from "/providers/RelayProviders";
|
|||||||
import type { ConnectPageQuery } from "./__generated__/ConnectPageQuery.graphql";
|
import type { ConnectPageQuery } from "./__generated__/ConnectPageQuery.graphql";
|
||||||
|
|
||||||
function ConnectPageLoader() {
|
function ConnectPageLoader() {
|
||||||
const [queryRef, loadQuery] =
|
const [queryRef, loadQuery]
|
||||||
useQueryLoader<ConnectPageQuery>(connectPageQuery);
|
= useQueryLoader<ConnectPageQuery>(connectPageQuery);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!queryRef) {
|
if (!queryRef) {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default function VerifyMagicLinkPagePageMutation() {
|
|||||||
verifyMagicLinkMutation,
|
verifyMagicLinkMutation,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit((data) => {
|
||||||
verifyMagicLink({
|
verifyMagicLink({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
@@ -79,7 +79,7 @@ export default function VerifyMagicLinkPagePageMutation() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!submittedRef.current && searchParams.get("token")) {
|
if (!submittedRef.current && searchParams.get("token")) {
|
||||||
handleSubmit();
|
void handleSubmit();
|
||||||
submittedRef.current = true;
|
submittedRef.current = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -94,7 +94,7 @@ export default function VerifyMagicLinkPagePageMutation() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={e => void handleSubmit(e)} className="space-y-4">
|
||||||
<Field
|
<Field
|
||||||
label={__("Confirmation Token")}
|
label={__("Confirmation Token")}
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
Environment,
|
Environment,
|
||||||
type FetchFunction,
|
|
||||||
Network,
|
Network,
|
||||||
RecordSource,
|
RecordSource,
|
||||||
Store,
|
Store,
|
||||||
} from "relay-runtime";
|
} from "relay-runtime";
|
||||||
import { GraphQLError } from "graphql";
|
|
||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import { RelayEnvironmentProvider } from "react-relay";
|
import { RelayEnvironmentProvider } from "react-relay";
|
||||||
import { getPathPrefix } from "/utils/pathPrefix";
|
import { getPathPrefix } from "/utils/pathPrefix";
|
||||||
|
import { makeFetchQuery } from "@probo/relay";
|
||||||
|
|
||||||
export class UnAuthenticatedError extends Error {
|
export class UnAuthenticatedError extends Error {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -43,8 +42,8 @@ export function buildEndpoint(): string {
|
|||||||
host = window.location.origin;
|
host = window.location.origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedHost =
|
const formattedHost
|
||||||
host.startsWith("http://") || host.startsWith("https://")
|
= host.startsWith("http://") || host.startsWith("https://")
|
||||||
? host
|
? host
|
||||||
: `https://${host}`;
|
: `https://${host}`;
|
||||||
|
|
||||||
@@ -63,99 +62,6 @@ export function buildEndpoint(): string {
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasUnauthenticatedError = (error: GraphQLError) =>
|
|
||||||
error.extensions?.code == "UNAUTHENTICATED";
|
|
||||||
|
|
||||||
const hasInvalidError = (error: GraphQLError) =>
|
|
||||||
error.extensions?.code == "INVALID_REQUEST";
|
|
||||||
|
|
||||||
const fetchRelay: FetchFunction = async (
|
|
||||||
request,
|
|
||||||
variables,
|
|
||||||
_,
|
|
||||||
uploadables,
|
|
||||||
) => {
|
|
||||||
const requestInit: RequestInit = {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "include",
|
|
||||||
headers: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (uploadables) {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append(
|
|
||||||
"operations",
|
|
||||||
JSON.stringify({
|
|
||||||
operationName: request.name,
|
|
||||||
query: request.text,
|
|
||||||
variables: variables,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const uploadableMap: {
|
|
||||||
[key: string]: string[];
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
Object.keys(uploadables).forEach((key, index) => {
|
|
||||||
uploadableMap[index] = [`variables.${key}`];
|
|
||||||
});
|
|
||||||
|
|
||||||
formData.append("map", JSON.stringify(uploadableMap));
|
|
||||||
|
|
||||||
Object.keys(uploadables).forEach((key, index) => {
|
|
||||||
formData.append(index.toString(), uploadables[key]);
|
|
||||||
});
|
|
||||||
|
|
||||||
requestInit.body = formData;
|
|
||||||
} else {
|
|
||||||
// Extract slug from URL if present for slug-based routing
|
|
||||||
const slugMatch = window.location.pathname.match(/^\/trust\/([^/]+)/);
|
|
||||||
const slug = slugMatch ? slugMatch[1] : null;
|
|
||||||
|
|
||||||
requestInit.headers = {
|
|
||||||
Accept:
|
|
||||||
"application/graphql-response+json; charset=utf-8, application/json; charset=utf-8",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
...(slug ? { "X-Trust-Slug": slug } : {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
requestInit.body = JSON.stringify({
|
|
||||||
operationName: request.name,
|
|
||||||
query: request.text,
|
|
||||||
variables,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(buildEndpoint(), requestInit);
|
|
||||||
|
|
||||||
if (response.status === 500) {
|
|
||||||
throw new InternalServerError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const json = await response.json();
|
|
||||||
|
|
||||||
if (json.errors) {
|
|
||||||
const errors = json.errors as GraphQLError[];
|
|
||||||
|
|
||||||
if (errors.find(hasUnauthenticatedError)) {
|
|
||||||
throw new UnAuthenticatedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const invalidError = errors.find(hasInvalidError);
|
|
||||||
if (invalidError) {
|
|
||||||
throw new InvalidError(
|
|
||||||
invalidError.message,
|
|
||||||
(invalidError.extensions.field as string) ?? "",
|
|
||||||
(invalidError.extensions.cause as string) ?? "",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Error fetching GraphQL query '${request.name}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
};
|
|
||||||
|
|
||||||
const source = new RecordSource();
|
const source = new RecordSource();
|
||||||
const store = new Store(source, {
|
const store = new Store(source, {
|
||||||
queryCacheExpirationTime: 1 * 60 * 1000,
|
queryCacheExpirationTime: 1 * 60 * 1000,
|
||||||
@@ -164,7 +70,7 @@ const store = new Store(source, {
|
|||||||
|
|
||||||
export const consoleEnvironment = new Environment({
|
export const consoleEnvironment = new Environment({
|
||||||
configName: "trust",
|
configName: "trust",
|
||||||
network: Network.create(fetchRelay),
|
network: Network.create(makeFetchQuery(buildEndpoint())),
|
||||||
store,
|
store,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
function ErrorBoundary({ error: propsError }: { error?: string }) {
|
function ErrorBoundary({ error: propsError }: { error?: string }) {
|
||||||
const error = useRouteError() ?? propsError;
|
const error = useRouteError() ?? propsError;
|
||||||
|
|
||||||
return <PageError error={error?.toString()} />;
|
return <PageError error={error instanceof Error ? error.message : ""} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
@@ -41,7 +41,8 @@ const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
loader: async () => {
|
loader: () => {
|
||||||
|
// eslint-disable-next-line
|
||||||
throw redirect("/overview");
|
throw redirect("/overview");
|
||||||
},
|
},
|
||||||
Component: Fragment,
|
Component: Fragment,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export type NodeOf<T> =
|
export type NodeOf<T>
|
||||||
NonNullable<T> extends
|
= NonNullable<T> extends
|
||||||
| { readonly edges: ReadonlyArray<{ readonly node: infer U }> }
|
| { readonly edges: ReadonlyArray<{ readonly node: infer U }> }
|
||||||
| undefined
|
| undefined
|
||||||
? U
|
? U
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"allowJs": true,
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"lib": ["ES2023"],
|
"lib": ["ES2023"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
@@ -21,5 +22,5 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts", "eslint.config.mjs"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user