[website] Add blog (#417)
* add ghost api * connect blog overview page * connect and render post detail * add processing to server * update blog detail page * add eslint-plugin-tailwindcss * add @tanstack/react-query * truncate text by numberOfLines * use ComponentPropsWithRef to infer style prop type * update ghost fns * update index * add tag page * add author page * update / * update /tag * update /author * update detail page * fix posts type * disable redirect * remove global background - not found pages - pages without common layout (preview) * tmp: hide nav on smaller screens * update app layout spacing * remove text truncating * update / spacing and sizing * update .vscode/settings.json * update .eslintrc * update prettier-plugin-tailwindcss * sort tailwind classes * add packages/eslint-config-custom * use turbo lint * use tailwind.config.cjs see https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/212 * reset global line-height * fix lint-staged * update color tokens * update spacing and sizes * Update apps/website/src/lib/ghost.ts Co-authored-by: Pavel <14926950+prichodko@users.noreply.github.com> * update layout max width * update card min width * set line-height * set overflow on pre * use flex for markdown content to prevent overflow * collect follow-ups * add visibility filter * update page count * rename var * remove filter * use prod ghost api key * update ghost api * revert line-height * add limit to getPosts params * update visible posts * add related articles * add env vars to gh * rename eslint config package * update gh vars * rename envs * set emtpy array to related posts * fix lint-staged * prevent importing server envs on client * set limit --------- Co-authored-by: Felicio Mununga <felicio@users.noreply.github.com>
This commit is contained in:
parent
820eafadde
commit
45e36b2360
119
.eslintrc
119
.eslintrc
|
@ -1,119 +0,0 @@
|
||||||
{
|
|
||||||
"root": true,
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
// TODO: Enable type-aware linting (https://typescript-eslint.io/docs/linting/type-linting)
|
|
||||||
// "tsconfigRootDir": ".",
|
|
||||||
// "project": ["./packages/*/tsconfig.json"],
|
|
||||||
"sourceType": "module",
|
|
||||||
"ecmaFeatures": {
|
|
||||||
"jsx": true
|
|
||||||
},
|
|
||||||
"warnOnUnsupportedTypeScriptVersion": true
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
"@typescript-eslint",
|
|
||||||
"import",
|
|
||||||
"simple-import-sort",
|
|
||||||
"react",
|
|
||||||
"jsx-a11y"
|
|
||||||
],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
// "plugin:@typescript-eslint/recommended-requiring-type-checking",
|
|
||||||
"plugin:eslint-comments/recommended",
|
|
||||||
"plugin:import/recommended",
|
|
||||||
"plugin:import/typescript",
|
|
||||||
"plugin:jsx-a11y/recommended",
|
|
||||||
"plugin:react/recommended",
|
|
||||||
"plugin:react-hooks/recommended",
|
|
||||||
"plugin:react/jsx-runtime",
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["examples/**/*.tsx"],
|
|
||||||
"rules": {
|
|
||||||
"react/jsx-uses-react": "off",
|
|
||||||
"react/react-in-jsx-scope": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// TODO: add https://github.com/francoismassart/eslint-plugin-tailwindcss
|
|
||||||
"files": ["./apps/website/"],
|
|
||||||
"extends": ["next", "next/core-web-vitals"]
|
|
||||||
// "settings": {
|
|
||||||
// "import/resolver": {
|
|
||||||
// "typescript": {
|
|
||||||
// "extensions": [".js", ".jsx", ".ts", ".tsx"],
|
|
||||||
// "project": ["tsconfig.json", "apps/website/tsconfig.json"]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"react/prop-types": 0,
|
|
||||||
// "@typescript-eslint/consistent-type-definitions": ["error", "interface"],
|
|
||||||
"@typescript-eslint/consistent-type-imports": "error",
|
|
||||||
// TODO: turn on this rul
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
// "@typescript-eslint/consistent-type-exports": "error",
|
|
||||||
"simple-import-sort/imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"groups": [
|
|
||||||
// Side effect imports.
|
|
||||||
["^\\u0000"],
|
|
||||||
// `react` related packages come first.
|
|
||||||
["^react$", "^react-dom$"],
|
|
||||||
// Things that start with a letter (or digit or underscore), or `@` followed by a letter.
|
|
||||||
["^@?\\w"],
|
|
||||||
// Absolute imports and other imports such as Vue-style `@/foo`.
|
|
||||||
// Anything not matched in another group.
|
|
||||||
["^"],
|
|
||||||
// Relative imports.
|
|
||||||
// Anything that starts with a dot.
|
|
||||||
["^\\."],
|
|
||||||
// type imports last as a separate group
|
|
||||||
["^.+\\u0000$"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"simple-import-sort/exports": "error",
|
|
||||||
"import/first": "error",
|
|
||||||
"import/newline-after-import": "error",
|
|
||||||
"import/no-duplicates": "error"
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"react": {
|
|
||||||
"version": "detect"
|
|
||||||
},
|
|
||||||
"import/resolver": {
|
|
||||||
"node": {
|
|
||||||
"extensions": [".js", ".jsx", ".ts", ".tsx"],
|
|
||||||
"project": [
|
|
||||||
"tsconfig.base.json",
|
|
||||||
"packages/*/tsconfig.json",
|
|
||||||
"apps/*/tsconfig.json"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"typescript": {
|
|
||||||
"alwaysTryTypes": true,
|
|
||||||
"project": [
|
|
||||||
"tsconfig.base.json",
|
|
||||||
"packages/*/tsconfig.json",
|
|
||||||
"apps/*/tsconfig.json"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"import/ignore": ["react-native"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// appp/website; extend eslint-config-next
|
|
|
@ -6,6 +6,11 @@ on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
|
|
||||||
|
env:
|
||||||
|
NEXT_PUBLIC_GHOST_API_KEY: ''
|
||||||
|
INFURA_API_KEY: ''
|
||||||
|
TAMAGUI_TARGET: 'web'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build and Test
|
name: Build and Test
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"eslint.packageManager": "yarn",
|
"eslint.packageManager": "yarn",
|
||||||
"npm.packageManager": "yarn"
|
"npm.packageManager": "yarn",
|
||||||
|
"eslint.workingDirectories": [
|
||||||
|
{
|
||||||
|
"mode": "auto",
|
||||||
|
"#comment": "See https://github.com/microsoft/vscode-eslint/issues/1161 for reason (i.e. multiple .eslintrc config files)"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": ["@status-im/eslint-config"]
|
||||||
|
}
|
|
@ -38,6 +38,7 @@
|
||||||
"@types/react-native": "~0.70.6",
|
"@types/react-native": "~0.70.6",
|
||||||
"babel-plugin-module-resolver": "^4.1.0",
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
|
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
|
||||||
|
"@status-im/eslint-config": "*",
|
||||||
"typescript": "^5.0.3"
|
"typescript": "^5.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": [
|
||||||
|
"@status-im/eslint-config",
|
||||||
|
"plugin:tailwindcss/recommended",
|
||||||
|
"next",
|
||||||
|
"next/core-web-vitals"
|
||||||
|
]
|
||||||
|
}
|
|
@ -24,6 +24,7 @@
|
||||||
"@types/react": "^18.0.33",
|
"@types/react": "^18.0.33",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
"@vitejs/plugin-react-swc": "^3.2.0",
|
"@vitejs/plugin-react-swc": "^3.2.0",
|
||||||
|
"@status-im/eslint-config": "*",
|
||||||
"typescript": "^5.0.3",
|
"typescript": "^5.0.3",
|
||||||
"vite": "^4.2.1"
|
"vite": "^4.2.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
IGNORE_TS_CONFIG_PATHS=true
|
IGNORE_TS_CONFIG_PATHS=true
|
||||||
TAMAGUI_TARGET=web
|
TAMAGUI_TARGET=web
|
||||||
TAMAGUI_DISABLE_WARN_DYNAMIC_LOAD=1
|
TAMAGUI_DISABLE_WARN_DYNAMIC_LOAD=1
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": [
|
||||||
|
"@status-im/eslint-config",
|
||||||
|
"plugin:tailwindcss/recommended",
|
||||||
|
"next",
|
||||||
|
"next/core-web-vitals"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"tailwindConfig": "./tailwind.config.cjs"
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import type { env } from './src/config/env.mjs'
|
import type { clientEnv } from './src/config/env.client.mjs'
|
||||||
|
import type { serverEnv } from './src/config/env.server.mjs'
|
||||||
|
|
||||||
type Env = typeof env
|
type Env = typeof clientEnv & typeof serverEnv
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||||
/* eslint-disable import/default */
|
/* eslint-disable import/default */
|
||||||
|
|
||||||
import './src/config/env.mjs'
|
import './src/config/env.server.mjs'
|
||||||
|
import './src/config/env.client.mjs'
|
||||||
|
|
||||||
import tamagui_next_plugin from '@tamagui/next-plugin'
|
import tamagui_next_plugin from '@tamagui/next-plugin'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"typecheck": "tsc",
|
"typecheck": "tsc",
|
||||||
"clean": "rimraf .next .tamagui .vercel/output node_modules .turbo",
|
"clean": "rimraf .next .tamagui .turbo .vercel/output node_modules",
|
||||||
"preview": "next start --port 8151"
|
"preview": "next start --port 8151"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -23,6 +23,7 @@
|
||||||
"@tamagui/next-theme": "1.11.1",
|
"@tamagui/next-theme": "1.11.1",
|
||||||
"@tanstack/react-query": "^4.29.7",
|
"@tanstack/react-query": "^4.29.7",
|
||||||
"@vercel/og": "^0.5.4",
|
"@vercel/og": "^0.5.4",
|
||||||
|
"@tryghost/content-api": "^1.11.13",
|
||||||
"@visx/visx": "^2.18.0",
|
"@visx/visx": "^2.18.0",
|
||||||
"class-variance-authority": "^0.6.0",
|
"class-variance-authority": "^0.6.0",
|
||||||
"d3-array": "^3.2.3",
|
"d3-array": "^3.2.3",
|
||||||
|
@ -37,17 +38,24 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@achingbrain/ssdp": "^4.0.1",
|
"@achingbrain/ssdp": "^4.0.1",
|
||||||
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@tamagui/next-plugin": "1.11.1",
|
"@tamagui/next-plugin": "1.11.1",
|
||||||
"@types/d3-array": "^3.0.4",
|
"@types/d3-array": "^3.0.4",
|
||||||
"@types/d3-time-format": "^4.0.0",
|
"@types/d3-time-format": "^4.0.0",
|
||||||
"@types/node": "^18.11.11",
|
"@types/node": "^18.11.11",
|
||||||
"@types/react": "^18.0.33",
|
"@types/react": "^18.0.33",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
|
"@types/tryghost__content-api": "^1.3.11",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
|
"@status-im/eslint-config": "*",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
|
"rehype-parse": "^8.0.4",
|
||||||
|
"rehype-react": "^7.2.0",
|
||||||
|
"rehype-stringify": "^9.0.3",
|
||||||
"tailwindcss": "^3.3.1",
|
"tailwindcss": "^3.3.1",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
"typescript": "^5.0.3"
|
"typescript": "^5.0.3",
|
||||||
|
"unified": "^10.1.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.x"
|
"node": "^18.x"
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g clip-path="url(#clip0_1111_39343)">
|
||||||
|
<circle cx="10" cy="10" r="10" fill="#647084" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M11.5625 19.8785C11.0534 19.9584 10.5315 19.9999 10 19.9999C9.46846 19.9999 8.94661 19.9584 8.4375 19.8785V12.9694H5.89844V10.0611H8.4375V7.84446C8.4375 5.32289 9.93044 3.93005 12.2146 3.93005C13.3087 3.93005 14.4531 4.12656 14.4531 4.12656V6.60254H13.1921C11.9499 6.60254 11.5625 7.37809 11.5625 8.17376V10.0611H14.3359L13.8926 12.9694H11.5625V19.8785Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1111_39343">
|
||||||
|
<rect width="20" height="20" fill="white" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 790 B |
|
@ -0,0 +1,19 @@
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g clip-path="url(#clip0_1111_39344)">
|
||||||
|
<path
|
||||||
|
d="M11.865 12.79C11.9358 12.86 11.9358 12.9742 11.865 13.045C11.4775 13.43 10.87 13.6175 10.0058 13.6175L9.99917 13.6158L9.9925 13.6175C9.12917 13.6175 8.52083 13.43 8.13333 13.0442C8.0625 12.9742 8.0625 12.86 8.13333 12.79C8.20333 12.72 8.31833 12.72 8.38917 12.79C8.705 13.1042 9.22917 13.2575 9.9925 13.2575L9.99917 13.2592L10.0058 13.2575C10.7683 13.2575 11.2925 13.1042 11.6092 12.79C11.68 12.72 11.795 12.72 11.865 12.79ZM8.99833 10.775C8.99833 10.3525 8.65333 10.0092 8.23 10.0092C7.80583 10.0092 7.46083 10.3525 7.46083 10.775C7.46083 11.1967 7.80583 11.54 8.23 11.54C8.65333 11.5408 8.99833 11.1975 8.99833 10.775ZM20 10C20 15.5225 15.5225 20 10 20C4.4775 20 0 15.5225 0 10C0 4.4775 4.4775 0 10 0C15.5225 0 20 4.4775 20 10ZM15.8333 9.8925C15.8333 9.18333 15.2542 8.60667 14.5417 8.60667C14.1942 8.60667 13.8792 8.74583 13.6467 8.96917C12.7667 8.39 11.5758 8.02167 10.2583 7.97417L10.9792 5.70417L12.9317 6.16167L12.9292 6.19C12.9292 6.77 13.4033 7.24167 13.9858 7.24167C14.5683 7.24167 15.0417 6.77 15.0417 6.19C15.0417 5.61 14.5683 5.13833 13.9858 5.13833C13.5383 5.13833 13.1575 5.4175 13.0033 5.80833L10.8992 5.315C10.8075 5.2925 10.7133 5.34583 10.685 5.43583L9.88083 7.9675C8.50083 7.98417 7.25167 8.35583 6.3325 8.95167C6.10167 8.73917 5.79583 8.60583 5.4575 8.60583C4.74583 8.60667 4.16667 9.18333 4.16667 9.8925C4.16667 10.3642 4.42583 10.7725 4.80667 10.9967C4.78167 11.1333 4.765 11.2725 4.765 11.4133C4.765 13.3142 7.1025 14.8608 9.97583 14.8608C12.8492 14.8608 15.1867 13.3142 15.1867 11.4133C15.1867 11.28 15.1725 11.1492 15.15 11.02C15.555 10.8025 15.8333 10.3817 15.8333 9.8925ZM11.7733 10.01C11.3492 10.01 11.005 10.3533 11.005 10.7758C11.005 11.1975 11.35 11.5408 11.7733 11.5408C12.1967 11.5408 12.5417 11.1975 12.5417 10.7758C12.5417 10.3533 12.1975 10.01 11.7733 10.01Z"
|
||||||
|
fill="#647084"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1111_39344">
|
||||||
|
<rect width="20" height="20" fill="white" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,108 @@
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g clip-path="url(#clip0_1111_39335)">
|
||||||
|
<g clip-path="url(#clip1_1111_39335)">
|
||||||
|
<mask
|
||||||
|
id="mask0_1111_39335"
|
||||||
|
style="mask-type: alpha"
|
||||||
|
maskUnits="userSpaceOnUse"
|
||||||
|
x="0"
|
||||||
|
y="-1"
|
||||||
|
width="20"
|
||||||
|
height="21"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10 -0.000366211C2.5 -0.000366211 0 2.49963 0 9.99963C0 17.4996 2.5 19.9996 10 19.9996C17.5 19.9996 20 17.4996 20 9.99963C20 2.49963 17.5 -0.000366211 10 -0.000366211Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_1111_39335)">
|
||||||
|
<g filter="url(#filter0_f_1111_39335)">
|
||||||
|
<circle cx="9.0625" cy="13.4375" r="17.8125" fill="#647084" />
|
||||||
|
</g>
|
||||||
|
<g filter="url(#filter1_d_1111_39335)">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M12.6063 10.6139C12.4204 12.9416 10.7481 14.9991 8.48319 15.1285C7.0925 15.2078 5.70345 14.3591 5.62842 12.9809C5.57025 11.8718 6.25689 11.0625 7.45512 10.7524C7.73822 10.6783 8.02824 10.6331 8.32063 10.6176C9.59334 10.5459 10.3903 10.8376 11.6631 10.7654C11.9567 10.7505 12.2481 10.7066 12.5329 10.6344C12.5558 10.6279 12.5835 10.6209 12.6063 10.6139Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M7.39258 9.38378C7.57851 7.05609 9.25081 4.99853 11.5157 4.86912C12.9064 4.78986 14.2954 5.63855 14.3705 7.01672C14.4314 8.12584 13.742 8.93517 12.5438 9.24574C12.2607 9.31992 11.9707 9.36509 11.6783 9.38054C10.4055 9.45225 9.60854 9.16055 8.33583 9.23173C8.0422 9.24659 7.75081 9.29049 7.46597 9.36275C7.44314 9.36976 7.41541 9.37677 7.39258 9.38378Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter
|
||||||
|
id="filter0_f_1111_39335"
|
||||||
|
x="-14.6439"
|
||||||
|
y="-10.2689"
|
||||||
|
width="47.4128"
|
||||||
|
height="47.4128"
|
||||||
|
filterUnits="userSpaceOnUse"
|
||||||
|
color-interpolation-filters="sRGB"
|
||||||
|
>
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||||
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="BackgroundImageFix"
|
||||||
|
result="shape"
|
||||||
|
/>
|
||||||
|
<feGaussianBlur
|
||||||
|
stdDeviation="2.94696"
|
||||||
|
result="effect1_foregroundBlur_1111_39335"
|
||||||
|
/>
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
id="filter1_d_1111_39335"
|
||||||
|
x="-15.8517"
|
||||||
|
y="-10.8855"
|
||||||
|
width="51.7033"
|
||||||
|
height="53.2229"
|
||||||
|
filterUnits="userSpaceOnUse"
|
||||||
|
color-interpolation-filters="sRGB"
|
||||||
|
>
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||||
|
<feColorMatrix
|
||||||
|
in="SourceAlpha"
|
||||||
|
type="matrix"
|
||||||
|
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||||
|
result="hardAlpha"
|
||||||
|
/>
|
||||||
|
<feOffset dy="5.72711" />
|
||||||
|
<feGaussianBlur stdDeviation="10.7383" />
|
||||||
|
<feColorMatrix
|
||||||
|
type="matrix"
|
||||||
|
values="0 0 0 0 0.0352941 0 0 0 0 0.0627451 0 0 0 0 0.109804 0 0 0 0.12 0"
|
||||||
|
/>
|
||||||
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in2="BackgroundImageFix"
|
||||||
|
result="effect1_dropShadow_1111_39335"
|
||||||
|
/>
|
||||||
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="effect1_dropShadow_1111_39335"
|
||||||
|
result="shape"
|
||||||
|
/>
|
||||||
|
</filter>
|
||||||
|
<clipPath id="clip0_1111_39335">
|
||||||
|
<rect width="20" height="20" fill="white" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip1_1111_39335">
|
||||||
|
<rect width="20" height="20" fill="white" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.5 KiB |
|
@ -0,0 +1,12 @@
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 3.51238C19.2477 3.85565 18.452 4.08379 17.6375 4.18977C18.4958 3.65705 19.1376 2.8135 19.4412 1.81891C18.6375 2.31758 17.7582 2.66875 16.8412 2.85722C16.2791 2.22923 15.5493 1.79265 14.7469 1.60417C13.9444 1.4157 13.1063 1.48406 12.3415 1.80037C11.5766 2.11667 10.9204 2.66631 10.458 3.37786C9.99566 4.08942 9.74853 4.93 9.74875 5.79039C9.74875 6.13039 9.77625 6.45732 9.84375 6.76855C8.2126 6.68489 6.6167 6.242 5.16038 5.46885C3.70406 4.6957 2.42012 3.60969 1.3925 2.28183C0.866024 3.22581 0.703261 4.34422 0.937354 5.40933C1.17145 6.47445 1.78479 7.40617 2.6525 8.01479C2.00345 7.99658 1.36809 7.81539 0.8 7.48648V7.53356C0.801087 8.52405 1.12833 9.48391 1.72665 10.2516C2.32496 11.0192 3.15781 11.5478 4.085 11.7483C3.73423 11.845 3.37273 11.8925 3.01 11.8895C2.74949 11.8944 2.48923 11.8698 2.23375 11.8163C2.49868 12.6676 3.00936 13.4121 3.69549 13.9472C4.38162 14.4824 5.20945 14.7818 6.065 14.8044C4.61367 15.9918 2.82372 16.636 0.98125 16.6338C0.645 16.6338 0.3225 16.6182 0 16.575C1.87485 17.8389 4.05959 18.5075 6.29 18.4999C13.835 18.4999 17.96 11.9614 17.96 6.29386C17.96 6.10424 17.9538 5.92116 17.945 5.73939C18.7537 5.13391 19.4501 4.37923 20 3.51238Z"
|
||||||
|
fill="#647084"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -1,8 +1,8 @@
|
||||||
import NextLink from 'next/link'
|
import NextLink from 'next/link'
|
||||||
|
|
||||||
import type { ComponentProps } from 'react'
|
import type { ComponentPropsWithRef } from 'react'
|
||||||
|
|
||||||
export const Link = (props: ComponentProps<typeof NextLink>) => {
|
export const Link = (props: ComponentPropsWithRef<typeof NextLink>) => {
|
||||||
const external =
|
const external =
|
||||||
typeof props.href === 'string'
|
typeof props.href === 'string'
|
||||||
? props.href.startsWith('http')
|
? props.href.startsWith('http')
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const Logo = (props: Props) => {
|
||||||
const { pathname } = useRouter()
|
const { pathname } = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-shrink-0 items-center gap-2">
|
<div className="flex shrink-0 items-center gap-2">
|
||||||
{match(pathname)
|
{match(pathname)
|
||||||
.with(
|
.with(
|
||||||
P.when(p => p.startsWith('/insights')),
|
P.when(p => p.startsWith('/insights')),
|
||||||
|
|
|
@ -38,7 +38,7 @@ export const NavMenu = () => {
|
||||||
data-visible={visible}
|
data-visible={visible}
|
||||||
className={cx([
|
className={cx([
|
||||||
'fixed left-1/2 top-5 z-10 min-w-[746px] -translate-x-1/2 overflow-hidden',
|
'fixed left-1/2 top-5 z-10 min-w-[746px] -translate-x-1/2 overflow-hidden',
|
||||||
'bg-blur-neutral-80/80 border-neutral-80/5 rounded-2xl border backdrop-blur-md',
|
'rounded-2xl border border-neutral-80/5 bg-blur-neutral-80/80 backdrop-blur-md',
|
||||||
'data-[visible=false]:pointer-events-none',
|
'data-[visible=false]:pointer-events-none',
|
||||||
'opacity-0 transition-opacity data-[visible=true]:opacity-100',
|
'opacity-0 transition-opacity data-[visible=true]:opacity-100',
|
||||||
])}
|
])}
|
||||||
|
@ -58,7 +58,7 @@ export const NavMenu = () => {
|
||||||
className={cx([
|
className={cx([
|
||||||
'grid gap-3 pb-12 pl-[60px] pt-6',
|
'grid gap-3 pb-12 pl-[60px] pt-6',
|
||||||
'data-[state=open]:animate-in',
|
'data-[state=open]:animate-in',
|
||||||
'data-[state=closed]:animate-out fade-out-20',
|
'fade-out-20 data-[state=closed]:animate-out',
|
||||||
])}
|
])}
|
||||||
>
|
>
|
||||||
{links.map(link => {
|
{links.map(link => {
|
||||||
|
@ -91,8 +91,8 @@ export const NavMenu = () => {
|
||||||
|
|
||||||
<NavigationMenu.Viewport
|
<NavigationMenu.Viewport
|
||||||
className={cx([
|
className={cx([
|
||||||
'data-[state=open]:animate-heightIn data-[state=closed]:animate-heightOut',
|
'data-[state=closed]:animate-heightOut data-[state=open]:animate-heightIn',
|
||||||
'transition-height h-[var(--radix-navigation-menu-viewport-height)]',
|
'h-[var(--radix-navigation-menu-viewport-height)] transition-height',
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
</NavigationMenu.Root>
|
</NavigationMenu.Root>
|
||||||
|
|
|
@ -118,7 +118,7 @@ const ActionCard = (props: ActionCardProps) => {
|
||||||
const { title, description, action } = props
|
const { title, description, action } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-netural-95 border-neutral-90 flex items-center rounded-[20px] border px-5 py-3">
|
<div className="bg-netural-95 flex items-center rounded-[20px] border border-neutral-90 px-5 py-3">
|
||||||
<div className="grid flex-1 gap-px">
|
<div className="grid flex-1 gap-px">
|
||||||
<Text size={19} color="$white-100" weight="semibold">
|
<Text size={19} color="$white-100" weight="semibold">
|
||||||
{title}
|
{title}
|
||||||
|
|
|
@ -36,7 +36,7 @@ const SideBar = (props: Props) => {
|
||||||
}, [defaultLabel])
|
}, [defaultLabel])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-neutral-10 border-r p-5">
|
<div className="border-r border-neutral-10 p-5">
|
||||||
<aside className=" sticky top-5 min-w-[320px]">
|
<aside className=" sticky top-5 min-w-[320px]">
|
||||||
<Accordion.Root
|
<Accordion.Root
|
||||||
type="single"
|
type="single"
|
||||||
|
|
|
@ -31,19 +31,19 @@ const issues = [
|
||||||
|
|
||||||
export const TableIssues = () => {
|
export const TableIssues = () => {
|
||||||
return (
|
return (
|
||||||
<div className="border-neutral-10 overflow-hidden rounded-2xl border">
|
<div className="overflow-hidden rounded-2xl border border-neutral-10">
|
||||||
<div className="bg-neutral-5 border-neutral-10 border-b p-3">
|
<div className="border-b border-neutral-10 bg-neutral-5 p-3">
|
||||||
<Text size={15} weight="medium">
|
<Text size={15} weight="medium">
|
||||||
784 Open
|
784 Open
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="divide-neutral-10 divide-y">
|
<div className="divide-y divide-neutral-10">
|
||||||
{issues.map(issue => (
|
{issues.map(issue => (
|
||||||
<Link
|
<Link
|
||||||
key={issue.id}
|
key={issue.id}
|
||||||
href={`https://github.com/status-im/status-react/issues/${issue.id}`}
|
href={`https://github.com/status-im/status-react/issues/${issue.id}`}
|
||||||
className="hover:bg-neutral-5 flex items-center justify-between px-4 py-3 transition-colors duration-200"
|
className="flex items-center justify-between px-4 py-3 transition-colors duration-200 hover:bg-neutral-5"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Text size={15} weight="medium">
|
<Text size={15} weight="medium">
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const envSchema = z.object({
|
||||||
|
NEXT_PUBLIC_GHOST_API_KEY: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const clientEnv = envSchema.parse({
|
||||||
|
NEXT_PUBLIC_GHOST_API_KEY: process.env.NEXT_PUBLIC_GHOST_API_KEY,
|
||||||
|
})
|
|
@ -1,8 +0,0 @@
|
||||||
import { z } from 'zod'
|
|
||||||
|
|
||||||
export const envSchema = z.object({
|
|
||||||
INFURA_API_KEY: z.string(),
|
|
||||||
TAMAGUI_TARGET: z.literal('web'),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const env = envSchema.parse(process.env)
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
throw new Error(
|
||||||
|
'❌ Attempted to access a server-side environment variable on the client'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const envSchema = z.object({
|
||||||
|
INFURA_API_KEY: z.string(),
|
||||||
|
TAMAGUI_TARGET: z.literal('web'),
|
||||||
|
NEXT_PUBLIC_GHOST_API_KEY: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const serverEnv = envSchema.parse(process.env)
|
|
@ -15,21 +15,23 @@ import type { PageLayout } from 'next'
|
||||||
export const AppLayout: PageLayout = page => {
|
export const AppLayout: PageLayout = page => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className="hidden lg:block">
|
||||||
<NavMenu />
|
<NavMenu />
|
||||||
|
</div>
|
||||||
<div className="min-h-full bg-neutral-100">
|
<div className="min-h-full bg-neutral-100">
|
||||||
<NavigationMenu.Root>
|
<NavigationMenu.Root>
|
||||||
<div className="flex items-center px-6">
|
<div className="flex items-center px-6 py-3">
|
||||||
<div className="mr-5">
|
<div className="mr-5">
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<Logo />
|
<Logo />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1">
|
<div className="hidden flex-1 lg:flex">
|
||||||
<NavigationMenu.List className="flex items-center">
|
<NavigationMenu.List className="flex items-center">
|
||||||
{Object.entries(LINKS).map(([name, links]) => (
|
{Object.entries(LINKS).map(([name, links]) => (
|
||||||
<NavigationMenu.Item key={name}>
|
<NavigationMenu.Item key={name}>
|
||||||
<NavigationMenu.Trigger className="py-4 pr-5 aria-expanded:opacity-50">
|
<NavigationMenu.Trigger className="pr-5 aria-expanded:opacity-50">
|
||||||
<Text size={15} weight="medium" color="$white-100">
|
<Text size={15} weight="medium" color="$white-100">
|
||||||
{name}
|
{name}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -64,7 +66,7 @@ export const AppLayout: PageLayout = page => {
|
||||||
</NavigationMenu.List>
|
</NavigationMenu.List>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="hidden justify-end lg:flex">
|
||||||
<Button
|
<Button
|
||||||
size={32}
|
size={32}
|
||||||
variant="darkGrey"
|
variant="darkGrey"
|
||||||
|
@ -76,8 +78,8 @@ export const AppLayout: PageLayout = page => {
|
||||||
</div>
|
</div>
|
||||||
<NavigationMenu.Viewport
|
<NavigationMenu.Viewport
|
||||||
className={cx([
|
className={cx([
|
||||||
'data-[state=open]:animate-heightIn data-[state=closed]:animate-heightOut',
|
'data-[state=closed]:animate-heightOut data-[state=open]:animate-heightIn',
|
||||||
'transition-height h-[var(--radix-navigation-menu-viewport-height)]',
|
'h-[var(--radix-navigation-menu-viewport-height)] transition-height',
|
||||||
// 'data-[state=open]:animate-heightIn animate-',
|
// 'data-[state=open]:animate-heightIn animate-',
|
||||||
// 'data-[state=closed]:animate-heightOut',
|
// 'data-[state=closed]:animate-heightOut',
|
||||||
// 'transition-height h-[var(--radix-navigation-menu-viewport-height)]',
|
// 'transition-height h-[var(--radix-navigation-menu-viewport-height)]',
|
||||||
|
|
|
@ -83,7 +83,7 @@ const MENU_LINKS = [
|
||||||
|
|
||||||
export const InsightsLayout: PageLayout = page => {
|
export const InsightsLayout: PageLayout = page => {
|
||||||
return AppLayout(
|
return AppLayout(
|
||||||
<div className="bg-white-100 relative mx-1 flex min-h-[calc(100vh-56px-4px)] rounded-3xl">
|
<div className="relative mx-1 flex min-h-[calc(100vh-56px-4px)] rounded-3xl bg-white-100">
|
||||||
<SideBar data={MENU_LINKS} />
|
<SideBar data={MENU_LINKS} />
|
||||||
<main className="flex-1">{page}</main>
|
<main className="flex-1">{page}</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { EthereumClient } from '@status-im/js'
|
import { EthereumClient } from '@status-im/js'
|
||||||
|
|
||||||
import { env } from '@/config/env.mjs'
|
import { serverEnv } from '@/config/env.server.mjs'
|
||||||
|
|
||||||
let client: EthereumClient | undefined
|
let client: EthereumClient | undefined
|
||||||
|
|
||||||
export function getEthereumClient(): EthereumClient | undefined {
|
export function getEthereumClient(): EthereumClient | undefined {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
client = new EthereumClient(
|
client = new EthereumClient(
|
||||||
`https://mainnet.infura.io/v3/${env.INFURA_API_KEY}`
|
`https://mainnet.infura.io/v3/${serverEnv.INFURA_API_KEY}`
|
||||||
)
|
)
|
||||||
|
|
||||||
return client
|
return client
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
import GhostContentAPI from '@tryghost/content-api'
|
||||||
|
|
||||||
|
import { clientEnv } from '@/config/env.client.mjs'
|
||||||
|
|
||||||
|
/** @see https://ghost.org/docs/content-api# */
|
||||||
|
const ghost = new GhostContentAPI({
|
||||||
|
url: 'https://our.status.im',
|
||||||
|
key: clientEnv.NEXT_PUBLIC_GHOST_API_KEY,
|
||||||
|
version: 'v5.0',
|
||||||
|
})
|
||||||
|
|
||||||
|
type Params = { page?: number; limit?: number; tag?: string }
|
||||||
|
|
||||||
|
export const getPosts = async (params: Params = {}) => {
|
||||||
|
const { page = 1, limit = 7, tag } = params
|
||||||
|
|
||||||
|
const response = await ghost.posts.browse({
|
||||||
|
include: ['tags', 'authors'],
|
||||||
|
order: 'published_at DESC',
|
||||||
|
limit,
|
||||||
|
page,
|
||||||
|
...(tag && { filter: `tag:${tag}` }),
|
||||||
|
filter: 'visibility:public',
|
||||||
|
})
|
||||||
|
|
||||||
|
return { posts: [...response], meta: response.meta }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPostBySlug = async (slug: string) => {
|
||||||
|
return await ghost.posts.read(
|
||||||
|
{ slug },
|
||||||
|
{
|
||||||
|
include: ['tags', 'authors'],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPostsByTagSlug = async (slug: string, page = 1) => {
|
||||||
|
const response = await ghost.posts.browse({
|
||||||
|
filter: `tag:${slug}+visibility:public`,
|
||||||
|
include: ['tags', 'authors'],
|
||||||
|
limit: 6,
|
||||||
|
order: 'published_at DESC',
|
||||||
|
page,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { posts: [...response], meta: response.meta }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPostsByAuthorSlug = async (slug: string, page = 1) => {
|
||||||
|
const response = await ghost.posts.browse({
|
||||||
|
filter: `author:${slug}+visibility:public`,
|
||||||
|
include: ['tags', 'authors'],
|
||||||
|
limit: 6,
|
||||||
|
order: 'published_at DESC',
|
||||||
|
page,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { posts: [...response], meta: response.meta }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPostSlugs = async (): Promise<string[]> => {
|
||||||
|
const posts = await ghost.posts.browse({
|
||||||
|
limit: '7',
|
||||||
|
fields: 'slug',
|
||||||
|
filter: 'visibility:public',
|
||||||
|
})
|
||||||
|
|
||||||
|
return posts.map(post => post.slug)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTags = async () => {
|
||||||
|
return await ghost.tags.browse({
|
||||||
|
limit: 'all',
|
||||||
|
fields: 'name,slug',
|
||||||
|
filter: 'visibility:public',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTagSlugs = async (): Promise<string[]> => {
|
||||||
|
const tags = await ghost.tags.browse({
|
||||||
|
limit: 'all',
|
||||||
|
fields: 'slug',
|
||||||
|
filter: 'visibility:public',
|
||||||
|
})
|
||||||
|
|
||||||
|
return tags.map(tag => tag.slug)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAuthorSlugs = async (): Promise<string[]> => {
|
||||||
|
const authors = await ghost.authors.browse({
|
||||||
|
limit: 'all',
|
||||||
|
fields: 'slug',
|
||||||
|
filter: 'visibility:public',
|
||||||
|
})
|
||||||
|
|
||||||
|
return authors.map(author => author.slug)
|
||||||
|
}
|
|
@ -1,11 +1,249 @@
|
||||||
|
import { createElement, Fragment, useMemo } from 'react'
|
||||||
|
|
||||||
|
import { Avatar, Provider, Tag, Text } from '@status-im/components'
|
||||||
|
import { renderToString } from 'react-dom/server'
|
||||||
|
import rehypeParse from 'rehype-parse'
|
||||||
|
import rehypeReact from 'rehype-react'
|
||||||
|
import { unified } from 'unified'
|
||||||
|
|
||||||
|
import { Breadcrumbs } from '@/components'
|
||||||
|
import { formatDate } from '@/components/chart/utils/format-time'
|
||||||
import { AppLayout } from '@/layouts/app-layout'
|
import { AppLayout } from '@/layouts/app-layout'
|
||||||
|
import { getPostBySlug, getPostsByTagSlug, getPostSlugs } from '@/lib/ghost'
|
||||||
|
|
||||||
import type { Page } from 'next'
|
import { PostCard } from '.'
|
||||||
|
|
||||||
|
import type { PostOrPage } from '@tryghost/content-api'
|
||||||
|
import type {
|
||||||
|
GetStaticPaths,
|
||||||
|
GetStaticProps,
|
||||||
|
InferGetStaticPropsType,
|
||||||
|
Page,
|
||||||
|
} from 'next'
|
||||||
|
import type React from 'react'
|
||||||
|
|
||||||
|
type Params = { slug: string }
|
||||||
|
|
||||||
|
export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
||||||
|
const slugs = await getPostSlugs()
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths: slugs.map(slug => ({ params: { slug } })),
|
||||||
|
fallback: 'blocking',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticProps: GetStaticProps<
|
||||||
|
{
|
||||||
|
post: PostOrPage
|
||||||
|
relatedPosts: PostOrPage[]
|
||||||
|
},
|
||||||
|
Params
|
||||||
|
> = async context => {
|
||||||
|
const post = await getPostBySlug(context.params!.slug)
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
return {
|
||||||
|
// notFound: true,
|
||||||
|
redirect: { destination: '/blog', permanent: false },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let relatedPosts: PostOrPage[] = []
|
||||||
|
if (post.primary_tag) {
|
||||||
|
const { posts } = await getPostsByTagSlug(post.primary_tag.slug)
|
||||||
|
|
||||||
|
const filteredPosts = posts.filter(p => p.slug !== post.slug).slice(0, 4)
|
||||||
|
relatedPosts = filteredPosts
|
||||||
|
}
|
||||||
|
|
||||||
|
const r = unified()
|
||||||
|
.use(rehypeParse, { fragment: true })
|
||||||
|
.use(rehypeReact, {
|
||||||
|
createElement,
|
||||||
|
Fragment,
|
||||||
|
components: {
|
||||||
|
a: (props: React.ComponentProps<'a'>) => (
|
||||||
|
<a {...props}>
|
||||||
|
<Text size={19} weight="regular" color="$blue-50">
|
||||||
|
{props.children!}
|
||||||
|
</Text>
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
p: ({ children }: React.ComponentProps<'p'>) => (
|
||||||
|
<p className="">
|
||||||
|
<Text size={19} weight="regular">
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
img: (props: React.ComponentProps<'img'>) => (
|
||||||
|
<img {...props} className="rounded-[20px]" /> // eslint-disable-line jsx-a11y/alt-text
|
||||||
|
),
|
||||||
|
h2: ({ children, ...rest }: React.ComponentProps<'h2'>) => (
|
||||||
|
<h2 {...rest}>
|
||||||
|
<Text size={27} weight="semibold">
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</h2>
|
||||||
|
),
|
||||||
|
ul: (props: React.ComponentProps<'ul'>) => (
|
||||||
|
<ul {...props} className="list-inside list-disc" />
|
||||||
|
),
|
||||||
|
pre: (props: React.ComponentProps<'pre'>) => (
|
||||||
|
<pre {...props} className="overflow-scroll" />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.processSync(post.html!).result
|
||||||
|
|
||||||
|
const html = renderToString(<Provider>{r}</Provider>)
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
post: {
|
||||||
|
...post,
|
||||||
|
// fixme?: name html
|
||||||
|
hhh: html,
|
||||||
|
},
|
||||||
|
relatedPosts,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = InferGetStaticPropsType<typeof getStaticProps>
|
||||||
|
|
||||||
|
const BlogDetailPage: Page<Props> = ({ post, relatedPosts }) => {
|
||||||
|
const author = post.primary_author!
|
||||||
|
|
||||||
|
// todo?: MOVE TO SERVER SIDE
|
||||||
|
const result = useMemo(() => {
|
||||||
|
return unified()
|
||||||
|
.use(rehypeParse, { fragment: true })
|
||||||
|
.use(rehypeReact, {
|
||||||
|
createElement,
|
||||||
|
Fragment,
|
||||||
|
components: {
|
||||||
|
a: (props: React.ComponentProps<'a'>) => (
|
||||||
|
<a {...props}>
|
||||||
|
<Text size={19} weight="regular" color="$blue-50">
|
||||||
|
{props.children!}
|
||||||
|
</Text>
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
p: ({ children }: React.ComponentProps<'p'>) => (
|
||||||
|
<p className="">
|
||||||
|
<Text size={19} weight="regular">
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
img: (props: React.ComponentProps<'img'>) => (
|
||||||
|
<img {...props} className="rounded-[20px]" /> // eslint-disable-line jsx-a11y/alt-text
|
||||||
|
),
|
||||||
|
h2: ({ children, ...rest }: React.ComponentProps<'h2'>) => (
|
||||||
|
<h2 {...rest}>
|
||||||
|
<Text size={27} weight="semibold">
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</h2>
|
||||||
|
),
|
||||||
|
ul: (props: React.ComponentProps<'ul'>) => (
|
||||||
|
<ul {...props} className="list-inside list-disc" />
|
||||||
|
),
|
||||||
|
pre: (props: React.ComponentProps<'pre'>) => (
|
||||||
|
<pre {...props} className="overflow-scroll" />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.processSync(post.html!).result
|
||||||
|
}, [post.html])
|
||||||
|
|
||||||
const BlogDetailPage: Page = () => {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="min-h-[900px] rounded-3xl bg-white-100 lg:mx-1">
|
||||||
<h1>Blog</h1>
|
<div className="border-b border-neutral-10 px-5 py-[13px]">
|
||||||
|
<Breadcrumbs cutFirstSegment={false} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto flex max-w-2xl flex-col gap-3 px-5 pb-6 pt-12 lg:pt-20">
|
||||||
|
<div className="flex">
|
||||||
|
{post.primary_tag && (
|
||||||
|
<Tag size={32} label={post.primary_tag!.name!} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-[40px] font-bold leading-[44px] tracking-[-.02em] lg:text-[64px] lg:leading-[68px]">
|
||||||
|
{post.title!}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div className="mt-auto flex h-5 items-center gap-1">
|
||||||
|
<Avatar
|
||||||
|
type="user"
|
||||||
|
size={20}
|
||||||
|
name={author.name ?? author.slug}
|
||||||
|
src={author.profile_image ?? undefined}
|
||||||
|
/>
|
||||||
|
<Text size={15} weight="semibold">
|
||||||
|
{author.name ?? author.slug}
|
||||||
|
</Text>
|
||||||
|
<Text size={15} color="$neutral-50">
|
||||||
|
on {formatDate(new Date(post.published_at!))}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full px-2 py-4 lg:px-6 lg:py-10">
|
||||||
|
<img
|
||||||
|
src={post.feature_image!}
|
||||||
|
className="aspect-[374/182] h-full w-full rounded-[20px] object-cover lg:aspect-[1456/470]"
|
||||||
|
alt={post.feature_image_alt!}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto flex max-w-2xl flex-col gap-12 px-5 py-6">
|
||||||
|
{result}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto flex max-w-2xl flex-col gap-[17px] px-5 py-6">
|
||||||
|
<div className="flex flex-row items-center gap-2">
|
||||||
|
<Avatar
|
||||||
|
type="user"
|
||||||
|
size={32}
|
||||||
|
name={author.name ?? author.slug}
|
||||||
|
src={author.profile_image ?? undefined}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<Text size={15} weight="semibold">
|
||||||
|
{author.name ?? author.slug}
|
||||||
|
</Text>
|
||||||
|
<Text size={13} color="$neutral-50">
|
||||||
|
{author.meta_description}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{relatedPosts.length && (
|
||||||
|
<div className="border-t border-neutral-10 bg-neutral-5 px-5 pb-[64px] pt-12 lg:px-10">
|
||||||
|
<div className="mb-6">
|
||||||
|
<Text size={27} weight="semibold">
|
||||||
|
Related articles
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="grid auto-rows-[1fr] grid-cols-[repeat(auto-fill,minmax(350px,1fr))] gap-5"> */}
|
||||||
|
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||||
|
{relatedPosts.map(post => (
|
||||||
|
<PostCard key={post.id} post={post} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* <div
|
||||||
|
className="mx-auto grid max-w-2xl gap-4"
|
||||||
|
dangerouslySetInnerHTML={{ __html: post.hhh }}
|
||||||
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
import { Avatar, Button, Text } from '@status-im/components'
|
||||||
|
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||||
|
|
||||||
|
// import { redirect } from 'next/navigation'
|
||||||
|
// import { useRouter } from 'next/router'
|
||||||
|
import { Breadcrumbs } from '@/components'
|
||||||
|
import { AppLayout } from '@/layouts/app-layout'
|
||||||
|
import { getAuthorSlugs, getPostsByAuthorSlug } from '@/lib/ghost'
|
||||||
|
|
||||||
|
import { PostCard } from '..'
|
||||||
|
|
||||||
|
import type { PostOrPage, PostsOrPages } from '@tryghost/content-api'
|
||||||
|
import type {
|
||||||
|
GetStaticPaths,
|
||||||
|
GetStaticProps,
|
||||||
|
InferGetStaticPropsType,
|
||||||
|
Page,
|
||||||
|
} from 'next'
|
||||||
|
|
||||||
|
type Params = { slug: string }
|
||||||
|
|
||||||
|
export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
||||||
|
const slugs = await getAuthorSlugs()
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths: slugs.map(slug => ({ params: { slug } })),
|
||||||
|
fallback: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticProps: GetStaticProps<
|
||||||
|
{
|
||||||
|
posts: PostOrPage[]
|
||||||
|
meta: PostsOrPages['meta']
|
||||||
|
},
|
||||||
|
Params
|
||||||
|
> = async context => {
|
||||||
|
const { posts, meta } = await getPostsByAuthorSlug(context.params!.slug)
|
||||||
|
|
||||||
|
if (!posts || !posts.length) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
// redirect: { destination: '/blog', permanent: false },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
posts,
|
||||||
|
meta,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = InferGetStaticPropsType<typeof getStaticProps>
|
||||||
|
|
||||||
|
const BlogAuthorPage: Page<Props> = ({ posts, meta }) => {
|
||||||
|
// const { isFallback } = useRouter()
|
||||||
|
|
||||||
|
// if (isFallback || !posts.length) {
|
||||||
|
// redirect('/blog')
|
||||||
|
// }
|
||||||
|
|
||||||
|
const author = posts[0].primary_author!
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
// error,
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
// isFetching,
|
||||||
|
// isFetchingNextPage,
|
||||||
|
// status,
|
||||||
|
// isFetched,
|
||||||
|
} = useInfiniteQuery({
|
||||||
|
queryKey: ['posts', author.slug],
|
||||||
|
queryFn: async ({ pageParam: page, queryKey }) => {
|
||||||
|
const [, tag] = queryKey
|
||||||
|
|
||||||
|
const { posts, meta } = await getPostsByAuthorSlug(tag, page)
|
||||||
|
|
||||||
|
return { posts, meta }
|
||||||
|
},
|
||||||
|
getNextPageParam: ({ meta }) => meta.pagination.next,
|
||||||
|
initialData: { pages: [{ posts, meta }], pageParams: [1] },
|
||||||
|
staleTime: Infinity,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const allPosts = data.pages.flatMap(page => page.posts)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-[900px] rounded-3xl bg-white-100 lg:mx-1">
|
||||||
|
<div className="border-b border-neutral-10 px-5 py-[13px]">
|
||||||
|
<Breadcrumbs cutFirstSegment={false} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-5">
|
||||||
|
<div className="mx-auto max-w-[1184px] pb-24 pt-12 lg:pb-32 lg:pt-20">
|
||||||
|
<div className="mb-12 grid gap-2">
|
||||||
|
<Avatar
|
||||||
|
type="user"
|
||||||
|
size={56}
|
||||||
|
name={author.name ?? author.slug}
|
||||||
|
src={author.profile_image ?? undefined}
|
||||||
|
/>
|
||||||
|
<h1 className="text-[40px] font-bold leading-[44px] tracking-[-.02em] lg:text-[64px] lg:leading-[68px]">
|
||||||
|
{author.name}
|
||||||
|
</h1>
|
||||||
|
<Text size={19}>{author.meta_description}</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid auto-rows-[1fr] grid-cols-[repeat(auto-fill,minmax(350px,1fr))] gap-5">
|
||||||
|
{allPosts.map(post => (
|
||||||
|
<PostCard key={post.id} post={post} showAuthor={false} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasNextPage && (
|
||||||
|
<div className="mt-8 flex justify-center">
|
||||||
|
<Button variant="outline" onPress={() => fetchNextPage()}>
|
||||||
|
Load more posts
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
BlogAuthorPage.getLayout = AppLayout
|
||||||
|
|
||||||
|
export default BlogAuthorPage
|
|
@ -1,64 +1,223 @@
|
||||||
import { Button, Shadow, Tag, Text } from '@status-im/components'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
import { Avatar, Button, Shadow, Tag, Text } from '@status-im/components'
|
||||||
|
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||||
|
|
||||||
|
// import Image from 'next/image'
|
||||||
|
import { formatDate } from '@/components/chart/utils/format-time'
|
||||||
|
import { Link } from '@/components/link'
|
||||||
import { AppLayout } from '@/layouts/app-layout'
|
import { AppLayout } from '@/layouts/app-layout'
|
||||||
|
import { getPosts } from '@/lib/ghost'
|
||||||
|
|
||||||
import type { Page } from 'next'
|
import type { PostOrPage, PostsOrPages } from '@tryghost/content-api'
|
||||||
|
import type { GetStaticProps, InferGetStaticPropsType, Page } from 'next'
|
||||||
|
|
||||||
|
export const getStaticProps: GetStaticProps<{
|
||||||
|
posts: PostOrPage[]
|
||||||
|
meta: PostsOrPages['meta']
|
||||||
|
}> = async () => {
|
||||||
|
const { posts, meta } = await getPosts()
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
posts,
|
||||||
|
meta,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = InferGetStaticPropsType<typeof getStaticProps>
|
||||||
|
|
||||||
|
const BlogPage: Page<Props> = props => {
|
||||||
|
const { posts, meta } = props
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
// error,
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
// isFetching,
|
||||||
|
// isFetchingNextPage,
|
||||||
|
// status,
|
||||||
|
// isFetched,
|
||||||
|
} = useInfiniteQuery({
|
||||||
|
queryKey: ['posts'],
|
||||||
|
queryFn: async ({ pageParam: page }) => await getPosts({ page }),
|
||||||
|
getNextPageParam: ({ meta }) => meta.pagination.next,
|
||||||
|
initialData: { pages: [{ posts, meta }], pageParams: [1] },
|
||||||
|
staleTime: Infinity,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { highlightedPost, visiblePosts } = useMemo(() => {
|
||||||
|
const [highlightedPost, ...posts] = data!.pages.flatMap(page => page.posts)
|
||||||
|
const maxLength = posts.length - (posts.length % 3) // the number of posts should be divisible by 3
|
||||||
|
const visiblePosts = posts.slice(0, maxLength)
|
||||||
|
return { highlightedPost, visiblePosts }
|
||||||
|
}, [data])
|
||||||
|
|
||||||
const BlogPage: Page = () => {
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white-100 mx-1 min-h-[900px] rounded-3xl">
|
<div className="min-h-[900px] rounded-3xl bg-white-100 lg:mx-1">
|
||||||
<div className="mx-auto max-w-[1184px] py-32">
|
<div className="px-5">
|
||||||
<div className="grid gap-2">
|
<div className="mx-auto max-w-[1184px] pb-24 pt-12 lg:pb-32 lg:pt-20">
|
||||||
<h1 className="text-7xl font-bold">Blog</h1>
|
<div className="mb-10 grid gap-2">
|
||||||
|
<h1 className="text-[40px] font-bold leading-[44px] tracking-[-.02em] lg:text-[64px] lg:leading-[68px]">
|
||||||
|
Blog.
|
||||||
|
</h1>
|
||||||
<Text size={19}>Long form articles, thoughts, and ideas.</Text>
|
<Text size={19}>Long form articles, thoughts, and ideas.</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="mt-16 grid grid-cols-3 gap-5">
|
<div className="mb-[44px] xl:mb-12">
|
||||||
{[1, 2, 3, 4, 5, 6, 7, 8, 9].map(v => (
|
<HighlightedPostCard post={highlightedPost} />
|
||||||
<PostCard key={v} />
|
</div>
|
||||||
|
|
||||||
|
<div className="grid auto-rows-[1fr] grid-cols-[repeat(auto-fill,minmax(350px,1fr))] gap-5">
|
||||||
|
{visiblePosts.map(post => (
|
||||||
|
<PostCard key={post.id} post={post} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-center pt-8">
|
{hasNextPage && (
|
||||||
<Button variant="outline">Load more posts</Button>
|
<div className="mt-8 flex justify-center">
|
||||||
|
<Button variant="outline" onPress={() => fetchNextPage()}>
|
||||||
|
Load more posts
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostCard = () => {
|
type HighlightedPostCardProps = {
|
||||||
|
post: PostOrPage
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HighlightedPostCard = (props: HighlightedPostCardProps) => {
|
||||||
|
const { post } = props
|
||||||
|
const author = post.primary_author!
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Shadow className="border-neutral-5 rounded-[20px] border">
|
// <Link href={`/blog/${post.slug}`} className="flex flex-row-reverse gap-7">
|
||||||
<div className="flex flex-col gap-2 p-4">
|
<Link
|
||||||
<div className="self-start">
|
href={`/blog/${post.slug}`}
|
||||||
<Tag size={24} label="Updates" />
|
className="grid grid-cols-1 gap-5 xl:grid-cols-3 xl:gap-7"
|
||||||
</div>
|
>
|
||||||
<Text size={19} weight="semibold">
|
<div className="col-span-2 w-full flex-[2] shrink-0">
|
||||||
Long form articles, thoughts, and ideas.
|
|
||||||
</Text>
|
|
||||||
<div className="flex gap-1">
|
|
||||||
<Text size={15} weight="semibold">
|
|
||||||
Status
|
|
||||||
</Text>
|
|
||||||
<Text size={15} color="$neutral-50">
|
|
||||||
on Jul 12, 2022
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="px-2 pb-2">
|
|
||||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
|
||||||
<img
|
<img
|
||||||
className="rounded-2xl"
|
className="aspect-[366/206] h-full w-full rounded-2xl object-cover"
|
||||||
style={{
|
src={post.feature_image!}
|
||||||
aspectRatio: '366/206',
|
alt={post.feature_image_alt!}
|
||||||
objectFit: 'cover',
|
|
||||||
}}
|
|
||||||
src="https://images.unsplash.com/photo-1683053243792-28e9d984c25a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1780&q=80"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-[1] flex-col gap-2 xl:py-5 xl:pr-5">
|
||||||
|
<div className="h-6 overflow-hidden">
|
||||||
|
{post.primary_tag && (
|
||||||
|
<div className="flex">
|
||||||
|
<Tag size={24} label={post.primary_tag.name!} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span className="text-[27px] font-semibold leading-[32px] tracking-[-.021em] lg:text-[40px] lg:font-bold lg:leading-[44px] lg:tracking-[-.02em]">
|
||||||
|
{post.title}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size={19} weight="regular">
|
||||||
|
{post.excerpt}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-auto flex h-5 items-center gap-1">
|
||||||
|
<Avatar
|
||||||
|
type="user"
|
||||||
|
size={20}
|
||||||
|
name={author.name ?? author.slug}
|
||||||
|
src={author.profile_image ?? undefined}
|
||||||
|
/>
|
||||||
|
<Text size={15} weight="semibold">
|
||||||
|
{author.name ?? author.slug}
|
||||||
|
</Text>
|
||||||
|
<Text size={15} color="$neutral-50">
|
||||||
|
on {formatDate(new Date(post.published_at!))}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostCardProps = {
|
||||||
|
post: PostOrPage
|
||||||
|
showTag?: boolean
|
||||||
|
showAuthor?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PostCard = (props: PostCardProps) => {
|
||||||
|
const { post, showTag = true, showAuthor = true } = props
|
||||||
|
const author = post.primary_author!
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Shadow className="h-full rounded-[20px]">
|
||||||
|
<Link
|
||||||
|
href={`/blog/${post.slug}`}
|
||||||
|
className="flex h-full w-full flex-col rounded-[20px] border border-neutral-5 bg-white-100"
|
||||||
|
>
|
||||||
|
<div className="flex grow flex-col gap-2 p-4">
|
||||||
|
{showTag && (
|
||||||
|
<div className="h-6 overflow-hidden">
|
||||||
|
{post.primary_tag && (
|
||||||
|
<div className="flex">
|
||||||
|
<Tag size={24} label={post.primary_tag.name!} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size={19} weight="semibold">
|
||||||
|
{post.title}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showAuthor ? (
|
||||||
|
<div className="mt-auto flex h-5 gap-1">
|
||||||
|
<Avatar
|
||||||
|
type="user"
|
||||||
|
size={20}
|
||||||
|
name={author.name ?? author.slug}
|
||||||
|
src={author.profile_image ?? undefined}
|
||||||
|
/>
|
||||||
|
<Text size={15} weight="semibold">
|
||||||
|
{author.name ?? author.slug}
|
||||||
|
</Text>
|
||||||
|
<Text size={15} color="$neutral-50">
|
||||||
|
on {formatDate(new Date(post.published_at!))}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="mt-auto h-5">
|
||||||
|
<Text size={15} color="$neutral-50">
|
||||||
|
{formatDate(new Date(post.published_at!))}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full px-2 pb-2">
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
||||||
|
<img
|
||||||
|
className="aspect-[334/188] h-full w-full rounded-2xl object-cover"
|
||||||
|
src={post.feature_image!}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
</Shadow>
|
</Shadow>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
import { Button, Text } from '@status-im/components'
|
||||||
|
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import { Breadcrumbs } from '@/components'
|
||||||
|
import { AppLayout } from '@/layouts/app-layout'
|
||||||
|
import { getPostsByTagSlug, getTagSlugs } from '@/lib/ghost'
|
||||||
|
|
||||||
|
import { PostCard } from '..'
|
||||||
|
|
||||||
|
import type { PostOrPage, PostsOrPages } from '@tryghost/content-api'
|
||||||
|
import type {
|
||||||
|
GetStaticPaths,
|
||||||
|
GetStaticProps,
|
||||||
|
InferGetStaticPropsType,
|
||||||
|
Page,
|
||||||
|
} from 'next'
|
||||||
|
|
||||||
|
type Params = { slug: string }
|
||||||
|
|
||||||
|
export const getStaticPaths: GetStaticPaths<Params> = async () => {
|
||||||
|
const slugs = await getTagSlugs()
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths: slugs.map(slug => ({ params: { slug } })),
|
||||||
|
/** If fallback is false, then any paths not returned by getStaticPaths will result in a 404 page.
|
||||||
|
*
|
||||||
|
* @see https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-false
|
||||||
|
*/
|
||||||
|
fallback: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticProps: GetStaticProps<
|
||||||
|
{
|
||||||
|
posts: PostOrPage[]
|
||||||
|
meta: PostsOrPages['meta']
|
||||||
|
},
|
||||||
|
Params
|
||||||
|
> = async context => {
|
||||||
|
const { posts, meta } = await getPostsByTagSlug(context.params!.slug)
|
||||||
|
|
||||||
|
if (!posts || !posts.length) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
// redirect: { destination: '/blog', permanent: false },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
posts,
|
||||||
|
meta,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = InferGetStaticPropsType<typeof getStaticProps>
|
||||||
|
|
||||||
|
const BlogTagPage: Page<Props> = ({ posts, meta }) => {
|
||||||
|
const tag = posts[0].primary_tag!
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
// error,
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
// isFetching,
|
||||||
|
// isFetchingNextPage,
|
||||||
|
// status,
|
||||||
|
// isFetched,
|
||||||
|
} = useInfiniteQuery({
|
||||||
|
queryKey: ['posts', tag.slug],
|
||||||
|
queryFn: async ({ pageParam: page, queryKey }) => {
|
||||||
|
const [, tag] = queryKey
|
||||||
|
|
||||||
|
const { posts, meta } = await getPostsByTagSlug(tag, page)
|
||||||
|
|
||||||
|
return { posts, meta }
|
||||||
|
},
|
||||||
|
getNextPageParam: ({ meta }) => meta.pagination.next,
|
||||||
|
initialData: { pages: [{ posts, meta }], pageParams: [1] },
|
||||||
|
staleTime: Infinity,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const allPosts = data.pages.flatMap(page => page.posts)
|
||||||
|
|
||||||
|
return (
|
||||||
|
// layout 1 (showBreadcrumbs, showHighlightedPostCard, ?posts=renderPosts())
|
||||||
|
<div className="min-h-[900px] rounded-3xl bg-white-100 lg:mx-1">
|
||||||
|
{/* layout 2 */}
|
||||||
|
{/* breadcumbs */}
|
||||||
|
<div className="border-b border-neutral-10 px-5 py-[13px]">
|
||||||
|
<Breadcrumbs cutFirstSegment={false} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-5">
|
||||||
|
<div className="mx-auto max-w-[1184px] pb-24 pt-12 lg:pb-32 lg:pt-20">
|
||||||
|
{/* content */}
|
||||||
|
{/* note: diff mb than index.ts (mb-12 vs. mb-10) */}
|
||||||
|
<div className="mb-12 grid gap-2">
|
||||||
|
<h1 className="text-[40px] font-bold leading-[44px] tracking-[-.02em] lg:text-[64px] lg:leading-[68px]">
|
||||||
|
{tag.name}
|
||||||
|
</h1>
|
||||||
|
<Text size={19}>{tag.description}</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid auto-rows-[1fr] grid-cols-[repeat(auto-fill,minmax(350px,1fr))] gap-5">
|
||||||
|
{allPosts.map(post => (
|
||||||
|
<PostCard key={post.id} post={post} showTag={false} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasNextPage && (
|
||||||
|
<div className="mt-8 flex justify-center">
|
||||||
|
<Button variant="outline" onPress={() => fetchNextPage()}>
|
||||||
|
Load more posts
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
BlogTagPage.getLayout = AppLayout
|
||||||
|
|
||||||
|
export default BlogTagPage
|
|
@ -8,7 +8,7 @@ import type { Page } from 'next'
|
||||||
const HomePage: Page = () => {
|
const HomePage: Page = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="bg-white-100 mx-1 rounded-3xl py-32">
|
<div className="mx-1 rounded-3xl bg-white-100 py-32">
|
||||||
<div className="mx-40 mb-40 grid gap-8">
|
<div className="mx-40 mb-40 grid gap-8">
|
||||||
<div className="grid gap-6">
|
<div className="grid gap-6">
|
||||||
<h1 className="text-7xl font-bold">
|
<h1 className="text-7xl font-bold">
|
||||||
|
@ -44,7 +44,7 @@ const HomePage: Page = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h2 className="text-white-100 text-4xl">
|
<h2 className="text-4xl text-white-100">
|
||||||
Own a community? Time to take back control!
|
Own a community? Time to take back control!
|
||||||
</h2>
|
</h2>
|
||||||
<Text size={19} color="$white-100">
|
<Text size={19} color="$white-100">
|
||||||
|
@ -52,7 +52,7 @@ const HomePage: Page = () => {
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white-100 mx-1 space-y-[200px] rounded-3xl py-32">
|
<div className="mx-1 space-y-[200px] rounded-3xl bg-white-100 py-32">
|
||||||
<FeatureSection
|
<FeatureSection
|
||||||
title={`Chat privately\nwith friends`}
|
title={`Chat privately\nwith friends`}
|
||||||
description="Protect your right to free speech with e2e encryption & metadata privacy."
|
description="Protect your right to free speech with e2e encryption & metadata privacy."
|
||||||
|
@ -101,22 +101,22 @@ const FeatureSection = ({ title, description }: FeatureSectionProps) => {
|
||||||
const FeatureGrid = () => {
|
const FeatureGrid = () => {
|
||||||
return (
|
return (
|
||||||
<div className="grid h-[800px] grid-cols-3 grid-rows-2 gap-5">
|
<div className="grid h-[800px] grid-cols-3 grid-rows-2 gap-5">
|
||||||
<div className="border-neutral-80/5 row-span-2 rounded-[32px] border">
|
<div className="row-span-2 rounded-[32px] border border-neutral-80/5">
|
||||||
<Text size={27} weight="semibold">
|
<Text size={27} weight="semibold">
|
||||||
Title
|
Title
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-neutral-80/5 rounded-[32px] border">
|
<div className="rounded-[32px] border border-neutral-80/5">
|
||||||
<Text size={27} weight="semibold">
|
<Text size={27} weight="semibold">
|
||||||
Title
|
Title
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-neutral-80/5 row-span-2 rounded-[32px] border">
|
<div className="row-span-2 rounded-[32px] border border-neutral-80/5">
|
||||||
<Text size={27} weight="semibold">
|
<Text size={27} weight="semibold">
|
||||||
Title
|
Title
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-neutral-80/5 rounded-[32px] border">
|
<div className="rounded-[32px] border border-neutral-80/5">
|
||||||
<Text size={27} weight="semibold">
|
<Text size={27} weight="semibold">
|
||||||
Title
|
Title
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { Page } from 'next'
|
||||||
const EpicsDetailPage: Page = () => {
|
const EpicsDetailPage: Page = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="border-neutral-10 border-b px-5 py-3">
|
<div className="border-b border-neutral-10 px-5 py-3">
|
||||||
<Breadcrumbs />
|
<Breadcrumbs />
|
||||||
</div>
|
</div>
|
||||||
<div className="px-10 py-6">
|
<div className="px-10 py-6">
|
||||||
|
@ -18,7 +18,7 @@ const EpicsDetailPage: Page = () => {
|
||||||
fullscreen
|
fullscreen
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div role="separator" className="bg-neutral-10 -mx-6 my-6 h-px" />
|
<div role="separator" className="-mx-6 my-6 h-px bg-neutral-10" />
|
||||||
|
|
||||||
<TableIssues />
|
<TableIssues />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -58,7 +58,7 @@ const ReposPage: Page = () => {
|
||||||
<Link
|
<Link
|
||||||
key={repo.name}
|
key={repo.name}
|
||||||
href={`https://github.com/status-im/${repo.name}`}
|
href={`https://github.com/status-im/${repo.name}`}
|
||||||
className="border-neutral-10 hover:border-neutral-40 flex h-[124px] flex-col rounded-2xl border px-4 py-3 transition-colors duration-200"
|
className="flex h-[124px] flex-col rounded-2xl border border-neutral-10 px-4 py-3 transition-colors duration-200 hover:border-neutral-40"
|
||||||
>
|
>
|
||||||
<Text size={15} weight="semibold">
|
<Text size={15} weight="semibold">
|
||||||
{repo.name}
|
{repo.name}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { Page } from 'next'
|
||||||
const WorkstreamDetailPage: Page = () => {
|
const WorkstreamDetailPage: Page = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="border-neutral-10 border-b px-5 py-3">
|
<div className="border-b border-neutral-10 px-5 py-3">
|
||||||
<Breadcrumbs />
|
<Breadcrumbs />
|
||||||
</div>
|
</div>
|
||||||
<div className="px-10 py-6">
|
<div className="px-10 py-6">
|
||||||
|
@ -18,7 +18,7 @@ const WorkstreamDetailPage: Page = () => {
|
||||||
fullscreen
|
fullscreen
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div role="separator" className="bg-neutral-10 -mx-6 my-6 h-px" />
|
<div role="separator" className="-mx-6 my-6 h-px bg-neutral-10" />
|
||||||
|
|
||||||
<TableIssues />
|
<TableIssues />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,12 +2,6 @@
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
|
||||||
body {
|
|
||||||
@apply bg-neutral-100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes gradient {
|
@keyframes gradient {
|
||||||
0% {
|
0% {
|
||||||
background-position: 0% 50%;
|
background-position: 0% 50%;
|
||||||
|
|
|
@ -14,7 +14,7 @@ module.exports = {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ['var(--font-inter)', ...fontFamily.sans],
|
sans: ['var(--font-inter)', ...fontFamily.sans],
|
||||||
},
|
},
|
||||||
colors,
|
colors: colors,
|
||||||
|
|
||||||
// use <Text /> from @status-im/components or arbitrary values
|
// use <Text /> from @status-im/components or arbitrary values
|
||||||
// fontSize: {},
|
// fontSize: {},
|
21
package.json
21
package.json
|
@ -3,6 +3,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
|
"packages/eslint-config*",
|
||||||
"packages/status-js",
|
"packages/status-js",
|
||||||
"packages/colors",
|
"packages/colors",
|
||||||
"packages/icons",
|
"packages/icons",
|
||||||
|
@ -17,7 +18,7 @@
|
||||||
"test": "turbo run test --filter=@status-im/* -- --run",
|
"test": "turbo run test --filter=@status-im/* -- --run",
|
||||||
"dev": "turbo run dev --filter=@status-im/* --parallel",
|
"dev": "turbo run dev --filter=@status-im/* --parallel",
|
||||||
"build": "turbo run build --filter=@status-im/*",
|
"build": "turbo run build --filter=@status-im/*",
|
||||||
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint/.eslint-cache .",
|
"lint": "turbo run lint --filter=@status-im/* --filter=website --filter=web",
|
||||||
"typecheck": "turbo run typecheck",
|
"typecheck": "turbo run typecheck",
|
||||||
"format": "prettier --ignore-path .gitignore --write .",
|
"format": "prettier --ignore-path .gitignore --write .",
|
||||||
"clean": "turbo run clean && rimraf node_modules",
|
"clean": "turbo run clean && rimraf node_modules",
|
||||||
|
@ -35,24 +36,12 @@
|
||||||
"@changesets/cli": "^2.23.0",
|
"@changesets/cli": "^2.23.0",
|
||||||
"@tsconfig/strictest": "^2.0.0",
|
"@tsconfig/strictest": "^2.0.0",
|
||||||
"@types/prettier": "^2.7.2",
|
"@types/prettier": "^2.7.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
"@status-im/eslint-config": "*",
|
||||||
"@typescript-eslint/parser": "^5.57.1",
|
|
||||||
"eslint": "^8.37.0",
|
|
||||||
"eslint-config-next": "^13.2.4",
|
|
||||||
"eslint-config-prettier": "^8.8.0",
|
|
||||||
"eslint-import-resolver-node": "^0.3.7",
|
|
||||||
"eslint-import-resolver-typescript": "^3.5.5",
|
|
||||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
||||||
"eslint-plugin-import": "^2.27.5",
|
|
||||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
|
||||||
"eslint-plugin-react": "^7.32.2",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lint-staged": "^13.2.0",
|
"lint-staged": "^13.2.0",
|
||||||
"patch-package": "^6.5.1",
|
"patch-package": "^6.5.1",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.8",
|
"prettier-plugin-tailwindcss": "0.3.0",
|
||||||
"rimraf": "^4.4.1",
|
"rimraf": "^4.4.1",
|
||||||
"turbo": "^1.8.8",
|
"turbo": "^1.8.8",
|
||||||
"typescript": "^5.0.3",
|
"typescript": "^5.0.3",
|
||||||
|
@ -61,7 +50,7 @@
|
||||||
"vitest": "^0.29.8"
|
"vitest": "^0.29.8"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{ts,tsx,js,jsx}": [
|
"*.{ts,tsx,js,jsx,mjs}": [
|
||||||
"eslint",
|
"eslint",
|
||||||
"prettier --write"
|
"prettier --write"
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": ["@status-im/eslint-config"]
|
||||||
|
}
|
|
@ -35,6 +35,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@clack/prompts": "^0.6.3",
|
"@clack/prompts": "^0.6.3",
|
||||||
"colorjs.io": "^0.4.3",
|
"colorjs.io": "^0.4.3",
|
||||||
|
"@status-im/eslint-config": "*",
|
||||||
"figma-api": "^1.11.0",
|
"figma-api": "^1.11.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"vite": "^4.1.4",
|
"vite": "^4.1.4",
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": ["@status-im/eslint-config"]
|
||||||
|
}
|
|
@ -63,6 +63,7 @@
|
||||||
"@storybook/testing-library": "^0.1.0",
|
"@storybook/testing-library": "^0.1.0",
|
||||||
"@tamagui/vite-plugin": "1.11.1",
|
"@tamagui/vite-plugin": "1.11.1",
|
||||||
"@vitejs/plugin-react-swc": "^3.2.0",
|
"@vitejs/plugin-react-swc": "^3.2.0",
|
||||||
|
"@status-im/eslint-config": "*",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-native-svg": "^13.8.0",
|
"react-native-svg": "^13.8.0",
|
||||||
|
|
|
@ -84,13 +84,13 @@ export const tokens = createTokens({
|
||||||
'turquoise-50-opa-20': 'hsla(193, 41%, 45%, 0.2)',
|
'turquoise-50-opa-20': 'hsla(193, 41%, 45%, 0.2)',
|
||||||
'turquoise-50-opa-30': 'hsla(193, 41%, 45%, 0.3)',
|
'turquoise-50-opa-30': 'hsla(193, 41%, 45%, 0.3)',
|
||||||
'turquoise-50-opa-40': 'hsla(193, 41%, 45%, 0.4)',
|
'turquoise-50-opa-40': 'hsla(193, 41%, 45%, 0.4)',
|
||||||
'blue-50': 'hsla(202, 84%, 62%, 1)',
|
'blue-50': 'hsla(231, 91%, 56%, 1)',
|
||||||
'blue-60': 'hsla(202, 56%, 52%, 1)',
|
'blue-60': 'hsla(231, 70%, 45%, 1)',
|
||||||
'blue-50-opa-5': 'hsla(202, 84%, 62%, 0.05)',
|
'blue-50-opa-5': 'hsla(231, 91%, 56%, 0.05)',
|
||||||
'blue-50-opa-10': 'hsla(202, 84%, 62%, 0.1)',
|
'blue-50-opa-10': 'hsla(231, 91%, 56%, 0.1)',
|
||||||
'blue-50-opa-20': 'hsla(202, 84%, 62%, 0.2)',
|
'blue-50-opa-20': 'hsla(231, 91%, 56%, 0.2)',
|
||||||
'blue-50-opa-30': 'hsla(202, 84%, 62%, 0.3)',
|
'blue-50-opa-30': 'hsla(231, 91%, 56%, 0.3)',
|
||||||
'blue-50-opa-40': 'hsla(202, 84%, 62%, 0.4)',
|
'blue-50-opa-40': 'hsla(231, 91%, 56%, 0.4)',
|
||||||
'green-50': 'hsla(151, 53%, 58%, 1)',
|
'green-50': 'hsla(151, 53%, 58%, 1)',
|
||||||
'green-60': 'hsla(151, 38%, 48%, 1)',
|
'green-60': 'hsla(151, 38%, 48%, 1)',
|
||||||
'green-50-opa-5': 'hsla(151, 53%, 58%, 0.05)',
|
'green-50-opa-5': 'hsla(151, 53%, 58%, 0.05)',
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
// TODO: Enable type-aware linting (https://typescript-eslint.io/docs/linting/type-linting)
|
||||||
|
// "tsconfigRootDir": ".",
|
||||||
|
// "project": ["./packages/*/tsconfig.json"],
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
warnOnUnsupportedTypeScriptVersion: true,
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
'import',
|
||||||
|
'simple-import-sort',
|
||||||
|
'react',
|
||||||
|
'jsx-a11y',
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
// "plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||||
|
'plugin:eslint-comments/recommended',
|
||||||
|
'plugin:import/recommended',
|
||||||
|
'plugin:import/typescript',
|
||||||
|
'plugin:jsx-a11y/recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:react/jsx-runtime',
|
||||||
|
'prettier',
|
||||||
|
],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['examples/**/*.tsx'],
|
||||||
|
rules: {
|
||||||
|
'react/jsx-uses-react': 'off',
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'react/prop-types': 0,
|
||||||
|
// "@typescript-eslint/consistent-type-definitions": ["error", "interface"],
|
||||||
|
'@typescript-eslint/consistent-type-imports': 'error',
|
||||||
|
// TODO: turn on this rul
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
// "@typescript-eslint/consistent-type-exports": "error",
|
||||||
|
'simple-import-sort/imports': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
groups: [
|
||||||
|
// Side effect imports.
|
||||||
|
['^\\u0000'],
|
||||||
|
// `react` related packages come first.
|
||||||
|
['^react$', '^react-dom$'],
|
||||||
|
// Things that start with a letter (or digit or underscore), or `@` followed by a letter.
|
||||||
|
['^@?\\w'],
|
||||||
|
// Absolute imports and other imports such as Vue-style `@/foo`.
|
||||||
|
// Anything not matched in another group.
|
||||||
|
['^'],
|
||||||
|
// Relative imports.
|
||||||
|
// Anything that starts with a dot.
|
||||||
|
['^\\.'],
|
||||||
|
// type imports last as a separate group
|
||||||
|
['^.+\\u0000$'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'simple-import-sort/exports': 'error',
|
||||||
|
'import/first': 'error',
|
||||||
|
'import/newline-after-import': 'error',
|
||||||
|
'import/no-duplicates': 'error',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
'import/resolver': {
|
||||||
|
node: {
|
||||||
|
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||||
|
project: [
|
||||||
|
'tsconfig.base.json',
|
||||||
|
'packages/*/tsconfig.json',
|
||||||
|
'apps/*/tsconfig.json',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
typescript: {
|
||||||
|
alwaysTryTypes: true,
|
||||||
|
project: [
|
||||||
|
'tsconfig.base.json',
|
||||||
|
'packages/*/tsconfig.json',
|
||||||
|
'apps/*/tsconfig.json',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'import/ignore': ['react-native'],
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"version": "0.1.0",
|
||||||
|
"name": "@status-im/eslint-config",
|
||||||
|
"main": "index.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./index.js",
|
||||||
|
"require": "./index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf node_modules"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.59.11",
|
||||||
|
"@typescript-eslint/parser": "^5.59.11",
|
||||||
|
"eslint": "^8.42.0",
|
||||||
|
"eslint-config-next": "^13.2.4",
|
||||||
|
"eslint-config-prettier": "^8.8.0",
|
||||||
|
"eslint-import-resolver-node": "^0.3.7",
|
||||||
|
"eslint-import-resolver-typescript": "^3.5.5",
|
||||||
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
|
"eslint-plugin-react": "^7.32.2",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
|
"eslint-plugin-tailwindcss": "^3.12.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": ["@status-im/eslint-config"]
|
||||||
|
}
|
|
@ -46,6 +46,7 @@
|
||||||
"@svgr/plugin-prettier": "^7.0.0",
|
"@svgr/plugin-prettier": "^7.0.0",
|
||||||
"@svgr/plugin-svgo": "^7.0.0",
|
"@svgr/plugin-svgo": "^7.0.0",
|
||||||
"@types/fs-extra": "^11.0.1",
|
"@types/fs-extra": "^11.0.1",
|
||||||
|
"@status-im/eslint-config": "*",
|
||||||
"figma-api": "^1.11.0",
|
"figma-api": "^1.11.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"svgo": "^3.0.2",
|
"svgo": "^3.0.2",
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": ["@status-im/eslint-config"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["./src/protos/**/*_pb.ts"],
|
||||||
|
"rules": {
|
||||||
|
"eslint-comments/disable-enable-pair": "off",
|
||||||
|
"eslint-comments/no-unlimited-disable": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -55,6 +55,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bufbuild/protoc-gen-es": "^1.0.0",
|
"@bufbuild/protoc-gen-es": "^1.0.0",
|
||||||
|
"@status-im/eslint-config": "*",
|
||||||
"happy-dom": "^9.1.7"
|
"happy-dom": "^9.1.7"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": ["@status-im/eslint-config"]
|
||||||
|
}
|
|
@ -63,6 +63,7 @@
|
||||||
"@types/node": "^16.9.6",
|
"@types/node": "^16.9.6",
|
||||||
"@types/react": "^18.0.28",
|
"@types/react": "^18.0.28",
|
||||||
"@vitejs/plugin-react": "^1.3.2",
|
"@vitejs/plugin-react": "^1.3.2",
|
||||||
|
"@status-im/eslint-config": "*",
|
||||||
"happy-dom": "^5.3.1"
|
"happy-dom": "^5.3.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
Loading…
Reference in New Issue