Repository navigation
fix: wire AddressInput to core-ts detect/parse validation #345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,70 @@ | ||
| import React, { useState, useMemo } from 'react'; | ||
| import React, { useState, useMemo, useEffect } from 'react'; | ||
| import { TypeBadge, AddressType } from './components/TypeBadge'; | ||
| import { WarningList } from './components/WarningList'; | ||
| import type { ParseMessage } from './components/WarningList'; | ||
| import { MemoField } from './components/MemoField'; | ||
| import { detect, parse } from '../packages/core-ts'; | ||
|
|
||
| const DEBOUNCE_MS = 300; | ||
|
|
||
| export const AddressInput: React.FC = () => { | ||
| const [address, setAddress] = useState(''); | ||
| const [memo, setMemo] = useState(''); | ||
| const [debouncedAddress, setDebouncedAddress] = useState(''); | ||
|
|
||
| const type = useMemo<AddressType>(() => { | ||
| if (!address) return 'UNKNOWN'; | ||
| const firstChar = address.charAt(0).toUpperCase(); | ||
| if (firstChar === 'M') return 'M'; | ||
| if (firstChar === 'G') return 'G'; | ||
| if (firstChar === 'C') return 'C'; | ||
| return 'UNKNOWN'; | ||
| // Debounce input changes so validation does not block UI rendering. | ||
| useEffect(() => { | ||
| const handle = setTimeout(() => setDebouncedAddress(address), DEBOUNCE_MS); | ||
| return () => clearTimeout(handle); | ||
| }, [address]); | ||
|
|
||
| const type = useMemo<AddressType>(() => { | ||
| if (!debouncedAddress) return 'UNKNOWN'; | ||
| try { | ||
| const detected = detect(debouncedAddress); | ||
| if (detected === 'M' || detected === 'G') return detected as AddressType; | ||
| if (detected === 'C') return 'C'; | ||
| return 'UNKNOWN'; | ||
| } catch { | ||
| return 'UNKNOWN'; | ||
| } | ||
| }, [debouncedAddress]); | ||
|
|
||
| const parseResult = useMemo(() => { | ||
| if (!debouncedAddress) return null; | ||
| try { | ||
| return parse(debouncedAddress); | ||
| } catch (err) { | ||
| return { | ||
| valid: false, | ||
| warnings: [], | ||
| errors: [err instanceof Error ? err.message : 'Unable to parse address'], | ||
| }; | ||
| } | ||
| }, [debouncedAddress]); | ||
|
|
||
| const showMemo = type === 'G' || type === 'UNKNOWN'; | ||
|
|
||
| const warnings = useMemo(() => { | ||
| const list: string[] = []; | ||
|
|
||
| const messages = useMemo<ParseMessage[]>(() => { | ||
| const list: ParseMessage[] = []; | ||
| if (parseResult) { | ||
| for (const e of parseResult.errors || []) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Handle the actual
Also applies to: 54-54 🤖 Prompt for AI Agents |
||
| list.push({ severity: 'error', message: typeof e === 'string' ? e : e.message }); | ||
| } | ||
| for (const w of parseResult.warnings || []) { | ||
| list.push({ severity: 'warning', message: typeof w === 'string' ? w : w.message }); | ||
| } | ||
| } | ||
| if (type === 'C') { | ||
| list.push('Contract addresses cannot be used for standard payments.'); | ||
| list.push({ | ||
| severity: 'warning', | ||
| message: 'Contract addresses cannot be used for standard payments.', | ||
| }); | ||
| } | ||
| return list; | ||
| }, [type]); | ||
| }, [parseResult, type]); | ||
|
|
||
| const hasError = messages.some((m) => m.severity === 'error'); | ||
|
|
||
| return ( | ||
| <div style={{ maxWidth: '32rem', margin: '0 auto', fontFamily: 'sans-serif' }}> | ||
|
|
@@ -42,19 +82,19 @@ export const AddressInput: React.FC = () => { | |
| style={{ | ||
| width: '100%', | ||
| padding: `0.75rem 0.75rem 0.75rem ${type !== 'UNKNOWN' ? '3rem' : '0.75rem'}`, | ||
| border: '1px solid #cbd5e1', | ||
| border: hasError ? '1px solid #ef4444' : '1px solid #cbd5e1', | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Expose the validation error on the address input. When Based on learnings: visible input errors need programmatic invalidity and an associated error description. 🤖 Prompt for AI AgentsSource: Learnings |
||
| borderRadius: '0.5rem', | ||
| fontSize: '1rem', | ||
| boxSizing: 'border-box', | ||
| outline: 'none', | ||
| transition: 'padding 0.2s ease' | ||
| transition: 'padding 0.2s ease, border-color 0.2s ease', | ||
| }} | ||
| /> | ||
| </div> | ||
|
|
||
| <MemoField isVisible={showMemo && !!address} value={memo} onChange={setMemo} /> | ||
| <WarningList warnings={warnings} /> | ||
|
|
||
| <WarningList messages={messages} /> | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import React from 'react'; | ||
|
|
||
| export type ParseMessageSeverity = 'error' | 'warning'; | ||
|
|
||
| export interface ParseMessage { | ||
| severity: ParseMessageSeverity; | ||
| message: string; | ||
| } | ||
|
|
||
| export interface WarningListProps { | ||
| messages?: ParseMessage[]; | ||
| /** Backwards-compatible string warnings (rendered as warnings). */ | ||
| warnings?: string[]; | ||
| } | ||
|
|
||
| const styleBySeverity: Record<ParseMessageSeverity, React.CSSProperties> = { | ||
| error: { | ||
| background: '#fef2f2', | ||
| border: '1px solid #ef4444', | ||
| color: '#991b1b', | ||
| }, | ||
| warning: { | ||
| background: '#fffbeb', | ||
| border: '1px solid #f59e0b', | ||
| color: '#92400e', | ||
| }, | ||
| }; | ||
|
|
||
| export const WarningList: React.FC<WarningListProps> = ({ messages, warnings }) => { | ||
| const normalized: ParseMessage[] = [ | ||
| ...(messages ?? []), | ||
| ...(warnings ?? []).map((w) => ({ severity: 'warning' as const, message: w })), | ||
| ]; | ||
|
|
||
| if (normalized.length === 0) return null; | ||
|
|
||
| return ( | ||
| <ul | ||
| role="alert" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Use polite announcements for routine validation messages.
Based on learnings: dynamic status regions should not use assertive announcements for routine updates. 🤖 Prompt for AI AgentsSource: Learnings |
||
| style={{ | ||
| listStyle: 'none', | ||
| margin: '0.75rem 0 0 0', | ||
| padding: 0, | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| gap: '0.5rem', | ||
| }} | ||
| > | ||
| {normalized.map((msg, idx) => ( | ||
| <li | ||
| key={`${msg.severity}-${idx}`} | ||
| style={{ | ||
| padding: '0.5rem 0.75rem', | ||
| borderRadius: '0.375rem', | ||
| fontSize: '0.875rem', | ||
| lineHeight: 1.4, | ||
| ...styleBySeverity[msg.severity], | ||
| }} | ||
| > | ||
| {msg.message} | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| ); | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Import the WarningList implementation changed by this PR.
This type import and the component import on Line 3 target
src/components/WarningList.tsx. The newParseMessagetype andmessagesprop are defined insrc/WarningList.tsxinstead. TypeScript cannot findParseMessageat the imported path or passmessagesto the existing component. The validation messages also will not render through that component. Import both symbols from the new file, or update the existing component at the imported path. (github.com)🤖 Prompt for AI Agents