Skip to content

Commit

Permalink
fix(website): CAN-540 safe tx service.
Browse files Browse the repository at this point in the history
Fix: remove trailing slash if present when a safe tx service url is entered.
Feat: add URL validation  in safe tx service url input.
Refactor: Move Safe tx URL to a new file.
  • Loading branch information
nicosampler committed Oct 4, 2024
1 parent 2945c07 commit faf473a
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 119 deletions.
7 changes: 5 additions & 2 deletions packages/website/src/features/Settings/CustomProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ export default function CustomProviders() {
} else {
setInputError(undefined);
}
}, [inputValue]);
}, [editProviderIndex, inputValue, settings.customProviders]);

return (
<>
<Heading size="sm" fontWeight={600} mb={1}>
Provider
Custom Providers
</Heading>
<Text fontSize="sm" mb={3}>
Cannon will use custom providers (which may include{' '}
Expand Down Expand Up @@ -126,6 +126,9 @@ export default function CustomProviders() {
{settings.customProviders.map((provider, index) => (
<Flex key={index} ml={2} alignItems="center">
<Text
textOverflow="ellipsis"
whiteSpace="nowrap"
overflow="hidden"
width="100%"
borderColor="whiteAlpha.400"
placeholder="e.g. https://mainnet.infura.io/v3/api_key"
Expand Down
74 changes: 74 additions & 0 deletions packages/website/src/features/Settings/SafeTransactionService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useState, useEffect } from 'react';
import {
FormControl,
FormLabel,
Input,
Text,
Heading,
FormErrorMessage,
FormHelperText,
} from '@chakra-ui/react';
import { useStore } from '@/helpers/store';
import { z } from 'zod';

const SafeTransactionService: React.FC = () => {
const settings = useStore((s) => s.settings);
const setSettings = useStore((s) => s.setSettings);
const [inputUrl, setInputUrl] = useState(settings.stagingUrl);
const [validationError, setValidationError] = useState<string | null>(null);

const urlSchema = z.string().url('Change not saved! Invalid URL format.');

useEffect(() => {
setInputUrl(settings.stagingUrl);
}, [settings.stagingUrl]);

const handleUrlChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
// Remove trailing slash if present
const newUrl = evt.target.value.replace(/\/$/, '');
setInputUrl(newUrl);

try {
urlSchema.parse(newUrl);
setValidationError(null);
setSettings({ stagingUrl: newUrl });
} catch (error) {
if (error instanceof z.ZodError) {
setValidationError(error.errors[0].message);
}
}
};

return (
<>
<Heading size="md" mb={3}>
Safe Transaction Service
</Heading>
<Text fontSize="md" mb={4}>
Enter the URL for the Safe Transaction Service.
</Text>
<FormControl isInvalid={!!validationError}>
<FormLabel>Safe Transaction Service URL</FormLabel>
<Input
bg="black"
borderColor="whiteAlpha.400"
value={inputUrl}
type="text"
name="stagingUrl"
onChange={handleUrlChange}
placeholder="https://safe-transaction.example.com"
/>
{validationError && (
<FormErrorMessage>{validationError}</FormErrorMessage>
)}
<FormHelperText color="gray.300">
The same collection service URL must be used by all signers for a
given transaction. Hosting Instructions:
https://github.com/usecannon/cannon-safe-app-backend
</FormHelperText>
</FormControl>
</>
);
};

export default SafeTransactionService;
144 changes: 27 additions & 117 deletions packages/website/src/features/Settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
Code,
Container,
FormControl,
FormErrorMessage,
FormHelperText,
FormLabel,
Heading,
Expand All @@ -21,45 +20,27 @@ import {
Thead,
Tr,
} from '@chakra-ui/react';
import entries from 'just-entries';
import { Store, initialState, useStore } from '@/helpers/store';
import { links } from '@/constants/links';
import { Alert } from '@/components/Alert';
import CustomProviders from '@/features/Settings/CustomProviders';

type Setting = {
title: string;
placeholder?: string;
description?: string;
password?: boolean;
optional?: boolean;
// Validate function should return an error message if the value is invalid
validate?: (value: any) => string | undefined;
};

const SETTINGS: Record<
Exclude<
keyof Store['settings'],
'ipfsApiUrl' | 'isIpfsGateway' | 'customProviders' | 'pythUrl'
>,
Setting
> = {
stagingUrl: {
title: 'Safe Signature Collection Service',
placeholder: 'https://service.com',
description:
'The same collection service URL must be used by all signers for a given transaction. Hosting Instructions: https://github.com/usecannon/cannon-safe-app-backend ',
},
};

export function useSettingsValidation() {
const settings = useStore((s) => s.settings);

return !entries(SETTINGS).some(([key, s]) => {
const val = settings[key];
return (!s.optional && !val) || !!s.validate?.(val);
});
}
import SafeTransactionService from '@/features/Settings/SafeTransactionService';
import { initialState, useStore } from '@/helpers/store';
import { FC, PropsWithChildren } from 'react';

const SectionBox: FC<PropsWithChildren> = ({ children }) => (
<Box
mb={6}
p={6}
bg="gray.800"
display="block"
borderWidth="1px"
borderStyle="solid"
borderColor="gray.600"
borderRadius="4px"
>
{children}
</Box>
);

export default function SettingsPage() {
const settings = useStore((s) => s.settings);
Expand All @@ -76,20 +57,11 @@ export default function SettingsPage() {
Settings
</Heading>

<Box mb="6">
<SectionBox>
<CustomProviders />
</Box>
</SectionBox>

<Box
mb={6}
p={6}
bg="gray.800"
display="block"
borderWidth="1px"
borderStyle="solid"
borderColor="gray.600"
borderRadius="4px"
>
<SectionBox>
<Heading size="md" mb={3}>
Ethereum
</Heading>
Expand Down Expand Up @@ -167,18 +139,9 @@ export default function SettingsPage() {
</Table>
</TableContainer>
</Box>
</Box>
</SectionBox>

<Box
mb={6}
p={6}
bg="gray.800"
display="block"
borderWidth="1px"
borderStyle="solid"
borderColor="gray.600"
borderRadius="4px"
>
<SectionBox>
<Heading size="md" mb={2}>
IPFS
</Heading>
Expand Down Expand Up @@ -217,64 +180,11 @@ export default function SettingsPage() {
Test IPFS Endpoint
</Button>
) : null}
</Box>

<Box
mb={6}
p={6}
bg="gray.800"
display="block"
borderWidth="1px"
borderStyle="solid"
borderColor="gray.600"
borderRadius="4px"
>
<Heading size="md" mb={2}>
Deployer
</Heading>

<Text fontSize="md" mb={4}>
Configure the{' '}
<Link as={NextLink} href="/deploy">
deployer
</Link>
, which allows the staging and execution of builds for protocols
controlled by{' '}
<Link isExternal href="https://safe.global/">
Safes
</Link>
.
</Text>

{entries(SETTINGS).map(([key, s]) => {
const val = settings[key];
const validationError =
!val && val.length ? s.description : s.validate?.(settings[key]);
</SectionBox>

return (
<FormControl key={key} isInvalid={!!validationError} mb="4">
<FormLabel>{s.title}</FormLabel>
<Input
bg="black"
borderColor="whiteAlpha.400"
type={s.password ? 'password' : 'text'}
name={key}
placeholder={s.placeholder}
value={settings[key]}
onChange={(evt) => setSettings({ [key]: evt.target.value })}
/>
{!validationError && s.description && (
<FormHelperText color="gray.300">
{s.description}
</FormHelperText>
)}
{validationError && (
<FormErrorMessage>{validationError}</FormErrorMessage>
)}
</FormControl>
);
})}
</Box>
<SectionBox>
<SafeTransactionService />
</SectionBox>

<FormControl>
<FormHelperText color="gray.300" mb={5} textAlign="right">
Expand Down

0 comments on commit faf473a

Please sign in to comment.