From b255d658b7037bd7fb83d556528031f960e68f0d Mon Sep 17 00:00:00 2001 From: Luc Date: Wed, 22 Nov 2023 12:30:52 +0000 Subject: [PATCH] Introduce Home Page --- web/src/App.tsx | 299 +------------------------ web/src/Home.tsx | 41 ++++ web/src/Layout.tsx | 43 ++++ web/src/Profile.tsx | 267 ++++++++++++++++++++++ web/src/field/Field.tsx | 2 +- web/src/global.css | 12 + web/src/migration/SetResolverModal.tsx | 3 +- 7 files changed, 373 insertions(+), 294 deletions(-) create mode 100644 web/src/Home.tsx create mode 100644 web/src/Layout.tsx create mode 100644 web/src/Profile.tsx diff --git a/web/src/App.tsx b/web/src/App.tsx index 022f5cb..66a3492 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,86 +1,5 @@ -import { MenuSVG } from '@ensdomains/thorin'; -import { FC, PropsWithChildren, useState } from 'react'; -import useSWR from 'swr'; -import { namehash } from 'viem'; -import { useAccount, useContractRead, useEnsResolver } from 'wagmi'; - -import { Field } from './field/Field'; -import { Header } from './header/Header'; -import { GoGassless } from './migration/GoGassless'; -import { UserProfile } from './UserProfile'; - -export const GATEWAY_VIEW = 'https://rs.myeth.id/view/'; -export const GATEWAY_UPDATE = 'https://rs.myeth.id/update'; -export const ENSTATE_URL = 'https://worker.enstate.rs/n/'; -export const ENS_MAINNET_REGISTRY = - '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; - -type ProfileResponse = { - name: string; - records: Record; - addresses: Record; -}; - -type EnstateResponse = { - name: string; - avatar: string; - records: Record; - chains: Record; -}; - -const getEnstate = async (name: string) => { - try { - const request = await fetch(ENSTATE_URL + name); - - const data: EnstateResponse = await request.json(); - - return data; - } catch { - console.log('Failed to load from ccip gateway'); - } -}; - -export const getProfile = async (name: string) => { - try { - const request = await fetch(GATEWAY_VIEW + name); - - const data: ProfileResponse = await request.json(); - - return data; - } catch { - console.log('Failed to load from ccip gateway'); - } - - const data = await getEnstate(name); - - if (!data) return; - - data.records['avatar'] = data.avatar; - data.chains['60'] = data.chains['eth']; - - return { ...data, records: data.records, addresses: data.chains }; -}; - -type ProfileDataPost = { - name: string; - records: Record; - addresses: Record; - auth: 'yes'; -}; - -const postUpdateProfile = async (name: string, data: ProfileDataPost) => { - const request = await fetch(GATEWAY_UPDATE, { - method: 'POST', - body: JSON.stringify(data), - headers: { - 'Content-Type': 'application/json', - }, - }); - - const response = await request.text(); - - console.log({ response }); -}; +import { Home } from './Home'; +import { Profile } from './Profile'; export const DEVELOPER_MODE = false; @@ -89,216 +8,12 @@ export const App = () => { const path = window.location.pathname; const name = path.replace('/', ''); + if (name.length === 0) { + return ; + } + if (!/^([\dA-Za-z-]([\d.A-Za-z-])+\.)+[\dA-Za-z-]+$/.test(name)) return
Invalid ENS name
; - const { data } = useSWR(name, getProfile); - const { address } = useAccount(); - - const { data: ensResolver, isSuccess: isEnsResolverFinished } = - useEnsResolver({ name }); - const { data: ownerData } = useContractRead({ - address: ENS_MAINNET_REGISTRY, - abi: [ - { - type: 'function', - name: 'owner', - stateMutability: 'view', - inputs: [ - { - type: 'bytes32', - name: 'node', - }, - ], - outputs: [ - { - type: 'address', - name: 'owner', - }, - ], - }, - ], - functionName: 'owner', - args: [namehash(name)], - }); - - const isUsingOffchainResolver = - ensResolver?.toLowerCase() === - '0xdCcB68ac05BB2Ee83F0873DCd0BF5F57E2968344'.toLowerCase(); - const canChangeResolver = - ownerData?.toString().toLowerCase() === address?.toLowerCase(); - const isOwner = data?.addresses[60] == address; - const editable = address && data && isUsingOffchainResolver && isOwner; - - const shouldSuggestGassless = - isEnsResolverFinished && - !isUsingOffchainResolver && - isOwner && - canChangeResolver; - - const mutateProfile = () => { - postUpdateProfile(name, { - name: name, - records: { - ...data.records, - 'com.discord': 'lucemans', - }, - addresses: data.addresses, - auth: 'yes', - }); - }; - - const [headerExpanded, setHeaderExpanded] = useState(false); - - if (!data) return
Loading...
; - - return ( - <> - {headerExpanded && ( -
{ - setHeaderExpanded(false); - }} - name={name} - /> - )} -
-
-
- mark - -
-
- -
-
-
- - {name.split('.')[0]} - - - .{name.split('.').slice(1).join('.')} - -
-
-
-
-
-
- {data.records['avatar'] && ( - avatar - )} -
-
- {editable && ( -
- pencil -
- )} -
-
-
- - - - - - - {DEVELOPER_MODE && ( - - )} - {DEVELOPER_MODE && ownerData && ( - - )} -
- {editable && ( - - - - )} - {shouldSuggestGassless && ( - - - - )} -
-
- - ); + return ; }; - -export const FloatingButton: FC> = ({ children }) => ( -
-
-
- {children} -
-
-
-
-); diff --git a/web/src/Home.tsx b/web/src/Home.tsx new file mode 100644 index 0000000..4638f2d --- /dev/null +++ b/web/src/Home.tsx @@ -0,0 +1,41 @@ +import { clsx } from 'clsx'; +import { useState } from 'react'; +import { FiSearch } from 'react-icons/fi'; + +import { Layout } from './Layout'; + +export const Home = () => { + const [name, setName] = useState(''); + + return ( + +
+
Search Name
+
+
+ +
+ { + setName(event.target.value); + }} + /> +
+ 0 ? 'btn-primary' : 'btn-disabled' + )} + aria-disabled={name.length === 0} + > + Go + +
+
+ ); +}; diff --git a/web/src/Layout.tsx b/web/src/Layout.tsx new file mode 100644 index 0000000..30629ba --- /dev/null +++ b/web/src/Layout.tsx @@ -0,0 +1,43 @@ +/* eslint-disable no-undef */ +import { MenuSVG } from '@ensdomains/thorin'; +import { FC, PropsWithChildren, useState } from 'react'; + +import { Header } from './header/Header'; +import { UserProfile } from './UserProfile'; + +export const Layout: FC> = ({ children }) => { + const [headerExpanded, setHeaderExpanded] = useState(false); + + return ( + <> + {headerExpanded && ( +
{ + setHeaderExpanded(false); + }} + name={location?.href?.replace('/', '')} + /> + )} +
+
+
+ + mark + + +
+
+ +
+
+ {children} +
+ + ); +}; diff --git a/web/src/Profile.tsx b/web/src/Profile.tsx new file mode 100644 index 0000000..9598829 --- /dev/null +++ b/web/src/Profile.tsx @@ -0,0 +1,267 @@ +import { FC, PropsWithChildren } from 'react'; +import useSWR from 'swr'; +import { namehash } from 'viem'; +import { useAccount, useContractRead, useEnsResolver } from 'wagmi'; + +import { DEVELOPER_MODE } from './App'; +import { Field } from './field/Field'; +import { Layout } from './Layout'; +import { GoGassless } from './migration/GoGassless'; + +export const GATEWAY_VIEW = 'https://rs.myeth.id/view/'; +export const GATEWAY_UPDATE = 'https://rs.myeth.id/update'; +export const ENSTATE_URL = 'https://worker.enstate.rs/n/'; +export const ENS_MAINNET_REGISTRY = + '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; + +type ProfileResponse = { + name: string; + records: Record; + addresses: Record; +}; + +type EnstateResponse = { + name: string; + avatar: string; + records: Record; + chains: Record; +}; + +const getEnstate = async (name: string) => { + try { + const request = await fetch(ENSTATE_URL + name); + + const data: EnstateResponse = await request.json(); + + return data; + } catch { + console.log('Failed to load from ccip gateway'); + } +}; + +export const getProfile = async (name: string) => { + try { + const request = await fetch(GATEWAY_VIEW + name); + + const data: ProfileResponse = await request.json(); + + return data; + } catch { + console.log('Failed to load from ccip gateway'); + } + + const data = await getEnstate(name); + + if (!data) return; + + data.records['avatar'] = data.avatar; + data.chains['60'] = data.chains['eth']; + + return { ...data, records: data.records, addresses: data.chains }; +}; + +type ProfileDataPost = { + name: string; + records: Record; + addresses: Record; + auth: 'yes'; +}; + +const postUpdateProfile = async (name: string, data: ProfileDataPost) => { + const request = await fetch(GATEWAY_UPDATE, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json', + }, + }); + + const response = await request.text(); + + console.log({ response }); +}; + +export const Profile: FC<{ name: string }> = ({ name }) => { + const { data } = useSWR(name, getProfile); + const { address } = useAccount(); + + const { data: ensResolver, isSuccess: isEnsResolverFinished } = + useEnsResolver({ name }); + const { data: ownerData } = useContractRead({ + address: ENS_MAINNET_REGISTRY, + abi: [ + { + type: 'function', + name: 'owner', + stateMutability: 'view', + inputs: [ + { + type: 'bytes32', + name: 'node', + }, + ], + outputs: [ + { + type: 'address', + name: 'owner', + }, + ], + }, + ], + functionName: 'owner', + args: [namehash(name)], + }); + + const isUsingOffchainResolver = + ensResolver?.toLowerCase() === + '0xdCcB68ac05BB2Ee83F0873DCd0BF5F57E2968344'.toLowerCase(); + const canChangeResolver = + ownerData?.toString().toLowerCase() === address?.toLowerCase(); + const isOwner = data?.addresses[60] == address; + const editable = address && data && isUsingOffchainResolver && isOwner; + + const shouldSuggestGassless = + isEnsResolverFinished && + !isUsingOffchainResolver && + isOwner && + canChangeResolver; + + const mutateProfile = () => { + postUpdateProfile(name, { + name: name, + records: { + ...data.records, + 'com.discord': 'lucemans', + }, + addresses: data.addresses, + auth: 'yes', + }); + }; + + if (!data) return
Loading...
; + + return ( + +
+ + {name.split('.')[0]} + + + .{name.split('.').slice(1).join('.')} + +
+
+
+
+
+
+ {data.records['avatar'] && ( + avatar + )} +
+
+ {editable && ( +
+ pencil +
+ )} +
+
+
+ + + + + + + {DEVELOPER_MODE && ( + + )} + {DEVELOPER_MODE && ownerData && ( + + )} +
+ {editable && ( + + + + )} + {shouldSuggestGassless && ( + + + + )} +
+
+ ); +}; + +export const FloatingButton: FC> = ({ children }) => ( +
+
+
+ {children} +
+
+
+
+); diff --git a/web/src/field/Field.tsx b/web/src/field/Field.tsx index 303d604..64b6766 100644 --- a/web/src/field/Field.tsx +++ b/web/src/field/Field.tsx @@ -42,7 +42,7 @@ export const Field: FC<{ {field_icon}