375 lines
14 KiB
Plaintext
375 lines
14 KiB
Plaintext
|
|
package components
|
||
|
|
|
||
|
|
// TokenOption represents a token choice in dropdowns
|
||
|
|
type TokenOption struct {
|
||
|
|
Symbol string
|
||
|
|
Name string
|
||
|
|
Balance string
|
||
|
|
Color string
|
||
|
|
Initials string
|
||
|
|
}
|
||
|
|
|
||
|
|
// DefaultTokenOptions returns the standard token options for drawers
|
||
|
|
func DefaultTokenOptions() []TokenOption {
|
||
|
|
return []TokenOption{
|
||
|
|
{Symbol: "SNR", Name: "Sonr", Balance: "8,432.50", Color: "linear-gradient(135deg, #17c2ff, #0090ff)", Initials: "S"},
|
||
|
|
{Symbol: "ETH", Name: "Ethereum", Balance: "2.847", Color: "#627eea", Initials: "E"},
|
||
|
|
{Symbol: "USDC", Name: "USD Coin", Balance: "1,250.00", Color: "#2775ca", Initials: "U"},
|
||
|
|
{Symbol: "AVAX", Name: "Avalanche", Balance: "24.83", Color: "#e84142", Initials: "A"},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ReceiveDrawer component for receiving tokens
|
||
|
|
templ ReceiveDrawer(tokens []TokenOption, walletAddress string) {
|
||
|
|
<wa-drawer label="Receive" placement="end" class="drawer-receive" style="--size: 400px;" id="receive-drawer">
|
||
|
|
<div class="drawer-form">
|
||
|
|
<div class="drawer-form-content">
|
||
|
|
<div class="wa-stack wa-gap-l">
|
||
|
|
<wa-select label="Select Token" value="snr">
|
||
|
|
for _, token := range tokens {
|
||
|
|
<wa-option value={ token.Symbol }>
|
||
|
|
<wa-avatar slot="prefix" initials={ token.Initials } style={ "--size: 24px; background: " + token.Color + ";" }></wa-avatar>
|
||
|
|
{ token.Symbol } - { token.Name }
|
||
|
|
</wa-option>
|
||
|
|
}
|
||
|
|
</wa-select>
|
||
|
|
<div class="qr-container">
|
||
|
|
<wa-qr-code value={ walletAddress } size="180" radius="0.5"></wa-qr-code>
|
||
|
|
<span class="wa-caption-s" style="margin-top: var(--wa-space-m); color: var(--wa-color-neutral-500);">Scan to receive tokens</span>
|
||
|
|
</div>
|
||
|
|
<div class="wa-stack wa-gap-xs">
|
||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Your Wallet Address</span>
|
||
|
|
<div class="address-display">
|
||
|
|
<span style="flex: 1;">{ walletAddress }</span>
|
||
|
|
<wa-copy-button value={ walletAddress }></wa-copy-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<wa-callout variant="neutral">
|
||
|
|
<wa-icon slot="icon" name="circle-info"></wa-icon>
|
||
|
|
Only send <strong>SNR</strong> tokens to this address. Sending other assets may result in permanent loss.
|
||
|
|
</wa-callout>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="wa-cluster wa-gap-s" style="justify-content: flex-end;">
|
||
|
|
<wa-button variant="neutral" appearance="outlined" onclick="document.getElementById('receive-drawer').open = false">Close</wa-button>
|
||
|
|
<wa-button variant="brand">
|
||
|
|
<wa-icon slot="start" name="share-nodes"></wa-icon>
|
||
|
|
Share Address
|
||
|
|
</wa-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</wa-drawer>
|
||
|
|
@drawerStyles()
|
||
|
|
}
|
||
|
|
|
||
|
|
// SendDrawer component for sending tokens
|
||
|
|
templ SendDrawer(tokens []TokenOption) {
|
||
|
|
<wa-drawer label="Send" placement="end" class="drawer-send" style="--size: 420px;" id="send-drawer">
|
||
|
|
<div class="drawer-form">
|
||
|
|
<div class="drawer-form-content">
|
||
|
|
<div class="wa-stack wa-gap-l">
|
||
|
|
<wa-select label="From Token" value="snr" id="send-token-select">
|
||
|
|
for _, token := range tokens {
|
||
|
|
<wa-option value={ token.Symbol }>
|
||
|
|
<wa-avatar slot="prefix" initials={ token.Initials } style={ "--size: 24px; background: " + token.Color + ";" }></wa-avatar>
|
||
|
|
{ token.Symbol } - { token.Balance } available
|
||
|
|
</wa-option>
|
||
|
|
}
|
||
|
|
</wa-select>
|
||
|
|
<wa-input label="Recipient Address" placeholder="sonr1... or ENS name" clearable>
|
||
|
|
<wa-icon slot="prefix" name="wallet"></wa-icon>
|
||
|
|
<wa-tooltip slot="suffix" content="Paste from clipboard">
|
||
|
|
<wa-icon-button name="paste" label="Paste"></wa-icon-button>
|
||
|
|
</wa-tooltip>
|
||
|
|
</wa-input>
|
||
|
|
<div class="wa-stack wa-gap-xs">
|
||
|
|
<wa-input label="Amount" type="number" placeholder="0.00" id="send-amount">
|
||
|
|
<span slot="suffix" style="color: var(--wa-color-neutral-500);">SNR</span>
|
||
|
|
</wa-input>
|
||
|
|
<div class="wa-flank">
|
||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">≈ $0.00 USD</span>
|
||
|
|
<wa-button size="small" appearance="plain" style="font-size: var(--wa-font-size-xs);">MAX</wa-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<wa-textarea label="Memo (Optional)" placeholder="Add a note to this transaction" rows="2"></wa-textarea>
|
||
|
|
<wa-details summary="Gas Settings">
|
||
|
|
<div class="gas-settings">
|
||
|
|
<wa-radio-group label="Transaction Speed" value="standard" orientation="horizontal">
|
||
|
|
<wa-radio appearance="button" value="slow">Slow</wa-radio>
|
||
|
|
<wa-radio appearance="button" value="standard">Standard</wa-radio>
|
||
|
|
<wa-radio appearance="button" value="fast">Fast</wa-radio>
|
||
|
|
</wa-radio-group>
|
||
|
|
<div class="preview-row" style="margin-top: var(--wa-space-m);">
|
||
|
|
<span class="label">Estimated Gas</span>
|
||
|
|
<span>~0.001 SNR ($0.05)</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</wa-details>
|
||
|
|
<wa-divider></wa-divider>
|
||
|
|
<div class="wa-stack wa-gap-xs">
|
||
|
|
<div class="preview-row">
|
||
|
|
<span class="label">Amount</span>
|
||
|
|
<span>0.00 SNR</span>
|
||
|
|
</div>
|
||
|
|
<div class="preview-row">
|
||
|
|
<span class="label">Network Fee</span>
|
||
|
|
<span>~0.001 SNR</span>
|
||
|
|
</div>
|
||
|
|
<div class="preview-row">
|
||
|
|
<span class="label" style="font-weight: 500;">Total</span>
|
||
|
|
<span style="font-weight: 500;">0.001 SNR</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="wa-cluster wa-gap-s" style="justify-content: flex-end;">
|
||
|
|
<wa-button variant="neutral" appearance="outlined" onclick="document.getElementById('send-drawer').open = false">Cancel</wa-button>
|
||
|
|
<wa-button variant="brand" id="send-confirm-btn">
|
||
|
|
<wa-icon slot="start" name="paper-plane"></wa-icon>
|
||
|
|
Review Send
|
||
|
|
</wa-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</wa-drawer>
|
||
|
|
@drawerStyles()
|
||
|
|
}
|
||
|
|
|
||
|
|
// SwapDrawer component for swapping tokens
|
||
|
|
templ SwapDrawer(tokens []TokenOption) {
|
||
|
|
<wa-drawer label="Swap Tokens" placement="end" class="drawer-swap" style="--size: 440px;" id="swap-drawer">
|
||
|
|
<div class="drawer-form">
|
||
|
|
<div class="drawer-form-content">
|
||
|
|
<div class="wa-stack wa-gap-m">
|
||
|
|
<!-- From Token Box -->
|
||
|
|
<div class="swap-token-box">
|
||
|
|
<div class="wa-flank" style="margin-bottom: var(--wa-space-s);">
|
||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">You Pay</span>
|
||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Balance: 2.847 ETH</span>
|
||
|
|
</div>
|
||
|
|
<div class="wa-flank wa-gap-m">
|
||
|
|
<wa-input type="number" placeholder="0.00" style="flex: 1; --wa-input-border-color: transparent; --wa-input-background-color: transparent;" id="swap-from-amount"></wa-input>
|
||
|
|
<wa-dropdown>
|
||
|
|
<wa-button slot="trigger" appearance="outlined" size="small" with-caret>
|
||
|
|
<wa-avatar slot="prefix" initials="E" style="--size: 20px; background: #627eea;"></wa-avatar>
|
||
|
|
ETH
|
||
|
|
</wa-button>
|
||
|
|
for _, token := range tokens {
|
||
|
|
<wa-dropdown-item>
|
||
|
|
<wa-avatar slot="icon" initials={ token.Initials } style={ "--size: 20px; background: " + token.Color + ";" }></wa-avatar>
|
||
|
|
{ token.Symbol }
|
||
|
|
</wa-dropdown-item>
|
||
|
|
}
|
||
|
|
</wa-dropdown>
|
||
|
|
</div>
|
||
|
|
<div class="wa-flank" style="margin-top: var(--wa-space-xs);">
|
||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">≈ $0.00</span>
|
||
|
|
<div class="wa-cluster wa-gap-xs">
|
||
|
|
<wa-button size="small" appearance="plain" style="font-size: var(--wa-font-size-xs);">25%</wa-button>
|
||
|
|
<wa-button size="small" appearance="plain" style="font-size: var(--wa-font-size-xs);">50%</wa-button>
|
||
|
|
<wa-button size="small" appearance="plain" style="font-size: var(--wa-font-size-xs);">MAX</wa-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<!-- Swap Direction Button -->
|
||
|
|
<div class="swap-arrow">
|
||
|
|
<wa-icon-button name="arrow-down" label="Swap direction" id="swap-direction-btn"></wa-icon-button>
|
||
|
|
</div>
|
||
|
|
<!-- To Token Box -->
|
||
|
|
<div class="swap-token-box">
|
||
|
|
<div class="wa-flank" style="margin-bottom: var(--wa-space-s);">
|
||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">You Receive</span>
|
||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Balance: 1,250.00 USDC</span>
|
||
|
|
</div>
|
||
|
|
<div class="wa-flank wa-gap-m">
|
||
|
|
<wa-input type="number" placeholder="0.00" style="flex: 1; --wa-input-border-color: transparent; --wa-input-background-color: transparent;" id="swap-to-amount" readonly></wa-input>
|
||
|
|
<wa-dropdown>
|
||
|
|
<wa-button slot="trigger" appearance="outlined" size="small" with-caret>
|
||
|
|
<wa-avatar slot="prefix" initials="U" style="--size: 20px; background: #2775ca;"></wa-avatar>
|
||
|
|
USDC
|
||
|
|
</wa-button>
|
||
|
|
for _, token := range tokens {
|
||
|
|
<wa-dropdown-item>
|
||
|
|
<wa-avatar slot="icon" initials={ token.Initials } style={ "--size: 20px; background: " + token.Color + ";" }></wa-avatar>
|
||
|
|
{ token.Symbol }
|
||
|
|
</wa-dropdown-item>
|
||
|
|
}
|
||
|
|
</wa-dropdown>
|
||
|
|
</div>
|
||
|
|
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500); margin-top: var(--wa-space-xs); display: block;">≈ $0.00</span>
|
||
|
|
</div>
|
||
|
|
<!-- Swap Settings -->
|
||
|
|
<wa-details summary="Swap Settings">
|
||
|
|
<div class="wa-stack wa-gap-m" style="padding-top: var(--wa-space-s);">
|
||
|
|
<div class="wa-stack wa-gap-xs">
|
||
|
|
<span class="wa-caption-s">Slippage Tolerance</span>
|
||
|
|
<wa-radio-group value="0.5" orientation="horizontal">
|
||
|
|
<wa-radio appearance="button" value="0.1">0.1%</wa-radio>
|
||
|
|
<wa-radio appearance="button" value="0.5">0.5%</wa-radio>
|
||
|
|
<wa-radio appearance="button" value="1">1%</wa-radio>
|
||
|
|
<wa-radio appearance="button" value="custom">Custom</wa-radio>
|
||
|
|
</wa-radio-group>
|
||
|
|
</div>
|
||
|
|
<wa-input label="Transaction Deadline" type="number" value="30" size="small">
|
||
|
|
<span slot="suffix">minutes</span>
|
||
|
|
</wa-input>
|
||
|
|
</div>
|
||
|
|
</wa-details>
|
||
|
|
<!-- Swap Preview -->
|
||
|
|
<wa-card style="background: var(--wa-color-surface-alt);">
|
||
|
|
<div class="wa-stack wa-gap-xs">
|
||
|
|
<div class="preview-row">
|
||
|
|
<span class="label">Rate</span>
|
||
|
|
<span>1 ETH = 2,345.00 USDC</span>
|
||
|
|
</div>
|
||
|
|
<div class="preview-row">
|
||
|
|
<span class="label">Price Impact</span>
|
||
|
|
<span style="color: var(--wa-color-success);">< 0.01%</span>
|
||
|
|
</div>
|
||
|
|
<div class="preview-row">
|
||
|
|
<span class="label">Minimum Received</span>
|
||
|
|
<span>0.00 USDC</span>
|
||
|
|
</div>
|
||
|
|
<div class="preview-row">
|
||
|
|
<span class="label">Network Fee</span>
|
||
|
|
<span>~$2.50</span>
|
||
|
|
</div>
|
||
|
|
<div class="preview-row">
|
||
|
|
<span class="label">Route</span>
|
||
|
|
<span class="wa-cluster wa-gap-2xs">
|
||
|
|
ETH
|
||
|
|
<wa-icon name="arrow-right" style="font-size: 10px;"></wa-icon>
|
||
|
|
USDC
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</wa-card>
|
||
|
|
<wa-callout variant="warning" style="--padding: var(--wa-space-s);">
|
||
|
|
<wa-icon slot="icon" name="triangle-alert"></wa-icon>
|
||
|
|
<span class="wa-caption-s">Price updates in real-time. Review carefully before confirming.</span>
|
||
|
|
</wa-callout>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="wa-cluster wa-gap-s" style="justify-content: flex-end;">
|
||
|
|
<wa-button variant="neutral" appearance="outlined" onclick="document.getElementById('swap-drawer').open = false">Cancel</wa-button>
|
||
|
|
<wa-button variant="brand" id="swap-confirm-btn">
|
||
|
|
<wa-icon slot="start" name="arrow-right-arrow-left"></wa-icon>
|
||
|
|
Review Swap
|
||
|
|
</wa-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</wa-drawer>
|
||
|
|
@drawerStyles()
|
||
|
|
@swapDirectionScript()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Script to handle swap direction button
|
||
|
|
script swapDirectionScript() {
|
||
|
|
document.getElementById('swap-direction-btn')?.addEventListener('click', function() {
|
||
|
|
this.style.transform = this.style.transform === 'rotate(180deg)' ? '' : 'rotate(180deg)';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Drawer-specific styles
|
||
|
|
templ drawerStyles() {
|
||
|
|
<style>
|
||
|
|
.drawer-form {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: var(--wa-space-l);
|
||
|
|
height: 100%;
|
||
|
|
}
|
||
|
|
.drawer-form-content {
|
||
|
|
flex: 1;
|
||
|
|
overflow-y: auto;
|
||
|
|
}
|
||
|
|
.qr-container {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
padding: var(--wa-space-xl);
|
||
|
|
background: var(--wa-color-surface-alt);
|
||
|
|
border-radius: var(--wa-radius-l);
|
||
|
|
}
|
||
|
|
.address-display {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: var(--wa-space-s);
|
||
|
|
padding: var(--wa-space-m);
|
||
|
|
background: var(--wa-color-surface-alt);
|
||
|
|
border-radius: var(--wa-radius-m);
|
||
|
|
font-family: var(--wa-font-mono);
|
||
|
|
font-size: var(--wa-font-size-s);
|
||
|
|
word-break: break-all;
|
||
|
|
}
|
||
|
|
.swap-token-box {
|
||
|
|
padding: var(--wa-space-m);
|
||
|
|
background: var(--wa-color-surface-alt);
|
||
|
|
border-radius: var(--wa-radius-m);
|
||
|
|
border: 1px solid var(--wa-color-neutral-200);
|
||
|
|
}
|
||
|
|
.swap-arrow {
|
||
|
|
display: flex;
|
||
|
|
justify-content: center;
|
||
|
|
margin: calc(var(--wa-space-s) * -1) 0;
|
||
|
|
position: relative;
|
||
|
|
z-index: 1;
|
||
|
|
}
|
||
|
|
.swap-arrow wa-icon-button {
|
||
|
|
background: var(--wa-color-surface);
|
||
|
|
border: 1px solid var(--wa-color-neutral-200);
|
||
|
|
border-radius: 50%;
|
||
|
|
transition: transform 0.2s;
|
||
|
|
}
|
||
|
|
.gas-settings {
|
||
|
|
padding: var(--wa-space-m);
|
||
|
|
background: var(--wa-color-surface-alt);
|
||
|
|
border-radius: var(--wa-radius-m);
|
||
|
|
}
|
||
|
|
.preview-row {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
padding: var(--wa-space-xs) 0;
|
||
|
|
font-size: var(--wa-font-size-s);
|
||
|
|
}
|
||
|
|
.preview-row .label {
|
||
|
|
color: var(--wa-color-neutral-500);
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
}
|
||
|
|
|
||
|
|
// AllDrawers renders all three drawers together for convenience
|
||
|
|
templ AllDrawers(tokens []TokenOption, walletAddress string) {
|
||
|
|
@ReceiveDrawer(tokens, walletAddress)
|
||
|
|
@SendDrawer(tokens)
|
||
|
|
@SwapDrawer(tokens)
|
||
|
|
}
|
||
|
|
|
||
|
|
// DrawerTriggerScript provides the JavaScript to wire up drawer triggers
|
||
|
|
script drawerTriggerScript() {
|
||
|
|
// Open receive drawer
|
||
|
|
document.querySelectorAll('[data-drawer-open="receive"]').forEach(el => {
|
||
|
|
el.addEventListener('click', () => {
|
||
|
|
document.getElementById('receive-drawer').open = true;
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Open send drawer
|
||
|
|
document.querySelectorAll('[data-drawer-open="send"]').forEach(el => {
|
||
|
|
el.addEventListener('click', () => {
|
||
|
|
document.getElementById('send-drawer').open = true;
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Open swap drawer
|
||
|
|
document.querySelectorAll('[data-drawer-open="swap"]').forEach(el => {
|
||
|
|
el.addEventListener('click', () => {
|
||
|
|
document.getElementById('swap-drawer').open = true;
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// DrawerTriggers component to initialize drawer triggers
|
||
|
|
templ DrawerTriggers() {
|
||
|
|
@drawerTriggerScript()
|
||
|
|
}
|