Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -368,19 +368,25 @@ export default function AIModelDetailsPage() {
);

const [prevId, setPrevId] = useState(params.id);
if (params.id !== prevId) {
// Fill the form when this model is first loaded, not on every refetch.
// A refetch is a new object for the same id and was wiping in-progress text.
const [hydratedModelId, setHydratedModelId] = useState<string | null>(null);
const modelId = model?.id != null ? String(model.id) : null;
const routeChanged = params.id !== prevId;

if (routeChanged) {
setPrevId(params.id);
setHydratedModelId(null);
setFormData(emptyAIModelForm());
}

const [prevModel, setPrevModel] = useState<typeof model | undefined>(
undefined
);
if (model !== prevModel) {
setPrevModel(model);
if (model) {
setFormData(formDataFromModel(model));
}
if (
model &&
modelId === params.id &&
modelId !== (routeChanged ? null : hydratedModelId)
) {
setHydratedModelId(modelId);
setFormData(formDataFromModel(model));
}

const handleInputChange = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React from 'react';
import { useParams } from 'next/navigation';
import { signIn } from 'next-auth/react';
import { graphql } from '@/gql';
import {
SectorInfoDocument,
Expand Down Expand Up @@ -306,13 +307,13 @@ const UserProfile = () => {

const openChangePassword = async () => {
try {
const response = await fetch('/api/auth/change-password');
const data = (await response.json()) as { url?: string };
if (!data.url) {
toast.error('Could not open the Keycloak password page.');
return;
}
window.location.assign(data.url);
// kc_action skips the account "Signing in" page and opens the
// update-password form. NextAuth adds state so the user returns here.
await signIn(
'keycloak',
{ callbackUrl: window.location.href },
{ kc_action: 'UPDATE_PASSWORD' }
);
} catch {
toast.error('Could not open the Keycloak password page.');
}
Expand Down Expand Up @@ -539,7 +540,6 @@ const UserProfile = () => {
kind="tertiary"
size="slim"
onClick={openChangePassword}
disabled
icon={<IconArrowRight size={16} />}
>
Change password
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import MediaImage from '@/components/MediaImage';
import { ContentEditor } from '../../components/ContentEditor';
import {
extractEmbedUrl,
hasContentBlocks,
parseUseCaseContent,
serializeUseCaseContent,
type UseCaseContentDocument,
Expand Down Expand Up @@ -346,8 +347,14 @@ export default function BuilderPage() {
}, [savingUseCase, addingDashboard, savingDashboard, setStatus]);

useEffect(() => {
setBuilderCompleted(isUseCaseBuilderComplete({ title, logo }));
}, [title, logo, setBuilderCompleted]);
setBuilderCompleted(
isUseCaseBuilderComplete({
title,
logo,
summary: serializeUseCaseContent(contentDoc),
})
);
}, [title, logo, contentDoc, setBuilderCompleted]);

const handleSaveDashboard = () => {
const link = extractEmbedUrl(embedCode);
Expand Down Expand Up @@ -434,6 +441,10 @@ export default function BuilderPage() {
stepShowErrors && !title.trim() ? 'Enter a use case title' : undefined;
const thumbnailError =
stepShowErrors && !logo ? 'Upload a thumbnail image' : undefined;
const contentError =
stepShowErrors && !hasContentBlocks(contentDoc)
? 'Add at least one content block'
: undefined;
const extractedDashboardLink = extractEmbedUrl(embedCode);
const dashboardLinkLooksLikeUrl = Boolean(
extractedDashboardLink &&
Expand Down Expand Up @@ -563,7 +574,14 @@ export default function BuilderPage() {
</SectionCard>

<SectionCard
title="Content"
title={
<>
Content{' '}
<Text as="span" color="critical">
*
</Text>
</>
}
description="Add the main information that explains this content."
className={styles.overflowVisible}
>
Expand All @@ -589,6 +607,13 @@ export default function BuilderPage() {
persistBuilder({ document });
}}
/>
{contentError ? (
<div id="use-case-content-error" className="mt-2">
<Text variant="bodySm" color="critical">
{contentError}
</Text>
</div>
) : null}
</div>
</SectionCard>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,48 @@ function sdgLabel(item: {
return `${num}. ${item.name ?? ''}`;
}

const emptyConnectForm: ConnectFormData = {
tags: [],
sdgs: [],
sectors: [],
geographies: [],
};

function toConnectForm(useCase: {
tags?: Array<{ id: string; value?: string | null }> | null;
sdgs?: Array<{
id: string;
number?: number | null;
code?: string | null;
name?: string | null;
}> | null;
sectors?: Array<{ id: string; name?: string | null }> | null;
geographies?: Array<{ id: string; name?: string | null }> | null;
}): ConnectFormData {
return {
tags:
useCase.tags?.map((item) => ({
label: item.value ?? '',
value: item.id,
})) ?? [],
sdgs:
useCase.sdgs?.map((item) => ({
label: sdgLabel(item),
value: item.id,
})) ?? [],
sectors:
useCase.sectors?.map((item) => ({
label: item.name ?? '',
value: item.id,
})) ?? [],
geographies:
useCase.geographies?.map((item) => ({
label: item.name ?? '',
value: item.id,
})) ?? [],
};
}

export default function ConnectPage() {
const params = useParams<{
entityType: string;
Expand All @@ -274,16 +316,6 @@ export default function ConnectPage() {
} = useUseCaseEditStatus();
const ownerArgs = { [params.entityType]: params.entitySlug };

const [formData, setFormData] = useState<ConnectFormData>({
tags: [],
sdgs: [],
sectors: [],
geographies: [],
});
const formRef = useRef(formData);
useEffect(() => {
formRef.current = formData;
});
const [userSearch, setUserSearch] = useState('');
const [datasetSearch, setDatasetSearch] = useState('');
const [debouncedDatasetSearch, setDebouncedDatasetSearch] = useState('');
Expand Down Expand Up @@ -374,33 +406,22 @@ export default function ConnectPage() {
]);

const useCase = useCaseQuery.data?.useCases?.[0];
const [formData, setFormData] = useState<ConnectFormData>(() =>
useCase ? toConnectForm(useCase) : emptyConnectForm
);
const formRef = useRef(formData);
const [syncedUseCase, setSyncedUseCase] = useState(useCase);
const [metadataReady, setMetadataReady] = useState(Boolean(useCase));
if (useCase && useCase !== syncedUseCase) {
setSyncedUseCase(useCase);
setFormData({
tags:
useCase.tags?.map((item) => ({
label: item.value ?? '',
value: item.id,
})) ?? [],
sdgs:
useCase.sdgs?.map((item) => ({
label: sdgLabel(item),
value: item.id,
})) ?? [],
sectors:
useCase.sectors?.map((item) => ({
label: item.name ?? '',
value: item.id,
})) ?? [],
geographies:
useCase.geographies?.map((item) => ({
label: item.name ?? '',
value: item.id,
})) ?? [],
});
setFormData(toConnectForm(useCase));
setMetadataReady(true);
}

useEffect(() => {
formRef.current = formData;
});

useEffect(() => {
const timer = window.setTimeout(() => {
setDebouncedDatasetSearch(datasetSearch.trim());
Expand Down Expand Up @@ -439,6 +460,7 @@ export default function ConnectPage() {

const persistMetadata = useCallback(
(next = formRef.current) => {
if (!metadataReady) return Promise.resolve();
return saveMetadata({
id: params.id,
metadata: [],
Expand All @@ -450,7 +472,7 @@ export default function ConnectPage() {
),
}).catch(() => undefined);
},
[params.id, saveMetadata]
[metadataReady, params.id, saveMetadata]
);

const handleClassChange = (
Expand Down Expand Up @@ -828,7 +850,7 @@ export default function ConnectPage() {
}}
/>
{datasetError ? (
<div className="mt-2">
<div id="connected-datasets-error" className="mt-2">
<Text variant="bodySm" color="critical">
{datasetError}
</Text>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { ReactNode, useEffect } from 'react';
import { ReactNode, useEffect, useRef } from 'react';
import { useParams, usePathname, useRouter } from 'next/navigation';
import { graphql } from '@/gql';
import { UseCaseInputPartial } from '@/gql/generated/graphql';
Expand Down Expand Up @@ -54,13 +54,21 @@ const UpdateUseCaseTitleMutation = graphql(`

function StepErrorSync() {
const { showErrors } = useStepperStep();
const { setStepShowErrors } = useUseCaseEditStatus();
const { stepShowErrors, setStepShowErrors } = useUseCaseEditStatus();
const markerRef = useRef<HTMLSpanElement>(null);

useEffect(() => {
setStepShowErrors(showErrors);
}, [showErrors, setStepShowErrors]);

return null;
useEffect(() => {
if (!stepShowErrors) return;
markerRef.current?.parentElement
?.querySelector<HTMLElement>('[id$="-error"]')
?.scrollIntoView({ block: 'center' });
}, [stepShowErrors]);

return <span ref={markerRef} hidden />;
}

function stepContent(isActive: boolean, children: ReactNode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,13 @@ export type UseCaseEditStep = 'builder' | 'connect' | 'publish';
export function isUseCaseBuilderComplete(useCase: {
title?: string | null;
logo?: unknown;
summary?: string | null;
}): boolean {
return Boolean(useCase.title?.trim()) && useCase.logo != null;
return (
Boolean(useCase.title?.trim()) &&
useCase.logo != null &&
getUseCaseHasContent(useCase.summary)
);
}

export function isUseCaseConnectComplete(useCase: {
Expand Down
18 changes: 0 additions & 18 deletions app/api/auth/change-password/route.ts

This file was deleted.

Loading