feat: big cleanup and refactor (#644)

* feat: big cleanup and refactor

* chore: fix config

* chore: security stuff

* chore: dep bumps

* chore: more version bumps

* chore: move to lucide icons

* chore: change slider

* chore: more stuff

* chore: move component to shadcn

* chore: remove numeral

* chore: remove night theme

* chore: move to sse

* chore: cleanup deps

* feat!: remove lnmarkets integration

* feat!: remove rebalancing flow

* chore: move to svg progress bar

* chore: new toast lib

* chore!: remove accounting report tool

* chore: dependency updates

* chore: donate button changes

* chore: cleanup file

* chore: cleanup

* ci: build changes

* fix: routing
This commit is contained in:
Anthony Potdevin 2026-02-19 16:16:23 -05:00 committed by GitHub
parent b2ca712ef1
commit d94e7c341c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
354 changed files with 14258 additions and 19884 deletions

View file

@ -4,17 +4,18 @@
*.md
!README*.md
/node_modules
/.next
src/client/.next
/dist
/docs
/.github
.vscode
CHANGELOG.md
coverage
test
src/client/dist
# all env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env.production.local

View file

@ -1,6 +0,0 @@
node_modules
.next
src/client/.next
**/*.generated.tsx
src/graphql/types.ts

View file

@ -1,26 +0,0 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'prettier',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'error',
},
};

55
.github/workflows/docker-release.yml vendored Normal file
View file

@ -0,0 +1,55 @@
name: Build and push Docker images
on:
push:
tags:
- 'v*'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Build and push standard image
uses: docker/build-push-action@v6
with:
context: ./
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
apotdevin/thunderhub:${{ steps.version.outputs.version }}
apotdevin/thunderhub:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build and push base-path image
uses: docker/build-push-action@v6
with:
context: ./
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
build-args: BASE_PATH=/thub
tags: |
apotdevin/thunderhub:base-${{ steps.version.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max

6
.gitignore vendored
View file

@ -1,8 +1,7 @@
# compiled output
/dist
/node_modules
/.next
src/client/.next
src/client/dist
# Logs
logs
@ -48,3 +47,6 @@ config.yaml
# For Dev
local_data
docker-compose.dev.yml
# LLM
.claude

View file

@ -1,4 +1 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint-staged

2
.nvmrc
View file

@ -1 +1 @@
v18.18.2
v24.13.1

View file

@ -1,11 +1,13 @@
FROM node:24.13.1-alpine AS base
# ---------------
# Install Dependencies
# ---------------
FROM node:18.18.2-alpine as deps
FROM base AS deps
WORKDIR /app
# Install dependencies neccesary for node-gyp on node alpine
# Install dependencies necessary for node-gyp on node alpine
RUN apk add --update --no-cache \
libc6-compat \
python3 \
@ -19,7 +21,7 @@ RUN npm ci
# ---------------
# Build App
# ---------------
FROM deps as build
FROM deps AS build
WORKDIR /app
@ -28,41 +30,36 @@ ARG BASE_PATH=""
ENV BASE_PATH=${BASE_PATH}
ARG NODE_ENV="production"
ENV NODE_ENV=${NODE_ENV}
ENV NEXT_TELEMETRY_DISABLED=1
# Build the NestJS and NextJS application
# Build the NestJS and Vite application
COPY . .
RUN npm run build:nest
RUN npm run build:next
RUN npm run build:nest && npm run build:client
# Remove non production necessary modules
RUN npm prune --production
RUN npm prune --omit=dev
# ---------------
# Release App
# ---------------
FROM node:18.18.2-alpine as final
FROM base AS final
WORKDIR /app
# Set env variables
ARG BASE_PATH=""
ENV BASE_PATH=${BASE_PATH}
ARG NODE_ENV="production"
ENV NODE_ENV=${NODE_ENV}
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV="production"
# Copy build artifacts
COPY --from=build /app/package.json ./
COPY --from=build /app/node_modules/ ./node_modules
# Copy NextJS files
COPY --from=build /app/src/client/public ./src/client/public
COPY --from=build /app/src/client/next.config.js ./src/client/
COPY --from=build /app/src/client/.next/ ./src/client/.next
# Copy NestJS files
COPY --from=build /app/src/client/dist/ ./src/client/dist
COPY --from=build /app/dist/ ./dist
# Run as non-root user
USER node
ENV PORT=3000
EXPOSE 3000
CMD [ "npm", "run", "start:prod" ]

21
components.json Normal file
View file

@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/client/src/styles/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

71
eslint.config.js Normal file
View file

@ -0,0 +1,71 @@
const {
defineConfig,
globalIgnores,
} = require("eslint/config");
const tsParser = require("@typescript-eslint/parser");
const typescriptEslintEslintPlugin = require("@typescript-eslint/eslint-plugin");
const globals = require("globals");
const js = require("@eslint/js");
const {
FlatCompat,
} = require("@eslint/eslintrc");
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
module.exports = defineConfig([{
languageOptions: {
parser: tsParser,
sourceType: "module",
parserOptions: {
project: "tsconfig.json",
},
globals: {
...globals.node,
...globals.jest,
},
},
plugins: {
"@typescript-eslint": typescriptEslintEslintPlugin,
},
extends: compat.extends(
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier",
),
rules: {
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "error",
},
}, {
files: ["src/client/**/*.ts", "src/client/**/*.tsx"],
languageOptions: {
parserOptions: {
project: "src/client/tsconfig.json",
},
globals: {
...globals.browser,
...globals.node,
...globals.jest,
},
},
}, globalIgnores([
"**/node_modules",
"**/dist",
"**/*.generated.tsx",
])]);

22592
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,22 +4,23 @@
"description": "Lightning Node Manager",
"license": "MIT",
"scripts": {
"prebuild": "rimraf dist && rimraf .next",
"build": "npm run build:nest && npm run build:next",
"prebuild": "rimraf dist && rimraf src/client/dist",
"build": "npm run build:nest && npm run build:client",
"build:nest": "nest build",
"build:next": "cd src/client && next build",
"build:client": "cd src/client && npx vite build",
"build:all": "sh ./scripts/buildMultiArchImage.sh",
"build:all:test": "sh ./scripts/buildMultiArchImage.sh --test",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "cross-env NODE_ENV=production nest start",
"start:dev": "nest start --watch",
"start:dev": "concurrently \"nest start --watch\" \"cd src/client && npx vite\"",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"start:prod:env": "NODE_ENV=production node dist/main",
"release": "standard-version",
"release:test": "standard-version --dry-run",
"release:minor": "standard-version --release-as minor",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"lint:check": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"lint": "eslint \"{src,apps,libs,test}/**/*.{ts,tsx}\" --fix",
"lint:check": "eslint \"{src,apps,libs,test}/**/*.{ts,tsx}\"",
"lint-staged": "lint-staged",
"generate": "graphql-codegen --config codegen.yml",
"test": "jest --passWithNoTests",
@ -31,146 +32,130 @@
"update": "sh ./scripts/updateToLatest.sh",
"build:image": "docker build --pull --rm -f Dockerfile -t thunderhub:testing '.'",
"build:image:base": "docker build --pull --rm -f Dockerfile --build-arg BASE_PATH=/thub -t thunderhub:testing .",
"prepare": "husky install"
"prepare": "husky"
},
"dependencies": {
"@apollo/client": "^3.8.8",
"@fullerstack/nax-ipware": "^0.10.0",
"@nestjs/apollo": "^12.0.11",
"@nestjs/common": "^10.2.10",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.2.10",
"@nestjs/graphql": "^12.0.11",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.2",
"@nestjs/platform-express": "^10.2.10",
"@nestjs/platform-socket.io": "^10.2.10",
"@nestjs/schedule": "^4.0.0",
"@nestjs/throttler": "^5.0.1",
"@nestjs/websockets": "^10.2.10",
"@tanstack/react-table": "^8.10.7",
"@apollo/server": "^5.4.0",
"@as-integrations/express5": "^1.1.2",
"@nestjs/apollo": "^13.2.4",
"@nestjs/common": "^11.1.14",
"@nestjs/config": "^4.0.3",
"@nestjs/core": "^11.1.14",
"@nestjs/graphql": "^13.2.4",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.1.14",
"@nestjs/schedule": "^6.1.1",
"@nestjs/serve-static": "^5.0.4",
"@nestjs/throttler": "^6.5.0",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.4",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-table": "^8.21.3",
"@vulpemventures/secp256k1-zkp": "^3.2.1",
"apollo-server-express": "^3.13.0",
"async": "^3.2.6",
"balanceofsatoshis": "^17.5.2",
"bcryptjs": "^2.4.3",
"bcryptjs": "^3.0.3",
"bech32": "^2.0.0",
"big.js": "^6.2.1",
"big.js": "^7.0.1",
"bip32": "^4.0.0",
"bip39": "^3.1.0",
"bitcoinjs-lib": "^6.1.5",
"boltz-core": "^2.1.1",
"cookie": "^0.6.0",
"cross-env": "^7.0.3",
"crypto-js": "^4.1.1",
"d3-array": "^3.2.4",
"bitcoinjs-lib": "^6.1.7",
"boltz-core": "^3.1.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cookie": "^1.1.1",
"d3-time-format": "^4.1.0",
"dataloader": "^2.2.2",
"date-fns": "^2.30.0",
"dotenv": "^16.3.1",
"echarts": "^5.5.0",
"echarts-for-react": "^3.0.2",
"ecpair": "^2.0.1",
"graphql": "^16.8.1",
"jest-fetch-mock": "^3.0.3",
"dataloader": "^2.2.3",
"date-fns": "^4.1.0",
"echarts": "^6.0.0",
"echarts-for-react": "^3.0.6",
"ecpair": "^3.0.1",
"graphql": "^16.12.0",
"helmet": "^8.1.0",
"js-cookie": "^3.0.5",
"js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.2",
"lightning": "^10.1.3",
"lodash": "^4.17.21",
"nest-winston": "^1.9.4",
"next": "^13.5.5",
"node-fetch": "^3.3.2",
"numeral": "^2.0.6",
"otplib": "^12.0.1",
"passport": "^0.6.0",
"js-yaml": "^4.1.1",
"jsonwebtoken": "^9.0.3",
"lightning": "^11.0.1",
"lodash": "^4.17.23",
"lucide-react": "^0.574.0",
"nest-winston": "^1.10.2",
"otplib": "^13.3.0",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"qrcode.react": "^3.1.0",
"qrcode.react": "^4.2.0",
"react": "^18.2.0",
"react-circular-progressbar": "^2.1.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.10",
"react-grid-layout": "^1.4.4",
"react-is": "^18.2.0",
"react-select": "^5.8.0",
"react-slider": "^2.0.6",
"react-spinners": "^0.13.8",
"react-toastify": "^9.1.3",
"react-tooltip": "^5.24.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^5.0.5",
"rxjs": "^7.5.6",
"secp256k1": "^5.0.0",
"socket.io-client": "^4.7.2",
"socks-proxy-agent": "^8.0.1",
"styled-components": "^6.1.1",
"styled-react-modal": "^3.0.1",
"react-grid-layout": "^2.2.2",
"react-hot-toast": "^2.6.0",
"react-router-dom": "^7.13.0",
"react-tooltip": "^5.30.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2",
"secp256k1": "^5.0.1",
"socks-proxy-agent": "^8.0.5",
"styled-components": "^6.3.9",
"styled-theming": "^2.2.0",
"stylis": "^4.3.0",
"tiny-secp256k1": "^2.2.3",
"uuid": "^9.0.1",
"winston": "^3.11.0"
"tailwind-merge": "^3.4.1",
"tailwindcss": "^4.1.18",
"tiny-secp256k1": "^2.2.4",
"tw-animate-css": "^1.4.0",
"uuid": "^13.0.0",
"winston": "^3.19.0"
},
"devDependencies": {
"@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/fragment-matcher": "^5.0.0",
"@graphql-codegen/introspection": "^4.0.0",
"@graphql-codegen/near-operation-file-preset": "^3.0.0",
"@graphql-codegen/typescript": "^4.0.1",
"@graphql-codegen/typescript-operations": "^4.0.1",
"@graphql-codegen/typescript-react-apollo": "^4.1.0",
"@graphql-codegen/typescript-resolvers": "^4.0.1",
"@nestjs/cli": "^10.2.1",
"@nestjs/schematics": "^10.0.3",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^10.0.1",
"@graphql-codegen/cli": "^6.1.1",
"@graphql-codegen/fragment-matcher": "^6.0.0",
"@graphql-codegen/introspection": "^5.0.0",
"@graphql-codegen/near-operation-file-preset": "^4.0.0",
"@graphql-codegen/typescript": "^5.0.7",
"@graphql-codegen/typescript-operations": "^5.0.7",
"@graphql-codegen/typescript-react-apollo": "^4.4.0",
"@graphql-codegen/typescript-resolvers": "^5.1.5",
"@nestjs/cli": "^11.0.16",
"@nestjs/schematics": "^11.0.9",
"@types/async": "^3.2.25",
"@types/bcryptjs": "^2.4.6",
"@types/bcryptjs": "^3.0.0",
"@types/big.js": "^6.2.2",
"@types/cookie": "^0.6.0",
"@types/cron": "^2.4.0",
"@types/crypto-js": "^4.2.1",
"@types/d3-array": "^3.2.1",
"@types/cookie": "^1.0.0",
"@types/d3-time-format": "^4.0.3",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.10",
"@types/jest": "^30.0.0",
"@types/js-cookie": "^3.0.6",
"@types/js-yaml": "^4.0.9",
"@types/jsonwebtoken": "^9.0.5",
"@types/lodash": "^4.14.202",
"@types/node": "^20.10.1",
"@types/numeral": "^2.0.5",
"@types/qrcode.react": "^1.0.5",
"@types/react": "^18.2.39",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-grid-layout": "^1.3.5",
"@types/react-qr-reader": "^2.1.7",
"@types/react-slider": "^1.3.5",
"@types/secp256k1": "^4.0.6",
"@types/styled-react-modal": "^1.2.5",
"@types/styled-theming": "^2.2.8",
"@types/supertest": "^2.0.16",
"@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^6.13.1",
"apollo-server": "^3.13.0",
"@types/jsonwebtoken": "^9.0.10",
"@types/lodash": "^4.17.23",
"@types/node": "^25.2.3",
"@types/qrcode.react": "^3.0.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/react-grid-layout": "^2.1.0",
"@types/secp256k1": "^4.0.7",
"@types/styled-theming": "^2.2.9",
"@types/uuid": "^11.0.0",
"@typescript-eslint/eslint-plugin": "^8.56.0",
"@typescript-eslint/parser": "^8.56.0",
"@vitejs/plugin-react": "^5.1.4",
"babel-plugin-styled-components": "^2.1.4",
"eslint": "^8.54.0",
"eslint-config-next": "^14.0.3",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1",
"husky": "^8.0.3",
"jest": "^29.7.0",
"lint-staged": "^15.1.0",
"prettier": "^3.1.0",
"source-map-support": "^0.5.21",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^17.3.0",
"husky": "^9.1.7",
"jest": "^30.2.0",
"lint-staged": "^16.2.7",
"prettier": "^3.8.1",
"rimraf": "^6.1.3",
"standard-version": "^9.5.0",
"supertest": "^6.3.3",
"ts-jest": "^29.1.1",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.1",
"ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.3.2",
"ws": "^8.14.2"
"typescript": "^5.9.3",
"vite": "^7.3.1"
},
"jest": {
"moduleFileExtensions": [

View file

@ -65,28 +65,11 @@ type Balances {
onchain: OnChainBalance!
}
type BaseInvoice {
id: String!
request: String!
}
type BaseNode {
_id: String
name: String
public_key: String!
socket: String!
}
type BaseNodeInfo {
alias: String!
public_key: String!
}
type BasePoints {
alias: String!
amount: Float!
}
type BitcoinFee {
fast: Float!
halfHour: Float!
@ -116,37 +99,6 @@ type BoltzSwapTransaction {
id: String
}
type BosDecrease {
decreased_inbound_on: String!
liquidity_inbound: String!
liquidity_inbound_opening: String
liquidity_inbound_pending: String
liquidity_outbound: String!
liquidity_outbound_opening: String
liquidity_outbound_pending: String
}
type BosIncrease {
increased_inbound_on: String!
liquidity_inbound: String!
liquidity_inbound_opening: String
liquidity_inbound_pending: String
liquidity_outbound: String!
liquidity_outbound_opening: String
liquidity_outbound_pending: String
}
type BosRebalanceResult {
decrease: BosDecrease
increase: BosIncrease
result: BosResult
}
type BosResult {
rebalance_fees_spent: String!
rebalanced: String!
}
type ChainAddressSend {
confirmationCount: Float!
id: String!
@ -450,15 +402,6 @@ type LightningNodeSocialInfo {
socials: NodeSocial
}
type LnMarketsUserInfo {
account_type: String
balance: String
last_ip: String
linkingpublickey: String
uid: String
username: String
}
union LnUrlRequest = ChannelRequest | PayRequest | WithdrawRequest
type Message {
@ -478,23 +421,16 @@ type MessageType {
type Mutation {
addPeer(isTemporary: Boolean, publicKey: String, socket: String, url: String): Boolean!
bosRebalance(avoid: [String!], in_through: String, max_fee: Float, max_fee_rate: Float, max_rebalance: Float, node: String, out_inbound: Float, out_through: String, timeout_minutes: Float): BosRebalanceResult!
claimBoltzTransaction(destination: String!, fee: Float!, id: String!, lockupAddress: String!, preimage: String!, privateKey: String!, redeem: String!): String!
closeChannel(forceClose: Boolean, id: String!, targetConfirmations: Float, tokensPerVByte: Float): OpenOrCloseChannel!
createAddress(type: String! = "p2tr"): String!
createBaseInvoice(amount: Float!): BaseInvoice!
createBoltzReverseSwap(address: String, amount: Float!): CreateBoltzReverseSwapType!
createInvoice(amount: Float!, description: String, includePrivate: Boolean, secondsUntil: Float): CreateInvoice!
createMacaroon(permissions: NetworkInfoInput!): CreateMacaroon!
createThunderPoints(alias: String!, id: String!, public_key: String!, uris: [String!]!): Boolean!
fetchLnUrl(url: String!): LnUrlRequest!
getAuthToken(cookie: String): Boolean!
getSessionToken(id: String!, password: String!, token: String): String!
keysend(destination: String, tokens: Float!): PayInvoice!
lnMarketsDeposit(amount: Float!): Boolean!
lnMarketsLogin: AuthResponse!
lnMarketsLogout: Boolean!
lnMarketsWithdraw(amount: Float!): Boolean!
lnUrlAuth(url: String!): AuthResponse!
lnUrlChannel(callback: String!, k1: String!, uri: String!): String!
lnUrlPay(amount: Float!, callback: String!, comment: String): PaySuccess!
@ -740,13 +676,9 @@ type Policy {
type Query {
decodeRequest(request: String!): DecodeInvoice!
getAccount: ServerAccount!
getAccountingReport(category: String, currency: String, fiat: String, month: String, year: String): String!
getAmbossLoginToken(redirect_url: String): String!
getAmbossUser: AmbossUser
getBackups: String!
getBaseCanConnect: Boolean!
getBaseNodes: [BaseNode!]!
getBasePoints: [BasePoints!]!
getBitcoinFees: BitcoinFee!
getBitcoinPrice: String!
getBoltzInfo: BoltzInfoType!
@ -765,9 +697,6 @@ type Query {
getLatestVersion: String!
getLightningAddressInfo(address: String!): PayRequest!
getLiquidityPerUsd: String!
getLnMarketsStatus: String!
getLnMarketsUrl: String!
getLnMarketsUserInfo: LnMarketsUserInfo!
getMessages(initialize: Boolean): GetMessages!
getNetworkInfo: NetworkInfo!
getNode(publicKey: String!, withoutChannels: Boolean): Node!

View file

@ -4,6 +4,43 @@ trap "exit" INT
REPO=apotdevin/thunderhub
BASE=base
TEST_MODE=false
# Parse flags
while [ "$#" -gt 0 ]; do
case "$1" in
--test) TEST_MODE=true; shift;;
*) echo "Unknown option: $1"; exit 1;;
esac
done
if [ "$TEST_MODE" = true ]; then
echo
echo "------------------------------------------"
echo "Test build (local, no push, no branch switch)"
echo "------------------------------------------"
echo
docker buildx create --name mybuilder --driver-opt network=host --use 2>/dev/null || true
START=`date +%s`
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag $REPO:test \
--file ./Dockerfile \
./
END=`date +%s`
RUNTIME=$((END-START))
echo
echo "------------------------------------------"
echo "DONE - Test build took $RUNTIME seconds"
echo "------------------------------------------"
echo
exit 0
fi
echo
echo
@ -26,7 +63,7 @@ echo "Do you want to build images for version" $VERSION "?"
select yn in "Yes" "No" "Specify"; do
case $yn in
Yes ) break;;
Specify) NOT_LATEST=true; break;;
Specify) NOT_LATEST=true; break;;
No ) exit;;
esac
done
@ -36,16 +73,19 @@ read -p "Enter the version you want to build: " VERSION
git checkout $VERSION || exit
fi
# docker buildx create --use
START=`date +%s`
docker buildx create --name mybuilder --use
docker buildx install
docker buildx create --name mybuilder --driver-opt network=host --use 2>/dev/null || true
docker build \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
LATEST_TAGS=""
if [ "$NOT_LATEST" = false ]; then
LATEST_TAGS="--tag $REPO:latest"
fi
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag $REPO:$VERSION \
$LATEST_TAGS \
--file ./Dockerfile \
--push ./
@ -57,9 +97,9 @@ echo "Building basepath multiarch image for" $REPO
echo
echo
docker build \
docker buildx build \
--build-arg BASE_PATH='/thub' \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
--platform linux/amd64,linux/arm64 \
--tag $REPO:$BASE-$VERSION \
--file ./Dockerfile \
--push ./
@ -81,4 +121,4 @@ echo
echo "multiarch took" $RUNTIME "seconds"
echo "multiarch base took" $RUNTIME1 "seconds"
echo
echo
echo

View file

@ -20,13 +20,13 @@ else
echo "Installing dependencies..."
npm install --quiet
# build nextjs
# build app
echo "Building application..."
npm run build
# remove useless deps
echo "Removing unneccesary modules..."
npm prune --production
# remove dev deps
echo "Removing unnecessary modules..."
npm prune --omit=dev
TAG=$(git tag | sort -V | tail -1)
echo "Updated to version" $TAG

View file

@ -1,30 +0,0 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
sourceType: 'module',
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'next/core-web-vitals',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'prettier',
],
root: true,
env: {
browser: true,
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'error',
// '@next/next/no-html-link-for-pages': ['error', '/src/client/pages/'],
},
};

View file

@ -1,7 +0,0 @@
{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"printWidth": 80,
"arrowParens": "avoid"
}

View file

@ -1,5 +1,3 @@
/* eslint @typescript-eslint/no-var-requires: 0 */
import { IncomingMessage, ServerResponse } from 'http';
import { useMemo } from 'react';
import {
ApolloClient,
@ -8,23 +6,18 @@ import {
InMemoryCache,
NormalizedCacheObject,
} from '@apollo/client';
import getConfig from 'next/config';
import { config } from '../src/config/thunderhubConfig';
import possibleTypes from '../src/graphql/fragmentTypes.json';
import { onError } from '@apollo/client/link/error';
import { setContext } from '@apollo/client/link/context';
const { publicRuntimeConfig } = getConfig();
const { apiUrl: uri } = publicRuntimeConfig;
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined;
export type ResolverContext = {
req?: IncomingMessage;
res?: ServerResponse;
};
function createApolloClient(authToken: string) {
const httpLink = createHttpLink({ uri, credentials: 'include' });
const httpLink = createHttpLink({
uri: config.apiUrl,
credentials: 'include',
});
const authLink = setContext((_, { headers }) => {
return {
@ -51,7 +44,7 @@ function createApolloClient(authToken: string) {
return new ApolloClient({
credentials: 'same-origin',
ssrMode: typeof window === 'undefined',
ssrMode: false,
link,
cache: new InMemoryCache({
...possibleTypes,
@ -98,7 +91,6 @@ export function initializeApollo(
const existingCache = _apolloClient.extract();
_apolloClient.cache.restore({ ...existingCache, ...initialState });
}
if (typeof window === 'undefined') return _apolloClient;
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;

17
src/client/index.html Normal file
View file

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Manage and monitor your lightning network node right inside your browser"
/>
<link rel="icon" href="/favicon.ico" />
<title>ThunderHub - Lightning Node Manager</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View file

@ -1,5 +0,0 @@
/// <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.

View file

@ -1,32 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const dotenv = require('dotenv');
dotenv.config({ path: path.resolve(process.cwd(), '.env.local') });
dotenv.config({ path: path.resolve(process.cwd(), '.env') });
module.exports = {
reactStrictMode: true,
poweredByHeader: false,
basePath: process.env.BASE_PATH || '',
transpilePackages: ['echarts', 'zrender'],
compiler: {
styledComponents: true,
},
publicRuntimeConfig: {
mempoolUrl: process.env.MEMPOOL_URL || 'https://mempool.space',
disable2FA: process.env.DISABLE_TWOFA === 'true',
apiUrl: `${process.env.BASE_PATH || ''}/graphql`,
basePath: process.env.BASE_PATH || '',
npmVersion: process.env.npm_package_version || '0.0.0',
defaultTheme: process.env.THEME || 'dark',
defaultCurrency: process.env.CURRENCY || 'sat',
fetchPrices: process.env.FETCH_PRICES === 'false' ? false : true,
fetchFees: process.env.FETCH_FEES === 'false' ? false : true,
disableLinks: process.env.DISABLE_LINKS === 'true',
disableLnMarkets: process.env.DISABLE_LNMARKETS === 'true',
noVersionCheck: process.env.NO_VERSION_CHECK === 'true',
logoutUrl: process.env.LOGOUT_URL || '',
},
};

View file

@ -1,112 +0,0 @@
import { FC, ReactNode, useEffect } from 'react';
import { StyleSheetManager, ThemeProvider } from 'styled-components';
import { ModalProvider, BaseModalBackground } from 'styled-react-modal';
import { AppProps } from 'next/app';
import { ApolloProvider } from '@apollo/client';
import { useApollo } from '../config/client';
import { BaseProvider } from '../src/context/BaseContext';
import { ContextProvider } from '../src/context/ContextProvider';
import { useConfigState, ConfigProvider } from '../src/context/ConfigContext';
import { GlobalStyles } from '../src/styles/GlobalStyle';
import { Header } from '../src/layouts/header/Header';
import { Footer } from '../src/layouts/footer/Footer';
import { PageWrapper, HeaderBodyWrapper } from '../src/layouts/Layout.styled';
import { ToastContainer } from 'react-toastify';
import { useListener } from '../src/hooks/UseListener';
import { SocketProvider } from '../src/context/SocketContext';
import { useRouter } from 'next/router';
import getConfig from 'next/config';
import Head from 'next/head';
import isPropValid from '@emotion/is-prop-valid';
import 'react-toastify/dist/ReactToastify.min.css';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import 'react-circular-progressbar/dist/styles.css';
const { publicRuntimeConfig } = getConfig();
const { logoutUrl } = publicRuntimeConfig;
function shouldForwardProp(propName: string, target: any) {
if (typeof target === 'string') {
// For HTML elements, forward the prop if it is a valid HTML attribute
return isPropValid(propName);
}
// For other elements, forward all props
return true;
}
const NotAuthenticated: React.FC = () => {
const { push } = useRouter();
useEffect(() => {
push(logoutUrl || '/login');
}, [push]);
return null;
};
const Listener: FC<{ isRoot: boolean }> = ({ isRoot }) => {
useListener(isRoot);
return null;
};
const Wrapper: React.FC<{ authenticated: boolean; children?: ReactNode }> = ({
children,
authenticated,
}) => {
const { theme } = useConfigState();
const { pathname } = useRouter();
const isRoot = pathname === '/login' || pathname === '/sso';
return (
<ThemeProvider theme={{ mode: isRoot ? 'light' : theme }}>
<ModalProvider backgroundComponent={BaseModalBackground}>
<GlobalStyles />
<PageWrapper>
<HeaderBodyWrapper>
<Header />
<Listener isRoot={isRoot} />
{authenticated ? children : <NotAuthenticated />}
</HeaderBodyWrapper>
<Footer />
<ToastContainer theme={theme === 'light' ? 'light' : 'dark'} />
</PageWrapper>
</ModalProvider>
</ThemeProvider>
);
};
export default function App({ Component, pageProps }: AppProps) {
const {
initialApolloState,
initialConfig,
hasToken,
authToken,
authenticated,
} = pageProps;
const apolloClient = useApollo(authToken, initialApolloState);
return (
<StyleSheetManager shouldForwardProp={shouldForwardProp}>
<ApolloProvider client={apolloClient}>
<Head>
<title>ThunderHub - Lightning Node Manager</title>
</Head>
<ConfigProvider initialConfig={initialConfig}>
<BaseProvider initialHasToken={hasToken}>
<SocketProvider authToken={authToken}>
<ContextProvider>
<Wrapper authenticated={authenticated}>
<Component {...pageProps} />
</Wrapper>
</ContextProvider>
</SocketProvider>
</BaseProvider>
</ConfigProvider>
</ApolloProvider>
</StyleSheetManager>
);
}

View file

@ -1,55 +0,0 @@
import * as React from 'react';
import Document, {
DocumentContext,
Html,
Head,
Main,
NextScript,
} from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
// eslint-disable-next-line react/display-name
enhanceApp: App => props => sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
render() {
return (
<Html>
<Head>
<meta
name="description"
content="Manage and monitor your lightning network node right inside your browser"
key="description"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

View file

@ -1,12 +0,0 @@
import Error from 'next/error';
function Page({ statusCode }: any) {
return <Error statusCode={statusCode}></Error>;
}
Page.getInitialProps = ({ res, err }: any) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default Page;

View file

@ -1,42 +0,0 @@
import React from 'react';
import { GridWrapper } from '../../src/components/gridWrapper/GridWrapper';
import { SingleLine } from '../../src/components/generic/Styled';
import { NextPageContext } from 'next';
import { getProps } from '../../src/utils/ssr';
import { AmbossLoginButton } from '../../src/views/amboss/LoginButton';
import { Backups } from '../../src/views/amboss/Backups';
import { SectionTitle, Text } from '../../src/components/typography/Styled';
import { Healthchecks } from '../../src/views/amboss/Healthchecks';
import { Balances } from '../../src/views/amboss/Balances';
import { Billboard } from '../../src/views/amboss/Billboard';
const AmbossView = () => (
<>
<SingleLine>
<SectionTitle style={{ margin: '0', color: '#ff0080', fontWeight: 900 }}>
AMBOSS
</SectionTitle>
<AmbossLoginButton />
</SingleLine>
<Text>
Amboss offers different integration options that can help you monitor your
node, store backups and get historical graphs about your balances.
</Text>
</>
);
const Wrapped = () => (
<GridWrapper>
<AmbossView />
<Backups />
<Healthchecks />
<Balances />
<Billboard />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -1,42 +0,0 @@
import React from 'react';
import { GridWrapper } from '../src/components/gridWrapper/GridWrapper';
import { NextPageContext } from 'next';
import { getProps } from '../src/utils/ssr';
import { ChainTransactions } from '../src/views/chain/transactions/ChainTransactions';
import { ChainUtxos } from '../src/views/chain/utxos/ChainUtxos';
import {
Card,
CardWithTitle,
SubTitle,
} from '../src/components/generic/Styled';
const ChainView = () => {
return (
<>
<CardWithTitle>
<SubTitle>Chain Utxos</SubTitle>
<Card mobileNoBackground={true}>
<ChainUtxos />
</Card>
</CardWithTitle>
<CardWithTitle>
<SubTitle>Chain Transactions</SubTitle>
<Card mobileNoBackground={true}>
<ChainTransactions />
</Card>
</CardWithTitle>
</>
);
};
const Wrapped = () => (
<GridWrapper>
<ChainView />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -1,35 +0,0 @@
import { NextPageContext } from 'next';
import { getProps } from '../src/utils/ssr';
import dynamic from 'next/dynamic';
import { LoadingCard } from '../src/components/loading/LoadingCard';
import { SimpleWrapper } from '../src/components/gridWrapper/GridWrapper';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
position: relative;
`,
};
const LoadingComp = () => <LoadingCard noCard={true} loadingHeight={'90vh'} />;
const Dashboard = dynamic(() => import('../src/views/dashboard'), {
ssr: false,
loading: LoadingComp,
});
const Wrapped = () => {
return (
<SimpleWrapper>
<S.wrapper>
<Dashboard />
</S.wrapper>
</SimpleWrapper>
);
};
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -1,41 +0,0 @@
import React from 'react';
import { GridWrapper } from '../src/components/gridWrapper/GridWrapper';
import { Version } from '../src/components/version/Version';
import { NextPageContext } from 'next';
import { getProps } from '../src/utils/ssr';
import { MempoolReport } from '../src/views/home/reports/mempool';
import { LiquidityGraph } from '../src/views/home/reports/liquidReport/LiquidityGraph';
import { AccountButtons } from '../src/views/home/account/AccountButtons';
import { AccountInfo } from '../src/views/home/account/AccountInfo';
import { QuickActions } from '../src/views/home/quickActions/QuickActions';
import { FlowBox } from '../src/views/home/reports/flow';
import { ForwardBox } from '../src/views/home/reports/forwardReport';
import { ConnectCard } from '../src/views/home/connect/Connect';
import { Liquidity } from '../src/views/home/liquidity/Liquidity';
const HomeView = () => (
<>
<Version />
<AccountInfo />
<AccountButtons />
<ConnectCard />
<Liquidity />
<QuickActions />
<FlowBox />
<LiquidityGraph />
<ForwardBox />
<MempoolReport />
</>
);
const Wrapped = () => (
<GridWrapper>
<HomeView />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -1,59 +0,0 @@
import React from 'react';
import { GridWrapper } from '../src/components/gridWrapper/GridWrapper';
import { NextPageContext } from 'next';
import { getProps } from '../src/utils/ssr';
import { useGetBasePointsQuery } from '../src/graphql/queries/__generated__/getBasePoints.generated';
import { NodeCard } from '../src/views/leaderboard/NodeCard';
import { SupportBar } from '../src/views/home/quickActions/donate/DonateContent';
import {
CardWithTitle,
SubTitle,
Card,
} from '../src/components/generic/Styled';
import { LoadingCard } from '../src/components/loading/LoadingCard';
const LeaderboardView = () => {
const { loading, data } = useGetBasePointsQuery({ ssr: false });
const renderBoard = () => {
if (loading || !data?.getBasePoints) {
return <LoadingCard title={'Supporters'} />;
}
if (!data.getBasePoints.length) {
return null;
}
return (
<CardWithTitle>
<SubTitle>Supporters</SubTitle>
<Card mobileCardPadding={'0'} mobileNoBackground={true}>
{data.getBasePoints.map((node, index: number) => (
<React.Fragment key={index}>
<NodeCard node={node} index={index + 1} />
</React.Fragment>
))}
</Card>
</CardWithTitle>
);
};
return (
<>
<Card>
<SupportBar />
</Card>
{renderBoard()}
</>
);
};
const Wrapped = () => (
<GridWrapper>
<LeaderboardView />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -1,108 +0,0 @@
import React, { useEffect } from 'react';
import styled from 'styled-components';
import { GridWrapper } from '../src/components/gridWrapper/GridWrapper';
import { NextPageContext } from 'next';
import { getProps } from '../src/utils/ssr';
import { UserInfo } from '../src/views/lnmarkets/UserInfo';
import { Title } from '../src/components/typography/Styled';
import { GoToLnMarkets } from '../src/views/lnmarkets/GoToLnMarkets';
import { DepositWithdraw } from '../src/views/lnmarkets/DepositWithdraw';
import { useGetLnMarketsStatusQuery } from '../src/graphql/queries/__generated__/getLnMarketsStatus.generated';
import {
useLnMarketsLoginMutation,
useLnMarketsLogoutMutation,
} from '../src/graphql/mutations/__generated__/lnMarkets.generated';
import { getErrorContent } from '../src/utils/error';
import { toast } from 'react-toastify';
import { ColorButton } from '../src/components/buttons/colorButton/ColorButton';
import { useConfigDispatch } from '../src/context/ConfigContext';
import { SingleLine } from '../src/components/generic/Styled';
export const ButtonRow = styled.div`
width: auto;
display: flex;
`;
export const SettingsLine = styled(SingleLine)`
margin: 8px 0;
`;
const LnMarketsView = () => {
const dispatch = useConfigDispatch();
const { data: statusData } = useGetLnMarketsStatusQuery({
fetchPolicy: 'no-cache',
});
const [login, { data, loading }] = useLnMarketsLoginMutation({
onError: error => toast.error(getErrorContent(error)),
refetchQueries: ['GetLnMarketsStatus'],
});
const [logout, { loading: logoutLoading }] = useLnMarketsLogoutMutation({
onCompleted: () => {
toast.success('Logged out');
dispatch({ type: 'change', lnMarketsAuth: false });
},
refetchQueries: ['GetLnMarketsStatus'],
});
useEffect(() => {
if (data?.lnMarketsLogin?.status === 'OK') {
dispatch({ type: 'change', lnMarketsAuth: true });
toast.success('Logged In');
}
}, [data, dispatch]);
const Content = () => {
if (statusData?.getLnMarketsStatus === 'out') {
return (
<ColorButton
loading={loading}
disabled={loading}
withMargin={'8px 0 0 '}
fullWidth={true}
onClick={login}
>
Login
</ColorButton>
);
}
return (
<>
<UserInfo />
<DepositWithdraw />
<GoToLnMarkets />
<ColorButton
loading={logoutLoading}
disabled={logoutLoading}
withMargin={'8px 0 0 '}
fullWidth={true}
onClick={logout}
>
Logout
</ColorButton>
</>
);
};
return (
<>
<Title>LnMarkets</Title>
<Content />
</>
);
};
const Wrapped = () => (
<GridWrapper>
<LnMarketsView />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -1,23 +0,0 @@
import * as React from 'react';
import { Spacer } from '../src/components/spacer/Spacer';
import { ThunderStorm } from '../src/views/homepage/HomePage.styled';
import { appendBasePath } from '../src/utils/basePath';
import { NextPageContext } from 'next';
import { getProps } from '../src/utils/ssr';
import { TopSection } from '../src/views/homepage/Top';
import { Accounts } from '../src/views/homepage/Accounts';
const ContextApp = () => (
<>
<ThunderStorm alt={''} src={appendBasePath('/static/thunderstorm.webp')} />
<TopSection />
<Accounts />
<Spacer />
</>
);
export default ContextApp;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, true);
}

View file

@ -1,44 +0,0 @@
import React from 'react';
import { GridWrapper } from '../src/components/gridWrapper/GridWrapper';
import {
CardWithTitle,
SingleLine,
SubTitle,
} from '../src/components/generic/Styled';
import { AdvancedBalance } from '../src/views/balance/AdvancedBalance';
import { NextPageContext } from 'next';
import { getProps } from '../src/utils/ssr';
import { HelpCircle } from 'react-feather';
import styled from 'styled-components';
import { chartColors } from '../src/styles/Themes';
const Button = styled.a`
cursor: pointer;
`;
const BalanceView = () => (
<CardWithTitle>
<SingleLine>
<SubTitle>Rebalance</SubTitle>
<Button
href={'https://apotdevin.com/blog/thunderhub-rebalance'}
target={'__blank'}
>
<HelpCircle size={18} color={chartColors.orange} />
</Button>
</SingleLine>
<AdvancedBalance />
</CardWithTitle>
);
const Wrapped = () => (
<GridWrapper>
<BalanceView />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -1,25 +0,0 @@
import React from 'react';
import { GridWrapper } from '../../src/components/gridWrapper/GridWrapper';
import { NextPageContext } from 'next';
import { getProps } from '../../src/utils/ssr';
import dynamic from 'next/dynamic';
import { LoadingCard } from '../../src/components/loading/LoadingCard';
const LoadingComp = () => <LoadingCard noCard={true} loadingHeight={'30vh'} />;
const Dashboard = dynamic(() => import('../../src/views/settings/DashPanel'), {
ssr: false,
loading: LoadingComp,
});
const Wrapped = () => (
<GridWrapper noNavigation={true}>
<Dashboard />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -1,52 +0,0 @@
import React from 'react';
import styled from 'styled-components';
import { GridWrapper } from '../../src/components/gridWrapper/GridWrapper';
import { NextPageContext } from 'next';
import { getProps } from '../../src/utils/ssr';
import { DashboardSettings } from '../../src/views/settings/Dashboard';
import { SingleLine } from '../../src/components/generic/Styled';
import { InterfaceSettings } from '../../src/views/settings/Interface';
import { DangerView } from '../../src/views/settings/Danger';
import { ChatSettings } from '../../src/views/settings/Chat';
import { PrivacySettings } from '../../src/views/settings/Privacy';
import { Security } from '../../src/views/settings/Security';
import { NetworkInfo } from '../../src/views/home/networkInfo/NetworkInfo';
import { NotificationSettings } from '../../src/views/settings/Notifications';
import { AmbossSettings } from '../../src/views/settings/Amboss';
export const ButtonRow = styled.div`
width: auto;
display: flex;
`;
export const SettingsLine = styled(SingleLine)`
margin: 8px 0;
`;
const SettingsView = () => {
return (
<>
<InterfaceSettings />
<NotificationSettings />
<AmbossSettings />
<Security />
<DashboardSettings />
<PrivacySettings />
<ChatSettings />
<DangerView />
<NetworkInfo />
</>
);
};
const Wrapped = () => (
<GridWrapper>
<SettingsView />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -1,17 +0,0 @@
import * as React from 'react';
import { useCheckAuthToken } from '../src/hooks/UseCheckAuthToken';
import { NextPageContext } from 'next';
import { getProps } from '../src/utils/ssr';
import { LoadingCard } from '../src/components/loading/LoadingCard';
const Wrapped = () => {
useCheckAuthToken();
return <LoadingCard noCard={true} loadingHeight={'80vh'} />;
};
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, true);
}

View file

@ -1,45 +0,0 @@
import React from 'react';
import styled from 'styled-components';
import { GridWrapper } from '../src/components/gridWrapper/GridWrapper';
import { VolumeStats } from '../src/views/stats/FlowStats';
import { TimeStats } from '../src/views/stats/TimeStats';
import { FeeStats } from '../src/views/stats/FeeStats';
import { StatResume } from '../src/views/stats/StatResume';
import { StatsProvider } from '../src/views/stats/context';
import { NextPageContext } from 'next';
import { getProps } from '../src/utils/ssr';
import { SingleLine } from '../src/components/generic/Styled';
export const ButtonRow = styled.div`
width: auto;
display: flex;
`;
export const SettingsLine = styled(SingleLine)`
margin: 8px 0;
`;
const StatsView = () => {
return (
<>
<StatResume />
<VolumeStats />
<TimeStats />
<FeeStats />
</>
);
};
const Wrapped = () => (
<GridWrapper>
<StatsProvider>
<StatsView />
</StatsProvider>
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -1,17 +0,0 @@
import React from 'react';
import { GridWrapper } from '../src/components/gridWrapper/GridWrapper';
import { NextPageContext } from 'next';
import { getProps } from '../src/utils/ssr';
import { SwapView } from '../src/views/swap';
const Wrapped = () => (
<GridWrapper>
<SwapView />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -1,31 +0,0 @@
import React from 'react';
import { GridWrapper } from '../src/components/gridWrapper/GridWrapper';
import { Bakery } from '../src/views/tools/bakery/Bakery';
import { Accounting } from '../src/views/tools/accounting/Accounting';
import { NextPageContext } from 'next';
import { getProps } from '../src/utils/ssr';
import { BackupsView } from '../src/views/tools/backups/Backups';
import { MessagesView } from '../src/views/tools/messages/Messages';
import { WalletVersion } from '../src/views/tools/WalletVersion';
const ToolsView = () => (
<>
<Accounting />
<BackupsView />
<MessagesView />
<Bakery />
<WalletVersion />
</>
);
const Wrapped = () => (
<GridWrapper>
<ToolsView />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

178
src/client/src/App.tsx Normal file
View file

@ -0,0 +1,178 @@
import { FC, ReactNode, useEffect, lazy, Suspense } from 'react';
import { StyleSheetManager, ThemeProvider } from 'styled-components';
import { ApolloProvider } from '@apollo/client';
import { Routes, Route, useNavigate, useLocation } from 'react-router-dom';
import Cookies from 'js-cookie';
import isPropValid from '@emotion/is-prop-valid';
import { useApollo } from '../config/client';
import { ContextProvider } from './context/ContextProvider';
import { useConfigState, ConfigProvider } from './context/ConfigContext';
import { GlobalStyles } from './styles/GlobalStyle';
import { Header } from './layouts/header/Header';
import { Footer } from './layouts/footer/Footer';
import { PageWrapper, HeaderBodyWrapper } from './layouts/Layout.styled';
import { Toaster } from 'react-hot-toast';
import { useListener } from './hooks/UseListener';
import { SseProvider } from './context/SseContext';
import { config } from './config/thunderhubConfig';
import { LoadingCard } from './components/loading/LoadingCard';
import { useGetNodeInfoQuery } from './graphql/queries/__generated__/getNodeInfo.generated';
import styled from 'styled-components';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
// Page imports
import HomePage from './pages/HomePage';
import LoginPage from './pages/LoginPage';
import SsoPage from './pages/SsoPage';
import ChannelsPage from './pages/ChannelsPage';
import ChannelDetailPage from './pages/ChannelDetailPage';
import PeersPage from './pages/PeersPage';
import TransactionsPage from './pages/TransactionsPage';
import ForwardsPage from './pages/ForwardsPage';
import ChainPage from './pages/ChainPage';
import ToolsPage from './pages/ToolsPage';
import StatsPage from './pages/StatsPage';
import SwapPage from './pages/SwapPage';
import ChatPage from './pages/ChatPage';
import SettingsPage from './pages/SettingsPage';
import AmbossPage from './pages/AmbossPage';
const DashboardPage = lazy(() => import('./pages/DashboardPage'));
const SettingsDashboardPage = lazy(
() => import('./pages/SettingsDashboardPage')
);
const LoadingComp = () => <LoadingCard noCard={true} loadingHeight={'90vh'} />;
const LoadingCompSmall = () => (
<LoadingCard noCard={true} loadingHeight={'30vh'} />
);
const S = {
wrapper: styled.div`
position: relative;
`,
};
function shouldForwardProp(propName: string, target: any) {
if (typeof target === 'string') {
return isPropValid(propName);
}
return true;
}
const NotAuthenticated: FC = () => {
const navigate = useNavigate();
useEffect(() => {
navigate(config.logoutUrl || '/login');
}, [navigate]);
return null;
};
const Listener: FC<{ isRoot: boolean }> = ({ isRoot }) => {
useListener(isRoot);
return null;
};
const Wrapper: FC<{ children?: ReactNode }> = ({ children }) => {
const { theme } = useConfigState();
const { pathname } = useLocation();
useEffect(() => {
document.documentElement.classList.toggle('dark', theme === 'dark');
}, [theme]);
const { data, loading, error } = useGetNodeInfoQuery({
fetchPolicy: 'network-only',
skip: pathname === '/login' || pathname === '/sso',
});
const isRoot = pathname === '/login' || pathname === '/sso';
const authenticated = !isRoot && !loading && !error && !!data?.getNodeInfo;
const checking = !isRoot && loading;
return (
<ThemeProvider theme={{ mode: isRoot ? 'light' : theme }}>
<GlobalStyles />
<PageWrapper>
<HeaderBodyWrapper>
<Header />
<Listener isRoot={isRoot} />
{checking ? (
<LoadingCard noCard={true} loadingHeight={'80vh'} />
) : isRoot || authenticated ? (
children
) : (
<NotAuthenticated />
)}
</HeaderBodyWrapper>
<Footer />
<Toaster position="top-right" />
</PageWrapper>
</ThemeProvider>
);
};
const AuthenticatedRoutes = () => (
<Routes>
<Route path="/" element={<HomePage />} />
<Route
path="/dashboard"
element={
<Suspense fallback={<LoadingComp />}>
<S.wrapper>
<DashboardPage />
</S.wrapper>
</Suspense>
}
/>
<Route path="/channels" element={<ChannelsPage />} />
<Route path="/channels/:slug" element={<ChannelDetailPage />} />
<Route path="/peers" element={<PeersPage />} />
<Route path="/transactions" element={<TransactionsPage />} />
<Route path="/forwards" element={<ForwardsPage />} />
<Route path="/chain" element={<ChainPage />} />
<Route path="/tools" element={<ToolsPage />} />
<Route path="/stats" element={<StatsPage />} />
<Route path="/swap" element={<SwapPage />} />
<Route path="/chat" element={<ChatPage />} />
<Route path="/settings" element={<SettingsPage />} />
<Route
path="/settings/dashboard"
element={
<Suspense fallback={<LoadingCompSmall />}>
<SettingsDashboardPage />
</Suspense>
}
/>
<Route path="/amboss" element={<AmbossPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/sso" element={<SsoPage />} />
<Route path="*" element={<HomePage />} />
</Routes>
);
export default function App() {
const themeCookie = Cookies.get('theme') || config.defaultTheme;
const apolloClient = useApollo('', null);
return (
<StyleSheetManager shouldForwardProp={shouldForwardProp}>
<ApolloProvider client={apolloClient}>
<ConfigProvider initialConfig={{ theme: themeCookie }}>
<SseProvider>
<ContextProvider>
<Wrapper>
<AuthenticatedRoutes />
</Wrapper>
</ContextProvider>
</SseProvider>
</ConfigProvider>
</ApolloProvider>
</StyleSheetManager>
);
}

View file

@ -1,12 +1,11 @@
import { useEffect } from 'react';
import { useGetBitcoinFeesQuery } from '../../../src/graphql/queries/__generated__/getBitcoinFees.generated';
import { useGetBitcoinFeesQuery } from '@/graphql/queries/__generated__/getBitcoinFees.generated';
import { useConfigState } from '../../context/ConfigContext';
export const BitcoinFees: React.FC = () => {
const { fetchFees } = useConfigState();
const { stopPolling, error } = useGetBitcoinFeesQuery({
ssr: false,
skip: !fetchFees,
fetchPolicy: 'network-only',
pollInterval: 60000,

View file

@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { useGetBitcoinPriceQuery } from '../../../src/graphql/queries/__generated__/getBitcoinPrice.generated';
import { useGetBitcoinPriceQuery } from '@/graphql/queries/__generated__/getBitcoinPrice.generated';
import { usePriceDispatch } from '../../context/PriceContext';
import { useConfigState } from '../../context/ConfigContext';
@ -7,7 +7,6 @@ export const BitcoinPrice: React.FC = () => {
const { fetchPrices } = useConfigState();
const setPrices = usePriceDispatch();
const { loading, data, stopPolling } = useGetBitcoinPriceQuery({
ssr: false,
skip: !fetchPrices,
fetchPolicy: 'network-only',
onError: () => {
@ -28,7 +27,7 @@ export const BitcoinPrice: React.FC = () => {
try {
const prices = JSON.parse(data.getBitcoinPrice);
setPrices({ type: 'fetched', state: { prices } });
} catch (error: any) {
} catch {
setPrices({ type: 'dontShow' });
stopPolling();
}

View file

@ -1,4 +1,3 @@
import React from 'react';
import styled, { css } from 'styled-components';
import { burgerColor } from '../../styles/Themes';
import { NodeInfo } from '../../layouts/navigation/nodeInfo/NodeInfo';

View file

@ -1,7 +1,7 @@
import React, { ReactNode } from 'react';
import { FC, ReactNode } from 'react';
import styled, { css } from 'styled-components';
import { ChevronRight } from 'react-feather';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { ChevronRight } from 'lucide-react';
import { Loader2 } from 'lucide-react';
import { ThemeSet } from 'styled-theming';
import {
textColor,
@ -134,7 +134,7 @@ export interface ColorButtonProps {
children?: ReactNode;
}
export const ColorButton: React.FC<ColorButtonProps> = ({
export const ColorButton: FC<ColorButtonProps> = ({
loading,
color,
disabled,
@ -174,7 +174,11 @@ export const ColorButton: React.FC<ColorButtonProps> = ({
mobileFullWidth={mobileFullWidth}
buttonWidth={width}
>
<ScaleLoader height={16} color={themeColors.blue2} />
<Loader2
className="animate-spin"
size={16}
style={{ color: themeColors.blue2 }}
/>
</DisabledButton>
);
}

View file

@ -1,4 +1,4 @@
import React, { ReactNode } from 'react';
import { FC, ReactNode } from 'react';
import styled, { css } from 'styled-components';
import {
multiSelectColor,
@ -6,7 +6,7 @@ import {
multiButtonColor,
themeColors,
} from '../../../styles/Themes';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { Loader2 } from 'lucide-react';
interface StyledSingleProps {
selected?: boolean;
@ -44,7 +44,7 @@ interface SingleButtonProps {
children?: ReactNode;
}
export const SingleButton: React.FC<SingleButtonProps> = ({
export const SingleButton: FC<SingleButtonProps> = ({
children,
disabled,
selected,
@ -59,7 +59,7 @@ export const SingleButton: React.FC<SingleButtonProps> = ({
buttonColor={color}
withPadding={withPadding}
onClick={() => {
onClick && onClick();
if (onClick) onClick();
}}
>
{children}
@ -90,7 +90,7 @@ interface MultiButtonProps {
children?: ReactNode;
}
export const MultiButton: React.FC<MultiButtonProps> = ({
export const MultiButton: FC<MultiButtonProps> = ({
children,
margin,
loading,
@ -100,7 +100,11 @@ export const MultiButton: React.FC<MultiButtonProps> = ({
<MultiBackground margin={margin}>
{loading ? (
<div style={{ width, textAlign: 'center' }}>
<ScaleLoader height={21} color={themeColors.blue3} />
<Loader2
className="animate-spin"
size={21}
style={{ color: themeColors.blue3 }}
/>
</div>
) : (
children

View file

@ -12,7 +12,6 @@ import * as echarts from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import ReactEChartsCore from 'echarts-for-react/lib/core';
import { ThemeContext } from 'styled-components';
import numeral from 'numeral';
import { timeFormat, timeParse } from 'd3-time-format';
import { formatSats } from '../../utils/helpers';
import { COMMON_CHART_STYLES } from './common';
@ -110,8 +109,10 @@ export const BarChart = ({
axisTick: { show: true },
axisLabel: {
formatter: function (value: number) {
const format = value < 1000 ? '0a' : '0.0a';
return numeral(value).format(format);
return value.toLocaleString('en-US', {
notation: 'compact',
maximumFractionDigits: value < 1000 ? 0 : 1,
});
},
},
},

View file

@ -1,5 +1,5 @@
import { useGetForwardsQuery } from '../../graphql/queries/__generated__/getForwards.generated';
import { toast } from 'react-toastify';
import toast from 'react-hot-toast';
import { getErrorContent } from '../../utils/error';
import ReactEChartsCore from 'echarts-for-react/lib/core';
import * as echarts from 'echarts/core';
@ -25,7 +25,6 @@ const getMaxHeight = (arr: number[], rounding?: number): number => {
export const ChannelCart = ({ channelId, days }: ChannelCartProps) => {
const themeContext = useContext(ThemeContext);
const { data } = useGetForwardsQuery({
ssr: false,
variables: { days: days },
onError: error => toast.error(getErrorContent(error)),
});

View file

@ -1,26 +1,25 @@
import * as React from 'react';
import { toast } from 'react-toastify';
import { useRouter } from 'next/router';
import { useGetMessagesQuery } from '../../../src/graphql/queries/__generated__/getMessages.generated';
import { useAccount } from '../../../src/hooks/UseAccount';
import { FC, useEffect } from 'react';
import toast from 'react-hot-toast';
import { useLocation } from 'react-router-dom';
import { useGetMessagesQuery } from '@/graphql/queries/__generated__/getMessages.generated';
import { useAccount } from '@/hooks/UseAccount';
import { useChatState, useChatDispatch } from '../../context/ChatContext';
import { getErrorContent } from '../../utils/error';
import { useConfigState } from '../../context/ConfigContext';
export const ChatFetcher: React.FC = () => {
export const ChatFetcher: FC = () => {
const newChatToastId = 'newChatToastId';
const { chatPollingSpeed } = useConfigState();
const account = useAccount();
const { pathname } = useRouter();
const { pathname } = useLocation();
const { lastChat, chats, sentChats, initialized } = useChatState();
const dispatch = useChatDispatch();
const noChatsAvailable = chats.length <= 0 && sentChats.length <= 0;
const { data, loading, error } = useGetMessagesQuery({
ssr: false,
skip: initialized || noChatsAvailable || !account,
pollInterval: chatPollingSpeed,
fetchPolicy: 'network-only',
@ -28,7 +27,7 @@ export const ChatFetcher: React.FC = () => {
onError: error => toast.error(getErrorContent(error)),
});
React.useEffect(() => {
useEffect(() => {
if (data && data.getMessages?.messages) {
const messages = [...data.getMessages.messages];
let index = -1;

View file

@ -1,11 +1,11 @@
import * as React from 'react';
import { toast } from 'react-toastify';
import { useGetMessagesLazyQuery } from '../../../src/graphql/queries/__generated__/getMessages.generated';
import { useAccount } from '../../../src/hooks/UseAccount';
import { FC, useEffect } from 'react';
import toast from 'react-hot-toast';
import { useGetMessagesLazyQuery } from '@/graphql/queries/__generated__/getMessages.generated';
import { useAccount } from '@/hooks/UseAccount';
import { useChatDispatch } from '../../context/ChatContext';
import { getErrorContent } from '../../utils/error';
export const ChatInit: React.FC = () => {
export const ChatInit: FC = () => {
const dispatch = useChatDispatch();
const [
@ -18,7 +18,7 @@ export const ChatInit: React.FC = () => {
const account = useAccount();
React.useEffect(() => {
useEffect(() => {
if (account) {
const storageChats =
localStorage.getItem(`${account.id}-sentChats`) || '';
@ -34,7 +34,7 @@ export const ChatInit: React.FC = () => {
sender,
});
}
} catch (error: any) {
} catch {
localStorage.removeItem('sentChats');
}
}
@ -42,7 +42,7 @@ export const ChatInit: React.FC = () => {
}
}, [dispatch, getMessages, account]);
React.useEffect(() => {
useEffect(() => {
if (!initLoading && !initError && initData && initData.getMessages) {
const { messages } = initData.getMessages;

View file

@ -1,4 +1,4 @@
import React, { ReactNode } from 'react';
import { FC, ReactNode } from 'react';
import styled from 'styled-components';
import {
colorButtonBackground,
@ -42,7 +42,7 @@ type CheckboxProps = {
children?: ReactNode;
};
export const Checkbox: React.FC<CheckboxProps> = ({
export const Checkbox: FC<CheckboxProps> = ({
children,
checked,
onChange,

View file

@ -1,11 +1,11 @@
import React, { useState } from 'react';
import { toast } from 'react-toastify';
import { ChevronRight } from 'react-feather';
import { useUpdateFeesMutation } from '../../../../src/graphql/mutations/__generated__/updateFees.generated';
import { Input } from '../../../../src/components/input';
import { InputWithDeco } from '../../../../src/components/input/InputWithDeco';
import { ColorButton } from '../../../../src/components/buttons/colorButton/ColorButton';
import { getErrorContent } from '../../../../src/utils/error';
import { useState } from 'react';
import toast from 'react-hot-toast';
import { ChevronRight } from 'lucide-react';
import { useUpdateFeesMutation } from '@/graphql/mutations/__generated__/updateFees.generated';
import { Input } from '@/components/input';
import { InputWithDeco } from '@/components/input/InputWithDeco';
import { ColorButton } from '@/components/buttons/colorButton/ColorButton';
import { getErrorContent } from '@/utils/error';
import { RightAlign } from '../../generic/Styled';
type DetailsChangeProps = {
@ -27,10 +27,12 @@ export const DetailsChange = ({ callback }: DetailsChangeProps) => {
setCLTV(0);
setMax(0);
setMin(0);
data.updateFees
? toast.success('Channel Details Updated')
: toast.error('Error updating fees');
callback && callback();
if (data.updateFees) {
toast.success('Channel Details Updated');
} else {
toast.error('Error updating fees');
}
if (callback) callback();
},
refetchQueries: ['GetChannels', 'ChannelFees'],
});

View file

@ -1,5 +1,3 @@
import React from 'react';
interface EmojiProps {
symbol: string;
label?: string;

View file

@ -1,4 +1,3 @@
import React from 'react';
import {
isFuture,
format,
@ -6,10 +5,9 @@ import {
differenceInCalendarDays,
isToday,
} from 'date-fns';
import { X, Copy } from 'react-feather';
import CopyToClipboard from 'react-copy-to-clipboard';
import { toast } from 'react-toastify';
import getConfig from 'next/config';
import { X, Copy } from 'lucide-react';
import toast from 'react-hot-toast';
import { config } from '../../config/thunderhubConfig';
import {
SmallLink,
DarkSubTitle,
@ -19,9 +17,6 @@ import {
} from './Styled';
import { StatusDot, DetailLine } from './CardGeneric';
const { publicRuntimeConfig } = getConfig();
const { disableLinks, mempoolUrl } = publicRuntimeConfig;
export const shorten = (text: string, length?: number): string => {
if (!text) return '';
const amount = length || 6;
@ -49,62 +44,64 @@ export const addEllipsis = (
};
export const copyLink = (text: string) => (
<CopyToClipboard text={text} onCopy={() => toast.success('Copied')}>
<CopyIcon>
<Copy size={12} />
</CopyIcon>
</CopyToClipboard>
<CopyIcon
onClick={() =>
navigator.clipboard.writeText(text).then(() => toast.success('Copied'))
}
>
<Copy size={12} />
</CopyIcon>
);
export const getAddressLink = (transaction: string | null | undefined) => {
if (!transaction) return null;
if (disableLinks) {
if (config.disableLinks) {
return (
<>
<span className="flex items-center">
{shorten(transaction)}
{copyLink(transaction)}
</>
</span>
);
}
const link = `${mempoolUrl}/address/${transaction}`;
const link = `${config.mempoolUrl}/address/${transaction}`;
return (
<>
<span className="flex items-center">
<SmallLink href={link} target="_blank">
{shorten(transaction)}
</SmallLink>
{copyLink(transaction)}
</>
</span>
);
};
export const getTransactionLink = (transaction: string | null | undefined) => {
if (!transaction) return null;
if (disableLinks) {
if (config.disableLinks) {
return (
<>
<span className="flex items-center">
{shorten(transaction)}
{copyLink(transaction)}
</>
</span>
);
}
const link = `${mempoolUrl}/tx/${transaction}`;
const link = `${config.mempoolUrl}/tx/${transaction}`;
return (
<>
<span className="flex items-center">
<SmallLink href={link} target="_blank">
{shorten(transaction)}
</SmallLink>
{copyLink(transaction)}
</>
</span>
);
};
export const getWithCopy = (text: string | null | undefined) => {
if (!text) return null;
return (
<>
<span className="flex items-center">
{shorten(text)}
{copyLink(text)}
</>
</span>
);
};
@ -118,8 +115,8 @@ export const getNodeLink = (
const link = `https://amboss.space/node/${publicKey}`;
const text = alias ? alias : shorten(publicKey);
return (
<>
{disableLinks ? (
<span className="flex items-center">
{config.disableLinks ? (
text
) : (
<SmallLink href={link} target="_blank">
@ -127,15 +124,15 @@ export const getNodeLink = (
</SmallLink>
)}
{copyLink(publicKey)}
</>
</span>
);
};
export const getChannelLink = (id: string) => {
const link = `https://amboss.space/edge/${id}`;
return (
<>
{disableLinks ? (
<span className="flex items-center">
{config.disableLinks ? (
id
) : (
<SmallLink href={link} target="_blank">
@ -143,7 +140,7 @@ export const getChannelLink = (id: string) => {
</SmallLink>
)}
{copyLink(id)}
</>
</span>
);
};

View file

@ -1,7 +1,7 @@
import React, { ReactNode } from 'react';
import { FC, ReactNode } from 'react';
import styled, { css } from 'styled-components';
import { BitcoinFees } from '../../../src/components/bitcoinInfo/BitcoinFees';
import { BitcoinPrice } from '../../../src/components/bitcoinInfo/BitcoinPrice';
import { BitcoinFees } from '@/components/bitcoinInfo/BitcoinFees';
import { BitcoinPrice } from '@/components/bitcoinInfo/BitcoinPrice';
import { mediaWidths } from '../../styles/Themes';
import { Section } from '../section/Section';
import { Navigation } from '../../layouts/navigation/Navigation';
@ -32,7 +32,7 @@ const ContentStyle = styled.div`
grid-area: content;
`;
export const GridWrapper: React.FC<
export const GridWrapper: FC<
GridProps & { centerContent?: boolean; children?: ReactNode }
> = ({ children, centerContent = true, noNavigation }) => (
<Section padding={'16px 16px 32px'}>
@ -51,7 +51,7 @@ export const GridWrapper: React.FC<
</Section>
);
export const SimpleWrapper: React.FC<GridProps> = ({ children }) => (
export const SimpleWrapper: FC<GridProps> = ({ children }) => (
<Section padding={'16px'}>
<BitcoinPrice />
<BitcoinFees />

View file

@ -1,6 +1,6 @@
import * as React from 'react';
import { FC, KeyboardEvent, ReactNode } from 'react';
import styled from 'styled-components';
import { unSelectedNavButton, mediaWidths } from '../../../src/styles/Themes';
import { unSelectedNavButton, mediaWidths } from '@/styles/Themes';
import { SingleLine } from '../generic/Styled';
import { Price } from '../price/Price';
import { Input } from '.';
@ -48,12 +48,12 @@ type InputWithDecoProps = {
inputType?: string;
inputCallback?: (value: string) => void;
blurCallback?: (value: string) => void;
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void;
onEnter?: () => void;
children?: React.ReactNode;
children?: ReactNode;
};
export const InputWithDeco: React.FC<InputWithDecoProps> = ({
export const InputWithDeco: FC<InputWithDecoProps> = ({
title,
value,
amount,

View file

@ -1,4 +1,4 @@
import React from 'react';
import { ChangeEvent, KeyboardEvent } from 'react';
import styled, { css } from 'styled-components';
import { ThemeSet } from 'styled-theming';
import {
@ -83,9 +83,9 @@ interface InputCompProps {
mobileFullWidth?: boolean;
maxWidth?: string;
autoFocus?: boolean;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onBlur?: (e: React.ChangeEvent<HTMLInputElement>) => void;
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
onBlur?: (e: ChangeEvent<HTMLInputElement>) => void;
onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void;
onEnter?: () => void;
}
@ -125,7 +125,7 @@ export const Input = ({
if (onEnter && e.key === 'Enter') {
onEnter();
} else {
onKeyDown && onKeyDown(e);
if (onKeyDown) onKeyDown(e);
}
}}
/>

View file

@ -1,7 +1,7 @@
import React, { ReactNode } from 'react';
import { FC, ReactNode } from 'react';
import styled, { css } from 'styled-components';
import { ThemeSet } from 'styled-theming';
import RouterLink from 'next/link';
import { Link as RouterLink } from 'react-router-dom';
import { textColor, linkHighlight } from '../../styles/Themes';
interface StyledProps {
@ -11,10 +11,38 @@ interface StyledProps {
fullWidth?: boolean;
}
const StyledSpan = styled.span<StyledProps>`
cursor: pointer;
color: ${({ fontColor, inheritColor }) =>
inheritColor ? 'inherit' : (fontColor ?? textColor)};
text-decoration: none;
${({ fullWidth }: StyledProps) =>
fullWidth &&
css`
width: 100%;
`};
:hover {
background: linear-gradient(
to bottom,
${({ underline }: StyledProps) => underline ?? linkHighlight} 0%,
${({ underline }: StyledProps) => underline ?? linkHighlight} 100%
);
background-position: 0 100%;
background-size: 2px 2px;
background-repeat: repeat-x;
}
`;
const NoStylingSpan = styled.span`
cursor: pointer;
text-decoration: none;
`;
const StyledLink = styled.a<StyledProps>`
cursor: pointer;
color: ${({ fontColor, inheritColor }) =>
inheritColor ? 'inherit' : fontColor ?? textColor};
inheritColor ? 'inherit' : (fontColor ?? textColor)};
text-decoration: none;
${({ fullWidth }: StyledProps) =>
fullWidth &&
@ -51,7 +79,7 @@ interface LinkProps {
children?: ReactNode;
}
export const Link: React.FC<LinkProps> = ({
export const Link: FC<LinkProps> = ({
children,
href,
to,
@ -81,9 +109,10 @@ export const Link: React.FC<LinkProps> = ({
}
if (to) {
const CorrectSpan = noStyling ? NoStylingSpan : StyledSpan;
return (
<RouterLink href={to} passHref legacyBehavior>
<CorrectLink {...props}>{children}</CorrectLink>
<RouterLink to={to} style={{ textDecoration: 'none' }}>
<CorrectSpan {...props}>{children}</CorrectSpan>
</RouterLink>
);
}

View file

@ -1,5 +1,4 @@
import React from 'react';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { Loader2 } from 'lucide-react';
import styled from 'styled-components';
import { CardWithTitle, CardTitle, SubTitle, Card } from '../generic/Styled';
import { themeColors } from '../../styles/Themes';
@ -34,7 +33,11 @@ export const LoadingCard = ({
if (noCard) {
return (
<Loading loadingHeight={loadingHeight}>
<ScaleLoader height={20} color={loadingColor} />
<Loader2
className="animate-spin"
size={20}
style={{ color: loadingColor }}
/>
</Loading>
);
}
@ -43,7 +46,11 @@ export const LoadingCard = ({
return (
<Card>
<Loading loadingHeight={loadingHeight}>
<ScaleLoader height={20} color={loadingColor} />
<Loader2
className="animate-spin"
size={20}
style={{ color: loadingColor }}
/>
</Loading>
</Card>
);
@ -56,7 +63,11 @@ export const LoadingCard = ({
</CardTitle>
<Card>
<Loading loadingHeight={loadingHeight}>
<ScaleLoader height={20} color={loadingColor} />
<Loader2
className="animate-spin"
size={20}
style={{ color: loadingColor }}
/>
</Loading>
</Card>
</CardWithTitle>

View file

@ -1,4 +1,3 @@
import React from 'react';
import styled from 'styled-components';
import { progressBackground } from '../../styles/Themes';

View file

@ -1,17 +1,15 @@
import React, { FC, ReactNode, useEffect } from 'react';
import { LogOut } from 'react-feather';
import { useLogoutMutation } from '../../../src/graphql/mutations/__generated__/logout.generated';
import { FC, ReactNode, useEffect } from 'react';
import { LogOut } from 'lucide-react';
import { useLogoutMutation } from '@/graphql/mutations/__generated__/logout.generated';
import { useApolloClient } from '@apollo/client';
import { HeaderNavButton } from '../../../src/layouts/header/Header.styled';
import { HeaderNavButton } from '@/layouts/header/Header.styled';
import styled from 'styled-components';
import { themeColors } from '../../../src/styles/Themes';
import ScaleLoader from 'react-spinners/ScaleLoader';
import getConfig from 'next/config';
import { themeColors } from '@/styles/Themes';
import { Loader2 } from 'lucide-react';
import { config } from '../../config/thunderhubConfig';
import { safeRedirect } from '../../utils/url';
import { useChatDispatch } from '../../context/ChatContext';
const { publicRuntimeConfig } = getConfig();
const { logoutUrl, basePath } = publicRuntimeConfig;
const Logout = styled.button`
cursor: pointer;
text-decoration: none;
@ -39,14 +37,21 @@ export const LogoutWrapper: FC<{ children?: ReactNode }> = ({ children }) => {
dispatchChat({ type: 'disconnected' });
client.clearStore();
window.location.href = logoutUrl || `${basePath}/login`;
safeRedirect(
config.logoutUrl || `${config.basePath}/login`,
`${config.basePath}/login`
);
}
}, [data, dispatchChat, client]);
if (loading) {
return (
<LogoutWrapperStyled>
<ScaleLoader height={14} width={1} color={themeColors.blue3} />
<Loader2
className="animate-spin"
size={14}
style={{ color: themeColors.blue3 }}
/>
</LogoutWrapperStyled>
);
}
@ -72,7 +77,10 @@ export const LogoutButton = () => {
dispatchChat({ type: 'disconnected' });
client.clearStore();
window.location.href = logoutUrl || `${basePath}/login`;
safeRedirect(
config.logoutUrl || `${config.basePath}/login`,
`${config.basePath}/login`
);
}
}, [data, dispatchChat, client]);
@ -80,7 +88,11 @@ export const LogoutButton = () => {
return (
<Logout>
<HeaderNavButton>
<ScaleLoader height={14} width={1} color={themeColors.blue3} />
<Loader2
className="animate-spin"
size={14}
style={{ color: themeColors.blue3 }}
/>
</HeaderNavButton>
</Logout>
);

View file

@ -1,7 +1,5 @@
import React, { ReactNode } from 'react';
import { css } from 'styled-components';
import ReactModal from 'styled-react-modal';
import { cardColor, mediaWidths, themeColors } from '../../styles/Themes';
import { ReactNode } from 'react';
import { Dialog, DialogContent } from '../ui/dialog';
interface ModalProps {
children: ReactNode;
@ -10,55 +8,30 @@ interface ModalProps {
closeCallback: () => void;
}
const generalCSS = css`
position: absolute;
top: 50%;
left: 50%;
transform: translateY(-50%) translateX(-50%);
background-color: ${cardColor};
padding: 20px;
border-radius: 5px;
outline: none;
max-height: 80%;
overflow-y: auto;
border: 1px solid ${themeColors.grey8};
@media (${mediaWidths.mobile}) {
/* top: 100%; */
border-radius: 0px;
/* transform: translateY(-100%) translateX(-50%); */
width: 100%;
min-width: 325px;
max-height: 100%;
}
`;
const StyleModal = ReactModal.styled`
${generalCSS}
min-width: 578px;
`;
const StyleModalSmall = ReactModal.styled`
${generalCSS}
background-color: ${themeColors.white};
`;
const Modal = ({
children,
isOpen,
noMinWidth = false,
closeCallback,
}: ModalProps) => {
const Styled = noMinWidth ? StyleModalSmall : StyleModal;
return (
<Styled
isOpen={isOpen}
onBackgroundClick={closeCallback}
onEscapeKeydown={closeCallback}
<Dialog
open={isOpen}
onOpenChange={open => {
if (!open) closeCallback();
}}
>
{children}
</Styled>
<DialogContent
showCloseButton={false}
className={
noMinWidth
? 'max-h-[80vh] overflow-y-auto'
: 'max-h-[80vh] overflow-y-auto sm:max-w-[578px]'
}
>
{children}
</DialogContent>
</Dialog>
);
};

View file

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { toast } from 'react-toastify';
import { useUpdateFeesMutation } from '../../../../src/graphql/mutations/__generated__/updateFees.generated';
import { getErrorContent } from '../../../../src/utils/error';
import { InputWithDeco } from '../../../../src/components/input/InputWithDeco';
import { ColorButton } from '../../../../src/components/buttons/colorButton/ColorButton';
import { Input } from '../../../../src/components/input';
import { useState } from 'react';
import toast from 'react-hot-toast';
import { useUpdateFeesMutation } from '@/graphql/mutations/__generated__/updateFees.generated';
import { getErrorContent } from '@/utils/error';
import { InputWithDeco } from '@/components/input/InputWithDeco';
import { ColorButton } from '@/components/buttons/colorButton/ColorButton';
import { Input } from '@/components/input';
import { SingleLine, SubTitle, Sub4Title } from '../../generic/Styled';
type ChangeDetailsType = {
@ -55,9 +55,11 @@ export const ChangeDetails = ({
toast.error(getErrorContent(error));
},
onCompleted: data => {
data.updateFees
? toast.success('Channel policy updated')
: toast.error('Error updating channel policy');
if (data.updateFees) {
toast.success('Channel policy updated');
} else {
toast.error('Error updating channel policy');
}
},
refetchQueries: ['GetChannels', 'ChannelFees'],
});

View file

@ -1,13 +1,13 @@
import React, { useState } from 'react';
import { AlertTriangle } from 'react-feather';
import { useState } from 'react';
import { AlertTriangle } from 'lucide-react';
import styled from 'styled-components';
import { toast } from 'react-toastify';
import { useCloseChannelMutation } from '../../../../src/graphql/mutations/__generated__/closeChannel.generated';
import { useBitcoinFees } from '../../../../src/hooks/UseBitcoinFees';
import { useConfigState } from '../../../../src/context/ConfigContext';
import { renderLine } from '../../../../src/components/generic/helpers';
import { InputWithDeco } from '../../../../src/components/input/InputWithDeco';
import { chartColors } from '../../../../src/styles/Themes';
import toast from 'react-hot-toast';
import { useCloseChannelMutation } from '@/graphql/mutations/__generated__/closeChannel.generated';
import { useBitcoinFees } from '@/hooks/UseBitcoinFees';
import { useConfigState } from '@/context/ConfigContext';
import { renderLine } from '@/components/generic/helpers';
import { InputWithDeco } from '@/components/input/InputWithDeco';
import { chartColors } from '@/styles/Themes';
import {
Separation,
SingleLine,
@ -56,7 +56,7 @@ export const CloseChannel = ({
const [closeChannel, { loading }] = useCloseChannelMutation({
onCompleted: () => {
toast.success('Channel Closed');
() => setIsConfirmed(false);
setIsConfirmed(false);
callback?.();
},
onError: error => toast.error(getErrorContent(error)),

View file

@ -1,8 +1,7 @@
import React from 'react';
import { AlertTriangle } from 'react-feather';
import { AlertTriangle } from 'lucide-react';
import styled from 'styled-components';
import { toast } from 'react-toastify';
import { useRemovePeerMutation } from '../../../../src/graphql/mutations/__generated__/removePeer.generated';
import toast from 'react-hot-toast';
import { useRemovePeerMutation } from '@/graphql/mutations/__generated__/removePeer.generated';
import { SubTitle } from '../../generic/Styled';
import { getErrorContent } from '../../../utils/error';
import { ColorButton } from '../../buttons/colorButton/ColorButton';

View file

@ -1,5 +1,5 @@
import styled from 'styled-components';
import { chartColors, mediaWidths } from '../../../src/styles/Themes';
import { chartColors, mediaWidths } from '@/styles/Themes';
export const BetaNotification = styled.div`
width: 100%;

View file

@ -1,4 +1,3 @@
import React from 'react';
import { useConfigState } from '../../context/ConfigContext';
import { getValue } from '../../utils/helpers';
import { usePriceState } from '../../context/PriceContext';

View file

@ -1,4 +1,4 @@
import React, { ReactNode } from 'react';
import { FC, Fragment, ReactNode } from 'react';
import styled, { css } from 'styled-components';
import { ThemeSet } from 'styled-theming';
import { backgroundColor, mediaWidths } from '../../styles/Themes';
@ -47,14 +47,14 @@ type SectionProps = {
children?: ReactNode;
};
export const Section: React.FC<SectionProps> = ({
export const Section: FC<SectionProps> = ({
fixedWidth = false,
children,
color,
textColor,
padding,
}) => {
const Fixed = fixedWidth ? FixedWidth : React.Fragment;
const Fixed = fixedWidth ? FixedWidth : Fragment;
return (
<FullWidth padding={padding} sectionColor={color} textColor={textColor}>

View file

@ -1,6 +1,6 @@
import styled from 'styled-components';
import { mediaWidths, themeColors } from '../../../src/styles/Themes';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { mediaWidths, themeColors } from '@/styles/Themes';
import { Loader2 } from 'lucide-react';
import { SingleLine } from '../generic/Styled';
import { Select, SelectWithValue, ValueProp } from '.';
import { FC, ReactNode } from 'react';
@ -33,7 +33,6 @@ const InputLine = styled(SingleLine)`
type InputWithDecoProps = {
title: string;
options: ValueProp[];
isMulti?: boolean;
noInput?: boolean;
loading?: boolean;
maxWidth?: string;
@ -46,7 +45,6 @@ export const SelectWithDeco: FC<InputWithDecoProps> = ({
title,
noInput,
options,
isMulti,
loading,
maxWidth,
callback,
@ -54,11 +52,16 @@ export const SelectWithDeco: FC<InputWithDecoProps> = ({
const renderContent = () => {
switch (true) {
case loading:
return <ScaleLoader height={20} color={themeColors.blue3} />;
return (
<Loader2
className="animate-spin"
size={20}
style={{ color: themeColors.blue3 }}
/>
);
case !noInput:
return (
<Select
isMulti={isMulti}
maxWidth={maxWidth || '500px'}
options={options}
callback={callback}
@ -83,7 +86,6 @@ type InputWithDecoAndValueProps = {
title: string;
value: ValueProp | undefined;
options: ValueProp[];
isMulti?: boolean;
noInput?: boolean;
loading?: boolean;
callback: (value: ValueProp[]) => void;
@ -95,7 +97,6 @@ export const SelectWithDecoAndValue: React.FC<InputWithDecoAndValueProps> = ({
title,
noInput,
options,
isMulti,
loading,
callback,
value,
@ -103,11 +104,16 @@ export const SelectWithDecoAndValue: React.FC<InputWithDecoAndValueProps> = ({
const renderContent = () => {
switch (true) {
case loading:
return <ScaleLoader height={20} color={themeColors.blue3} />;
return (
<Loader2
className="animate-spin"
size={20}
style={{ color: themeColors.blue3 }}
/>
);
case !noInput:
return (
<SelectWithValue
isMulti={isMulti}
maxWidth={'500px'}
options={options}
callback={callback}

View file

@ -1,97 +1,4 @@
import React, { useId } from 'react';
import ReactSelect from 'react-select';
import styled, { css } from 'styled-components';
import {
inputBackgroundColor,
textColor,
inputBorderColor,
themeColors,
selectColors,
} from '../../styles/Themes';
type WrapperProps = {
maxWidth?: string;
minWidth?: string;
fullWidth?: boolean;
};
const StyledWrapper = styled.div<WrapperProps>`
${({ maxWidth }) =>
maxWidth &&
css`
max-width: ${maxWidth};
`}
${({ minWidth }) =>
minWidth &&
css`
min-width: ${minWidth};
`}
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
`;
const StyledSelect = styled(ReactSelect)`
& .Select__control {
cursor: pointer;
background-color: ${inputBackgroundColor};
border: 1px solid ${inputBorderColor};
font-size: 14px;
& .Select__control--is-focused {
border: 1px solid ${themeColors.blue2};
}
& .Select__single-value {
color: ${textColor};
}
}
& .Select__menu {
font-size: 14px;
color: black;
& .Select__option {
cursor: pointer;
}
& .Select__option--is-selected {
background-color: ${themeColors.blue2};
}
}
`;
const StyledSmallSelect = styled(ReactSelect)`
& .Select__control {
cursor: pointer;
background-color: ${selectColors.smallBackground};
border: none;
font-size: 12px;
& .Select__control--is-focused {
border: 1px solid ${themeColors.blue2};
}
& .Select__single-value {
color: ${textColor};
}
& .Select__dropdown-indicator {
padding: 0 0 0 4px;
}
}
& .Select__menu {
font-size: 14px;
color: black;
& .Select__option {
cursor: pointer;
}
& .Select__option--is-selected {
background-color: ${themeColors.blue2};
}
}
`;
import { NativeSelect, NativeSelectOption } from '../ui/native-select';
export type ValueProp = {
value: string | number;
@ -100,41 +7,34 @@ export type ValueProp = {
type SelectProps = {
options: ValueProp[];
isMulti?: boolean;
maxWidth?: string;
callback: (value: ValueProp[]) => void;
};
export const Select = ({
isMulti,
options,
maxWidth,
callback,
}: SelectProps) => {
const handleChange: any = (value: ValueProp | ValueProp[]) => {
if (Array.isArray(value)) {
callback(value);
} else {
callback([value]);
}
};
export const Select = ({ options, maxWidth, callback }: SelectProps) => {
return (
<StyledWrapper maxWidth={maxWidth} fullWidth={true}>
<StyledSelect
instanceId={useId()}
isMulti={isMulti}
classNamePrefix={'Select'}
options={options}
onChange={handleChange}
/>
</StyledWrapper>
<div style={{ maxWidth: maxWidth || undefined, width: '100%' }}>
<NativeSelect
defaultValue=""
onChange={e => {
const option = options.find(o => String(o.value) === e.target.value);
if (option) callback([option]);
}}
>
<NativeSelectOption value="">Select...</NativeSelectOption>
{options.map(opt => (
<NativeSelectOption key={String(opt.value)} value={String(opt.value)}>
{String(opt.label)}
</NativeSelectOption>
))}
</NativeSelect>
</div>
);
};
type SelectWithValueProps = {
options: ValueProp[];
value: ValueProp | undefined;
isMulti?: boolean;
maxWidth?: string;
minWidth?: string;
isClearable?: boolean;
@ -142,7 +42,6 @@ type SelectWithValueProps = {
};
export const SelectWithValue = ({
isMulti,
options,
maxWidth,
minWidth,
@ -150,54 +49,64 @@ export const SelectWithValue = ({
value,
isClearable = true,
}: SelectWithValueProps) => {
const handleChange: any = (value: ValueProp | ValueProp[]) => {
if (Array.isArray(value)) {
callback(value);
} else {
callback([value]);
}
};
return (
<StyledWrapper maxWidth={maxWidth} minWidth={minWidth} fullWidth={false}>
<StyledSelect
instanceId={useId()}
isMulti={isMulti}
classNamePrefix={'Select'}
options={options}
onChange={handleChange}
value={value || null}
isClearable={isClearable}
/>
</StyledWrapper>
<div style={{ maxWidth, minWidth, width: maxWidth ? undefined : 'auto' }}>
<NativeSelect
value={value ? String(value.value) : ''}
onChange={e => {
const selectedValue = e.target.value;
if (!selectedValue) {
callback([]);
return;
}
const option = options.find(o => String(o.value) === selectedValue);
if (option) callback([option]);
}}
>
{(isClearable || !value) && (
<NativeSelectOption value="">Select...</NativeSelectOption>
)}
{options.map(opt => (
<NativeSelectOption key={String(opt.value)} value={String(opt.value)}>
{String(opt.label)}
</NativeSelectOption>
))}
</NativeSelect>
</div>
);
};
export const SmallSelectWithValue = ({
isMulti,
options,
maxWidth,
callback,
value,
isClearable = true,
}: SelectWithValueProps) => {
const handleChange: any = (value: ValueProp | ValueProp[]) => {
if (Array.isArray(value)) {
callback(value);
} else {
callback([value]);
}
};
return (
<StyledWrapper maxWidth={maxWidth} fullWidth={true}>
<StyledSmallSelect
instanceId={useId()}
isMulti={isMulti}
classNamePrefix={'Select'}
options={options}
onChange={handleChange}
value={value || null}
isClearable={isClearable}
/>
</StyledWrapper>
<div style={{ maxWidth, width: '100%' }}>
<NativeSelect
size="sm"
value={value ? String(value.value) : ''}
onChange={e => {
const selectedValue = e.target.value;
if (!selectedValue) {
callback([]);
return;
}
const option = options.find(o => String(o.value) === selectedValue);
if (option) callback([option]);
}}
>
{(isClearable || !value) && (
<NativeSelectOption value="">Select...</NativeSelectOption>
)}
{options.map(opt => (
<NativeSelectOption key={String(opt.value)} value={String(opt.value)}>
{String(opt.label)}
</NativeSelectOption>
))}
</NativeSelect>
</div>
);
};

View file

@ -1,20 +1,17 @@
import React from 'react';
import { shorten } from '../../../../src/components/generic/helpers';
import { useGetChannelsWithPeersQuery } from '../../../../src/graphql/queries/__generated__/getChannels.generated';
import { shorten } from '@/components/generic/helpers';
import { useGetChannelsWithPeersQuery } from '@/graphql/queries/__generated__/getChannels.generated';
import { SelectWithDeco } from '../SelectWithDeco';
import { Channel } from '../../../graphql/types';
import { ValueProp } from '..';
type ChannelSelectProps = {
title: string;
isMulti?: boolean;
maxWidth?: string;
callback: (peer: Channel[]) => void;
};
export const ChannelSelect = ({
title,
isMulti,
maxWidth,
callback,
}: ChannelSelectProps) => {
@ -59,7 +56,6 @@ export const ChannelSelect = ({
return (
<SelectWithDeco
isMulti={isMulti}
loading={loading}
title={title}
options={options}

View file

@ -1,17 +1,15 @@
import React from 'react';
import { useGetPeersQuery } from '../../../../src/graphql/queries/__generated__/getPeers.generated';
import { shorten } from '../../../../src/components/generic/helpers';
import { Peer } from '../../../../src/graphql/types';
import { useGetPeersQuery } from '@/graphql/queries/__generated__/getPeers.generated';
import { shorten } from '@/components/generic/helpers';
import { Peer } from '@/graphql/types';
import { SelectWithDeco } from '../SelectWithDeco';
import { ValueProp } from '..';
type PeerSelectProps = {
title: string;
isMulti?: boolean;
callback: (peer: Peer[]) => void;
};
export const PeerSelect = ({ title, isMulti, callback }: PeerSelectProps) => {
export const PeerSelect = ({ title, callback }: PeerSelectProps) => {
const { data, loading } = useGetPeersQuery();
const peers = data?.getPeers || [];
@ -55,7 +53,6 @@ export const PeerSelect = ({ title, isMulti, callback }: PeerSelectProps) => {
return (
<SelectWithDeco
isMulti={isMulti}
loading={loading}
title={title}
options={options}

View file

@ -1,41 +1,4 @@
import ReactSlider from 'react-slider';
import {
sliderBackgroundColor,
sliderThumbColor,
themeColors,
} from '../../../src/styles/Themes';
import styled from 'styled-components';
const StyledSlider = styled(ReactSlider)`
max-width: 440px;
width: 100%;
height: 38px;
display: flex;
align-items: center;
outline: none;
`;
const StyledThumb = styled.div`
height: 24px;
width: 24px;
background-color: ${sliderThumbColor};
color: #fff;
border-radius: 50%;
cursor: grab;
`;
const Thumb = (props: any) => <StyledThumb {...props} />;
const StyledTrack = styled.div<{ index: number }>`
height: 8px;
background: ${({ index }) =>
index === 1 ? sliderBackgroundColor : themeColors.blue2};
border-radius: 8px;
`;
const Track = (props: any, state: any) => (
<StyledTrack {...props} index={state.index} />
);
import { Slider as ShadcnSlider } from '@/components/ui/slider';
type SliderProps = {
value: number;
@ -46,13 +9,12 @@ type SliderProps = {
export const Slider = ({ value, max, min, onChange }: SliderProps) => {
return (
<StyledSlider
value={value}
<ShadcnSlider
className="max-w-[440px]"
value={[value]}
max={max}
min={min}
renderTrack={Track}
renderThumb={Thumb}
onChange={value => value && typeof value === 'number' && onChange(value)}
onValueChange={values => onChange(values[0])}
/>
);
};

View file

@ -1,6 +1,5 @@
import * as React from 'react';
import styled from 'styled-components';
import { mediaWidths } from '../../../src/styles/Themes';
import { mediaWidths } from '@/styles/Themes';
const StyledSpacer = styled.div`
height: 32px;

View file

@ -10,7 +10,7 @@ import {
SortingState,
VisibilityState,
} from '@tanstack/react-table';
import { Settings, X } from 'react-feather';
import { Settings, X } from 'lucide-react';
import { separationColor } from '../../styles/Themes';
import { ColorButton } from '../buttons/colorButton/ColorButton';
import { ColumnConfigurations } from './ColumnConfigurations';

View file

@ -0,0 +1,64 @@
import { ComponentProps } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { Slot } from '@radix-ui/react-slot';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost:
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
'icon-xs': "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
'icon-sm': 'size-8',
'icon-lg': 'size-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
function Button({
className,
variant = 'default',
size = 'default',
asChild = false,
...props
}: ComponentProps<'button'> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
const Comp = asChild ? Slot.Root : 'button';
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
export { Button, buttonVariants };

View file

@ -0,0 +1,154 @@
import { ComponentProps } from 'react';
import { XIcon } from 'lucide-react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
function Dialog({ ...props }: ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
function DialogTrigger({
...props
}: ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}
function DialogPortal({
...props
}: ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
function DialogClose({
...props
}: ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}
function DialogOverlay({
className,
...props
}: ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className
)}
{...props}
/>
);
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg',
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
);
}
function DialogHeader({ className, ...props }: ComponentProps<'div'>) {
return (
<div
data-slot="dialog-header"
className={cn('flex flex-col gap-2 text-center sm:text-left', className)}
{...props}
/>
);
}
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: ComponentProps<'div'> & {
showCloseButton?: boolean;
}) {
return (
<div
data-slot="dialog-footer"
className={cn(
'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close asChild>
<Button variant="outline">Close</Button>
</DialogPrimitive.Close>
)}
</div>
);
}
function DialogTitle({
className,
...props
}: ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn('text-lg leading-none font-semibold', className)}
{...props}
/>
);
}
function DialogDescription({
className,
...props
}: ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};

View file

@ -0,0 +1,53 @@
import { ComponentProps } from 'react';
import { ChevronDownIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
function NativeSelect({
className,
size = 'default',
...props
}: Omit<ComponentProps<'select'>, 'size'> & { size?: 'sm' | 'default' }) {
return (
<div
className="group/native-select relative w-fit has-[select:disabled]:opacity-50"
data-slot="native-select-wrapper"
>
<select
data-slot="native-select"
data-size={size}
className={cn(
'border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 h-9 w-full min-w-0 appearance-none rounded-md border bg-transparent px-3 py-2 pr-9 text-sm shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed data-[size=sm]:h-8 data-[size=sm]:py-1',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className
)}
{...props}
/>
<ChevronDownIcon
className="text-muted-foreground pointer-events-none absolute top-1/2 right-3.5 size-4 -translate-y-1/2 opacity-50 select-none"
aria-hidden="true"
data-slot="native-select-icon"
/>
</div>
);
}
function NativeSelectOption({ ...props }: ComponentProps<'option'>) {
return <option data-slot="native-select-option" {...props} />;
}
function NativeSelectOptGroup({
className,
...props
}: ComponentProps<'optgroup'>) {
return (
<optgroup
data-slot="native-select-optgroup"
className={cn(className)}
{...props}
/>
);
}
export { NativeSelect, NativeSelectOptGroup, NativeSelectOption };

View file

@ -0,0 +1,61 @@
import { ComponentProps, useMemo } from 'react';
import * as SliderPrimitive from '@radix-ui/react-slider';
import { cn } from '@/lib/utils';
function Slider({
className,
defaultValue,
value,
min = 0,
max = 100,
...props
}: ComponentProps<typeof SliderPrimitive.Root>) {
const _values = useMemo(
() =>
Array.isArray(value)
? value
: Array.isArray(defaultValue)
? defaultValue
: [min, max],
[value, defaultValue, min, max]
);
return (
<SliderPrimitive.Root
data-slot="slider"
defaultValue={defaultValue}
value={value}
min={min}
max={max}
className={cn(
'relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',
className
)}
{...props}
>
<SliderPrimitive.Track
data-slot="slider-track"
className={cn(
'bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5'
)}
>
<SliderPrimitive.Range
data-slot="slider-range"
className={cn(
'bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full'
)}
/>
</SliderPrimitive.Track>
{Array.from({ length: _values.length }, (_, index) => (
<SliderPrimitive.Thumb
data-slot="slider-thumb"
key={index}
className="border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
/>
))}
</SliderPrimitive.Root>
);
}
export { Slider };

View file

@ -1,6 +1,5 @@
import * as React from 'react';
import { useGetLatestVersionQuery } from '../../../src/graphql/queries/__generated__/getLatestVersion.generated';
import getConfig from 'next/config';
import { useGetLatestVersionQuery } from '@/graphql/queries/__generated__/getLatestVersion.generated';
import { config } from '../../config/thunderhubConfig';
import styled from 'styled-components';
import { Link } from '../link/Link';
@ -17,10 +16,9 @@ const VersionBox = styled.div`
}
`;
const { publicRuntimeConfig } = getConfig();
const { npmVersion, noVersionCheck } = publicRuntimeConfig;
export const Version = () => {
const { npmVersion, noVersionCheck } = config;
const { data, loading, error } = useGetLatestVersionQuery({
skip: noVersionCheck,
});
@ -34,7 +32,7 @@ export const Version = () => {
}
const githubVersion = data.getLatestVersion.replace('v', '');
const version = githubVersion.split('.');
const version = githubVersion.split('.').map(Number);
const localVersion = npmVersion.split('.').map(Number);
const newVersionAvailable =

View file

@ -1,4 +1,4 @@
import React, { ReactNode } from 'react';
import { FC, ReactNode } from 'react';
import styled from 'styled-components';
import { mediaWidths } from '../../styles/Themes';
@ -20,10 +20,7 @@ interface ViewSwitchProps {
children?: ReactNode;
}
export const ViewSwitch: React.FC<ViewSwitchProps> = ({
hideMobile,
children,
}) => {
export const ViewSwitch: FC<ViewSwitchProps> = ({ hideMobile, children }) => {
return hideMobile ? (
<HideMobile>{children}</HideMobile>
) : (

View file

@ -0,0 +1,33 @@
interface ThunderhubConfig {
apiUrl: string;
basePath: string;
mempoolUrl: string;
defaultTheme: string;
defaultCurrency: string;
fetchPrices: boolean;
fetchFees: boolean;
disableLinks: boolean;
noVersionCheck: boolean;
logoutUrl: string;
disable2FA: boolean;
npmVersion: string;
}
export const config: ThunderhubConfig = {
apiUrl: '/graphql',
basePath: '',
mempoolUrl: 'https://mempool.space',
defaultTheme: 'dark',
defaultCurrency: 'sat',
fetchPrices: true,
fetchFees: true,
disableLinks: false,
noVersionCheck: false,
logoutUrl: '',
disable2FA: false,
npmVersion: '0.0.1',
};
export function initConfig(data: Partial<ThunderhubConfig>): void {
Object.assign(config, data);
}

View file

@ -1,57 +0,0 @@
import React, { ReactNode, createContext, useContext, useReducer } from 'react';
type State = {
hasToken: boolean;
};
type ActionType = {
type: 'change';
hasToken: boolean;
};
type Dispatch = (action: ActionType) => void;
export const StateContext = createContext<State | undefined>(undefined);
export const DispatchContext = createContext<Dispatch | undefined>(undefined);
const stateReducer = (state: State, action: ActionType): State => {
switch (action.type) {
case 'change':
return { hasToken: action.hasToken };
default:
return state;
}
};
const BaseProvider: React.FC<{
initialHasToken: boolean;
children?: ReactNode;
}> = ({ children, initialHasToken = false }) => {
const [state, dispatch] = useReducer(stateReducer, {
hasToken: initialHasToken,
});
return (
<DispatchContext.Provider value={dispatch}>
<StateContext.Provider value={state}>{children}</StateContext.Provider>
</DispatchContext.Provider>
);
};
const useBaseState = () => {
const context = useContext(StateContext);
if (context === undefined) {
throw new Error('useBaseState must be used within a BaseProvider');
}
return context;
};
const useBaseDispatch = () => {
const context = useContext(DispatchContext);
if (context === undefined) {
throw new Error('useBaseDispatch must be used within a BaseProvider');
}
return context;
};
export { BaseProvider, useBaseState, useBaseDispatch };

View file

@ -1,5 +1,5 @@
import React, { ReactNode, createContext, useContext, useReducer } from 'react';
import { Message } from '../../src/graphql/types';
import { FC, ReactNode, createContext, useContext, useReducer } from 'react';
import { Message } from '@/graphql/types';
export interface SentChatProps extends Message {
isSent?: boolean;
@ -92,7 +92,7 @@ const stateReducer = (state: State, action: ActionType): State => {
}
};
const ChatProvider: React.FC<{ children?: ReactNode }> = ({ children }) => {
const ChatProvider: FC<{ children?: ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(stateReducer, initialState);
return (

View file

@ -1,15 +1,16 @@
import React, {
import {
FC,
createContext,
useContext,
useReducer,
useEffect,
ReactNode,
} from 'react';
import getConfig from 'next/config';
import Cookies from 'js-cookie';
import { omit } from 'lodash';
import { config } from '../config/thunderhubConfig';
const themeTypes = ['dark', 'light', 'night'];
const themeTypes = ['dark', 'light'];
const currencyTypes = ['sat', 'btc', 'fiat'];
export type channelBarStyleTypes =
@ -39,7 +40,6 @@ export type maxSatValueType = 'auto' | 1000000 | 5000000 | 10000000 | 16777215;
type State = {
currency: string;
theme: string;
lnMarketsAuth: boolean;
sidebar: boolean;
fetchFees: boolean;
fetchPrices: boolean;
@ -67,7 +67,6 @@ type ActionType =
type: 'change' | 'initChange';
currency?: string;
theme?: string;
lnMarketsAuth?: boolean;
sidebar?: boolean;
fetchFees?: boolean;
fetchPrices?: boolean;
@ -91,33 +90,27 @@ type Dispatch = (action: ActionType) => void;
const StateContext = createContext<State | undefined>(undefined);
const DispatchContext = createContext<Dispatch | undefined>(undefined);
const { publicRuntimeConfig } = getConfig();
const {
defaultTheme: defT,
defaultCurrency: defC,
fetchPrices,
fetchFees,
} = publicRuntimeConfig;
const initialState: State = {
currency: currencyTypes.indexOf(defC) > -1 ? defC : 'sat',
theme: themeTypes.indexOf(defT) > -1 ? defT : 'dark',
lnMarketsAuth: false,
sidebar: true,
fetchFees,
fetchPrices,
displayValues: true,
hideFee: false,
hideNonVerified: false,
maxFee: 20,
chatPollingSpeed: 1000,
channelBarStyle: 'normal',
channelBarType: 'balance',
channelSort: 'none',
sortDirection: 'decrease',
extraColumns: 'none',
maxSatValue: 'auto',
useSatWord: false,
const getInitialState = (): State => {
const { defaultTheme: defT, defaultCurrency: defC } = config;
return {
currency: currencyTypes.indexOf(defC) > -1 ? defC : 'sat',
theme: themeTypes.indexOf(defT) > -1 ? defT : 'dark',
sidebar: true,
fetchFees: config.fetchFees,
fetchPrices: config.fetchPrices,
displayValues: true,
hideFee: false,
hideNonVerified: false,
maxFee: 20,
chatPollingSpeed: 1000,
channelBarStyle: 'normal',
channelBarType: 'balance',
channelSort: 'none',
sortDirection: 'decrease',
extraColumns: 'none',
maxSatValue: 'auto',
useSatWord: false,
};
};
const stateReducer = (state: State, action: ActionType): State => {
@ -134,10 +127,7 @@ const stateReducer = (state: State, action: ActionType): State => {
...state,
...settings,
};
localStorage.setItem(
'config',
JSON.stringify(omit(newState, 'theme', 'lnMarketsAuth'))
);
localStorage.setItem('config', JSON.stringify(omit(newState, 'theme')));
return newState;
}
case 'themeChange': {
@ -158,12 +148,12 @@ const stateReducer = (state: State, action: ActionType): State => {
}
};
const ConfigProvider: React.FC<ConfigInitProps> = ({
const ConfigProvider: FC<ConfigInitProps> = ({
children,
initialConfig = { theme: 'dark' },
}) => {
const [state, dispatch] = useReducer(stateReducer, {
...initialState,
...getInitialState(),
theme:
themeTypes.indexOf(initialConfig.theme) > -1
? initialConfig.theme

View file

@ -1,19 +1,14 @@
import React, { ReactNode } from 'react';
import { FC, ReactNode } from 'react';
import { PriceProvider } from './PriceContext';
import { ChatProvider } from './ChatContext';
import { RebalanceProvider } from './RebalanceContext';
import { DashProvider } from './DashContext';
import { NotificationProvider } from './NotificationContext';
export const ContextProvider: React.FC<{ children?: ReactNode }> = ({
children,
}) => (
export const ContextProvider: FC<{ children?: ReactNode }> = ({ children }) => (
<NotificationProvider>
<DashProvider>
<PriceProvider>
<ChatProvider>
<RebalanceProvider>{children}</RebalanceProvider>
</ChatProvider>
<ChatProvider>{children}</ChatProvider>
</PriceProvider>
</DashProvider>
</NotificationProvider>

View file

@ -1,4 +1,4 @@
import React, { ReactNode, createContext, useContext, useReducer } from 'react';
import { FC, ReactNode, createContext, useContext, useReducer } from 'react';
type State = {
modalType: string;
@ -23,7 +23,7 @@ const stateReducer = (state: State, action: ActionType): State => {
}
};
const DashProvider: React.FC<{ children?: ReactNode }> = ({ children }) => {
const DashProvider: FC<{ children?: ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(stateReducer, {
modalType: '',
});

View file

@ -1,4 +1,5 @@
import React, {
import {
FC,
createContext,
useContext,
useReducer,
@ -63,9 +64,7 @@ const stateReducer = (state: State, action: ActionType): State => {
}
};
const NotificationProvider: React.FC<{ children?: ReactNode }> = ({
children,
}) => {
const NotificationProvider: FC<{ children?: ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(stateReducer, initialState);
useEffect(() => {

View file

@ -1,4 +1,5 @@
import React, {
import {
FC,
createContext,
useContext,
useReducer,
@ -63,7 +64,7 @@ const stateReducer = (state: State, action: ActionType): State => {
}
};
const PriceProvider: React.FC<{ children?: ReactNode }> = ({ children }) => {
const PriceProvider: FC<{ children?: ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(stateReducer, initialState);
useEffect(() => {

View file

@ -1,77 +0,0 @@
import React, { ReactNode, createContext, useContext, useReducer } from 'react';
import { Channel } from '../../src/graphql/types';
type State = {
inChannel: Channel | null;
outChannel: Channel | null;
};
type ActionType =
| {
type: 'setIn';
channel: Channel | null;
}
| {
type: 'setOut';
channel: Channel | null;
}
| {
type: 'clear';
};
type Dispatch = (action: ActionType) => void;
export const StateContext = createContext<State | undefined>(undefined);
export const DispatchContext = createContext<Dispatch | undefined>(undefined);
const initialState: State = {
inChannel: null,
outChannel: null,
};
const stateReducer = (state: State, action: ActionType): State => {
switch (action.type) {
case 'setIn':
return { ...state, inChannel: action.channel };
case 'setOut':
return { ...state, outChannel: action.channel };
case 'clear':
return initialState;
default:
return state;
}
};
const RebalanceProvider: React.FC<{ children?: ReactNode }> = ({
children,
}) => {
const [state, dispatch] = useReducer(stateReducer, initialState);
return (
<DispatchContext.Provider value={dispatch}>
<StateContext.Provider value={state}>{children}</StateContext.Provider>
</DispatchContext.Provider>
);
};
const useRebalanceState = () => {
const context = useContext(StateContext);
if (context === undefined) {
throw new Error(
'useRebalanceState must be used within a RebalanceProvider'
);
}
return context;
};
const useRebalanceDispatch = () => {
const context = useContext(DispatchContext);
if (context === undefined) {
throw new Error(
'useRebalanceDispatch must be used within a RebalanceProvider'
);
}
return context;
};
export { RebalanceProvider, useRebalanceState, useRebalanceDispatch };

View file

@ -1,115 +0,0 @@
import React, { FC, ReactNode, useCallback, useRef, useState } from 'react';
import io from 'socket.io-client';
import { Socket } from 'socket.io-client';
import getConfig from 'next/config';
const { publicRuntimeConfig } = getConfig();
const { basePath } = publicRuntimeConfig;
type Connection = {
socket: Socket | undefined;
cleanup: () => void;
};
type CreateConnection = () => Connection;
type Status = 'connecting' | 'connected' | 'disconnected';
type Context = {
createConnection: CreateConnection;
getConnection: () => Socket | undefined;
getLastMessage: (forEvent: string) => any;
setLastMessage: (forEvent: string, message: any) => void;
registerSharedListener: (forEvent: string) => void;
getError: () => any;
setError: (error: any) => void;
getStatus: () => Status;
};
const SocketContext = React.createContext<Context | undefined>(undefined);
const SocketProvider: FC<{ authToken?: string; children?: ReactNode }> = ({
children,
authToken,
}) => {
const sockets = useRef<Socket | undefined>(undefined);
const [status, setStatus] = useState<Status>('disconnected');
const [error, setError] = useState<any>(undefined);
const [lastMessages, setLastMessages] = useState<Record<string, any>>({});
const createConnection = useCallback(() => {
const cleanup = () => {
sockets.current?.disconnect();
};
// Early return if the user has no authToken cookie
if (!authToken) {
return { socket: undefined, cleanup };
}
if (sockets.current) {
sockets.current.connect();
return { socket: sockets.current, cleanup };
}
const handleConnect = () => setStatus('connected');
const handleDisconnect = () => setStatus('disconnected');
const socket = io({
...(basePath ? { path: `${basePath}/socket.io` } : {}),
reconnectionAttempts: 5,
});
sockets.current = socket;
socket.on('error', (error: any) => setError(error));
socket.on('connect', handleConnect);
socket.on('disconnect', handleDisconnect);
return { socket, cleanup };
}, [authToken]);
const getLastMessage = (forEvent = '') => lastMessages[forEvent];
const setLastMessage = (forEvent: string, message: any) =>
setLastMessages(state => ({
...state,
[forEvent]: message,
}));
const getConnection = () => sockets.current;
const getStatus = () => status;
const getError = () => error;
const registerSharedListener = (forEvent = '') => {
if (!sockets.current) return;
if (sockets.current.hasListeners(forEvent)) return;
sockets.current.on(forEvent, (message: any) => {
setLastMessages(state => ({
...state,
[forEvent]: message,
}));
});
};
return (
<SocketContext.Provider
value={{
createConnection,
getConnection,
getLastMessage,
setLastMessage,
getError,
setError,
getStatus,
registerSharedListener,
}}
>
{children}
</SocketContext.Provider>
);
};
export { SocketProvider, SocketContext };

View file

@ -0,0 +1,99 @@
import {
createContext,
FC,
ReactNode,
useCallback,
useRef,
useState,
} from 'react';
import { config } from '../config/thunderhubConfig';
type Status = 'connecting' | 'connected' | 'disconnected';
type Connection = {
cleanup: () => void;
};
type Context = {
connect: () => Connection;
getLastMessage: (forEvent: string) => any;
getError: () => any;
getStatus: () => Status;
};
const SseContext = createContext<Context | undefined>(undefined);
const SseProvider: FC<{ children?: ReactNode }> = ({ children }) => {
const eventSource = useRef<EventSource | undefined>(undefined);
const [status, setStatus] = useState<Status>('disconnected');
const [error, setError] = useState<any>(undefined);
const [lastMessages, setLastMessages] = useState<Record<string, any>>({});
const connect = useCallback(() => {
const cleanup = () => {
eventSource.current?.close();
setStatus('disconnected');
};
if (
eventSource.current &&
eventSource.current.readyState !== EventSource.CLOSED
) {
return { cleanup };
}
setStatus('connecting');
const url = `${config.basePath || ''}/api/sse/events`;
const es = new EventSource(url, { withCredentials: true });
eventSource.current = es;
es.onopen = () => setStatus('connected');
es.onmessage = (event: MessageEvent) => {
if (!event.data) return;
try {
const parsed = JSON.parse(event.data);
const { event: eventName, data } = parsed;
if (eventName && data) {
setLastMessages(state => ({
...state,
[eventName]: data,
}));
}
} catch {
// heartbeat or invalid data, ignore
}
};
es.onerror = () => {
setError('SSE connection error');
setStatus('disconnected');
};
return { cleanup };
}, []);
const getLastMessage = (forEvent = '') => lastMessages[forEvent];
const getStatus = () => status;
const getError = () => error;
return (
<SseContext.Provider
value={{
connect,
getLastMessage,
getError,
getStatus,
}}
>
{children}
</SseContext.Provider>
);
};
export { SseProvider, SseContext };

View file

@ -1,150 +0,0 @@
import * as Types from '../../types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type BosRebalanceMutationVariables = Types.Exact<{
avoid?: Types.InputMaybe<
Array<Types.Scalars['String']['input']> | Types.Scalars['String']['input']
>;
in_through?: Types.InputMaybe<Types.Scalars['String']['input']>;
max_fee?: Types.InputMaybe<Types.Scalars['Float']['input']>;
max_fee_rate?: Types.InputMaybe<Types.Scalars['Float']['input']>;
max_rebalance?: Types.InputMaybe<Types.Scalars['Float']['input']>;
timeout_minutes?: Types.InputMaybe<Types.Scalars['Float']['input']>;
node?: Types.InputMaybe<Types.Scalars['String']['input']>;
out_through?: Types.InputMaybe<Types.Scalars['String']['input']>;
out_inbound?: Types.InputMaybe<Types.Scalars['Float']['input']>;
}>;
export type BosRebalanceMutation = {
__typename?: 'Mutation';
bosRebalance: {
__typename?: 'BosRebalanceResult';
increase?: {
__typename?: 'BosIncrease';
increased_inbound_on: string;
liquidity_inbound: string;
liquidity_inbound_opening?: string | null;
liquidity_inbound_pending?: string | null;
liquidity_outbound: string;
liquidity_outbound_opening?: string | null;
liquidity_outbound_pending?: string | null;
} | null;
decrease?: {
__typename?: 'BosDecrease';
decreased_inbound_on: string;
liquidity_inbound: string;
liquidity_inbound_opening?: string | null;
liquidity_inbound_pending?: string | null;
liquidity_outbound: string;
liquidity_outbound_opening?: string | null;
liquidity_outbound_pending?: string | null;
} | null;
result?: {
__typename?: 'BosResult';
rebalanced: string;
rebalance_fees_spent: string;
} | null;
};
};
export const BosRebalanceDocument = gql`
mutation BosRebalance(
$avoid: [String!]
$in_through: String
$max_fee: Float
$max_fee_rate: Float
$max_rebalance: Float
$timeout_minutes: Float
$node: String
$out_through: String
$out_inbound: Float
) {
bosRebalance(
avoid: $avoid
in_through: $in_through
max_fee: $max_fee
max_fee_rate: $max_fee_rate
max_rebalance: $max_rebalance
timeout_minutes: $timeout_minutes
node: $node
out_through: $out_through
out_inbound: $out_inbound
) {
increase {
increased_inbound_on
liquidity_inbound
liquidity_inbound_opening
liquidity_inbound_pending
liquidity_outbound
liquidity_outbound_opening
liquidity_outbound_pending
}
decrease {
decreased_inbound_on
liquidity_inbound
liquidity_inbound_opening
liquidity_inbound_pending
liquidity_outbound
liquidity_outbound_opening
liquidity_outbound_pending
}
result {
rebalanced
rebalance_fees_spent
}
}
}
`;
export type BosRebalanceMutationFn = Apollo.MutationFunction<
BosRebalanceMutation,
BosRebalanceMutationVariables
>;
/**
* __useBosRebalanceMutation__
*
* To run a mutation, you first call `useBosRebalanceMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useBosRebalanceMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [bosRebalanceMutation, { data, loading, error }] = useBosRebalanceMutation({
* variables: {
* avoid: // value for 'avoid'
* in_through: // value for 'in_through'
* max_fee: // value for 'max_fee'
* max_fee_rate: // value for 'max_fee_rate'
* max_rebalance: // value for 'max_rebalance'
* timeout_minutes: // value for 'timeout_minutes'
* node: // value for 'node'
* out_through: // value for 'out_through'
* out_inbound: // value for 'out_inbound'
* },
* });
*/
export function useBosRebalanceMutation(
baseOptions?: Apollo.MutationHookOptions<
BosRebalanceMutation,
BosRebalanceMutationVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
BosRebalanceMutation,
BosRebalanceMutationVariables
>(BosRebalanceDocument, options);
}
export type BosRebalanceMutationHookResult = ReturnType<
typeof useBosRebalanceMutation
>;
export type BosRebalanceMutationResult =
Apollo.MutationResult<BosRebalanceMutation>;
export type BosRebalanceMutationOptions = Apollo.BaseMutationOptions<
BosRebalanceMutation,
BosRebalanceMutationVariables
>;

View file

@ -1,69 +0,0 @@
import * as Types from '../../types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type CreateBaseInvoiceMutationVariables = Types.Exact<{
amount: Types.Scalars['Float']['input'];
}>;
export type CreateBaseInvoiceMutation = {
__typename?: 'Mutation';
createBaseInvoice: {
__typename?: 'BaseInvoice';
request: string;
id: string;
};
};
export const CreateBaseInvoiceDocument = gql`
mutation CreateBaseInvoice($amount: Float!) {
createBaseInvoice(amount: $amount) {
request
id
}
}
`;
export type CreateBaseInvoiceMutationFn = Apollo.MutationFunction<
CreateBaseInvoiceMutation,
CreateBaseInvoiceMutationVariables
>;
/**
* __useCreateBaseInvoiceMutation__
*
* To run a mutation, you first call `useCreateBaseInvoiceMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateBaseInvoiceMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createBaseInvoiceMutation, { data, loading, error }] = useCreateBaseInvoiceMutation({
* variables: {
* amount: // value for 'amount'
* },
* });
*/
export function useCreateBaseInvoiceMutation(
baseOptions?: Apollo.MutationHookOptions<
CreateBaseInvoiceMutation,
CreateBaseInvoiceMutationVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
CreateBaseInvoiceMutation,
CreateBaseInvoiceMutationVariables
>(CreateBaseInvoiceDocument, options);
}
export type CreateBaseInvoiceMutationHookResult = ReturnType<
typeof useCreateBaseInvoiceMutation
>;
export type CreateBaseInvoiceMutationResult =
Apollo.MutationResult<CreateBaseInvoiceMutation>;
export type CreateBaseInvoiceMutationOptions = Apollo.BaseMutationOptions<
CreateBaseInvoiceMutation,
CreateBaseInvoiceMutationVariables
>;

View file

@ -1,80 +0,0 @@
import * as Types from '../../types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type CreateThunderPointsMutationVariables = Types.Exact<{
id: Types.Scalars['String']['input'];
alias: Types.Scalars['String']['input'];
uris:
| Array<Types.Scalars['String']['input']>
| Types.Scalars['String']['input'];
public_key: Types.Scalars['String']['input'];
}>;
export type CreateThunderPointsMutation = {
__typename?: 'Mutation';
createThunderPoints: boolean;
};
export const CreateThunderPointsDocument = gql`
mutation CreateThunderPoints(
$id: String!
$alias: String!
$uris: [String!]!
$public_key: String!
) {
createThunderPoints(
id: $id
alias: $alias
uris: $uris
public_key: $public_key
)
}
`;
export type CreateThunderPointsMutationFn = Apollo.MutationFunction<
CreateThunderPointsMutation,
CreateThunderPointsMutationVariables
>;
/**
* __useCreateThunderPointsMutation__
*
* To run a mutation, you first call `useCreateThunderPointsMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateThunderPointsMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createThunderPointsMutation, { data, loading, error }] = useCreateThunderPointsMutation({
* variables: {
* id: // value for 'id'
* alias: // value for 'alias'
* uris: // value for 'uris'
* public_key: // value for 'public_key'
* },
* });
*/
export function useCreateThunderPointsMutation(
baseOptions?: Apollo.MutationHookOptions<
CreateThunderPointsMutation,
CreateThunderPointsMutationVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
CreateThunderPointsMutation,
CreateThunderPointsMutationVariables
>(CreateThunderPointsDocument, options);
}
export type CreateThunderPointsMutationHookResult = ReturnType<
typeof useCreateThunderPointsMutation
>;
export type CreateThunderPointsMutationResult =
Apollo.MutationResult<CreateThunderPointsMutation>;
export type CreateThunderPointsMutationOptions = Apollo.BaseMutationOptions<
CreateThunderPointsMutation,
CreateThunderPointsMutationVariables
>;

View file

@ -1,238 +0,0 @@
import * as Types from '../../types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type LnMarketsLoginMutationVariables = Types.Exact<{
[key: string]: never;
}>;
export type LnMarketsLoginMutation = {
__typename?: 'Mutation';
lnMarketsLogin: {
__typename?: 'AuthResponse';
status: string;
message: string;
};
};
export type LnMarketsWithdrawMutationVariables = Types.Exact<{
amount: Types.Scalars['Float']['input'];
}>;
export type LnMarketsWithdrawMutation = {
__typename?: 'Mutation';
lnMarketsWithdraw: boolean;
};
export type LnMarketsDepositMutationVariables = Types.Exact<{
amount: Types.Scalars['Float']['input'];
}>;
export type LnMarketsDepositMutation = {
__typename?: 'Mutation';
lnMarketsDeposit: boolean;
};
export type LnMarketsLogoutMutationVariables = Types.Exact<{
[key: string]: never;
}>;
export type LnMarketsLogoutMutation = {
__typename?: 'Mutation';
lnMarketsLogout: boolean;
};
export const LnMarketsLoginDocument = gql`
mutation LnMarketsLogin {
lnMarketsLogin {
status
message
}
}
`;
export type LnMarketsLoginMutationFn = Apollo.MutationFunction<
LnMarketsLoginMutation,
LnMarketsLoginMutationVariables
>;
/**
* __useLnMarketsLoginMutation__
*
* To run a mutation, you first call `useLnMarketsLoginMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useLnMarketsLoginMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [lnMarketsLoginMutation, { data, loading, error }] = useLnMarketsLoginMutation({
* variables: {
* },
* });
*/
export function useLnMarketsLoginMutation(
baseOptions?: Apollo.MutationHookOptions<
LnMarketsLoginMutation,
LnMarketsLoginMutationVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
LnMarketsLoginMutation,
LnMarketsLoginMutationVariables
>(LnMarketsLoginDocument, options);
}
export type LnMarketsLoginMutationHookResult = ReturnType<
typeof useLnMarketsLoginMutation
>;
export type LnMarketsLoginMutationResult =
Apollo.MutationResult<LnMarketsLoginMutation>;
export type LnMarketsLoginMutationOptions = Apollo.BaseMutationOptions<
LnMarketsLoginMutation,
LnMarketsLoginMutationVariables
>;
export const LnMarketsWithdrawDocument = gql`
mutation LnMarketsWithdraw($amount: Float!) {
lnMarketsWithdraw(amount: $amount)
}
`;
export type LnMarketsWithdrawMutationFn = Apollo.MutationFunction<
LnMarketsWithdrawMutation,
LnMarketsWithdrawMutationVariables
>;
/**
* __useLnMarketsWithdrawMutation__
*
* To run a mutation, you first call `useLnMarketsWithdrawMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useLnMarketsWithdrawMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [lnMarketsWithdrawMutation, { data, loading, error }] = useLnMarketsWithdrawMutation({
* variables: {
* amount: // value for 'amount'
* },
* });
*/
export function useLnMarketsWithdrawMutation(
baseOptions?: Apollo.MutationHookOptions<
LnMarketsWithdrawMutation,
LnMarketsWithdrawMutationVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
LnMarketsWithdrawMutation,
LnMarketsWithdrawMutationVariables
>(LnMarketsWithdrawDocument, options);
}
export type LnMarketsWithdrawMutationHookResult = ReturnType<
typeof useLnMarketsWithdrawMutation
>;
export type LnMarketsWithdrawMutationResult =
Apollo.MutationResult<LnMarketsWithdrawMutation>;
export type LnMarketsWithdrawMutationOptions = Apollo.BaseMutationOptions<
LnMarketsWithdrawMutation,
LnMarketsWithdrawMutationVariables
>;
export const LnMarketsDepositDocument = gql`
mutation LnMarketsDeposit($amount: Float!) {
lnMarketsDeposit(amount: $amount)
}
`;
export type LnMarketsDepositMutationFn = Apollo.MutationFunction<
LnMarketsDepositMutation,
LnMarketsDepositMutationVariables
>;
/**
* __useLnMarketsDepositMutation__
*
* To run a mutation, you first call `useLnMarketsDepositMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useLnMarketsDepositMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [lnMarketsDepositMutation, { data, loading, error }] = useLnMarketsDepositMutation({
* variables: {
* amount: // value for 'amount'
* },
* });
*/
export function useLnMarketsDepositMutation(
baseOptions?: Apollo.MutationHookOptions<
LnMarketsDepositMutation,
LnMarketsDepositMutationVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
LnMarketsDepositMutation,
LnMarketsDepositMutationVariables
>(LnMarketsDepositDocument, options);
}
export type LnMarketsDepositMutationHookResult = ReturnType<
typeof useLnMarketsDepositMutation
>;
export type LnMarketsDepositMutationResult =
Apollo.MutationResult<LnMarketsDepositMutation>;
export type LnMarketsDepositMutationOptions = Apollo.BaseMutationOptions<
LnMarketsDepositMutation,
LnMarketsDepositMutationVariables
>;
export const LnMarketsLogoutDocument = gql`
mutation LnMarketsLogout {
lnMarketsLogout
}
`;
export type LnMarketsLogoutMutationFn = Apollo.MutationFunction<
LnMarketsLogoutMutation,
LnMarketsLogoutMutationVariables
>;
/**
* __useLnMarketsLogoutMutation__
*
* To run a mutation, you first call `useLnMarketsLogoutMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useLnMarketsLogoutMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [lnMarketsLogoutMutation, { data, loading, error }] = useLnMarketsLogoutMutation({
* variables: {
* },
* });
*/
export function useLnMarketsLogoutMutation(
baseOptions?: Apollo.MutationHookOptions<
LnMarketsLogoutMutation,
LnMarketsLogoutMutationVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
LnMarketsLogoutMutation,
LnMarketsLogoutMutationVariables
>(LnMarketsLogoutDocument, options);
}
export type LnMarketsLogoutMutationHookResult = ReturnType<
typeof useLnMarketsLogoutMutation
>;
export type LnMarketsLogoutMutationResult =
Apollo.MutationResult<LnMarketsLogoutMutation>;
export type LnMarketsLogoutMutationOptions = Apollo.BaseMutationOptions<
LnMarketsLogoutMutation,
LnMarketsLogoutMutationVariables
>;

View file

@ -1,50 +0,0 @@
import { gql } from '@apollo/client';
export const BOS_REBALANCE = gql`
mutation BosRebalance(
$avoid: [String!]
$in_through: String
$max_fee: Float
$max_fee_rate: Float
$max_rebalance: Float
$timeout_minutes: Float
$node: String
$out_through: String
$out_inbound: Float
) {
bosRebalance(
avoid: $avoid
in_through: $in_through
max_fee: $max_fee
max_fee_rate: $max_fee_rate
max_rebalance: $max_rebalance
timeout_minutes: $timeout_minutes
node: $node
out_through: $out_through
out_inbound: $out_inbound
) {
increase {
increased_inbound_on
liquidity_inbound
liquidity_inbound_opening
liquidity_inbound_pending
liquidity_outbound
liquidity_outbound_opening
liquidity_outbound_pending
}
decrease {
decreased_inbound_on
liquidity_inbound
liquidity_inbound_opening
liquidity_inbound_pending
liquidity_outbound
liquidity_outbound_opening
liquidity_outbound_pending
}
result {
rebalanced
rebalance_fees_spent
}
}
}
`;

View file

@ -1,10 +0,0 @@
import { gql } from '@apollo/client';
export const CREATE_BASE_INVOICE = gql`
mutation CreateBaseInvoice($amount: Float!) {
createBaseInvoice(amount: $amount) {
request
id
}
}
`;

View file

@ -1,17 +0,0 @@
import { gql } from '@apollo/client';
export const CREATE_THUNDER_POINTS = gql`
mutation CreateThunderPoints(
$id: String!
$alias: String!
$uris: [String!]!
$public_key: String!
) {
createThunderPoints(
id: $id
alias: $alias
uris: $uris
public_key: $public_key
)
}
`;

View file

@ -1,28 +0,0 @@
import { gql } from '@apollo/client';
export const LN_MARKETS_LOGIN = gql`
mutation LnMarketsLogin {
lnMarketsLogin {
status
message
}
}
`;
export const LN_MARKETS_WITHDRAW = gql`
mutation LnMarketsWithdraw($amount: Float!) {
lnMarketsWithdraw(amount: $amount)
}
`;
export const LN_MARKETS_DEPOSIT = gql`
mutation LnMarketsDeposit($amount: Float!) {
lnMarketsDeposit(amount: $amount)
}
`;
export const LN_MARKETS_LOGOUT = gql`
mutation LnMarketsLogout {
lnMarketsLogout
}
`;

View file

@ -83,7 +83,11 @@ export function useDecodeRequestQuery(
baseOptions: Apollo.QueryHookOptions<
DecodeRequestQuery,
DecodeRequestQueryVariables
>
> &
(
| { variables: DecodeRequestQueryVariables; skip?: boolean }
| { skip: boolean }
)
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<DecodeRequestQuery, DecodeRequestQueryVariables>(
@ -103,13 +107,39 @@ export function useDecodeRequestLazyQuery(
options
);
}
// @ts-ignore
export function useDecodeRequestSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions<
DecodeRequestQuery,
DecodeRequestQueryVariables
>
): Apollo.UseSuspenseQueryResult<
DecodeRequestQuery,
DecodeRequestQueryVariables
>;
export function useDecodeRequestSuspenseQuery(
baseOptions?:
| Apollo.SkipToken
| Apollo.SuspenseQueryHookOptions<
DecodeRequestQuery,
DecodeRequestQueryVariables
>
): Apollo.UseSuspenseQueryResult<
DecodeRequestQuery | undefined,
DecodeRequestQueryVariables
>;
export function useDecodeRequestSuspenseQuery(
baseOptions?:
| Apollo.SkipToken
| Apollo.SuspenseQueryHookOptions<
DecodeRequestQuery,
DecodeRequestQueryVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
const options =
baseOptions === Apollo.skipToken
? baseOptions
: { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery<
DecodeRequestQuery,
DecodeRequestQueryVariables

View file

@ -68,13 +68,30 @@ export function useGetAccountLazyQuery(
options
);
}
// @ts-ignore
export function useGetAccountSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions<
GetAccountQuery,
GetAccountQueryVariables
>
): Apollo.UseSuspenseQueryResult<GetAccountQuery, GetAccountQueryVariables>;
export function useGetAccountSuspenseQuery(
baseOptions?:
| Apollo.SkipToken
| Apollo.SuspenseQueryHookOptions<GetAccountQuery, GetAccountQueryVariables>
): Apollo.UseSuspenseQueryResult<
GetAccountQuery | undefined,
GetAccountQueryVariables
>;
export function useGetAccountSuspenseQuery(
baseOptions?:
| Apollo.SkipToken
| Apollo.SuspenseQueryHookOptions<GetAccountQuery, GetAccountQueryVariables>
) {
const options = { ...defaultOptions, ...baseOptions };
const options =
baseOptions === Apollo.skipToken
? baseOptions
: { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery<GetAccountQuery, GetAccountQueryVariables>(
GetAccountDocument,
options

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