feat: UI ap flow (#223)
* chore: AP flow based on designs * chore: preview ap creation * style: max-width for explore container * fix: fix setArgs * feat: add success step for access point creation * feat: add dots spinner * chore: AP flow finished * chore: release changes * feat: store ens once the user log in with wallet * feat: add mocked function for bunny cdn creation * chore: finish bunny cdn mocked functions * chore: run eslint * feat: add domain validator * style: move animation to stitches keyframes * chore: set ens only when is not set * Update ui/src/store/features/bunny-cdn/async-thunk/verify-ap.ts Co-authored-by: Felipe Mendes <zo.fmendes@gmail.com> * chore: remove nfa-picker * chore: get nfa name * chore: change verify thunk name * fix: fix verifyPullzone thunk --------- Co-authored-by: Felipe Mendes <zo.fmendes@gmail.com>
This commit is contained in:
parent
22a6d70e98
commit
21b9660164
|
|
@ -21,7 +21,6 @@ export const App: React.FC = () => {
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Explore />} />
|
<Route path="/" element={<Explore />} />
|
||||||
<Route path="/mint" element={<Mint />} />
|
<Route path="/mint" element={<Mint />} />
|
||||||
<Route path="/create-ap" element={<CreateAP />} />
|
|
||||||
<Route path="/create-ap/:id" element={<CreateAP />} />
|
<Route path="/create-ap/:id" element={<CreateAP />} />
|
||||||
<Route path="/nfa/:id" element={<IndexedNFAView />} />
|
<Route path="/nfa/:id" element={<IndexedNFAView />} />
|
||||||
{/** TODO remove for release */}
|
{/** TODO remove for release */}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { styled } from '@/theme';
|
||||||
|
|
||||||
|
export const CardTag = styled('span', {
|
||||||
|
fontSize: '$sm',
|
||||||
|
backgroundColor: '$slate4',
|
||||||
|
color: '$slate11',
|
||||||
|
p: '$1 $3h',
|
||||||
|
height: '$7',
|
||||||
|
borderRadius: '$md',
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './card-tag';
|
||||||
|
|
@ -24,6 +24,5 @@ export abstract class InputFileStyles {
|
||||||
'&[aria-invalid=true], &[data-invalid]': {
|
'&[aria-invalid=true], &[data-invalid]': {
|
||||||
borderColor: '$red9',
|
borderColor: '$red9',
|
||||||
},
|
},
|
||||||
//TODO add error state
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ export * from './toast';
|
||||||
export * from './step';
|
export * from './step';
|
||||||
export * from './nfa-card';
|
export * from './nfa-card';
|
||||||
export * from './nfa-preview';
|
export * from './nfa-preview';
|
||||||
|
export * from './card-tag';
|
||||||
export * from './resolved-address';
|
export * from './resolved-address';
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,29 @@
|
||||||
import { Avatar, ConnectKitButton } from 'connectkit';
|
import { Avatar, ConnectKitButton } from 'connectkit';
|
||||||
|
|
||||||
import { Button, Flex } from '@/components';
|
import { Button, Flex } from '@/components';
|
||||||
|
import { ENSActions, useAppDispatch, useENSStore } from '@/store';
|
||||||
|
|
||||||
export const ConnectWalletButton: React.FC = () => {
|
export const ConnectWalletButton: React.FC = () => {
|
||||||
|
const { addressMap } = useENSStore();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const setEnsNameStore = (ensName: string, address: string): void => {
|
||||||
|
const stored = addressMap[address] || {};
|
||||||
|
if (typeof stored.state !== 'undefined') return;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
ENSActions.setAddress({
|
||||||
|
key: address,
|
||||||
|
value: { state: 'success', value: ensName },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConnectKitButton.Custom>
|
<ConnectKitButton.Custom>
|
||||||
{({ isConnected, show, truncatedAddress, address, ensName }) => {
|
{({ isConnected, show, truncatedAddress, address, ensName }) => {
|
||||||
|
if (ensName && address) setEnsNameStore(ensName, address);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={show}>
|
<Button onClick={show}>
|
||||||
{isConnected && !!address && !!truncatedAddress ? (
|
{isConnected && !!address && !!truncatedAddress ? (
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,20 @@ export type ResolvedAddressProps = React.ComponentPropsWithRef<
|
||||||
typeof RAS.Container
|
typeof RAS.Container
|
||||||
> & {
|
> & {
|
||||||
children: string;
|
children: string;
|
||||||
|
truncated?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ResolvedAddress = forwardStyledRef<
|
export const ResolvedAddress = forwardStyledRef<
|
||||||
HTMLSpanElement,
|
HTMLSpanElement,
|
||||||
ResolvedAddressProps
|
ResolvedAddressProps
|
||||||
>(({ children, ...props }, ref) => {
|
>(({ children, truncated = false, ...props }, ref) => {
|
||||||
const [resolvedAddress, loading] = useResolvedAddress(children);
|
const [resolvedAddress, loading] = useResolvedAddress(children);
|
||||||
|
|
||||||
const text = useMemo(() => {
|
const text = useMemo(() => {
|
||||||
if (!resolvedAddress.endsWith('.eth'))
|
if (!resolvedAddress.endsWith('.eth') && truncated)
|
||||||
return `${resolvedAddress.slice(0, 6)}...${resolvedAddress.slice(-4)}`;
|
return `${resolvedAddress.slice(0, 6)}...${resolvedAddress.slice(-4)}`;
|
||||||
return resolvedAddress;
|
return resolvedAddress;
|
||||||
}, [resolvedAddress]);
|
}, [resolvedAddress, truncated]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RAS.Container {...props} ref={ref} data-loading={loading}>
|
<RAS.Container {...props} ref={ref} data-loading={loading}>
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
export * from './spinner';
|
export * from './spinner';
|
||||||
|
export * from './spinner-dot';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { SpinnerStyles } from './spinner.styles';
|
||||||
|
|
||||||
|
export const SpinnerDot: React.FC<SpinnerStyles.ContainerProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<SpinnerStyles.Container
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g>
|
||||||
|
<circle cx="12" cy="2.5" r="1" opacity=".14" fill="#FFFFFF" />
|
||||||
|
<circle cx="16.75" cy="3.77" r="1" opacity=".29" fill="#FFFFFF" />
|
||||||
|
<circle cx="20.23" cy="7.25" r="1" opacity=".43" fill="#FFFFFF" />
|
||||||
|
<circle cx="21.50" cy="12.00" r="1" opacity=".57" fill="#FFFFFF" />
|
||||||
|
<circle cx="20.23" cy="16.75" r="1" opacity=".71" fill="#FFFFFF" />
|
||||||
|
<circle cx="16.75" cy="20.23" r="1" opacity=".86" fill="#FFFFFF" />
|
||||||
|
<circle cx="12" cy="21.5" r="1" fill="#FFFFFF" />
|
||||||
|
</g>
|
||||||
|
</SpinnerStyles.Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,12 +1,57 @@
|
||||||
import { styled } from '@/theme';
|
import { keyframes, styled } from '@/theme';
|
||||||
|
|
||||||
export abstract class SpinnerStyles {
|
const DotSpinner = keyframes({
|
||||||
static readonly Container = styled('svg', {
|
'8.3%': {
|
||||||
|
transform: 'rotate(30deg)',
|
||||||
|
},
|
||||||
|
'16.6%': {
|
||||||
|
transform: 'rotate(60deg)',
|
||||||
|
},
|
||||||
|
'25%': {
|
||||||
|
transform: 'rotate(90deg)',
|
||||||
|
},
|
||||||
|
'33.3%': {
|
||||||
|
transform: 'rotate(120deg)',
|
||||||
|
},
|
||||||
|
'41.6%': {
|
||||||
|
transform: 'rotate(150deg)',
|
||||||
|
},
|
||||||
|
'50%': {
|
||||||
|
transform: 'rotate(180deg)',
|
||||||
|
},
|
||||||
|
'58.3%': {
|
||||||
|
transform: 'rotate(210deg)',
|
||||||
|
},
|
||||||
|
'66.6%': {
|
||||||
|
transform: 'rotate(240deg)',
|
||||||
|
},
|
||||||
|
'75%': {
|
||||||
|
transform: 'rotate(270deg)',
|
||||||
|
},
|
||||||
|
'83.3%': {
|
||||||
|
transform: 'rotate(300deg)',
|
||||||
|
},
|
||||||
|
'91.6%': {
|
||||||
|
transform: 'rotate(330deg)',
|
||||||
|
},
|
||||||
|
'100%': {
|
||||||
|
transform: 'rotate(360deg)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SpinnerStyles = {
|
||||||
|
KeyFrames: {},
|
||||||
|
Container: styled('svg', {
|
||||||
fontSize: '1.5rem',
|
fontSize: '1.5rem',
|
||||||
width: '1em',
|
width: '1em',
|
||||||
height: '1em',
|
height: '1em',
|
||||||
});
|
|
||||||
}
|
g: {
|
||||||
|
transformOrigin: 'center',
|
||||||
|
animation: `${DotSpinner} 0.75s step-end infinite`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
export namespace SpinnerStyles {
|
export namespace SpinnerStyles {
|
||||||
export type ContainerProps = React.ComponentProps<
|
export type ContainerProps = React.ComponentProps<
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
export const createBunnyCDNMock = async (
|
||||||
|
domain: string,
|
||||||
|
targetDomain: string
|
||||||
|
): Promise<{ bunnyURL: string }> => {
|
||||||
|
return new Promise((resolved, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolved({
|
||||||
|
bunnyURL: '8c12c649402442d88b5f.b-cdn.net',
|
||||||
|
});
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const verifyBunnyCDNMock = async (domain: string): Promise<boolean> => {
|
||||||
|
return new Promise((resolved, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolved(true);
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export * from './mint-site';
|
export * from './mint-site';
|
||||||
export * from './detail';
|
export * from './detail';
|
||||||
export * from './list';
|
export * from './list';
|
||||||
|
export * from './bunny-cdn';
|
||||||
export * from './nfa';
|
export * from './nfa';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import { createBunnyCDNMock } from '@/mocks';
|
||||||
|
import { RootState } from '@/store';
|
||||||
|
import { AppLog } from '@/utils';
|
||||||
|
|
||||||
|
import { bunnyCDNActions } from '../bunny-cdn-slice';
|
||||||
|
|
||||||
|
type CNAMERecord = {
|
||||||
|
domain: string;
|
||||||
|
targetDomain: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createBunnyCDN = createAsyncThunk<void, CNAMERecord>(
|
||||||
|
'BunnyCDN/CreateCDN',
|
||||||
|
async ({ domain, targetDomain }, { dispatch, getState }) => {
|
||||||
|
const { state } = (getState() as RootState).bunnyCDN;
|
||||||
|
|
||||||
|
if (state === 'loading') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
dispatch(bunnyCDNActions.setState('loading'));
|
||||||
|
|
||||||
|
const CDNRecord = await createBunnyCDNMock(domain, targetDomain);
|
||||||
|
|
||||||
|
dispatch(bunnyCDNActions.setCDNRecordData(CDNRecord.bunnyURL));
|
||||||
|
} catch (error) {
|
||||||
|
AppLog.errorToast(
|
||||||
|
'Failed to create the CDN record. Please, try again',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
dispatch(bunnyCDNActions.setState('failed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './create-cdn';
|
||||||
|
export * from './verify-pullzone';
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import { verifyBunnyCDNMock } from '@/mocks';
|
||||||
|
import { RootState } from '@/store';
|
||||||
|
import { AppLog } from '@/utils';
|
||||||
|
|
||||||
|
import { bunnyCDNActions } from '../bunny-cdn-slice';
|
||||||
|
|
||||||
|
export const verifyBunnyPullzone = createAsyncThunk<void, string>(
|
||||||
|
'BunnyCDN/VerifyPullzone',
|
||||||
|
async (domain, { dispatch, getState }): Promise<void> => {
|
||||||
|
const { state } = (getState() as RootState).bunnyCDN;
|
||||||
|
|
||||||
|
if (state === 'loading') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
dispatch(bunnyCDNActions.setState('loading'));
|
||||||
|
|
||||||
|
const verifyAPState = await verifyBunnyCDNMock(domain);
|
||||||
|
|
||||||
|
if (verifyAPState) dispatch(bunnyCDNActions.setState('success'));
|
||||||
|
else throw new Error('Invalid AP state');
|
||||||
|
} catch (error) {
|
||||||
|
AppLog.errorToast(
|
||||||
|
'There was an error trying to verify the domain. Please, try again',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
dispatch(bunnyCDNActions.setState('failed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import { RootState } from '@/store';
|
||||||
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
|
import * as asyncThunk from './async-thunk';
|
||||||
|
|
||||||
|
export namespace BunnyCDNState {
|
||||||
|
export type CreateCDNState =
|
||||||
|
| undefined
|
||||||
|
| 'loading'
|
||||||
|
| 'unferified'
|
||||||
|
| 'failed'
|
||||||
|
| 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BunnyCDNState {
|
||||||
|
state: BunnyCDNState.CreateCDNState;
|
||||||
|
bunnyURL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: BunnyCDNState = {
|
||||||
|
state: undefined,
|
||||||
|
bunnyURL: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bunnyCDNSlice = createSlice({
|
||||||
|
name: 'BunnyCDNSlice',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setState: (state, action: PayloadAction<BunnyCDNState.CreateCDNState>) => {
|
||||||
|
state.state = action.payload;
|
||||||
|
},
|
||||||
|
setCDNRecordData: (state, action: PayloadAction<string>) => {
|
||||||
|
state.bunnyURL = action.payload;
|
||||||
|
state.state = 'success';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const bunnyCDNActions = {
|
||||||
|
...bunnyCDNSlice.actions,
|
||||||
|
...asyncThunk,
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectENSState = (state: RootState): BunnyCDNState => state.bunnyCDN;
|
||||||
|
|
||||||
|
export const useBunnyCDNStore = (): BunnyCDNState =>
|
||||||
|
useAppSelector(selectENSState);
|
||||||
|
|
||||||
|
export default bunnyCDNSlice.reducer;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './bunny-cdn-slice';
|
||||||
|
|
@ -2,3 +2,4 @@ export * from './fleek-erc721';
|
||||||
export * from './github';
|
export * from './github';
|
||||||
export * from './toasts';
|
export * from './toasts';
|
||||||
export * from './ens';
|
export * from './ens';
|
||||||
|
export * from './bunny-cdn';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { configureStore } from '@reduxjs/toolkit';
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import bunnyCDNReducer from './features/bunny-cdn/bunny-cdn-slice';
|
||||||
import ENSReducer from './features/ens/ens-slice';
|
import ENSReducer from './features/ens/ens-slice';
|
||||||
import fleekERC721Reducer from './features/fleek-erc721/fleek-erc721-slice';
|
import fleekERC721Reducer from './features/fleek-erc721/fleek-erc721-slice';
|
||||||
import githubReducer from './features/github/github-slice';
|
import githubReducer from './features/github/github-slice';
|
||||||
|
|
@ -7,10 +8,11 @@ import toastsReducer from './features/toasts/toasts-slice';
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
|
bunnyCDN: bunnyCDNReducer,
|
||||||
|
ENS: ENSReducer,
|
||||||
fleekERC721: fleekERC721Reducer,
|
fleekERC721: fleekERC721Reducer,
|
||||||
github: githubReducer,
|
github: githubReducer,
|
||||||
toasts: toastsReducer,
|
toasts: toastsReducer,
|
||||||
ENS: ENSReducer,
|
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware({
|
getDefaultMiddleware({
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Converts a hex color string to a number.
|
||||||
|
*/
|
||||||
|
export const parseColorToNumber = (color: string): number => {
|
||||||
|
const hexColor = color.replace('#', '');
|
||||||
|
return parseInt(hexColor, 16);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts string number to hex color string.
|
||||||
|
*/
|
||||||
|
export const parseNumberToHexColor = (color: number): string => {
|
||||||
|
const hexColor = color.toString(16);
|
||||||
|
return hexColor;
|
||||||
|
};
|
||||||
|
|
@ -54,12 +54,23 @@ const hasSpecialCharacters: StringValidator = {
|
||||||
message: 'This field has special characters',
|
message: 'This field has special characters',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isValidDomain: StringValidator = {
|
||||||
|
name: 'isValidDomain',
|
||||||
|
validate: (value = '') => {
|
||||||
|
const regex =
|
||||||
|
/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/;
|
||||||
|
return regex.test(value);
|
||||||
|
},
|
||||||
|
message: 'This field is not a valid domain',
|
||||||
|
};
|
||||||
|
|
||||||
export const StringValidators = {
|
export const StringValidators = {
|
||||||
required,
|
required,
|
||||||
maxLength,
|
maxLength,
|
||||||
isUrl,
|
isUrl,
|
||||||
maxFileSize,
|
maxFileSize,
|
||||||
hasSpecialCharacters,
|
hasSpecialCharacters,
|
||||||
|
isValidDomain,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hasValidator = <
|
export const hasValidator = <
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
import { useQuery } from '@apollo/client';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { useAccount } from 'wagmi';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
CardTag,
|
||||||
|
Flex,
|
||||||
|
Form,
|
||||||
|
Spinner,
|
||||||
|
Stepper,
|
||||||
|
Text,
|
||||||
|
} from '@/components';
|
||||||
|
import { getNFADocument } from '@/graphclient';
|
||||||
|
import { useAppDispatch } from '@/store';
|
||||||
|
import { bunnyCDNActions, useBunnyCDNStore } from '@/store/features/bunny-cdn';
|
||||||
|
import { AppLog } from '@/utils';
|
||||||
|
|
||||||
|
import { CreateAccessPoint } from '../create-ap.context';
|
||||||
|
import { NFAIconFragment } from '../nfa-icon';
|
||||||
|
import { useAccessPointFormContext } from './create-ap.form.context';
|
||||||
|
|
||||||
|
export const SelectedNFA: React.FC = () => {
|
||||||
|
const { nfa } = CreateAccessPoint.useContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
css={{
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex css={{ alignItems: 'center', maxWidth: '65%' }}>
|
||||||
|
<NFAIconFragment image={nfa.logo} color={nfa.color} />
|
||||||
|
<Text
|
||||||
|
css={{
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{nfa.name}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<CardTag css={{ minWidth: '$28' }}>Selected NFA</CardTag>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CreateAccessPointFormBody: React.FC = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const { address } = useAccount();
|
||||||
|
const { nextStep } = Stepper.useContext();
|
||||||
|
const { nfa, setNfa, billing } = CreateAccessPoint.useContext();
|
||||||
|
const { setArgs } = CreateAccessPoint.useTransactionContext();
|
||||||
|
const { state } = useBunnyCDNStore();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const {
|
||||||
|
form: {
|
||||||
|
domain: {
|
||||||
|
value: [domain],
|
||||||
|
},
|
||||||
|
isValid: [isValid],
|
||||||
|
},
|
||||||
|
} = useAccessPointFormContext();
|
||||||
|
|
||||||
|
const {
|
||||||
|
form: { domain: domainContext },
|
||||||
|
} = useAccessPointFormContext();
|
||||||
|
|
||||||
|
const { loading: nfaLoading } = useQuery(getNFADocument, {
|
||||||
|
skip: id === undefined,
|
||||||
|
variables: {
|
||||||
|
id: ethers.utils.hexlify(Number(id)),
|
||||||
|
},
|
||||||
|
onCompleted(data) {
|
||||||
|
if (data.token && id) {
|
||||||
|
const { name, tokenId, logo, color, externalURL: domain } = data.token;
|
||||||
|
setNfa({ name, tokenId, logo, color, domain });
|
||||||
|
} else {
|
||||||
|
AppLog.errorToast("We couldn't find the NFA you are looking for");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
AppLog.errorToast('Error fetching NFA', error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state === 'success') {
|
||||||
|
nextStep();
|
||||||
|
dispatch(bunnyCDNActions.setState(undefined));
|
||||||
|
}
|
||||||
|
}, [state, nextStep, dispatch]);
|
||||||
|
|
||||||
|
if (nfaLoading) {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
css={{
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '$48',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spinner />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleContinueClick = (): void => {
|
||||||
|
if (!address) {
|
||||||
|
AppLog.errorToast('No address found. Please connect your wallet.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nfa && domain) {
|
||||||
|
try {
|
||||||
|
setArgs([Number(nfa.tokenId), domain, { value: billing }]);
|
||||||
|
dispatch(
|
||||||
|
bunnyCDNActions.createBunnyCDN({
|
||||||
|
domain: 'domain',
|
||||||
|
targetDomain: domain,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
AppLog.errorToast('Error setting transaction arguments');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex css={{ flexDirection: 'column', gap: '$6' }}>
|
||||||
|
<SelectedNFA />
|
||||||
|
<Text css={{ fontSize: '$sm', color: '$slate11' }}>
|
||||||
|
Enter the domain you want to host the NFA. You will need access to the
|
||||||
|
DNS settings in the next step.
|
||||||
|
</Text>
|
||||||
|
<Form.Field context={domainContext}>
|
||||||
|
<Form.Label>Domain</Form.Label>
|
||||||
|
<Form.Input placeholder="mydomain.com" />
|
||||||
|
<Form.Overline />
|
||||||
|
</Form.Field>
|
||||||
|
<Button
|
||||||
|
disabled={!isValid || nfa.tokenId === ''}
|
||||||
|
isLoading={state === 'loading'}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={handleContinueClick}
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Card, Grid, Icon, IconButton, Stepper } from '@/components';
|
import { Card, Flex, Icon, IconButton, Stepper } from '@/components';
|
||||||
|
|
||||||
import { CreateAccessPointFormBody } from './create-ap.form-body';
|
import { CreateAccessPointFormBody } from './create-ap-form-body';
|
||||||
|
|
||||||
export const CreateAccessPointForm: React.FC = () => {
|
export const CreateAccessPointForm: React.FC = () => {
|
||||||
const { prevStep } = Stepper.useContext();
|
const { prevStep } = Stepper.useContext();
|
||||||
|
|
@ -8,7 +8,7 @@ export const CreateAccessPointForm: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Card.Container css={{ width: '$107h' }}>
|
<Card.Container css={{ width: '$107h' }}>
|
||||||
<Card.Heading
|
<Card.Heading
|
||||||
title="Create Access Point"
|
title="Enter Domain"
|
||||||
leftIcon={
|
leftIcon={
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Add"
|
aria-label="Add"
|
||||||
|
|
@ -29,13 +29,14 @@ export const CreateAccessPointForm: React.FC = () => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Grid
|
<Flex
|
||||||
css={{
|
css={{
|
||||||
rowGap: '$6',
|
flexDirection: 'column',
|
||||||
|
gap: '$6',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CreateAccessPointFormBody />
|
<CreateAccessPointFormBody />
|
||||||
</Grid>
|
</Flex>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card.Container>
|
</Card.Container>
|
||||||
);
|
);
|
||||||
|
|
@ -5,7 +5,7 @@ import { createContext, StringValidators } from '@/utils';
|
||||||
|
|
||||||
export type CreateAccessPointFormContext = {
|
export type CreateAccessPointFormContext = {
|
||||||
form: {
|
form: {
|
||||||
appName: FormField;
|
domain: FormField;
|
||||||
isValid: ReactState<boolean>;
|
isValid: ReactState<boolean>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -20,9 +20,9 @@ export const [CreateAccessPointFormProvider, useAccessPointFormContext] =
|
||||||
export const useAccessPointFormContextInit =
|
export const useAccessPointFormContextInit =
|
||||||
(): CreateAccessPointFormContext => ({
|
(): CreateAccessPointFormContext => ({
|
||||||
form: {
|
form: {
|
||||||
appName: useFormField('appName', [
|
domain: useFormField('domain', [
|
||||||
StringValidators.required,
|
StringValidators.required,
|
||||||
StringValidators.maxLength(50),
|
StringValidators.isValidDomain,
|
||||||
]),
|
]),
|
||||||
isValid: useState(false),
|
isValid: useState(false),
|
||||||
},
|
},
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './create-ap.form.context';
|
||||||
|
export * from './create-ap-form';
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { Button, Card, Grid, SpinnerDot, Stepper, Text } from '@/components';
|
||||||
|
import { bunnyCDNActions, useAppDispatch, useBunnyCDNStore } from '@/store';
|
||||||
|
|
||||||
|
import { useAccessPointFormContext } from '../ap-form-step';
|
||||||
|
import { CreateAccessPoint } from '../create-ap.context';
|
||||||
|
import { DisplayText } from '../display-text';
|
||||||
|
import { isSubdomain } from './record-step.utils';
|
||||||
|
|
||||||
|
export const APRecordCardBody: React.FC = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { bunnyURL, state } = useBunnyCDNStore();
|
||||||
|
const {
|
||||||
|
nfa: { domain: nfaDomain },
|
||||||
|
} = CreateAccessPoint.useContext();
|
||||||
|
const {
|
||||||
|
form: {
|
||||||
|
domain: {
|
||||||
|
value: [accesPointDomain],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = useAccessPointFormContext();
|
||||||
|
const { nextStep } = Stepper.useContext();
|
||||||
|
|
||||||
|
const isSudomain = useMemo(
|
||||||
|
() => isSubdomain(accesPointDomain),
|
||||||
|
[accesPointDomain]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state === 'success') {
|
||||||
|
dispatch(bunnyCDNActions.setState(undefined));
|
||||||
|
nextStep();
|
||||||
|
}
|
||||||
|
}, [state, nextStep, dispatch]);
|
||||||
|
|
||||||
|
const handleContinueClick = (): void => {
|
||||||
|
dispatch(bunnyCDNActions.verifyBunnyPullzone(nfaDomain));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card.Body>
|
||||||
|
{state === 'loading' ? (
|
||||||
|
<Card.Text css={{ p: '$12 $10', gap: '$7' }}>
|
||||||
|
<SpinnerDot css={{ fontSize: '$7xl' }} />
|
||||||
|
<Text css={{ fontSize: '$md' }}>
|
||||||
|
Waiting for DNS propagation, allow a few minutes.
|
||||||
|
</Text>
|
||||||
|
</Card.Text>
|
||||||
|
) : (
|
||||||
|
<Grid
|
||||||
|
css={{
|
||||||
|
rowGap: '$6',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
{`Create a ${
|
||||||
|
isSudomain ? 'CNAME' : 'ANAME'
|
||||||
|
} record in your DNS provider pointing to our CDN
|
||||||
|
endpoint.`}
|
||||||
|
</Text>
|
||||||
|
<DisplayText
|
||||||
|
label="Record Type"
|
||||||
|
value={isSudomain ? 'CNAME' : 'ANAME'}
|
||||||
|
/>
|
||||||
|
<DisplayText label="Host" value={isSudomain ? 'App' : '@'} />
|
||||||
|
<DisplayText label="Data (Points to)" value={bunnyURL} />
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={handleContinueClick}
|
||||||
|
>
|
||||||
|
I added the record
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Card.Body>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { Card, Icon, IconButton, Stepper } from '@/components';
|
||||||
|
|
||||||
|
export const APRecordCardHeader: React.FC = () => {
|
||||||
|
const { prevStep } = Stepper.useContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card.Heading
|
||||||
|
title="Create Record"
|
||||||
|
leftIcon={
|
||||||
|
<IconButton
|
||||||
|
aria-label="Add"
|
||||||
|
colorScheme="gray"
|
||||||
|
variant="link"
|
||||||
|
icon={<Icon name="back" />}
|
||||||
|
css={{ mr: '$2' }}
|
||||||
|
onClick={prevStep}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
rightIcon={
|
||||||
|
<IconButton
|
||||||
|
aria-label="Add"
|
||||||
|
colorScheme="gray"
|
||||||
|
variant="link"
|
||||||
|
icon={<Icon name="info" />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Card } from '@/components';
|
||||||
|
|
||||||
|
import { APRecordCardBody } from './ap-record-body';
|
||||||
|
import { APRecordCardHeader } from './ap-record-header';
|
||||||
|
|
||||||
|
export const APRecordStep: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Card.Container css={{ width: '$107h' }}>
|
||||||
|
<APRecordCardHeader />
|
||||||
|
<APRecordCardBody />
|
||||||
|
</Card.Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './ap-record-step';
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const isSubdomain = (url: string): boolean => {
|
||||||
|
const urlParts = url.split('.');
|
||||||
|
|
||||||
|
return urlParts.length > 2;
|
||||||
|
};
|
||||||
|
|
@ -1,36 +1,71 @@
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
|
import { useAccount } from 'wagmi';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Flex,
|
Flex,
|
||||||
Grid,
|
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
ResolvedAddress,
|
||||||
Stepper,
|
Stepper,
|
||||||
|
Text,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useTransactionCost } from '@/hooks';
|
import { useTransactionCost } from '@/hooks';
|
||||||
import { FleekERC721 } from '@/integrations';
|
import { FleekERC721 } from '@/integrations';
|
||||||
|
import { AppLog } from '@/utils';
|
||||||
|
|
||||||
|
import { useAccessPointFormContext } from './ap-form-step/create-ap.form.context';
|
||||||
|
import { SelectedNFA } from './ap-form-step/create-ap-form-body';
|
||||||
import { CreateAccessPoint } from './create-ap.context';
|
import { CreateAccessPoint } from './create-ap.context';
|
||||||
import { useAccessPointFormContext } from './create-ap.form.context';
|
import { DisplayText } from './display-text';
|
||||||
|
|
||||||
|
export const AccessPointDataFragment: React.FC = () => {
|
||||||
|
const { address, status } = useAccount();
|
||||||
|
const {
|
||||||
|
form: {
|
||||||
|
domain: {
|
||||||
|
value: [domain],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = useAccessPointFormContext();
|
||||||
|
|
||||||
|
if (status === 'connecting') return <div>Loading...</div>; //TODO replace with spinner
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SelectedNFA />
|
||||||
|
<DisplayText
|
||||||
|
label="Owner"
|
||||||
|
value={
|
||||||
|
address ? (
|
||||||
|
<ResolvedAddress truncated={false}>{address || ''}</ResolvedAddress>
|
||||||
|
) : (
|
||||||
|
'Please connect to wallet'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<DisplayText label="Frontend URL" value={domain} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const CreateAccessPointPreview: React.FC = () => {
|
export const CreateAccessPointPreview: React.FC = () => {
|
||||||
const { prevStep } = Stepper.useContext();
|
const { prevStep } = Stepper.useContext();
|
||||||
|
const { address } = useAccount();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
prepare: { status: prepareStatus, data: prepareData, error: prepareError },
|
prepare: { status: prepareStatus, data: prepareData, error: prepareError },
|
||||||
write: { status: writeStatus, write },
|
write: { status: writeStatus, write },
|
||||||
transaction: { status: transactionStatus },
|
transaction: { status: transactionStatus },
|
||||||
} = CreateAccessPoint.useTransactionContext();
|
} = CreateAccessPoint.useTransactionContext();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
form: {
|
form: {
|
||||||
appName: {
|
isValid: [isValid],
|
||||||
value: [appName],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
} = useAccessPointFormContext();
|
} = useAccessPointFormContext();
|
||||||
const { nfa } = CreateAccessPoint.useContext();
|
|
||||||
|
|
||||||
const [cost, currency, isCostLoading] = useTransactionCost(
|
const [cost, currency, isCostLoading] = useTransactionCost(
|
||||||
prepareData?.request.value,
|
prepareData?.request.value,
|
||||||
|
|
@ -66,10 +101,19 @@ export const CreateAccessPointPreview: React.FC = () => {
|
||||||
[prepareStatus, writeStatus, transactionStatus]
|
[prepareStatus, writeStatus, transactionStatus]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const error = [writeStatus, transactionStatus].some(
|
||||||
|
(status) => status === 'error'
|
||||||
|
);
|
||||||
|
if (error) {
|
||||||
|
AppLog.errorToast('An error occurred while minting the NFA');
|
||||||
|
}
|
||||||
|
}, [writeStatus, transactionStatus]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card.Container css={{ width: '$107h' }}>
|
<Card.Container css={{ width: '$107h' }}>
|
||||||
<Card.Heading
|
<Card.Heading
|
||||||
title={`Create Access Point ${nfa.label || ''}`}
|
title="Review Details"
|
||||||
leftIcon={
|
leftIcon={
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Add"
|
aria-label="Add"
|
||||||
|
|
@ -90,26 +134,19 @@ export const CreateAccessPointPreview: React.FC = () => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Grid
|
<Flex css={{ flexDirection: 'column', gap: '$6' }}>
|
||||||
css={{
|
<AccessPointDataFragment />
|
||||||
rowGap: '$6',
|
<Text>{message}</Text>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex css={{ flexDirection: 'column' }}>
|
|
||||||
<span>NFA: {nfa.value}</span>
|
|
||||||
<span>{appName}</span>
|
|
||||||
<span className="text-slate11 text-sm">{message}</span>
|
|
||||||
</Flex>
|
|
||||||
<Button
|
<Button
|
||||||
disabled={!!prepareError || !nfa}
|
isLoading={isLoading}
|
||||||
|
isDisabled={isLoading || !isValid || !address}
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onClick={write}
|
onClick={write}
|
||||||
isLoading={isLoading}
|
|
||||||
>
|
>
|
||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Flex>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card.Container>
|
</Card.Container>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Button, Card, Flex, Icon, IconButton, Text } from '@/components';
|
||||||
|
|
||||||
|
import { CreateAccessPoint } from './create-ap.context';
|
||||||
|
import { AccessPointDataFragment } from './create-ap-preview';
|
||||||
|
|
||||||
|
export const CreateAccessPointSuccess: React.FC = () => {
|
||||||
|
const { nfa } = CreateAccessPoint.useContext();
|
||||||
|
return (
|
||||||
|
<Card.Container css={{ width: '$107h' }}>
|
||||||
|
<Card.Heading
|
||||||
|
title="Hosting Successful"
|
||||||
|
leftIcon={
|
||||||
|
<Icon
|
||||||
|
name="check-circle"
|
||||||
|
css={{ color: '$green11', fontSize: '$xl', mr: '$2' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
rightIcon={
|
||||||
|
<IconButton
|
||||||
|
aria-label="Add"
|
||||||
|
colorScheme="gray"
|
||||||
|
variant="link"
|
||||||
|
icon={<Icon name="info" />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Card.Body>
|
||||||
|
<Flex css={{ flexDirection: 'column', gap: '$6' }}>
|
||||||
|
<Text css={{ fontSize: '$sm', color: '$slate11' }}>
|
||||||
|
{`You have successfully hosted a ${nfa.name} frontend on your own domain.`}
|
||||||
|
</Text>
|
||||||
|
<AccessPointDataFragment />
|
||||||
|
<Flex css={{ flexDirection: 'column', gap: '$4' }}>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
leftIcon={<Icon name="twitter" />}
|
||||||
|
>
|
||||||
|
Tweet about your frontend!
|
||||||
|
</Button>
|
||||||
|
<Button>Manage Frontend</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Card.Body>
|
||||||
|
</Card.Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,16 +1,23 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { Token } from '@/graphclient';
|
|
||||||
import { EthereumHooks } from '@/integrations';
|
import { EthereumHooks } from '@/integrations';
|
||||||
import { useFleekERC721Billing } from '@/store';
|
import { useFleekERC721Billing } from '@/store';
|
||||||
import { AppLog, createContext, pushToast } from '@/utils';
|
import { AppLog, createContext } from '@/utils';
|
||||||
|
|
||||||
type NFA = Pick<Token, 'id' | 'name'>;
|
export type NFA = {
|
||||||
|
tokenId: string;
|
||||||
|
name: string;
|
||||||
|
logo: string;
|
||||||
|
color: number;
|
||||||
|
domain: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type AccessPointContext = {
|
export type AccessPointContext = {
|
||||||
billing: string | undefined;
|
billing: string | undefined;
|
||||||
nfa: NFA | undefined;
|
nfa: NFA;
|
||||||
setNfa: ReactState<NFA | undefined>[1];
|
setNfa: (nfa: NFA) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const [CreateAPProvider, useContext] = createContext<AccessPointContext>({
|
const [CreateAPProvider, useContext] = createContext<AccessPointContext>({
|
||||||
|
|
@ -31,7 +38,13 @@ export abstract class CreateAccessPoint {
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const [billing] = useFleekERC721Billing('AddAccessPoint');
|
const [billing] = useFleekERC721Billing('AddAccessPoint');
|
||||||
const [nfa, setNfa] = useState<NFA>();
|
const [nfa, setNfa] = useState<NFA>({
|
||||||
|
tokenId: '',
|
||||||
|
name: '',
|
||||||
|
logo: '',
|
||||||
|
color: 0,
|
||||||
|
domain: '',
|
||||||
|
});
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
billing,
|
billing,
|
||||||
|
|
@ -44,12 +57,10 @@ export abstract class CreateAccessPoint {
|
||||||
<TransactionProvider
|
<TransactionProvider
|
||||||
config={{
|
config={{
|
||||||
transaction: {
|
transaction: {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data: any) => {
|
||||||
AppLog.info('Transaction:', data);
|
AppLog.info('Transaction:', data);
|
||||||
pushToast('success', 'Your transaction was successful!');
|
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
onError: (error: any) => {
|
||||||
onError: (error) => {
|
|
||||||
AppLog.errorToast(
|
AppLog.errorToast(
|
||||||
'There was an error trying to create the Access Point. Please try again'
|
'There was an error trying to create the Access Point. Please try again'
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
import { useQuery } from '@apollo/client';
|
|
||||||
import { ethers } from 'ethers';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Button, Flex, Form, Spinner, Stepper } from '@/components';
|
|
||||||
import { getNFADocument } from '@/graphclient';
|
|
||||||
import { AppLog } from '@/utils';
|
|
||||||
|
|
||||||
import { CreateAccessPoint } from './create-ap.context';
|
|
||||||
import { useAccessPointFormContext } from './create-ap.form.context';
|
|
||||||
import { NfaPicker } from './nfa-picker';
|
|
||||||
|
|
||||||
export const CreateAccessPointFormBody: React.FC = () => {
|
|
||||||
const { id } = useParams();
|
|
||||||
const { nextStep } = Stepper.useContext();
|
|
||||||
const { nfa, setNfa, billing } = CreateAccessPoint.useContext();
|
|
||||||
const { setArgs } = CreateAccessPoint.useTransactionContext();
|
|
||||||
|
|
||||||
const {
|
|
||||||
form: {
|
|
||||||
appName: {
|
|
||||||
value: [appName],
|
|
||||||
},
|
|
||||||
isValid: [isValid],
|
|
||||||
},
|
|
||||||
} = useAccessPointFormContext();
|
|
||||||
|
|
||||||
const {
|
|
||||||
form: { appName: appNameContext },
|
|
||||||
} = useAccessPointFormContext();
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: nfaData,
|
|
||||||
error: nfaError,
|
|
||||||
loading: nfaLoading,
|
|
||||||
} = useQuery(getNFADocument, {
|
|
||||||
skip: id === undefined,
|
|
||||||
variables: {
|
|
||||||
id: ethers.utils.hexlify(Number(id)),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (nfaError) {
|
|
||||||
AppLog.errorToast('Error fetching NFA');
|
|
||||||
}
|
|
||||||
}, [nfaError]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (nfaData) {
|
|
||||||
if (nfaData.token && id) {
|
|
||||||
const { name } = nfaData.token;
|
|
||||||
setNfa({ value: id, label: name });
|
|
||||||
} else {
|
|
||||||
AppLog.errorToast("We couldn't find the NFA you are looking for");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [nfaData, id, setNfa]);
|
|
||||||
|
|
||||||
if (nfaLoading) {
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
css={{
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '$48',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Spinner />
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleContinueClick = (): void => {
|
|
||||||
if (nfa && appName) {
|
|
||||||
setArgs([Number(nfa.value), appName, { value: billing }]);
|
|
||||||
nextStep();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* TODO will have to do some changes on the Form.Combobox if we use this component for the NFA picker */}
|
|
||||||
{id === undefined && <NfaPicker />}
|
|
||||||
<Form.Field context={appNameContext}>
|
|
||||||
<Form.Label>App Name</Form.Label>
|
|
||||||
<Form.Input />
|
|
||||||
</Form.Field>
|
|
||||||
<Button
|
|
||||||
disabled={!isValid}
|
|
||||||
colorScheme="blue"
|
|
||||||
variant="solid"
|
|
||||||
onClick={handleContinueClick}
|
|
||||||
>
|
|
||||||
Continue
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,16 +1,29 @@
|
||||||
import { Form, Step, Stepper } from '@/components';
|
import { Form, Step, Stepper } from '@/components';
|
||||||
|
|
||||||
import { WalletStep } from '../mint/wallet-step';
|
import { WalletStep } from '../mint/wallet-step';
|
||||||
import { useAccessPointFormContext } from './create-ap.form.context';
|
import { useAccessPointFormContext } from './ap-form-step/create-ap.form.context';
|
||||||
import { CreateAccessPointForm } from './create-ap-form';
|
import { CreateAccessPointForm } from './ap-form-step/create-ap-form';
|
||||||
|
import { APRecordStep } from './ap-record-step/ap-record-step';
|
||||||
|
import { isSubdomain } from './ap-record-step/record-step.utils';
|
||||||
|
import { CreateAccessPoint } from './create-ap.context';
|
||||||
import { CreateAccessPointPreview } from './create-ap-preview';
|
import { CreateAccessPointPreview } from './create-ap-preview';
|
||||||
|
import { CreateAccessPointSuccess } from './create-ap-success';
|
||||||
|
|
||||||
export const CreateApStepper: React.FC = () => {
|
export const CreateApStepper: React.FC = () => {
|
||||||
|
const {
|
||||||
|
transaction: { isSuccess },
|
||||||
|
} = CreateAccessPoint.useTransactionContext();
|
||||||
const {
|
const {
|
||||||
form: {
|
form: {
|
||||||
|
domain: {
|
||||||
|
value: [accesPointDomain],
|
||||||
|
},
|
||||||
isValid: [, setIsValid],
|
isValid: [, setIsValid],
|
||||||
},
|
},
|
||||||
} = useAccessPointFormContext();
|
} = useAccessPointFormContext();
|
||||||
|
|
||||||
|
if (isSuccess) return <CreateAccessPointSuccess />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stepper.Root initialStep={1}>
|
<Stepper.Root initialStep={1}>
|
||||||
<Form.Root onValidationChange={setIsValid}>
|
<Form.Root onValidationChange={setIsValid}>
|
||||||
|
|
@ -22,13 +35,23 @@ export const CreateApStepper: React.FC = () => {
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
|
|
||||||
<Stepper.Step>
|
<Stepper.Step>
|
||||||
<Step header="Set Access Point">
|
<Step header="Enter the domain you want to host the NFA">
|
||||||
<CreateAccessPointForm />
|
<CreateAccessPointForm />
|
||||||
</Step>
|
</Step>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
|
|
||||||
<Stepper.Step>
|
<Stepper.Step>
|
||||||
<Step header="Create Access Point">
|
<Step
|
||||||
|
header={`Add a ${
|
||||||
|
isSubdomain(accesPointDomain) ? 'CNAME' : 'ANAME'
|
||||||
|
} record to your DNS provider`}
|
||||||
|
>
|
||||||
|
<APRecordStep />
|
||||||
|
</Step>
|
||||||
|
</Stepper.Step>
|
||||||
|
|
||||||
|
<Stepper.Step>
|
||||||
|
<Step header="Review your hosted frontend and confirm">
|
||||||
<CreateAccessPointPreview />
|
<CreateAccessPointPreview />
|
||||||
</Step>
|
</Step>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Flex } from '@/components';
|
import { Flex } from '@/components';
|
||||||
|
|
||||||
import { CreateAccessPoint } from './create-ap.context';
|
|
||||||
import {
|
import {
|
||||||
CreateAccessPointFormProvider,
|
CreateAccessPointFormProvider,
|
||||||
useAccessPointFormContextInit,
|
useAccessPointFormContextInit,
|
||||||
} from './create-ap.form.context';
|
} from './ap-form-step/create-ap.form.context';
|
||||||
|
import { CreateAccessPoint } from './create-ap.context';
|
||||||
import { CreateApStepper } from './create-ap.stepper';
|
import { CreateApStepper } from './create-ap.stepper';
|
||||||
|
|
||||||
export const CreateAP: React.FC = () => {
|
export const CreateAP: React.FC = () => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { styled } from '@/theme';
|
||||||
|
|
||||||
|
import { Flex } from '../../../components/layout';
|
||||||
|
|
||||||
|
export const DisplayTextStyles = {
|
||||||
|
Container: styled(Flex, {
|
||||||
|
flexDirection: 'column',
|
||||||
|
}),
|
||||||
|
Label: styled('label', {
|
||||||
|
color: '$slate11',
|
||||||
|
mb: '$1h',
|
||||||
|
|
||||||
|
fontSize: '$xs',
|
||||||
|
//TODO add variants
|
||||||
|
}),
|
||||||
|
Input: styled('span', {
|
||||||
|
backgroundColor: '$slate1',
|
||||||
|
borderColor: '$slate1',
|
||||||
|
color: '$slate12',
|
||||||
|
borderRadius: '$lg',
|
||||||
|
fontSize: '$sm',
|
||||||
|
height: '$11',
|
||||||
|
p: '$3 $3h',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { DisplayTextStyles as S } from './display-text.styles';
|
||||||
|
type DisplayTextProps = {
|
||||||
|
label: string;
|
||||||
|
value: string | React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DisplayText: React.FC<DisplayTextProps> = ({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
}: DisplayTextProps) => {
|
||||||
|
return (
|
||||||
|
<S.Container>
|
||||||
|
<S.Label>{label}</S.Label>
|
||||||
|
<S.Input>{value}</S.Input>
|
||||||
|
</S.Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './display-text';
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './nfa-icon';
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { styled } from '@/theme';
|
||||||
|
|
||||||
|
export const NFAIconStyles = {
|
||||||
|
Container: styled('span', {
|
||||||
|
p: '$1h',
|
||||||
|
borderRadius: '$full',
|
||||||
|
width: '$7',
|
||||||
|
height: '$7',
|
||||||
|
mr: '$2',
|
||||||
|
}),
|
||||||
|
Image: styled('img', {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { parseNumberToHexColor } from '@/utils/color';
|
||||||
|
|
||||||
|
import { NFAIconStyles as NS } from './nfa-icon.styles';
|
||||||
|
|
||||||
|
type NFAIconProps = {
|
||||||
|
image: string;
|
||||||
|
color: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NFAIconFragment: React.FC<NFAIconProps> = ({
|
||||||
|
image,
|
||||||
|
color,
|
||||||
|
}: NFAIconProps) => {
|
||||||
|
return (
|
||||||
|
<NS.Container
|
||||||
|
css={{ backgroundColor: `#${parseNumberToHexColor(color)}57` }}
|
||||||
|
>
|
||||||
|
<NS.Image src={image} />
|
||||||
|
</NS.Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
import { useQuery } from '@apollo/client';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
import { Combobox } from '@/components';
|
|
||||||
import { getLatestNFAsDocument } from '@/graphclient';
|
|
||||||
import { AppLog } from '@/utils';
|
|
||||||
|
|
||||||
import { CreateAccessPoint } from './create-ap.context';
|
|
||||||
|
|
||||||
export const NfaPicker: React.FC = () => {
|
|
||||||
const { nfa, setNfa } = CreateAccessPoint.useContext();
|
|
||||||
const { data, loading, error } = useQuery(getLatestNFAsDocument);
|
|
||||||
|
|
||||||
const items = useMemo(() => data?.tokens || [], [data]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
AppLog.errorToast('Error loading NFA list', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Combobox
|
|
||||||
isLoading={loading}
|
|
||||||
items={items}
|
|
||||||
selected={[nfa, setNfa]}
|
|
||||||
queryKey={['name', 'id']}
|
|
||||||
>
|
|
||||||
{({ Field, Options }) => (
|
|
||||||
<>
|
|
||||||
<Field>{(selected) => selected?.name || 'Select NFA'}</Field>
|
|
||||||
|
|
||||||
<Options>{(item) => item.name}</Options>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Combobox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -2,13 +2,15 @@ import { Flex, ResolvedAddress } from '@/components';
|
||||||
|
|
||||||
import { ColorPickerTest } from './color-picker';
|
import { ColorPickerTest } from './color-picker';
|
||||||
import { ComboboxTest } from './combobox-test';
|
import { ComboboxTest } from './combobox-test';
|
||||||
|
import { SpinnerTest } from './spinner-test';
|
||||||
import { ToastTest } from './toast-test';
|
import { ToastTest } from './toast-test';
|
||||||
|
|
||||||
export const ComponentsTest: React.FC = () => {
|
export const ComponentsTest: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Flex css={{ flexDirection: 'column' }}>
|
<Flex css={{ flexDirection: 'column' }}>
|
||||||
|
<SpinnerTest />
|
||||||
<ResolvedAddress css={{ alignSelf: 'center' }}>
|
<ResolvedAddress css={{ alignSelf: 'center' }}>
|
||||||
{'0x7ed735b7095c05d78df169f991f2b7f1a1f1a049'}
|
{'0x7ed735b7095c05d78df169f991f2b7f1a1f1a049a'}
|
||||||
</ResolvedAddress>
|
</ResolvedAddress>
|
||||||
<ComboboxTest />
|
<ComboboxTest />
|
||||||
<ColorPickerTest />
|
<ColorPickerTest />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Flex, Spinner, SpinnerDot } from '@/components';
|
||||||
|
|
||||||
|
export const SpinnerTest: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex css={{ alignItems: 'center' }}>
|
||||||
|
<SpinnerDot css={{ fontSize: '$6xl' }} />
|
||||||
|
<SpinnerDot css={{ fontSize: '$4xl' }} />
|
||||||
|
<SpinnerDot css={{ fontSize: '$lg' }} />
|
||||||
|
</Flex>
|
||||||
|
<Flex css={{ alignItems: 'center' }}>
|
||||||
|
<Spinner css={{ fontSize: '$6xl' }} />
|
||||||
|
<Spinner css={{ fontSize: '$4xl' }} />
|
||||||
|
<Spinner css={{ fontSize: '$lg' }} />
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -4,7 +4,7 @@ import { styled } from '@/theme';
|
||||||
export abstract class Explore {
|
export abstract class Explore {
|
||||||
static readonly Container = styled(Flex, {
|
static readonly Container = styled(Flex, {
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
width: '64.75rem', //TODO replace for max-width
|
width: '63.4rem', //TODO replace for max-width
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button, Card, Flex, Stepper } from '@/components';
|
import { Button, Card, CardTag, Flex, Stepper } from '@/components';
|
||||||
import { Mint } from '@/views/mint/mint.context';
|
import { Mint } from '@/views/mint/mint.context';
|
||||||
import { useMintFormContext } from '@/views/mint/nfa-step/form-step';
|
import { useMintFormContext } from '@/views/mint/nfa-step/form-step';
|
||||||
|
|
||||||
|
|
@ -25,17 +25,8 @@ export const RepoConfigurationBody: React.FC = () => {
|
||||||
<Flex css={{ rowGap: '$6', flexDirection: 'column' }}>
|
<Flex css={{ rowGap: '$6', flexDirection: 'column' }}>
|
||||||
<RepoRow
|
<RepoRow
|
||||||
repo={repositoryName.name}
|
repo={repositoryName.name}
|
||||||
css={{ mb: '0' }}
|
css={{ mb: '0', cursor: 'default' }}
|
||||||
button={
|
button={<CardTag>Use for NFA</CardTag>}
|
||||||
<Button
|
|
||||||
colorScheme="gray"
|
|
||||||
disabled
|
|
||||||
variant="outline"
|
|
||||||
css={{ py: '$1', height: '$5', borderRadius: '$md' }}
|
|
||||||
>
|
|
||||||
Use for NFA
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<RepoBranchCommitFields />
|
<RepoBranchCommitFields />
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ export const Repository: React.FC<RepositoryProps> = ({
|
||||||
<RepoRow
|
<RepoRow
|
||||||
onClick={handleSelectRepo}
|
onClick={handleSelectRepo}
|
||||||
repo={repository.name}
|
repo={repository.name}
|
||||||
|
css={{ cursor: 'pointer' }}
|
||||||
button={
|
button={
|
||||||
<Button
|
<Button
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ export const RepoRow = forwardRef<HTMLDivElement, RepoRowProps>(
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
my: '$4',
|
my: '$4',
|
||||||
...props.css,
|
...props.css,
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex css={{ alignItems: 'center' }}>
|
<Flex css={{ alignItems: 'center' }}>
|
||||||
|
|
|
||||||
|
|
@ -18,39 +18,37 @@ export const MintStepper: React.FC = () => {
|
||||||
},
|
},
|
||||||
} = useMintFormContext();
|
} = useMintFormContext();
|
||||||
|
|
||||||
if (!isSuccess) {
|
if (isSuccess) return <NftMinted />;
|
||||||
return (
|
|
||||||
<Stepper.Root initialStep={1}>
|
|
||||||
<Form.Root onValidationChange={setIsValid}>
|
|
||||||
<Stepper.Container>
|
|
||||||
<Stepper.Step>
|
|
||||||
<Step header="Connect your Ethereum Wallet to mint an NFA">
|
|
||||||
<WalletStep />
|
|
||||||
</Step>
|
|
||||||
</Stepper.Step>
|
|
||||||
|
|
||||||
<Stepper.Step>
|
return (
|
||||||
<Step header="Connect GitHub and select repository">
|
<Stepper.Root initialStep={1}>
|
||||||
<GithubStep />
|
<Form.Root onValidationChange={setIsValid}>
|
||||||
</Step>
|
<Stepper.Container>
|
||||||
</Stepper.Step>
|
<Stepper.Step>
|
||||||
|
<Step header="Connect your Ethereum Wallet to mint an NFA">
|
||||||
|
<WalletStep />
|
||||||
|
</Step>
|
||||||
|
</Stepper.Step>
|
||||||
|
|
||||||
<Stepper.Step>
|
<Stepper.Step>
|
||||||
<Step header="Finalize a few key things for your NFA">
|
<Step header="Connect GitHub and select repository">
|
||||||
<NFAStep />
|
<GithubStep />
|
||||||
</Step>
|
</Step>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
|
|
||||||
<Stepper.Step>
|
<Stepper.Step>
|
||||||
<Step header="Review your NFA and mint it on Ethereum">
|
<Step header="Finalize a few key things for your NFA">
|
||||||
<MintPreview />
|
<NFAStep />
|
||||||
</Step>
|
</Step>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
</Stepper.Container>
|
|
||||||
</Form.Root>
|
<Stepper.Step>
|
||||||
</Stepper.Root>
|
<Step header="Review your NFA and mint it on Ethereum">
|
||||||
);
|
<MintPreview />
|
||||||
} else {
|
</Step>
|
||||||
return <NftMinted />;
|
</Stepper.Step>
|
||||||
}
|
</Stepper.Container>
|
||||||
|
</Form.Root>
|
||||||
|
</Stepper.Root>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,3 @@ export const fileToBase64 = (file: File): Promise<string> =>
|
||||||
reader.onload = () => resolve(reader.result?.toString() || '');
|
reader.onload = () => resolve(reader.result?.toString() || '');
|
||||||
reader.onerror = reject;
|
reader.onerror = reject;
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a hex color string to a number.
|
|
||||||
*/
|
|
||||||
export const parseColorToNumber = (color: string): number => {
|
|
||||||
const hexColor = color.replace('#', '');
|
|
||||||
return parseInt(hexColor, 16);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useAccount } from 'wagmi';
|
||||||
|
|
||||||
import { Button, Card, Grid, Stepper } from '@/components';
|
import { Button, Card, Grid, Stepper } from '@/components';
|
||||||
import { AppLog } from '@/utils';
|
import { AppLog } from '@/utils';
|
||||||
|
import { parseColorToNumber } from '@/utils/color';
|
||||||
|
|
||||||
import { Mint } from '../../mint.context';
|
import { Mint } from '../../mint.context';
|
||||||
import { MintCardHeader } from '../../mint-card';
|
import { MintCardHeader } from '../../mint-card';
|
||||||
|
|
@ -11,7 +12,6 @@ import {
|
||||||
EnsDomainField,
|
EnsDomainField,
|
||||||
LogoField,
|
LogoField,
|
||||||
} from './fields';
|
} from './fields';
|
||||||
import { parseColorToNumber } from './form.utils';
|
|
||||||
import { useMintFormContext } from './mint-form.context';
|
import { useMintFormContext } from './mint-form.context';
|
||||||
|
|
||||||
export const MintFormStep: React.FC = () => {
|
export const MintFormStep: React.FC = () => {
|
||||||
|
|
@ -54,7 +54,7 @@ export const MintFormStep: React.FC = () => {
|
||||||
AppLog.errorToast('No address found. Please connect your wallet.');
|
AppLog.errorToast('No address found. Please connect your wallet.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// setting the args otherwise mint may fail
|
|
||||||
setArgs([
|
setArgs([
|
||||||
address,
|
address,
|
||||||
appName,
|
appName,
|
||||||
|
|
@ -62,7 +62,7 @@ export const MintFormStep: React.FC = () => {
|
||||||
domainURL,
|
domainURL,
|
||||||
ens,
|
ens,
|
||||||
gitCommit,
|
gitCommit,
|
||||||
`${repositoryName.url}/tree/${gitBranch}`,
|
`${repositoryName?.url}/tree/${gitBranch}`,
|
||||||
appLogo,
|
appLogo,
|
||||||
parseColorToNumber(logoColor),
|
parseColorToNumber(logoColor),
|
||||||
verifyNFA,
|
verifyNFA,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue