Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2024-10-24 - Persisting Error State in Async Buttons

**Learning:** Async buttons that handle errors often fail to reset their error state on subsequent attempts. This leads to a confusing UX where a successful retry still displays the error icon, making the user believe the action failed again.
**Action:** Always ensure that error flags (e.g., `hasError`) are reset at the _start_ of the async operation, not just set in the `catch` block.
6 changes: 3 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: "weekly"
interval: 'weekly'
10 changes: 10 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Bolt's Journal

## 2024-05-22 - Async Race Conditions

**Learning:** Asynchronous typeahead searches must implement a request ID mechanism. Without it, stale responses can overwrite newer ones, leading to correct search terms displaying incorrect results.
**Action:** Always use a request ID or cancellation token pattern when implementing async search/filter operations.

## 2024-10-24 - [Svelte Search Optimization]
**Learning:** Using `on:keyup` for search inputs misses paste/drag events and triggers on navigation keys. Reactive statements `$: if (val !== null) debounce()` are superior for covering all input methods and reducing unnecessary calls.
**Action:** Prefer reactive statements over `on:keyup` for input-driven side effects.
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-10-24 - Accessible Icon Props and Loading Button State
**Learning:** Svelte wrapper components (like `Icon.svelte`) must spread `$$restProps` to allow passing accessibility attributes (e.g., `aria-label`) from parent components. Without this, icons remain inaccessible to screen readers. Also, persistent "Success" states on buttons can be confusing; auto-resetting them after a timeout improves clarity.
**Action:** Always include `{...$$restProps}` in wrapper components and implement auto-reset logic for temporary success states in interactive elements.
8 changes: 6 additions & 2 deletions src/components/DomainPayment.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@
<h4>{domainName}</h4>

<div class="years">
<IconButton on:click={() => addYears(-1)} disabled={years === 1 || feesApproved} aria-label="remove-year">
<IconButton
on:click={() => addYears(-1)}
disabled={years === 1 || feesApproved}
aria-label="remove-year"
>
<Icon icon="remove" />
</IconButton>
<span>{years} {yearsLabel}</span>
Expand All @@ -111,7 +115,7 @@
</IconButton>
</div>

<div class="coin">
<div class="coin">
<p class="title text-center">Payment token</p>
<div class="row centered">
<Select bind:value={$selectedCoin} label="Select Token" variant="outlined">
Expand Down
2 changes: 1 addition & 1 deletion src/components/Icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
export { key as icon, align, width, height, color };
</script>

<Icon class={alignClass} {icon} {height} {width} {color} />
<Icon class={alignClass} {icon} {height} {width} {color} {...$$restProps} />

<style>
:global(.align-icon-right) {
Expand Down
31 changes: 26 additions & 5 deletions src/components/LoadingButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import Button, { Label } from '@smui/button';
import Icon from 'src/components/Icon.svelte';
import CircularProgress from '@smui/circular-progress';
import { onDestroy } from 'svelte';

let className = '';
export { className as class };
export let onClick: () => Promise<void>;
export let onError: (error: any) => Promise<void> = async (error) => {
export let onError: (error: unknown) => Promise<void> = async (error) => {
let message;
if (error && error instanceof Error) message = error.message;
else message = 'Something went wrong';
Expand All @@ -24,11 +25,18 @@

let loading: boolean | undefined;
let hasError = false;
let resetTimeout: ReturnType<typeof setTimeout>;

onDestroy(() => {
clearTimeout(resetTimeout);
});

async function handleClick() {
if (loading) return;

hasError = false;
loading = true;
hasError = false;

try {
await onClick();
Expand All @@ -38,19 +46,32 @@
}

loading = false;

if (!hasError) {
clearTimeout(resetTimeout);
resetTimeout = setTimeout(() => {
loading = undefined;
}, 3000);
}
}
</script>

<Button class={className} disabled={isDisabled} on:click={handleClick} {variant}>
<Button
class={className}
disabled={isDisabled}
on:click={handleClick}
{variant}
aria-busy={loading}
>
<Label><slot /></Label>
{#if loading}
<div class="loading">
<div class="loading" role="status" aria-label="Loading">
<CircularProgress style="height: 20px; width: 20px;" indeterminate />
</div>
{:else if hasError}
<Icon icon="error" align="right" />
<Icon icon="error" align="right" aria-label="Error" />
{:else if loading === false}
<Icon icon="done" align="right"/>
<Icon icon="done" align="right" aria-label="Success" />
{/if}
</Button>

Expand Down
14 changes: 11 additions & 3 deletions src/components/Record.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
$: errors = invalid ? validator.getErrors() : [];
$: disabled = !edit;
$: validator = getValidator(klass);
$: maxLength = 'maxLength' in validator.rules ? validator.rules['maxLength'] as number : 64
$: maxLength = 'maxLength' in validator.rules ? (validator.rules['maxLength'] as number) : 64;

let edit = false;

Expand Down Expand Up @@ -99,10 +99,18 @@
</div>
{:else if editMode}
<div class="actions">
<IconButton on:click={() => toggleEdit()} disabled={!$walletConnected} aria-label="edit-record">
<IconButton
on:click={() => toggleEdit()}
disabled={!$walletConnected}
aria-label="edit-record"
>
<Icon icon="edit" />
</IconButton>
<IconButton on:click={() => (dialogOpen = true)} disabled={!$walletConnected} aria-label="delete-record">
<IconButton
on:click={() => (dialogOpen = true)}
disabled={!$walletConnected}
aria-label="delete-record"
>
<Icon icon="delete" />
</IconButton>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ export const profileRecords = [
RecordClassEnum.Price
].map((v) => RecordClassEnum[v]);

export const getValidator = (klass: string) => getRecordValidator(getRecordClassFrom(klass))
export const getValidator = (klass: string) => getRecordValidator(getRecordClassFrom(klass));
1 change: 0 additions & 1 deletion src/lib/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,4 @@ export const getStats = async (): Promise<DomainStats> => {
};
};


export const apiError = (message: string, status = 400) => json({ error: message }, { status });
8 changes: 4 additions & 4 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export interface DomainPaymentParams {
}

export interface DomainFeesResponse {
feesLabel: number
fees: string
symbol: string
address: string
feesLabel: number;
fees: string;
symbol: string;
address: string;
}
9 changes: 4 additions & 5 deletions src/lib/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ export const explorerAddressUrl = (address: string) => {
if (address.startsWith('00'))
// Account
url += `/accounts/${address}/assets`;
else
// Contract
url += `/contracts/${address}`;
// Contract
else url += `/contracts/${address}`;

return url
}
return url;
};

export const bridgeUrl = `${config.browserUrl}/bridge`;

Expand Down
1 change: 0 additions & 1 deletion src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -201,5 +201,4 @@
align-items: center;
height: 100%;
}

</style>
22 changes: 17 additions & 5 deletions src/routes/DomainSearch.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,23 @@
let domainName: string = '';
let nameSearched: string = '';
let isLoading: boolean = false;
let debounceTimer: NodeJS.Timeout;
let debounceTimer: ReturnType<typeof setTimeout>;
let requestId = 0;

$: errors = invalid ? validator.getErrors() : [];
$: invalid = domainName !== '' && !validator.validate(domainName, { raiseError: false });
$: nameSearchedLabel = nameSearched ? `${nameSearched}.${$metaNamesSdk.config.tld}` : null;
// Use a reactive statement instead of on:keyup to handle all input methods (paste, drag/drop)
// and prevent unnecessary searches on navigation keys (arrows, shift).
$: if (domainName !== null) debounce();

function debounce() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => await search(), 400);
}

async function search(submit = false) {
clearTimeout(debounceTimer);
if (invalid) return;

if (domainName === '') return;
Expand All @@ -36,12 +41,16 @@
return goto(url);
}

const currentRequestId = ++requestId;
nameSearched = domainName.toLocaleLowerCase();
isLoading = true;

domain = await $metaNamesSdk.domainRepository.find(domainName);
const result = await $metaNamesSdk.domainRepository.find(domainName);

isLoading = false;
if (currentRequestId === requestId) {
domain = result;
isLoading = false;
}
}

async function submit() {
Expand All @@ -55,7 +64,6 @@
class="domain-input"
variant="outlined"
bind:value={domainName}
on:keyup={() => debounce()}
bind:invalid
label="Domain name"
withTrailingIcon
Expand All @@ -81,7 +89,11 @@
<div class="card-content">
<span>{nameSearchedLabel}</span>

<CircularProgress style="height: 32px; width: 32px;" indeterminate />
<CircularProgress
style="height: 32px; width: 32px;"
indeterminate
aria-label="Loading domain search results"
/>
</div>
</CardContent>
</Card>
Expand Down
19 changes: 11 additions & 8 deletions src/routes/api/register/[name]/fees/[coin]/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import { json } from '@sveltejs/kit';
import type { DomainFeesResponse } from 'src/lib/types';

export async function GET({ params: { name, coin } }) {
return handleError(async () => {
const validCoins = metaNamesSdk.config.byoc.map(byoc => byoc.symbol.toString())
if (!(validCoins.includes(coin))) return apiError('Invalid coin');
return handleError(async () => {
const validCoins = metaNamesSdk.config.byoc.map((byoc) => byoc.symbol.toString());
if (!validCoins.includes(coin)) return apiError('Invalid coin');

const normalizedDomain = metaNamesSdk.domainRepository.domainValidator.normalize(name)
const domainFees = await metaNamesSdk.domainRepository.calculateMintFees(normalizedDomain, coin as BYOCSymbol);
const fees = { ...domainFees, fees: domainFees.fees.toString() }
const normalizedDomain = metaNamesSdk.domainRepository.domainValidator.normalize(name);
const domainFees = await metaNamesSdk.domainRepository.calculateMintFees(
normalizedDomain,
coin as BYOCSymbol
);
const fees = { ...domainFees, fees: domainFees.fees.toString() };

return json(fees);
});
return json(fees);
});
}
1 change: 0 additions & 1 deletion src/routes/profile/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
</div>

<style lang="scss">

.domains {
margin-top: 1.5rem;
margin-bottom: 0;
Expand Down
2 changes: 1 addition & 1 deletion src/styles/mixins.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use "sass:string";
@use 'sass:string';

$amounts: (0, 1, 2, 3, 4);
$types: (top, bottom, left, right);
Expand Down
2 changes: 1 addition & 1 deletion src/theme/dark/_smui-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ $text-color: #d5ccff;
@use '@material/theme/color-palette';
@use '@material/theme/index' as theme with (
$primary: #6849fe,
$secondary: #D0C7FF,
$secondary: #d0c7ff,
$surface: color.adjust(color-palette.$grey-900, $blue: +4),
$background: #363535,
$error: color-palette.$red-700
Expand Down
20 changes: 10 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2743,12 +2743,12 @@ available-typed-arrays@^1.0.7:
possible-typed-array-names "^1.0.0"

axios@^1.6.2:
version "1.13.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.4.tgz#15d109a4817fb82f73aea910d41a2c85606076bc"
integrity sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==
version "1.13.5"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.5.tgz#5e464688fa127e11a660a2c49441c009f6567a43"
integrity sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.4"
follow-redirects "^1.15.11"
form-data "^4.0.5"
proxy-from-env "^1.1.0"

axobject-query@^4.0.0:
Expand Down Expand Up @@ -3794,10 +3794,10 @@ focus-trap@^7.5.2:
dependencies:
tabbable "^6.2.0"

follow-redirects@^1.15.6:
version "1.15.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
follow-redirects@^1.15.11:
version "1.15.11"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340"
integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==

for-each@^0.3.3:
version "0.3.3"
Expand All @@ -3806,7 +3806,7 @@ for-each@^0.3.3:
dependencies:
is-callable "^1.1.3"

form-data@^4.0.4:
form-data@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053"
integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==
Expand Down