From 9b83e1974aa973078ce2a18d32530fa2168b8d63 Mon Sep 17 00:00:00 2001 From: Greg Rickaby Date: Fri, 9 Feb 2024 14:08:46 -0600 Subject: [PATCH] initial commit of v5 --- .env.example | 3 + .eslintrc.js | 1 - .gitignore | 12 +- .prettierrc.js | 3 +- .stylelintrc.js | 19 +- .vscode/extensions.json | 8 - .vscode/settings.json | 29 - CONTRIBUTING.md | 3 + app/Page.module.css | 21 - app/actions.ts | 162 ++ app/api/(reddit)/popular/route.ts | 102 - app/api/(reddit)/reddit/route.ts | 144 -- app/api/(reddit)/search/route.ts | 107 - app/globals.css | 47 + app/layout.tsx | 82 +- app/not-found.tsx | 15 +- app/page.tsx | 29 +- app/r/[slug]/components/BackToTop.tsx | 49 + .../r/[slug]/components}/HlsPlayer.tsx | 11 +- app/r/[slug]/components/Media.tsx | 131 + app/r/[slug]/components/Posts.tsx | 41 + app/r/[slug]/loading.tsx | 30 + app/r/[slug]/page.tsx | 57 + app/sitemap.ts | 18 + components/BackToTop.module.css | 6 - components/BackToTop.tsx | 32 - components/Footer.module.css | 19 - components/Footer.tsx | 16 +- components/Header.module.css | 21 - components/Header.tsx | 30 +- components/LoadingCard.tsx | 17 - components/Media.tsx | 191 -- components/PreloadResources.tsx | 13 - components/RedditProvider.tsx | 55 - components/Results.module.css | 12 - components/Results.tsx | 113 - components/Search.module.css | 3 - components/Search.tsx | 199 +- components/Settings.module.css | 7 - components/Settings.tsx | 98 - components/Sort.module.css | 3 - components/Sort.tsx | 36 - lib/config.ts | 6 +- lib/functions.ts | 113 +- lib/theme.ts | 14 - lib/types.d.ts | 143 +- next-sitemap.config.js | 6 - next.config.js => next.config.mjs | 5 +- package-lock.json | 2185 +++++++++-------- package.json | 31 +- postcss.config.js | 14 +- public/sitemap-0.xml | 4 - public/sitemap.xml | 4 - tailwind.config.ts | 11 + tsconfig.json | 9 +- viewer-for-reddit.code-workspace | 36 + 56 files changed, 2039 insertions(+), 2537 deletions(-) delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json delete mode 100644 app/Page.module.css create mode 100644 app/actions.ts delete mode 100644 app/api/(reddit)/popular/route.ts delete mode 100644 app/api/(reddit)/reddit/route.ts delete mode 100644 app/api/(reddit)/search/route.ts create mode 100644 app/globals.css create mode 100644 app/r/[slug]/components/BackToTop.tsx rename {components => app/r/[slug]/components}/HlsPlayer.tsx (82%) create mode 100644 app/r/[slug]/components/Media.tsx create mode 100644 app/r/[slug]/components/Posts.tsx create mode 100644 app/r/[slug]/loading.tsx create mode 100644 app/r/[slug]/page.tsx create mode 100644 app/sitemap.ts delete mode 100644 components/BackToTop.module.css delete mode 100644 components/BackToTop.tsx delete mode 100644 components/Footer.module.css delete mode 100644 components/Header.module.css delete mode 100644 components/LoadingCard.tsx delete mode 100644 components/Media.tsx delete mode 100644 components/PreloadResources.tsx delete mode 100644 components/RedditProvider.tsx delete mode 100644 components/Results.module.css delete mode 100644 components/Results.tsx delete mode 100644 components/Search.module.css delete mode 100644 components/Settings.module.css delete mode 100644 components/Settings.tsx delete mode 100644 components/Sort.module.css delete mode 100644 components/Sort.tsx delete mode 100644 lib/theme.ts delete mode 100644 next-sitemap.config.js rename next.config.js => next.config.mjs (58%) delete mode 100644 public/sitemap-0.xml delete mode 100644 public/sitemap.xml create mode 100644 tailwind.config.ts create mode 100644 viewer-for-reddit.code-workspace diff --git a/.env.example b/.env.example index 0358ecaf..41b64f3c 100644 --- a/.env.example +++ b/.env.example @@ -6,5 +6,8 @@ REDDIT_CLIENT_ID="YOUR-TOKEN-HERE" # Get one here: https://www.reddit.com/prefs/apps REDDIT_CLIENT_SECRET="YOUR-TOKEN-HERE" +# Search Secret. Used to verify the search request. +NEXT_PUBLIC_SEARCH_SECRET="ANY-RANDOM-STRING-HERE" + # Used on production to verify the site with Google Webmaster Tools. NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION="YOUR-TOKEN-HERE" diff --git a/.eslintrc.js b/.eslintrc.js index 66c7e8f1..b90c6fe2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,6 @@ module.exports = { extends: ['next/core-web-vitals', 'prettier'], rules: { '@next/next/no-img-element': 'off', - 'func-style': ['error', 'declaration'], 'no-console': ['error', {allow: ['warn', 'error']}] } } diff --git a/.gitignore b/.gitignore index 941d19cc..fd3dbb57 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +.yarn/install-state.gz # testing /coverage @@ -25,18 +26,11 @@ yarn-debug.log* yarn-error.log* # local env files -.env -.env.local -.env.development.local -.env.test.local -.env.production.local +.env*.local # vercel .vercel -# sitemap -robots.txt -sitemap.xml - # typescript *.tsbuildinfo +next-env.d.ts diff --git a/.prettierrc.js b/.prettierrc.js index 762c4195..53c7abdb 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -4,5 +4,6 @@ module.exports = { singleQuote: true, bracketSpacing: false, semi: false, - trailingComma: 'none' + trailingComma: 'none', + plugins: ['prettier-plugin-tailwindcss'] } diff --git a/.stylelintrc.js b/.stylelintrc.js index 2ac1de7b..d1f59f2b 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -1,3 +1,20 @@ module.exports = { - extends: ['stylelint-config-standard'] + extends: ['stylelint-config-standard'], + rules: { + 'at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'tailwind', + 'apply', + 'layer', + 'variants', + 'responsive', + 'screen' + ] + } + ], + 'no-descending-specificity': null, + 'selector-class-pattern': null + } } diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 9077c981..00000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "recommendations": [ - "EditorConfig.EditorConfig", - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", - "stylelint.vscode-stylelint" - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c3c33225..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "editor.codeActionsOnSave": { - "source.fixAll": "explicit" - }, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnPaste": true, - "editor.formatOnSave": true, - "eslint.format.enable": false, - "eslint.run": "onSave", - "javascript.suggest.autoImports": true, - "javascript.updateImportsOnFileMove.enabled": "always", - "typescript.suggest.autoImports": true, - "typescript.updateImportsOnFileMove.enabled": "always", - "[typescript]": { - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - } - }, - "[typescriptreact]": { - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - } - }, - "[typescript][typescriptreact]": { - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - } - } -} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a935d7da..7d55b46b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,6 +85,9 @@ REDDIT_CLIENT_ID="YOUR-TOKEN-HERE" # Get one here: https://www.reddit.com/prefs/apps REDDIT_CLIENT_SECRET="YOUR-TOKEN-HERE" +# Search Secret +NEXT_PUBLIC_SEARCH_SECRET="ANY-RANDOM-STRING-HERE" + # Used on production to verify the site with Google Webmaster Tools. NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION="YOUR-TOKEN-HERE" ``` diff --git a/app/Page.module.css b/app/Page.module.css deleted file mode 100644 index 063ac50b..00000000 --- a/app/Page.module.css +++ /dev/null @@ -1,21 +0,0 @@ -.container { - margin: 0 auto; - padding: 0 20px; - max-width: 1200px; - - & > * { - margin-bottom: 20px; - margin-top: 20px; - } -} - -.search { - align-items: center; - display: flex; - gap: 10px; - margin-bottom: 20px; -} - -.main { - min-height: 100vh; -} diff --git a/app/actions.ts b/app/actions.ts new file mode 100644 index 00000000..dc0579e5 --- /dev/null +++ b/app/actions.ts @@ -0,0 +1,162 @@ +'use server' + +import config from '@/lib/config' +import { + FetchSubredditProps, + RedditPostResponse, + RedditSearchResponse, + RedditTokenResponse +} from '@/lib/types' + +/** + * Fetch a Reddit oAuth token. + * + * @see https://github.com/reddit-archive/reddit/wiki/OAuth2#application-only-oauth + */ +export async function fetchToken(): Promise { + try { + // Fetch the Reddit oAuth token. + const response = await fetch( + 'https://www.reddit.com/api/v1/access_token?grant_type=client_credentials&device_id=DO_NOT_TRACK_THIS_DEVICE', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'User-Agent': config.userAgent, + Authorization: `Basic ${btoa( + `${process.env.REDDIT_CLIENT_ID}:${process.env.REDDIT_CLIENT_SECRET}` + )}` + }, + next: { + tags: ['token'], + revalidate: config.cacheTtl + } + } + ) + + // Bad response? Bail. + if (!response.ok) { + throw new Error('Failed to fetch Reddit oAuth Token.') + } + + // Parse the response. + const data = (await response.json()) as RedditTokenResponse + + // If the response is empty, bail. + if (!data.access_token) { + throw new Error(data.error) + } + + // Return the token. + return { + access_token: data.access_token + } + } catch (error) { + console.error('Exception thrown in fetchToken()') + return {error: `${error}`} + } +} + +/** + * Fetch search results. + */ +export async function fetchSearchResults( + query: string +): Promise { + try { + // Get the access token. + const {access_token} = await fetchToken() + + // Attempt to fetch subreddits. + const response = await fetch( + `https://oauth.reddit.com/api/subreddit_autocomplete_v2?query=${query}&limit=10&include_over_18=true&include_profiles=false&typeahead_active=true&search_query_id=DO_NOT_TRACK`, + { + headers: { + authorization: `Bearer ${access_token}` + }, + next: { + revalidate: config.cacheTtl + } + } + ) + // Bad response? Bail. + if (!response.ok) { + throw new Error(`Failed to fetch search results. ${response.statusText}`) + } + + // Parse the response. + const data = (await response.json()) as RedditSearchResponse + + // If the response is empty, bail. + if (!data.data) { + throw new Error('Failed to parse search results.') + } + + // Return the search results. + return data + } catch (error) { + console.error('Exception thrown in fetchSearchResults()') + return {error: `${error}`} + } +} + +/** + * Fetch subreddit posts. + */ +export async function fetchSubredditPosts( + props: FetchSubredditProps +): Promise { + try { + // Destructure props. + const {slug, sortBy, limit, after} = props + + // Fetch the Reddit oAuth token. + const {access_token} = await fetchToken() + + // Fetch the subreddit posts. + const response = await fetch( + `https://oauth.reddit.com/r/${slug}/${sortBy}/.json?limit=${limit}&after=${after}&raw_json=1`, + { + headers: { + 'User-Agent': config.userAgent, + authorization: `Bearer ${access_token}` + }, + next: { + tags: [slug], + revalidate: config.cacheTtl + } + } + ) + + // Bad response? Bail. + if (!response.ok) { + throw new Error(` ${response.statusText}: /r/${slug}`) + } + + // Parse the response. + const data = (await response.json()) as RedditPostResponse + + // If the response is empty, bail. + if (!data.data) { + throw new Error('Failed to parse subreddit response.') + } + + // Return the posts. + return { + kind: data.kind, + data: { + modhash: data.data.modhash, + dist: data.data.dist, + children: data.data.children.filter( + ({data}) => + data.post_hint && data.post_hint !== 'self' && !data.poststickied // Exclude self/stickied posts. + ), + after: data.data.after, + before: data.data.before + } + } + } catch (error) { + console.error('Exception thrown in fetchSubredditPosts()') + return {error: `${error}`} + } +} diff --git a/app/api/(reddit)/popular/route.ts b/app/api/(reddit)/popular/route.ts deleted file mode 100644 index 6e1ba9ca..00000000 --- a/app/api/(reddit)/popular/route.ts +++ /dev/null @@ -1,102 +0,0 @@ -import config from '@/lib/config' -import {fetchToken} from '@/lib/functions' - -/** - * Route segment config. - * - * @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config - */ -export const runtime = 'edge' - -/** - * Popular Subreddit Reddit API. - * - * @example - * /api/popular?limit=5 - * - * @see https://www.reddit.com/dev/api#GET_subreddits_{where} - * @see https://nextjs.org/docs/app/building-your-application/routing/route-handlers - * @see https://nextjs.org/docs/pages/api-reference/edge - */ -export async function GET() { - try { - // Get the access token. - const {token} = await fetchToken() - - // Attempt to fetch subreddits. - const response = await fetch( - `https://oauth.reddit.com/subreddits/popular?limit=${config.redditApi.popularLimit}`, - { - headers: { - authorization: `Bearer ${token}` - }, - next: {revalidate: config.cacheTtl} - } - ) - - // No response? Bail... - if (response.status != 200) { - return new Response( - JSON.stringify({ - error: `${response.statusText}` - }), - { - headers: { - 'X-Robots-Tag': 'noindex' - }, - status: response.status, - statusText: response.statusText - } - ) - } - - // Parse the response. - const subs = await response.json() - - // No data in the response? Bail... - if (!subs.data && !subs.data.children.length) { - return new Response( - JSON.stringify({ - error: `No data returned from Reddit.` - }), - { - headers: { - 'X-Robots-Tag': 'noindex' - }, - status: 400, - statusText: 'Bad Request' - } - ) - } - - // Filter uneeded data to keep the payload small. - const filtered = subs.data.children.map( - (sub: {data: {over18?: string; url?: string; display_name?: string}}) => { - return { - over_18: sub.data.over18 ? true : false, - url: sub.data.url ? sub.data.url : '', - value: sub.data.display_name ? sub.data.display_name : '' - } - } - ) - - // Send the response. - return new Response(JSON.stringify(filtered), { - headers: { - 'Content-Type': 'application/json', - 'Cache-Control': `public, s-maxage=${config.cacheTtl}`, - 'CDN-Cache-Control': `public, s-maxage=${config.cacheTtl}`, - 'Vercel-CDN-Cache-Control': `public, s-maxage=${config.cacheTtl}` - }, - status: 200, - statusText: 'OK' - }) - } catch (error) { - // Issue? Leave a message and bail. - console.error(error) - return new Response(JSON.stringify({error: `${error}`}), { - status: 500, - statusText: 'Internal Server Error' - }) - } -} diff --git a/app/api/(reddit)/reddit/route.ts b/app/api/(reddit)/reddit/route.ts deleted file mode 100644 index b21e2916..00000000 --- a/app/api/(reddit)/reddit/route.ts +++ /dev/null @@ -1,144 +0,0 @@ -import config from '@/lib/config' -import {fetchToken, getMediumImage} from '@/lib/functions' -import {Posts, RedditAPIResponse} from '@/lib/types' - -/** - * Route segment config. - * - * @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config - */ -export const runtime = 'edge' - -/** - * Query Reddit API. - * - * @example - * /api/reddit?sub=itookapicture&sort=hot&limit=24&after=t3_9x9j4d - * - * @see https://nextjs.org/docs/app/building-your-application/routing/route-handlers - * @see https://nextjs.org/docs/pages/api-reference/edge - */ -export async function GET(request: Request) { - // Get query params from request. - const {searchParams} = new URL(request.url) - - // Parse params. - const unsanitizedParams = { - postLimit: searchParams.get('limit') || '', - sortBy: searchParams.get('sort') || '', - subReddit: searchParams.get('sub') || '', - lastPost: searchParams.get('after') || '' - } - - // Parse and sanitize query params from request. - const postLimit = unsanitizedParams.postLimit - ? encodeURIComponent(unsanitizedParams.postLimit) - : config.redditApi.limit - const sortBy = unsanitizedParams.sortBy - ? encodeURIComponent(unsanitizedParams.sortBy) - : config.redditApi.sort - const subReddit = unsanitizedParams.subReddit - ? encodeURIComponent(unsanitizedParams.subReddit) - : config.redditApi.subReddit - const lastPost = unsanitizedParams.lastPost - ? encodeURIComponent(unsanitizedParams.lastPost) - : '' - - try { - // Get the access token. - const {token} = await fetchToken() - - // Attempt to fetch posts. - const response = await fetch( - `https://oauth.reddit.com/r/${subReddit}/${sortBy}/.json?limit=${postLimit}&after=${lastPost}&raw_json=1`, - { - headers: { - Authorization: `Bearer: ${token}` - } - } - ) - - // No response? Bail... - if (response.status != 200) { - return new Response( - JSON.stringify({ - error: `${response.statusText}` - }), - { - status: response.status, - statusText: response.statusText - } - ) - } - - // Parse the response. - const json = (await response.json()) as RedditAPIResponse - - // No data in the response? Bail... - if (!json.data || !json.data.children.length) { - return new Response( - JSON.stringify({ - error: `No data returned from Reddit.` - }), - { - status: 400, - statusText: 'Bad Request' - } - ) - } - - // Filter out any self or stickied posts. - const postsContainImage = json.data.children.filter((post) => { - return ( - post.data.post_hint && - post.data.post_hint !== 'self' && - post.data.stickied !== true - ) - }) - - // Create response shape. - const data = { - posts: postsContainImage.map((post) => ({ - id: post.data.id, - images: { - original: post.data.preview.images[0].source, - cropped: getMediumImage(post.data.preview.images[0].resolutions), - obfuscated: getMediumImage( - post.data.preview.images[0]?.variants?.obfuscated?.resolutions - ) - }, - media: post.data.media, - video_preview: post.data.preview.reddit_video_preview, - over_18: post.data.over_18, - permalink: `https://www.reddit.com${post.data.permalink}`, - post_hint: post.data.post_hint, - score: post.data.score, - secure_media_embed: post.data.secure_media_embed, - subreddit: post.data.subreddit, - thumbnail: post.data.thumbnail, - title: post.data.title, - url: post.data.url - })), - after: json?.data?.after - } as Posts - - // Send the response. - return new Response(JSON.stringify(data), { - headers: { - 'Content-Type': 'application/json', - 'Cache-Control': `public, s-maxage=${config.cacheTtl}`, - 'CDN-Cache-Control': `public, s-maxage=${config.cacheTtl}`, - 'Vercel-CDN-Cache-Control': `public, s-maxage=${config.cacheTtl}` - }, - status: 200, - statusText: 'OK' - }) - } catch (error) { - // Issue? Leave a message and bail. - console.error(error) - return new Response(JSON.stringify({error: `${error}`}), { - status: 500, - statusText: 'Internal Server Error' - }) - } -} diff --git a/app/api/(reddit)/search/route.ts b/app/api/(reddit)/search/route.ts deleted file mode 100644 index af02c562..00000000 --- a/app/api/(reddit)/search/route.ts +++ /dev/null @@ -1,107 +0,0 @@ -import config from '@/lib/config' -import {fetchToken} from '@/lib/functions' - -/** - * Route segment config. - * - * @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config - */ -export const runtime = 'edge' - -/** - * Search Reddit API. - * - * @example - * /api/search?term=itookapicture - * - * @see https://www.reddit.com/dev/api#GET_api_subreddit_autocomplete_v2 - * @see https://nextjs.org/docs/app/building-your-application/routing/route-handlers - * @see https://nextjs.org/docs/pages/api-reference/edge - */ -export async function GET(request: Request) { - // Get query params from request. - const {searchParams} = new URL(request.url) - - // Parse params. - const unsanitizedTerm = searchParams.get('term') || '' - - // Parse and sanitize query params from request. - const term = unsanitizedTerm - ? encodeURIComponent(unsanitizedTerm).trim() - : config.redditApi.subReddit - - try { - // Get the access token. - const {token} = await fetchToken() - - // Attempt to fetch subreddits. - const response = await fetch( - `https://oauth.reddit.com/api/subreddit_autocomplete_v2?query=${term}&limit=10&include_over_18=true&include_profiles=true&typeahead_active=true`, - { - headers: { - authorization: `Bearer ${token}` - } - } - ) - - // No response? Bail... - if (response.status != 200) { - return new Response( - JSON.stringify({ - error: `${response.statusText}` - }), - { - status: response.status, - statusText: response.statusText - } - ) - } - - // Parse the response. - const subs = await response.json() - - // No data in the response? Bail... - if (!subs.data && !subs.data.children.length) { - return new Response( - JSON.stringify({ - error: `No data returned from Reddit.` - }), - { - status: 400, - statusText: 'Bad Request' - } - ) - } - - // Filter uneeded data to keep the payload small. - const filtered = subs.data.children.map( - (sub: {data: {over18?: string; url?: string; display_name?: string}}) => { - return { - over_18: sub.data.over18 ? true : false, - url: sub.data.url ? sub.data.url : '', - value: sub.data.display_name ? sub.data.display_name : '' - } - } - ) - - // Send the response. - return new Response(JSON.stringify(filtered), { - status: 200, - statusText: 'OK', - headers: { - 'X-Robots-Tag': 'noindex', - 'Content-Type': 'application/json', - 'Cache-Control': `public, s-maxage=${config.cacheTtl}`, - 'CDN-Cache-Control': `public, s-maxage=${config.cacheTtl}`, - 'Vercel-CDN-Cache-Control': `public, s-maxage=${config.cacheTtl}` - } - }) - } catch (error) { - // Issue? Leave a message and bail. - console.error(error) - return new Response(JSON.stringify({error: `${error}`}), { - status: 500, - statusText: 'Internal Server Error' - }) - } -} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 00000000..1b79c7d5 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,47 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + body { + @apply relative mx-auto space-y-12 p-8 antialiased; + @apply prose prose-zinc lg:prose-xl; + @apply bg-white dark:prose-invert dark:bg-zinc-900; + } + + input, + button, + .button { + @apply rounded bg-zinc-200 text-zinc-900 dark:bg-zinc-700 dark:text-white; + @apply px-4 py-3 transition duration-300 ease-in-out; + } + + input { + @apply w-full outline-1 dark:outline-zinc-900; + } + + button, + .button { + @apply leading-tight no-underline; + @apply hover:bg-zinc-300 hover:no-underline dark:hover:bg-zinc-700; + } + + a { + @apply no-underline hover:underline; + } + + .main { + @apply relative; + + /* Breakout of the container for larger screens. */ + @media screen and (width >= 768px) { + left: -12vw; + width: calc(100% + 24vw); + } + + @media screen and (width >= 1920px) { + left: -24vw; + width: calc(100% + 48vw); + } + } +} diff --git a/app/layout.tsx b/app/layout.tsx index c61e0212..15a1a943 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,79 +1,33 @@ -import PreloadResources from '@/components/PreloadResources' -import RedditProvider from '@/components/RedditProvider' +import '@/app/globals.css' +import Footer from '@/components/Footer' +import Header from '@/components/Header' +import Search from '@/components/Search' import config from '@/lib/config' -import theme from '@/lib/theme' -import {ColorSchemeScript, MantineProvider} from '@mantine/core' -import '@mantine/core/styles.css' -import {Metadata, Viewport} from 'next' +import type {Metadata} from 'next' /** - * Setup metadata. - * - * @see https://nextjs.org/docs/app/building-your-application/optimizing/metadata + * Generate default site metadata. */ export const metadata: Metadata = { - metadataBase: new URL(config.siteUrl), - title: `${config.siteName} - ${config.siteDescription}`, - description: config.siteDescription, - robots: 'follow, index', - alternates: { - canonical: config.siteUrl - }, - manifest: '/manifest.json', - openGraph: { - title: config.siteName, - description: config.metaDescription, - type: 'website', - locale: 'en_US', - url: config.siteUrl, - images: [ - { - url: `${config.siteUrl}social-share.webp`, - width: 1200, - height: 630, - alt: config.siteName - } - ] - }, - icons: { - icon: '/favicon.ico', - apple: '/icon.png', - shortcut: '/icon.png' - }, - other: { - 'google-site-verification': - process.env.NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION || '' - } + title: config.siteName, + description: config.metaDescription } /** - * Setup viewport. - * - * @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport + * The root layout. */ -export const viewport: Viewport = { - themeColor: [ - {media: '(prefers-color-scheme: light)', color: 'white'}, - {media: '(prefers-color-scheme: dark)', color: '#1a1b1e'} - ] -} - -/** - * Root layout component. - * - * @see https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required - */ -export default function RootLayout({children}: {children: any}) { +export default function RootLayout({ + children +}: Readonly<{ + children: React.ReactNode +}>) { return ( - - - - - - {children} - +
+ +
{children}
+