solve conflicts
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:
|
||||
types: [opened, synchronize]
|
||||
|
||||
env:
|
||||
NEXT_PUBLIC_GHOST_API_KEY: ''
|
||||
INFURA_API_KEY: ''
|
||||
TAMAGUI_TARGET: 'web'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Test
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"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",
|
||||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
|
||||
"@status-im/eslint-config": "*",
|
||||
"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-dom": "^18.0.11",
|
||||
"@vitejs/plugin-react-swc": "^3.2.0",
|
||||
"@status-im/eslint-config": "*",
|
||||
"typescript": "^5.0.3",
|
||||
"vite": "^4.2.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"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import type { clientEnv } from './src/config/env.client.mjs'
|
||||
import type { serverEnv } from './src/config/env.server.mjs'
|
||||
|
||||
type Env = typeof clientEnv & typeof serverEnv
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
/* eslint-disable @typescript-eslint/no-empty-interface */
|
||||
interface ProcessEnv extends Env {}
|
||||
/* eslint-enable @typescript-eslint/no-empty-interface */
|
||||
}
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-disable import/default */
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const { withTamagui } = require('@tamagui/next-plugin')
|
||||
const { join } = require('path')
|
||||
import './src/config/env.server.mjs'
|
||||
import './src/config/env.client.mjs'
|
||||
|
||||
import tamagui_next_plugin from '@tamagui/next-plugin'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const { withTamagui } = tamagui_next_plugin
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
let config = {
|
||||
|
@ -26,6 +30,7 @@ let config = {
|
|||
experimental: {
|
||||
legacyBrowsers: false,
|
||||
// esmExternals: 'loose',
|
||||
scrollRestoration: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -59,7 +64,7 @@ const plugins = [
|
|||
}),
|
||||
]
|
||||
|
||||
module.exports = () => {
|
||||
export default () => {
|
||||
for (const plugin of plugins) {
|
||||
config = {
|
||||
...config,
|
|
@ -8,7 +8,7 @@
|
|||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -21,9 +21,11 @@
|
|||
"@status-im/icons": "*",
|
||||
"@status-im/js": "*",
|
||||
"@tamagui/next-theme": "1.11.1",
|
||||
"@tanstack/react-query": "^4.29.7",
|
||||
"@vercel/og": "^0.5.4",
|
||||
"class-variance-authority": "^0.6.0",
|
||||
"@tryghost/content-api": "^1.11.13",
|
||||
"@visx/visx": "^2.18.0",
|
||||
"class-variance-authority": "^0.6.0",
|
||||
"d3-array": "^3.2.3",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"next": "13.2.4",
|
||||
|
@ -31,22 +33,29 @@
|
|||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-native-web": "^0.18.12",
|
||||
"@tanstack/react-query": "^4.29.7",
|
||||
"ts-pattern": "^4.3.0"
|
||||
"ts-pattern": "^4.3.0",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@achingbrain/ssdp": "^4.0.1",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@tamagui/next-plugin": "1.11.1",
|
||||
"@types/d3-array": "^3.0.4",
|
||||
"@types/d3-time-format": "^4.0.0",
|
||||
"@types/node": "^18.11.11",
|
||||
"@types/react": "^18.0.33",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/tryghost__content-api": "^1.3.11",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"@status-im/eslint-config": "*",
|
||||
"postcss": "^8.4.21",
|
||||
"rehype-parse": "^8.0.4",
|
||||
"rehype-react": "^7.2.0",
|
||||
"rehype-stringify": "^9.0.3",
|
||||
"tailwindcss": "^3.3.1",
|
||||
"tailwindcss-animate": "^1.0.5",
|
||||
"typescript": "^5.0.3"
|
||||
"typescript": "^5.0.3",
|
||||
"unified": "^10.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.x"
|
||||
|
|
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 164 KiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 197 KiB |
After Width: | Height: | Size: 195 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 153 KiB |
After Width: | Height: | Size: 230 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 163 KiB |
After Width: | Height: | Size: 310 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 642 KiB |
After Width: | Height: | Size: 210 KiB |
Before Width: | Height: | Size: 6.0 KiB |
|
@ -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 |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,161 @@
|
|||
import { cva } from 'class-variance-authority'
|
||||
import Image from 'next/image'
|
||||
|
||||
import type { StaticImageData } from 'next/image'
|
||||
|
||||
// Variants for the grid hero class names
|
||||
const biggerCardClassNames = cva(
|
||||
[
|
||||
'min-w-[350px] rounded-[40px]',
|
||||
'px-[22px] py-6 sm:min-w-0 sm:px-[35px] sm:py-[48px] lg:px-5 xl:px-[34px] xl:py-[68px] 2xl:px-[73px]',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['bg-customisation-yellow/10'],
|
||||
turquoise: ['bg-customisation-turquoise/10'],
|
||||
purple: ['bg-customisation-purple/10'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const imagesWithBorders = cva(['border-4', 'rounded-3xl'], {
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['border-customisation-yellow/5'],
|
||||
turquoise: ['border-customisation-turquoise/5'],
|
||||
purple: ['border-customisation-purple/5'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const imagesWithBordersTopOrBottom = cva(['border-4', 'rounded-3xl'], {
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['border-customisation-yellow/5'],
|
||||
turquoise: ['border-customisation-turquoise/5'],
|
||||
purple: ['border-customisation-purple/5'],
|
||||
},
|
||||
alignment: {
|
||||
top: ['border-t-0'],
|
||||
bottom: ['border-b-0'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const thirdCardClassNames = cva(
|
||||
[
|
||||
'flex min-w-[calc(50%-10px)] rounded-[40px]',
|
||||
'px-[22px] sm:min-w-0 sm:px-[35px] lg:px-5 xl:px-[34px] 2xl:px-[73px]',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['bg-customisation-yellow/10'],
|
||||
turquoise: ['bg-customisation-turquoise/10'],
|
||||
purple: ['bg-customisation-purple/10'],
|
||||
},
|
||||
alignment: {
|
||||
top: [
|
||||
'pt-0',
|
||||
'pb-6 sm:pb-[48px] xl:pb-[68px]',
|
||||
'items-start',
|
||||
'justify-start',
|
||||
],
|
||||
bottom: [
|
||||
'pb-0',
|
||||
'pt-6 sm:pt-[48px] xl:pt-[68px]',
|
||||
'items-end',
|
||||
'justify-end',
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const fourthCardClassNames = cva(
|
||||
[
|
||||
'flex min-w-[calc(50%-10px)] rounded-[40px]',
|
||||
'grow items-center justify-center px-0 sm:px-5 md:px-10 lg:px-5 2xl:px-10',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['bg-customisation-yellow/10'],
|
||||
turquoise: ['bg-customisation-turquoise/10'],
|
||||
purple: ['bg-customisation-purple/10'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type Props = {
|
||||
color: 'yellow' | 'turquoise' | 'purple'
|
||||
cardOne: {
|
||||
image: StaticImageData
|
||||
alt: string
|
||||
}
|
||||
cardTwo: {
|
||||
image: StaticImageData
|
||||
alt: string
|
||||
}
|
||||
cardThree: {
|
||||
image: StaticImageData
|
||||
alt: string
|
||||
alignment?: 'top' | 'bottom'
|
||||
}
|
||||
cardFour: {
|
||||
image: StaticImageData
|
||||
alt: string
|
||||
}
|
||||
}
|
||||
|
||||
const GridHero = (props: Props) => {
|
||||
const { color, cardOne, cardTwo, cardThree, cardFour } = props
|
||||
|
||||
return (
|
||||
<div className="relative z-[2] flex w-full max-w-[1504px] justify-start overflow-x-auto px-5 sm:justify-center sm:px-10">
|
||||
<div className="flex min-w-[712px] flex-row gap-3 sm:min-w-fit sm:flex-col sm:gap-5 lg:flex-row">
|
||||
<div className="flex flex-row gap-3 sm:gap-5">
|
||||
<div className={biggerCardClassNames({ color })}>
|
||||
<Image
|
||||
src={cardOne.image}
|
||||
alt={cardOne.alt}
|
||||
className={imagesWithBorders({ color })}
|
||||
/>
|
||||
</div>
|
||||
<div className={biggerCardClassNames({ color })}>
|
||||
<Image
|
||||
src={cardTwo.image}
|
||||
alt={cardTwo.alt}
|
||||
className={imagesWithBorders({ color })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-w-[350px] flex-col gap-3 pr-5 sm:min-w-0 sm:flex-row sm:gap-5 sm:pr-0 lg:flex-col">
|
||||
<div
|
||||
className={thirdCardClassNames({
|
||||
color,
|
||||
alignment: cardThree.alignment || 'bottom',
|
||||
})}
|
||||
>
|
||||
<Image
|
||||
src={cardThree.image}
|
||||
alt={cardThree.alt}
|
||||
className={imagesWithBordersTopOrBottom({
|
||||
color,
|
||||
alignment: cardThree.alignment || 'bottom',
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className={fourthCardClassNames({ color })}>
|
||||
<Image src={cardFour.image} alt={cardFour.alt} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { GridHero }
|
|
@ -0,0 +1,2 @@
|
|||
export { GridHero } from './grid-hero'
|
||||
export { Section } from './section'
|
|
@ -0,0 +1,149 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import { PopupIcon } from '@status-im/icons'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import Image from 'next/image'
|
||||
|
||||
import { Border } from './border'
|
||||
|
||||
import type { StaticImageData } from 'next/image'
|
||||
|
||||
type Props = {
|
||||
direction?: 'ltr' | 'rtl'
|
||||
title: string
|
||||
description: string
|
||||
image: StaticImageData
|
||||
imageAlt: string
|
||||
imageSecondary: StaticImageData
|
||||
imageSecondaryAlt: string
|
||||
secondaryDescription: string
|
||||
secondaryTitle: string
|
||||
customNode?: React.ReactNode
|
||||
color: 'yellow' | 'turquoise' | 'purple'
|
||||
information?: string
|
||||
}
|
||||
|
||||
const containerClassNames = cva(
|
||||
[
|
||||
'relative flex w-full justify-center',
|
||||
'overflow-hidden',
|
||||
'max-h-[854px]',
|
||||
'max-w-[582px]',
|
||||
'rounded-[32px]',
|
||||
'py-[65px]',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['bg-customisation-yellow/10'],
|
||||
turquoise: ['bg-customisation-turquoise/10'],
|
||||
purple: ['bg-customisation-purple/10'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const imageClassNames = cva(
|
||||
['rounded-3xl', 'border-4', 'h-auto max-h-[724px] w-auto'],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['border-customisation-yellow/5'],
|
||||
turquoise: ['border-customisation-turquoise/5'],
|
||||
purple: ['border-customisation-purple/5'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const borderContainerClassNames = cva(
|
||||
['absolute left-0 top-0', 'w-full', 'h-[100%]'],
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
yellow: ['text-customisation-yellow/5'],
|
||||
turquoise: ['text-customisation-turquoise/5'],
|
||||
purple: ['text-customisation-purple/5'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const Section = (props: Props) => {
|
||||
const {
|
||||
title,
|
||||
color,
|
||||
description,
|
||||
image,
|
||||
imageAlt,
|
||||
imageSecondary,
|
||||
imageSecondaryAlt,
|
||||
secondaryDescription,
|
||||
secondaryTitle,
|
||||
direction = 'ltr',
|
||||
} = props
|
||||
|
||||
const directionOrder = direction === 'ltr' ? 'order-0' : 'order-1'
|
||||
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<div className="relative z-[3] grid auto-cols-auto grid-flow-dense auto-rows-[1fr] grid-cols-1 gap-24 px-5 md:grid-cols-2 lg:gap-12 lg:px-[100px] xl:gap-[140px]">
|
||||
<div
|
||||
className={`${directionOrder} flex max-w-[1504px] justify-center overflow-hidden rounded-[32px]`}
|
||||
>
|
||||
<div className={containerClassNames({ color })}>
|
||||
<div className={borderContainerClassNames({ color })}>
|
||||
<Border />
|
||||
</div>
|
||||
<div className="absolute left-0 top-0 h-full w-full bg-[url('/assets/wallet/texture.png')] bg-contain bg-[left_top_0] bg-no-repeat" />
|
||||
<Image
|
||||
src={image}
|
||||
alt={imageAlt}
|
||||
className={imageClassNames({ color })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-start md:justify-center">
|
||||
<div className="flex flex-col">
|
||||
<Image
|
||||
src={imageSecondary}
|
||||
alt={imageSecondaryAlt}
|
||||
width={48}
|
||||
height={48}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col pt-4">
|
||||
<Text size={27} weight="semibold">
|
||||
{title}
|
||||
</Text>
|
||||
<div className="relative flex pt-1">
|
||||
<Text size={27}>{description}</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 flex flex-col rounded-[20px] border border-dashed border-neutral-80/20 p-4">
|
||||
<Text size={19} weight="semibold">
|
||||
{secondaryTitle}
|
||||
</Text>
|
||||
<div className="flex pt-1">
|
||||
<Text size={19}>
|
||||
{secondaryDescription}
|
||||
{props.information && (
|
||||
<span className="relative left-1 top-1 inline-block">
|
||||
<PopupIcon size={20} color="$neutral-50" />
|
||||
</span>
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{props.customNode && (
|
||||
<div className="mt-4 flex flex-col">{props.customNode}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { Section }
|
|
@ -0,0 +1,45 @@
|
|||
import { Calendar } from '@status-im/components/src/calendar/calendar'
|
||||
import { Popover } from '@status-im/components/src/popover'
|
||||
import { EditIcon } from '@status-im/icons'
|
||||
|
||||
import { formatDate } from '../chart/utils/format-time'
|
||||
|
||||
import type { DateRange } from '@status-im/components/src/calendar/calendar'
|
||||
|
||||
type Props = {
|
||||
selected?: DateRange
|
||||
onSelect: (selected?: DateRange) => void
|
||||
}
|
||||
|
||||
const DatePicker = (props: Props) => {
|
||||
const { selected, onSelect } = props
|
||||
|
||||
return (
|
||||
<div className="sticky bottom-5 flex justify-center">
|
||||
<Popover alignOffset={8} align="center" sideOffset={8}>
|
||||
<button className="active inline-flex min-h-[30px] cursor-pointer items-center justify-center gap-2 rounded-xl border-solid border-neutral-80/5 bg-blur-neutral-5/70 pl-3 pr-2 uppercase text-neutral-100 backdrop-blur-sm">
|
||||
<span className="text-[13px] font-medium text-blur-neutral-80/80">
|
||||
Filter between
|
||||
</span>
|
||||
<span className="text-[13px] font-medium text-neutral-100">
|
||||
{`${selected?.from ? formatDate(selected.from) : 'Start Date'} → ${
|
||||
selected?.to ? formatDate(selected.to) : 'End Date'
|
||||
}`}
|
||||
</span>
|
||||
<div className="h-full w-[1px] bg-neutral-80/5" />
|
||||
<EditIcon size={20} color="$neutral-80-opa-40" />
|
||||
</button>
|
||||
<Popover.Content>
|
||||
<Calendar
|
||||
mode="range"
|
||||
selected={selected}
|
||||
onSelect={onSelect}
|
||||
fixedWeeks
|
||||
/>
|
||||
</Popover.Content>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { DatePicker }
|
|
@ -13,7 +13,7 @@ export const ErrorPage = (props: Props) => {
|
|||
// todo!: design review, not in designs
|
||||
case ERROR_CODES.NOT_FOUND:
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-8 bg-white text-center">
|
||||
<div className="bg-white flex h-full w-full flex-col items-center justify-center gap-8 text-center">
|
||||
<div className="h-[160px] w-[160px] rounded-full bg-[#b3b3b3]" />
|
||||
<Text size={27} weight="semibold">
|
||||
Page not found.
|
||||
|
@ -24,7 +24,7 @@ export const ErrorPage = (props: Props) => {
|
|||
case ERROR_CODES.INTERNAL_SERVER_ERROR:
|
||||
default:
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-8 bg-white text-center">
|
||||
<div className="bg-white flex h-full w-full flex-col items-center justify-center gap-8 text-center">
|
||||
<div className="h-[160px] w-[160px] rounded-full bg-[hsla(355,47%,50%,1)]" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text size={27} weight="semibold">
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { Button, Text } from '@status-im/components'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
description: string
|
||||
action: string
|
||||
}
|
||||
|
||||
const ActionCard = (props: Props) => {
|
||||
const { title, description, action } = props
|
||||
|
||||
return (
|
||||
<div className="flex items-center rounded-[20px] border border-neutral-90 bg-neutral-95 px-5 py-3">
|
||||
<div className="grid flex-1 gap-px">
|
||||
<Text size={19} color="$white-100" weight="semibold">
|
||||
{title}
|
||||
</Text>
|
||||
<Text size={15} color="$white-100">
|
||||
{description}
|
||||
</Text>
|
||||
</div>
|
||||
<Button size={32} variant="darkGrey">
|
||||
{action}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { ActionCard }
|
|
@ -0,0 +1,13 @@
|
|||
export const Dot = () => (
|
||||
<span className="flex self-center">
|
||||
<svg
|
||||
width="2"
|
||||
height="2"
|
||||
viewBox="0 0 2 2"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="1" cy="1" r="1" fill="#647084" />
|
||||
</svg>
|
||||
</span>
|
||||
)
|
|
@ -0,0 +1,31 @@
|
|||
export const MessariIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_2650_62112)">
|
||||
<path
|
||||
d="M11.9531 4.24242L16.0019 0V12.2667L11.9531 15.9515V4.24242Z"
|
||||
fill="#647084"
|
||||
/>
|
||||
<path
|
||||
d="M5.97656 6.37524L7.92778 7.97523L10.0253 6.13281V12.2661L5.97656 15.951V6.37524Z"
|
||||
fill="#647084"
|
||||
/>
|
||||
<path
|
||||
d="M4.07317 4.24242L0 0V16L4.07317 12.5576V4.24242Z"
|
||||
fill="#647084"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2650_62112">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
import { Text } from '@status-im/components'
|
||||
|
||||
import { SOCIALS } from '@/config/links'
|
||||
|
||||
import { Logo } from '../logo'
|
||||
import { AccordionMenu } from '../navigation/accordion-menu'
|
||||
import { Dot } from './components/dot'
|
||||
import { MessariIcon } from './components/messari-icon'
|
||||
|
||||
type Props = {
|
||||
hasBorderTop?: boolean
|
||||
}
|
||||
|
||||
export const FooterMobile = (props: Props) => {
|
||||
const { hasBorderTop } = props
|
||||
|
||||
return (
|
||||
<footer
|
||||
className={`block border-dashed border-neutral-80 ${
|
||||
hasBorderTop ? 'border-t' : 'border-t-0'
|
||||
} pb-12 sm:hidden`}
|
||||
>
|
||||
<div className="">
|
||||
<div className="flex flex-col px-2 pt-6">
|
||||
<div className="px-3 pb-3">
|
||||
<Logo />
|
||||
</div>
|
||||
<div className="pb-5">
|
||||
<AccordionMenu />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-2 border-t border-dashed border-neutral-80 px-6 pt-6">
|
||||
<div className="flex w-full flex-col items-start gap-2 ">
|
||||
<Text size={11} color="$neutral-50">
|
||||
© Status Research & Development GmbH
|
||||
</Text>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Text size={11} color="$neutral-40">
|
||||
Terms of use
|
||||
</Text>
|
||||
<Dot />
|
||||
<Text size={11} color="$neutral-40">
|
||||
Privacy policy
|
||||
</Text>
|
||||
<Dot />
|
||||
<Text size={11} color="$neutral-40">
|
||||
Cookies
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-start gap-3">
|
||||
<div className="flex w-full items-center justify-start gap-3 py-2">
|
||||
<MessariIcon />
|
||||
<Text size={11} color="$neutral-50">
|
||||
Messari Transparency Verified
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
{Object.values(SOCIALS).map(social => {
|
||||
const IconComponent = social.icon
|
||||
return (
|
||||
<IconComponent
|
||||
key={social.name}
|
||||
size={20}
|
||||
color="$neutral-40"
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
import { LINKS, SOCIALS } from '@/config/links'
|
||||
|
||||
import { Logo } from '../logo'
|
||||
import { Dot } from './components/dot'
|
||||
import { MessariIcon } from './components/messari-icon'
|
||||
import { Section } from './section'
|
||||
|
||||
type Props = {
|
||||
hasBorderTop?: boolean
|
||||
}
|
||||
|
||||
const section = cva(
|
||||
[
|
||||
'border-neutral-80',
|
||||
'mb-6',
|
||||
'flex',
|
||||
'items-start',
|
||||
'border-dashed',
|
||||
'pl-6',
|
||||
'pt-6',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
hasBorderTop: {
|
||||
true: ['border-t'],
|
||||
false: ['border-t-0'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export const Footer = (props: Props) => {
|
||||
const { hasBorderTop } = props
|
||||
|
||||
return (
|
||||
<footer className="hidden pb-3 sm:block">
|
||||
<div className="grid grid-cols-4 gap-5 sm:gap-0 lg:mb-10 lg:grid-cols-8">
|
||||
<div
|
||||
className={section({
|
||||
hasBorderTop,
|
||||
})}
|
||||
>
|
||||
<Logo />
|
||||
</div>
|
||||
{Object.entries(LINKS).map(([title, links], index) => (
|
||||
<Section
|
||||
key={title}
|
||||
title={title}
|
||||
links={links}
|
||||
hasBorderLeft={index !== 3}
|
||||
hasBorderTop={hasBorderTop}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col items-start justify-between gap-2 px-5 lg:px-6 md-lg:flex-row md-lg:items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Text size={11} color="$neutral-50">
|
||||
© Status Research & Development GmbH
|
||||
</Text>
|
||||
<Dot />
|
||||
<div className="flex gap-3">
|
||||
<Text size={11} color="$neutral-40">
|
||||
Terms of use
|
||||
</Text>
|
||||
<Text size={11} color="$neutral-40">
|
||||
Privacy policy
|
||||
</Text>
|
||||
<Text size={11} color="$neutral-40">
|
||||
Cookies
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<MessariIcon />
|
||||
<Text size={11} color="$neutral-50">
|
||||
Messari Transparency Verified
|
||||
</Text>
|
||||
<Dot />
|
||||
</div>
|
||||
{Object.values(SOCIALS).map(social => {
|
||||
const IconComponent = social.icon
|
||||
return (
|
||||
<IconComponent key={social.name} size={20} color="$neutral-40" />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
import { Text } from '@status-im/components'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
import { Link } from '../link'
|
||||
|
||||
import type { Links } from '@/config/links'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
links: Links
|
||||
hasBorderLeft?: boolean
|
||||
hasBorderTop?: boolean
|
||||
}
|
||||
|
||||
const section = cva(
|
||||
[
|
||||
'border-neutral-80',
|
||||
'relative',
|
||||
'grid',
|
||||
'gap-3',
|
||||
'border-dashed',
|
||||
'px-5',
|
||||
'pb-6',
|
||||
'pt-6',
|
||||
'lg:border-l',
|
||||
'lg:pb-0',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
hasBorderTop: {
|
||||
true: ['border-t'],
|
||||
false: ['border-t-0'],
|
||||
},
|
||||
hasBorderLeft: {
|
||||
true: ['border-l'],
|
||||
false: ['border-l-0'],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const Section = (props: Props) => {
|
||||
const { title, links, hasBorderLeft, hasBorderTop } = props
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={section({
|
||||
hasBorderTop,
|
||||
hasBorderLeft,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(180deg, transparent 0%, rgba(9 16 28 / 100%))',
|
||||
}}
|
||||
className=" absolute left-[-4px] top-0 h-full w-2"
|
||||
/>
|
||||
<Text size={13} color="$neutral-50">
|
||||
{title}
|
||||
</Text>
|
||||
<ul className="grid gap-1">
|
||||
{links.map(link => (
|
||||
<li key={link.name}>
|
||||
<Link href={link.href}>
|
||||
<Text size={15} color="$white-100" weight="medium">
|
||||
{link.name}
|
||||
</Text>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { Section }
|
|
@ -1,53 +0,0 @@
|
|||
import Head from 'next/head'
|
||||
|
||||
type Props = {
|
||||
index?: boolean
|
||||
children?: React.ReactElement
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
function _Head(props: Props) {
|
||||
const { index = true, children, imageUrl } = props
|
||||
return (
|
||||
<Head>
|
||||
<title>Status</title>
|
||||
<meta name="description" content="Generated by create next app" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
{/* todo: app stores banners/redirects */}
|
||||
{/* todo: eval following meta tags */}
|
||||
<meta property="og:site_name" content="Join Status" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Status — A secure messaging app, crypto wallet, and Web3 browser"
|
||||
/>
|
||||
<meta property="og:title" content="Join [@|#]<name> in Status" />
|
||||
<meta property="og:url" content="<url>" />
|
||||
|
||||
{imageUrl && <meta property="og:image" content={imageUrl} />}
|
||||
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:site" content="@ethstatus" />
|
||||
{/* <meta property="twitter:image" content="<logo>" /> */}
|
||||
<meta property="twitter:image:alt" content="Status logo" />
|
||||
<meta property="status-im:target" content="<displayName>" />
|
||||
<meta property="al:ios:url" content="status-im:/<url>" />
|
||||
<meta property="al:ios:app_store_id" content="1178893006" />
|
||||
<meta property="al:ios:app_name" content="Status — Ethereum. Anywhere" />
|
||||
<meta property="al:android:url" content="status-im:/<url>" />
|
||||
<meta property="al:android:package" content="im.status.ethereum" />
|
||||
<meta
|
||||
property="al:android:app_name"
|
||||
content="Status — Ethereum. Anywhere"
|
||||
/>
|
||||
{/* todo?: except communities; ask product */}
|
||||
{!index && <meta name="robots" content="noindex" />}
|
||||
{/* todo?: entity QR */}
|
||||
{/* todo?: fallback OG */}
|
||||
{children}
|
||||
</Head>
|
||||
)
|
||||
}
|
||||
|
||||
export { _Head as Head }
|
|
@ -1,8 +1,8 @@
|
|||
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 =
|
||||
typeof props.href === 'string'
|
||||
? props.href.startsWith('http')
|
||||
|
|
|
@ -2,21 +2,23 @@ import Image from 'next/image'
|
|||
import { useRouter } from 'next/router'
|
||||
import { match, P } from 'ts-pattern'
|
||||
|
||||
import logoBlogSrc from '../../public/images/logo/blog.svg'
|
||||
import logoSrc from '../../public/images/logo/default.svg'
|
||||
import logoDevSrc from '../../public/images/logo/dev.svg'
|
||||
import logoLearnSrc from '../../public/images/logo/learn.svg'
|
||||
|
||||
type Props = {
|
||||
label?: boolean
|
||||
isTopbarDesktop?: boolean
|
||||
}
|
||||
|
||||
export const Logo = (props: Props) => {
|
||||
const { label = true } = props
|
||||
const { label = true, isTopbarDesktop } = props
|
||||
|
||||
const { pathname } = useRouter()
|
||||
|
||||
return (
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
{match(pathname)
|
||||
.with(
|
||||
P.when(p => p.startsWith('/insights')),
|
||||
|
@ -28,7 +30,7 @@ export const Logo = (props: Props) => {
|
|||
)
|
||||
.with(
|
||||
P.when(p => p.startsWith('/blog')),
|
||||
() => <Image src={logoSrc} alt="Status logo" />
|
||||
() => <Image src={logoBlogSrc} alt="Status logo" />
|
||||
)
|
||||
.otherwise(() => (
|
||||
<Image src={logoSrc} alt="Status logo" />
|
||||
|
@ -39,7 +41,7 @@ export const Logo = (props: Props) => {
|
|||
width="70"
|
||||
height="16"
|
||||
fill="none"
|
||||
className="mr-[10px]"
|
||||
className={`mr-[10px] ${isTopbarDesktop ? 'hidden lg:block' : ''}`}
|
||||
>
|
||||
<path
|
||||
fill="#fff"
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
import { useState } from 'react'
|
||||
|
||||
import * as Accordion from '@radix-ui/react-accordion'
|
||||
import { Text } from '@status-im/components'
|
||||
import { ChevronRightIcon, ExternalIcon } from '@status-im/icons'
|
||||
|
||||
import { LINKS } from '@/config/links'
|
||||
|
||||
import { Link } from '../link'
|
||||
|
||||
const AccordionMenu = () => {
|
||||
const [openLink, setOpenLink] = useState('')
|
||||
|
||||
const handleToggle = (value: string) => {
|
||||
setOpenLink(value === openLink ? '' : value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col px-3">
|
||||
{Object.entries(LINKS).map(([name, links]) => (
|
||||
<Accordion.Root
|
||||
key={name}
|
||||
type="single"
|
||||
collapsible
|
||||
value={openLink}
|
||||
onValueChange={handleToggle}
|
||||
className="accordion-root flex flex-col pb-3 first-of-type:pt-3"
|
||||
>
|
||||
<Accordion.Item key={name} value={name} className="accordion-item">
|
||||
<div>
|
||||
<Accordion.Trigger className="accordion-trigger">
|
||||
<div className="accordion-chevron inline-flex h-5 w-5">
|
||||
<ChevronRightIcon size={20} color="$white-100" />
|
||||
</div>
|
||||
<Text size={19} weight="medium" color={'$white-100'}>
|
||||
{name}
|
||||
</Text>
|
||||
</Accordion.Trigger>
|
||||
<Accordion.Content className="accordion-content pl-5">
|
||||
<div className="overflow-hidden">
|
||||
{links.map((link, index) => {
|
||||
const external = link.href.startsWith('http')
|
||||
return (
|
||||
<div
|
||||
key={link.name + index}
|
||||
className={`pt-3 transition-opacity first-of-type:pt-5 last-of-type:pb-5 hover:opacity-50`}
|
||||
>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<Text size={15} weight="medium" color="$white-100">
|
||||
{link.name}
|
||||
</Text>
|
||||
|
||||
{external && (
|
||||
<ExternalIcon size={20} color="$white-100" />
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Accordion.Content>
|
||||
</div>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { AccordionMenu }
|
|
@ -1,5 +1,3 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import * as NavigationMenu from '@radix-ui/react-navigation-menu'
|
||||
import { Button, Text } from '@status-im/components'
|
||||
import { DownloadIcon, ExternalIcon } from '@status-im/icons'
|
||||
|
@ -7,39 +5,22 @@ import { cx } from 'class-variance-authority'
|
|||
|
||||
import { LINKS } from '@/config/links'
|
||||
|
||||
import { Link } from './link'
|
||||
import { Logo } from './logo'
|
||||
import { Link } from '../link'
|
||||
import { Logo } from '../logo'
|
||||
|
||||
export const NavMenu = () => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
type Props = {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
// Using ref to prevent re-running of useEffect
|
||||
const visibleRef = useRef(visible)
|
||||
visibleRef.current = visible
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
if (window.scrollY > 60) {
|
||||
visibleRef.current === false && setVisible(true)
|
||||
} else {
|
||||
visibleRef.current === true && setVisible(false)
|
||||
}
|
||||
}
|
||||
|
||||
handleScroll()
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [])
|
||||
const FloatingDesktop = (props: Props) => {
|
||||
const { visible } = props
|
||||
|
||||
return (
|
||||
<NavigationMenu.Root
|
||||
data-visible={visible}
|
||||
className={cx([
|
||||
'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',
|
||||
'data-[visible=false]:pointer-events-none',
|
||||
'data-[visible=true]:pointer-events-auto',
|
||||
'opacity-0 transition-opacity data-[visible=true]:opacity-100',
|
||||
])}
|
||||
>
|
||||
|
@ -58,7 +39,7 @@ export const NavMenu = () => {
|
|||
className={cx([
|
||||
'grid gap-3 pb-12 pl-[60px] pt-6',
|
||||
'data-[state=open]:animate-in',
|
||||
'data-[state=closed]:animate-out fade-out-20',
|
||||
'fade-out-20 data-[state=closed]:animate-out',
|
||||
])}
|
||||
>
|
||||
{links.map(link => {
|
||||
|
@ -91,10 +72,12 @@ export const NavMenu = () => {
|
|||
|
||||
<NavigationMenu.Viewport
|
||||
className={cx([
|
||||
'data-[state=open]:animate-heightIn data-[state=closed]:animate-heightOut',
|
||||
'transition-height h-[var(--radix-navigation-menu-viewport-height)]',
|
||||
'data-[state=closed]:animate-heightOut data-[state=open]:animate-heightIn',
|
||||
'h-[var(--radix-navigation-menu-viewport-height)] transition-height',
|
||||
])}
|
||||
/>
|
||||
</NavigationMenu.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export { FloatingDesktop }
|
|
@ -0,0 +1,82 @@
|
|||
import { useRef, useState } from 'react'
|
||||
|
||||
import { animated, useScroll } from '@react-spring/web'
|
||||
import { cx } from 'class-variance-authority'
|
||||
|
||||
import { useLockScroll } from '@/hooks/use-lock-scroll'
|
||||
import { useOutsideClick } from '@/hooks/use-outside-click'
|
||||
|
||||
import { FloatingDesktop } from './floating-desktop'
|
||||
import { FloatingMobile } from './floating-mobile'
|
||||
|
||||
const FloatingMenu = (): JSX.Element => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
const [open, setOpen] = useState(false)
|
||||
const openRef = useRef(open)
|
||||
openRef.current = open
|
||||
|
||||
// Using ref to prevent re-running of useEffect
|
||||
const visibleRef = useRef(visible)
|
||||
const scrollYRef = useRef(0)
|
||||
visibleRef.current = visible
|
||||
|
||||
// Close menu on outside click
|
||||
const ref = useOutsideClick(() => setOpen(false))
|
||||
useLockScroll(open)
|
||||
|
||||
useScroll({
|
||||
onChange: ({ value: { scrollYProgress } }) => {
|
||||
const isMenuOpen = openRef.current
|
||||
const isScrollingUp = scrollYProgress < scrollYRef.current
|
||||
const detectionPoint = scrollYProgress > 0.005
|
||||
|
||||
if (detectionPoint && isScrollingUp) {
|
||||
if (!visibleRef.current) {
|
||||
setVisible(true)
|
||||
}
|
||||
} else {
|
||||
if (!isMenuOpen) {
|
||||
setVisible(false)
|
||||
}
|
||||
}
|
||||
scrollYRef.current = scrollYProgress
|
||||
},
|
||||
default: {
|
||||
immediate: true,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<animated.div
|
||||
ref={ref}
|
||||
style={{
|
||||
opacity: visible ? 1 : 0,
|
||||
}}
|
||||
className={cx([
|
||||
'fixed left-1/2 top-1 z-10 flex -translate-x-1/2 flex-col items-center justify-between p-2 pb-0 lg:hidden',
|
||||
'rounded-2xl border border-neutral-80/5 bg-blur-neutral-80/80 backdrop-blur-md',
|
||||
' w-[calc(100%-24px)]',
|
||||
' opacity-0 transition-opacity data-[visible=true]:opacity-100',
|
||||
'z-10',
|
||||
])}
|
||||
>
|
||||
<FloatingMobile open={open} setOpen={setOpen} />
|
||||
</animated.div>
|
||||
<animated.div
|
||||
style={{
|
||||
opacity: visible ? 1 : 0,
|
||||
}}
|
||||
className={cx([
|
||||
'fixed left-1/2 top-5 z-10 w-max min-w-[746px] -translate-x-1/2 overflow-hidden',
|
||||
'rounded-2xl border border-neutral-80/5 bg-blur-neutral-80/80 backdrop-blur-md',
|
||||
'hidden md-lg:block',
|
||||
])}
|
||||
>
|
||||
<FloatingDesktop visible={visible} />
|
||||
</animated.div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { FloatingMenu }
|
|
@ -0,0 +1,61 @@
|
|||
import { Button, IconButton } from '@status-im/components'
|
||||
import { CloseIcon, DownloadIcon, MenuIcon } from '@status-im/icons'
|
||||
|
||||
import { Link } from '../link'
|
||||
import { Logo } from '../logo'
|
||||
import { AccordionMenu } from './accordion-menu'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
}
|
||||
|
||||
const FloatingMobile = (props: Props) => {
|
||||
const { open, setOpen } = props
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="z-10 flex w-full items-center justify-between">
|
||||
<div className="flex">
|
||||
<Link href="/">
|
||||
<Logo />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<IconButton
|
||||
variant="outline"
|
||||
icon={
|
||||
open ? (
|
||||
<CloseIcon size={20} color={'$white-100'} />
|
||||
) : (
|
||||
<MenuIcon size={20} />
|
||||
)
|
||||
}
|
||||
onPress={() => setOpen(!open)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
height: open ? '100%' : 0,
|
||||
opacity: open ? 1 : 0,
|
||||
pointerEvents: open ? 'auto' : 'none',
|
||||
overflow: open ? 'auto' : 'hidden',
|
||||
}}
|
||||
className={`z-10 flex w-full flex-col justify-between pt-2`}
|
||||
>
|
||||
<AccordionMenu />
|
||||
<div className="flex justify-center py-3">
|
||||
<Button
|
||||
size={40}
|
||||
variant="grey"
|
||||
icon={<DownloadIcon size={20} />}
|
||||
fullWidth
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { FloatingMobile }
|
|
@ -0,0 +1,88 @@
|
|||
import * as NavigationMenu from '@radix-ui/react-navigation-menu'
|
||||
import { Button, Text } from '@status-im/components'
|
||||
import { DownloadIcon, ExternalIcon } from '@status-im/icons'
|
||||
import { cx } from 'class-variance-authority'
|
||||
|
||||
import { Logo } from '@/components/logo'
|
||||
import { LINKS } from '@/config/links'
|
||||
|
||||
import { Link } from '../link'
|
||||
|
||||
const NavDesktop = () => {
|
||||
return (
|
||||
<>
|
||||
<NavigationMenu.Root className="relative z-10 hidden md-lg:block">
|
||||
<div className="flex items-center px-6">
|
||||
<div className="mr-5 flex shrink-0 ">
|
||||
<Link href="/">
|
||||
<Logo isTopbarDesktop />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<NavigationMenu.List className="flex items-center">
|
||||
{Object.entries(LINKS).map(([name, links]) => (
|
||||
<NavigationMenu.Item key={name}>
|
||||
<NavigationMenu.Trigger className="py-4 pr-5 aria-expanded:opacity-50">
|
||||
<Text size={15} weight="medium" color="$white-100">
|
||||
{name}
|
||||
</Text>
|
||||
</NavigationMenu.Trigger>
|
||||
<NavigationMenu.Content className="grid gap-3 pb-8 pl-[164px] pt-3">
|
||||
{links.map(link => {
|
||||
const external = link.href.startsWith('http')
|
||||
|
||||
return (
|
||||
<NavigationMenu.Link key={link.name} asChild>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<Text
|
||||
size={27}
|
||||
weight="semibold"
|
||||
color="$white-100"
|
||||
>
|
||||
{link.name}
|
||||
</Text>
|
||||
{external && (
|
||||
<ExternalIcon size={20} color="$white-100" />
|
||||
)}
|
||||
</Link>
|
||||
</NavigationMenu.Link>
|
||||
)
|
||||
})}
|
||||
</NavigationMenu.Content>
|
||||
</NavigationMenu.Item>
|
||||
))}
|
||||
</NavigationMenu.List>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end ">
|
||||
<Button
|
||||
size={32}
|
||||
variant="darkGrey"
|
||||
icon={<DownloadIcon size={20} />}
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<NavigationMenu.Viewport
|
||||
className={cx([
|
||||
'data-[state=closed]:animate-heightOut data-[state=open]:animate-heightIn',
|
||||
'h-[var(--radix-navigation-menu-viewport-height)] transition-height',
|
||||
// 'data-[state=open]:animate-heightIn animate-',
|
||||
// 'data-[state=closed]:animate-heightOut',
|
||||
// 'transition-height h-[var(--radix-navigation-menu-viewport-height)]',
|
||||
// 'transition-height mb-8 h-[var(--radix-navigation-menu-viewport-height)] duration-1000',
|
||||
// 'h-[var(--radix-navigation-menu-viewport-height)]',
|
||||
// 'data-[state=open]:animate-scaleIn data-[state=closed]:animate-scaleOut relative mt-[10px] h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_center] overflow-hidden rounded-[6px] bg-white transition-[width,height] duration-300 sm:w-[var(--radix-navigation-menu-viewport-width)]',
|
||||
])}
|
||||
/>
|
||||
</NavigationMenu.Root>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { NavDesktop }
|
|
@ -0,0 +1,67 @@
|
|||
import { useState } from 'react'
|
||||
|
||||
import { Button, IconButton } from '@status-im/components'
|
||||
import { CloseIcon, DownloadIcon, MenuIcon } from '@status-im/icons'
|
||||
|
||||
import { Logo } from '@/components/logo'
|
||||
import { useLockScroll } from '@/hooks/use-lock-scroll'
|
||||
|
||||
import { Link } from '../link'
|
||||
import { AccordionMenu } from './accordion-menu'
|
||||
|
||||
const NavMobile = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const screenHeight = typeof window !== 'undefined' ? window.innerHeight : 0
|
||||
|
||||
useLockScroll(open)
|
||||
const handleToggle = () => {
|
||||
setOpen(prevOpen => !prevOpen)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="z-10 flex flex-col items-center justify-between px-5 py-3 pb-1 md-lg:hidden">
|
||||
<div className="z-10 flex w-full items-center justify-between">
|
||||
<div className="flex">
|
||||
<Link href="/">
|
||||
<Logo />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<IconButton
|
||||
variant="outline"
|
||||
icon={
|
||||
open ? (
|
||||
<CloseIcon size={20} color="$white-100" />
|
||||
) : (
|
||||
<MenuIcon size={20} color="$white-100" />
|
||||
)
|
||||
}
|
||||
onPress={handleToggle}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
height: open ? screenHeight - 100 : 0,
|
||||
opacity: open ? 1 : 0,
|
||||
pointerEvents: open ? 'auto' : 'none',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
className={`z-10 flex w-full flex-col justify-between bg-blur-neutral-100/70 pt-2 transition-all duration-300`}
|
||||
>
|
||||
<AccordionMenu />
|
||||
<div className="flex justify-center py-3">
|
||||
<Button
|
||||
size={40}
|
||||
variant="grey"
|
||||
icon={<DownloadIcon size={20} />}
|
||||
fullWidth
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { NavMobile }
|
|
@ -1,135 +0,0 @@
|
|||
import { Button, Text } from '@status-im/components'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { LINKS, SOCIALS } from '@/config/links'
|
||||
|
||||
import { Logo } from './logo'
|
||||
|
||||
import type { Links } from '@/config/links'
|
||||
|
||||
export const PageFooter = () => {
|
||||
return (
|
||||
<footer className="px-6 pb-3 pt-14">
|
||||
<div className="mb-6">
|
||||
<Logo />
|
||||
</div>
|
||||
<div className="mb-10 grid grid-cols-2 lg:grid-cols-8">
|
||||
{Object.entries(LINKS).map(([title, links]) => (
|
||||
<Section key={title} title={title} links={links} />
|
||||
))}
|
||||
|
||||
<div>
|
||||
<Text size={13} color="$neutral-50">
|
||||
{"Let's connect"}
|
||||
</Text>
|
||||
<div className="flex flex-col">
|
||||
{Object.values(SOCIALS).map(social => (
|
||||
<Text size={11} key={social.name}>
|
||||
{social.name}
|
||||
</Text>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 grid gap-5 lg:grid-cols-[2fr_1fr]">
|
||||
<ActionCard
|
||||
title="Own a community?"
|
||||
description="Don't give Discord and Telegram power over your community"
|
||||
action="Take back control"
|
||||
/>
|
||||
<ActionCard
|
||||
title="Give us feedback!"
|
||||
description="Tell us how to make it better"
|
||||
action="Upvote"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Text size={11} color="$neutral-50">
|
||||
© Status Research & Development GmbH
|
||||
</Text>
|
||||
|
||||
<span>
|
||||
<svg
|
||||
width="2"
|
||||
height="2"
|
||||
viewBox="0 0 2 2"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="1" cy="1" r="1" fill="#647084" />
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Text size={11} color="$neutral-40">
|
||||
Terms of use
|
||||
</Text>
|
||||
<Text size={11} color="$neutral-40">
|
||||
Privacy policy
|
||||
</Text>
|
||||
<Text size={11} color="$neutral-40">
|
||||
Cookies
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
type SectionProps = {
|
||||
title: string
|
||||
links: Links
|
||||
}
|
||||
|
||||
const Section = (props: SectionProps) => {
|
||||
const { title, links } = props
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="grid gap-3">
|
||||
<Text size={13} color="$neutral-50">
|
||||
{title}
|
||||
</Text>
|
||||
<ul className="grid gap-1">
|
||||
{links.map(link => (
|
||||
<li key={link.name}>
|
||||
<Link href={link.href}>
|
||||
<Text size={15} color="$white-100" weight="medium">
|
||||
{link.name}
|
||||
</Text>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type ActionCardProps = {
|
||||
title: string
|
||||
description: string
|
||||
action: string
|
||||
}
|
||||
|
||||
const ActionCard = (props: ActionCardProps) => {
|
||||
const { title, description, action } = props
|
||||
|
||||
return (
|
||||
<div className="bg-netural-95 border-neutral-90 flex items-center rounded-[20px] border px-5 py-3">
|
||||
<div className="grid flex-1 gap-px">
|
||||
<Text size={19} color="$white-100" weight="semibold">
|
||||
{title}
|
||||
</Text>
|
||||
<Text size={15} color="$white-100">
|
||||
{description}
|
||||
</Text>
|
||||
</div>
|
||||
<Button size={32} variant="darkGrey">
|
||||
{action}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import { Button, Text } from '@status-im/components'
|
||||
import { DownloadIcon } from '@status-im/icons'
|
||||
import Image from 'next/image'
|
||||
|
||||
import logoSrc from '../../public/images/logo/default.svg'
|
||||
import { ComingSoon } from './coming-soon'
|
||||
|
||||
const Prefooter = () => {
|
||||
return (
|
||||
<div className="bg-neutral-100 p-5 py-[120px]">
|
||||
<div className="flex justify-center">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<Image src={logoSrc} alt="Status logo" width={80} />
|
||||
<h1 className="py-4 pb-3 pt-5 text-center text-[40px] font-bold leading-[44px] text-white-100 lg:pb-5 lg:text-[88px] lg:leading-[84px]">
|
||||
Be unstoppable
|
||||
</h1>
|
||||
<span className="max-w-md text-center font-bold">
|
||||
<Text size={19} color={'$white-100'}>
|
||||
Use the open source, decentralized crypto communication super app.
|
||||
</Text>
|
||||
</span>
|
||||
<div className="relative flex flex-col justify-center pt-8">
|
||||
<div className="inline-flex justify-center">
|
||||
<Button
|
||||
size={40}
|
||||
icon={<DownloadIcon size={20} />}
|
||||
variant="yellow"
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative max-w-xs pt-4 text-center leading-3">
|
||||
<Text size={11} color={'$neutral-40'}>
|
||||
Betas for Mac, Windows, Linux <br />
|
||||
Alphas for iOS & Android
|
||||
</Text>
|
||||
<div className="absolute right-[-48px] top-9">
|
||||
<ComingSoon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { Prefooter }
|
|
@ -21,9 +21,9 @@ import {
|
|||
QrCodeIcon,
|
||||
} from '@status-im/icons'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { Head } from '@/components/head'
|
||||
import { ERROR_CODES } from '@/consts/error-codes'
|
||||
import { useURLData } from '@/hooks/use-url-data'
|
||||
import { getRequestClient } from '@/lib/request-client'
|
||||
|
@ -80,6 +80,12 @@ export type Data =
|
|||
info: UserInfo
|
||||
}
|
||||
|
||||
const ACTION_VERB: Record<Type, string> = {
|
||||
community: 'Join',
|
||||
channel: 'View',
|
||||
profile: 'Open',
|
||||
}
|
||||
|
||||
const INSTRUCTIONS_HEADING: Record<Type, string> = {
|
||||
community: 'How to join this community:',
|
||||
channel: 'How to join this channel:',
|
||||
|
@ -93,7 +99,7 @@ const JOIN_BUTTON_LABEL: Record<Type, string> = {
|
|||
}
|
||||
|
||||
export function PreviewPage(props: PreviewPageProps) {
|
||||
const { type, decodedData, encodedData } = props
|
||||
const { type, decodedData, encodedData, index } = props
|
||||
|
||||
const { asPath } = useRouter()
|
||||
|
||||
|
@ -109,8 +115,10 @@ export function PreviewPage(props: PreviewPageProps) {
|
|||
channelUuid: urlChannelUuid,
|
||||
data: urlData,
|
||||
errorCode: urlErrorCode,
|
||||
isLoading: urlIsLoading,
|
||||
} = useURLData(type, decodedData, encodedData)
|
||||
|
||||
const wakuQueryIsEnabled = Boolean(publicKey)
|
||||
const {
|
||||
data: wakuData,
|
||||
isLoading,
|
||||
|
@ -119,7 +127,7 @@ export function PreviewPage(props: PreviewPageProps) {
|
|||
} = useQuery({
|
||||
refetchOnWindowFocus: false,
|
||||
queryKey: [type],
|
||||
enabled: !!publicKey,
|
||||
enabled: wakuQueryIsEnabled,
|
||||
queryFn: async function ({ queryKey }): Promise<Data | null> {
|
||||
const client = await getRequestClient()
|
||||
|
||||
|
@ -179,7 +187,20 @@ export function PreviewPage(props: PreviewPageProps) {
|
|||
},
|
||||
})
|
||||
|
||||
const loading = status === 'loading' || isLoading
|
||||
const loading = getLoading()
|
||||
|
||||
function getLoading(): boolean {
|
||||
if (urlIsLoading) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (wakuQueryIsEnabled) {
|
||||
return status === 'loading' || isLoading
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const data: Data | undefined = wakuData ?? urlData
|
||||
|
||||
const { avatarURL, bannerURL } = useMemo(() => {
|
||||
|
@ -201,9 +222,34 @@ export function PreviewPage(props: PreviewPageProps) {
|
|||
return <ErrorPage errorCode={ERROR_CODES.NOT_FOUND} />
|
||||
}
|
||||
|
||||
// const urlOrigin = process.env.VERCEL_URL
|
||||
// ? 'https://' + process.env.VERCEL_URL
|
||||
// : ''
|
||||
|
||||
if ((loading && !data) || !data || !publicKey) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta property="og:url" content={`https://status.app${asPath}`} />
|
||||
{/* todo: test if server-rendered version with which a (social) card would be
|
||||
generated would not effectively override actual shared link on clicking */}
|
||||
{/* <meta
|
||||
property="og:image"
|
||||
content={`${urlOrigin}/assets/preview/entity.png`}
|
||||
key="og:image"
|
||||
/> */}
|
||||
<meta
|
||||
property="al:ios:url"
|
||||
content={`https://status.app${asPath}`}
|
||||
key="al:ios:url"
|
||||
/>
|
||||
<meta
|
||||
property="al:android:url"
|
||||
content={`https://status.app${asPath}`}
|
||||
key="al:android:url"
|
||||
/>
|
||||
{!index && <meta name="robots" content="noindex" />}
|
||||
</Head>
|
||||
<div className="h-full xl:grid xl:grid-cols-[560px,auto]">
|
||||
<div className="pb-10">
|
||||
<div className="mx-auto px-5 pt-20 xl:px-20">
|
||||
|
@ -311,16 +357,39 @@ export function PreviewPage(props: PreviewPageProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Head index={props.index} />
|
||||
<Head>
|
||||
<title>
|
||||
{`${ACTION_VERB[type]} ${data.info.displayName} in Status`}
|
||||
</title>
|
||||
|
||||
<meta property="og:url" content={`https://status.app${asPath}`} />
|
||||
<meta
|
||||
property="og:title"
|
||||
content={`${ACTION_VERB[type]} ${data.info.displayName} in Status`}
|
||||
/>
|
||||
{/* <meta
|
||||
property="og:image"
|
||||
content={`${urlOrigin}/assets/preview/entity.png`}
|
||||
key="og:image"
|
||||
/> */}
|
||||
<meta property="og:description" content={data.info.description} />
|
||||
<meta
|
||||
name="apple-itunes-app"
|
||||
content={`app-id=1178893006, app-argument=status-app://${asPath.replace(
|
||||
/\//,
|
||||
''
|
||||
)}`}
|
||||
/>
|
||||
{!index && <meta name="robots" content="noindex" />}
|
||||
</Head>
|
||||
<>
|
||||
{/* todo: theme; based on user system settings */}
|
||||
{/* todo: (system or both?) install banner */}
|
||||
<div
|
||||
style={!bannerURL ? getGradientStyles(data) : undefined}
|
||||
className="relative h-full bg-gradient-to-b from-[var(--gradient-color)] to-[#fff] to-20% xl:grid xl:grid-cols-[560px,auto]"
|
||||
>
|
||||
<div className="absolute left-0 right-0 top-0 xl:hidden">
|
||||
<div className="from-white-100 to-white-60 absolute h-full w-full bg-gradient-to-t" />
|
||||
<div className="absolute inset-x-0 top-0 xl:hidden">
|
||||
<div className="absolute h-full w-full bg-gradient-to-t from-white-100 to-white-60" />
|
||||
{bannerURL && (
|
||||
<img
|
||||
className="aspect-video h-full w-full object-cover"
|
||||
|
@ -420,7 +489,7 @@ export function PreviewPage(props: PreviewPageProps) {
|
|||
|
||||
{/* INSTRUCTIONS */}
|
||||
<div className="mb-6 grid gap-3">
|
||||
<div className="border-neutral-10 bg-white-100 rounded-2xl border px-4 py-3">
|
||||
<div className="rounded-2xl border border-neutral-10 bg-white-100 px-4 py-3">
|
||||
<h3 className="mb-2 text-[15px] font-semibold xl:text-[19px]">
|
||||
{INSTRUCTIONS_HEADING[type]}
|
||||
</h3>
|
||||
|
@ -460,7 +529,7 @@ export function PreviewPage(props: PreviewPageProps) {
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="border-neutral-10 bg-white-100 flex flex-col items-start gap-4 rounded-2xl border p-4 pt-3">
|
||||
<div className="flex flex-col items-start gap-4 rounded-2xl border border-neutral-10 bg-white-100 p-4 pt-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text size={15} weight="semibold">
|
||||
Have Status already?
|
||||
|
|
|
@ -26,8 +26,8 @@ export const QrDialog = (props: Props) => {
|
|||
<Dialog.Content className="data-[state=open]:animate-contentShow fixed inset-0 flex items-center justify-center focus:outline-none">
|
||||
<div>
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="bg-white-5 aspect-square w-full max-w-[335px] rounded-2xl p-3">
|
||||
<div className="bg-white-100 rounded-xl p-3">
|
||||
<div className="aspect-square w-full max-w-[335px] rounded-2xl bg-white-5 p-3">
|
||||
<div className="rounded-xl bg-white-100 p-3">
|
||||
<QRCodeSVG value={value} height={286} width={286} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -36,8 +36,8 @@ const SideBar = (props: Props) => {
|
|||
}, [defaultLabel])
|
||||
|
||||
return (
|
||||
<div className="border-neutral-10 border-r p-5">
|
||||
<aside className=" sticky top-5 w-[280px]">
|
||||
<div className="border-r border-neutral-10 p-5">
|
||||
<aside className=" sticky top-5 min-w-[320px]">
|
||||
<Accordion.Root
|
||||
type="single"
|
||||
collapsible
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import { Avatar, Button, Tag, Text } from '@status-im/components'
|
||||
import Link from 'next/link'
|
||||
|
||||
const issues = [
|
||||
{
|
||||
id: 5154,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'Open',
|
||||
},
|
||||
{
|
||||
id: 5155,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'Open',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'Open',
|
||||
},
|
||||
{
|
||||
id: 4324,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'Open',
|
||||
},
|
||||
{
|
||||
id: 876,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'Open',
|
||||
},
|
||||
]
|
||||
|
||||
export const TableIssues = () => {
|
||||
return (
|
||||
<div className="overflow-hidden rounded-2xl border border-neutral-10">
|
||||
<div className="border-b border-neutral-10 bg-neutral-5 p-3">
|
||||
<Text size={15} weight="medium">
|
||||
784 Open
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-neutral-10">
|
||||
{issues.map(issue => (
|
||||
<Link
|
||||
key={issue.id}
|
||||
href={`https://github.com/status-im/status-react/issues/${issue.id}`}
|
||||
className="flex items-center justify-between px-4 py-3 transition-colors duration-200 hover:bg-neutral-5"
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<Text size={15} weight="medium">
|
||||
{issue.title}
|
||||
</Text>
|
||||
<Text size={13} color="$neutral-50">
|
||||
#9667 • Opened 2 days ago by slaedjenic
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="flex gap-1">
|
||||
<Tag size={24} label="E:Syncing" color="$orange-50" />
|
||||
<Tag size={24} label="E:Wallet" color="$green-50" />
|
||||
<Tag size={24} label="Feature" color="$pink-50" />
|
||||
<Tag size={24} label="Web" color="$purple-50" />
|
||||
</div>
|
||||
|
||||
<Tag size={24} label="9435" />
|
||||
|
||||
<Avatar
|
||||
type="user"
|
||||
size={24}
|
||||
name="jkbktl"
|
||||
src="https://images.unsplash.com/photo-1552058544-f2b08422138a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1299&q=80"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="p-3">
|
||||
<Button size={40} variant="outline">
|
||||
Show more 10
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
import { Button, Counter, Tag, Text } from '@status-im/components'
|
||||
|
||||
import { useParalax } from '@/hooks/use-parallax'
|
||||
|
||||
import { ParalaxCircle } from './parallax-circle'
|
||||
|
||||
const ComparisionSection = () => {
|
||||
const { top, bottom } = useParalax({
|
||||
initialTop: -690,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="relative grid grid-cols-4 border-t border-dashed border-neutral-80/20 bg-white-100 mix-blend-normal">
|
||||
<div className="col-span-2 border-r border-dashed border-neutral-80/20 py-[160px] pl-10 pr-[60px]">
|
||||
<div className="flex h-full flex-col justify-between">
|
||||
<div className="inline-flex max-w-[646px] flex-col">
|
||||
<div className="inline-flex">
|
||||
<Tag label="Use case" size={24} />
|
||||
</div>
|
||||
<h2 className="pt-4 text-[40px] font-bold leading-[44px]">
|
||||
Alice has 50 DAI on both Ethereum Mainnet and Optimism and wants
|
||||
to send 100 DAI to Bob on Arbitrum.
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 flex max-w-[341px] flex-col rounded-[20px] border border-dashed border-neutral-80/20 p-4">
|
||||
<Text size={19} weight="semibold">
|
||||
Finally! Multi-chain done right!
|
||||
</Text>
|
||||
<div className="flex pt-1">
|
||||
<Text size={19}>
|
||||
Interested in how Status’s send with auto routing and bridging
|
||||
works and helps users?{' '}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="inline-flex pt-1">
|
||||
<Button variant="outline">Read more</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-r border-dashed border-neutral-80/20 px-10 py-[160px]">
|
||||
<Text size={19} weight="semibold">
|
||||
Other wallets
|
||||
</Text>
|
||||
<div className="flex flex-col pt-3">
|
||||
{listOneData.map(item => (
|
||||
<Item key={item.count} {...item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-10 py-[160px]">
|
||||
<Text size={19} weight="semibold">
|
||||
Status Wallet
|
||||
</Text>
|
||||
<div className="flex flex-col pt-3">
|
||||
{listTwoData.map(item => (
|
||||
<Item key={item.count} {...item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<ParalaxCircle initialTop={-600} />
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
top: `${top}px`,
|
||||
bottom: `${bottom}px`,
|
||||
left: -40,
|
||||
// TODO: use font from design when it's ready
|
||||
fontFamily: 'Menlo',
|
||||
}}
|
||||
className="absolute whitespace-nowrap text-[240px] font-bold leading-[212px] text-neutral-80/5"
|
||||
>
|
||||
eth:opt:arb:0xAgafhja
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { ComparisionSection }
|
||||
|
||||
const listOneData = [
|
||||
{
|
||||
count: 1,
|
||||
label: 'Open dApp Browser',
|
||||
},
|
||||
{
|
||||
count: 2,
|
||||
label: 'Visit Bridge dApp',
|
||||
},
|
||||
{
|
||||
count: 3,
|
||||
label: 'Bridge DAI from Mainnet to Arbitrum',
|
||||
},
|
||||
{
|
||||
count: 4,
|
||||
label: 'Send DAI on Arbitrum',
|
||||
},
|
||||
{
|
||||
count: 5,
|
||||
label: 'Open dApp Browser',
|
||||
},
|
||||
{
|
||||
count: 6,
|
||||
label: 'Visit Bridge dApp',
|
||||
},
|
||||
{
|
||||
count: 7,
|
||||
label: 'Bridge DAI from Optimism to Arbitrum',
|
||||
},
|
||||
{
|
||||
count: 8,
|
||||
label: 'Send DAI on Arbitrum',
|
||||
noBorder: true,
|
||||
},
|
||||
{
|
||||
count: '😮💨',
|
||||
label: 'Is that it?',
|
||||
secondaryLabel: 'Did I use cheapest route and bridges?',
|
||||
noBorder: true,
|
||||
},
|
||||
]
|
||||
|
||||
const listTwoData = [
|
||||
{
|
||||
count: 1,
|
||||
label: 'Select the token',
|
||||
},
|
||||
{
|
||||
count: 2,
|
||||
label: 'Select the amount',
|
||||
},
|
||||
{
|
||||
count: 3,
|
||||
label: 'Send',
|
||||
noBorder: true,
|
||||
},
|
||||
{
|
||||
count: '🎉',
|
||||
label: 'That’s it!',
|
||||
secondaryLabel: 'Lowest possible cost!',
|
||||
noBorder: true,
|
||||
},
|
||||
]
|
||||
|
||||
const Item = (props: {
|
||||
count: string | number
|
||||
label: string
|
||||
secondaryLabel?: string
|
||||
noBorder?: boolean
|
||||
}) => {
|
||||
const { count, label, secondaryLabel, noBorder } = props
|
||||
const isNumber = typeof count === 'number'
|
||||
return (
|
||||
<div
|
||||
className={`flex border-neutral-80/20 ${
|
||||
noBorder ? 'border-b-0' : 'border-b'
|
||||
} border-dashed py-3`}
|
||||
>
|
||||
{isNumber ? (
|
||||
<Counter value={count} type="outline" />
|
||||
) : (
|
||||
<Text size={19}>{count}</Text>
|
||||
)}
|
||||
<div className="flex flex-col pl-[10px]">
|
||||
<Text size={19} weight={isNumber ? 'regular' : 'semibold'}>
|
||||
{label}
|
||||
</Text>
|
||||
{!isNumber && (
|
||||
<Text size={19} weight="regular">
|
||||
{secondaryLabel}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import { Text } from '@status-im/components'
|
||||
|
||||
const HandsSection = () => {
|
||||
return (
|
||||
<div className="relative bg-[url('/assets/wallet/background-pattern.png')] bg-[length:100%_100%] bg-no-repeat py-[322px]">
|
||||
<div className="mx-40 mb-24">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<h2 className="max-w-[600px] py-4 pb-5 text-center text-[64px] font-bold leading-[68px]">
|
||||
Take control
|
||||
<br />
|
||||
of your crypto
|
||||
</h2>
|
||||
<span className="max-w-xl text-center font-bold">
|
||||
<Text size={27}>
|
||||
No one (including Status!) has the power to freeze, lock-out or
|
||||
stop a Status user from accessing and transacting their tokens.
|
||||
</Text>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative z-[2] flex justify-center pt-[320px]">
|
||||
<div className="mr-24 flex max-w-[381px] flex-col items-center">
|
||||
<img src="/assets/wallet/skull.png" alt="skull" width="48px" />
|
||||
<div className="flex flex-col items-center pt-4 text-center">
|
||||
<Text size={27} weight="semibold">
|
||||
Ethereum based assets
|
||||
</Text>
|
||||
<Text size={19}>
|
||||
We support all assets in the Uniswap Labs default tokenlist and
|
||||
those minted by communities using Status.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex max-w-[381px] flex-col items-center">
|
||||
<img src="/assets/wallet/nft.png" alt="nft" width="48px" />
|
||||
<div className="flex flex-col items-center pt-4 text-center">
|
||||
<Text size={27} weight="semibold">
|
||||
NFTs and collectibles
|
||||
</Text>
|
||||
<Text size={19}>
|
||||
We will display any NFTs or collectibles listed on OpenSea plus
|
||||
those minted by communities using Status.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="/assets/wallet/hands.png"
|
||||
alt="hands"
|
||||
className="absolute left-0 top-0 w-full"
|
||||
/>
|
||||
<img
|
||||
src="/assets/wallet/gentleman.png"
|
||||
alt="gentleman"
|
||||
className="absolute bottom-0 left-0 mix-blend-multiply"
|
||||
width={403}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { HandsSection }
|
|
@ -0,0 +1,73 @@
|
|||
import { Button, Tag, Text } from '@status-im/components'
|
||||
import { DownloadIcon, WalletIcon } from '@status-im/icons'
|
||||
import image1 from 'public/assets/wallet/1.png'
|
||||
import image2 from 'public/assets/wallet/2.png'
|
||||
import image3 from 'public/assets/wallet/3.png'
|
||||
import image4 from 'public/assets/wallet/vegas.png'
|
||||
|
||||
import { GridHero } from '../cards/grid-hero'
|
||||
import { ParalaxCircle } from './parallax-circle'
|
||||
|
||||
const HeroSection = () => {
|
||||
return (
|
||||
<div className="flex w-full flex-col items-center">
|
||||
<div className="relative mb-16 grid px-5 lg:mb-24 lg:px-40">
|
||||
<ParalaxCircle initialLeft={-206} initialTop={-170} />
|
||||
<div className="relative flex flex-col items-center justify-center">
|
||||
<div className="inline-flex">
|
||||
<Tag
|
||||
size={32}
|
||||
icon={WalletIcon}
|
||||
color="$yellow-50"
|
||||
label="Wallet"
|
||||
/>
|
||||
</div>
|
||||
<h1 className="max-w-[600px] py-4 text-center text-[48px] font-bold leading-[50px] lg:pb-5 lg:text-[88px] lg:leading-[84px]">
|
||||
The future
|
||||
<br />
|
||||
is multi-chain
|
||||
</h1>
|
||||
<span className="max-w-md text-center font-bold">
|
||||
<Text size={19}>
|
||||
L2s made simple - send and manage your crypto easily and safely
|
||||
across multiple networks.
|
||||
</Text>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative flex justify-center">
|
||||
<div className="mt-6 inline-flex rounded-[20px] border border-dashed border-neutral-80/20 p-2 lg:mt-8">
|
||||
<Button
|
||||
size={40}
|
||||
icon={<DownloadIcon size={20} />}
|
||||
variant="yellow"
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<GridHero
|
||||
color="yellow"
|
||||
cardOne={{
|
||||
alt: 'wallet-1',
|
||||
image: image1,
|
||||
}}
|
||||
cardTwo={{
|
||||
alt: 'wallet-2',
|
||||
image: image2,
|
||||
}}
|
||||
cardThree={{
|
||||
alt: 'wallet-3',
|
||||
image: image3,
|
||||
}}
|
||||
cardFour={{
|
||||
alt: 'wallet-4',
|
||||
image: image4,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { HeroSection }
|
|
@ -0,0 +1,5 @@
|
|||
export { ComparisionSection } from './comparision-section'
|
||||
export { HandsSection } from './hands-section'
|
||||
export { HeroSection } from './hero-section'
|
||||
export { ParalaxCircle } from './parallax-circle'
|
||||
export { VideoSection } from './video-section'
|
|
@ -0,0 +1,28 @@
|
|||
import { useParalax } from '@/hooks/use-parallax'
|
||||
|
||||
type ParalaxProps = {
|
||||
initialLeft?: number
|
||||
initialTop?: number
|
||||
initialRight?: number
|
||||
initialBottom?: number
|
||||
}
|
||||
|
||||
const ParalaxCircle = (props: ParalaxProps) => {
|
||||
const { top, bottom } = useParalax(props)
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute z-[0] h-[676px] w-[676px] rounded-full bg-customisation-yellow-50 opacity-[0.10] blur-[260px] "
|
||||
style={{
|
||||
// Fixes the performance issue on safari with filter: blur() to force the browser use GPU acceleration for that particular element instead of the CPU.
|
||||
transform: 'translate3d(0, 0, 0)',
|
||||
left: `${props.initialLeft}px`,
|
||||
top: `${top}px`,
|
||||
right: `${props.initialRight}px`,
|
||||
bottom: `${bottom}px`,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { ParalaxCircle }
|
|
@ -0,0 +1,76 @@
|
|||
import { ContextTag, Text } from '@status-im/components'
|
||||
|
||||
import { ParalaxCircle } from './parallax-circle'
|
||||
|
||||
const VideoSection = () => {
|
||||
return (
|
||||
<div className="relative flex flex-col md:flex-row">
|
||||
<ParalaxCircle initialLeft={-100} initialTop={-100} />
|
||||
|
||||
<div className="relative z-[1] flex flex-col px-5 pt-24 lg:px-[164px] lg:pt-[240px]">
|
||||
<h2 className="text-[40px] font-bold leading-[44px] lg:text-[64px] lg:leading-[68px]">
|
||||
Fully
|
||||
<br />
|
||||
Decentralized
|
||||
<br />
|
||||
Networks
|
||||
</h2>
|
||||
|
||||
<div className="flex max-w-[462px] flex-col pt-4">
|
||||
<Text size={27}>
|
||||
Status supports blockchain networks that are fully committed to
|
||||
decentralization.
|
||||
</Text>
|
||||
<div className="pt-8">
|
||||
<Text size={13} color="$neutral-50" weight="medium">
|
||||
Currently supported networks
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex pt-3">
|
||||
<div className="pr-[10px]">
|
||||
<ContextTag
|
||||
type="network"
|
||||
network={{
|
||||
name: 'Mainnet',
|
||||
src: '/assets/wallet/ethereum.png',
|
||||
}}
|
||||
size={24}
|
||||
/>
|
||||
</div>
|
||||
<div className="pr-[10px]">
|
||||
<ContextTag
|
||||
type="network"
|
||||
network={{
|
||||
name: 'Optmism',
|
||||
src: '/assets/wallet/optimism.png',
|
||||
}}
|
||||
size={24}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ContextTag
|
||||
type="network"
|
||||
network={{
|
||||
name: 'Arbitrum',
|
||||
src: '/assets/wallet/arbitrum.png',
|
||||
}}
|
||||
size={24}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative right-[-5px] top-0 flex justify-center md:absolute">
|
||||
<video autoPlay loop muted playsInline>
|
||||
<source
|
||||
src="/assets/wallet/vitalik.mp4"
|
||||
type="video/mp4;codecs=hvc1"
|
||||
/>
|
||||
<source src="/assets/wallet/vitalik.webm" type="video/webm" />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { VideoSection }
|
|
@ -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,
|
||||
})
|
|
@ -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)
|
|
@ -1,3 +1,10 @@
|
|||
import {
|
||||
AdvancedIcon,
|
||||
AirdropIcon,
|
||||
CommunitiesIcon,
|
||||
TokenIcon,
|
||||
} from '@status-im/icons'
|
||||
|
||||
export const LINKS = {
|
||||
Features: [
|
||||
{ name: 'Communities', href: '/features/communities' },
|
||||
|
@ -45,22 +52,27 @@ export const LINKS = {
|
|||
],
|
||||
} as const
|
||||
|
||||
// TODO Update icons when available
|
||||
export const SOCIALS = {
|
||||
status: {
|
||||
name: 'Status',
|
||||
href: '<TODO>',
|
||||
icon: CommunitiesIcon,
|
||||
},
|
||||
twitter: {
|
||||
name: 'Twitter',
|
||||
href: 'https://twitter.com/ethstatus',
|
||||
icon: TokenIcon,
|
||||
},
|
||||
github: {
|
||||
name: 'GitHub',
|
||||
href: 'https://github.com/status-im',
|
||||
icon: AirdropIcon,
|
||||
},
|
||||
youtube: {
|
||||
name: 'YouTube',
|
||||
href: 'https://youtube.com/<TODO>',
|
||||
icon: AdvancedIcon,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -2,4 +2,5 @@ export const ERROR_CODES = {
|
|||
NOT_FOUND: 404,
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
INVALID_PUBLIC_KEY: 601,
|
||||
INVALID_ENS_NAME: 602,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
export const useLockScroll = (open = false) => {
|
||||
if (typeof document === 'undefined') {
|
||||
return
|
||||
}
|
||||
// Adds the following code to disable scrolling when the menu is open
|
||||
const rootElement = document.documentElement
|
||||
if (open) {
|
||||
rootElement.style.overflowY = 'hidden'
|
||||
} else {
|
||||
rootElement.style.overflowY = 'auto'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
|
||||
export const useOutsideClick = (callback: () => void) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', handleClick, true)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClick, true)
|
||||
}
|
||||
}, [callback])
|
||||
|
||||
return ref
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
|
||||
type ParalaxProps = {
|
||||
initialLeft?: number
|
||||
initialTop?: number
|
||||
initialRight?: number
|
||||
initialBottom?: number
|
||||
}
|
||||
|
||||
const PARALLAX_SPEED = 4
|
||||
|
||||
function useParalax(props?: ParalaxProps) {
|
||||
const initialTop = props?.initialTop || 0
|
||||
const initialBottom = props?.initialBottom || 0
|
||||
|
||||
const [top, setTop] = useState(initialTop)
|
||||
|
||||
const [bottom, setBottom] = useState(initialBottom)
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const scrollY = window.scrollY
|
||||
|
||||
setTop(scrollY / PARALLAX_SPEED + initialTop)
|
||||
setBottom(scrollY / PARALLAX_SPEED + initialBottom)
|
||||
}
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [initialBottom, initialTop])
|
||||
|
||||
return { top, bottom }
|
||||
}
|
||||
|
||||
export { useParalax }
|
|
@ -13,6 +13,7 @@ import {
|
|||
import { ERROR_CODES } from '@/consts/error-codes'
|
||||
|
||||
import type { Data } from '@/components/preview-page'
|
||||
import type { EnsResponse } from '@/pages/api/ens'
|
||||
import type { ChannelInfo, CommunityInfo, UserInfo } from '@status-im/js'
|
||||
import type {
|
||||
decodeChannelURLData,
|
||||
|
@ -34,6 +35,7 @@ export const useURLData = (
|
|||
const [channelUuid, setChannelUuid] = useState<string>()
|
||||
const [data, setData] = useState<Data>()
|
||||
const [error, setError] = useState<keyof typeof ERROR_CODES>()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const compressPublicKey = type !== 'profile'
|
||||
|
||||
|
@ -47,7 +49,7 @@ export const useURLData = (
|
|||
|
||||
const hash = window.location.hash.replace('#', '')
|
||||
|
||||
// use provided public key
|
||||
// use provided public key or recover it from ENS name
|
||||
if (!decodedData || !encodedData) {
|
||||
if (!hash) {
|
||||
setError('NOT_FOUND')
|
||||
|
@ -55,6 +57,32 @@ export const useURLData = (
|
|||
return
|
||||
}
|
||||
|
||||
// recover public key from ENS name
|
||||
const ensName = hash.match(/^.+\.eth$/)?.[0]
|
||||
if (ensName) {
|
||||
const fetchEnsPubkey = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/ens', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ensName, compress: compressPublicKey }),
|
||||
})
|
||||
const { publicKey } = (await response.json()) as EnsResponse
|
||||
|
||||
setPublicKey(publicKey)
|
||||
} catch {
|
||||
setError('INVALID_ENS_NAME')
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
fetchEnsPubkey()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// use provided public key
|
||||
try {
|
||||
const publicKey = deserializePublicKey(hash, {
|
||||
compress: compressPublicKey,
|
||||
|
@ -71,7 +99,7 @@ export const useURLData = (
|
|||
}
|
||||
}
|
||||
|
||||
// recover public key
|
||||
// recover public key from encoded data
|
||||
let deserializedPublicKey
|
||||
try {
|
||||
const recoveredPublicKey = recoverPublicKeyFromEncodedURLData(
|
||||
|
@ -154,5 +182,6 @@ export const useURLData = (
|
|||
channelUuid,
|
||||
data,
|
||||
errorCode: error ? ERROR_CODES[error] : undefined,
|
||||
isLoading,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,98 +1,38 @@
|
|||
import * as NavigationMenu from '@radix-ui/react-navigation-menu'
|
||||
import { Button, Text } from '@status-im/components'
|
||||
import { DownloadIcon, ExternalIcon } from '@status-im/icons'
|
||||
import { cx } from 'class-variance-authority'
|
||||
import { Footer } from '@/components/footer/footer'
|
||||
import { FooterMobile } from '@/components/footer/footer-mobile'
|
||||
import { FloatingMenu } from '@/components/navigation/floating-menu'
|
||||
import { NavDesktop } from '@/components/navigation/nav-desktop'
|
||||
import { NavMobile } from '@/components/navigation/nav-mobile'
|
||||
import { Prefooter } from '@/components/pre-footer'
|
||||
|
||||
import { Logo } from '@/components/logo'
|
||||
import { NavMenu } from '@/components/nav-menu'
|
||||
import { PageFooter } from '@/components/page-footer'
|
||||
import { LINKS } from '@/config/links'
|
||||
import type { ReactElement } from 'react'
|
||||
|
||||
import { Link } from '../components/link'
|
||||
type AppLayoutProps = {
|
||||
hasPreFooter?: boolean
|
||||
children: ReactElement
|
||||
}
|
||||
|
||||
import type { PageLayout } from 'next'
|
||||
|
||||
export const AppLayout: PageLayout = page => {
|
||||
export const AppLayout: React.FC<AppLayoutProps> = ({
|
||||
hasPreFooter = true,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<NavMenu />
|
||||
<div className="min-h-full bg-neutral-100">
|
||||
<NavigationMenu.Root>
|
||||
<div className="flex items-center px-6">
|
||||
<div className="mr-5">
|
||||
<Link href="/">
|
||||
<Logo />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<NavigationMenu.List className="flex items-center">
|
||||
{Object.entries(LINKS).map(([name, links]) => (
|
||||
<NavigationMenu.Item key={name}>
|
||||
<NavigationMenu.Trigger className="py-4 pr-5 aria-expanded:opacity-50">
|
||||
<Text size={15} weight="medium" color="$white-100">
|
||||
{name}
|
||||
</Text>
|
||||
</NavigationMenu.Trigger>
|
||||
<NavigationMenu.Content className="grid gap-3 pb-8 pl-[164px] pt-3">
|
||||
{links.map(link => {
|
||||
const external = link.href.startsWith('http')
|
||||
|
||||
return (
|
||||
<NavigationMenu.Link key={link.name} asChild>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<Text
|
||||
size={27}
|
||||
weight="semibold"
|
||||
color="$white-100"
|
||||
>
|
||||
{link.name}
|
||||
</Text>
|
||||
{external && (
|
||||
<ExternalIcon size={20} color="$white-100" />
|
||||
)}
|
||||
</Link>
|
||||
</NavigationMenu.Link>
|
||||
)
|
||||
})}
|
||||
</NavigationMenu.Content>
|
||||
</NavigationMenu.Item>
|
||||
))}
|
||||
</NavigationMenu.List>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
size={32}
|
||||
variant="darkGrey"
|
||||
icon={<DownloadIcon size={20} />}
|
||||
>
|
||||
Sign up for early access
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<NavigationMenu.Viewport
|
||||
className={cx([
|
||||
'data-[state=open]:animate-heightIn data-[state=closed]:animate-heightOut',
|
||||
'transition-height h-[var(--radix-navigation-menu-viewport-height)]',
|
||||
// 'data-[state=open]:animate-heightIn animate-',
|
||||
// 'data-[state=closed]:animate-heightOut',
|
||||
// 'transition-height h-[var(--radix-navigation-menu-viewport-height)]',
|
||||
// 'transition-height mb-8 h-[var(--radix-navigation-menu-viewport-height)] duration-1000',
|
||||
// 'h-[var(--radix-navigation-menu-viewport-height)]',
|
||||
// 'data-[state=open]:animate-scaleIn data-[state=closed]:animate-scaleOut relative mt-[10px] h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_center] overflow-hidden rounded-[6px] bg-white transition-[width,height] duration-300 sm:w-[var(--radix-navigation-menu-viewport-width)]',
|
||||
])}
|
||||
/>
|
||||
</NavigationMenu.Root>
|
||||
<FloatingMenu />
|
||||
<div className="min-h-full w-full bg-neutral-100">
|
||||
<NavDesktop />
|
||||
<NavMobile />
|
||||
|
||||
{/* ROUNDED WHITE BG */}
|
||||
{/* <div className="bg-white-100 mx-1 min-h-[900px] rounded-3xl">{page}</div> */}
|
||||
{page}
|
||||
|
||||
<PageFooter />
|
||||
<div className="flex justify-center lg:p-1">
|
||||
{/* TODO Check max-width to use */}
|
||||
<div className="min-h-[900px] w-full rounded-3xl bg-white-100">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
{hasPreFooter && <Prefooter />}
|
||||
<Footer hasBorderTop={hasPreFooter} />
|
||||
<FooterMobile hasBorderTop={hasPreFooter} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { SideBar } from '../components'
|
||||
import { AppLayout } from './app-layout'
|
||||
|
||||
import type { PageLayout } from 'next'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
// Eventually this will be fetched from the API, at least the nested links
|
||||
const MENU_LINKS = [
|
||||
|
@ -81,11 +81,17 @@ const MENU_LINKS = [
|
|||
},
|
||||
]
|
||||
|
||||
export const InsightsLayout: PageLayout = page => {
|
||||
return AppLayout(
|
||||
<div className="bg-white-100 relative mx-1 flex min-h-[calc(100vh-56px-4px)] rounded-3xl">
|
||||
interface InsightsLayoutProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const InsightsLayout: React.FC<InsightsLayoutProps> = ({ children }) => {
|
||||
return (
|
||||
<AppLayout hasPreFooter={false}>
|
||||
<div className="relative mx-1 flex min-h-[calc(100vh-56px-4px)] w-full rounded-3xl bg-white-100">
|
||||
<SideBar data={MENU_LINKS} />
|
||||
<main className="flex-1">{page}</main>
|
||||
<main className="flex-1">{children}</main>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { EthereumClient } from '@status-im/js'
|
||||
|
||||
import { serverEnv } from '@/config/env.server.mjs'
|
||||
|
||||
let client: EthereumClient | undefined
|
||||
|
||||
export function getEthereumClient(): EthereumClient | undefined {
|
||||
if (!client) {
|
||||
client = new EthereumClient(
|
||||
`https://mainnet.infura.io/v3/${serverEnv.INFURA_API_KEY}`
|
||||
)
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
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}+visibility:public` }
|
||||
: { 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)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
const basicAuth = req.headers.get('authorization')
|
||||
|
||||
if (process.env.VERCEL_ENV !== 'production') {
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
||||
if (basicAuth) {
|
||||
const auth = basicAuth.split(' ')[1]
|
||||
const [user, password] = Buffer.from(auth, 'base64').toString().split(':')
|
||||
|
||||
if (user === 'status' && password === process.env.AUTH_PASSWORD) {
|
||||
return NextResponse.next()
|
||||
}
|
||||
}
|
||||
|
||||
return new NextResponse('Authentication Required', {
|
||||
status: 401,
|
||||
headers: { 'WWW-Authenticate': `Basic realm="website"` },
|
||||
})
|
||||
}
|
|
@ -4,6 +4,9 @@ import '@/styles/nav-nested-links.css'
|
|||
import { ThemeProvider } from '@status-im/components'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { Inter } from 'next/font/google'
|
||||
import Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
import { match, P } from 'ts-pattern'
|
||||
|
||||
import type { Page, PageLayout } from 'next'
|
||||
import type { AppProps } from 'next/app'
|
||||
|
@ -23,11 +26,122 @@ type Props = AppProps & {
|
|||
export default function App({ Component, pageProps }: Props) {
|
||||
const getLayout: PageLayout = Component.getLayout || (page => page)
|
||||
|
||||
// const urlOrigin = process.env.VERCEL_URL
|
||||
// ? 'https://' + process.env.VERCEL_URL
|
||||
// : ''
|
||||
|
||||
const { pathname, asPath } = useRouter()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Status - Private, Secure Communication</title>
|
||||
|
||||
<meta name="title" content="Status - Private, Secure Communication" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Status brings the power of Ethereum into your pocket by combining a messenger, crypto-wallet, and Web3 browser."
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={`https://status.app${asPath}`} />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Status - Private, Secure Communication"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Status brings the power of Ethereum into your pocket by combining a messenger, crypto-wallet, and Web3 browser."
|
||||
/>
|
||||
{/* <meta
|
||||
property="og:image"
|
||||
content={`${urlOrigin}/assets/preview/page.png`}
|
||||
key="og:image"
|
||||
/> */}
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content="@ethstatus" />
|
||||
<meta name="apple-itunes-app" content="app-id=1178893006" />
|
||||
<meta
|
||||
property="al:ios:url"
|
||||
content={`https://status.app${asPath}`}
|
||||
key="al:ios:url"
|
||||
/>
|
||||
<meta property="al:ios:app_store_id" content="1178893006" />
|
||||
<meta
|
||||
property="al:ios:app_name"
|
||||
content="Status — Ethereum. Anywhere"
|
||||
/>
|
||||
<meta
|
||||
property="al:android:url"
|
||||
content={`https://status.app${asPath}`}
|
||||
key="al:android:url"
|
||||
/>
|
||||
<meta property="al:android:package" content="im.status.ethereum" />
|
||||
<meta
|
||||
property="al:android:app_name"
|
||||
content="Status — Ethereum. Anywhere"
|
||||
/>
|
||||
<meta
|
||||
property="article:publisher"
|
||||
content="https://www.facebook.com/ethstatus"
|
||||
/>
|
||||
{match(pathname)
|
||||
.with(
|
||||
P.when(p => p.startsWith('/insights')),
|
||||
() => (
|
||||
<>
|
||||
<link rel="icon" href="/assets/favicon/dev.png" />
|
||||
{/* <link rel="apple-touch-icon" href="/assets/favicon/dev.png" />
|
||||
<link
|
||||
rel="apple-touch-icon-precomposed"
|
||||
href="/assets/favicon/dev.png"
|
||||
/> */}
|
||||
</>
|
||||
)
|
||||
)
|
||||
.with(
|
||||
P.when(p => p.startsWith('/learn')),
|
||||
() => (
|
||||
<>
|
||||
<link rel="icon" href="/assets/favicon/learn.png" />
|
||||
{/* <link rel="apple-touch-icon" href="/assets/favicon/learn.png" />
|
||||
<link
|
||||
rel="apple-touch-icon-precomposed"
|
||||
href="/assets/favicon/learn.png"
|
||||
/> */}
|
||||
</>
|
||||
)
|
||||
)
|
||||
.with(
|
||||
P.when(p => p.startsWith('/blog')),
|
||||
() => (
|
||||
<>
|
||||
<link rel="icon" href="/assets/favicon/blog.png" />
|
||||
{/* <link rel="apple-touch-icon" href="/assets/favicon/blog.png" />
|
||||
<link
|
||||
rel="apple-touch-icon-precomposed"
|
||||
href="/assets/favicon/blog.png"
|
||||
/> */}
|
||||
</>
|
||||
)
|
||||
)
|
||||
.otherwise(() => (
|
||||
<>
|
||||
<link rel="icon" href="/assets/favicon/default.png" />
|
||||
{/* <link rel="apple-touch-icon" href="/assets/favicon/default.png" />
|
||||
<link
|
||||
rel="apple-touch-icon-precomposed"
|
||||
href="/assets/favicon/default.png"
|
||||
/> */}
|
||||
</>
|
||||
))}
|
||||
</Head>
|
||||
<div id="app" className={inter.variable + ' font-sans'}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider>{getLayout(<Component {...pageProps} />)}</ThemeProvider>
|
||||
<ThemeProvider>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|