Skip to content

Commit d27706c

Browse files
committed
website page feature
1 parent aae5bdc commit d27706c

File tree

12 files changed

+292
-2
lines changed

12 files changed

+292
-2
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { notFound } from 'next/navigation';
2+
import { Metadata } from 'next';
3+
import { dataSiteBySubdomain, dataSitePageBySlug } from '@/lib/data/site';
4+
import { TWebsitePageProps } from '@/types';
5+
import SitePage from '@/components/website/page';
6+
7+
export async function generateMetadata(
8+
props: TWebsitePageProps<{ slug: string }>
9+
): Promise<Metadata> {
10+
const {
11+
params: { subdomain, slug },
12+
} = props;
13+
const site = await dataSiteBySubdomain(subdomain);
14+
if (!site) notFound();
15+
const item = await dataSitePageBySlug(site.id, slug);
16+
if (!item) notFound();
17+
18+
return {
19+
title: item.title,
20+
description: item.description ?? site.name,
21+
};
22+
}
23+
export default async function Page(props: TWebsitePageProps<{ slug: string }>) {
24+
const {
25+
params: { subdomain, slug },
26+
} = props;
27+
const site = await dataSiteBySubdomain(subdomain);
28+
if (!site) notFound();
29+
const item = await dataSitePageBySlug(site.id, slug);
30+
if (!item) notFound();
31+
32+
return (
33+
<>
34+
<SitePage
35+
site={site}
36+
data={{ item }}
37+
page={{
38+
...props,
39+
name: 'page/[slug]',
40+
title: item.title,
41+
subTitle: item.description ?? '',
42+
}}
43+
/>
44+
</>
45+
);
46+
}

src/components/share/item-news.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ import { useEffect, useState } from 'react';
77

88
export default function NewsShareItem({
99
item,
10+
isPage,
1011
...props
1112
}: Partial<ShareItemProps> & {
1213
item: TNewsOrWebNewsItemBySlug;
14+
isPage?: true;
1315
}) {
1416
const [url, setUrl] = useState('');
1517
useEffect(() => {
16-
setUrl(`${window.origin}/berita/${item.slug}`);
17-
}, [item]);
18+
setUrl(`${window.origin}/${isPage ? 'page' : 'berita'}/${item.slug}`);
19+
}, [item, isPage]);
1820
return (
1921
<ShareItem
2022
url={url}

src/components/website/templates/base/pages/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ const pages = {
88
dokumen: dynamic(() => import('./dokumen/page')),
99
'dokumen/[slug]': dynamic(() => import('./dokumen/[slug]/page')),
1010
'profil/[slug]': dynamic(() => import('./profil/[slug]/page')),
11+
'page/[slug]': dynamic(() => import('./page/[slug]/page')),
1112
};
1213
export default pages;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import BlurImage from '@/components/blur-image';
2+
import ContentBlocks from '@/components/content-blocks';
3+
import BaseIcon from '@/components/icons/base-icon';
4+
import NewsShareItem from '@/components/share/item-news';
5+
import { SiteContextType } from '@/components/website/provider';
6+
import { TWebPageItemBySlug } from '@/types';
7+
8+
export default function Page({
9+
data: { item },
10+
site,
11+
}: SiteContextType<{ slug: string }, { item: TWebPageItemBySlug }>) {
12+
return (
13+
<article className="px-5">
14+
<section className="h-full grid grid-cols-1 gap-8 lg:grid-cols-[70%,25%] xl:gap-[72px]">
15+
<div className="flex flex-col gap-7">
16+
<div className="w-full min-h-screen">
17+
<div className="flex flex-col gap-2 mt-3">
18+
{item.image_cover && (
19+
<BlurImage
20+
src={item.image_cover.url}
21+
alt={item.title}
22+
width="0"
23+
height="0"
24+
sizes="100vw"
25+
className="image-zoom w-full h-full rounded-lg"
26+
/>
27+
)}
28+
</div>
29+
{item.content && item.content.blocks && (
30+
<div className="prose max-w-none">
31+
<ContentBlocks {...item.content} />
32+
</div>
33+
)}
34+
</div>
35+
<p className="font-lora text-gray-800"></p>
36+
</div>
37+
<div className="my-5 w-full">
38+
<div className="flex flex-col gap-2 lg:sticky lg:top-[88px] w-full overflow-auto">
39+
<div className="flex flex-col gap-3 mb-5 overflow-y-auto">
40+
<p className="inline-flex gap-3 font-lato text-xs text-blue-gray-200 leading-5">
41+
<BaseIcon
42+
icon="share"
43+
className="self-start text-primary w-5 h-5"
44+
/>
45+
Bagikan
46+
</p>
47+
</div>
48+
<NewsShareItem item={item as any} isPage />
49+
</div>
50+
</div>
51+
</section>
52+
</article>
53+
);
54+
}

src/lib/api/resource/items/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ export { default as organization_pejabat } from './organization_pejabat';
1111
export { default as organizations } from './organizations';
1212
export { default as public_services } from './public_services';
1313
export { default as web_aduan_publik } from './web_aduan_publik';
14+
export { default as web_menu } from './web_menu';
1415
export { default as web_news } from './web_news';
16+
export { default as web_pages } from './web_pages';
1517
export { default as websites } from './websites';
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import _ from 'lodash';
2+
import { createApiResource } from '../base';
3+
4+
export default createApiResource('web_menu', {
5+
baseFields: [
6+
'id',
7+
'slug',
8+
'title',
9+
'status',
10+
'title',
11+
'sort',
12+
{ pages: ['id', 'title', 'status', 'slug', 'sort'] },
13+
{ website: ['id', 'name', 'subdomain'] },
14+
{
15+
user_created: ['first_name', 'last_name', 'content_author_name'],
16+
},
17+
{ user_updated: ['first_name', 'last_name', 'content_author_name'] },
18+
'date_updated',
19+
'date_created',
20+
],
21+
baseQuery: {
22+
sort: ['sort'],
23+
filter: { status: { _eq: 'published' } },
24+
},
25+
baseNormalizer: (data) => {
26+
return data;
27+
},
28+
}).addPath('read', 'siteMenu', (res) => async ({ paths: [websiteId] }) => {
29+
if (!websiteId) res.errorThrow('Website dibutuhkan.');
30+
return await res.read
31+
.clientOptions({ cache: 'no-cache' })
32+
.setQuery({
33+
filter: { website: { _eq: websiteId } },
34+
limit: -1,
35+
})
36+
.items({
37+
normalizer: [
38+
[
39+
'id',
40+
'title',
41+
'slug',
42+
'sort',
43+
{ pages: ['id', 'slug', 'title', 'sort', 'description'] },
44+
],
45+
(data) => {
46+
return data;
47+
},
48+
],
49+
});
50+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import _ from 'lodash';
2+
import { createApiResource } from '../base';
3+
import {
4+
imageFileNormalizer,
5+
contentBlocksNormalizer,
6+
} from '../../normalizer-helper';
7+
8+
export default createApiResource('web_pages', {
9+
baseFields: [
10+
'id',
11+
'slug',
12+
'title',
13+
'description',
14+
'sort',
15+
'menu',
16+
{ image_cover: ['*'] },
17+
{ website: ['id', 'name', 'subdomain'] },
18+
{
19+
user_created: ['first_name', 'last_name', 'content_author_name'],
20+
},
21+
{ user_updated: ['first_name', 'last_name', 'content_author_name'] },
22+
'date_updated',
23+
'date_created',
24+
],
25+
baseQuery: {
26+
sort: ['sort'],
27+
filter: { status: { _eq: 'published' } },
28+
},
29+
baseNormalizer: (data) => {
30+
return {
31+
..._.omit(data, 'user_updated', 'user_created'),
32+
image_cover: data.image_cover
33+
? imageFileNormalizer(data.image_cover!)
34+
: null,
35+
};
36+
},
37+
}).addPath('read', 'bySlug', (res) => ({ paths: [websiteId, slug] }) => {
38+
if (!websiteId) res.errorThrow('Website dibutuhkan.');
39+
if (!slug) res.errorThrow('Slug dibutuhkan.');
40+
return res.read
41+
.setQuery({
42+
filter: {
43+
website: { _eq: websiteId },
44+
slug: { _eq: slug },
45+
},
46+
})
47+
.items({
48+
normalizer: [
49+
[...res.baseFields, 'content'],
50+
(data) => {
51+
return {
52+
...res.baseNormalizer(data),
53+
content: contentBlocksNormalizer(data.content),
54+
};
55+
},
56+
],
57+
single: true,
58+
});
59+
});

src/lib/data/site.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { BaseIconNamesType } from '@/components/icons/base-icon';
22
import { apiResourceItemPathRead } from '../server';
33
import { TWebsiteItemBySubdomain, TWebsiteMenu } from '@/types';
44
import { urlToWww } from '@/init';
5+
import _ from 'lodash';
56

67
export async function dataSiteBySubdomain(subdomain: string) {
78
return await apiResourceItemPathRead('websites')
@@ -17,6 +18,12 @@ export async function dataSiteBeritaBySlug(slug: string) {
1718
.catch(() => null);
1819
}
1920

21+
export async function dataSitePageBySlug(websiteId: string, slug: string) {
22+
return await apiResourceItemPathRead('web_pages')
23+
.bySlug({ paths: [websiteId, slug] })
24+
.catch(() => null);
25+
}
26+
2027
export function dataSiteMenuProfilItems(
2128
site: TWebsiteItemBySubdomain
2229
): TWebsiteMenu['items'] {
@@ -110,6 +117,26 @@ export async function dataSiteMenu(site: TWebsiteItemBySubdomain) {
110117
items: dataSiteMenuDokumenItems(site),
111118
},
112119
];
120+
const menusData = await apiResourceItemPathRead('web_menu')
121+
.siteMenu({ paths: [site.id] })
122+
.catch((e) => null);
123+
124+
if (menusData) {
125+
_.forEach(menusData, (menu) => {
126+
menuItems.push({
127+
title: menu.title,
128+
link: `/menu/${menu.slug}`,
129+
items: _.map(_.sortBy(menu.pages, 'sort'), (page) => {
130+
return {
131+
title: page.title,
132+
link: `/page/${page.slug}`,
133+
description: page.description ?? '',
134+
icon: 'newspaper',
135+
};
136+
}),
137+
});
138+
});
139+
}
113140
if (modules.indexOf('aduan_publik') >= 0) {
114141
menuItems.push({
115142
title: 'Aduan Publik',

src/types/resource.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export type TApiResourcePathReturn<
1818
export type TNewsItemBySlug = TApiResourcePathReturn<'news'>['read']['bySlug'];
1919
export type TWebNewsItemBySlug =
2020
TApiResourcePathReturn<'web_news'>['read']['bySlug'];
21+
export type TWebPageItemBySlug =
22+
TApiResourcePathReturn<'web_pages'>['read']['bySlug'];
2123

2224
export type TNewsOrWebNewsItemBySlug = TNewsItemBySlug | TWebNewsItemBySlug;
2325

src/types/schemas/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { PublicServices, PublicServicesMedia } from './public_services';
2020
import { WebAduanPublik } from './web_aduan_publik';
2121
import { WebNews } from './web_news';
2222
import { Websites } from './websites';
23+
import { WebPages } from './web_pages';
24+
import { WebMenu } from './web_menu';
2325

2426
export * from './_base';
2527

@@ -41,7 +43,9 @@ export type ApiItemsSchema = CompleteSchema<{
4143
public_services_media: PublicServicesMedia[];
4244
public_services: PublicServices[];
4345
web_aduan_publik: WebAduanPublik[];
46+
web_menu: WebMenu[];
4447
web_news: WebNews[];
48+
web_pages: WebPages[];
4549
websites: Websites[];
4650
directus_users: MergeCoreCollection<
4751
CoreSchema,

0 commit comments

Comments
 (0)