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