diff --git a/apps/app/package.json b/apps/app/package.json index 6f5a848a..74db708f 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -60,7 +60,7 @@ "near-api-js": "5.0.0", "near-social-vm": "github:NearSocial/VM#2.5.5", "nearblock-translations": "https://github.com/Nearblocks/translations.git#516d489658dea178226e90f1707935e970598c51", - "nearblocks-trans-next-intl": "https://github.com/kevin-devstack/translations.git#c23d13c6e049ad5721dfd71a8f0cbd8e6725c6ae", + "nearblocks-trans-next-intl": "https://github.com/kevin-devstack/translations.git#3f83876f543977b95c8b11f45c621576084114fe", "next": "14.2.5", "next-intl": "^3.19.4", "next-runtime-env": "1.x", diff --git a/apps/app/src/app/[locale]/blocks/[hash]/layout.tsx b/apps/app/src/app/[locale]/blocks/[hash]/layout.tsx index 2324ddf8..4c95c6be 100644 --- a/apps/app/src/app/[locale]/blocks/[hash]/layout.tsx +++ b/apps/app/src/app/[locale]/blocks/[hash]/layout.tsx @@ -21,7 +21,7 @@ export async function generateMetadata({ const metaTitle = t('block.metaTitle', { block: hash }); const metaDescription = t('block.metaDescription', { block: hash }); - const ogImageUrl = `${ogUrl}/api/og?block=true&block_height=${encodeURIComponent( + const ogImageUrl = `${ogUrl}/api/og?blockHash=true&block_height=${encodeURIComponent( blockHeight, )}&title=${encodeURIComponent(metaTitle)}`; diff --git a/apps/app/src/app/[locale]/blocks/layout.tsx b/apps/app/src/app/[locale]/blocks/layout.tsx new file mode 100644 index 00000000..1122e144 --- /dev/null +++ b/apps/app/src/app/[locale]/blocks/layout.tsx @@ -0,0 +1,51 @@ +import { appUrl } from '@/utils/app/config'; +import { Metadata } from 'next'; +import { unstable_setRequestLocale } from 'next-intl/server'; + +const network = process.env.NEXT_PUBLIC_NETWORK_ID; +const ogUrl = process.env.NEXT_PUBLIC_OG_URL; + +export async function generateMetadata({ + params: { hash, locale }, +}: { + params: { hash: string; locale: string }; +}): Promise { + unstable_setRequestLocale(locale); + + const metaTitle = 'Latest Near Protocol Blocks'; + const metaDescription = + 'All Near (Ⓝ Blocks that are included in Near blockchain. The timestamp, author, gas used, gas price and included transactions are shown.'; + + const ogImageUrl = `${ogUrl}/api/og?basic=true&title=${encodeURIComponent( + metaTitle, + )}`; + + return { + title: `${network === 'testnet' ? 'TESTNET' : ''} ${metaTitle}`, + description: metaDescription, + openGraph: { + title: metaTitle, + description: metaDescription, + images: [ + { + url: ogImageUrl.toString(), + width: 720, + height: 405, + alt: metaTitle, + }, + ], + }, + alternates: { + canonical: `${appUrl}/blocks/${hash}`, + }, + }; +} + +export default async function HashLayout({ + children, +}: { + children: React.ReactNode; + params: any; +}) { + return <>{children}; +} diff --git a/apps/app/src/app/[locale]/blocks/loading.tsx b/apps/app/src/app/[locale]/blocks/loading.tsx new file mode 100644 index 00000000..7244d654 --- /dev/null +++ b/apps/app/src/app/[locale]/blocks/loading.tsx @@ -0,0 +1,29 @@ +import ListSkeleton from '@/components/app/skeleton/blocks/list'; +import Skeleton from '@/components/app/skeleton/common/Skeleton'; + +export default function Loading() { + return ( + <> +
+
+

+ Latest Near Protocol Blocks +

+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ + ); +} diff --git a/apps/app/src/app/[locale]/blocks/page.tsx b/apps/app/src/app/[locale]/blocks/page.tsx new file mode 100644 index 00000000..19964001 --- /dev/null +++ b/apps/app/src/app/[locale]/blocks/page.tsx @@ -0,0 +1,46 @@ +import List from '@/components/app/Blocks/List'; +import ListSkeleton from '@/components/app/skeleton/blocks/list'; +import { Suspense } from 'react'; +import { getRequest } from '@/utils/app/api'; +import { getTranslations } from 'next-intl/server'; + +export default async function Blocks({ + params: { locale }, + searchParams, +}: { + params: { locale: string }; + searchParams: { cursor?: string }; +}) { + const data = await getRequest('blocks', { cursor: searchParams?.cursor }); + const dataCount = await getRequest('blocks/count'); + const t = await getTranslations({ locale }); + + return ( + <> +
+
+

+ {t('blockHeading') || 'Latest Near Protocol Blocks'} +

+
+
+
+
+
+ }> + + +
+
+
+
+ + ); +} + +export const revalidate = 20; diff --git a/apps/app/src/app/api/og/route.tsx b/apps/app/src/app/api/og/route.tsx index 02b88f99..35473ae1 100644 --- a/apps/app/src/app/api/og/route.tsx +++ b/apps/app/src/app/api/og/route.tsx @@ -31,7 +31,7 @@ export async function GET(request: Request) { const title = url.searchParams.get('title') || 'Near blocks'; const basic = url.searchParams.has('basic'); const account = url.searchParams.has('account'); - const block = url.searchParams.has('block'); + const blockHash = url.searchParams.has('blockHash'); const token = url.searchParams.has('token'); const nft = url.searchParams.has('nft'); @@ -53,7 +53,7 @@ export async function GET(request: Request) { svgString = thumbnailBasicSvg(brandConfig); } else if (account) { svgString = thumbnailAccountSvg(brandConfig); - } else if (block) { + } else if (blockHash) { svgString = thumbnailBlockSvg(brandConfig); } else if (token) { svgString = thumbnailTokenSvg(brandConfig); @@ -77,7 +77,7 @@ export async function GET(request: Request) { : null; let titleText = title; - if (block && blockHeight) { + if (blockHash) { titleText = `${ brand.charAt(0).toUpperCase() + brand.slice(1) } Block Height`; @@ -167,7 +167,7 @@ export async function GET(request: Request) { >
{titleText}
- {block && blockHeight && ( + {blockHash && blockHeight && (
{(t && - t('listing', { + t('block.listing', { from: start?.block_height ? localFormat(String(start?.block_height)) : '', diff --git a/apps/app/src/pages/[locale]/blocks/index.tsx b/apps/app/src/pages/[locale]/blocks/index.tsx deleted file mode 100644 index ac4284c7..00000000 --- a/apps/app/src/pages/[locale]/blocks/index.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { ReactElement } from 'react'; -import Head from 'next/head'; -import Layout from '@/components/Layouts'; -import List from '@/components/Blocks/List'; -import { InferGetServerSidePropsType, GetServerSideProps } from 'next'; -import { env } from 'next-runtime-env'; -import { appUrl } from '@/utils/config'; -import fetcher from '@/utils/fetcher'; -import queryString from 'qs'; -import { fetchData } from '@/utils/fetchData'; -import { useTranslations } from 'next-intl'; - -const ogUrl = env('NEXT_PUBLIC_OG_URL'); -const network = env('NEXT_PUBLIC_NETWORK_ID'); - -export const getServerSideProps: GetServerSideProps<{ - data: any; - dataCount: any; - error: boolean; - statsDetails: any; - latestBlocks: any; - searchResultDetails: any; - searchRedirectDetails: any; -}> = async (context) => { - const { - query: { keyword = '', filter = 'all', query = '', ...qs }, - }: any = context; - - const apiUrl = 'blocks'; - const fetchUrl = qs ? `blocks?${queryString.stringify(qs)}` : `${apiUrl}`; - - const key = keyword?.replace(/[\s,]/g, ''); - const q = query?.replace(/[\s,]/g, ''); - - try { - const [dataResult, dataCountResult] = await Promise.allSettled([ - fetcher(fetchUrl), - fetcher('blocks/count'), - ]); - const data = dataResult.status === 'fulfilled' ? dataResult.value : null; - const dataCount = - dataCountResult.status === 'fulfilled' ? dataCountResult.value : null; - const error = dataResult.status === 'rejected'; - - const { - statsDetails, - latestBlocks, - searchResultDetails, - searchRedirectDetails, - } = await fetchData(q, key, filter); - - const locale = context?.params?.locale; - const [commonMessages, blockMessages] = await Promise.all([ - import(`nearblocks-trans-next-intl/${locale || 'en'}/common.json`), - import(`nearblocks-trans-next-intl/${locale || 'en'}/blocks.json`), - ]); - - const messages = { - ...commonMessages.default, - ...blockMessages.default, - }; - - return { - props: { - data, - dataCount, - error, - apiUrl, - statsDetails, - latestBlocks, - searchResultDetails, - searchRedirectDetails, - messages, - }, - }; - } catch (error) { - console.error('Error fetching blocks:', error); - return { - props: { - data: null, - dataCount: null, - error: true, - apiUrl: '', - statsDetails: null, - latestBlocks: null, - searchResultDetails: null, - searchRedirectDetails: null, - messages: null, - }, - }; - } -}; - -const Blocks = ({ - data, - dataCount, - error, -}: InferGetServerSidePropsType) => { - const t = useTranslations(); - - const thumbnail = `${ogUrl}/thumbnail/basic?title=${encodeURIComponent( - t('heading'), - )}&brand=near`; - - return ( - <> - - - {`${network === 'testnet' ? 'TESTNET' : ''} ${t('metaTitle')} `} - - - - - - - - - - - -
-
-

- {t ? t('heading') : 'Latest Near Protocol Blocks'} -

-
-
-
-
-
- -
-
-
-
- - ); -}; - -Blocks.getLayout = (page: ReactElement) => ( - - {page} - -); - -export default Blocks; diff --git a/apps/app/src/pages/blocks/index.tsx b/apps/app/src/pages/blocks/index.tsx deleted file mode 100644 index ac4284c7..00000000 --- a/apps/app/src/pages/blocks/index.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { ReactElement } from 'react'; -import Head from 'next/head'; -import Layout from '@/components/Layouts'; -import List from '@/components/Blocks/List'; -import { InferGetServerSidePropsType, GetServerSideProps } from 'next'; -import { env } from 'next-runtime-env'; -import { appUrl } from '@/utils/config'; -import fetcher from '@/utils/fetcher'; -import queryString from 'qs'; -import { fetchData } from '@/utils/fetchData'; -import { useTranslations } from 'next-intl'; - -const ogUrl = env('NEXT_PUBLIC_OG_URL'); -const network = env('NEXT_PUBLIC_NETWORK_ID'); - -export const getServerSideProps: GetServerSideProps<{ - data: any; - dataCount: any; - error: boolean; - statsDetails: any; - latestBlocks: any; - searchResultDetails: any; - searchRedirectDetails: any; -}> = async (context) => { - const { - query: { keyword = '', filter = 'all', query = '', ...qs }, - }: any = context; - - const apiUrl = 'blocks'; - const fetchUrl = qs ? `blocks?${queryString.stringify(qs)}` : `${apiUrl}`; - - const key = keyword?.replace(/[\s,]/g, ''); - const q = query?.replace(/[\s,]/g, ''); - - try { - const [dataResult, dataCountResult] = await Promise.allSettled([ - fetcher(fetchUrl), - fetcher('blocks/count'), - ]); - const data = dataResult.status === 'fulfilled' ? dataResult.value : null; - const dataCount = - dataCountResult.status === 'fulfilled' ? dataCountResult.value : null; - const error = dataResult.status === 'rejected'; - - const { - statsDetails, - latestBlocks, - searchResultDetails, - searchRedirectDetails, - } = await fetchData(q, key, filter); - - const locale = context?.params?.locale; - const [commonMessages, blockMessages] = await Promise.all([ - import(`nearblocks-trans-next-intl/${locale || 'en'}/common.json`), - import(`nearblocks-trans-next-intl/${locale || 'en'}/blocks.json`), - ]); - - const messages = { - ...commonMessages.default, - ...blockMessages.default, - }; - - return { - props: { - data, - dataCount, - error, - apiUrl, - statsDetails, - latestBlocks, - searchResultDetails, - searchRedirectDetails, - messages, - }, - }; - } catch (error) { - console.error('Error fetching blocks:', error); - return { - props: { - data: null, - dataCount: null, - error: true, - apiUrl: '', - statsDetails: null, - latestBlocks: null, - searchResultDetails: null, - searchRedirectDetails: null, - messages: null, - }, - }; - } -}; - -const Blocks = ({ - data, - dataCount, - error, -}: InferGetServerSidePropsType) => { - const t = useTranslations(); - - const thumbnail = `${ogUrl}/thumbnail/basic?title=${encodeURIComponent( - t('heading'), - )}&brand=near`; - - return ( - <> - - - {`${network === 'testnet' ? 'TESTNET' : ''} ${t('metaTitle')} `} - - - - - - - - - - - -
-
-

- {t ? t('heading') : 'Latest Near Protocol Blocks'} -

-
-
-
-
-
- -
-
-
-
- - ); -}; - -Blocks.getLayout = (page: ReactElement) => ( - - {page} - -); - -export default Blocks;