Skip to content

Commit d49c965

Browse files
committed
feat: implement PublishersListing and PublisherPage components with metadata generation
1 parent 7a568aa commit d49c965

File tree

4 files changed

+337
-242
lines changed

4 files changed

+337
-242
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import Image from 'next/image';
5+
import { graphql } from '@/gql';
6+
import { useQuery } from '@tanstack/react-query';
7+
import { Button, ButtonGroup, Spinner, Text } from 'opub-ui';
8+
9+
import { GraphQL } from '@/lib/api';
10+
import { cn } from '@/lib/utils';
11+
import BreadCrumbs from '@/components/BreadCrumbs';
12+
import PublisherCard from './PublisherCard';
13+
14+
const getAllPublishers: any = graphql(`
15+
query PublishersList {
16+
getPublishers {
17+
__typename
18+
... on TypeOrganization {
19+
name
20+
id
21+
description
22+
logo {
23+
url
24+
}
25+
membersCount
26+
publishedUseCasesCount
27+
publishedDatasetsCount
28+
}
29+
... on TypeUser {
30+
fullName
31+
id
32+
bio
33+
profilePicture {
34+
url
35+
}
36+
publishedUseCasesCount
37+
publishedDatasetsCount
38+
}
39+
}
40+
}
41+
`);
42+
43+
const PublishersListingPage = () => {
44+
const [type, setType] = useState<'all' | 'org' | 'pub'>('all');
45+
const Details: {
46+
data: any;
47+
isLoading: boolean;
48+
isError: boolean;
49+
refetch: any;
50+
} = useQuery(['publishers_list_page'], () =>
51+
GraphQL(getAllPublishers, {}, [])
52+
);
53+
54+
type PublisherType = 'all' | 'org' | 'pub';
55+
const publisherButtons: { key: PublisherType; label: string }[] = [
56+
{ key: 'all', label: 'All Publishers' },
57+
{ key: 'org', label: 'Organizations' },
58+
{ key: 'pub', label: 'Individual Publishers' },
59+
];
60+
61+
const filteredPublishers = Details?.data?.getPublishers?.filter(
62+
(publisher: any) => {
63+
if (type === 'all') return true;
64+
if (type === 'pub') return publisher.__typename === 'TypeUser';
65+
if (type === 'org') return publisher.__typename === 'TypeOrganization';
66+
return false;
67+
}
68+
);
69+
70+
return (
71+
<main >
72+
<BreadCrumbs
73+
data={[
74+
{ href: '/', label: 'Home' },
75+
{ href: '#', label: 'Publishers' },
76+
]}
77+
/>
78+
<>
79+
<>
80+
<div className="w-full">
81+
<div className=" bg-primaryBlue">
82+
<div className=" container flex flex-col-reverse items-center gap-8 py-10 lg:flex-row ">
83+
<div className="flex flex-col gap-5 ">
84+
<Text
85+
variant="heading2xl"
86+
fontWeight="bold"
87+
color="onBgDefault"
88+
>
89+
Our Publishers{' '}
90+
</Text>
91+
<Text
92+
variant="headingLg"
93+
color="onBgDefault"
94+
fontWeight="regular"
95+
className=" leading-3 lg:leading-5"
96+
>
97+
Meet the data providers powering CivicDataSpace — explore
98+
individual and organizational publishers across domains who
99+
are opening up data for impact and transparency.
100+
</Text>
101+
</div>
102+
<div className="flex items-center justify-center gap-2 px-3 ">
103+
<Image
104+
src={'/s2.png'}
105+
alt={'s1'}
106+
width={130}
107+
height={100}
108+
className="h-auto w-[80px] sm:w-[100px] md:w-[120px] lg:w-[130px]"
109+
priority
110+
/>
111+
<Image
112+
src={'/s4.png'}
113+
alt={'s1'}
114+
width={232}
115+
height={100}
116+
className="h-auto w-[140px] sm:w-[180px] md:w-[200px] lg:w-[230px]"
117+
priority
118+
/>
119+
<Image
120+
src={'/s1.png'}
121+
alt={'s1'}
122+
width={130}
123+
height={100}
124+
className="h-auto w-[80px] sm:w-[100px] md:w-[120px] lg:w-[130px]"
125+
priority
126+
/>
127+
</div>
128+
</div>
129+
</div>
130+
131+
<div className="container flex flex-col gap-4 py-10 lg:gap-6">
132+
<Text variant="heading2xl" fontWeight="bold">
133+
Explore Publishers
134+
</Text>
135+
<div>
136+
<ButtonGroup>
137+
<div className="flex flex-wrap gap-4">
138+
{publisherButtons.map((btn) => (
139+
<Button
140+
key={btn.key}
141+
onClick={() => setType(btn.key)}
142+
className={cn(
143+
' w-72 rounded-full py-3',
144+
type === btn.key
145+
? 'bg-tertiaryAccent'
146+
: 'border-1 border-solid border-tertiaryAccent bg-surfaceDefault'
147+
)}
148+
>
149+
<Text variant="headingLg" fontWeight="semibold">
150+
{btn.label}
151+
</Text>
152+
</Button>
153+
))}
154+
</div>
155+
</ButtonGroup>
156+
</div>
157+
{Details.isLoading ? (
158+
<div className="m-4 flex justify-center">
159+
<Spinner />
160+
</div>
161+
) : (
162+
Details.data &&
163+
Details.data.getPublishers.length > 0 && (
164+
<PublisherCard data={filteredPublishers} />
165+
)
166+
)}
167+
</div>
168+
</div>
169+
</>
170+
</>
171+
</main>
172+
);
173+
};
174+
175+
export default PublishersListingPage;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import { graphql } from '@/gql';
5+
import { useQuery } from '@tanstack/react-query';
6+
import { Spinner } from 'opub-ui';
7+
8+
import { GraphQL } from '@/lib/api';
9+
import BreadCrumbs from '@/components/BreadCrumbs';
10+
import ProfileDetails from '../components/ProfileDetails';
11+
import SidebarCard from '../components/SidebarCard';
12+
13+
const userInfoQuery: any = graphql(`
14+
query UserData($userId: ID!) {
15+
userById(userId: $userId) {
16+
id
17+
bio
18+
dateJoined
19+
contributedSectorsCount
20+
location
21+
twitterProfile
22+
githubProfile
23+
fullName
24+
profilePicture {
25+
url
26+
}
27+
publishedUseCasesCount
28+
publishedDatasetsCount
29+
linkedinProfile
30+
}
31+
}
32+
`);
33+
34+
const PublisherPageClient = ({ publisherSlug }: { publisherSlug: string }) => {
35+
const userInfo: any = useQuery([`${publisherSlug}`], () =>
36+
GraphQL(
37+
userInfoQuery,
38+
{
39+
// Entity Headers if present
40+
},
41+
{ userId: publisherSlug }
42+
)
43+
);
44+
45+
return (
46+
<main className="bg-primaryBlue">
47+
<BreadCrumbs
48+
data={[
49+
{ href: '/', label: 'Home' },
50+
{ href: '/publishers', label: 'Publishers' },
51+
{ href: '#', label: `${userInfo?.data?.userById?.fullName || ''} ` },
52+
]}
53+
/>
54+
{
55+
<div className="container py-10 text-surfaceDefault">
56+
<div className="flex flex-wrap gap-10 lg:flex-nowrap">
57+
<div className="w-full lg:w-1/4">
58+
{userInfo?.isLoading ? (
59+
<div className="m-4 flex justify-center rounded-2 bg-surfaceDefault p-4">
60+
<Spinner color="highlight" />
61+
</div>
62+
) : (
63+
<SidebarCard data={userInfo?.data?.userById} type="Publisher" />
64+
)}
65+
</div>
66+
<div className="w-full">
67+
{userInfo?.isLoading ? (
68+
<div className="m-4 flex justify-center rounded-2 bg-surfaceDefault p-4">
69+
<Spinner color="highlight" />
70+
</div>
71+
) : (
72+
<ProfileDetails
73+
data={userInfo?.data?.userById}
74+
type="Publisher"
75+
/>
76+
)}
77+
</div>
78+
</div>
79+
</div>
80+
}
81+
</main>
82+
);
83+
};
84+
85+
export default PublisherPageClient;
Lines changed: 45 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,62 @@
1-
'use client';
2-
3-
import React from 'react';
4-
import { useParams } from 'next/navigation';
1+
import { Metadata } from 'next';
52
import { graphql } from '@/gql';
6-
import { useQuery } from '@tanstack/react-query';
7-
import { Spinner } from 'opub-ui';
83

94
import { GraphQL } from '@/lib/api';
10-
import BreadCrumbs from '@/components/BreadCrumbs';
11-
import ProfileDetails from '../components/ProfileDetails';
12-
import SidebarCard from '../components/SidebarCard';
5+
import { generatePageMetadata } from '@/lib/utils';
6+
import PublisherPageClient from './PublisherPageClient';
137

14-
const userInfoQuery: any = graphql(`
15-
query UserData($userId: ID!) {
8+
const userInfo = graphql(`
9+
query Userdetails($userId: ID!) {
1610
userById(userId: $userId) {
1711
id
1812
bio
19-
dateJoined
20-
contributedSectorsCount
21-
location
22-
twitterProfile
23-
githubProfile
2413
fullName
2514
profilePicture {
2615
url
2716
}
28-
publishedUseCasesCount
29-
publishedDatasetsCount
30-
linkedinProfile
3117
}
3218
}
3319
`);
20+
export async function generateMetadata({
21+
params,
22+
}: {
23+
params: { publisherSlug: string };
24+
}): Promise<Metadata> {
25+
const data = await GraphQL(userInfo, {}, { userId: params.publisherSlug });
3426

35-
const PublisherPage = () => {
36-
const params = useParams();
37-
const userInfo: any = useQuery([`${params.publisherSlug}`], () =>
38-
GraphQL(
39-
userInfoQuery,
40-
{
41-
// Entity Headers if present
42-
},
43-
{ userId: params.publisherSlug }
44-
)
45-
);
27+
const user = data.userById;
4628

47-
return (
48-
<main className="bg-primaryBlue">
49-
<BreadCrumbs
50-
data={[
51-
{ href: '/', label: 'Home' },
52-
{ href: '/publishers', label: 'Publishers' },
53-
{ href: '#', label: `${userInfo?.data?.userById?.fullName || ''} ` },
54-
]}
55-
/>
56-
{
57-
<div className="container py-10 text-surfaceDefault">
58-
<div className="flex flex-wrap gap-10 lg:flex-nowrap">
59-
<div className="w-full lg:w-1/4">
60-
{userInfo?.isLoading ? (
61-
<div className="m-4 flex justify-center rounded-2 bg-surfaceDefault p-4">
62-
<Spinner color="highlight" />
63-
</div>
64-
) : (
65-
<SidebarCard data={userInfo?.data?.userById} type="Publisher" />
66-
)}
67-
</div>
68-
<div className="w-full">
69-
{userInfo?.isLoading ? (
70-
<div className="m-4 flex justify-center rounded-2 bg-surfaceDefault p-4">
71-
<Spinner color="highlight" />
72-
</div>
73-
) : (
74-
<ProfileDetails data={userInfo?.data?.userById} type="Publisher" />
75-
)}
76-
</div>
77-
</div>
78-
</div>
79-
}
80-
</main>
81-
);
82-
};
29+
return generatePageMetadata({
30+
title: `${user?.fullName} | Publisher on CivicDataSpace`,
31+
description:
32+
user?.bio || 'Explore datasets and use cases by this publisher.',
33+
keywords: [
34+
'CivicDataSpace Publisher',
35+
'Open Data Contributor',
36+
'Use Case Publisher',
37+
'Dataset Publisher',
38+
'CivicTech',
39+
'Open Government Data',
40+
],
41+
openGraph: {
42+
title: `${user?.fullName} | Publisher on CivicDataSpace`,
43+
description:
44+
user?.bio || 'Explore datasets and use cases by this publisher.',
45+
type: 'profile',
46+
siteName: 'CivicDataSpace',
47+
url: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/publishers/${params.publisherSlug}`,
48+
image: user?.profilePicture?.url
49+
? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${user.profilePicture.url}`
50+
: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/og.png`,
51+
locale: 'en_US',
52+
},
53+
});
54+
}
8355

84-
export default PublisherPage;
56+
export default function PublisherPage({
57+
params,
58+
}: {
59+
params: { publisherSlug: string };
60+
}) {
61+
return <PublisherPageClient publisherSlug={params.publisherSlug} />;
62+
}

0 commit comments

Comments
 (0)