diff --git a/app/[locale]/(user)/aimodels/[modelId]/AIModelDetailsPage.tsx b/app/[locale]/(user)/aimodels/[modelId]/AIModelDetailsPage.tsx index 4269055c..3d994490 100644 --- a/app/[locale]/(user)/aimodels/[modelId]/AIModelDetailsPage.tsx +++ b/app/[locale]/(user)/aimodels/[modelId]/AIModelDetailsPage.tsx @@ -1,6 +1,5 @@ 'use client'; -import { graphql } from '@/gql'; import { useQuery } from '@tanstack/react-query'; import { Spinner, Text } from 'opub-ui'; @@ -12,81 +11,7 @@ import { generateJsonLd } from '@/lib/utils'; import Metadata from './components/Metadata'; import PrimaryData from './components/PrimaryData'; import Versions from './components/Versions'; - -const aiModelQuery = graphql(` - query getAIModel($modelId: Int, $publicId: String) { - getAiModel(modelId: $modelId, publicId: $publicId) { - id - urlSlug - name - displayName - description - modelType - domain - metadata - status - isPublic - isActive - createdAt - updatedAt - tags { - id - value - } - sectors { - id - name - } - geographies { - id - name - } - organization { - id - name - logo { - url - } - } - user { - id - fullName - profilePicture { - url - } - } - versions { - id - version - versionNotes - lifecycleStage - isLatest - supportsStreaming - maxTokens - supportedLanguages - status - createdAt - updatedAt - providers { - id - provider - providerModelId - isPrimary - isActive - # API Configuration - apiEndpointUrl - apiHttpMethod - apiTimeoutSeconds - apiAuthType - # HuggingFace Configuration - hfUsePipeline - hfModelClass - framework - } - } - } - } -`); +import { aiModelQuery } from './queries'; export default function AIModelDetailsPage({ modelId, @@ -96,12 +21,7 @@ export default function AIModelDetailsPage({ const { data, isLoading, error } = useQuery( [`aimodel_details_${modelId}`], () => GraphQL(aiModelQuery, {}, aiModelLookup(modelId)), - { - retry: false, - onError: (err) => { - console.error('Error fetching AI model:', err); - }, - } + { retry: false } ); const modelData = data?.getAiModel; diff --git a/app/[locale]/(user)/aimodels/[modelId]/page.tsx b/app/[locale]/(user)/aimodels/[modelId]/page.tsx index 19a06915..24cc91f9 100644 --- a/app/[locale]/(user)/aimodels/[modelId]/page.tsx +++ b/app/[locale]/(user)/aimodels/[modelId]/page.tsx @@ -1,11 +1,13 @@ import { graphql } from '@/gql'; +import { permanentRedirect } from 'next/navigation'; import { GraphQL } from '@/lib/api'; import { - isCanonicalRedirect, - redirectToCanonical, -} from '@/lib/canonical-redirect'; -import { aiModelLookup, publicPageUrl } from '@/lib/public-link'; + aiModelLookup, + isIntegerId, + publicHref, + publicPageUrl, +} from '@/lib/public-link'; import { generatePageMetadata } from '@/lib/utils'; import AIModelDetailsPage from './AIModelDetailsPage'; @@ -29,13 +31,11 @@ export async function generateMetadata({ try { const res = await GraphQL(aiModelMetaQuery, {}, aiModelLookup(modelId)); const model = res?.getAiModel; - await redirectToCanonical('aimodels', modelId, model?.urlSlug); const title = model?.displayName || model?.name || 'AI Model Details'; const description = model?.description || 'Explore AI model details, capabilities, and specifications'; const segment = model?.urlSlug || modelId; - return generatePageMetadata({ title: `${title} | CivicDataSpace`, description, @@ -50,9 +50,8 @@ export async function generateMetadata({ image: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/og.png`, }, }); - } catch (e) { - if (isCanonicalRedirect(e)) throw e; - console.error('Metadata fetch error', e); + } catch (error) { + console.error('Metadata fetch error', error); return generatePageMetadata({ title: 'AI Model Details' }); } } @@ -64,12 +63,14 @@ export default async function Page({ }) { const { modelId } = await params; - try { - const res = await GraphQL(aiModelMetaQuery, {}, aiModelLookup(modelId)); - await redirectToCanonical('aimodels', modelId, res?.getAiModel?.urlSlug); - } catch (error) { - if (isCanonicalRedirect(error)) throw error; - console.error('AI model lookup error', error); + if (isIntegerId(modelId)) { + const res = await GraphQL(aiModelMetaQuery, {}, { + modelId: Number.parseInt(modelId, 10), + }).catch(() => null); + const urlSlug = res?.getAiModel?.urlSlug; + if (urlSlug && urlSlug !== modelId) { + permanentRedirect(publicHref('aimodels', urlSlug, urlSlug)); + } } return ; diff --git a/app/[locale]/(user)/aimodels/[modelId]/queries.ts b/app/[locale]/(user)/aimodels/[modelId]/queries.ts new file mode 100644 index 00000000..d7b2c7b6 --- /dev/null +++ b/app/[locale]/(user)/aimodels/[modelId]/queries.ts @@ -0,0 +1,74 @@ +import { graphql } from '@/gql'; + +export const aiModelQuery = graphql(` + query getAIModel($modelId: Int, $publicId: String) { + getAiModel(modelId: $modelId, publicId: $publicId) { + id + urlSlug + name + displayName + description + modelType + domain + metadata + status + isPublic + isActive + createdAt + updatedAt + tags { + id + value + } + sectors { + id + name + } + geographies { + id + name + } + organization { + id + name + logo { + url + } + } + user { + id + fullName + profilePicture { + url + } + } + versions { + id + version + versionNotes + lifecycleStage + isLatest + supportsStreaming + maxTokens + supportedLanguages + status + createdAt + updatedAt + providers { + id + provider + providerModelId + isPrimary + isActive + apiEndpointUrl + apiHttpMethod + apiTimeoutSeconds + apiAuthType + hfUsePipeline + hfModelClass + framework + } + } + } + } +`); diff --git a/app/[locale]/(user)/datasets/[datasetIdentifier]/DatasetDetailsPage.tsx b/app/[locale]/(user)/datasets/[datasetIdentifier]/DatasetDetailsPage.tsx index b62266ca..76f51899 100644 --- a/app/[locale]/(user)/datasets/[datasetIdentifier]/DatasetDetailsPage.tsx +++ b/app/[locale]/(user)/datasets/[datasetIdentifier]/DatasetDetailsPage.tsx @@ -24,70 +24,7 @@ import { } from './dataset-cover'; import { DatasetCover } from './components/DatasetCover/DatasetCover'; import { DatasetTabs } from './components/DatasetTabs/DatasetTabs'; - -const datasetQuery = graphql(` - query getDataset($datasetId: UUID, $publicId: String) { - getDataset(datasetId: $datasetId, publicId: $publicId) { - id - urlSlug - title - description - downloadCount - created - modified - isIndividualDataset - license - formats - user { - fullName - id - bio - location - profilePicture { - url - } - } - tags { - id - value - } - accessType - metadata { - metadataItem { - id - label - dataType - } - value - } - source { - sourceHomepage - sourceUrl - platform - } - resources { - id - noOfEntries - } - organization { - name - description - location - logo { - url - } - slug - id - } - sectors { - name - } - geographies { - name - } - } - } -`); +import { datasetQuery } from './queries'; const chartCountQuery = graphql(` query datasetVisualisationCount($datasetId: UUID!) { @@ -135,7 +72,6 @@ export default function DatasetDetailsPage({ useTourTrigger(true, 1500); const { trackDataset } = useAnalytics(); - // Load this dataset. Old UUID links and the current urlSlug both resolve. const datasetQueryResult = useQuery(['dataset-cover', datasetId], () => GraphQL(datasetQuery, {}, datasetLookup(datasetId)) ); diff --git a/app/[locale]/(user)/datasets/[datasetIdentifier]/components/DatasetTabs/DatasetTabs.tsx b/app/[locale]/(user)/datasets/[datasetIdentifier]/components/DatasetTabs/DatasetTabs.tsx index 14fb90f3..ec8f3172 100644 --- a/app/[locale]/(user)/datasets/[datasetIdentifier]/components/DatasetTabs/DatasetTabs.tsx +++ b/app/[locale]/(user)/datasets/[datasetIdentifier]/components/DatasetTabs/DatasetTabs.tsx @@ -121,7 +121,9 @@ export function DatasetTabs({

Charts and maps created from this dataset.

- {tab === 'visualisations' ?
: null} + {tab === 'visualisations' ? ( +
+ ) : null}
{ - const params = useParams(); - - const datasetId = Array.isArray(params.datasetIdentifier) - ? params.datasetIdentifier[0] - : params.datasetIdentifier; - +const Details = ({ datasetId }: { datasetId: string }) => { const { data, isLoading } = useQuery( [`chartDetails_${datasetId}`], () => GraphQL(DetailsQuery, {}, { datasetId }), diff --git a/app/[locale]/(user)/datasets/[datasetIdentifier]/page.tsx b/app/[locale]/(user)/datasets/[datasetIdentifier]/page.tsx index dcb794d9..34753d57 100644 --- a/app/[locale]/(user)/datasets/[datasetIdentifier]/page.tsx +++ b/app/[locale]/(user)/datasets/[datasetIdentifier]/page.tsx @@ -1,12 +1,9 @@ import { graphql } from '@/gql'; import { ClientError } from 'graphql-request'; +import { permanentRedirect } from 'next/navigation'; import { GraphQL } from '@/lib/api'; -import { - isCanonicalRedirect, - redirectToCanonical, -} from '@/lib/canonical-redirect'; -import { datasetLookup, publicPageUrl } from '@/lib/public-link'; +import { datasetLookup, isUuid, publicHref, publicPageUrl } from '@/lib/public-link'; import { generatePageMetadata } from '@/lib/utils'; import DatasetDetailsPage from './DatasetDetailsPage'; @@ -15,7 +12,6 @@ const datasetMetaQuery = graphql(` getDataset(datasetId: $datasetId, publicId: $publicId) { title description - id urlSlug tags { id @@ -50,13 +46,7 @@ export async function generateMetadata({ {}, datasetLookup(datasetIdentifier) ); - const dataset = res?.getDataset; - await redirectToCanonical( - 'datasets', - datasetIdentifier, - dataset?.urlSlug - ); const segment = dataset?.urlSlug || datasetIdentifier; return generatePageMetadata({ title: `${dataset?.title ?? ''} | Dataset | CivicDataSpace`, @@ -72,10 +62,9 @@ export async function generateMetadata({ image: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/og.png`, }, }); - } catch (e) { - if (isCanonicalRedirect(e)) throw e; - if (!isUnpublishedDatasetError(e)) { - console.error('Metadata fetch error', e); + } catch (error) { + if (!isUnpublishedDatasetError(error)) { + console.error('Metadata fetch error', error); } return generatePageMetadata({ title: 'Dataset Details' }); } @@ -88,21 +77,13 @@ export default async function Page({ }) { const { datasetIdentifier } = await params; - try { - const res = await GraphQL( - datasetMetaQuery, - {}, - datasetLookup(datasetIdentifier) + if (isUuid(datasetIdentifier)) { + const res = await GraphQL(datasetMetaQuery, {}, { datasetId: datasetIdentifier }).catch( + () => null ); - await redirectToCanonical( - 'datasets', - datasetIdentifier, - res?.getDataset?.urlSlug - ); - } catch (error) { - if (isCanonicalRedirect(error)) throw error; - if (!isUnpublishedDatasetError(error)) { - console.error('Dataset lookup error', error); + const urlSlug = res?.getDataset?.urlSlug; + if (urlSlug && urlSlug !== datasetIdentifier) { + permanentRedirect(publicHref('datasets', urlSlug, urlSlug)); } } diff --git a/app/[locale]/(user)/datasets/[datasetIdentifier]/queries.ts b/app/[locale]/(user)/datasets/[datasetIdentifier]/queries.ts new file mode 100644 index 00000000..be06a72c --- /dev/null +++ b/app/[locale]/(user)/datasets/[datasetIdentifier]/queries.ts @@ -0,0 +1,65 @@ +import { graphql } from '@/gql'; + +export const datasetQuery = graphql(` + query getDataset($datasetId: UUID, $publicId: String) { + getDataset(datasetId: $datasetId, publicId: $publicId) { + id + urlSlug + title + description + downloadCount + created + modified + isIndividualDataset + license + formats + user { + fullName + id + bio + location + profilePicture { + url + } + } + tags { + id + value + } + accessType + metadata { + metadataItem { + id + label + dataType + } + value + } + source { + sourceHomepage + sourceUrl + platform + } + resources { + id + noOfEntries + } + organization { + name + description + location + logo { + url + } + slug + id + } + sectors { + name + } + geographies { + name + } + } + } +`); diff --git a/app/[locale]/(user)/events/[eventSlug]/page.tsx b/app/[locale]/(user)/events/[eventSlug]/page.tsx index 07156fe9..6b561895 100644 --- a/app/[locale]/(user)/events/[eventSlug]/page.tsx +++ b/app/[locale]/(user)/events/[eventSlug]/page.tsx @@ -1,13 +1,9 @@ -import { notFound } from 'next/navigation'; +import { notFound, permanentRedirect } from 'next/navigation'; import { Text } from 'opub-ui'; import { GraphQLPublic } from '@/lib/api'; import { lumaCover } from '@/lib/luma-cover'; -import { - isCanonicalRedirect, - redirectToCanonical, -} from '@/lib/canonical-redirect'; -import { publicPageUrl } from '@/lib/public-link'; +import { publicHref, publicPageUrl } from '@/lib/public-link'; import { generatePageMetadata } from '@/lib/utils'; import BreadCrumbs from '@/components/BreadCrumbs'; import { @@ -31,25 +27,26 @@ export async function generateMetadata({ { publicId: eventSlug } ); const event = result.eventBySlug; - await redirectToCanonical('events', eventSlug, event?.urlSlug); const title = event?.name?.trim() || 'Event'; const segment = event?.urlSlug || eventSlug; - return generatePageMetadata({ - title: `${title} | Events | CivicDataSpace`, - description: event?.location?.trim() || title, - keywords: ['Events', 'CivicDataSpace'], - openGraph: { - type: 'article', - locale: 'en_US', - url: publicPageUrl('events', segment), - title, + return { + ...generatePageMetadata({ + title: `${title} | Events | CivicDataSpace`, description: event?.location?.trim() || title, - siteName: 'CivicDataSpace', - image: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/og.png`, - }, - }); - } catch (error) { - if (isCanonicalRedirect(error)) throw error; + keywords: ['Events', 'CivicDataSpace'], + openGraph: { + type: 'article', + locale: 'en_US', + url: publicPageUrl('events', segment), + title, + description: event?.location?.trim() || title, + siteName: 'CivicDataSpace', + image: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/og.png`, + }, + }), + alternates: { canonical: publicPageUrl('events', segment) }, + }; + } catch { return generatePageMetadata({ title: 'Event | CivicDataSpace', description: 'An event on CivicDataSpace.', @@ -80,7 +77,9 @@ export default async function EventDetailPage({ ).catch(() => null); const event = result?.eventBySlug; if (!event) notFound(); - await redirectToCanonical('events', eventSlug, event.urlSlug); + if (event.urlSlug && event.urlSlug !== eventSlug) { + permanentRedirect(publicHref('events', event.urlSlug, event.urlSlug)); + } const name = event.name?.trim() || 'Untitled event'; const cover = await lumaCover(event.lumaEmbed?.src); diff --git a/app/[locale]/(user)/usecases/[useCaseSlug]/UsecaseDetailsClient.tsx b/app/[locale]/(user)/usecases/[useCaseSlug]/UsecaseDetailsClient.tsx index 401ab23c..7fe48a68 100644 --- a/app/[locale]/(user)/usecases/[useCaseSlug]/UsecaseDetailsClient.tsx +++ b/app/[locale]/(user)/usecases/[useCaseSlug]/UsecaseDetailsClient.tsx @@ -5,11 +5,7 @@ import { useEffect } from 'react'; import { useParams } from 'next/navigation'; import { getUseCaseSummaryExcerpt } from '@/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/content-document'; -import { graphql } from '@/gql'; -import { - UseCasedetailsDocument, - type UseCasedetailsQuery, -} from '@/gql/generated/graphql'; +import { type UseCasedetailsQuery } from '@/gql/generated/graphql'; import { useAnalytics } from '@/hooks/use-analytics'; import { useQuery } from '@tanstack/react-query'; import { Text } from 'opub-ui'; @@ -37,107 +33,13 @@ import { } from '../components/usecase-rail'; import { UseCaseStory } from '../components/UseCaseStory'; import Dashboards from './Dashboards'; - -void graphql(` - query UseCasedetails($publicId: String!) { - useCaseBySlug(publicId: $publicId) { - id - urlSlug - title - summary - created - isIndividualUsecase - user { - fullName - id - profilePicture { - url - } - } - organization { - name - slug - id - logo { - url - } - } - sectors { - id - name - } - geographies { - name - } - sdgs { - id - code - name - } - tags { - id - value - } - logo { - url - } - datasets { - title - id - urlSlug - isIndividualDataset - user { - fullName - profilePicture { - url - } - } - downloadCount - description - organization { - name - logo { - url - } - } - sectors { - name - } - geographies { - name - } - modified - } - contributors { - id - fullName - profilePicture { - url - } - } - supportingOrganizations { - id - slug - name - logo { - url - } - } - partnerOrganizations { - id - slug - name - logo { - url - } - } - } - } -`); +import { useCaseDetailsQuery } from './queries'; const UseCaseDetailClient = () => { const params = useParams(); const { trackUsecase } = useAnalytics(); + const requested = + typeof params.useCaseSlug === 'string' ? params.useCaseSlug : ''; const { data: UseCaseDetails, @@ -146,21 +48,12 @@ const UseCaseDetailClient = () => { } = useQuery( [`fetch_UsecaseDetails_${params.useCaseSlug}`], async () => { - const segment = - typeof params.useCaseSlug === 'string' ? params.useCaseSlug : ''; const result = await GraphQLPublic( - UseCasedetailsDocument, + useCaseDetailsQuery, {}, - { publicId: segment } + { publicId: requested } ); return result; - }, - { - refetchOnMount: true, - refetchOnReconnect: true, - retry: (failureCount) => { - return failureCount < 3; - }, } ); diff --git a/app/[locale]/(user)/usecases/[useCaseSlug]/page.tsx b/app/[locale]/(user)/usecases/[useCaseSlug]/page.tsx index 77292bf5..bf1e79ce 100644 --- a/app/[locale]/(user)/usecases/[useCaseSlug]/page.tsx +++ b/app/[locale]/(user)/usecases/[useCaseSlug]/page.tsx @@ -1,26 +1,19 @@ import { Metadata } from 'next'; import { graphql } from '@/gql'; +import { permanentRedirect } from 'next/navigation'; import { GraphQLPublic } from '@/lib/api'; -import { - isCanonicalRedirect, - redirectToCanonical, -} from '@/lib/canonical-redirect'; -import { isIntegerId, publicPageUrl } from '@/lib/public-link'; +import { isIntegerId, publicHref, publicPageUrl } from '@/lib/public-link'; import { generatePageMetadata } from '@/lib/utils'; import { getUseCaseSummaryExcerpt } from '@/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/content-document'; import UseCaseDetailClient from './UsecaseDetailsClient'; -const UseCaseInfoQuery = graphql(` +const useCaseMetaQuery = graphql(` query UseCaseInfo($publicId: String!) { useCaseBySlug(publicId: $publicId) { - id title summary urlSlug - logo { - url - } tags { id value @@ -29,16 +22,12 @@ const UseCaseInfoQuery = graphql(` } `); -const UseCaseLegacyInfoQuery = graphql(` +const useCaseLegacyMetaQuery = graphql(` query UseCaseLegacyInfo($pk: ID!) { useCase(pk: $pk) { - id title summary urlSlug - logo { - url - } tags { id value @@ -47,67 +36,47 @@ const UseCaseLegacyInfoQuery = graphql(` } `); -async function loadUseCase(segment: string) { - if (isIntegerId(segment)) { - const data = await GraphQLPublic( - UseCaseLegacyInfoQuery, - {}, - { pk: segment } - ); - return data.useCase; - } - - const data = await GraphQLPublic(UseCaseInfoQuery, {}, { publicId: segment }); - return data.useCaseBySlug; -} - export async function generateMetadata({ params, }: { params: Promise<{ useCaseSlug: string }>; }): Promise { const { useCaseSlug } = await params; - try { - const UseCase = await loadUseCase(useCaseSlug); - await redirectToCanonical('usecases', useCaseSlug, UseCase?.urlSlug); - const segment = UseCase?.urlSlug || useCaseSlug; + const useCase = isIntegerId(useCaseSlug) + ? ( + await GraphQLPublic(useCaseLegacyMetaQuery, {}, { pk: useCaseSlug }).catch( + () => null + ) + )?.useCase + : ( + await GraphQLPublic(useCaseMetaQuery, {}, { publicId: useCaseSlug }).catch( + () => null + ) + )?.useCaseBySlug; + const segment = useCase?.urlSlug || useCaseSlug; + const description = + getUseCaseSummaryExcerpt(useCase?.summary) || + `Explore open data and curated datasets in the ${useCase?.title ?? 'this'} sector.`; - return generatePageMetadata({ - title: `${UseCase?.title} | Sector Data | CivicDataSpace`, - description: - getUseCaseSummaryExcerpt(UseCase?.summary) || - `Explore open data and curated datasets in the ${UseCase?.title} sector.`, - keywords: UseCase?.tags?.map((tag) => tag.value) || [], - openGraph: { - type: 'article', - locale: 'en_US', - url: publicPageUrl('usecases', segment), - title: `${UseCase?.title} | Sector Data | CivicDataSpace`, - description: - getUseCaseSummaryExcerpt(UseCase?.summary) || - `Explore open data and curated datasets in the ${UseCase?.title} sector.`, - siteName: 'CivicDataSpace', - image: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/og.png`, - }, - }); - } catch (error) { - if (isCanonicalRedirect(error)) throw error; - console.error('Error fetching use case info:', error); - return generatePageMetadata({ - title: `Use Case Details | CivicDataSpace`, - description: `Explore open data and curated datasets in this use case.`, - keywords: ['usecase', 'data', 'civic', 'open data'], - openGraph: { - type: 'article', - locale: 'en_US', - url: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/usecases/${useCaseSlug}`, - title: `Use Case Details | CivicDataSpace`, - description: `Explore open data and curated datasets in this use case.`, - siteName: 'CivicDataSpace', - image: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/og.png`, - }, - }); - } + return generatePageMetadata({ + title: `${useCase?.title ?? 'Use Case Details'} | Sector Data | CivicDataSpace`, + description, + keywords: useCase?.tags?.map((tag) => tag.value) || [ + 'usecase', + 'data', + 'civic', + 'open data', + ], + openGraph: { + type: 'article', + locale: 'en_US', + url: publicPageUrl('usecases', segment), + title: `${useCase?.title ?? 'Use Case Details'} | Sector Data | CivicDataSpace`, + description, + siteName: 'CivicDataSpace', + image: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/og.png`, + }, + }); } export default async function Page({ @@ -117,12 +86,16 @@ export default async function Page({ }) { const { useCaseSlug } = await params; - try { - const useCase = await loadUseCase(useCaseSlug); - await redirectToCanonical('usecases', useCaseSlug, useCase?.urlSlug); - } catch (error) { - if (isCanonicalRedirect(error)) throw error; - console.error('Use case lookup error', error); + if (isIntegerId(useCaseSlug)) { + const data = await GraphQLPublic( + useCaseLegacyMetaQuery, + {}, + { pk: useCaseSlug } + ).catch(() => null); + const urlSlug = data?.useCase?.urlSlug; + if (urlSlug && urlSlug !== useCaseSlug) { + permanentRedirect(publicHref('usecases', urlSlug, urlSlug)); + } } return ; diff --git a/app/[locale]/(user)/usecases/[useCaseSlug]/queries.ts b/app/[locale]/(user)/usecases/[useCaseSlug]/queries.ts new file mode 100644 index 00000000..4a2503b1 --- /dev/null +++ b/app/[locale]/(user)/usecases/[useCaseSlug]/queries.ts @@ -0,0 +1,98 @@ +import { graphql } from '@/gql'; + +export const useCaseDetailsQuery = graphql(` + query UseCasedetails($publicId: String!) { + useCaseBySlug(publicId: $publicId) { + id + urlSlug + title + summary + created + isIndividualUsecase + user { + fullName + id + profilePicture { + url + } + } + organization { + name + slug + id + logo { + url + } + } + sectors { + id + name + } + geographies { + name + } + sdgs { + id + code + name + } + tags { + id + value + } + logo { + url + } + datasets { + title + id + urlSlug + isIndividualDataset + user { + fullName + profilePicture { + url + } + } + downloadCount + description + organization { + name + logo { + url + } + } + sectors { + name + } + geographies { + name + } + modified + } + contributors { + id + fullName + profilePicture { + url + } + } + supportingOrganizations { + id + slug + name + logo { + url + } + } + partnerOrganizations { + id + slug + name + logo { + url + } + } + } + } +`); diff --git a/lib/canonical-redirect.ts b/lib/canonical-redirect.ts deleted file mode 100644 index 8e23e4b5..00000000 --- a/lib/canonical-redirect.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { getLocale } from 'next-intl/server'; -import { permanentRedirect } from 'next/navigation'; - -import { publicHref, type PublicSection } from '@/lib/public-link'; - -/** Next.js implements redirects by throwing. Callers must rethrow this error. */ -export function isCanonicalRedirect(error: unknown): boolean { - return ( - typeof error === 'object' && - error !== null && - 'digest' in error && - String((error as { digest?: unknown }).digest).startsWith('NEXT_REDIRECT') - ); -} - -/** 308 to the current urlSlug when the address is an old id or a renamed link. */ -export async function redirectToCanonical( - section: PublicSection, - requested: string, - urlSlug: string | null | undefined -): Promise { - const canonical = urlSlug?.trim(); - if (!canonical || canonical === requested) return; - - const locale = await getLocale(); - const prefix = locale === 'en' ? '' : `/${locale}`; - permanentRedirect(`${prefix}${publicHref(section, canonical, canonical)}`); -}