Compare commits

..
Author SHA1 Message Date
Felicio Mununga 013039ff40 Update README.md 2023-06-27 15:47:09 +01:00
356 changed files with 14721 additions and 8801 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@status-im/js': patch
---
get peer based on environment
+5
View File
@@ -0,0 +1,5 @@
---
'@status-im/js': patch
---
use built-in crypto for pbkdf2
-1
View File
@@ -7,7 +7,6 @@ on:
types: [opened, synchronize]
env:
NEXT_PUBLIC_GHOST_API_URL: ''
NEXT_PUBLIC_GHOST_API_KEY: ''
INFURA_API_KEY: ''
TAMAGUI_TARGET: 'web'
+1 -13
View File
@@ -5,14 +5,6 @@ on:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
env:
NEXT_PUBLIC_GHOST_API_URL: ''
NEXT_PUBLIC_GHOST_API_KEY: ''
INFURA_API_KEY: ''
TAMAGUI_TARGET: 'web'
jobs:
release:
runs-on: ubuntu-latest
@@ -20,12 +12,8 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
node-version: 16
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn typecheck
- run: yarn lint && yarn format --check
- run: yarn test
- uses: changesets/action@v1
id: changesets
with:
-3
View File
@@ -104,6 +104,3 @@ web-build/
# Local
**/.data
# Contentlayer
.contentlayer
+42
View File
@@ -59,6 +59,48 @@
"smartStep": true,
"console": "integratedTerminal",
"sourceMaps": true
},
{
"name": "Launch Website via Next.js (server-side)",
"type": "node-terminal",
"request": "launch",
"cwd": "${workspaceFolder}/apps/website",
"command": "yarn dev"
},
{
"name": "Attach to Website via Next.js (client-side)",
"type": "chrome",
"request": "launch",
"webRoot": "${workspaceFolder}/apps/website",
"url": "http://localhost:3000",
"skipFiles": [
".next/**",
"${workspaceFolder}/node_modules/**",
"<node_internals>/**",
"**/webpack-internal://**"
],
"runtimeArgs": ["--auto-open-devtools-for-tabs"]
},
// todo: consider https://code.visualstudio.com/docs/editor/debugging#_compound-launch-configurations instead
// todo: consider client+prelaunch as full stack
// todo: consider workspaces https://code.visualstudio.com/docs/editor/multi-root-workspaces#_debugging
{
"name": "Launch Website via Next.js (full stack)",
"type": "node-terminal",
"request": "launch",
"cwd": "${workspaceFolder}/apps/website",
"command": "yarn dev -p 3000",
"serverReadyAction": {
"pattern": "started server on .+, url: (https?://.+)",
"action": "startDebugging",
"name": "Attach to Website via Next.js (client-side)",
"killOnServerStop": false
},
"skipFiles": [
".next/**",
"${workspaceFolder}/node_modules/**",
"<node_internals>/**"
]
}
]
}
+1 -5
View File
@@ -1,4 +1,4 @@
# Status Web 🌐
# Status Web
[![CI](https://github.com/status-im/status-web/actions/workflows/ci.yml/badge.svg)](https://github.com/status-im/status-web/actions/workflows/ci.yml)
@@ -125,7 +125,3 @@ And run it:
```sh
yarn workspace with-vite run dev
```
## Sponsors
This project is sponsored by Browserstack.
-9
View File
@@ -23,14 +23,5 @@
"@vitejs/plugin-react": "^3.1.0",
"typescript": "^5.0.3",
"vite": "^4.2.1"
},
"lint-staged": {
"*.{ts,tsx,js,jsx,mjs}": [
"eslint",
"prettier --write"
],
"*.{md,mdx,yml,yaml,json}": [
"prettier --write"
]
}
}
+1 -1
View File
@@ -34,7 +34,7 @@
"devDependencies": {
"@babel/core": "^7.17.9",
"@expo/metro-config": "^0.3.21",
"@tamagui/babel-plugin": "1.74.21",
"@tamagui/babel-plugin": "1.11.1",
"@types/react-native": "~0.70.6",
"babel-plugin-module-resolver": "^4.1.0",
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
+1 -4
View File
@@ -5,8 +5,5 @@
"plugin:tailwindcss/recommended",
"next",
"next/core-web-vitals"
],
"rules": {
"@next/next/no-html-link-for-pages": "off"
}
]
}
+3 -14
View File
@@ -13,30 +13,19 @@
"dependencies": {
"@status-im/components": "*",
"@status-im/icons": "*",
"@tamagui/core": "1.74.21",
"@tamagui/core": "1.11.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-native-web": "^0.18.12",
"use-resize-observer": "^9.1.0"
},
"devDependencies": {
"@status-im/eslint-config": "*",
"@tamagui/vite-plugin": "1.74.21",
"@tamagui/vite-plugin": "1.11.1",
"@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react-swc": "^3.2.0",
"next": "^13.4.12",
"tailwindcss": "^3.3.3",
"@status-im/eslint-config": "*",
"typescript": "^5.0.3",
"vite": "^4.2.1"
},
"lint-staged": {
"*.{ts,tsx,js,jsx,mjs}": [
"eslint",
"prettier --write"
],
"*.{md,mdx,yml,yaml,json}": [
"prettier --write"
]
}
}
+3
View File
@@ -0,0 +1,3 @@
IGNORE_TS_CONFIG_PATHS=true
TAMAGUI_TARGET=web
TAMAGUI_DISABLE_WARN_DYNAMIC_LOAD=1
+9
View File
@@ -0,0 +1,9 @@
{
"root": true,
"extends": [
"@status-im/eslint-config",
"plugin:tailwindcss/recommended",
"next",
"next/core-web-vitals"
]
}
+39
View File
@@ -0,0 +1,39 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
!.env.*
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
# Tamagui
.tamagui
+6
View File
@@ -0,0 +1,6 @@
{
"semi": false,
"singleQuote": true,
"arrowParens": "avoid",
"tailwindConfig": "./tailwind.config.cjs"
}
+12
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 */
}
}
+5
View File
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
+76
View File
@@ -0,0 +1,76 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable import/default */
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 = {
// output: 'export',
reactStrictMode: true,
typescript: {
ignoreBuildErrors: true,
},
images: {
// disableStaticImages: true,
},
transpilePackages: [
// 'react-native',
'react-native-web',
// 'expo-modules-core',
'expo-blur',
// '@status-im/components',
// '@status-im/js',
],
experimental: {
legacyBrowsers: false,
// esmExternals: 'loose',
scrollRestoration: true,
},
}
const plugins = [
withTamagui({
config: './tamagui.config.ts',
components: [
// fixme?: works without it
// '@status-im/icons',
'@status-im/components',
// './node_modules/@status-im/components/packages/components/dist',
],
importsWhitelist: ['constants.js', 'colors.js'],
logTimings: true,
disableExtraction: true,
// experiment - reduced bundle size react-native-web
useReactNativeWebLite: false,
shouldExtract: path => {
if (path.includes(join('packages', 'app'))) {
return true
}
},
excludeReactNativeWebExports: [
'Switch',
'ProgressBar',
'Picker',
'CheckBox',
'Touchable',
'Modal',
],
}),
]
export default () => {
for (const plugin of plugins) {
config = {
...config,
...plugin(config),
}
}
return config
}
+63
View File
@@ -0,0 +1,63 @@
{
"name": "website",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"typecheck": "tsc",
"clean": "rimraf .next .tamagui .turbo .vercel/output node_modules",
"preview": "next start --port 8151"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.0.3",
"@radix-ui/react-navigation-menu": "^1.1.2",
"@react-spring/web": "^9.7.2",
"@scure/base": "^1.1.1",
"@status-im/colors": "*",
"@status-im/components": "*",
"@status-im/icons": "*",
"@status-im/js": "*",
"@tamagui/next-theme": "1.11.1",
"@tanstack/react-query": "^4.29.7",
"@vercel/og": "^0.5.4",
"@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",
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-native-web": "^0.18.12",
"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",
"unified": "^10.1.2"
},
"engines": {
"node": "^18.x"
}
}
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
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: 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: 24 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.
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><g clip-path="url(#a)"><mask id="b" width="32" height="33" x="0" y="-1" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#fff" d="M16 0C4 0 0 4 0 16s4 16 16 16 16-4 16-16S28 0 16 0Z"/></mask><g mask="url(#b)"><g filter="url(#c)"><circle cx="23" cy="9" r="19" fill="#1992D7"/></g><g filter="url(#d)"><circle cx="33" cy="19" r="19" fill="#F6B03C"/></g><g filter="url(#e)"><circle cx="5" cy="31" r="19" fill="#FF7D46"/></g><g filter="url(#f)"><circle cx="-7" cy="9" r="19" fill="#7140FD"/></g><g fill="#fff" fill-rule="evenodd" clip-rule="evenodd" filter="url(#g)"><path d="M19.656 16.862c-.26 3.263-2.603 6.148-5.776 6.329-1.949.111-3.895-1.079-4-3.01-.082-1.556.88-2.69 2.56-3.125a6.04 6.04 0 0 1 1.212-.189c1.783-.1 2.9.308 4.683.207.411-.02.82-.082 1.219-.184.032-.009.07-.018.102-.028ZM12.352 15.137c.26-3.263 2.603-6.147 5.776-6.329 1.949-.11 3.895 1.08 4 3.011.085 1.555-.88 2.69-2.56 3.125a6.04 6.04 0 0 1-1.212.189c-1.783.1-2.9-.308-4.683-.209a6.24 6.24 0 0 0-1.219.184l-.102.03Z"/></g></g></g><defs><filter id="c" width="56.861" height="56.861" x="-5.43" y="-19.431" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92851" stdDeviation="4.715"/></filter><filter id="d" width="56.861" height="56.861" x="4.57" y="-9.431" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92851" stdDeviation="4.715"/></filter><filter id="e" width="56.861" height="56.861" x="-23.43" y="2.569" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92851" stdDeviation="4.715"/></filter><filter id="f" width="56.861" height="56.861" x="-35.43" y="-19.431" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92851" stdDeviation="4.715"/></filter><filter id="g" width="72.456" height="74.596" x="-20.224" y="-13.271" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="8.026"/><feGaussianBlur stdDeviation="15.05"/><feColorMatrix 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 in2="BackgroundImageFix" result="effect1_dropShadow_718_92851"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_718_92851" result="shape"/></filter><clipPath id="a"><path fill="#fff" d="M0 0h32v32H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><g clip-path="url(#a)"><mask id="b" width="32" height="33" x="0" y="-1" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#fff" d="M16 0C4 0 0 4 0 16s4 16 16 16 16-4 16-16S28 0 16 0Z"/></mask><g mask="url(#b)"><g filter="url(#c)"><circle cx="23" cy="9" r="19" fill="#1992D7"/></g><g filter="url(#d)"><circle cx="33" cy="19" r="19" fill="#F6B03C"/></g><g filter="url(#e)"><circle cx="5" cy="31" r="19" fill="#FF7D46"/></g><g filter="url(#f)"><circle cx="-7" cy="9" r="19" fill="#7140FD"/></g><g fill="#fff" fill-rule="evenodd" clip-rule="evenodd" filter="url(#g)"><path d="M19.656 16.862c-.26 3.263-2.603 6.148-5.776 6.329-1.949.111-3.895-1.079-4-3.01-.082-1.556.88-2.69 2.56-3.125a6.04 6.04 0 0 1 1.212-.189c1.783-.1 2.9.308 4.683.207.411-.02.82-.082 1.219-.184.032-.009.07-.018.102-.028ZM12.352 15.137c.26-3.263 2.603-6.147 5.776-6.329 1.949-.11 3.895 1.08 4 3.011.085 1.555-.88 2.69-2.56 3.125a6.04 6.04 0 0 1-1.212.189c-1.783.1-2.9-.308-4.683-.209a6.24 6.24 0 0 0-1.219.184l-.102.03Z"/></g></g></g><defs><filter id="c" width="56.861" height="56.861" x="-5.43" y="-19.431" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92851" stdDeviation="4.715"/></filter><filter id="d" width="56.861" height="56.861" x="4.57" y="-9.431" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92851" stdDeviation="4.715"/></filter><filter id="e" width="56.861" height="56.861" x="-23.43" y="2.569" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92851" stdDeviation="4.715"/></filter><filter id="f" width="56.861" height="56.861" x="-35.43" y="-19.431" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92851" stdDeviation="4.715"/></filter><filter id="g" width="72.456" height="74.596" x="-20.224" y="-13.271" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="8.026"/><feGaussianBlur stdDeviation="15.05"/><feColorMatrix 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 in2="BackgroundImageFix" result="effect1_dropShadow_718_92851"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_718_92851" result="shape"/></filter><clipPath id="a"><path fill="#fff" d="M0 0h32v32H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><g clip-path="url(#a)"><mask id="b" width="32" height="32" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#fff" d="M16 0C4 0 0 4 0 16s4 16 16 16 16-4 16-16S28 0 16 0Z"/></mask><g mask="url(#b)"><g filter="url(#c)"><circle cx="23" cy="9" r="19" fill="#2A4AF5" fill-opacity=".4"/></g><g filter="url(#d)"><circle cx="33" cy="19" r="19" fill="#2A4AF5" fill-opacity=".4"/></g><g filter="url(#e)"><circle cx="5" cy="31" r="19" fill="#2A4AF5" fill-opacity=".4"/></g><g filter="url(#f)"><circle cx="-7" cy="9" r="19" fill="#2A4AF5" fill-opacity=".4"/></g><g stroke="#fff" stroke-width=".5" filter="url(#g)"><path d="M19.532 16.863c-.259 3.253-2.59 6.13-5.75 6.31-1.939.111-3.876-1.075-3.98-3.001-.081-1.55.876-2.682 2.547-3.116.395-.103.8-.166 1.207-.188 1.775-.1 2.886.308 4.661.207.41-.021.816-.083 1.213-.184.032-.009.07-.018.102-.028ZM12.266 15.143c.259-3.254 2.591-6.13 5.75-6.31 1.939-.111 3.876 1.075 3.98 3.001.085 1.55-.876 2.682-2.547 3.116a6 6 0 0 1-1.207.189c-1.775.1-2.886-.308-4.661-.208-.41.02-.816.082-1.213.183l-.102.03Z" clip-rule="evenodd"/><path d="M16 1C4.75 1 1 4.75 1 16s3.75 15 15 15 15-3.75 15-15S27.25 1 16 1Z" opacity=".5"/></g></g></g><defs><filter id="c" width="58" height="58" x="-6" y="-20" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92063" stdDeviation="5"/></filter><filter id="d" width="58" height="58" x="4" y="-10" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92063" stdDeviation="5"/></filter><filter id="e" width="58" height="58" x="-24" y="2" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92063" stdDeviation="5"/></filter><filter id="f" width="58" height="58" x="-36" y="-20" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_92063" stdDeviation="5"/></filter><filter id="g" width="90.5" height="90.5" x="-29.25" y="-21.25" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="8"/><feGaussianBlur stdDeviation="15"/><feColorMatrix 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 in2="BackgroundImageFix" result="effect1_dropShadow_718_92063"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_718_92063" result="shape"/></filter><clipPath id="a"><path fill="#fff" d="M0 0h32v32H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><g clip-path="url(#a)"><mask id="b" width="32" height="33" x="0" y="-1" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#fff" d="M16 0C4 0 0 4 0 16s4 16 16 16 16-4 16-16S28 0 16 0Z"/></mask><g mask="url(#b)"><g filter="url(#c)"><circle cx="23" cy="9" r="19" fill="#EC266C"/></g><g filter="url(#d)"><circle cx="33" cy="19" r="19" fill="#F66F8F"/></g><g filter="url(#e)"><circle cx="5" cy="31" r="19" fill="#EC266C"/></g><g filter="url(#f)"><circle cx="-7" cy="9" r="19" fill="#7140FD"/></g><g filter="url(#g)"><path fill="#fff" d="M23.787 15.315c-.223 3.894-3.781 6.765-7.792 7.085-4.01-.32-7.568-3.19-7.79-7.085-.137-2.392 1.325-4.78 3.7-4.91 1.91-.1 3.304 1.082 3.839 3.142.127.487.205.985.232 1.488.006.102.01.203.013.302.011-.096.02-.197.026-.302a7.399 7.399 0 0 1 .232-1.488c.534-2.06 1.929-3.241 3.84-3.141 2.374.129 3.836 2.517 3.7 4.909Z"/></g></g></g><defs><filter id="c" width="63.147" height="63.147" x="-8.574" y="-22.574" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_91701" stdDeviation="6.287"/></filter><filter id="d" width="63.147" height="63.147" x="1.426" y="-12.574" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_91701" stdDeviation="6.287"/></filter><filter id="e" width="63.147" height="63.147" x="-26.574" y="-.574" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_91701" stdDeviation="6.287"/></filter><filter id="f" width="63.147" height="63.147" x="-38.574" y="-22.574" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_718_91701" stdDeviation="6.287"/></filter><filter id="g" width="75.602" height="72" x="-21.805" y="-11.6" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="8"/><feGaussianBlur stdDeviation="15"/><feColorMatrix 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 in2="BackgroundImageFix" result="effect1_dropShadow_718_91701"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_718_91701" result="shape"/></filter><clipPath id="a"><path fill="#fff" d="M0 0h32v32H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 2.9 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

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

@@ -0,0 +1,60 @@
import { Text } from '@status-im/components'
import { ChevronRightIcon } from '@status-im/icons'
import { useRouter } from 'next/router'
import { Link } from '../link'
import { formatSegment } from './format-segment'
type Props = {
cutFirstSegment?: boolean
}
const Breadcrumbs = (props: Props) => {
const { cutFirstSegment = true } = props
const router = useRouter()
const { asPath } = router
// Splits the current path into segments
const segments = asPath.split('/').filter(segment => segment !== '')
return (
<nav className="flex items-center space-x-1">
{segments.map((segment, index) => {
// Builds the path up to the current segment
const path = `/${segments.slice(0, index + 1).join('/')}`
// Determines if the current segment is the last one
const isLastSegment = index === segments.length - 1
// If the first segment should be cut, skip it
if (cutFirstSegment && index === 0) {
return null
}
return (
<div className="flex flex-row items-center" key={segment}>
{/* Always render the first chevron unless cutFirstSegment is true and we need to avoid render the chevron before */}
{!cutFirstSegment || (cutFirstSegment && index > 1) ? (
<ChevronRightIcon size={20} color="$neutral-40" />
) : null}
{isLastSegment ? (
<Text size={15} color="$neutral-50" weight="medium">
{formatSegment(segment)}
</Text>
) : (
<Link href={path}>
<Text size={15} weight="medium">
{formatSegment(segment)}
</Text>
</Link>
)}
</div>
)
})}
</nav>
)
}
export { Breadcrumbs }
@@ -0,0 +1,15 @@
/**
* Util function that formats a segment for the breadcrumbs
* @param segment - string to be formatted
* formatSegment @returns a formatted string with the first letter of each word capitalized and spaces instead of "-"
**/
const formatSegment = (segment: string): string => {
// Replaces "-" with a space and capitalize the first letter of each word
const words = segment.split('-')
const formattedSegment = words
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
return formattedSegment
}
export { formatSegment }
File diff suppressed because one or more lines are too long
@@ -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,47 @@
import { Stack } from '@tamagui/core'
import { ParentSize } from '@visx/responsive'
import { ChartComponent, Empty, Loading } from './components'
type DayType = {
date: string
open_issues: number
closed_issues: number
}
interface Props {
data: DayType[]
height?: number
isLoading?: boolean
}
const Chart = (props: Props) => {
const { data, isLoading, ...rest } = props
if (isLoading) {
return (
<Stack width="100%" height={rest.height}>
<Loading />
</Stack>
)
}
if (!data.length) {
return (
<Stack width="100%" height={rest.height}>
<Empty />
</Stack>
)
}
return (
<ParentSize style={{ maxHeight: 326 }}>
{({ width }) => {
return <ChartComponent {...rest} data={data} width={width} />
}}
</ParentSize>
)
}
export { Chart }
export type { Props as ChartProps, DayType }
@@ -0,0 +1,98 @@
import { animated } from '@react-spring/web'
import { curveMonotoneX } from '@visx/curve'
import { LinearGradient } from '@visx/gradient'
import { AreaClosed } from '@visx/shape'
import { colors } from './chart-component'
import type { SpringValue } from '@react-spring/web'
import type { ScaleLinear, ScaleTime } from 'd3-scale'
type Datum = {
date: Date
value: number
}
type Props = {
yScale: ScaleLinear<number, number, never>
xScale: ScaleTime<number, number, never>
totalIssuesData: Datum[]
closedIssuesData: Datum[]
clipPathAnimation: {
clipPath: SpringValue<string>
}
}
const AnimatedAreaClosed = animated(AreaClosed)
const Areas = (props: Props) => {
const {
clipPathAnimation,
closedIssuesData,
totalIssuesData,
xScale,
yScale,
} = props
return (
<>
<LinearGradient
id="gradient-columns"
from={'rgba(255, 255, 255, 1)'}
to={'rgba(255, 255, 255, 0)'}
fromOpacity={1}
toOpacity={0}
/>
<LinearGradient
id="gradient"
from={colors.total}
to={colors.background}
fromOpacity={0.1}
toOpacity={0}
/>
<LinearGradient
id="gradient-open"
from={colors.closed}
to={colors.background}
fromOpacity={0.1}
toOpacity={0}
/>
{/* Total issues area */}
<AnimatedAreaClosed
data={totalIssuesData}
x={d => {
const datum = d as Datum
return xScale(datum.date)
}}
y={d => {
const datum = d as Datum
return yScale(datum.value)
}}
yScale={yScale}
fill="url(#gradient)"
curve={curveMonotoneX}
style={{ ...clipPathAnimation, zIndex: 10 }}
/>
{/* Closed issues area */}
<AnimatedAreaClosed
data={closedIssuesData}
x={d => {
const datum = d as Datum
return xScale(datum.date)
}}
y={d => {
const datum = d as Datum
return yScale(datum.value)
}}
yScale={yScale}
fill="url(#gradient-open)"
curve={curveMonotoneX}
style={{ ...clipPathAnimation, zIndex: 10 }}
/>
</>
)
}
export { Areas }
@@ -0,0 +1,2 @@
export { LineA } from './line-a'
export { LineB } from './line-b'
@@ -0,0 +1,15 @@
const LineA = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1066 104" fill="none">
<path
stroke="#F5F6F8"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m1 103 48.375-3a533.063 533.063 0 0 0 95.371-14.666v0a533.09 533.09 0 0 1 100.856-14.978L291.182 68l96.727-16.5 96.727-6.5 96.728-12 96.727-6 96.727-6 96.727-8h96.728L1065 1"
/>
</svg>
)
}
export { LineA }
@@ -0,0 +1,15 @@
const LineB = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1066 154" fill="none">
<path
stroke="#F5F6F8"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m1 153 30.72-1.906a358.367 358.367 0 0 0 126.206-31.478v0a358.437 358.437 0 0 1 75.827-24.742l11.248-2.325A460.974 460.974 0 0 1 338.339 83h146.297l96.728-10v0a638.4 638.4 0 0 0 189.113-28.654L774.818 43l51.57-10.663A450.746 450.746 0 0 1 917.658 23v0c33.638 0 67.169-3.765 99.972-11.225L1065 1"
/>
</svg>
)
}
export { LineB }
@@ -0,0 +1,235 @@
import { useMemo } from 'react'
import { animated } from '@react-spring/web'
import { Axis } from '@visx/axis'
import { GridColumns } from '@visx/grid'
import { Group } from '@visx/group'
import { scaleLinear, scaleTime } from '@visx/scale'
import { extent, max } from 'd3-array'
import { getClosedIssues, getTotalIssues } from '../helpers/get-data'
import { useAnimations, useChartTooltip } from '../hooks/'
import { format } from '../utils/format-time'
import { Areas } from './areas'
import { ChartTooltip } from './chart-tooltip'
import { Lines } from './lines'
import { Markers } from './markers'
import type { DayType } from '../chart'
// defining colors
export const colors = {
total: '#E95460',
closed: '#23ADA0',
background: '#F0F2F5',
marker: '#09101C',
totalGradient: 'rgba(233, 84, 96, 0.3)',
closedGradient: 'rgba(35, 173, 160, 0.3)',
white: '#FFF',
} as const
interface Props {
data: DayType[]
height?: number
width?: number
}
const AnimatedGridColumns = animated(GridColumns)
const ChartComponent = (props: Props): JSX.Element => {
const { data, width: defaultWidth, height: defaultHeight } = props
// Extract dates, open issues, and closed issues from data
const dates = data.map(d => new Date(d.date))
// Define dimensions
const height = defaultHeight || 300
const width = defaultWidth || 300
const margin = { top: 20, right: 0, bottom: 30, left: 26 }
const innerWidth = (width || 0) - margin.left - margin.right
const innerHeight = height - margin.top - margin.bottom
const filteredDates = dates.filter(Boolean) // filters out undefined values
// Calculate total issues by summing open and closed issues
const totalIssues = data.map(d => d.open_issues + d.closed_issues)
const xDomain = extent(filteredDates) as [Date, Date]
const xScale = scaleTime({
domain: xDomain,
range: [0, innerWidth],
})
const numTicksBottom = Math.min(data.length, 12)
const yScale = scaleLinear({
domain: [0, max(totalIssues) || 0],
range: [innerHeight, 0],
nice: true,
})
const { tooltipData, updateTooltip, hideTooltip, tooltipOpen } =
useChartTooltip({
data,
margin,
dates,
innerWidth,
})
const {
circleSpringClosed,
circleSpringTotal,
clipPathAnimation,
drawingGridColumns,
drawingLineStyle,
opacityAnimation,
tooltipAnimation,
} = useAnimations({
data,
margin,
innerHeight,
tooltipData,
yScale,
xScale,
})
// Convert data to array of objects with `date` and `value` properties
const totalIssuesData = useMemo(
() =>
data.map(d => ({
date: new Date(d.date),
value: getTotalIssues(d),
})),
[data]
)
const closedIssuesData = useMemo(
() =>
data.map(d => ({
date: new Date(d.date),
value: getClosedIssues(d),
})),
[data]
)
return (
<div>
<svg width={width} height={height} overflow="visible">
<Group left={margin.left} top={margin.top}>
{/* x-axis */}
<Axis
top={innerHeight}
scale={xScale}
hideTicks
hideAxisLine
hideZero
numTicks={numTicksBottom}
tickLabelProps={(_, index) => {
const textAnchor =
index === 0
? 'start'
: index === data.length - 1
? 'end'
: 'middle'
return {
dy: '.33em',
fill: '#A1ABBD',
fontFamily: 'Menlo',
fontSize: 11,
textAnchor,
}
}}
tickFormat={value => {
if (typeof value === 'number') {
return value.toString() // Handle number values
} else if (value instanceof Date) {
// Specify the desired date format
return format(value) // Handle date values
} else {
return ''
}
}}
/>
{/* y-axis */}
<Axis
orientation="left"
scale={yScale}
hideTicks
hideAxisLine
numTicks={6}
tickComponent={({ formattedValue, ...tickProps }) => (
<text
{...tickProps}
fill="#A1ABBD"
fontSize={11}
fontFamily="Menlo"
textAnchor="middle"
dx="-1em"
>
{formattedValue}
</text>
)}
/>
<AnimatedGridColumns
scale={xScale}
height={innerHeight}
style={drawingGridColumns}
numTicks={numTicksBottom}
/>
<rect
x={0}
y={0}
width={innerWidth}
height={innerHeight}
fill="url(#gradient-columns)"
/>
<Lines
closedIssuesData={closedIssuesData}
drawingLineStyle={drawingLineStyle}
totalIssuesData={totalIssuesData}
xScale={xScale}
yScale={yScale}
/>
<Areas
clipPathAnimation={clipPathAnimation}
closedIssuesData={closedIssuesData}
totalIssuesData={totalIssuesData}
xScale={xScale}
yScale={yScale}
/>
{tooltipOpen && (
<Markers
circleSpringClosed={circleSpringClosed}
circleSpringTotal={circleSpringTotal}
innerHeight={innerHeight}
opacityAnimation={opacityAnimation}
/>
)}
<rect
x={0}
y={0}
width={innerWidth}
height={innerHeight}
onTouchStart={updateTooltip}
fill="transparent"
onTouchMove={updateTooltip}
onMouseMove={updateTooltip}
onMouseLeave={() => hideTooltip()}
/>
</Group>
</svg>
{/* render a tooltip */}
{tooltipOpen && (
<ChartTooltip
tooltipAnimation={tooltipAnimation}
opacityAnimation={opacityAnimation}
tooltipData={tooltipData}
/>
)}
</div>
)
}
export { ChartComponent }
@@ -0,0 +1,115 @@
import { animated } from '@react-spring/web'
import { Text } from '@status-im/components'
import { DoneIcon, OpenIcon } from '@status-im/icons'
import { Stack } from '@tamagui/core'
import { defaultStyles, TooltipWithBounds } from '@visx/tooltip'
import type { TooltipData } from '../hooks/use-chart-tooltip'
import type { SpringValue } from '@react-spring/web'
const AnimatedTooltip = animated(TooltipWithBounds)
type Props = {
tooltipData: TooltipData
opacityAnimation: {
opacity: SpringValue<number>
}
tooltipAnimation: {
x: SpringValue<number>
y: SpringValue<number>
}
}
// defining tooltip styles
const tooltipStyles: React.CSSProperties = {
...defaultStyles,
minWidth: 272,
padding: 12,
backgroundColor: '#FFF',
border: '1px solid #F0F2F5',
boxShadow: '0px 2px 20px rgba(9, 16, 28, 0.04)',
borderRadius: 20,
marginLeft: 25,
}
const ChartTooltip = (props: Props) => {
const { tooltipData, opacityAnimation, tooltipAnimation } = props
return (
<AnimatedTooltip
top={tooltipAnimation.y}
left={tooltipAnimation.x}
style={{ ...tooltipStyles, opacity: opacityAnimation.opacity }}
className="rounded-2xl"
>
<Stack flexDirection="row" alignItems="center">
<Text size={19} weight="semibold">
{tooltipData.totalIssues}
</Text>
<Stack ml={3} alignItems="center">
<Text size={15} weight="medium">
issues
</Text>
</Stack>
</Stack>
<Stack pb={12}>
<Text size={13} weight="medium" color="$neutral-50">
{tooltipData.formattedDate}
</Text>
</Stack>
<Stack borderRadius="$8" backgroundColor="$danger-50-opa-10">
<Stack
animation="slow"
height={8}
backgroundColor="$success-50"
width={`${tooltipData.completedIssuesPercentage}%`}
borderRadius="$8"
/>
</Stack>
<Stack flexDirection="row" alignItems="center" pt={18}>
<OpenIcon size={16} color="$neutral-40" />
<Stack px={4}>
<Text size={13} weight="medium">
{tooltipData.openIssues} open
</Text>
</Stack>
<Stack
backgroundColor="$danger-50-opa-30"
borderRadius="$20"
px={6}
py={2}
minWidth={36}
justifyContent="center"
alignItems="center"
>
<Text size={11} weight="medium" color="$danger-50">
{`${tooltipData.openIssuesPercentage}%`}
</Text>
</Stack>
</Stack>
<Stack flexDirection="row" alignItems="center" pt={8}>
<DoneIcon size={16} color="$neutral-40" />
<Stack px={4}>
<Text size={13} weight="medium">
{tooltipData.closedIssues} closes
</Text>
</Stack>
<Stack
minWidth={36}
backgroundColor="$success-50-opa-30"
borderRadius="$20"
px={6}
py={2}
justifyContent="center"
alignItems="center"
>
<Text size={11} weight="medium" color="$success-50">
{`${tooltipData.completedIssuesPercentage}%`}
</Text>
</Stack>
</Stack>
</AnimatedTooltip>
)
}
export { ChartTooltip }
@@ -0,0 +1,48 @@
import { Image, Text } from '@status-im/components'
import { LineA, LineB } from './assets'
const Empty = () => {
return (
<div className="flex h-full w-full flex-col items-center justify-center">
<div className="relative flex h-full w-full items-center justify-center">
<div className="relative z-10 flex flex-col items-center justify-center">
<Image src={'/assets/chart/empty.png'} width={80} height={80} />
<div className="pb-3" />
<Text size={15} weight="semibold">
No results found
</Text>
<div className="pb-1" />
<Text size={13} color="$neutral-50">
Try adjusting your search or filter to find what youre looking for.
</Text>
</div>
<div className="left-50 absolute top-16 w-full opacity-60">
<LineA />
</div>
<div className="absolute left-0 top-28 w-full opacity-60">
<LineB />
</div>
<div
className="absolute left-0 top-0 h-24 w-full"
style={{
background: `linear-gradient(180deg, white, transparent)`,
}}
/>
<div className="absolute flex h-full w-full justify-between opacity-80">
{Array.from({ length: 12 }).map((_, i) => (
<div
className="h-full w-1"
style={{
borderLeft: '1px dashed #F0F2F5',
}}
key={i}
/>
))}
</div>
</div>
</div>
)
}
export { Empty }
@@ -0,0 +1,3 @@
export { ChartComponent } from './chart-component'
export { Empty } from './empty'
export { Loading } from './loading'
@@ -0,0 +1,73 @@
import { animated } from '@react-spring/web'
import { curveMonotoneX } from '@visx/curve'
import { LinePath } from '@visx/shape'
import { colors } from './chart-component'
import type { SpringValue } from '@react-spring/web'
import type { ScaleLinear, ScaleTime } from 'd3-scale'
type Datum = {
date: Date
value: number
}
type Props = {
yScale: ScaleLinear<number, number, never>
xScale: ScaleTime<number, number, never>
totalIssuesData: Datum[]
closedIssuesData: Datum[]
drawingLineStyle: {
strokeDasharray: SpringValue<string>
}
}
const AnimatedLinePath = animated(LinePath)
const Lines = (props: Props) => {
const {
closedIssuesData,
drawingLineStyle,
xScale,
yScale,
totalIssuesData,
} = props
return (
<>
{/* Total issues line */}
<AnimatedLinePath
data={totalIssuesData}
x={d => {
const datum = d as Datum
return xScale(datum.date)
}}
y={d => {
const datum = d as Datum
return yScale(datum.value)
}}
stroke={colors.total}
strokeWidth={2}
curve={curveMonotoneX}
style={drawingLineStyle}
/>
{/* Closed issues line */}
<AnimatedLinePath
data={closedIssuesData}
x={d => {
const datum = d as Datum
return xScale(datum.date)
}}
y={d => {
const datum = d as Datum
return yScale(datum.value)
}}
stroke={colors.closed}
strokeWidth={2}
curve={curveMonotoneX}
style={drawingLineStyle}
/>
</>
)
}
export { Lines }
@@ -0,0 +1,39 @@
import { Skeleton } from '@status-im/components'
const Loading = () => {
return (
<div className="flex h-full w-full flex-col items-center justify-center">
<div className="relative flex h-full w-full">
<div
className="absolute left-0 top-0 h-24 w-full"
style={{
background: `linear-gradient(180deg, white, transparent)`,
}}
/>
<div className="flex h-full w-10 flex-col justify-between ">
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={i} width={20} height={12} />
))}
</div>
<div className="flex h-full w-full justify-between pb-4">
{Array.from({ length: 12 }).map((_, i) => (
<div
className="h-full w-1"
style={{
borderLeft: '1px dashed #F0F2F5',
}}
key={i}
/>
))}
</div>
</div>
<div className="flex w-full flex-row justify-between pl-9">
{Array.from({ length: 12 }).map((_, i) => (
<Skeleton key={i} width={32} height={12} />
))}
</div>
</div>
)
}
export { Loading }
@@ -0,0 +1,74 @@
import { animated } from '@react-spring/web'
import { GlyphCircle } from '@visx/glyph'
import { Group } from '@visx/group'
import { Line } from '@visx/shape'
import { colors } from './chart-component'
import type { SpringValue } from '@react-spring/web'
type Props = {
innerHeight: number
circleSpringTotal: {
x: SpringValue<number>
y: SpringValue<number>
}
opacityAnimation: {
opacity: SpringValue<number>
}
circleSpringClosed: {
x: SpringValue<number>
y: SpringValue<number>
}
}
const AnimatedCircle = animated(GlyphCircle)
const AnimatedLine = animated(Line)
const AnimatedGroup = animated(Group)
const Markers = (props: Props) => {
const {
innerHeight,
circleSpringTotal,
opacityAnimation,
circleSpringClosed,
} = props
return (
<>
<AnimatedGroup
top={0}
left={circleSpringTotal.x}
opacity={opacityAnimation.opacity}
>
<AnimatedLine
from={{ x: 0, y: innerHeight }}
to={{ x: 0, y: 0 }}
stroke={colors.marker}
strokeWidth={1}
pointerEvents="none"
/>
</AnimatedGroup>
<AnimatedCircle
size={90}
top={circleSpringTotal.y}
left={circleSpringTotal.x}
strokeWidth={1}
stroke={colors.background}
fill={colors.marker}
opacity={opacityAnimation.opacity}
/>
<AnimatedCircle
size={90}
top={circleSpringClosed.y}
left={circleSpringClosed.x}
strokeWidth={1}
stroke={colors.background}
fill={colors.marker}
opacity={opacityAnimation.opacity}
/>
</>
)
}
export { Markers }
@@ -0,0 +1,15 @@
import type { DayType } from '../chart'
/**
* Gets the total issues of a day
* @param d - the day to get the total issues
* @returns the total issues of a day
**/
export const getTotalIssues = (d: DayType) => d?.open_issues + d?.closed_issues
/**
* Gets the closed issues of a day
* @param d - the day to get the closes issues
* @returns the total issues of a day
**/
export const getClosedIssues = (d: DayType) => d?.closed_issues
@@ -0,0 +1,2 @@
export { useAnimations } from './use-animations'
export { useChartTooltip } from './use-chart-tooltip'
@@ -0,0 +1,99 @@
import { config, useSpring } from '@react-spring/web'
import type { DayType } from '../chart'
import type { TooltipData } from './use-chart-tooltip'
import type { ScaleLinear, ScaleTime } from 'd3-scale'
type Props = {
data: DayType[]
yScale: ScaleLinear<number, number, never>
xScale: ScaleTime<number, number, never>
margin: {
top: number
right: number
bottom: number
left: number
}
innerHeight: number
tooltipData: TooltipData
}
/**
* An custom hook that handles animations logic
* @param data - the data to be used in the chart
* @param margin - the margin of the chart
* @param dates - the dates of the chart
* @param innerHeight - the inner height of the chart
* @param xScale - the xScale of the chart
* @param yScale - the yScale of the chart
* @param tooltipData - the data to be used in the tooltip
* @returns circleSpringTotal - the spring animation for the total issues circle
* @returns circleSpringClosed - the spring animation for the closed issues circle
* @returns opacityAnimation - the spring animation for the some chart elements opacity
* @returns tooltipAnimation - the spring animation for the tooltip
* @returns drawingLineStyle - the spring animation for the drawing line style
* @returns drawingGridColumns - the spring animation for the drawing grid columns
* @returns clipPathAnimation - the spring animation for the clip path
**/
const useAnimations = (props: Props) => {
const { data, margin, innerHeight, tooltipData, xScale, yScale } = props
// Define spring for circle position
const circleSpringTotal = useSpring({
x: xScale(new Date(tooltipData?.date || '')),
y: yScale(tooltipData?.totalIssues),
config: config.gentle,
})
const circleSpringClosed = useSpring({
x: xScale(new Date(tooltipData?.date)),
y: yScale(tooltipData?.closedIssues),
config: config.gentle,
})
const opacityAnimation = useSpring({
opacity: data ? 1 : 0,
config: config.gentle,
delay: 200,
})
const tooltipAnimation = useSpring({
x: xScale(new Date(tooltipData?.date)),
y: innerHeight + margin.top + margin.bottom,
config: config.gentle,
})
const [drawingLineStyle] = useSpring(() => ({
from: { strokeDasharray: `${3000}, ${0}` },
to: { strokeDasharray: `${0}, ${3000}` },
reverse: true,
config: { duration: 2000 },
}))
const [drawingGridColumns] = useSpring(() => ({
from: { strokeDasharray: `0, 150` },
to: { strokeDasharray: '4,2' },
config: { duration: 250 },
}))
const [clipPathAnimation] = useSpring(() => ({
from: { clipPath: 'inset(0 100% 0 0)' },
to: { clipPath: 'inset(0 0 0 0)' },
config: { duration: 800 },
}))
return {
circleSpringTotal,
circleSpringClosed,
opacityAnimation,
tooltipAnimation,
drawingLineStyle,
drawingGridColumns,
clipPathAnimation,
}
}
export { useAnimations }
@@ -0,0 +1,129 @@
import { useCallback } from 'react'
import { localPoint } from '@visx/event'
import { scaleTime } from '@visx/scale'
import { useTooltip } from '@visx/tooltip'
import { bisector, extent } from 'd3-array'
import { getClosedIssues, getTotalIssues } from '../helpers/get-data'
import { formatDate } from '../utils/format-time'
import { getPercentage } from '../utils/get-percentage'
import type { DayType } from '../chart'
import type { EventType } from '@visx/event/lib/types'
type Props = {
data: DayType[]
dates: Date[]
innerWidth: number
margin: {
top: number
right: number
bottom: number
left: number
}
}
type TooltipData = {
date: string
completedIssuesPercentage: number
openIssuesPercentage: number
totalIssues: number
openIssues: number
closedIssues: number
formattedDate: string
}
/**
* An custom hook that handles the tooltip logic
* @param data - the data to be used in the chart
* @param margin - the margin of the chart
* @param dates - the dates of the chart
* @param innerWidth - the inner width of the chart
* @returns tooltipData - the data to be used in the tooltip
* @returns updateTooltip - a function that updates the tooltip
* @returns hideTooltip - a function that hides the tooltip
* @returns tooltipOpen - a boolean that indicates if the tooltip is open
**/
const useChartTooltip = (props: Props) => {
const { data, margin, dates, innerWidth } = props
// tooltip parameters
const { tooltipData, showTooltip, hideTooltip, tooltipOpen } =
useTooltip<TooltipData>({
tooltipData: {
date: '',
completedIssuesPercentage: 0,
openIssuesPercentage: 0,
totalIssues: 0,
openIssues: 0,
closedIssues: 0,
formattedDate: '',
},
})
const filteredDates = dates.filter(Boolean) // filters out undefined values
const getDate = useCallback((d: DayType) => new Date(d?.date), [])
const bisectDate = bisector((d: DayType) => new Date(d?.date)).left
const xDomain = extent(filteredDates) as [Date, Date]
const xScale = scaleTime({
domain: xDomain,
range: [0, innerWidth],
})
// Define tooltip handler
const updateTooltip = useCallback(
(event: EventType) => {
const { x } = localPoint(event) || { x: 0 }
const x0 = xScale.invert(x - margin.left)
const index = bisectDate(data, x0, 1)
const d0 = data[index - 1]
const d1 = data[index]
let d = d0
if (d1 && getDate(d1)) {
d =
x0.valueOf() - getDate(d0).valueOf() >
getDate(d1).valueOf() - x0.valueOf()
? d1
: d0
}
const closedIssues = getClosedIssues(d)
const totalIssues = getTotalIssues(d)
const openIssues = totalIssues - closedIssues
const completedIssuesPercentage = getPercentage(closedIssues, totalIssues)
const openIssuesPercentage = getPercentage(openIssues, totalIssues)
showTooltip({
tooltipData: {
date: d?.date,
formattedDate: formatDate(getDate(d)),
completedIssuesPercentage,
openIssuesPercentage,
closedIssues,
totalIssues,
openIssues,
},
})
},
[xScale, margin.left, bisectDate, data, getDate, showTooltip]
)
return {
tooltipData: tooltipData!,
hideTooltip,
updateTooltip,
tooltipOpen,
}
}
export { useChartTooltip }
export type { TooltipData, Props as UseChartTooltipProps }
@@ -0,0 +1,13 @@
import { timeFormat } from 'd3-time-format'
/**
* Util functions that formats a date
* formatDate @returns a string with the format 'MMM dd yyyy' - Ex: Jan 01 2021
**/
export const formatDate = timeFormat('%b %d %Y')
/**
* Util functions that formats a date
* format @returns a string with the format 'dd MMM' - Ex: 01 Jan
**/
export const format = timeFormat('%d %b')
@@ -0,0 +1,8 @@
/**
* An util function that gets the percentage of a value from a total
* @param value - the value to calculate the percentage
* @param total - the total value
**/
export const getPercentage = (value: number, total: number): number =>
Math.round((value / total) * 100)
File diff suppressed because one or more lines are too long
@@ -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 }
@@ -0,0 +1,112 @@
import { useEffect, useState } from 'react'
import { Tag, Text } from '@status-im/components'
import { OpenIcon } from '@status-im/icons'
import { Chart } from './chart/chart'
const DATA = [
{
date: '2022-01-25',
open_issues: 100,
closed_issues: 2,
},
{
date: '2022-01-26',
open_issues: 100,
closed_issues: 10,
},
{
date: '2022-01-27',
open_issues: 100,
closed_issues: 20,
},
{
date: '2022-01-28',
open_issues: 90,
closed_issues: 30,
},
{
date: '2022-01-29',
open_issues: 80,
closed_issues: 40,
},
{
date: '2022-01-30',
open_issues: 40,
closed_issues: 80,
},
{
date: '2022-01-31',
open_issues: 30,
closed_issues: 90,
},
{
date: '2022-02-01',
open_issues: 25,
closed_issues: 95,
},
{
date: '2022-02-02',
open_issues: 20,
closed_issues: 98,
},
{
date: '2022-02-03',
open_issues: 10,
closed_issues: 130,
},
]
type Props = {
title: string
description: string
fullscreen?: boolean
}
export const EpicOverview = (props: Props) => {
const { title, description, fullscreen } = props
// Simulating loading state
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const timeout = setTimeout(() => {
setIsLoading(false)
}, 2000)
return () => {
clearTimeout(timeout)
}
}, [])
return (
<div style={{ position: 'relative' }}>
<div className="flex items-center gap-1">
<Text size={fullscreen ? 27 : 19} weight="semibold">
{title}
</Text>
<OpenIcon size={20} />
</div>
<Text size={fullscreen ? 19 : 15} color="$neutral-50">
{description}
</Text>
<div className="flex py-3">
<Tag size={24} label="E:CommunitiesProtocol" color="$blue-50" />
</div>
<Chart data={DATA} height={300} isLoading={isLoading} />
<div className="flex justify-between pt-3">
<div className="flex gap-1">
<Tag size={24} label="Communities" color="#FF7D46" icon="🧙‍♂️" />
<Tag size={24} label="Wallet" color="#7140FD" icon="🎎" />
</div>
<div className="flex gap-1">
<Tag size={24} label="M:0.11.0" color="$danger-50" />
<Tag size={24} label="M:0.12.0" color="$success-50" />
</div>
</div>
</div>
)
}
@@ -0,0 +1,40 @@
import { Text } from '@status-im/components'
import { ERROR_CODES } from '@/consts/error-codes'
type Props = {
errorCode: (typeof ERROR_CODES)[keyof typeof ERROR_CODES]
}
export const ErrorPage = (props: Props) => {
const { errorCode } = props
switch (errorCode) {
// todo!: design review, not in designs
case ERROR_CODES.NOT_FOUND:
return (
<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.
</Text>
</div>
)
case ERROR_CODES.INTERNAL_SERVER_ERROR:
default:
return (
<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">
{"Oh no, something's wrong!"}
</Text>
<Text size={19} weight="regular">
Try reloading the page or come back later!
</Text>
</div>
</div>
)
}
}
@@ -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 }
+4
View File
@@ -0,0 +1,4 @@
export { Breadcrumbs } from './breadcrumbs/breadcrumbs'
export { EpicOverview } from './epic-overview'
export { SideBar } from './side-bar/side-bar'
export { TableIssues } from './table-issues/table-issues'

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