Skip to content

Commit

Permalink
Merge pull request #18 from IroncladDev/co/mutinynet-actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Kodylow authored Jan 9, 2024
2 parents 1bb3a06 + 8bb29cc commit 807448c
Show file tree
Hide file tree
Showing 15 changed files with 759 additions and 452 deletions.
32 changes: 20 additions & 12 deletions src/client/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,27 @@ import { AccountInfo } from '../src/views/home/account/AccountInfo';
import { ForwardBox } from '../src/views/home/reports/forwardReport';
import { ConnectCard } from '../src/views/home/connect/Connect';
import { QuickActions } from '../src/views/home/quickActions/QuickActions';
import { useGatewayState } from '../src/context/GatewayContext';
import { Network } from '../src/api/types';
import { FaucetActions } from '../src/views/home/faucetActions/FaucetActions';

const HomeView = () => (
<>
<Version />
<AccountInfo />
<AccountButtons />
<ConnectCard />
<QuickActions />
<LiquidityGraph />
<ForwardBox />
<MempoolReport />
</>
);
const HomeView = () => {
const { gatewayInfo } = useGatewayState();

return (
<>
<Version />
<AccountInfo />
<AccountButtons />
<ConnectCard />
<QuickActions />
{gatewayInfo?.network === Network.Signet ? <FaucetActions /> : null}
<LiquidityGraph />
<ForwardBox />
<MempoolReport />
</>
);
};

const Wrapped = () => (
<GridWrapper>
Expand Down
91 changes: 91 additions & 0 deletions src/client/src/api/FaucetApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// GatewayApi is an implementation of the ApiInterface
class FaucetApi {
private baseUrl: string | undefined = 'https://faucet.mutinynet.com/api';

private post = async (api: string, body: unknown): Promise<Response> => {
return fetch(`${this.baseUrl}/${api}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
};

onchain = async (body: { address: string; sats: number }) => {
try {
if (body.sats < 10000 || body.sats > 200000)
throw new Error('Amount must be between 10000 and 200000');

const res = await this.post('onchain', body);

if (res.ok) {
const result = await res.json();
return Promise.resolve(result);
}

throw responseToError(res);
} catch (error) {
return Promise.reject({
message: (error as Error).message,
error,
});
}
};

payInvoice = async (body: { bolt11: string }) => {
try {
const res = await this.post('lightning', body);

if (res.ok) {
const result = await res.json();
return Promise.resolve(result);
}

throw responseToError(res);
} catch (error) {
return Promise.reject({ message: (error as Error).message, error });
}
};

refundFaucet = async (body: { amount_sats: number }) => {
try {
const res = await this.post('bolt11', body);

if (res.ok) {
const result = await res.text();
return Promise.resolve(result);
}

throw responseToError(res);
} catch (error) {
return Promise.reject({ message: (error as Error).message, error });
}
};

requestChannel = async (body: {
capacity: number;
push_amount: number;
pubkey: string;
host: string;
}) => {
try {
const res = await this.post('channel', body);

if (res.ok) {
const result = await res.json();
return Promise.resolve(result);
}

throw responseToError(res);
} catch (error) {
return Promise.reject({ message: (error as Error).message, error });
}
};
}

const responseToError = (res: Response): Error => {
return new Error(`Status : ${res.status} \nReason : ${res.statusText}\n`);
};

export const faucetApi = new FaucetApi();
21 changes: 21 additions & 0 deletions src/client/src/components/logo/GhostIcon.tsx
Original file line number Diff line number Diff line change
@@ -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';
132 changes: 132 additions & 0 deletions src/client/src/views/home/faucetActions/FaucetActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { useState } from 'react';
import styled from 'styled-components';
import {
X,
Link,
CloudLightning,
FastForward,
GitPullRequest,
} from 'react-feather';
import {
CardWithTitle,
SubTitle,
CardTitle,
SmallButton,
} from '../../../components/generic/Styled';
import {
unSelectedNavButton,
cardColor,
cardBorderColor,
mediaWidths,
} from '../../../styles/Themes';
import { Onchain } from './onchain';
import { RequestChannel } from './request-channel';
import { RefundFaucet } from './refund-faucet';
import { PayInvoice } from './pay-invoice';

export 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 {
border: 1px solid #69c0ff;
}
`;

export const QuickTitle = styled.div`
font-size: 12px;
color: ${unSelectedNavButton};
margin-top: 10px;
text-align: center;
`;

const QuickRow = styled.div`
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 16px 0 32px;
`;

export const FaucetActions = () => {
const [openCard, setOpenCard] = useState('none');

const getTitle = () => {
switch (openCard) {
case 'refund_faucet':
return 'Send your unused sats back to the Faucet';
case 'pay_invoice':
return 'Pay an invoice with the Faucet';
case 'request_channel':
return 'Request a channel from the Faucet';
case 'onchain':
return 'Receive onchain sats from the Faucet';
default:
return 'Mutinynet Faucet Actions';
}
};

const renderContent = () => {
switch (openCard) {
case 'refund_faucet':
return <RefundFaucet />;
case 'request_channel':
return <RequestChannel />;
case 'onchain':
return <Onchain />;
case 'pay_invoice':
return <PayInvoice />;
default:
return (
<QuickRow>
<QuickCard onClick={() => setOpenCard('onchain')}>
<Link size={24} />
<QuickTitle>Onchain</QuickTitle>
</QuickCard>
<QuickCard onClick={() => setOpenCard('pay_invoice')}>
<CloudLightning size={24} />
<QuickTitle>Pay Invoice</QuickTitle>
</QuickCard>
<QuickCard onClick={() => setOpenCard('refund_faucet')}>
<FastForward size={24} />
<QuickTitle>Refund Faucet</QuickTitle>
</QuickCard>
<QuickCard onClick={() => setOpenCard('request_channel')}>
<GitPullRequest size={24} />
<QuickTitle>Request Channel</QuickTitle>
</QuickCard>
</QuickRow>
);
}
};

return (
<CardWithTitle>
<CardTitle>
<SubTitle>{getTitle()}</SubTitle>
{openCard !== 'none' && (
<SmallButton onClick={() => setOpenCard('none')}>
<X size={18} />
</SmallButton>
)}
</CardTitle>
{renderContent()}
</CardWithTitle>
);
};
55 changes: 55 additions & 0 deletions src/client/src/views/home/faucetActions/onchain.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useState } from 'react';
import { toast } from 'react-toastify';
import { Card } from '../../../components/generic/Styled';
import { InputWithDeco } from '../../../components/input/InputWithDeco';
import { ColorButton } from '../../../components/buttons/colorButton/ColorButton';
import { faucetApi } from '../../../api/FaucetApi';

export const Onchain = () => {
const [loading, setLoading] = useState<boolean>(false);
const [amount, setAmount] = useState<number>(50000);
const [address, setAddress] = useState<string>('');

const handleOnchain = async () => {
setLoading(true);

try {
await faucetApi.onchain({ sats: amount, address });
toast.success('Successfully Paid to Onchain Address');
} catch (err) {
toast.error((err as Error).message);
}
setLoading(false);
};

return (
<>
<Card>
<InputWithDeco
value={amount}
inputCallback={value => setAmount(Number(value))}
onEnter={() => handleOnchain()}
placeholder="Amount in sats"
title="Amount (sats)"
inputType="number"
/>
<InputWithDeco
value={address}
placeholder="bcrt1..."
title="Address"
inputCallback={value => setAddress(value)}
onEnter={() => handleOnchain()}
/>
<ColorButton
arrow={true}
fullWidth={true}
disabled={loading}
withMargin={'16px 0 0'}
onClick={() => handleOnchain()}
>
Make it Rain
</ColorButton>
</Card>
</>
);
};
46 changes: 46 additions & 0 deletions src/client/src/views/home/faucetActions/pay-invoice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useState } from 'react';
import { toast } from 'react-toastify';
import { Card } from '../../../components/generic/Styled';
import { InputWithDeco } from '../../../components/input/InputWithDeco';
import { ColorButton } from '../../../components/buttons/colorButton/ColorButton';
import { faucetApi } from '../../../api/FaucetApi';

export const PayInvoice = () => {
const [loading, setLoading] = useState<boolean>(false);
const [bolt11, setBolt11] = useState<string>('');

const handlePayInvoice = async () => {
setLoading(true);

try {
await faucetApi.payInvoice({ bolt11 });
toast.success('Successfully Paid to Onchain Address');
} catch (err) {
toast.error((err as Error).message);
}
setLoading(false);
};

return (
<>
<Card>
<InputWithDeco
value={bolt11}
inputCallback={value => setBolt11(value)}
onEnter={() => handlePayInvoice()}
placeholder="lnbt..."
title="Bolt11 Invoice"
/>
<ColorButton
arrow={true}
fullWidth={true}
disabled={loading}
withMargin={'16px 0 0'}
onClick={() => handlePayInvoice()}
>
Strike me now
</ColorButton>
</Card>
</>
);
};
Loading

0 comments on commit 807448c

Please sign in to comment.