solve conflicts

This commit is contained in:
marcelines 2023-06-27 12:08:52 +01:00
commit 0920018ac5
No known key found for this signature in database
GPG Key ID: 56B1E53E2A3F43C7
241 changed files with 7034 additions and 1025 deletions

119
.eslintrc
View File

@ -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

View File

@ -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

View File

@ -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)"
}
]
}

4
apps/mobile/.eslintrc Normal file
View File

@ -0,0 +1,4 @@
{
"root": true,
"extends": ["@status-im/eslint-config"]
}

View File

@ -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"
}
}

9
apps/web/.eslintrc Normal file
View File

@ -0,0 +1,9 @@
{
"root": true,
"extends": [
"@status-im/eslint-config",
"plugin:tailwindcss/recommended",
"next",
"next/core-web-vitals"
]
}

View File

@ -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"
}

9
apps/website/.eslintrc Normal file
View File

@ -0,0 +1,9 @@
{
"root": true,
"extends": [
"@status-im/eslint-config",
"plugin:tailwindcss/recommended",
"next",
"next/core-web-vitals"
]
}

6
apps/website/.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"semi": false,
"singleQuote": true,
"arrowParens": "avoid",
"tailwindConfig": "./tailwind.config.cjs"
}

12
apps/website/env.d.ts vendored Normal file
View File

@ -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 */
}
}

View File

@ -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,

View File

@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

File diff suppressed because one or more lines are too long

View File

@ -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 }

View File

@ -0,0 +1,2 @@
export { GridHero } from './grid-hero'
export { Section } from './section'

View File

@ -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 }

File diff suppressed because one or more lines are too long

View File

@ -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 }

View File

@ -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">

View File

@ -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 }

View File

@ -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>
)

View File

@ -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>
)
}

View File

@ -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>
)
}

View File

@ -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>
)
}

View File

@ -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 }

View File

@ -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 }

View File

@ -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')

View File

@ -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"

View File

@ -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 }

View File

@ -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)
// 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)
}
type Props = {
visible: boolean
}
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 }

View File

@ -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 }

View File

@ -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 }

View File

@ -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 }

View File

@ -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 }

View File

@ -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>
)
}

View File

@ -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 }

View File

@ -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?

View File

@ -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>

View File

@ -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

View File

@ -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>
)
}

View File

@ -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 Statuss 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: 'Thats 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>
)
}

View File

@ -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 }

View File

@ -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 }

View File

@ -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'

View File

@ -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 }

View File

@ -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 }

View File

@ -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,
})

View File

@ -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)

View File

@ -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,
},
}

View File

@ -2,4 +2,5 @@ export const ERROR_CODES = {
NOT_FOUND: 404,
INTERNAL_SERVER_ERROR: 500,
INVALID_PUBLIC_KEY: 601,
INVALID_ENS_NAME: 602,
}

View File

@ -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'
}
}

View File

@ -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
}

View File

@ -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 }

View File

@ -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,
}
}

View File

@ -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>
</>
)

View File

@ -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>
)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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"` },
})
}

View File

@ -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>
</>
)
}

Some files were not shown because too many files have changed in this diff Show More