feat: add leaderboard (#119)

* feat:  add leaderboard

* chore: 🔧 add leaderboard

* chore: 🔧 leaderboard card

* style: 🎨 node card

* chore: 🔧 cleanup

* chore: 🔧 remove ssr query
This commit is contained in:
Anthony Potdevin 2020-08-09 23:16:44 +02:00 committed by GitHub
parent f9ba0b64fe
commit f985e51b98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 783 additions and 420 deletions

20
package-lock.json generated
View file

@ -3420,6 +3420,12 @@
"universalify": "^1.0.0"
}
},
"graphql-request": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-2.0.0.tgz",
"integrity": "sha512-Ww3Ax+G3l2d+mPT8w7HC9LfrKjutnCKtnDq7ZZp2ghVk5IQDjwAk3/arRF1ix17Ky15rm0hrSKVKxRhIVlSuoQ==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@ -10104,7 +10110,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz",
"integrity": "sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw==",
"dev": true,
"requires": {
"node-fetch": "2.6.0",
"whatwg-fetch": "3.0.0"
@ -13678,10 +13683,12 @@
}
},
"graphql-request": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-2.0.0.tgz",
"integrity": "sha512-Ww3Ax+G3l2d+mPT8w7HC9LfrKjutnCKtnDq7ZZp2ghVk5IQDjwAk3/arRF1ix17Ky15rm0hrSKVKxRhIVlSuoQ==",
"dev": true
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-3.0.0.tgz",
"integrity": "sha512-zW8AuLnKMYOnpVKdANU9FzLDoj4u4AoU6KZ79e+BcJaNiuw/vgCJ0p7ppDMSDrW77a12Moa7J7Mg4w0f9Kd/Kg==",
"requires": {
"cross-fetch": "^3.0.4"
}
},
"graphql-shield": {
"version": "6.1.0",
@ -26475,8 +26482,7 @@
"whatwg-fetch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==",
"dev": true
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
},
"whatwg-mimetype": {
"version": "2.3.0",

View file

@ -45,6 +45,7 @@
"graphql": "^15.3.0",
"graphql-iso-date": "^3.6.1",
"graphql-rate-limit": "^2.0.1",
"graphql-request": "^3.0.0",
"intersection-observer": "^0.11.0",
"js-cookie": "^2.2.1",
"js-yaml": "^3.14.0",

View file

@ -38,11 +38,6 @@ export default class MyDocument extends Document {
return (
<Html>
<Head>
<meta
name="viewport"
content="initial-scale=1.0, width=device-width"
key="viewport"
/>
<meta
name="description"
content="Manage and monitor your lightning network node right inside your browser"

58
pages/leaderboard.tsx Normal file
View file

@ -0,0 +1,58 @@
import React from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_BASE_POINTS } from 'src/graphql/queries/getBasePoints';
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();
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 (
<>
<SupportBar />
{renderBoard()}
</>
);
};
const Wrapped = () => (
<GridWrapper>
<LeaderboardView />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_BASE_POINTS]);
}

View file

@ -4,7 +4,6 @@ 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 { GET_WALLET_INFO } from 'src/graphql/queries/getWalletInfo';
import { BackupsView } from '../src/views/tools/backups/Backups';
import { MessagesView } from '../src/views/tools/messages/Messages';
import { WalletVersion } from '../src/views/tools/WalletVersion';
@ -28,5 +27,5 @@ const Wrapped = () => (
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_WALLET_INFO]);
return await getProps(context);
}

View file

@ -6,8 +6,6 @@ import { authResolvers } from './auth/resolvers';
import { generalTypes, queryTypes, mutationTypes } from './types';
import { accountResolvers } from './account/resolvers';
import { accountTypes } from './account/types';
import { lnpayResolvers } from './lnpay/resolvers';
import { lnpayTypes } from './lnpay/types';
import { bitcoinResolvers } from './bitcoin/resolvers';
import { bitcoinTypes } from './bitcoin/types';
import { peerTypes } from './peer/types';
@ -47,7 +45,6 @@ const typeDefs = [
mutationTypes,
nodeTypes,
accountTypes,
lnpayTypes,
bitcoinTypes,
peerTypes,
chainTypes,
@ -69,7 +66,6 @@ const resolvers = merge(
nodeResolvers,
authResolvers,
accountResolvers,
lnpayResolvers,
bitcoinResolvers,
peerResolvers,
routeResolvers,

View file

@ -1,68 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LnPay Resolvers getLnPay failure 1`] = `
Object {
"data": Object {
"getLnPay": null,
},
"errors": Array [
[GraphQLError: NoLnPayInvoice],
],
"extensions": undefined,
"http": Object {
"headers": Headers {
Symbol(map): Object {},
},
},
}
`;
exports[`LnPay Resolvers getLnPay success 1`] = `
Object {
"data": Object {
"getLnPay": "paymentRequest",
},
"errors": undefined,
"extensions": undefined,
"http": Object {
"headers": Headers {
Symbol(map): Object {},
},
},
}
`;
exports[`LnPay Resolvers getLnPayInfo failure 1`] = `
Object {
"data": Object {
"getLnPayInfo": null,
},
"errors": Array [
[GraphQLError: NoLnPay],
],
"extensions": undefined,
"http": Object {
"headers": Headers {
Symbol(map): Object {},
},
},
}
`;
exports[`LnPay Resolvers getLnPayInfo success 1`] = `
Object {
"data": Object {
"getLnPayInfo": Object {
"max": 1000,
"min": 1,
},
},
"errors": undefined,
"extensions": undefined,
"http": Object {
"headers": Headers {
Symbol(map): Object {},
},
},
}
`;

View file

@ -1,68 +0,0 @@
import testServer from 'server/tests/testServer';
import { GET_LN_PAY } from 'src/graphql/queries/getLnPay';
import fetchMock from 'jest-fetch-mock';
import { GraphQLError } from 'graphql';
import { GET_LN_PAY_INFO } from 'src/graphql/queries/getLnPayInfo';
describe('LnPay Resolvers', () => {
beforeEach(() => {
fetchMock.resetMocks();
});
describe('getLnPay', () => {
test('success', async () => {
fetchMock.mockResponseOnce(JSON.stringify({ pr: 'paymentRequest' }));
const { query } = testServer();
const res = await query({
query: GET_LN_PAY,
variables: { amount: 100 },
});
expect(res.errors).toBe(undefined);
expect(fetchMock).toBeCalledWith(
'https://thunderhub.io/api/lnpay?amount=100'
);
expect(res).toMatchSnapshot();
});
test('failure', async () => {
fetchMock.mockRejectOnce(new Error('Error'));
const { query } = testServer();
const res = await query({
query: GET_LN_PAY,
variables: { amount: 100 },
});
expect(res.errors).toStrictEqual([new GraphQLError('NoLnPayInvoice')]);
expect(res).toMatchSnapshot();
});
});
describe('getLnPayInfo', () => {
test('success', async () => {
fetchMock.mockResponseOnce(
JSON.stringify({ maxSendable: 1000, minSendable: 1 })
);
const { query } = testServer();
const res = await query({
query: GET_LN_PAY_INFO,
});
expect(res.errors).toBe(undefined);
expect(fetchMock).toBeCalledWith('https://thunderhub.io/api/lnpay');
expect(res).toMatchSnapshot();
});
test('failure', async () => {
fetchMock.mockRejectOnce(new Error('Error'));
const { query } = testServer();
const res = await query({
query: GET_LN_PAY_INFO,
});
expect(res.errors).toStrictEqual([new GraphQLError('NoLnPay')]);
expect(res).toMatchSnapshot();
});
});
});

View file

@ -1,43 +0,0 @@
import { ContextType } from 'server/types/apiTypes';
import { toWithError } from 'server/helpers/async';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { appUrls } from 'server/utils/appUrls';
export const lnpayResolvers = {
Query: {
getLnPay: async (
_: undefined,
params: { amount: number },
context: ContextType
) => {
await requestLimiter(context.ip, 'getLnPay');
const [response, error] = await toWithError(
fetch(`${appUrls.lnpay}?amount=${params.amount}`)
);
if (error || !response) {
logger.debug('Unable to get lnpay invoice: %o', error);
throw new Error('NoLnPayInvoice');
}
const json = await response.json();
return json.pr || null;
},
getLnPayInfo: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getLnPayInfo');
const [response, error] = await toWithError(fetch(appUrls.lnpay));
if (error || !response) {
logger.debug('Unable to connect to ThunderHub LNPAY');
throw new Error('NoLnPay');
}
const json = await response.json();
return { max: json.maxSendable, min: json.minSendable };
},
},
};

View file

@ -1,8 +0,0 @@
import { gql } from 'apollo-server-micro';
export const lnpayTypes = gql`
type lnPayInfoType {
max: Int
min: Int
}
`;

View file

@ -2,34 +2,129 @@ import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { toWithError } from 'server/helpers/async';
import { appUrls } from 'server/utils/appUrls';
import { request, gql } from 'graphql-request';
const getBaseCanConnectQuery = gql`
{
hello
}
`;
const getBaseNodesQuery = gql`
{
getNodes {
_id
name
public_key
socket
}
}
`;
const getBasePointsQuery = gql`
{
getPoints {
alias
amount
}
}
`;
const createBaseInvoiceQuery = gql`
mutation CreateInvoice($amount: Int!) {
createInvoice(amount: $amount) {
request
id
}
}
`;
const createThunderPointsQuery = gql`
mutation CreatePoints(
$id: String!
$alias: String!
$uris: [String!]!
$public_key: String!
) {
createPoints(id: $id, alias: $alias, uris: $uris, public_key: $public_key)
}
`;
export const tbaseResolvers = {
Query: {
getBaseNodes: async (_: undefined, params: any, context: ContextType) => {
getBaseCanConnect: async (
_: undefined,
__: undefined,
context: ContextType
): Promise<boolean> => {
await requestLimiter(context.ip, 'getBaseCanConnect');
const [data, error] = await toWithError(
request(appUrls.tbase, getBaseCanConnectQuery)
);
if (error || !data?.hello) return false;
return true;
},
getBaseNodes: async (_: undefined, __: any, context: ContextType) => {
await requestLimiter(context.ip, 'getBaseNodes');
const query = '{getNodes {_id, name, public_key, socket}}';
const [response, fetchError] = await toWithError(
fetch(appUrls.tbase, {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ query }),
})
const [data, error] = await toWithError(
request(appUrls.tbase, getBaseNodesQuery)
);
if (fetchError || !response) return [];
const result = await response.json();
const { errors, data } = result || {};
if (errors) return [];
return (
data?.getNodes?.filter(
(n: { public_key: string; socket: string }) =>
n.public_key && n.socket
) || []
if (error || !data?.getNodes) return [];
return data.getNodes.filter(
(n: { public_key: string; socket: string }) => n.public_key && n.socket
);
},
getBasePoints: async (_: undefined, __: any, context: ContextType) => {
await requestLimiter(context.ip, 'getBasePoints');
const [data, error] = await toWithError(
request(appUrls.tbase, getBasePointsQuery)
);
if (error || !data?.getPoints) return [];
return data.getPoints;
},
},
Mutation: {
createBaseInvoice: async (
_: undefined,
params: { amount: number },
context: ContextType
) => {
await requestLimiter(context.ip, 'getBaseInvoice');
if (!params?.amount) return '';
const [data, error] = await toWithError(
request(appUrls.tbase, createBaseInvoiceQuery, params)
);
if (error) return null;
if (data?.createInvoice) return data.createInvoice;
return null;
},
createThunderPoints: async (
_: undefined,
params: { id: string; alias: string; uris: string[]; public_key: string },
context: ContextType
): Promise<boolean> => {
await requestLimiter(context.ip, 'getThunderPoints');
const [info, error] = await toWithError(
request(appUrls.tbase, createThunderPointsQuery, params)
);
if (error || !info?.createPoints) return false;
return true;
},
},
};

View file

@ -7,4 +7,14 @@ export const tbaseTypes = gql`
public_key: String!
socket: String!
}
type basePointsType {
alias: String!
amount: Int!
}
type baseInvoiceType {
id: String!
request: String!
}
`;

View file

@ -28,7 +28,9 @@ export const generalTypes = gql`
export const queryTypes = gql`
type Query {
getBaseCanConnect: Boolean!
getBaseNodes: [baseNodesType]!
getBasePoints: [basePointsType]!
getAccountingReport(
category: String
currency: String
@ -83,14 +85,19 @@ export const queryTypes = gql`
getSessionToken(id: String, password: String): Boolean
getServerAccounts: [serverAccountType]
getAccount: serverAccountType
getLnPayInfo: lnPayInfoType
getLnPay(amount: Int): String
getLatestVersion: String
}
`;
export const mutationTypes = gql`
type Mutation {
createBaseInvoice(amount: Int!): baseInvoiceType
createThunderPoints(
id: String!
alias: String!
uris: [String!]!
public_key: String!
): Boolean!
closeChannel(
id: String!
forceClose: Boolean

View file

@ -1,15 +1,9 @@
const lnpay =
process.env.NODE_ENV === 'development'
? 'http://localhost:3001/api/lnpay'
: 'https://thunderhub.io/api/lnpay';
const tbase =
process.env.NODE_ENV === 'development'
? 'http://localhost:3010/dev/v1'
: 'https://api.thunderbase.io/v1';
export const appUrls = {
lnpay,
tbase,
oneml: 'https://1ml.com/node/',
blockchain: 'https://www.blockchain.com/btc/tx/',

View file

@ -0,0 +1,69 @@
import * as Apollo from '@apollo/client';
import * as Types from '../../types';
const gql = Apollo.gql;
export type CreateBaseInvoiceMutationVariables = Types.Exact<{
amount: Types.Scalars['Int'];
}>;
export type CreateBaseInvoiceMutation = { __typename?: 'Mutation' } & {
createBaseInvoice?: Types.Maybe<
{ __typename?: 'baseInvoiceType' } & Pick<
Types.BaseInvoiceType,
'request' | 'id'
>
>;
};
export const CreateBaseInvoiceDocument = gql`
mutation CreateBaseInvoice($amount: Int!) {
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
>
) {
return Apollo.useMutation<
CreateBaseInvoiceMutation,
CreateBaseInvoiceMutationVariables
>(CreateBaseInvoiceDocument, baseOptions);
}
export type CreateBaseInvoiceMutationHookResult = ReturnType<
typeof useCreateBaseInvoiceMutation
>;
export type CreateBaseInvoiceMutationResult = Apollo.MutationResult<
CreateBaseInvoiceMutation
>;
export type CreateBaseInvoiceMutationOptions = Apollo.BaseMutationOptions<
CreateBaseInvoiceMutation,
CreateBaseInvoiceMutationVariables
>;

View file

@ -0,0 +1,78 @@
import * as Apollo from '@apollo/client';
import * as Types from '../../types';
const gql = Apollo.gql;
export type CreateThunderPointsMutationVariables = Types.Exact<{
id: Types.Scalars['String'];
alias: Types.Scalars['String'];
uris: Array<Types.Scalars['String']>;
public_key: Types.Scalars['String'];
}>;
export type CreateThunderPointsMutation = { __typename?: 'Mutation' } & Pick<
Types.Mutation,
'createThunderPoints'
>;
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
>
) {
return Apollo.useMutation<
CreateThunderPointsMutation,
CreateThunderPointsMutationVariables
>(CreateThunderPointsDocument, baseOptions);
}
export type CreateThunderPointsMutationHookResult = ReturnType<
typeof useCreateThunderPointsMutation
>;
export type CreateThunderPointsMutationResult = Apollo.MutationResult<
CreateThunderPointsMutation
>;
export type CreateThunderPointsMutationOptions = Apollo.BaseMutationOptions<
CreateThunderPointsMutation,
CreateThunderPointsMutationVariables
>;

View file

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

View file

@ -0,0 +1,17 @@
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

@ -0,0 +1,67 @@
import * as Apollo from '@apollo/client';
import * as Types from '../../types';
const gql = Apollo.gql;
export type GetBaseCanConnectQueryVariables = Types.Exact<{
[key: string]: never;
}>;
export type GetBaseCanConnectQuery = { __typename?: 'Query' } & Pick<
Types.Query,
'getBaseCanConnect'
>;
export const GetBaseCanConnectDocument = gql`
query GetBaseCanConnect {
getBaseCanConnect
}
`;
/**
* __useGetBaseCanConnectQuery__
*
* To run a query within a React component, call `useGetBaseCanConnectQuery` and pass it any options that fit your needs.
* When your component renders, `useGetBaseCanConnectQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetBaseCanConnectQuery({
* variables: {
* },
* });
*/
export function useGetBaseCanConnectQuery(
baseOptions?: Apollo.QueryHookOptions<
GetBaseCanConnectQuery,
GetBaseCanConnectQueryVariables
>
) {
return Apollo.useQuery<
GetBaseCanConnectQuery,
GetBaseCanConnectQueryVariables
>(GetBaseCanConnectDocument, baseOptions);
}
export function useGetBaseCanConnectLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetBaseCanConnectQuery,
GetBaseCanConnectQueryVariables
>
) {
return Apollo.useLazyQuery<
GetBaseCanConnectQuery,
GetBaseCanConnectQueryVariables
>(GetBaseCanConnectDocument, baseOptions);
}
export type GetBaseCanConnectQueryHookResult = ReturnType<
typeof useGetBaseCanConnectQuery
>;
export type GetBaseCanConnectLazyQueryHookResult = ReturnType<
typeof useGetBaseCanConnectLazyQuery
>;
export type GetBaseCanConnectQueryResult = Apollo.QueryResult<
GetBaseCanConnectQuery,
GetBaseCanConnectQueryVariables
>;

View file

@ -0,0 +1,74 @@
import * as Apollo from '@apollo/client';
import * as Types from '../../types';
const gql = Apollo.gql;
export type GetBasePointsQueryVariables = Types.Exact<{ [key: string]: never }>;
export type GetBasePointsQuery = { __typename?: 'Query' } & {
getBasePoints: Array<
Types.Maybe<
{ __typename?: 'basePointsType' } & Pick<
Types.BasePointsType,
'alias' | 'amount'
>
>
>;
};
export const GetBasePointsDocument = gql`
query GetBasePoints {
getBasePoints {
alias
amount
}
}
`;
/**
* __useGetBasePointsQuery__
*
* To run a query within a React component, call `useGetBasePointsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetBasePointsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetBasePointsQuery({
* variables: {
* },
* });
*/
export function useGetBasePointsQuery(
baseOptions?: Apollo.QueryHookOptions<
GetBasePointsQuery,
GetBasePointsQueryVariables
>
) {
return Apollo.useQuery<GetBasePointsQuery, GetBasePointsQueryVariables>(
GetBasePointsDocument,
baseOptions
);
}
export function useGetBasePointsLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetBasePointsQuery,
GetBasePointsQueryVariables
>
) {
return Apollo.useLazyQuery<GetBasePointsQuery, GetBasePointsQueryVariables>(
GetBasePointsDocument,
baseOptions
);
}
export type GetBasePointsQueryHookResult = ReturnType<
typeof useGetBasePointsQuery
>;
export type GetBasePointsLazyQueryHookResult = ReturnType<
typeof useGetBasePointsLazyQuery
>;
export type GetBasePointsQueryResult = Apollo.QueryResult<
GetBasePointsQuery,
GetBasePointsQueryVariables
>;

View file

@ -1,63 +0,0 @@
import * as Apollo from '@apollo/client';
import * as Types from '../../types';
const gql = Apollo.gql;
export type GetLnPayQueryVariables = Types.Exact<{
amount: Types.Scalars['Int'];
}>;
export type GetLnPayQuery = { __typename?: 'Query' } & Pick<
Types.Query,
'getLnPay'
>;
export const GetLnPayDocument = gql`
query GetLnPay($amount: Int!) {
getLnPay(amount: $amount)
}
`;
/**
* __useGetLnPayQuery__
*
* To run a query within a React component, call `useGetLnPayQuery` and pass it any options that fit your needs.
* When your component renders, `useGetLnPayQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetLnPayQuery({
* variables: {
* amount: // value for 'amount'
* },
* });
*/
export function useGetLnPayQuery(
baseOptions?: Apollo.QueryHookOptions<GetLnPayQuery, GetLnPayQueryVariables>
) {
return Apollo.useQuery<GetLnPayQuery, GetLnPayQueryVariables>(
GetLnPayDocument,
baseOptions
);
}
export function useGetLnPayLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetLnPayQuery,
GetLnPayQueryVariables
>
) {
return Apollo.useLazyQuery<GetLnPayQuery, GetLnPayQueryVariables>(
GetLnPayDocument,
baseOptions
);
}
export type GetLnPayQueryHookResult = ReturnType<typeof useGetLnPayQuery>;
export type GetLnPayLazyQueryHookResult = ReturnType<
typeof useGetLnPayLazyQuery
>;
export type GetLnPayQueryResult = Apollo.QueryResult<
GetLnPayQuery,
GetLnPayQueryVariables
>;

View file

@ -1,69 +0,0 @@
import * as Apollo from '@apollo/client';
import * as Types from '../../types';
const gql = Apollo.gql;
export type GetLnPayInfoQueryVariables = Types.Exact<{ [key: string]: never }>;
export type GetLnPayInfoQuery = { __typename?: 'Query' } & {
getLnPayInfo?: Types.Maybe<
{ __typename?: 'lnPayInfoType' } & Pick<Types.LnPayInfoType, 'max' | 'min'>
>;
};
export const GetLnPayInfoDocument = gql`
query GetLnPayInfo {
getLnPayInfo {
max
min
}
}
`;
/**
* __useGetLnPayInfoQuery__
*
* To run a query within a React component, call `useGetLnPayInfoQuery` and pass it any options that fit your needs.
* When your component renders, `useGetLnPayInfoQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetLnPayInfoQuery({
* variables: {
* },
* });
*/
export function useGetLnPayInfoQuery(
baseOptions?: Apollo.QueryHookOptions<
GetLnPayInfoQuery,
GetLnPayInfoQueryVariables
>
) {
return Apollo.useQuery<GetLnPayInfoQuery, GetLnPayInfoQueryVariables>(
GetLnPayInfoDocument,
baseOptions
);
}
export function useGetLnPayInfoLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetLnPayInfoQuery,
GetLnPayInfoQueryVariables
>
) {
return Apollo.useLazyQuery<GetLnPayInfoQuery, GetLnPayInfoQueryVariables>(
GetLnPayInfoDocument,
baseOptions
);
}
export type GetLnPayInfoQueryHookResult = ReturnType<
typeof useGetLnPayInfoQuery
>;
export type GetLnPayInfoLazyQueryHookResult = ReturnType<
typeof useGetLnPayInfoLazyQuery
>;
export type GetLnPayInfoQueryResult = Apollo.QueryResult<
GetLnPayInfoQuery,
GetLnPayInfoQueryVariables
>;

View file

@ -73,7 +73,7 @@ export type GetCanConnectInfoQuery = { __typename?: 'Query' } & {
getNodeInfo?: Types.Maybe<
{ __typename?: 'nodeInfoType' } & Pick<
Types.NodeInfoType,
'public_key' | 'uris'
'alias' | 'public_key' | 'uris'
>
>;
};
@ -268,6 +268,7 @@ export type GetChannelAmountInfoQueryResult = Apollo.QueryResult<
export const GetCanConnectInfoDocument = gql`
query GetCanConnectInfo {
getNodeInfo {
alias
public_key
uris
}

View file

@ -501,6 +501,7 @@ exports[`Query tests "GET_CONNECT_INFO" matches snapshot 1`] = `
Object {
"data": Object {
"getNodeInfo": Object {
"alias": "TestNode",
"public_key": "key",
"uris": Array [
"uri",

View file

@ -0,0 +1,7 @@
import { gql } from '@apollo/client';
export const GET_BASE_CAN_CONNECT = gql`
query GetBaseCanConnect {
getBaseCanConnect
}
`;

View file

@ -0,0 +1,10 @@
import { gql } from '@apollo/client';
export const GET_BASE_POINTS = gql`
query GetBasePoints {
getBasePoints {
alias
amount
}
}
`;

View file

@ -1,7 +0,0 @@
import { gql } from '@apollo/client';
export const GET_LN_PAY = gql`
query GetLnPay($amount: Int!) {
getLnPay(amount: $amount)
}
`;

View file

@ -1,10 +0,0 @@
import { gql } from '@apollo/client';
export const GET_LN_PAY_INFO = gql`
query GetLnPayInfo {
getLnPayInfo {
max
min
}
}
`;

View file

@ -51,6 +51,7 @@ export const GET_CHANNEL_AMOUNT_INFO = gql`
export const GET_CONNECT_INFO = gql`
query GetCanConnectInfo {
getNodeInfo {
alias
public_key
uris
}

View file

@ -39,7 +39,9 @@ export type PermissionsType = {
export type Query = {
__typename?: 'Query';
getBaseCanConnect: Scalars['Boolean'];
getBaseNodes: Array<Maybe<BaseNodesType>>;
getBasePoints: Array<Maybe<BasePointsType>>;
getAccountingReport: Scalars['String'];
getVolumeHealth?: Maybe<ChannelsHealth>;
getTimeHealth?: Maybe<ChannelsTimeHealth>;
@ -79,8 +81,6 @@ export type Query = {
getSessionToken?: Maybe<Scalars['Boolean']>;
getServerAccounts?: Maybe<Array<Maybe<ServerAccountType>>>;
getAccount?: Maybe<ServerAccountType>;
getLnPayInfo?: Maybe<LnPayInfoType>;
getLnPay?: Maybe<Scalars['String']>;
getLatestVersion?: Maybe<Scalars['String']>;
};
@ -179,12 +179,10 @@ export type QueryGetSessionTokenArgs = {
password?: Maybe<Scalars['String']>;
};
export type QueryGetLnPayArgs = {
amount?: Maybe<Scalars['Int']>;
};
export type Mutation = {
__typename?: 'Mutation';
createBaseInvoice?: Maybe<BaseInvoiceType>;
createThunderPoints: Scalars['Boolean'];
closeChannel?: Maybe<CloseChannelType>;
openChannel?: Maybe<OpenChannelType>;
updateFees?: Maybe<Scalars['Boolean']>;
@ -202,6 +200,17 @@ export type Mutation = {
createMacaroon?: Maybe<Scalars['String']>;
};
export type MutationCreateBaseInvoiceArgs = {
amount: Scalars['Int'];
};
export type MutationCreateThunderPointsArgs = {
id: Scalars['String'];
alias: Scalars['String'];
uris: Array<Scalars['String']>;
public_key: Scalars['String'];
};
export type MutationCloseChannelArgs = {
id: Scalars['String'];
forceClose?: Maybe<Scalars['Boolean']>;
@ -339,12 +348,6 @@ export type ServerAccountType = {
loggedIn: Scalars['Boolean'];
};
export type LnPayInfoType = {
__typename?: 'lnPayInfoType';
max?: Maybe<Scalars['Int']>;
min?: Maybe<Scalars['Int']>;
};
export type BitcoinFeeType = {
__typename?: 'bitcoinFeeType';
fast?: Maybe<Scalars['Int']>;
@ -889,3 +892,15 @@ export type BaseNodesType = {
public_key: Scalars['String'];
socket: Scalars['String'];
};
export type BasePointsType = {
__typename?: 'basePointsType';
alias: Scalars['String'];
amount: Scalars['Int'];
};
export type BaseInvoiceType = {
__typename?: 'baseInvoiceType';
id: Scalars['String'];
request: Scalars['String'];
};

View file

@ -0,0 +1,15 @@
import { useState, useEffect } from 'react';
import { useGetBaseCanConnectQuery } from 'src/graphql/queries/__generated__/getBaseCanConnect.generated';
export const useBaseConnect = () => {
const [canConnect, setCanConnect] = useState<boolean>(false);
const { loading, error, data } = useGetBaseCanConnectQuery();
useEffect(() => {
if (loading || !data?.getBaseCanConnect || error) return;
setCanConnect(true);
}, [loading, data, error]);
return canConnect;
};

View file

@ -19,7 +19,7 @@ export const useBitcoinFees = (): State => {
const [bitcoinFees, setBitcoinFees] = useState<State>(initialState);
const { loading, data, error } = useGetBitcoinFeesQuery({
fetchPolicy: 'cache-only',
fetchPolicy: 'cache-first',
});
useEffect(() => {

View file

@ -41,7 +41,7 @@ const initialState = {
export const useNodeInfo = (): StatusState => {
const [nodeInfo, setNodeInfo] = useState<StatusState>(initialState);
const { data, loading, error } = useGetNodeInfoQuery({
fetchPolicy: 'cache-only',
fetchPolicy: 'cache-first',
});
useEffect(() => {

View file

@ -7,9 +7,11 @@ import {
Settings,
Home,
Icon,
Heart,
} from 'react-feather';
import { useTransition, animated } from 'react-spring';
import { useRouter } from 'next/router';
import { useBaseConnect } from 'src/hooks/UseBaseConnect';
import { headerColor, headerTextColor } from '../../styles/Themes';
import { SingleLine } from '../../components/generic/Styled';
import { BurgerMenu } from '../../components/burgerMenu/BurgerMenu';
@ -29,16 +31,19 @@ import {
const MAIN = '/';
const HOME = '/home';
const CHAT = '/chat';
const DONATIONS = '/leaderboard';
const SETTINGS = '/settings';
export const Header = () => {
const { pathname } = useRouter();
const [open, setOpen] = useState(false);
const connected = useBaseConnect();
const isRoot = pathname === '/';
const showHomeButton = (): boolean =>
pathname === CHAT || pathname === SETTINGS;
pathname === DONATIONS || pathname === CHAT || pathname === SETTINGS;
const transitions = useTransition(open, null, {
from: { position: 'absolute', opacity: 0 },
@ -74,6 +79,7 @@ export const Header = () => {
<ViewSwitch hideMobile={true}>
<HeaderButtons>
{showHomeButton() && renderNavButton(HOME, Home)}
{connected && renderNavButton(DONATIONS, Heart)}
{renderNavButton(CHAT, MessageCircle)}
{renderNavButton(SETTINGS, Settings)}
</HeaderButtons>

View file

@ -14,8 +14,10 @@ import {
MessageCircle,
BarChart2,
Icon,
Heart,
} from 'react-feather';
import { useRouter } from 'next/router';
import { useBaseConnect } from 'src/hooks/UseBaseConnect';
import {
unSelectedNavButton,
navBackgroundColor,
@ -121,6 +123,7 @@ const CHAIN_TRANS = '/chain';
const TOOLS = '/tools';
const DETAILS = '/details';
const STATS = '/stats';
const DONATIONS = '/leaderboard';
const CHAT = '/chat';
const SETTINGS = '/settings';
@ -133,6 +136,8 @@ export const Navigation = ({ isBurger, setOpen }: NavigationProps) => {
const { pathname } = useRouter();
const { sidebar } = useConfigState();
const connected = useBaseConnect();
const isRoot = pathname === '/';
const renderNavButton = (
@ -188,6 +193,7 @@ export const Navigation = ({ isBurger, setOpen }: NavigationProps) => {
{renderBurgerNav('Chain', CHAIN_TRANS, LinkIcon)}
{renderBurgerNav('Tools', TOOLS, Shield)}
{renderBurgerNav('Stats', STATS, BarChart2)}
{connected && renderBurgerNav('Donations', DONATIONS, Heart)}
{renderBurgerNav('Chat', CHAT, MessageCircle)}
{renderBurgerNav('Settings', SETTINGS, Settings)}
</BurgerRow>

View file

@ -86,6 +86,7 @@ export const RequestModal: React.FC<DecodeProps> = ({
handleReset,
}) => {
const { data, loading, error } = useDecodeRequestQuery({
skip: !request,
fetchPolicy: 'network-only',
variables: { request },
});

View file

@ -1,5 +1,4 @@
import * as React from 'react';
import { useGetLnPayInfoQuery } from 'src/graphql/queries/__generated__/getLnPayInfo.generated';
import { Heart } from 'react-feather';
import styled from 'styled-components';
import {
@ -8,6 +7,7 @@ import {
cardBorderColor,
unSelectedNavButton,
} from 'src/styles/Themes';
import { useBaseConnect } from 'src/hooks/UseBaseConnect';
const QuickTitle = styled.div`
font-size: 14px;
@ -47,11 +47,9 @@ type SupportCardProps = {
};
export const SupportCard = ({ callback }: SupportCardProps) => {
const { loading, error } = useGetLnPayInfoQuery();
const connected = useBaseConnect();
if (loading || error) {
return null;
}
if (!connected) return null;
return (
<QuickCard onClick={callback}>

View file

@ -1,5 +1,4 @@
import * as React from 'react';
import { useGetLnPayLazyQuery } from 'src/graphql/queries/__generated__/getLnPay.generated';
import {
Card,
SubTitle,
@ -10,31 +9,93 @@ import { InputWithDeco } from 'src/components/input/InputWithDeco';
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import Modal from 'src/components/modal/ReactModal';
import { Emoji } from 'src/components/emoji/Emoji';
import { useCreateBaseInvoiceMutation } from 'src/graphql/mutations/__generated__/createBaseInvoice.generated';
import {
SingleButton,
MultiButton,
} from 'src/components/buttons/multiButton/MultiButton';
import styled from 'styled-components';
import { mediaWidths } from 'src/styles/Themes';
import { useGetCanConnectInfoQuery } from 'src/graphql/queries/__generated__/getNodeInfo.generated';
import { useCreateThunderPointsMutation } from 'src/graphql/mutations/__generated__/createThunderPoints.generated';
import { toast } from 'react-toastify';
import { useBaseConnect } from 'src/hooks/UseBaseConnect';
import { RequestModal } from '../../account/pay/RequestModal';
const StyledText = styled.div`
text-align: center;
font-size: 14px;
margin: 16px 40px 0;
@media (${mediaWidths.mobile}) {
margin: 16px 0 0;
}
`;
export const SupportBar = () => {
const [modalOpen, modalOpenSet] = React.useState<boolean>(false);
const [amount, amountSet] = React.useState<number>(0);
const [invoice, invoiceSet] = React.useState<string>('');
const [id, idSet] = React.useState<string>('');
const connected = useBaseConnect();
const [withPoints, setWithPoints] = React.useState<boolean>(false);
const [getInvoice, { data, loading }] = useCreateBaseInvoiceMutation();
const [
getInvoice,
{ data, loading: invoiceLoading },
] = useGetLnPayLazyQuery();
createPoints,
{ data: pointsData, called, loading: pointsLoading },
] = useCreateThunderPointsMutation({ refetchQueries: ['GetBasePoints'] });
const { data: info } = useGetCanConnectInfoQuery();
React.useEffect(() => {
if (data && data.getLnPay) {
invoiceSet(data.getLnPay);
if (data?.createBaseInvoice) {
const { request, id } = data.createBaseInvoice;
invoiceSet(request);
idSet(id);
modalOpenSet(true);
}
}, [data]);
React.useEffect(() => {
if (!pointsLoading && called) {
if (pointsData?.createThunderPoints) {
toast.success('Points Created');
} else {
toast.error('Error creating points. Write to us on telegram!');
}
}
}, [pointsData, pointsLoading, called]);
if (!connected) return null;
const handleReset = () => {
modalOpenSet(false);
amountSet(0);
invoiceSet('');
idSet('');
};
const handlePaidReset = () => {
if (withPoints && info?.getNodeInfo) {
const { alias, public_key, uris } = info.getNodeInfo;
createPoints({ variables: { id, alias, public_key, uris } });
}
handleReset();
};
const renderButton = (
onClick: () => void,
text: string,
selected: boolean
) => (
<SingleButton selected={selected} onClick={onClick}>
{text}
</SingleButton>
);
return (
<>
<Card>
@ -53,24 +114,34 @@ export const SupportBar = () => {
inputType={'number'}
inputCallback={value => amountSet(Number(value))}
/>
<Separation />
<InputWithDeco title={'With Points'} noInput={true}>
<MultiButton>
{renderButton(() => setWithPoints(true), 'Yes', withPoints)}
{renderButton(() => setWithPoints(false), 'No', !withPoints)}
</MultiButton>
</InputWithDeco>
{withPoints && (
<StyledText>
This means your node will appear in the ThunderHub donation
leaderboard. If you want to remain anonymous, do not enable this
option. Your node alias and public key will be stored if you enable
it.
</StyledText>
)}
<Separation />
<ColorButton
onClick={() => getInvoice({ variables: { amount: amount * 1000 } })}
loading={invoiceLoading}
disabled={amount <= 0 || invoiceLoading}
onClick={() => getInvoice({ variables: { amount } })}
loading={loading}
disabled={amount <= 0 || loading}
fullWidth={true}
withMargin={'8px 0 0 0'}
>
Send
</ColorButton>
</Card>
<Modal
isOpen={modalOpen}
closeCallback={() => {
modalOpenSet(false);
amountSet(0);
}}
>
<RequestModal request={invoice} handleReset={handleReset} />
<Modal isOpen={modalOpen} closeCallback={handleReset}>
<RequestModal request={invoice} handleReset={handlePaidReset} />
</Modal>
</>
);

View file

@ -0,0 +1,91 @@
import React from 'react';
import { BasePointsType } from 'src/graphql/types';
import styled from 'styled-components';
import { DarkSubTitle } from 'src/components/generic/Styled';
import { themeColors } from 'src/styles/Themes';
type LeaderCardProps = {
color?: string;
borderColor?: string;
borderWidth?: string;
};
const LeaderCard = styled.div<LeaderCardProps>`
padding: 8px;
border: ${({ borderWidth }) => borderWidth || '2px'} solid
${({ borderColor }) => borderColor || 'gold'};
background-color: ${({ color }) => color || 'gold'};
margin: 8px 0;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
`;
const Line = styled.div`
display: flex;
align-items: center;
`;
const NumberPadding = styled.div`
margin-right: 8px;
`;
const getBorderColor = (index: number) => {
switch (index) {
case 1:
return 'gold';
case 2:
return 'orange';
case 3:
return 'white';
default:
return themeColors.blue2;
}
};
const getColor = (index: number) => {
switch (index) {
case 1:
case 2:
case 3:
default:
return 'transparent';
}
};
const getWidth = (index: number): string => {
switch (index) {
case 1:
case 2:
case 3:
return '2px';
default:
return '1px';
}
};
type NodeCardType = {
node: BasePointsType | null;
index: number;
};
export const NodeCard = ({ node, index }: NodeCardType) => {
if (!node) return null;
return (
<LeaderCard
color={getColor(index)}
borderColor={getBorderColor(index)}
borderWidth={getWidth(index)}
>
<Line>
<NumberPadding>{`${index}.`}</NumberPadding>
{node.alias}
</Line>
<Line>
<DarkSubTitle withMargin={'0 8px 0 0'}>Points:</DarkSubTitle>
{node.amount}
</Line>
</LeaderCard>
);
};