mirror of
https://github.com/apotdevin/thunderhub.git
synced 2024-11-19 09:50:03 +01:00
chore: ghost button (#596)
This commit is contained in:
parent
e0498a6c91
commit
5aad9670cf
10
schema.gql
10
schema.gql
@ -52,6 +52,7 @@ type AmbossSubscription {
|
|||||||
|
|
||||||
type AmbossUser {
|
type AmbossUser {
|
||||||
backups: UserBackupInfo!
|
backups: UserBackupInfo!
|
||||||
|
ghost: UserGhostInfo!
|
||||||
subscription: AmbossSubscription!
|
subscription: AmbossSubscription!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,6 +264,10 @@ type ChannelsTimeHealth {
|
|||||||
score: Float
|
score: Float
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClaimGhostAddress {
|
||||||
|
username: String!
|
||||||
|
}
|
||||||
|
|
||||||
type ClosedChannel {
|
type ClosedChannel {
|
||||||
capacity: Float!
|
capacity: Float!
|
||||||
channel_age: Float
|
channel_age: Float
|
||||||
@ -485,6 +490,7 @@ type Mutation {
|
|||||||
addPeer(isTemporary: Boolean, publicKey: String, socket: String, url: String): Boolean!
|
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!
|
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!, preimage: String!, privateKey: String!, redeem: String!, transaction: String!): String!
|
claimBoltzTransaction(destination: String!, fee: Float!, preimage: String!, privateKey: String!, redeem: String!, transaction: String!): String!
|
||||||
|
claimGhostAddress(address: String): ClaimGhostAddress!
|
||||||
closeChannel(forceClose: Boolean, id: String!, targetConfirmations: Float, tokensPerVByte: Float): OpenOrCloseChannel!
|
closeChannel(forceClose: Boolean, id: String!, targetConfirmations: Float, tokensPerVByte: Float): OpenOrCloseChannel!
|
||||||
createAddress(type: String! = "p2tr"): String!
|
createAddress(type: String! = "p2tr"): String!
|
||||||
createBaseInvoice(amount: Float!): BaseInvoice!
|
createBaseInvoice(amount: Float!): BaseInvoice!
|
||||||
@ -843,6 +849,10 @@ type UserBackupInfo {
|
|||||||
total_size_saved: String!
|
total_size_saved: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserGhostInfo {
|
||||||
|
username: String
|
||||||
|
}
|
||||||
|
|
||||||
type Utxo {
|
type Utxo {
|
||||||
address: String!
|
address: String!
|
||||||
address_format: String!
|
address_format: String!
|
||||||
|
@ -9,6 +9,7 @@ import { SectionTitle, Text } from '../../src/components/typography/Styled';
|
|||||||
import { Healthchecks } from '../../src/views/amboss/Healthchecks';
|
import { Healthchecks } from '../../src/views/amboss/Healthchecks';
|
||||||
import { Balances } from '../../src/views/amboss/Balances';
|
import { Balances } from '../../src/views/amboss/Balances';
|
||||||
import { Billboard } from '../../src/views/amboss/Billboard';
|
import { Billboard } from '../../src/views/amboss/Billboard';
|
||||||
|
import { Ghost } from '../../src/views/amboss/Ghost';
|
||||||
|
|
||||||
const AmbossView = () => (
|
const AmbossView = () => (
|
||||||
<>
|
<>
|
||||||
@ -28,6 +29,7 @@ const AmbossView = () => (
|
|||||||
const Wrapped = () => (
|
const Wrapped = () => (
|
||||||
<GridWrapper>
|
<GridWrapper>
|
||||||
<AmbossView />
|
<AmbossView />
|
||||||
|
<Ghost />
|
||||||
<Backups />
|
<Backups />
|
||||||
<Healthchecks />
|
<Healthchecks />
|
||||||
<Balances />
|
<Balances />
|
||||||
|
@ -73,7 +73,7 @@ export const Link: React.FC<LinkProps> = ({
|
|||||||
<CorrectLink
|
<CorrectLink
|
||||||
href={href}
|
href={href}
|
||||||
{...props}
|
{...props}
|
||||||
{...(newTab && { target: '_blank', rel: 'noreferrer' })}
|
{...(newTab && { target: '_blank', rel: 'noreferrer noopener' })}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</CorrectLink>
|
</CorrectLink>
|
||||||
|
21
src/client/src/components/logo/GhostIcon.tsx
Normal file
21
src/client/src/components/logo/GhostIcon.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
export const GhostLogo = forwardRef<any, any>(
|
||||||
|
({ color = 'currentColor', size = 100, children, ...rest }, ref) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
ref={ref}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={size}
|
||||||
|
fill={color}
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<path d="M50 1C22.9 1 1 22.9 1 50v36.8C1 93.5 6.5 99 13.2 99s12.3-5.5 12.3-12.3C25.5 93.5 31 99 37.7 99 44.5 99 50 93.5 50 86.8 50 93.5 55.5 99 62.3 99c6.8 0 12.3-5.5 12.3-12.3C74.5 93.5 80 99 86.8 99 93.5 99 99 93.5 99 86.8V50C99 22.9 77.1 1 50 1z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
GhostLogo.displayName = 'GhostLogo';
|
64
src/client/src/graphql/mutations/__generated__/claimGhostAddress.generated.tsx
generated
Normal file
64
src/client/src/graphql/mutations/__generated__/claimGhostAddress.generated.tsx
generated
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import * as Types from '../../types';
|
||||||
|
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import * as Apollo from '@apollo/client';
|
||||||
|
const defaultOptions = {} as const;
|
||||||
|
export type ClaimGhostAddressMutationVariables = Types.Exact<{
|
||||||
|
address?: Types.InputMaybe<Types.Scalars['String']['input']>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type ClaimGhostAddressMutation = {
|
||||||
|
__typename?: 'Mutation';
|
||||||
|
claimGhostAddress: { __typename?: 'ClaimGhostAddress'; username: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ClaimGhostAddressDocument = gql`
|
||||||
|
mutation ClaimGhostAddress($address: String) {
|
||||||
|
claimGhostAddress(address: $address) {
|
||||||
|
username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type ClaimGhostAddressMutationFn = Apollo.MutationFunction<
|
||||||
|
ClaimGhostAddressMutation,
|
||||||
|
ClaimGhostAddressMutationVariables
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useClaimGhostAddressMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useClaimGhostAddressMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useClaimGhostAddressMutation` 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 [claimGhostAddressMutation, { data, loading, error }] = useClaimGhostAddressMutation({
|
||||||
|
* variables: {
|
||||||
|
* address: // value for 'address'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useClaimGhostAddressMutation(
|
||||||
|
baseOptions?: Apollo.MutationHookOptions<
|
||||||
|
ClaimGhostAddressMutation,
|
||||||
|
ClaimGhostAddressMutationVariables
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
const options = { ...defaultOptions, ...baseOptions };
|
||||||
|
return Apollo.useMutation<
|
||||||
|
ClaimGhostAddressMutation,
|
||||||
|
ClaimGhostAddressMutationVariables
|
||||||
|
>(ClaimGhostAddressDocument, options);
|
||||||
|
}
|
||||||
|
export type ClaimGhostAddressMutationHookResult = ReturnType<
|
||||||
|
typeof useClaimGhostAddressMutation
|
||||||
|
>;
|
||||||
|
export type ClaimGhostAddressMutationResult =
|
||||||
|
Apollo.MutationResult<ClaimGhostAddressMutation>;
|
||||||
|
export type ClaimGhostAddressMutationOptions = Apollo.BaseMutationOptions<
|
||||||
|
ClaimGhostAddressMutation,
|
||||||
|
ClaimGhostAddressMutationVariables
|
||||||
|
>;
|
9
src/client/src/graphql/mutations/claimGhostAddress.ts
Normal file
9
src/client/src/graphql/mutations/claimGhostAddress.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const CLAIM_GHOST_ADDRESS = gql`
|
||||||
|
mutation ClaimGhostAddress($address: String) {
|
||||||
|
claimGhostAddress(address: $address) {
|
||||||
|
username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
@ -23,6 +23,7 @@ export type GetAmbossUserQuery = {
|
|||||||
available_size: string;
|
available_size: string;
|
||||||
remaining_size: string;
|
remaining_size: string;
|
||||||
};
|
};
|
||||||
|
ghost: { __typename?: 'UserGhostInfo'; username?: string | null };
|
||||||
} | null;
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,6 +42,9 @@ export const GetAmbossUserDocument = gql`
|
|||||||
available_size
|
available_size
|
||||||
remaining_size
|
remaining_size
|
||||||
}
|
}
|
||||||
|
ghost {
|
||||||
|
username
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -15,6 +15,9 @@ export const GET_AMBOSS_USER = gql`
|
|||||||
available_size
|
available_size
|
||||||
remaining_size
|
remaining_size
|
||||||
}
|
}
|
||||||
|
ghost {
|
||||||
|
username
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -83,6 +83,7 @@ export type AmbossSubscription = {
|
|||||||
export type AmbossUser = {
|
export type AmbossUser = {
|
||||||
__typename?: 'AmbossUser';
|
__typename?: 'AmbossUser';
|
||||||
backups: UserBackupInfo;
|
backups: UserBackupInfo;
|
||||||
|
ghost: UserGhostInfo;
|
||||||
subscription: AmbossSubscription;
|
subscription: AmbossSubscription;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -321,6 +322,11 @@ export type ChannelsTimeHealth = {
|
|||||||
score?: Maybe<Scalars['Float']['output']>;
|
score?: Maybe<Scalars['Float']['output']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ClaimGhostAddress = {
|
||||||
|
__typename?: 'ClaimGhostAddress';
|
||||||
|
username: Scalars['String']['output'];
|
||||||
|
};
|
||||||
|
|
||||||
export type ClosedChannel = {
|
export type ClosedChannel = {
|
||||||
__typename?: 'ClosedChannel';
|
__typename?: 'ClosedChannel';
|
||||||
capacity: Scalars['Float']['output'];
|
capacity: Scalars['Float']['output'];
|
||||||
@ -565,6 +571,7 @@ export type Mutation = {
|
|||||||
addPeer: Scalars['Boolean']['output'];
|
addPeer: Scalars['Boolean']['output'];
|
||||||
bosRebalance: BosRebalanceResult;
|
bosRebalance: BosRebalanceResult;
|
||||||
claimBoltzTransaction: Scalars['String']['output'];
|
claimBoltzTransaction: Scalars['String']['output'];
|
||||||
|
claimGhostAddress: ClaimGhostAddress;
|
||||||
closeChannel: OpenOrCloseChannel;
|
closeChannel: OpenOrCloseChannel;
|
||||||
createAddress: Scalars['String']['output'];
|
createAddress: Scalars['String']['output'];
|
||||||
createBaseInvoice: BaseInvoice;
|
createBaseInvoice: BaseInvoice;
|
||||||
@ -627,6 +634,10 @@ export type MutationClaimBoltzTransactionArgs = {
|
|||||||
transaction: Scalars['String']['input'];
|
transaction: Scalars['String']['input'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MutationClaimGhostAddressArgs = {
|
||||||
|
address?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
};
|
||||||
|
|
||||||
export type MutationCloseChannelArgs = {
|
export type MutationCloseChannelArgs = {
|
||||||
forceClose?: InputMaybe<Scalars['Boolean']['input']>;
|
forceClose?: InputMaybe<Scalars['Boolean']['input']>;
|
||||||
id: Scalars['String']['input'];
|
id: Scalars['String']['input'];
|
||||||
@ -1199,6 +1210,11 @@ export type UserBackupInfo = {
|
|||||||
total_size_saved: Scalars['String']['output'];
|
total_size_saved: Scalars['String']['output'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UserGhostInfo = {
|
||||||
|
__typename?: 'UserGhostInfo';
|
||||||
|
username?: Maybe<Scalars['String']['output']>;
|
||||||
|
};
|
||||||
|
|
||||||
export type Utxo = {
|
export type Utxo = {
|
||||||
__typename?: 'Utxo';
|
__typename?: 'Utxo';
|
||||||
address: Scalars['String']['output'];
|
address: Scalars['String']['output'];
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { createGlobalStyle } from 'styled-components';
|
import { createGlobalStyle } from 'styled-components';
|
||||||
import { backgroundColor, textColor } from './Themes';
|
import { backgroundColor, textColor } from './Themes';
|
||||||
import { Inter } from 'next/font/google';
|
import { Noto_Sans } from 'next/font/google';
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] });
|
const notoSans = Noto_Sans({
|
||||||
|
weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||||
|
subsets: ['latin'],
|
||||||
|
});
|
||||||
|
|
||||||
export const GlobalStyles = createGlobalStyle`
|
export const GlobalStyles = createGlobalStyle`
|
||||||
html, body {
|
html, body {
|
||||||
@ -11,7 +14,7 @@ export const GlobalStyles = createGlobalStyle`
|
|||||||
}
|
}
|
||||||
* {
|
* {
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-family: ${inter.style.fontFamily}, sans-serif;
|
font-family: ${notoSans.style.fontFamily}, sans-serif;
|
||||||
}
|
}
|
||||||
*, *::after, *::before {
|
*, *::after, *::before {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -20,7 +23,7 @@ export const GlobalStyles = createGlobalStyle`
|
|||||||
background: ${backgroundColor};
|
background: ${backgroundColor};
|
||||||
color: ${textColor};
|
color: ${textColor};
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-family: ${inter.style.fontFamily}, sans-serif;
|
font-family: ${notoSans.style.fontFamily}, sans-serif;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
137
src/client/src/views/amboss/Ghost.tsx
Normal file
137
src/client/src/views/amboss/Ghost.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
import { ColorButton } from '../../components/buttons/colorButton/ColorButton';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardWithTitle,
|
||||||
|
Separation,
|
||||||
|
SingleLine,
|
||||||
|
SubTitle,
|
||||||
|
} from '../../components/generic/Styled';
|
||||||
|
import { Link } from '../../components/link/Link';
|
||||||
|
import { Text } from '../../components/typography/Styled';
|
||||||
|
import { useAmbossUser } from '../../hooks/UseAmbossUser';
|
||||||
|
import { AmbossLoginButton } from './LoginButton';
|
||||||
|
import { mediaWidths } from '../../styles/Themes';
|
||||||
|
import { useClaimGhostAddressMutation } from '../../graphql/mutations/__generated__/claimGhostAddress.generated';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { getErrorContent } from '../../utils/error';
|
||||||
|
import { renderLine } from '../../components/generic/helpers';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Input } from '../../components/input';
|
||||||
|
|
||||||
|
const S = {
|
||||||
|
row: styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
@media (${mediaWidths.mobile}) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Buttons = () => {
|
||||||
|
const { user } = useAmbossUser();
|
||||||
|
|
||||||
|
const isSubscribed = !!user?.subscription.subscribed;
|
||||||
|
const hasClaimedAddress = !!user?.ghost.username;
|
||||||
|
|
||||||
|
const [savedUsername, setUsername] = useState(user?.ghost.username || '');
|
||||||
|
|
||||||
|
const [claimAddress, { loading }] = useClaimGhostAddressMutation({
|
||||||
|
onCompleted: () => toast.success('Address claimed'),
|
||||||
|
onError: error => toast.error(getErrorContent(error)),
|
||||||
|
refetchQueries: ['GetAmbossUser'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return (
|
||||||
|
<SingleLine>
|
||||||
|
<Text style={{ margin: '0' }}>Login to claim an address</Text>
|
||||||
|
<AmbossLoginButton />
|
||||||
|
</SingleLine>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSubscribed) {
|
||||||
|
if (!!hasClaimedAddress) {
|
||||||
|
return (
|
||||||
|
<ColorButton fullWidth loading={loading}>
|
||||||
|
<Link href="https://amboss.space/pricing" newTab>
|
||||||
|
Subscribe to claim a custom address
|
||||||
|
</Link>
|
||||||
|
</ColorButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<S.row>
|
||||||
|
<ColorButton fullWidth loading={loading} onClick={() => claimAddress()}>
|
||||||
|
Claim free random address
|
||||||
|
</ColorButton>
|
||||||
|
<ColorButton fullWidth loading={loading}>
|
||||||
|
<Link href="https://amboss.space/pricing" newTab>
|
||||||
|
Subscribe to claim a custom address
|
||||||
|
</Link>
|
||||||
|
</ColorButton>
|
||||||
|
</S.row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SingleLine>
|
||||||
|
<Input
|
||||||
|
value={savedUsername}
|
||||||
|
placeholder={`Custom alias`}
|
||||||
|
onChange={e => setUsername(e.target.value)}
|
||||||
|
onEnter={() => claimAddress({ variables: { address: savedUsername } })}
|
||||||
|
/>
|
||||||
|
<ColorButton
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading || savedUsername === '' || !savedUsername}
|
||||||
|
withMargin={'0 0 0 8px'}
|
||||||
|
onClick={() => claimAddress({ variables: { address: savedUsername } })}
|
||||||
|
>
|
||||||
|
Claim
|
||||||
|
</ColorButton>
|
||||||
|
</SingleLine>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CurrentAddress = () => {
|
||||||
|
const { user } = useAmbossUser();
|
||||||
|
|
||||||
|
if (!user) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Separation />
|
||||||
|
{renderLine(
|
||||||
|
'Your Ghost address',
|
||||||
|
user.ghost.username
|
||||||
|
? `${user.ghost.username}@ghst.to`
|
||||||
|
: `Has not been claimed!`
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Ghost = () => {
|
||||||
|
return (
|
||||||
|
<CardWithTitle>
|
||||||
|
<SubTitle>Ghost Address</SubTitle>
|
||||||
|
<Card>
|
||||||
|
<Text>
|
||||||
|
With a Ghost address you can have your very own
|
||||||
|
<Link href="https://lightningaddress.com/" newTab>
|
||||||
|
{' Lightning Address '}
|
||||||
|
</Link>
|
||||||
|
for you to receive payments directly to your node.
|
||||||
|
</Text>
|
||||||
|
<CurrentAddress />
|
||||||
|
<Separation />
|
||||||
|
<Buttons />
|
||||||
|
</Card>
|
||||||
|
</CardWithTitle>
|
||||||
|
);
|
||||||
|
};
|
@ -22,6 +22,7 @@ import { LnUrlCard } from './lnurl';
|
|||||||
import { LnMarketsCard } from './lnmarkets';
|
import { LnMarketsCard } from './lnmarkets';
|
||||||
import { AmbossCard } from './amboss/AmbossCard';
|
import { AmbossCard } from './amboss/AmbossCard';
|
||||||
import { LightningAddressCard } from './lightningAddress/LightningAddress';
|
import { LightningAddressCard } from './lightningAddress/LightningAddress';
|
||||||
|
import { GhostCard } from './ghost/GhostQuickAction';
|
||||||
|
|
||||||
export const QuickCard = styled.div`
|
export const QuickCard = styled.div`
|
||||||
background: ${cardColor};
|
background: ${cardColor};
|
||||||
@ -34,9 +35,7 @@ export const QuickCard = styled.div`
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 25px;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-right: 10px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #69c0ff;
|
color: #69c0ff;
|
||||||
|
|
||||||
@ -61,6 +60,8 @@ export const QuickTitle = styled.div`
|
|||||||
const QuickRow = styled.div`
|
const QuickRow = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 16px 0 32px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const QuickActions = () => {
|
export const QuickActions = () => {
|
||||||
@ -104,6 +105,7 @@ export const QuickActions = () => {
|
|||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<QuickRow>
|
<QuickRow>
|
||||||
|
<GhostCard />
|
||||||
<SupportCard callback={() => setOpenCard('support')} />
|
<SupportCard callback={() => setOpenCard('support')} />
|
||||||
<AmbossCard />
|
<AmbossCard />
|
||||||
<QuickCard onClick={() => setOpenCard('lightning_address')}>
|
<QuickCard onClick={() => setOpenCard('lightning_address')}>
|
||||||
|
@ -30,9 +30,7 @@ const QuickCard = styled.button`
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 25px;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-right: 10px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #69c0ff;
|
color: #69c0ff;
|
||||||
|
|
||||||
|
@ -26,9 +26,7 @@ const QuickCard = styled.div`
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 25px;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-right: 10px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #69c0ff;
|
color: #69c0ff;
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import { useCreateThunderPointsMutation } from '../../../../graphql/mutations/__
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useBaseConnect } from '../../../../hooks/UseBaseConnect';
|
import { useBaseConnect } from '../../../../hooks/UseBaseConnect';
|
||||||
import { Pay } from '../../account/pay/Pay';
|
import { Pay } from '../../account/pay/Pay';
|
||||||
|
import { getErrorContent } from '../../../../utils/error';
|
||||||
|
|
||||||
const StyledText = styled.div`
|
const StyledText = styled.div`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -45,7 +46,9 @@ export const SupportBar = () => {
|
|||||||
|
|
||||||
const [withPoints, setWithPoints] = React.useState<boolean>(false);
|
const [withPoints, setWithPoints] = React.useState<boolean>(false);
|
||||||
|
|
||||||
const [getInvoice, { data, loading }] = useCreateBaseInvoiceMutation();
|
const [getInvoice, { data, loading }] = useCreateBaseInvoiceMutation({
|
||||||
|
onError: error => toast.error(getErrorContent(error)),
|
||||||
|
});
|
||||||
|
|
||||||
const [createPoints, { data: pointsData, called, loading: pointsLoading }] =
|
const [createPoints, { data: pointsData, called, loading: pointsLoading }] =
|
||||||
useCreateThunderPointsMutation({ refetchQueries: ['GetBasePoints'] });
|
useCreateThunderPointsMutation({ refetchQueries: ['GetBasePoints'] });
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
cardBorderColor,
|
||||||
|
cardColor,
|
||||||
|
mediaWidths,
|
||||||
|
unSelectedNavButton,
|
||||||
|
} from '../../../../styles/Themes';
|
||||||
|
import { GhostLogo } from '../../../../components/logo/GhostIcon';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
|
||||||
|
const QuickTitle = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
color: ${unSelectedNavButton};
|
||||||
|
margin-top: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const QuickCard = styled.div`
|
||||||
|
background: ${cardColor};
|
||||||
|
box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid ${cardBorderColor};
|
||||||
|
height: 100px;
|
||||||
|
width: 100px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #69c0ff;
|
||||||
|
|
||||||
|
@media (${mediaWidths.mobile}) {
|
||||||
|
padding: 4px;
|
||||||
|
height: 80px;
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
& ${QuickTitle} {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GhostCard = () => {
|
||||||
|
const { push } = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QuickCard onClick={() => push('/amboss')}>
|
||||||
|
<GhostLogo size={24} />
|
||||||
|
<QuickTitle>Ghost</QuickTitle>
|
||||||
|
</QuickCard>
|
||||||
|
);
|
||||||
|
};
|
@ -15,6 +15,9 @@ export const getUserQuery = gql`
|
|||||||
remaining_size
|
remaining_size
|
||||||
total_size_saved
|
total_size_saved
|
||||||
}
|
}
|
||||||
|
ghost {
|
||||||
|
username
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -138,3 +141,11 @@ export const getGhostPayment = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const claimGhostAddress = gql`
|
||||||
|
mutation claimGhostAddress($address: String) {
|
||||||
|
claimGhostAddress(address: $address) {
|
||||||
|
username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -8,6 +8,7 @@ import { Logger } from 'winston';
|
|||||||
import cookie from 'cookie';
|
import cookie from 'cookie';
|
||||||
import {
|
import {
|
||||||
AmbossUser,
|
AmbossUser,
|
||||||
|
ClaimGhostAddress,
|
||||||
LightningAddress,
|
LightningAddress,
|
||||||
LightningNodeSocialInfo,
|
LightningNodeSocialInfo,
|
||||||
} from './amboss.types';
|
} from './amboss.types';
|
||||||
@ -17,6 +18,7 @@ import { NodeService } from '../../node/node.service';
|
|||||||
import { UserId } from '../../security/security.types';
|
import { UserId } from '../../security/security.types';
|
||||||
import { CurrentUser } from '../../security/security.decorators';
|
import { CurrentUser } from '../../security/security.decorators';
|
||||||
import {
|
import {
|
||||||
|
claimGhostAddress,
|
||||||
getLightningAddresses,
|
getLightningAddresses,
|
||||||
getLoginTokenQuery,
|
getLoginTokenQuery,
|
||||||
getNodeSocialInfo,
|
getNodeSocialInfo,
|
||||||
@ -25,6 +27,7 @@ import {
|
|||||||
loginMutation,
|
loginMutation,
|
||||||
} from './amboss.gql';
|
} from './amboss.gql';
|
||||||
import { AmbossService } from './amboss.service';
|
import { AmbossService } from './amboss.service';
|
||||||
|
import { GraphQLError } from 'graphql';
|
||||||
|
|
||||||
const ONE_MONTH_SECONDS = 60 * 60 * 24 * 30;
|
const ONE_MONTH_SECONDS = 60 * 60 * 24 * 30;
|
||||||
|
|
||||||
@ -198,4 +201,29 @@ export class AmbossResolver {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => ClaimGhostAddress)
|
||||||
|
async claimGhostAddress(
|
||||||
|
@Args('address', { nullable: true }) address: string | null,
|
||||||
|
@Context() { ambossAuth }: ContextType
|
||||||
|
) {
|
||||||
|
if (!ambossAuth) {
|
||||||
|
throw new GraphQLError(
|
||||||
|
'You need to login to Amboss before you can claim your Ghost address.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await this.fetchService.graphqlFetchWithProxy(
|
||||||
|
this.configService.get('urls.amboss'),
|
||||||
|
claimGhostAddress,
|
||||||
|
{ address },
|
||||||
|
{ authorization: `Bearer ${ambossAuth}` }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!data?.claimGhostAddress || error) {
|
||||||
|
throw new GraphQLError('Error claiming Ghost address.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.claimGhostAddress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,12 @@ export class UserBackupInfo {
|
|||||||
remaining_size: string;
|
remaining_size: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class UserGhostInfo {
|
||||||
|
@Field({ nullable: true })
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class AmbossUser {
|
export class AmbossUser {
|
||||||
@Field(() => AmbossSubscription)
|
@Field(() => AmbossSubscription)
|
||||||
@ -35,6 +41,9 @@ export class AmbossUser {
|
|||||||
|
|
||||||
@Field(() => UserBackupInfo)
|
@Field(() => UserBackupInfo)
|
||||||
backups: UserBackupInfo;
|
backups: UserBackupInfo;
|
||||||
|
|
||||||
|
@Field(() => UserGhostInfo)
|
||||||
|
ghost: UserGhostInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
@ -73,6 +82,12 @@ export class LightningNodeSocialInfo {
|
|||||||
socials: NodeSocial;
|
socials: NodeSocial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class ClaimGhostAddress {
|
||||||
|
@Field()
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type NodeAlias = {
|
export type NodeAlias = {
|
||||||
alias: string;
|
alias: string;
|
||||||
pub_key: string;
|
pub_key: string;
|
||||||
|
@ -3,6 +3,10 @@ import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
|||||||
import { gql } from 'graphql-tag';
|
import { gql } from 'graphql-tag';
|
||||||
import { FetchService } from '../../fetch/fetch.service';
|
import { FetchService } from '../../fetch/fetch.service';
|
||||||
import { BaseInvoice, BaseNode, BasePoints } from './base.types';
|
import { BaseInvoice, BaseNode, BasePoints } from './base.types';
|
||||||
|
import { GraphQLError } from 'graphql';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
|
||||||
const getBaseCanConnectQuery = gql`
|
const getBaseCanConnectQuery = gql`
|
||||||
{
|
{
|
||||||
@ -54,7 +58,8 @@ const createThunderPointsQuery = gql`
|
|||||||
export class BaseResolver {
|
export class BaseResolver {
|
||||||
constructor(
|
constructor(
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private fetchService: FetchService
|
private fetchService: FetchService,
|
||||||
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Query(() => Boolean)
|
@Query(() => Boolean)
|
||||||
@ -97,7 +102,9 @@ export class BaseResolver {
|
|||||||
|
|
||||||
@Mutation(() => BaseInvoice)
|
@Mutation(() => BaseInvoice)
|
||||||
async createBaseInvoice(@Args('amount') amount: number) {
|
async createBaseInvoice(@Args('amount') amount: number) {
|
||||||
if (!amount) return '';
|
if (!amount) {
|
||||||
|
throw new GraphQLError('No amount provided for donation invoice.');
|
||||||
|
}
|
||||||
|
|
||||||
const { data, error } = await this.fetchService.graphqlFetchWithProxy(
|
const { data, error } = await this.fetchService.graphqlFetchWithProxy(
|
||||||
this.configService.get('urls.tbase'),
|
this.configService.get('urls.tbase'),
|
||||||
@ -105,10 +112,12 @@ export class BaseResolver {
|
|||||||
{ amount }
|
{ amount }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (error) return null;
|
if (error || !data?.createInvoice) {
|
||||||
if (data?.createInvoice) return data.createInvoice;
|
this.logger.error('Error getting donation invoice.', { error, data });
|
||||||
|
throw new GraphQLError('Error creating donation invoice.');
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return data.createInvoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
|
Loading…
Reference in New Issue
Block a user