diff --git a/apps/platform/.env b/apps/platform/.env index 10e7d0df2..4ceb89976 100644 --- a/apps/platform/.env +++ b/apps/platform/.env @@ -1,3 +1,3 @@ -VITE_API_URL=https://api.genetics.dev.opentargets.xyz/api/v4/graphql +VITE_API_URL=https://api.platform.dev.opentargets.xyz/api/v4/graphql VITE_AI_API_URL=https://dev-ai-api-w37vlfsidq-ew.a.run.app VITE_PROFILE=default \ No newline at end of file diff --git a/apps/platform/src/pages/VariantPage/Profile.tsx b/apps/platform/src/pages/VariantPage/Profile.tsx index e9f5e5346..f723fa17b 100644 --- a/apps/platform/src/pages/VariantPage/Profile.tsx +++ b/apps/platform/src/pages/VariantPage/Profile.tsx @@ -18,9 +18,7 @@ import QTLCredibleSetsSummary from "sections/src/variant/QTLCredibleSets/Summary import client from "../../client"; import ProfileHeader from "./ProfileHeader"; -const PharmacogenomicsSection = lazy( - () => import("sections/src/variant/Pharmacogenomics/Body") -); +const PharmacogenomicsSection = lazy(() => import("sections/src/variant/Pharmacogenomics/Body")); const InSilicoPredictorsSection = lazy( () => import("sections/src/variant/InSilicoPredictors/Body") ); @@ -28,15 +26,9 @@ const VariantEffectPredictorSection = lazy( () => import("sections/src/variant/VariantEffectPredictor/Body") ); const EVASection = lazy(() => import("sections/src/variant/EVA/Body")); -const UniProtVariantsSection = lazy( - () => import("sections/src/variant/UniProtVariants/Body") -); -const GWASCredibleSetsSection = lazy( - () => import("sections/src/variant/GWASCredibleSets/Body") -); -const QTLCredibleSetsSection = lazy( - () => import("sections/src/variant/QTLCredibleSets/Body") -); +const UniProtVariantsSection = lazy(() => import("sections/src/variant/UniProtVariants/Body")); +const GWASCredibleSetsSection = lazy(() => import("sections/src/variant/GWASCredibleSets/Body")); +const QTLCredibleSetsSection = lazy(() => import("sections/src/variant/QTLCredibleSets/Body")); const summaries = [ PharmacogenomicsSummary, @@ -49,10 +41,7 @@ const summaries = [ ]; const VARIANT = "variant"; -const VARIANT_PROFILE_SUMMARY_FRAGMENT = summaryUtils.createSummaryFragment( - summaries, - "Variant" -); +const VARIANT_PROFILE_SUMMARY_FRAGMENT = summaryUtils.createSummaryFragment(summaries, "Variant"); const VARIANT_PROFILE_QUERY = gql` query VariantProfileQuery($variantId: String!) { variant(variantId: $variantId) { @@ -80,19 +69,16 @@ function Profile({ varId }: ProfileProps) { - + - }> - - }> @@ -111,6 +97,9 @@ function Profile({ varId }: ProfileProps) { }> + }> + + ); diff --git a/packages/sections/src/constants.js b/packages/sections/src/constants.js index 65d6d54f6..6aa97ee09 100644 --- a/packages/sections/src/constants.js +++ b/packages/sections/src/constants.js @@ -66,13 +66,18 @@ export const sourceMap = { export const clinvarStarMap = { "practice guideline": 4, + "SuSiE fine-mapped credible set with in-sample LD": 4, "reviewed by expert panel": 3, + "SuSiE fine-mapped credible set with out-of-sample LD": 3, "criteria provided, multiple submitters, no conflicts": 2, + "PICS fine-mapped credible set extracted from summary statistics": 2, "criteria provided, conflicting interpretations": 1, "criteria provided, single submitter": 1, + "PICS fine-mapped credible set based on reported top hit": 1, "no assertion for the individual variant": 0, "no assertion criteria provided": 0, "no assertion provided": 0, + "Unknown confidence": 0, }; export const formatMap = { diff --git a/packages/sections/src/variant/GWASCredibleSets/Body.tsx b/packages/sections/src/variant/GWASCredibleSets/Body.tsx index 6c0d1719a..dcf219686 100644 --- a/packages/sections/src/variant/GWASCredibleSets/Body.tsx +++ b/packages/sections/src/variant/GWASCredibleSets/Body.tsx @@ -1,7 +1,16 @@ import { useQuery } from "@apollo/client"; -import { Link, SectionItem, ScientificNotation, DisplayVariantId, OtTable } from "ui"; +import { + Link, + SectionItem, + ScientificNotation, + DisplayVariantId, + OtTable, + Tooltip, + ClinvarStars, + OtScoreLinearBar, +} from "ui"; import { Box, Chip } from "@mui/material"; -import { naLabel } from "../../constants"; +import { clinvarStarMap, naLabel } from "../../constants"; import { definition } from "."; import Description from "./Description"; import GWAS_CREDIBLE_SETS_QUERY from "./GWASCredibleSetsQuery.gql"; @@ -23,11 +32,11 @@ function getColumns({ }: getColumnsType) { return [ { - id: "view", - label: "Details", - renderCell: ({ studyLocusId }) => view, - filterValue: false, - exportValue: false, + id: "studyLocusId", + label: "More details", + renderCell: ({ studyLocusId }) => ( + {studyLocusId} + ), }, { id: "leadVariant", @@ -151,26 +160,18 @@ function getColumns({ exportValue: ({ locus }) => posteriorProbabilities.get(locus)?.toFixed(3), }, { - id: "ldr2", - label: "LD (r²)", - filterValue: false, - tooltip: ( - <> - Linkage disequilibrium with the fixed page variant ( - - ). - - ), - renderCell: ({ locus }) => { - const r2 = locus?.find(obj => obj.variant?.id === id)?.r2Overall; - if (typeof r2 !== "number") return naLabel; - return r2.toFixed(2); + id: "confidence", + label: "Confidence", + sortable: true, + renderCell: ({ confidence }) => { + if (!confidence) return naLabel; + return ( + + + + ); }, + filterValue: ({ confidence }) => clinvarStarMap[confidence], }, { id: "finemappingMethod", @@ -179,27 +180,28 @@ function getColumns({ { id: "topL2G", label: "Top L2G", - filterValue: ({ strongestLocus2gene }) => strongestLocus2gene?.target.approvedSymbol, + filterValue: ({ l2Gpredictions }) => l2Gpredictions?.target.approvedSymbol, tooltip: "Top gene prioritised by our locus-to-gene model", - renderCell: ({ strongestLocus2gene }) => { - if (!strongestLocus2gene?.target) return naLabel; - const { target } = strongestLocus2gene; + renderCell: ({ l2Gpredictions }) => { + if (!l2Gpredictions[0]?.target) return naLabel; + const { target } = l2Gpredictions[0]; return {target.approvedSymbol}; }, - exportValue: ({ strongestLocus2gene }) => strongestLocus2gene?.target.approvedSymbol, + exportValue: ({ l2Gpredictions }) => l2Gpredictions?.target.approvedSymbol, }, { id: "l2gScore", label: "L2G score", - comparator: (rowA, rowB) => - rowA?.strongestLocus2gene?.score - rowB?.strongestLocus2gene?.score, + comparator: (rowA, rowB) => rowA?.l2Gpredictions[0]?.score - rowB?.l2Gpredictions[0]?.score, sortable: true, - filterValue: false, - renderCell: ({ strongestLocus2gene }) => { - if (typeof strongestLocus2gene?.score !== "number") return naLabel; - return strongestLocus2gene.score.toFixed(3); + renderCell: ({ l2Gpredictions }) => { + if (!l2Gpredictions[0]?.score) return naLabel; + return ( + + + + ); }, - exportValue: ({ strongestLocus2gene }) => strongestLocus2gene?.score, }, { id: "credibleSetSize", diff --git a/packages/sections/src/variant/GWASCredibleSets/GWASCredibleSetsQuery.gql b/packages/sections/src/variant/GWASCredibleSets/GWASCredibleSetsQuery.gql index 8c590f09b..6ae3e5968 100644 --- a/packages/sections/src/variant/GWASCredibleSets/GWASCredibleSetsQuery.gql +++ b/packages/sections/src/variant/GWASCredibleSets/GWASCredibleSetsQuery.gql @@ -5,6 +5,11 @@ query GWASCredibleSetsQuery($variantId: String!) { alternateAllele credibleSets(studyTypes: [gwas], page: { size: 2000, index: 0 }) { studyLocusId + pValueMantissa + pValueExponent + beta + finemappingMethod + confidence variant { id chromosome @@ -20,9 +25,6 @@ query GWASCredibleSetsQuery($variantId: String!) { id } } - pValueMantissa - pValueExponent - beta locus { variant { id @@ -30,8 +32,7 @@ query GWASCredibleSetsQuery($variantId: String!) { r2Overall posteriorProbability } - finemappingMethod - strongestLocus2gene { + l2Gpredictions(size: 1) { target { id approvedSymbol @@ -40,4 +41,4 @@ query GWASCredibleSetsQuery($variantId: String!) { } } } -} \ No newline at end of file +} diff --git a/packages/sections/src/variant/QTLCredibleSets/Body.tsx b/packages/sections/src/variant/QTLCredibleSets/Body.tsx index bb9bdab39..f1d36c99a 100644 --- a/packages/sections/src/variant/QTLCredibleSets/Body.tsx +++ b/packages/sections/src/variant/QTLCredibleSets/Body.tsx @@ -1,11 +1,20 @@ import { useQuery } from "@apollo/client"; -import { Link, SectionItem, ScientificNotation, DisplayVariantId, OtTable } from "ui"; +import { + Link, + SectionItem, + ScientificNotation, + DisplayVariantId, + OtTable, + Tooltip, + ClinvarStars, +} from "ui"; import { Box, Chip } from "@mui/material"; -import { naLabel } from "../../constants"; +import { clinvarStarMap, naLabel } from "../../constants"; import { definition } from "."; import Description from "./Description"; import QTL_CREDIBLE_SETS_QUERY from "./QTLCredibleSetsQuery.gql"; import { mantissaExponentComparator, variantComparator } from "../../utils/comparators"; +import { ReactNode } from "react"; type getColumnsType = { id: string; @@ -22,11 +31,11 @@ function getColumns({ }: getColumnsType) { return [ { - id: "view", - label: "Details", - renderCell: ({ studyLocusId }) => view, - filterValue: false, - exportValue: false, + id: "studyLocusId", + label: "More details", + renderCell: ({ studyLocusId }) => ( + {studyLocusId} + ), }, { id: "leadVariant", @@ -89,14 +98,14 @@ function getColumns({ }, }, { - id: "study.biosample.biosampleId", + id: "study", label: "Affected tissue/cell", renderCell: ({ study }) => { const biosampleId = study?.biosample?.biosampleId; if (!biosampleId) return naLabel; return ( - {biosampleId} + {study?.biosample?.biosampleName} ); }, @@ -104,7 +113,7 @@ function getColumns({ { id: "study.condition", label: "Condition", - renderCell: () => Not in API, + renderCell: ({ study }) => study?.condition || naLabel, }, { id: "pValue", @@ -133,6 +142,7 @@ function getColumns({ label: "Beta", filterValue: false, tooltip: "Beta with respect to the ALT allele", + sortable: true, renderCell: ({ beta }) => { if (typeof beta !== "number") return naLabel; return beta.toPrecision(3); @@ -160,6 +170,20 @@ function getColumns({ renderCell: ({ locus }) => posteriorProbabilities.get(locus)?.toFixed(3) ?? naLabel, exportValue: ({ locus }) => posteriorProbabilities.get(locus)?.toFixed(3), }, + { + id: "confidence", + label: "Confidence", + sortable: true, + renderCell: ({ confidence }) => { + if (!confidence) return naLabel; + return ( + + + + ); + }, + filterValue: ({ confidence }) => clinvarStarMap[confidence], + }, { id: "finemappingMethod", label: "Finemapping method", @@ -181,7 +205,7 @@ type BodyProps = { entity: string; }; -function Body({ id, entity }: BodyProps) { +function Body({ id, entity }: BodyProps): ReactNode { const variables = { variantId: id, }; diff --git a/packages/sections/src/variant/QTLCredibleSets/Description.tsx b/packages/sections/src/variant/QTLCredibleSets/Description.tsx index 79a642951..6fc754ac4 100644 --- a/packages/sections/src/variant/QTLCredibleSets/Description.tsx +++ b/packages/sections/src/variant/QTLCredibleSets/Description.tsx @@ -1,3 +1,4 @@ +import { ReactElement } from "react"; import { Link, DisplayVariantId } from "ui"; type DescriptionProps = { @@ -6,7 +7,11 @@ type DescriptionProps = { alternateAllele: string; }; -function Description({ variantId, referenceAllele, alternateAllele }: DescriptionProps) { +function Description({ + variantId, + referenceAllele, + alternateAllele, +}: DescriptionProps): ReactElement { return ( <> molQTL 99% credible sets containing{" "} @@ -17,12 +22,9 @@ function Description({ variantId, referenceAllele, alternateAllele }: Descriptio alternateAllele={alternateAllele} /> - . Source{" "} - - eQTL Catalog - + . Source Open Targets ); } -export default Description; \ No newline at end of file +export default Description; diff --git a/packages/sections/src/variant/QTLCredibleSets/QTLCredibleSetsQuery.gql b/packages/sections/src/variant/QTLCredibleSets/QTLCredibleSetsQuery.gql index 20d1276b3..f21c2d27e 100644 --- a/packages/sections/src/variant/QTLCredibleSets/QTLCredibleSetsQuery.gql +++ b/packages/sections/src/variant/QTLCredibleSets/QTLCredibleSetsQuery.gql @@ -3,11 +3,14 @@ query QTLCredibleSetsQuery($variantId: String!) { id referenceAllele alternateAllele - credibleSets( - studyTypes: [sqtl, pqtl, eqtl, tuqtl], - page: { size: 2000, index: 0 } - ) { + credibleSets(studyTypes: [sqtl, pqtl, eqtl, tuqtl], page: { size: 2000, index: 0 }) { studyLocusId + pValueMantissa + pValueExponent + beta + finemappingMethod + confidence + variant { id chromosome @@ -18,24 +21,22 @@ query QTLCredibleSetsQuery($variantId: String!) { study { studyId studyType + condition target { id approvedSymbol } biosample { biosampleId - } + biosampleName + } } - pValueMantissa - pValueExponent - beta locus { variant { id } posteriorProbability } - finemappingMethod } } -} \ No newline at end of file +} diff --git a/packages/sections/src/variant/VariantEffectPredictor/Body.tsx b/packages/sections/src/variant/VariantEffectPredictor/Body.tsx index 4beb7e4d2..d4d686d0d 100644 --- a/packages/sections/src/variant/VariantEffectPredictor/Body.tsx +++ b/packages/sections/src/variant/VariantEffectPredictor/Body.tsx @@ -1,5 +1,5 @@ import { useQuery } from "@apollo/client"; -import { Box } from "@mui/material"; +import { Box, Chip } from "@mui/material"; import { Link, SectionItem, Tooltip, OtTable } from "ui"; import { Fragment } from "react"; import { definition } from "../VariantEffectPredictor"; @@ -12,32 +12,67 @@ function formatVariantConsequenceLabel(label) { return label.replace(/_/g, " "); } +function isNumber(value: any): boolean { + return typeof value === "number" && isFinite(value); +} + const columns = [ { id: "target.approvedSymbol", label: "Gene", - comparator: (a, b) => a.transcriptIndex - b.transcriptIndex, - renderCell: ({ target, transcriptId }) => { + sortable: true, + renderCell: ({ target, transcriptId, uniprotAccessions }) => { if (!target) return naLabel; let displayElement = {target.approvedSymbol}; + let tooltipContent = <>; if (transcriptId) { - displayElement = ( - - Ensembl canonical transcript:{" "} - - {transcriptId} + tooltipContent = ( + + Ensembl canonical transcript: +
+ + {transcriptId} + +
+ ); + } + if (uniprotAccessions?.length) { + tooltipContent = ( + <> + {tooltipContent} + Protein: +
+ {uniprotAccessions.map((id, i, arr) => ( + + + {id} - - } - showHelpIcon - > - {displayElement} -
+ {i < arr.length - 1 && ", "} + + ))} + + ); + } + displayElement = ( + + {displayElement} + + ); + + if (target?.biotype === "protein_coding") { + displayElement = ( + <> + {displayElement}{" "} + {" "} + ); } return displayElement; @@ -46,17 +81,46 @@ const columns = [ { id: "variantConsequences.label", label: "Predicted consequence", - renderCell: ({ variantConsequences }) => - variantConsequences.length - ? variantConsequences.map(({ id, label }, i, arr) => ( - - - {formatVariantConsequenceLabel(label)} - - {i < arr.length - 1 && ", "} - - )) - : naLabel, + renderCell: ({ variantConsequences, aminoAcidChange, codons, uniprotAccessions }) => { + if (!variantConsequences?.length) return naLabel; + let displayElement = variantConsequences.map(({ id, label }, i, arr) => ( + + + {formatVariantConsequenceLabel(label)} + + {i < arr.length - 1 && ", "} + + )); + if (aminoAcidChange) + displayElement = ( + + {displayElement} {" "} + + + ); + if (codons) { + const tooltipContent = ( + <> + Trancript consequence: +
+ {codons} +
+ + ); + + displayElement = ( + + {displayElement} + + ); + } + return displayElement; + }, exportValue: ({ variantConsequences }) => { return variantConsequences .map(({ label }) => { @@ -70,39 +134,23 @@ const columns = [ label: "Impact", renderCell: ({ impact }) => impact?.toLowerCase?.() ?? naLabel, }, - { - id: "aminoAcidChange", - label: "Amino acid change", - renderCell: ({ aminoAcidChange }) => aminoAcidChange ?? naLabel, - }, - { - id: "codons", - label: "Coding change", - renderCell: ({ codons }) => codons ?? naLabel, - }, { id: "distanceFromFootprint", label: "Distance from footprint", + numeric: true, + sortable: true, + renderCell: ({ distanceFromFootprint }) => + isNumber(distanceFromFootprint) + ? parseInt(distanceFromFootprint, 10).toLocaleString() + : naLabel, }, { id: "distanceFromTss", label: "Distance from start site", - }, - { - id: "uniprotAccession", - label: "Uniprot accession", - renderCell: ({ uniprotAccessions }) => - uniprotAccessions?.length - ? uniprotAccessions.map((id, i, arr) => ( - - - {id} - - {i < arr.length - 1 && ", "} - - )) - : naLabel, - exportValue: ({ uniprotAccessions }) => (uniprotAccessions ?? []).join(", "), + numeric: true, + sortable: true, + renderCell: ({ distanceFromTss }) => + isNumber(distanceFromTss) ? parseInt(distanceFromTss, 10).toLocaleString() : naLabel, }, ]; @@ -133,14 +181,15 @@ export function Body({ id, entity }: BodyProps) { /> )} renderBody={() => { + const sortedRows = [...request.data?.variant.transcriptConsequences]; + sortedRows.sort((a, b) => a.transcriptIndex - b.transcriptIndex); return ( ); diff --git a/packages/sections/src/variant/VariantEffectPredictor/Description.tsx b/packages/sections/src/variant/VariantEffectPredictor/Description.tsx index e5d3c19b1..bc1f99b9d 100644 --- a/packages/sections/src/variant/VariantEffectPredictor/Description.tsx +++ b/packages/sections/src/variant/VariantEffectPredictor/Description.tsx @@ -9,7 +9,7 @@ type DescriptionProps = { function Description({ variantId, referenceAllele, alternateAllele }: DescriptionProps) { return ( <> - Variant consequence prediction for{" "} + Variant consequence prediction for transcripts in the genemonic region of{" "} . {" "}Source:{" "} - VEP + Ensembl VEP ); diff --git a/packages/sections/src/variant/VariantEffectPredictor/VariantEffectPredictorQuery.gql b/packages/sections/src/variant/VariantEffectPredictor/VariantEffectPredictorQuery.gql index 2c2548254..9cb784c88 100644 --- a/packages/sections/src/variant/VariantEffectPredictor/VariantEffectPredictorQuery.gql +++ b/packages/sections/src/variant/VariantEffectPredictor/VariantEffectPredictorQuery.gql @@ -1,5 +1,5 @@ query VariantEffectPredictorQuery($variantId: String!) { - variant(variantId: $variantId){ + variant(variantId: $variantId) { id transcriptConsequences { variantConsequences { @@ -14,6 +14,7 @@ query VariantEffectPredictorQuery($variantId: String!) { target { id approvedSymbol + biotype } impact consequenceScore @@ -26,4 +27,4 @@ query VariantEffectPredictorQuery($variantId: String!) { referenceAllele alternateAllele } -} \ No newline at end of file +} diff --git a/packages/sections/src/variant/VariantEffectPredictor/index.ts b/packages/sections/src/variant/VariantEffectPredictor/index.ts index ac4c2a089..3f34eebbc 100644 --- a/packages/sections/src/variant/VariantEffectPredictor/index.ts +++ b/packages/sections/src/variant/VariantEffectPredictor/index.ts @@ -1,7 +1,7 @@ const id = "variant_effect_predictor"; export const definition = { id, - name: "Variant Effect Predictor (VEP)", - shortName: "VE", + name: "Transcript consequences", + shortName: "TC", hasData: data => data?.transcriptConsequences?.length > 0, }; diff --git a/packages/ui/src/components/Chip.tsx b/packages/ui/src/components/Chip.tsx index 29dc9e535..10a629e41 100644 --- a/packages/ui/src/components/Chip.tsx +++ b/packages/ui/src/components/Chip.tsx @@ -1,6 +1,7 @@ import classNames from "classnames"; import { makeStyles } from "@mui/styles"; import { Chip as MUIChip } from "@mui/material"; +import { ReactElement } from "react"; const useStyles = makeStyles({ chip: { @@ -12,10 +13,10 @@ const useStyles = makeStyles({ }); type ChipProps = { - className: string; - disabled: boolean; - label: string; - title: string; + className?: string; + disabled?: boolean; + label: ReactElement; + title?: string; }; export default function Chip({ className, label, title, disabled }: ChipProps) { diff --git a/packages/ui/src/components/Link.tsx b/packages/ui/src/components/Link.tsx index 59d3f2662..415235e0d 100644 --- a/packages/ui/src/components/Link.tsx +++ b/packages/ui/src/components/Link.tsx @@ -43,9 +43,9 @@ type LinkProptypes = { className?: string; to: string; onClick?: () => void | null; - external: boolean; + external?: boolean; newTab?: boolean; - footer: boolean; + footer?: boolean; tooltip?: unknown; children: ReactNode; ariaLabel?: string; diff --git a/packages/ui/src/components/OtScoreLinearBar.tsx b/packages/ui/src/components/OtScoreLinearBar.tsx new file mode 100644 index 000000000..f9078c48e --- /dev/null +++ b/packages/ui/src/components/OtScoreLinearBar.tsx @@ -0,0 +1,16 @@ +import LinearProgress, { linearProgressClasses } from "@mui/material/LinearProgress"; +import { styled } from "@mui/material"; + +const OtScoreLinearBar = styled(LinearProgress)(({ theme }) => ({ + height: 8, + borderRadius: 5, + maxWidth: 70, + [`&.${linearProgressClasses.colorPrimary}`]: { + backgroundColor: theme.palette.grey[200], + }, + [`& .${linearProgressClasses.bar}`]: { + borderRadius: 5, + backgroundColor: theme.palette.primary.main, + }, +})); +export default OtScoreLinearBar; diff --git a/packages/ui/src/components/Tooltip.jsx b/packages/ui/src/components/Tooltip.jsx index e5200b300..1db390e98 100644 --- a/packages/ui/src/components/Tooltip.jsx +++ b/packages/ui/src/components/Tooltip.jsx @@ -2,7 +2,14 @@ import { makeStyles } from "@mui/styles"; import { Tooltip as MUITooltip } from "@mui/material"; import { merge } from "lodash"; -function Tooltip({ style, children, title, showHelpIcon = false, placement = "top", ...props }) { +function Tooltip({ + style = "", + children, + title, + showHelpIcon = false, + placement = "top", + ...props +}) { const classes = makeStyles(theme => merge(style, { tooltip: { diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index c56cf6cfc..310d83585 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -37,6 +37,7 @@ export { default as Legend } from "./components/Legend"; export { default as ApiPlaygroundDrawer } from "./components/ApiPlaygroundDrawer"; export { default as OtTable } from "./components/OtTable/OtTable"; export { default as OtPopper } from "./components/OtPopper"; +export { default as OtScoreLinearBar } from "./components/OtScoreLinearBar"; export { default as EmptyPage } from "./pages/EmptyPage"; export { default as NotFoundPage } from "./pages/NotFoundPage";