Skip to content

Fix type validation with public project artifact table type #341

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 11 additions & 60 deletions src/components/Invites/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,15 @@ import Link from 'next/link';
import { useAtomValue } from 'jotai';
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { captureException } from '@sentry/nextjs';

import { acceptInvite as sendInviteAcceptRequest, getInviteDetails } from './api';
import { getErrorUrl, getLabUrl, getProjectUrl } from './utils';
import { getErrorUrl, getLabUrl } from './utils';
import Logo from '@/components/logo/as-svg';
import { InviteDetailsData } from '@/types/virtual-lab/invites';
import sessionAtom from '@/state/session';

import inviteBgImgSrc from '@/../public/images/invite/invite-bg.webp';
import { isVlmError } from '@/types/virtual-lab/common';
import { UserActiveSubscriptionResponse } from '@/api/virtual-lab-svc/queries/types';
import { getUserActiveSubscription } from '@/api/virtual-lab-svc/queries/subscription';

export default function InviteLoader() {
const inviteToken = useSearchParams().get('token');
Expand All @@ -27,48 +24,21 @@ export default function InviteLoader() {
const session = useAtomValue(sessionAtom);

const [inviteDetails, setInviteDetails] = useState<InviteDetailsData | null>(null);
const [subscription, setSubscription] = useState<UserActiveSubscriptionResponse | null>(null);
const [processing, setProcessing] = useState<boolean>(false);

const hasPaidPlan = subscription?.subscription.type === 'paid';

const goToUpgrade = () => {
const planUpgradeSuccessRedirectUrl = `/app/invite?token=${inviteToken}`;
const planUpgradePageUrl = '/app/virtual-lab/account/subscription';
const params = new URLSearchParams({ planUpgradeSuccessRedirectUrl });

return router.push(`${planUpgradePageUrl}?${params}`);
};

const acceptInvite = async () => {
if (!session?.accessToken || !inviteToken) {
throw new Error('Missing session or invite token');
}

setProcessing(true);

const res = await sendInviteAcceptRequest(session?.accessToken, inviteToken);

if (isVlmError(res)) {
router.push(getErrorUrl(res, session?.accessToken, inviteToken));
return;
}

switch (res.data.origin) {
case 'Lab':
router.push(getLabUrl(res.data));
return;
case 'Project':
router.push(getProjectUrl(res.data));
return;
default:
captureException(
new Error(
`User could not accept invite ${inviteToken} because unknown origin returned by server`
),
{ extra: res.data.origin }
);
router.push(getErrorUrl(res, session?.accessToken, inviteToken));
if (res.data.origin) {
router.push(getLabUrl(res.data));
}
};

Expand All @@ -78,14 +48,11 @@ export default function InviteLoader() {
}

const init = async () => {
const currentSubscription = await getUserActiveSubscription();

const inviteData = await getInviteDetails(session?.accessToken, inviteToken);
if (isVlmError(inviteData)) {
return router.push(getErrorUrl(inviteData, session?.accessToken, inviteToken));
}

setSubscription(currentSubscription);
setInviteDetails(inviteData.data);
};

Expand Down Expand Up @@ -116,12 +83,6 @@ export default function InviteLoader() {
{inviteDetails.virtual_lab_name}
</p>

{!hasPaidPlan && (
<p className="mt-4 text-xl text-primary-9">
Only users with a paid subscription can join other&apos;s Labs.
</p>
)}

<div className="mt-12 flex justify-center gap-8 text-lg">
<Link
className="border-gray-4 border border-solid px-12 py-8"
Expand All @@ -130,24 +91,14 @@ export default function InviteLoader() {
Browse platform
</Link>

{hasPaidPlan ? (
<button
onClick={acceptInvite}
className="bg-secondary-2 px-12 py-8 text-white disabled:text-gray-400"
type="button"
disabled={processing}
>
Join Virtual Lab
</button>
) : (
<button
onClick={goToUpgrade}
className="bg-primary-5 px-12 py-8 text-white disabled:text-gray-400"
type="button"
>
Upgrade subscription
</button>
)}
<button
onClick={acceptInvite}
className="bg-secondary-2 px-12 py-8 text-white disabled:text-gray-400"
type="button"
disabled={processing}
>
Join Virtual Lab
</button>
</div>
</div>
</div>
Expand Down
9 changes: 2 additions & 7 deletions src/components/PublicProjects/HeaderPublicProject.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import Image from 'next/image';
import { HeaderPublicProjectProps } from './type';

export default function HeaderPublicProject({
title,
headerImage,
}: {
title: string;
headerImage: string;
}) {
export default function HeaderPublicProject({ title, headerImage }: HeaderPublicProjectProps) {
return (
<header className="relative flex !h-36 min-h-36 w-full flex-col justify-center gap-1 bg-primary-8 px-8 text-white">
<h2 className="relative z-10 text-base uppercase tracking-wider">Public Project</h2>
Expand Down
26 changes: 9 additions & 17 deletions src/components/PublicProjects/NavigationSections.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import { usePathname, useRouter } from 'next/navigation';
import { Sections } from '@/types/public-projects';

export default function NavigationSections({
activeSection,
setActiveSection,
section,
updateSection,
}: {
activeSection: string;
setActiveSection: (section: string) => void;
section: string;
updateSection: (section: Sections) => void;
}) {
const router = useRouter();
const pathname = usePathname();

const SHOWCASE_TABS: string[] = ['description', 'artifacts', 'notebooks'];
const SHOWCASE_TABS: Sections[] = ['description', 'artifacts', 'notebooks'];

const handleTabChange = (tab: string) => {
setActiveSection(tab);

const params = new URLSearchParams(window.location.search);
params.set('section', tab); // Add or update query param

router.push(`${pathname}?${params.toString()}`);
updateSection(tab as Sections);
};

return (
Expand All @@ -30,8 +22,8 @@ export default function NavigationSections({
className="flex w-44 items-center justify-center py-3 text-lg font-semibold uppercase tracking-wider text-primary-9"
onClick={() => handleTabChange(tab)}
style={{
background: activeSection === tab ? '#fff' : 'transparent',
color: activeSection === tab ? '#002766' : 'white',
background: section === tab ? '#fff' : 'transparent',
color: section === tab ? '#002766' : 'white',
}}
>
{tab}
Expand Down
57 changes: 35 additions & 22 deletions src/components/PublicProjects/PublicProjectMain.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { useState } from 'react';
import type { Parser } from 'nuqs';
import { parseAsString, useQueryState } from 'nuqs';

import { tryType } from '../LandingPage/content';

Expand All @@ -14,6 +15,7 @@ import DescriptionSection from './sections/Description';
import NotebookSection from './sections/Notebook';

import { useSanity } from '@/services/sanity';
import { Sections } from '@/types/public-projects';

const isShowCaseProjectProps = (data: unknown): data is ShowCaseProjectQueryType => {
return tryType('ShowCaseProjectProps', data, {
Expand All @@ -40,9 +42,9 @@ const isShowCaseProjectProps = (data: unknown): data is ShowCaseProjectQueryType
url: 'string',
title: ['|', 'null', 'string'],
alt: 'string',
hasCaption: 'boolean',
hasCaption: ['|', 'null', 'boolean'],
caption: ['|', 'null', 'undefined', 'string'],
useTimestamps: 'boolean',
useTimestamps: ['|', 'null', 'boolean'],
timestamps: [
'|',
'null',
Expand Down Expand Up @@ -87,7 +89,7 @@ const isShowCaseProjectProps = (data: unknown): data is ShowCaseProjectQueryType
validated: 'boolean',
mType: 'string',
eType: 'string',
hasMorphologyThumbnail: 'boolean',
hasMorphologyThumbnail: ['|', 'null', 'undefined', 'boolean'],
morphologyId: 'string',
morphology: ['|', 'null', 'string'],
traceFileId: 'string',
Expand Down Expand Up @@ -124,7 +126,7 @@ const isShowCaseProjectProps = (data: unknown): data is ShowCaseProjectQueryType
brainRegion: 'string',
mType: ['|', 'null', 'string'],
eType: 'string',
hasMorphologyThumbnail: 'boolean',
hasMorphologyThumbnail: ['|', 'null', 'undefined', 'boolean'],
// morphology: 'string',
modelCumulatedScore: 'number',
species: 'string',
Expand All @@ -142,6 +144,10 @@ const isShowCaseProjectProps = (data: unknown): data is ShowCaseProjectQueryType
name: ['|', 'null', 'string'],
readMe: 'unknown',
url: ['|', 'null', 'string'],
objectOfInterest: ['|', 'null', 'string'],
scale: ['|', 'null', 'string'],
authors: ['|', 'null', 'string'],
creationDate: ['|', 'null', 'string'],
},
],
],
Expand All @@ -150,34 +156,41 @@ const isShowCaseProjectProps = (data: unknown): data is ShowCaseProjectQueryType
};

export default function PublicProjectMain({ slug }: { slug: string }) {
const content = useSanity(singleCaseQuery(slug), isShowCaseProjectProps) ?? null;
const content =
useSanity<ShowCaseProjectQueryType>(singleCaseQuery(slug), isShowCaseProjectProps) ?? null;

const [activeSection, setActiveSection] = useState<string>('description');
const [section, updateSection] = useQueryState(
'description',
parseAsString.withDefault('description') as Parser<Sections>
);
const handleTabChange = (tab: Sections) => updateSection(tab);

let activeSectionContent;

switch (activeSection) {
case 'description':
activeSectionContent = content !== null && <DescriptionSection content={content} />;
break;
case 'artifacts':
activeSectionContent = content !== null && <ArtifactsSection content={content} />;
break;
case 'notebooks':
activeSectionContent = content !== null && <NotebookSection content={content} />;
break;
default:
activeSectionContent = content !== null && <DescriptionSection content={content} />;
break;
if (content !== null) {
switch (section) {
case 'description':
activeSectionContent = <DescriptionSection content={content} />;
break;
case 'artifacts':
activeSectionContent = <ArtifactsSection content={content} />;
break;
case 'notebooks':
activeSectionContent = <NotebookSection content={content} />;
break;
default:
activeSectionContent = <DescriptionSection content={content} />;
break;
}
}

return (
content !== null && (
<div className="relative flex min-h-screen w-full flex-col gap-y-12 bg-primary-9 py-6 pl-28 pr-10">
<HeaderPublicProject title={content.name} headerImage={content?.heroImage} />
<HeaderPublicProject title={content.name} headerImage={content.heroImage} />

<div className="flex flex-col">
<NavigationSections activeSection={activeSection} setActiveSection={setActiveSection} />
<NavigationSections section={section ?? 'description'} updateSection={handleTabChange} />
<div className="scroll-behavior: smooth; flex min-h-[70vh] w-full flex-row gap-x-12 bg-white p-8 text-primary-9">
{activeSectionContent}
</div>
Expand Down
22 changes: 15 additions & 7 deletions src/components/PublicProjects/api/fetchSingleCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,19 @@ const singleCaseQuery = (slug: string) =>
meModelsList[] {
'file': file.asset->url,
name,
brainRegion,
hasMorphologyThumbnail,
"morphology": morphology.asset->url,
hasTraceThumbnail,
"trace": trace.asset->url,
validated,
brainRegion,
species,
mType,
eType,
createdBy,
creationDate,
morphologyId,
hasMorphologyThumbnail,
"morphology": morphology.asset->url,
traceFileId,
hasTraceThumbnail,
"trace": trace.asset->url,
url,
_type,
},
Expand All @@ -57,16 +60,21 @@ const singleCaseQuery = (slug: string) =>
brainRegion,
mType,
eType,
hasMorphologyThumbnail,
morphologyName,
modelCumulatedScore,
species,
validated,
contributor,
creationDate,
},
notebook[] {
name,
url,
readMe,
url,
objectOfInterest,
scale,
authors,
creationDate,
},
_updatedAt,
}`;
Expand Down
5 changes: 5 additions & 0 deletions src/components/PublicProjects/type/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,8 @@ export type ShowCaseProjectQueryType = {
minimalMeModel: MinimalMeModelProps[];
_updatedAt: string;
};

export type HeaderPublicProjectProps = {
title: string;
headerImage: string;
};
Loading