diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index bffb357..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/.gitignore b/.gitignore index 605b6fa..d030889 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ yarn-error.log* # vercel .vercel + +.idea \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index 4797941..071c786 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16.20.1 +24.4.1 \ No newline at end of file diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index fe7b190..0000000 --- a/.prettierignore +++ /dev/null @@ -1,5 +0,0 @@ -# Ignore artifacts: -coverage -build -out -.next diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 544138b..0000000 --- a/.prettierrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "singleQuote": true -} diff --git a/.tool-versions b/.tool-versions index 11848fa..3515b8b 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 16.20.1 +nodejs 24.4.1 diff --git a/.yarnrc b/.yarnrc deleted file mode 100644 index b162bd8..0000000 --- a/.yarnrc +++ /dev/null @@ -1 +0,0 @@ ---install.frozen-lockfile true diff --git a/README.md b/README.md index 4d2834f..ce612c9 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This project is built with [Contentful](https://www.contentful.com), [Next.js](h ### System Requirements -- [Node.js 16.20.1](https://nodejs.org/) or later +- [Node.js 24.4.1](https://nodejs.org/) or later ### Setup @@ -20,22 +20,18 @@ Follow nvm setup instructions [here](https://github.com/nvm-sh/nvm/blob/master/R 2. Install Node.js locally ``` -nvm install 16.20.1 -nvm use 16.20.1 +nvm install 24.4.1 +nvm use 24.4.1 ``` -3. Install `yarn` +3. Install `pnpm` -``` -npm install yarn -g -``` +Follow pnpm setup instruction [here](https://pnpm.io/installation). 4. Install dependencies -Using `--frozen-lockfile` makes sure that the environment is consistent on any machine by installing the exact package versions listed in the [yarn.lock](yarn.lock). - ``` -yarn install --frozen-lockfile +pnpm install ``` 5. Signup for a free [Contentful](https://www.contentful.com/) account and create an organisation and within that create a space. @@ -59,7 +55,6 @@ yarn install --frozen-lockfile ## Developing - For accessbility testing we use the [axe-react](https://github.com/dequelabs/axe-core-npm/blob/develop/packages/react/README.md) plugin. -- We use the [Headwind](https://github.com/heybourn/headwind) VSCode extension for sorting TailwindCSS classes. - We use [classnames](https://www.npmjs.com/package/classnames) to organise our classes into groups that combine at build time. If you are new to Next.js, this is a helpful introduction: https://nextjs.org/docs @@ -67,7 +62,7 @@ If you are new to Next.js, this is a helpful introduction: https://nextjs.org/do Now you can run the development server: ```bash -yarn dev +pnpm dev ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the Session homepage. @@ -136,7 +131,7 @@ We can't use template literals with classes if we want to purge (shrink) the CSS You can run the project in a production environment by running: ``` -yarn run build && yarn run start +pnpm run build && pnpm run start ``` **Make sure to do this locally and check for errors before pushing any code changes to your hosted repository** @@ -150,7 +145,7 @@ If you want to see your Contentful changes faster while using a production serve You can test the project in a staging environment by running: ``` -yarn run build:staging && yarn run start:staging +pnpm run build:staging && pnpm run start:staging ``` Staging environments are password protected using the password you assign to `STAGING_SECRET` in `.env.local`. diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..774dc75 --- /dev/null +++ b/biome.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.1.2/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "includes": ["./**/*.ts", "./**/*.tsx"] + }, + "formatter": { + "enabled": true, + "useEditorconfig": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 100, + "attributePosition": "auto", + "bracketSpacing": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "recommended": true, + "noUndeclaredDependencies": "off", + "useImportExtensions": "off" + }, + "complexity": { + "recommended": true, + "noVoid": "off", + "useSimplifiedLogicExpression": "off", + "noExcessiveCognitiveComplexity": { + "level": "warn", + "options": { + "maxAllowedComplexity": 20 + } + } + }, + "a11y": { + "recommended": true + }, + "performance": { + "recommended": true, + "noBarrelFile": "off", + "noReExportAll": "off" + }, + "security": { + "recommended": true + }, + "suspicious": { + "recommended": true, + "noReactSpecificProps": "off", + "noConsole": "off" + }, + "nursery": { + "useSortedClasses": { + "fix": "safe", + "level": "info", + "options": { + "functions": ["clsx", "cva", "cn"] + } + } + } + } + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "es5", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto", + "bracketSpacing": true + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/components/BlogPost.tsx b/components/BlogPost.tsx new file mode 100644 index 0000000..a852043 --- /dev/null +++ b/components/BlogPost.tsx @@ -0,0 +1,35 @@ +import type { ReactElement } from 'react'; +import Post from '@/components/posts/Post'; +import Layout from '@/components/ui/Layout'; +import METADATA from '@/constants/metadata'; +import type { IPost } from '@/types/cms'; + +interface Props { + post: IPost; + otherPosts?: IPost[]; +} + +export default function BlogPost(props: Props): ReactElement { + const { post } = props; + return ( + + + + ); +} diff --git a/components/BlogPost/index.tsx b/components/BlogPost/index.tsx deleted file mode 100644 index c942ee9..0000000 --- a/components/BlogPost/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { IPost } from '@/types/cms'; -import { Layout } from '@/components/ui'; -import METADATA from '@/constants/metadata'; -import { Post } from '@/components/posts'; -import { ReactElement } from 'react'; - -interface Props { - post: IPost; - otherPosts?: IPost[]; -} - -export default function BlogPost(props: Props): ReactElement { - const { post } = props; - return ( - <> - - - - - ); -} diff --git a/components/Container/index.tsx b/components/Container.tsx similarity index 82% rename from components/Container/index.tsx rename to components/Container.tsx index 874c3ee..cb063db 100644 --- a/components/Container/index.tsx +++ b/components/Container.tsx @@ -1,5 +1,5 @@ -import { ReactElement, ReactNode } from 'react'; import classNames from 'classnames'; +import type { ReactElement, ReactNode } from 'react'; import { useScreen } from '@/contexts/screen'; export interface IContainerSizes { @@ -20,14 +20,7 @@ interface Props { } export default function Container(props: Props): ReactElement { - const { - id, - hasMinHeight = false, - heights, - fullWidth = false, - classes, - children, - } = props; + const { id, hasMinHeight = false, heights, fullWidth = false, classes, children } = props; const minHeights: IContainerSizes = { small: '568px', medium: '1024px', @@ -50,11 +43,7 @@ export default function Container(props: Props): ReactElement { id={id} className={classNames( 'mx-auto', - !fullWidth && [ - 'container max-w-6xl p-6', - 'md:p-12', - 'lg:py-0 lg:px-10', - ], + !fullWidth && ['container max-w-6xl p-6', 'md:p-12', 'lg:px-10 lg:py-0'], classes )} // mobile safari needs style props to be explicitly undefined if not used diff --git a/components/CustomHead/index.tsx b/components/CustomHead.tsx similarity index 67% rename from components/CustomHead/index.tsx rename to components/CustomHead.tsx index 4653614..31b9ba1 100644 --- a/components/CustomHead/index.tsx +++ b/components/CustomHead.tsx @@ -1,30 +1,27 @@ -import METADATA, { IMetadata } from '@/constants/metadata'; - +/** biome-ignore-all lint/security/noDangerouslySetInnerHtml: Used as expected */ import Head from 'next/head'; -import { ReactElement } from 'react'; -import { isLocal } from '@/utils/links'; import { useRouter } from 'next/router'; +import type { ReactElement } from 'react'; +import METADATA, { type IMetadata } from '@/constants/metadata'; +import { isLocal } from '@/utils/links'; interface Props { title?: string; metadata?: IMetadata; + structuredData?: Array; } export default function CustomHead(props: Props): ReactElement { const router = useRouter(); - const { title, metadata } = props; + const { title, metadata, structuredData } = props; const pageTitle = - title && title.length > 0 - ? `${title} - Session Private Messenger` - : METADATA.TITLE; + title && title.length > 0 ? `${title} - Session Private Messenger` : METADATA.TITLE; const pageUrl = `${METADATA.HOST_URL}${router.asPath}`; - const canonicalUrl = metadata?.CANONICAL_URL ?? pageUrl; const imageALT = metadata?.OG_IMAGE?.ALT ?? METADATA.OG_IMAGE.ALT; let imageWidth = metadata?.OG_IMAGE?.WIDTH ?? METADATA.OG_IMAGE.WIDTH; let imageHeight = metadata?.OG_IMAGE?.HEIGHT ?? METADATA.OG_IMAGE.HEIGHT; let imageUrl = (() => { - if (!metadata?.OG_IMAGE?.URL) - return `${METADATA.HOST_URL}${METADATA.OG_IMAGE.URL}`; + if (!metadata?.OG_IMAGE?.URL) return `${METADATA.HOST_URL}${METADATA.OG_IMAGE.URL}`; if (metadata?.OG_IMAGE?.URL && isLocal(metadata.OG_IMAGE.URL)) { return `${METADATA.HOST_URL}${metadata.OG_IMAGE.URL}`; } else { @@ -41,19 +38,13 @@ export default function CustomHead(props: Props): ReactElement { const tags = metadata?.TAGS ? metadata?.TAGS : METADATA.TAGS; const renderTags = (() => { - const keywords = ( - - ); + const keywords = ; if (metadata?.TYPE !== 'article') return keywords; return ( <> - {tags.map((tag, index) => { + {tags.map((tag) => { return ( - + ); })} - + - + - - + + - - + + - - - + + + - + {renderTags} - + + + + - {metadata?.TYPE === 'article' && renderLdJSON} + {structuredData?.map((data, i) => ( +