From f571f3382481d8bb6c468f96844c13248a54a7f8 Mon Sep 17 00:00:00 2001 From: "kody.low" Date: Mon, 14 Oct 2024 13:49:53 -0700 Subject: [PATCH 1/7] feat: icon preview and sites input --- .../tabs/meta/manager/IconPreview.tsx | 34 +-- .../dashboard/tabs/meta/manager/MetaInput.tsx | 106 ++++++---- .../tabs/meta/manager/MetaManager.tsx | 103 ++++++--- .../tabs/meta/manager/RequiredMeta.tsx | 79 ++----- .../tabs/meta/manager/SitesInput.tsx | 195 ++++++++++++++++++ testing/btc.png | Bin 0 -> 87786 bytes testing/test-pngs.py | 39 ++++ 7 files changed, 418 insertions(+), 138 deletions(-) create mode 100644 apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx create mode 100644 testing/btc.png create mode 100644 testing/test-pngs.py diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/IconPreview.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/IconPreview.tsx index ad8be9bad..d55ea3cff 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/IconPreview.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/IconPreview.tsx @@ -1,30 +1,34 @@ import React from 'react'; -import { Flex, Image } from '@chakra-ui/react'; +import { Box, Flex, Image } from '@chakra-ui/react'; interface IconPreviewProps { - iconPreview: string | null; + imageUrl: string; + isValid: boolean; } -export const IconPreview: React.FC = ({ iconPreview }) => ( - = ({ + imageUrl, + isValid, +}) => ( + - {iconPreview ? ( + {isValid && imageUrl ? ( Federation Icon ) : ( = ({ iconPreview }) => ( ? )} - + ); diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx index 9ad3a21e6..430e4293e 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useCallback } from 'react'; import { Flex, FormLabel, Input } from '@chakra-ui/react'; import { snakeToTitleCase } from '@fedimint/utils'; import { IconPreview } from './IconPreview'; @@ -7,45 +7,81 @@ interface MetaInputProps { metaKey: string; value: string; onChange: (key: string, value: string) => void; - isIconValid: boolean; - iconPreview: string | null; } export const MetaInput: React.FC = ({ metaKey, value, onChange, - isIconValid, - iconPreview, -}) => ( - - - - {snakeToTitleCase(metaKey)} - - - onChange(metaKey, e.target.value)} - borderColor={ - (['federation_name', 'welcome_message'].includes(metaKey) && - value.trim() === '') || - (metaKey === 'federation_icon_url' && !isIconValid) - ? 'yellow.400' - : 'inherit' - } - /> - {metaKey === 'federation_icon_url' && ( - - )} +}) => { + const [localValue, setLocalValue] = useState(value); + const [localImageUrl, setLocalImageUrl] = useState(null); + const [localIsIconValid, setLocalIsIconValid] = useState(true); + + const validateIcon = useCallback(async (url: string) => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const blob = await response.blob(); + if (!blob.type.startsWith('image/')) { + throw new Error('Invalid image format'); + } + const objectURL = URL.createObjectURL(blob); + setLocalImageUrl(objectURL); + setLocalIsIconValid(true); + return objectURL; + } catch (error) { + setLocalImageUrl(null); + setLocalIsIconValid(false); + if (error instanceof Error) { + console.error(`Icon validation failed: ${error.message}`); + } + return null; + } + }, []); + + const handleInputChange = (e: React.ChangeEvent) => { + const newValue = e.target.value; + setLocalValue(newValue); + }; + + const handleBlur = () => { + onChange(metaKey, localValue); + if (metaKey === 'federation_icon_url' && localValue) { + validateIcon(localValue); + } + }; + + return ( + + + + {snakeToTitleCase(metaKey)} + + + + {metaKey === 'federation_icon_url' && ( + + )} + - -); + ); +}; diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx index cb3b5a805..3a29e353a 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx @@ -8,6 +8,7 @@ import { Input, Link, Text, + IconButton, } from '@chakra-ui/react'; import { fieldsToMeta, metaToHex, useTranslation } from '@fedimint/utils'; import { ParsedConsensusMeta } from '@fedimint/types'; @@ -15,6 +16,7 @@ import { DEFAULT_META_KEY } from '../../FederationTabsCard'; import { RequiredMeta } from './RequiredMeta'; import { useGuardianAdminApi } from '../../../../../../context/hooks'; import { ModuleRpc } from '../../../../../../types/guardian'; +import { FiX } from 'react-icons/fi'; const metaArrayToObject = ( metaArray: [string, string][] @@ -41,8 +43,8 @@ export const MetaManager = React.memo(function MetaManager({ const [requiredMeta, setRequiredMeta] = useState>({ federation_name: '', welcome_message: '', - popup_end_timestamp: '', federation_icon_url: '', + sites: '', }); const [isRequiredMetaValid, setIsRequiredMetaValid] = useState(true); const [optionalMeta, setOptionalMeta] = useState>({}); @@ -53,25 +55,27 @@ export const MetaManager = React.memo(function MetaManager({ const { federation_name, welcome_message, - popup_end_timestamp, federation_icon_url, + sites, ...rest } = metaObj; setRequiredMeta({ federation_name, welcome_message, - popup_end_timestamp, federation_icon_url, + sites: sites || '[]', }); setOptionalMeta(rest); } }, [consensusMeta]); const isAnyRequiredFieldEmpty = useCallback(() => { - // Popup end timestamp is optional but placed in required for simplicity - return ['federation_name', 'welcome_message', 'federation_icon_url'].some( - (key) => requiredMeta[key].trim() === '' - ); + return [ + 'federation_name', + 'welcome_message', + 'federation_icon_url', + 'sites', + ].some((key) => requiredMeta[key].trim() === ''); }, [requiredMeta]); const isMetaUnchanged = useCallback(() => { @@ -104,22 +108,33 @@ export const MetaManager = React.memo(function MetaManager({ const { federation_name, welcome_message, - popup_end_timestamp, federation_icon_url, + sites, ...rest } = metaObj; setRequiredMeta({ federation_name, welcome_message, - popup_end_timestamp, federation_icon_url, + sites: sites, }); setOptionalMeta(rest); } }, [consensusMeta]); - const handleOptionalMetaChange = (key: string, value: string) => { - setOptionalMeta((prev) => ({ ...prev, [key]: value })); + const handleOptionalMetaChange = ( + oldKey: string, + newKey: string, + value: string + ) => { + setOptionalMeta((prev) => { + const newMeta = { ...prev }; + if (oldKey !== newKey) { + delete newMeta[oldKey]; + } + newMeta[newKey] = value; + return newMeta; + }); }; const addCustomField = () => { @@ -141,7 +156,7 @@ export const MetaManager = React.memo(function MetaManager({ const updatedMetaArray = Object.entries({ ...requiredMeta, ...optionalMeta, - }).filter(([key, value]) => key !== 'popup_end_timestamp' || value !== ''); + }); api .moduleApiCall<{ metaValue: string }[]>( Number(metaModuleId), @@ -195,25 +210,53 @@ export const MetaManager = React.memo(function MetaManager({ /> {Object.entries(optionalMeta).map(([key, value]) => ( - - - - {key} - - + + + + + Key + + + handleOptionalMetaChange(key, e.target.value, value) + } + size='sm' + /> + + + + Value + + + handleOptionalMetaChange(key, key, e.target.value) + } + size='sm' + /> + - handleOptionalMetaChange(key, e.target.value)} + } + size='sm' + fontSize='xl' + color='red.500' + variant='ghost' + onClick={() => removeCustomField(key)} + minWidth='auto' + height='auto' + padding={1} /> ))} diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/RequiredMeta.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/RequiredMeta.tsx index d0d6472b4..7e656dbf9 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/RequiredMeta.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/RequiredMeta.tsx @@ -1,6 +1,7 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { Flex } from '@chakra-ui/react'; import { MetaInput } from './MetaInput'; +import { SitesInput } from './SitesInput'; interface RequiredMetaProps { requiredMeta: Record; @@ -12,70 +13,32 @@ interface RequiredMetaProps { export const RequiredMeta: React.FC = ({ requiredMeta, setRequiredMeta, - setIsValid, }) => { - const [iconPreview, setIconPreview] = useState(null); - const [isIconValid, setIsIconValid] = useState(true); - - useEffect(() => { - let objectURL: string | null = null; - - const validateIcon = async (url: string) => { - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const blob = await response.blob(); - if (!blob.type.startsWith('image/')) { - throw new Error('Invalid image format'); - } - objectURL = URL.createObjectURL(blob); - setIconPreview(objectURL); - setIsIconValid(true); - } catch (error) { - setIconPreview(null); - setIsIconValid(false); - if (error instanceof Error) { - console.error(`Icon validation failed: ${error.message}`); - } - } - }; - - if (requiredMeta.federation_icon_url) { - validateIcon(requiredMeta.federation_icon_url); - } else { - setIconPreview(null); - setIsIconValid(true); - } - - return () => { - if (objectURL) { - URL.revokeObjectURL(objectURL); - } - }; - }, [requiredMeta.federation_icon_url]); - - useEffect(() => { - setIsValid(isIconValid); - }, [isIconValid, setIsValid]); - const handleChange = (key: string, value: string) => { setRequiredMeta((prev) => ({ ...prev, [key]: value })); }; return ( - {Object.entries(requiredMeta).map(([key, value]) => ( - - ))} + {Object.entries(requiredMeta).map(([key, value]) => { + if (key === 'sites') { + return ( + handleChange(key, newValue)} + /> + ); + } + return ( + + ); + })} ); }; diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx new file mode 100644 index 000000000..bb7c32ca1 --- /dev/null +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx @@ -0,0 +1,195 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { Flex, FormLabel, Input, Button, IconButton } from '@chakra-ui/react'; +import { FiX } from 'react-icons/fi'; +import { IconPreview } from './IconPreview'; + +interface Site { + id: string; + url: string; + title: string; + imageUrl: string; +} + +interface SitesInputProps { + value: string; + onChange: (value: string) => void; +} + +export const SitesInput: React.FC = ({ value, onChange }) => { + const [sites, setSites] = useState([]); + const [imageValidities, setImageValidities] = useState([]); + const [localImageUrls, setLocalImageUrls] = useState<(string | null)[]>([]); + + useEffect(() => { + try { + const parsedSites = JSON.parse(value); + setSites(Array.isArray(parsedSites) ? parsedSites : []); + setImageValidities(new Array(parsedSites.length).fill(true)); + setLocalImageUrls(new Array(parsedSites.length).fill(null)); + } catch { + setSites([]); + setImageValidities([]); + setLocalImageUrls([]); + } + }, [value]); + + const validateIcon = useCallback(async (url: string, index: number) => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const blob = await response.blob(); + if (!blob.type.startsWith('image/')) { + throw new Error('Invalid image format'); + } + const objectURL = URL.createObjectURL(blob); + setLocalImageUrls((prev) => { + const newUrls = [...prev]; + newUrls[index] = objectURL; + return newUrls; + }); + setImageValidities((prev) => { + const newValidities = [...prev]; + newValidities[index] = true; + return newValidities; + }); + return objectURL; + } catch (error) { + setLocalImageUrls((prev) => { + const newUrls = [...prev]; + newUrls[index] = null; + return newUrls; + }); + setImageValidities((prev) => { + const newValidities = [...prev]; + newValidities[index] = false; + return newValidities; + }); + if (error instanceof Error) { + console.error(`Icon validation failed: ${error.message}`); + } + return null; + } + }, []); + + const handleSiteChange = ( + index: number, + field: keyof Site, + newValue: string + ) => { + const newSites = [...sites]; + newSites[index] = { ...newSites[index], [field]: newValue }; + onChange(JSON.stringify(newSites)); + + if (field === 'imageUrl') { + setImageValidities((prev) => { + const newValidities = [...prev]; + newValidities[index] = true; // Reset validity when user starts typing + return newValidities; + }); + setLocalImageUrls((prev) => { + const newUrls = [...prev]; + newUrls[index] = null; // Reset local URL when user starts typing + return newUrls; + }); + } + }; + + const handleImageUrlBlur = (index: number) => { + const imageUrl = sites[index].imageUrl; + if (imageUrl) { + validateIcon(imageUrl, index); + } + }; + + const addSite = () => { + const newSites = [...sites, { id: '', url: '', title: '', imageUrl: '' }]; + onChange(JSON.stringify(newSites)); + }; + + const removeSite = (index: number) => { + const newSites = sites.filter((_, i) => i !== index); + onChange(JSON.stringify(newSites)); + }; + + return ( + + + + Sites + + + + {sites.length > 0 && ( + + {sites.map((site, index) => ( + + + {(Object.keys(site) as Array).map((field) => ( + + + {field.charAt(0).toUpperCase() + field.slice(1)} + + + handleSiteChange(index, field, e.target.value) + } + onBlur={() => { + if (field === 'imageUrl') { + handleImageUrlBlur(index); + } + }} + size='sm' + isInvalid={ + field === 'imageUrl' && !imageValidities[index] + } + /> + + ))} + + + } + size='sm' + fontSize='xl' + color='red.500' + variant='ghost' + onClick={() => removeSite(index)} + minWidth='auto' + height='auto' + padding={1} + /> + + ))} + + )} + + ); +}; diff --git a/testing/btc.png b/testing/btc.png new file mode 100644 index 0000000000000000000000000000000000000000..1d14bc61d28355b1caabcd1095e06341b5527267 GIT binary patch literal 87786 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 literal 0 HcmV?d00001 diff --git a/testing/test-pngs.py b/testing/test-pngs.py new file mode 100644 index 000000000..768e99651 --- /dev/null +++ b/testing/test-pngs.py @@ -0,0 +1,39 @@ +from http.server import HTTPServer, SimpleHTTPRequestHandler +import os + + +class ImageServer(SimpleHTTPRequestHandler): + def do_GET(self): + if self.path.endswith(".png"): + self.send_response(200) + self.send_header("Content-type", "image/png") + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS") + self.send_header( + "Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Accept" + ) + self.end_headers() + with open(self.path[1:], "rb") as f: + self.wfile.write(f.read()) + else: + self.send_error(404, "File not found") + + def do_OPTIONS(self): + self.send_response(200) + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS") + self.send_header( + "Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Accept" + ) + self.end_headers() + + +def run(port=8000): + server_address = ("", port) + httpd = HTTPServer(server_address, ImageServer) + print(f"Serving images on port {port}") + httpd.serve_forever() + + +if __name__ == "__main__": + run() From 485f1b7c56e0554259eb133c8810368953ad91b5 Mon Sep 17 00:00:00 2001 From: "kody.low" Date: Mon, 14 Oct 2024 14:02:25 -0700 Subject: [PATCH 2/7] fix: split out sites --- .../tabs/meta/manager/MetaManager.tsx | 91 +++++++++++++++---- .../tabs/meta/manager/RequiredMeta.tsx | 10 -- 2 files changed, 74 insertions(+), 27 deletions(-) diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx index 3a29e353a..5a0fceb7b 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx @@ -17,6 +17,7 @@ import { RequiredMeta } from './RequiredMeta'; import { useGuardianAdminApi } from '../../../../../../context/hooks'; import { ModuleRpc } from '../../../../../../types/guardian'; import { FiX } from 'react-icons/fi'; +import { SitesInput } from './SitesInput'; const metaArrayToObject = ( metaArray: [string, string][] @@ -44,9 +45,9 @@ export const MetaManager = React.memo(function MetaManager({ federation_name: '', welcome_message: '', federation_icon_url: '', - sites: '', }); const [isRequiredMetaValid, setIsRequiredMetaValid] = useState(true); + const [sites, setSites] = useState('[]'); const [optionalMeta, setOptionalMeta] = useState>({}); useEffect(() => { @@ -63,19 +64,40 @@ export const MetaManager = React.memo(function MetaManager({ federation_name, welcome_message, federation_icon_url, - sites: sites || '[]', }); + setSites(sites || '[]'); setOptionalMeta(rest); } }, [consensusMeta]); const isAnyRequiredFieldEmpty = useCallback(() => { - return [ + const requiredFields = [ 'federation_name', 'welcome_message', 'federation_icon_url', - 'sites', - ].some((key) => requiredMeta[key].trim() === ''); + ]; + const areBasicFieldsEmpty = requiredFields.some( + (key) => requiredMeta[key].trim() === '' + ); + + if (areBasicFieldsEmpty) return true; + + // Check sites + if (requiredMeta.sites.trim() !== '') { + try { + const sites = JSON.parse(requiredMeta.sites); + if (Array.isArray(sites) && sites.length > 0) { + return sites.some( + (site) => !site.id || !site.url || !site.title || !site.imageUrl + ); + } + } catch (error) { + console.error('Error parsing sites JSON:', error); + return true; + } + } + + return false; }, [requiredMeta]); const isMetaUnchanged = useCallback(() => { @@ -84,23 +106,30 @@ export const MetaManager = React.memo(function MetaManager({ // Check if all current fields (required and optional) match the consensus meta const allCurrentFields = { ...requiredMeta, ...optionalMeta }; - const currentUnchanged = Object.entries(allCurrentFields).every( - ([key, value]) => consensusMetaObj[key] === value - ); - // Check if any fields from consensus meta are missing in the current fields - const noFieldsRemoved = Object.keys(consensusMetaObj).every( - (key) => key in allCurrentFields - ); + // Check if the number of fields has changed + if ( + Object.keys(allCurrentFields).length !== + Object.keys(consensusMetaObj).length + ) { + return false; + } - return currentUnchanged && noFieldsRemoved; + // Check if any field values have changed + return Object.entries(allCurrentFields).every( + ([key, value]) => consensusMetaObj[key] === value + ); }, [requiredMeta, optionalMeta, consensusMeta]); const canSubmit = useCallback(() => { - return ( - !isAnyRequiredFieldEmpty() && isRequiredMetaValid && !isMetaUnchanged() - ); - }, [isAnyRequiredFieldEmpty, isRequiredMetaValid, isMetaUnchanged]); + const allFieldsFilled = + Object.values(requiredMeta).every((value) => value.trim() !== '') && + Object.entries(optionalMeta).every( + ([key, value]) => key.trim() !== '' && value.trim() !== '' + ); + + return allFieldsFilled && isRequiredMetaValid && !isMetaUnchanged(); + }, [requiredMeta, optionalMeta, isRequiredMetaValid, isMetaUnchanged]); const resetToConsensus = useCallback(() => { if (consensusMeta?.value) { @@ -155,6 +184,7 @@ export const MetaManager = React.memo(function MetaManager({ if (metaModuleId === undefined || isAnyRequiredFieldEmpty()) return; const updatedMetaArray = Object.entries({ ...requiredMeta, + sites, ...optionalMeta, }); api @@ -177,11 +207,16 @@ export const MetaManager = React.memo(function MetaManager({ api, metaModuleId, requiredMeta, + sites, optionalMeta, isAnyRequiredFieldEmpty, setActiveTab, ]); + const handleSitesChange = (sitesJson: string) => { + setSites(sitesJson); + }; + return ( @@ -202,12 +237,32 @@ export const MetaManager = React.memo(function MetaManager({ + + {/* Required Meta */} + + Required Meta Fields + + + + + {/* Sites */} + + Sites + + + + + + {/* Optional Meta */} + + Optional Meta Fields + {Object.entries(optionalMeta).map(([key, value]) => ( @@ -261,6 +316,8 @@ export const MetaManager = React.memo(function MetaManager({ ))} + + {/* Buttons */} + + + {Object.entries(customMeta).map(([key, value]) => ( + + + + + Key + + handleMetaChange(key, e.target.value, value)} + size='sm' + /> + + + + Value + + handleMetaChange(key, key, e.target.value)} + onBlur={() => { + if (key === 'federation_icon_url') { + handleIconUrlBlur(); + } + }} + size='sm' + isInvalid={key === 'federation_icon_url' && !iconValidity} + /> + + + {key === 'federation_icon_url' && ( + + )} + } + size='sm' + fontSize='xl' + color='red.500' + variant='ghost' + onClick={() => removeCustomField(key)} + minWidth='auto' + height='auto' + padding={1} + /> + + ))} + + + ); +}; diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx index 430e4293e..42aaab4ba 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useEffect } from 'react'; import { Flex, FormLabel, Input } from '@chakra-ui/react'; import { snakeToTitleCase } from '@fedimint/utils'; import { IconPreview } from './IconPreview'; @@ -42,6 +42,13 @@ export const MetaInput: React.FC = ({ } }, []); + // Add this useEffect hook + useEffect(() => { + if (metaKey === 'federation_icon_url' && value) { + validateIcon(value); + } + }, [metaKey, value, validateIcon]); + const handleInputChange = (e: React.ChangeEvent) => { const newValue = e.target.value; setLocalValue(newValue); diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx index 5a0fceb7b..acef9db7a 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaManager.tsx @@ -1,23 +1,12 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { - Box, - Button, - Divider, - Flex, - FormLabel, - Input, - Link, - Text, - IconButton, -} from '@chakra-ui/react'; +import { Box, Button, Divider, Flex, Link, Text } from '@chakra-ui/react'; import { fieldsToMeta, metaToHex, useTranslation } from '@fedimint/utils'; import { ParsedConsensusMeta } from '@fedimint/types'; import { DEFAULT_META_KEY } from '../../FederationTabsCard'; -import { RequiredMeta } from './RequiredMeta'; import { useGuardianAdminApi } from '../../../../../../context/hooks'; import { ModuleRpc } from '../../../../../../types/guardian'; -import { FiX } from 'react-icons/fi'; import { SitesInput } from './SitesInput'; +import { CustomMetaFields } from './CustomMetaFields'; const metaArrayToObject = ( metaArray: [string, string][] @@ -41,53 +30,32 @@ export const MetaManager = React.memo(function MetaManager({ }: MetaManagerProps): JSX.Element { const { t } = useTranslation(); const api = useGuardianAdminApi(); - const [requiredMeta, setRequiredMeta] = useState>({ - federation_name: '', - welcome_message: '', - federation_icon_url: '', - }); - const [isRequiredMetaValid, setIsRequiredMetaValid] = useState(true); const [sites, setSites] = useState('[]'); - const [optionalMeta, setOptionalMeta] = useState>({}); + const [customMeta, setCustomMeta] = useState>({}); useEffect(() => { if (consensusMeta?.value) { const metaObj = metaArrayToObject(consensusMeta.value); - const { - federation_name, - welcome_message, - federation_icon_url, - sites, - ...rest - } = metaObj; - setRequiredMeta({ - federation_name, - welcome_message, - federation_icon_url, - }); - setSites(sites || '[]'); - setOptionalMeta(rest); + const { sites = '[]', ...rest } = metaObj; + + setSites(sites); + setCustomMeta(rest); } }, [consensusMeta]); - const isAnyRequiredFieldEmpty = useCallback(() => { - const requiredFields = [ - 'federation_name', - 'welcome_message', - 'federation_icon_url', - ]; - const areBasicFieldsEmpty = requiredFields.some( - (key) => requiredMeta[key].trim() === '' - ); - - if (areBasicFieldsEmpty) return true; + const isAnyFieldEmpty = useCallback(() => { + if ( + Object.entries(customMeta).some( + ([key, value]) => key.trim() === '' || value.trim() === '' + ) + ) + return true; - // Check sites - if (requiredMeta.sites.trim() !== '') { + if (sites.trim() !== '') { try { - const sites = JSON.parse(requiredMeta.sites); - if (Array.isArray(sites) && sites.length > 0) { - return sites.some( + const localSites = JSON.parse(sites); + if (Array.isArray(localSites) && localSites.length > 0) { + return localSites.some( (site) => !site.id || !site.url || !site.title || !site.imageUrl ); } @@ -98,94 +66,46 @@ export const MetaManager = React.memo(function MetaManager({ } return false; - }, [requiredMeta]); + }, [customMeta, sites]); const isMetaUnchanged = useCallback(() => { if (!consensusMeta?.value) return false; const consensusMetaObj = metaArrayToObject(consensusMeta.value); - // Check if all current fields (required and optional) match the consensus meta - const allCurrentFields = { ...requiredMeta, ...optionalMeta }; - // Check if the number of fields has changed if ( - Object.keys(allCurrentFields).length !== + Object.keys(customMeta).length + 1 !== // +1 for sites Object.keys(consensusMetaObj).length ) { return false; } // Check if any field values have changed - return Object.entries(allCurrentFields).every( - ([key, value]) => consensusMetaObj[key] === value + return ( + Object.entries(customMeta).every( + ([key, value]) => consensusMetaObj[key] === value + ) && consensusMetaObj.sites === sites ); - }, [requiredMeta, optionalMeta, consensusMeta]); + }, [customMeta, sites, consensusMeta]); const canSubmit = useCallback(() => { - const allFieldsFilled = - Object.values(requiredMeta).every((value) => value.trim() !== '') && - Object.entries(optionalMeta).every( - ([key, value]) => key.trim() !== '' && value.trim() !== '' - ); - - return allFieldsFilled && isRequiredMetaValid && !isMetaUnchanged(); - }, [requiredMeta, optionalMeta, isRequiredMetaValid, isMetaUnchanged]); + return !isAnyFieldEmpty() && !isMetaUnchanged(); + }, [isAnyFieldEmpty, isMetaUnchanged]); const resetToConsensus = useCallback(() => { if (consensusMeta?.value) { const metaObj = metaArrayToObject(consensusMeta.value); - const { - federation_name, - welcome_message, - federation_icon_url, - sites, - ...rest - } = metaObj; - setRequiredMeta({ - federation_name, - welcome_message, - federation_icon_url, - sites: sites, - }); - setOptionalMeta(rest); + const { sites, ...rest } = metaObj; + setSites(sites || '[]'); + setCustomMeta(rest); } }, [consensusMeta]); - const handleOptionalMetaChange = ( - oldKey: string, - newKey: string, - value: string - ) => { - setOptionalMeta((prev) => { - const newMeta = { ...prev }; - if (oldKey !== newKey) { - delete newMeta[oldKey]; - } - newMeta[newKey] = value; - return newMeta; - }); - }; - - const addCustomField = () => { - const timestamp = Date.now(); - const newKey = `custom_field_${timestamp}`; - setOptionalMeta((prev) => ({ ...prev, [newKey]: '' })); - }; - - const removeCustomField = (key: string) => { - setOptionalMeta((prev) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { [key]: _, ...rest } = prev; - return rest; - }); - }; - const proposeMetaEdits = useCallback(() => { - if (metaModuleId === undefined || isAnyRequiredFieldEmpty()) return; + if (metaModuleId === undefined || isAnyFieldEmpty()) return; const updatedMetaArray = Object.entries({ - ...requiredMeta, + ...customMeta, sites, - ...optionalMeta, }); api .moduleApiCall<{ metaValue: string }[]>( @@ -203,15 +123,7 @@ export const MetaManager = React.memo(function MetaManager({ console.error(error); alert('Failed to propose meta edits. Please try again.'); }); - }, [ - api, - metaModuleId, - requiredMeta, - sites, - optionalMeta, - isAnyRequiredFieldEmpty, - setActiveTab, - ]); + }, [api, metaModuleId, customMeta, sites, isAnyFieldEmpty, setActiveTab]); const handleSitesChange = (sitesJson: string) => { setSites(sitesJson); @@ -235,89 +147,10 @@ export const MetaManager = React.memo(function MetaManager({ {t('federation-dashboard.config.manage-meta.learn-more')} - - - {/* Required Meta */} - - Required Meta Fields - - - + - - {/* Sites */} - - Sites - - - - - {/* Optional Meta */} - - Optional Meta Fields - - - {Object.entries(optionalMeta).map(([key, value]) => ( - - - - - Key - - - handleOptionalMetaChange(key, e.target.value, value) - } - size='sm' - /> - - - - Value - - - handleOptionalMetaChange(key, key, e.target.value) - } - size='sm' - /> - - - } - size='sm' - fontSize='xl' - color='red.500' - variant='ghost' - onClick={() => removeCustomField(key)} - minWidth='auto' - height='auto' - padding={1} - /> - - ))} - - - {/* Buttons */} ); diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/RequiredMeta.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/RequiredMeta.tsx index d2cb46939..96aaf862d 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/RequiredMeta.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/RequiredMeta.tsx @@ -1,34 +1,73 @@ -import React from 'react'; -import { Flex } from '@chakra-ui/react'; +import React, { useCallback, useEffect, useState } from 'react'; +import { Flex, Text } from '@chakra-ui/react'; import { MetaInput } from './MetaInput'; +import { ParsedConsensusMeta } from '@fedimint/types'; interface RequiredMetaProps { - requiredMeta: Record; - setRequiredMeta: React.Dispatch>>; - isValid: boolean; - setIsValid: React.Dispatch>; + consensusMeta?: ParsedConsensusMeta; + onValidityChange: (isValid: boolean) => void; + onRequiredMetaChange: (requiredMeta: Record) => void; } export const RequiredMeta: React.FC = ({ - requiredMeta, - setRequiredMeta, + consensusMeta, + onValidityChange, + onRequiredMetaChange, }) => { + const [requiredMeta, setRequiredMeta] = useState>({ + federation_name: '', + welcome_message: '', + federation_icon_url: '', + }); + + useEffect(() => { + if (consensusMeta?.value) { + const metaObj = Object.fromEntries(consensusMeta.value); + const { + federation_name = '', + welcome_message = '', + federation_icon_url = '', + } = metaObj; + + setRequiredMeta({ + federation_name, + welcome_message, + federation_icon_url, + }); + } + }, [consensusMeta]); + + const isAnyRequiredFieldEmpty = useCallback(() => { + return Object.values(requiredMeta).some((value) => value.trim() === ''); + }, [requiredMeta]); + + useEffect(() => { + onValidityChange(!isAnyRequiredFieldEmpty()); + onRequiredMetaChange(requiredMeta); + }, [ + requiredMeta, + isAnyRequiredFieldEmpty, + onValidityChange, + onRequiredMetaChange, + ]); + const handleChange = (key: string, value: string) => { setRequiredMeta((prev) => ({ ...prev, [key]: value })); }; return ( - {Object.entries(requiredMeta).map(([key, value]) => { - return ( - - ); - })} + + Required Meta Fields + + {Object.entries(requiredMeta).map(([key, value]) => ( + + ))} ); }; diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx index bb7c32ca1..ab6726ce8 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx @@ -1,5 +1,12 @@ import React, { useState, useEffect, useCallback } from 'react'; -import { Flex, FormLabel, Input, Button, IconButton } from '@chakra-ui/react'; +import { + Flex, + FormLabel, + Input, + Button, + IconButton, + Text, +} from '@chakra-ui/react'; import { FiX } from 'react-icons/fi'; import { IconPreview } from './IconPreview'; @@ -20,33 +27,15 @@ export const SitesInput: React.FC = ({ value, onChange }) => { const [imageValidities, setImageValidities] = useState([]); const [localImageUrls, setLocalImageUrls] = useState<(string | null)[]>([]); - useEffect(() => { - try { - const parsedSites = JSON.parse(value); - setSites(Array.isArray(parsedSites) ? parsedSites : []); - setImageValidities(new Array(parsedSites.length).fill(true)); - setLocalImageUrls(new Array(parsedSites.length).fill(null)); - } catch { - setSites([]); - setImageValidities([]); - setLocalImageUrls([]); - } - }, [value]); - const validateIcon = useCallback(async (url: string, index: number) => { try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const blob = await response.blob(); - if (!blob.type.startsWith('image/')) { - throw new Error('Invalid image format'); - } - const objectURL = URL.createObjectURL(blob); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const response = await fetch(url, { mode: 'no-cors' }); + // Since we're using no-cors, we can't check response.ok + // We'll assume it's valid if we get here without an error setLocalImageUrls((prev) => { const newUrls = [...prev]; - newUrls[index] = objectURL; + newUrls[index] = url; // Use the original URL instead of creating an object URL return newUrls; }); setImageValidities((prev) => { @@ -54,8 +43,9 @@ export const SitesInput: React.FC = ({ value, onChange }) => { newValidities[index] = true; return newValidities; }); - return objectURL; + return url; } catch (error) { + // Silently handle the error setLocalImageUrls((prev) => { const newUrls = [...prev]; newUrls[index] = null; @@ -66,13 +56,30 @@ export const SitesInput: React.FC = ({ value, onChange }) => { newValidities[index] = false; return newValidities; }); - if (error instanceof Error) { - console.error(`Icon validation failed: ${error.message}`); - } return null; } }, []); + useEffect(() => { + try { + const parsedSites = JSON.parse(value); + setSites(Array.isArray(parsedSites) ? parsedSites : []); + setImageValidities(new Array(parsedSites.length).fill(true)); + setLocalImageUrls(new Array(parsedSites.length).fill(null)); + + // Validate icons on initial load + parsedSites.forEach((site: Site, index: number) => { + if (site.imageUrl) { + validateIcon(site.imageUrl, index); + } + }); + } catch { + setSites([]); + setImageValidities([]); + setLocalImageUrls([]); + } + }, [value, validateIcon]); + const handleSiteChange = ( index: number, field: keyof Site, @@ -115,17 +122,11 @@ export const SitesInput: React.FC = ({ value, onChange }) => { return ( - - + + Sites - - From cbb0721aac88469f2fae414de1530abae37f18c3 Mon Sep 17 00:00:00 2001 From: "kody.low" Date: Mon, 14 Oct 2024 16:49:55 -0700 Subject: [PATCH 4/7] fix: new meta manager working --- .../tabs/meta/manager/CustomMetaFields.tsx | 5 +- .../tabs/meta/manager/IconPreview.tsx | 23 +-- .../dashboard/tabs/meta/manager/MetaInput.tsx | 5 +- .../tabs/meta/manager/SitesInput.tsx | 170 +++++------------- 4 files changed, 49 insertions(+), 154 deletions(-) diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/CustomMetaFields.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/CustomMetaFields.tsx index 710876328..2f9999810 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/CustomMetaFields.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/CustomMetaFields.tsx @@ -129,10 +129,7 @@ export const CustomMetaFields: React.FC = ({ {key === 'federation_icon_url' && ( - + )} = ({ - imageUrl, - isValid, -}) => ( +export const IconPreview: React.FC = ({ imageUrl }) => ( = ({ boxShadow='sm' flexShrink={0} > - {isValid && imageUrl ? ( + {imageUrl ? ( Icon?} /> ) : ( - + ? - + )} ); diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx index 42aaab4ba..bd1cde95d 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/MetaInput.tsx @@ -82,10 +82,7 @@ export const MetaInput: React.FC = ({ } /> {metaKey === 'federation_icon_url' && ( - + )} diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx index ab6726ce8..784af37c7 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/manager/SitesInput.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect } from 'react'; import { Flex, FormLabel, @@ -24,90 +24,25 @@ interface SitesInputProps { export const SitesInput: React.FC = ({ value, onChange }) => { const [sites, setSites] = useState([]); - const [imageValidities, setImageValidities] = useState([]); - const [localImageUrls, setLocalImageUrls] = useState<(string | null)[]>([]); - - const validateIcon = useCallback(async (url: string, index: number) => { - try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const response = await fetch(url, { mode: 'no-cors' }); - // Since we're using no-cors, we can't check response.ok - // We'll assume it's valid if we get here without an error - setLocalImageUrls((prev) => { - const newUrls = [...prev]; - newUrls[index] = url; // Use the original URL instead of creating an object URL - return newUrls; - }); - setImageValidities((prev) => { - const newValidities = [...prev]; - newValidities[index] = true; - return newValidities; - }); - return url; - } catch (error) { - // Silently handle the error - setLocalImageUrls((prev) => { - const newUrls = [...prev]; - newUrls[index] = null; - return newUrls; - }); - setImageValidities((prev) => { - const newValidities = [...prev]; - newValidities[index] = false; - return newValidities; - }); - return null; - } - }, []); useEffect(() => { try { const parsedSites = JSON.parse(value); setSites(Array.isArray(parsedSites) ? parsedSites : []); - setImageValidities(new Array(parsedSites.length).fill(true)); - setLocalImageUrls(new Array(parsedSites.length).fill(null)); - - // Validate icons on initial load - parsedSites.forEach((site: Site, index: number) => { - if (site.imageUrl) { - validateIcon(site.imageUrl, index); - } - }); } catch { setSites([]); - setImageValidities([]); - setLocalImageUrls([]); } - }, [value, validateIcon]); + }, [value]); const handleSiteChange = ( index: number, field: keyof Site, newValue: string ) => { - const newSites = [...sites]; - newSites[index] = { ...newSites[index], [field]: newValue }; + const newSites = sites.map((site, i) => + i === index ? { ...site, [field]: newValue } : site + ); onChange(JSON.stringify(newSites)); - - if (field === 'imageUrl') { - setImageValidities((prev) => { - const newValidities = [...prev]; - newValidities[index] = true; // Reset validity when user starts typing - return newValidities; - }); - setLocalImageUrls((prev) => { - const newUrls = [...prev]; - newUrls[index] = null; // Reset local URL when user starts typing - return newUrls; - }); - } - }; - - const handleImageUrlBlur = (index: number) => { - const imageUrl = sites[index].imageUrl; - if (imageUrl) { - validateIcon(imageUrl, index); - } }; const addSite = () => { @@ -130,67 +65,44 @@ export const SitesInput: React.FC = ({ value, onChange }) => { Add Site - {sites.length > 0 && ( - - {sites.map((site, index) => ( - - - {(Object.keys(site) as Array).map((field) => ( - - - {field.charAt(0).toUpperCase() + field.slice(1)} - - - handleSiteChange(index, field, e.target.value) - } - onBlur={() => { - if (field === 'imageUrl') { - handleImageUrlBlur(index); - } - }} - size='sm' - isInvalid={ - field === 'imageUrl' && !imageValidities[index] - } - /> - - ))} + {sites.map((site, index) => ( + + + {(Object.keys(site) as Array).map((field) => ( + + + {field.charAt(0).toUpperCase() + field.slice(1)} + + + handleSiteChange(index, field, e.target.value) + } + size='sm' + /> - - } - size='sm' - fontSize='xl' - color='red.500' - variant='ghost' - onClick={() => removeSite(index)} - minWidth='auto' - height='auto' - padding={1} - /> - - ))} + ))} + + + } + size='sm' + color='red.500' + variant='ghost' + onClick={() => removeSite(index)} + /> - )} + ))} ); }; From 05792d0f79a39cbabf46003c456457d727e6a64e Mon Sep 17 00:00:00 2001 From: "kody.low" Date: Mon, 14 Oct 2024 19:58:28 -0700 Subject: [PATCH 5/7] fix: abs path --- testing/test-pngs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/test-pngs.py b/testing/test-pngs.py index 768e99651..1da56494f 100644 --- a/testing/test-pngs.py +++ b/testing/test-pngs.py @@ -13,7 +13,8 @@ def do_GET(self): "Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Accept" ) self.end_headers() - with open(self.path[1:], "rb") as f: + file_path = os.path.abspath(self.path[1:]) + with open(file_path, "rb") as f: self.wfile.write(f.read()) else: self.send_error(404, "File not found") From ebe92ec4667c52f52da6056a022c5fe2ecd4c678 Mon Sep 17 00:00:00 2001 From: "kody.low" Date: Tue, 15 Oct 2024 14:11:08 -0700 Subject: [PATCH 6/7] fix: flip back to meta tab on approve --- .../components/dashboard/tabs/FederationTabsCard.tsx | 1 + .../components/dashboard/tabs/meta/proposals/ProposedMetas.tsx | 3 +++ 2 files changed, 4 insertions(+) diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/FederationTabsCard.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/FederationTabsCard.tsx index 47748f788..be58788d7 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/FederationTabsCard.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/FederationTabsCard.tsx @@ -251,6 +251,7 @@ export const FederationTabsCard: React.FC = ({ consensusMeta={consensusMeta} metaSubmissions={metaSubmissions} hasVoted={hasVoted} + setActiveTab={setActiveTab} /> diff --git a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/proposals/ProposedMetas.tsx b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/proposals/ProposedMetas.tsx index eeb1c360f..336e16740 100644 --- a/apps/router/src/guardian-ui/components/dashboard/tabs/meta/proposals/ProposedMetas.tsx +++ b/apps/router/src/guardian-ui/components/dashboard/tabs/meta/proposals/ProposedMetas.tsx @@ -54,6 +54,7 @@ interface ProposedMetasProps { consensusMeta?: ParsedConsensusMeta; metaSubmissions: MetaSubmissionMap; hasVoted: boolean; + setActiveTab: (tab: number) => void; } export const ProposedMetas = React.memo(function ProposedMetas({ @@ -62,6 +63,7 @@ export const ProposedMetas = React.memo(function ProposedMetas({ metaModuleId, consensusMeta, metaSubmissions, + setActiveTab, }: ProposedMetasProps): JSX.Element { const { t } = useTranslation(); const api = useGuardianAdminApi(); @@ -180,6 +182,7 @@ export const ProposedMetas = React.memo(function ProposedMetas({ const confirmApproval = async () => { if (selectedMeta) { await handleApprove(selectedMeta); + setActiveTab(0); onClose(); } }; From e3793039a33f9f9419901c86283d84d620519085 Mon Sep 17 00:00:00 2001 From: "kody.low" Date: Wed, 16 Oct 2024 16:30:37 -0700 Subject: [PATCH 7/7] feat: solo deployment redirects --- apps/router/src/components/Header.tsx | 8 ++- apps/router/src/components/Logo.tsx | 84 +++++++++++++++------------ apps/router/src/index.tsx | 46 +++++++++++++-- 3 files changed, 96 insertions(+), 42 deletions(-) diff --git a/apps/router/src/components/Header.tsx b/apps/router/src/components/Header.tsx index 4f1ff4b00..bf939fc1a 100644 --- a/apps/router/src/components/Header.tsx +++ b/apps/router/src/components/Header.tsx @@ -17,14 +17,18 @@ export const Header = React.memo(function Header() { const hasServices = Object.keys(guardians).length > 0 || Object.keys(gateways).length > 0; + const isSoloDeploy = !!( + process.env.REACT_APP_FM_CONFIG_API || process.env.REACT_APP_FM_GATEWAY_API + ); + return ( - - {hasServices && ( + + {hasServices && !isSoloDeploy && ( {activeService && activeServiceInfo && ( diff --git a/apps/router/src/components/Logo.tsx b/apps/router/src/components/Logo.tsx index 00aa66986..4c5acc02f 100644 --- a/apps/router/src/components/Logo.tsx +++ b/apps/router/src/components/Logo.tsx @@ -1,42 +1,54 @@ import React from 'react'; import { Flex } from '@chakra-ui/react'; -export const Logo = () => ( +interface LogoProps { + isSoloDeploy: boolean; +} + +export const Logo = ({ isSoloDeploy }: LogoProps) => ( - - - - - - - - - - - - + {isSoloDeploy ? ( + + ) : ( + + + + )} ); + +const LogoSVG = () => ( + + + + + + + + + + +); diff --git a/apps/router/src/index.tsx b/apps/router/src/index.tsx index 0cf2e22a6..a7d4b5064 100644 --- a/apps/router/src/index.tsx +++ b/apps/router/src/index.tsx @@ -1,6 +1,11 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import ReactDOM from 'react-dom/client'; -import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; +import { + BrowserRouter as Router, + Route, + Routes, + Navigate, +} from 'react-router-dom'; import { Guardian } from './guardian-ui/Guardian'; import { Gateway } from './gateway-ui/Gateway'; import { Fonts, SharedChakraProvider, theme } from '@fedimint/ui'; @@ -8,7 +13,7 @@ import spaceGroteskTtf from '@fedimint/ui/assets/fonts/SpaceGrotesk-Variable.ttf import interTtf from '@fedimint/ui/assets/fonts/Inter-Variable.ttf'; import { ColorModeScript } from '@chakra-ui/react'; -import { i18nProvider } from '@fedimint/utils'; +import { i18nProvider, sha256Hash } from '@fedimint/utils'; import { languages } from './languages'; import { AppContextProvider } from './context/AppContext'; import { HomePage } from './home/HomePage'; @@ -19,11 +24,44 @@ import { Wrapper } from './components/Wrapper'; i18nProvider(languages); const App = () => { + const [redirectPath, setRedirectPath] = useState(null); + + useEffect(() => { + const calculateRedirectPath = async () => { + if (process.env.REACT_APP_FM_CONFIG_API) { + try { + const hash = await sha256Hash(process.env.REACT_APP_FM_CONFIG_API); + setRedirectPath(`/guardian/${hash}`); + } catch (e) { + console.error(e); + } + } else if (process.env.REACT_APP_FM_GATEWAY_API) { + try { + const hash = await sha256Hash(process.env.REACT_APP_FM_GATEWAY_API); + setRedirectPath(`/gateway/${hash}`); + } catch (e) { + console.error(e); + } + } + }; + + calculateRedirectPath(); + }, []); + return ( - } /> + + ) : ( + + ) + } + />