✨ Improve initial setup screen:
📊 Add relay log visibility during startup 💼 Implement automatic wallet configuration 🧭 Add progress indicator showing current setup step 🧹 Fix minor linting issues throughout codebase
This commit is contained in:
parent
295137b313
commit
9fe777abd9
7 changed files with 217 additions and 72 deletions
|
@ -14,7 +14,7 @@
|
|||
"email": "developers@arx-ccn.com"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && electron-vite build",
|
||||
"build": "electron-vite build",
|
||||
"build:mac": "npm run build && ./node_modules/.bin/electron-rebuild -p darwin -a x64 && electron-builder --mac",
|
||||
"build:linux": "bun run build && electron-builder --linux",
|
||||
"start": "electron-vite preview",
|
||||
|
|
|
@ -211,6 +211,7 @@ export class StyledButton extends LitElement {
|
|||
composed: true,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
private _handleKeyDown(e: KeyboardEvent) {
|
||||
|
|
|
@ -10,11 +10,13 @@ import * as nostrTools from '@nostr/tools/pure';
|
|||
import { encodeBase64 } from '@std/encoding/base64';
|
||||
import { LitElement, css, html } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import '@components/General/Button';
|
||||
import '@components/General/Fieldset';
|
||||
import '@components/General/Input';
|
||||
import '@components/LoadingView';
|
||||
import '@components/ProgressSteps';
|
||||
|
||||
@customElement('arx-initial-setup')
|
||||
export class InitialSetup extends LitElement {
|
||||
|
@ -24,6 +26,13 @@ export class InitialSetup extends LitElement {
|
|||
@state() private userName = '';
|
||||
@state() private profileImage = '';
|
||||
@state() private lightningAddress = '';
|
||||
@state() private relayStatus: { running: boolean; pid: number | null; logs: string[] } = {
|
||||
running: false,
|
||||
pid: null,
|
||||
logs: [],
|
||||
};
|
||||
|
||||
private readonly pageLabels = ['Welcome', 'Seed Phrase', 'Relay Setup', 'Profile', 'Complete'];
|
||||
|
||||
get encryptionPassphrase() {
|
||||
let encryptionPassphrase = localStorage.getItem('encryption_key');
|
||||
|
@ -85,6 +94,7 @@ export class InitialSetup extends LitElement {
|
|||
oklch(from var(--color-accent) l calc(c * 1.2) h)
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
margin-bottom: calc(var(--spacing-unit) * 8);
|
||||
}
|
||||
|
@ -139,7 +149,6 @@ export class InitialSetup extends LitElement {
|
|||
}
|
||||
|
||||
pre {
|
||||
white-space: normal;
|
||||
background-color: var(--color-base-200);
|
||||
padding: calc(var(--spacing-unit) * 4);
|
||||
border-radius: var(--radius-field);
|
||||
|
@ -220,6 +229,10 @@ export class InitialSetup extends LitElement {
|
|||
private renderPageOne() {
|
||||
return html`
|
||||
<main class="welcome-container" ${animate()}>
|
||||
<arx-progress-steps
|
||||
.currentPage=${this.currentPage}
|
||||
.pageLabels=${this.pageLabels}
|
||||
></arx-progress-steps>
|
||||
<section>
|
||||
<h1>Welcome to Eve</h1>
|
||||
<h2>Your Private Community Network</h2>
|
||||
|
@ -270,6 +283,10 @@ export class InitialSetup extends LitElement {
|
|||
private renderPageTwo() {
|
||||
return html`
|
||||
<main class="welcome-container" ${animate()}>
|
||||
<arx-progress-steps
|
||||
.currentPage=${this.currentPage}
|
||||
.pageLabels=${this.pageLabels}
|
||||
></arx-progress-steps>
|
||||
<section>
|
||||
<h1>Getting Started</h1>
|
||||
<h2>Creating a Community</h2>
|
||||
|
@ -338,11 +355,21 @@ export class InitialSetup extends LitElement {
|
|||
private async startRelay() {
|
||||
await window.relay.writeSeed(this.seedPhrase);
|
||||
await window.relay.start(this.encryptionPassphrase);
|
||||
this.updateRelayStatus();
|
||||
}
|
||||
|
||||
private async updateRelayStatus() {
|
||||
this.relayStatus = await window.relay.getStatus();
|
||||
if (this.relayStatus.running) setTimeout(() => this.updateRelayStatus(), 2000);
|
||||
}
|
||||
|
||||
private renderPageThree() {
|
||||
return html`
|
||||
<main class="welcome-container" ${animate()}>
|
||||
<arx-progress-steps
|
||||
.currentPage=${this.currentPage}
|
||||
.pageLabels=${this.pageLabels}
|
||||
></arx-progress-steps>
|
||||
<section>
|
||||
<h2>Configure Eve Relay</h2>
|
||||
<p>
|
||||
|
@ -352,11 +379,25 @@ export class InitialSetup extends LitElement {
|
|||
<p>Please press the button below to start the relay.</p>
|
||||
<arx-button
|
||||
variant="primary"
|
||||
label="Start Relay"
|
||||
label=${this.relayStatus.running ? 'Relay Running' : 'Start Relay'}
|
||||
?disabled=${this.relayStatus.running}
|
||||
@click=${() => this.startRelay()}
|
||||
>
|
||||
Start Relay
|
||||
${when(
|
||||
this.relayStatus.running,
|
||||
() => html`<iconify-icon slot="prefix" icon="mdi:check-circle"></iconify-icon>`,
|
||||
)}
|
||||
</arx-button>
|
||||
${when(
|
||||
this.relayStatus.running,
|
||||
() => html`
|
||||
<p class="note">
|
||||
<iconify-icon icon="mdi:information"></iconify-icon>
|
||||
Relay is running with PID: ${this.relayStatus.pid}
|
||||
</p>
|
||||
<pre class="relay-logs">${this.relayStatus.logs.slice(-5).join('\n')}</pre>
|
||||
`,
|
||||
)}
|
||||
<p>
|
||||
Having trouble? Our team is here to help if you encounter any
|
||||
issues.
|
||||
|
@ -374,6 +415,7 @@ export class InitialSetup extends LitElement {
|
|||
<arx-button
|
||||
@click=${() => this.handleNavigation(4)}
|
||||
variant="primary"
|
||||
?disabled=${!this.relayStatus.running}
|
||||
label="Continue"
|
||||
>
|
||||
</arx-button>
|
||||
|
@ -390,13 +432,13 @@ export class InitialSetup extends LitElement {
|
|||
this.profileImage = e.detail.value;
|
||||
}
|
||||
|
||||
private onLightningAddressInput(e: CustomEvent<{ value: string }>) {
|
||||
this.lightningAddress = e.detail.value;
|
||||
}
|
||||
|
||||
private renderPageFour() {
|
||||
return html`
|
||||
<main class="welcome-container" ${animate()}>
|
||||
<arx-progress-steps
|
||||
.currentPage=${this.currentPage}
|
||||
.pageLabels=${this.pageLabels}
|
||||
></arx-progress-steps>
|
||||
<section>
|
||||
<h2>Complete Your Profile</h2>
|
||||
<p>Great progress! Let's set up your community profile.</p>
|
||||
|
@ -437,9 +479,9 @@ export class InitialSetup extends LitElement {
|
|||
>
|
||||
</arx-button>
|
||||
<arx-button
|
||||
@click=${() => this.handleNavigation(5)}
|
||||
@click=${() => this.goToFinalStep()}
|
||||
variant="primary"
|
||||
label="Final Step"
|
||||
label="Next"
|
||||
>
|
||||
</arx-button>
|
||||
</div>
|
||||
|
@ -447,53 +489,12 @@ export class InitialSetup extends LitElement {
|
|||
`;
|
||||
}
|
||||
|
||||
private renderPageFive() {
|
||||
return html`
|
||||
<main class="welcome-container" ${animate()}>
|
||||
<section>
|
||||
<h2>Payment Setup</h2>
|
||||
<p>Almost done! Let's set up your payment options.</p>
|
||||
<p>
|
||||
Enter your existing Lightning address below for payments within the
|
||||
community. If you don't have one, leave this field blank and we'll
|
||||
automatically generate one for you through
|
||||
<a target="_blank" href="https://npub.cash" class="external-link"
|
||||
>npub.cash</a
|
||||
>.
|
||||
</p>
|
||||
<arx-input
|
||||
id="lightning-address"
|
||||
type="text"
|
||||
.value=${this.lightningAddress}
|
||||
@change=${this.onLightningAddressInput}
|
||||
placeholder="your@lightning.address"
|
||||
/></arx-input>
|
||||
<small class="note">
|
||||
Your Lightning address enables secure, instant payments within the
|
||||
community.
|
||||
</small>
|
||||
</section>
|
||||
|
||||
<div class="navigation">
|
||||
<arx-button
|
||||
@click=${() => this.handleNavigation(4)}
|
||||
variant="secondary"
|
||||
label="Back"
|
||||
>
|
||||
</arx-button>
|
||||
<arx-button variant="primary" label="Next" @click=${() => this.goToFinalStep()}>
|
||||
</arx-button>
|
||||
</div>
|
||||
</main>
|
||||
`;
|
||||
}
|
||||
|
||||
private async goToFinalStep() {
|
||||
const randomPrivateKey = nostrTools.generateSecretKey();
|
||||
const encryptedNsec = nip49.encrypt(randomPrivateKey, this.encryptionPassphrase);
|
||||
const npub = nip19.npubEncode(nostrTools.getPublicKey(randomPrivateKey));
|
||||
|
||||
if (!this.lightningAddress) this.lightningAddress = `${npub}@npub.cash`;
|
||||
this.lightningAddress = `${npub}@npub.cash`;
|
||||
|
||||
localStorage.setItem('ncryptsec', encryptedNsec);
|
||||
|
||||
|
@ -510,12 +511,16 @@ export class InitialSetup extends LitElement {
|
|||
await event.sign();
|
||||
await event.publish();
|
||||
|
||||
this.handleNavigation(6);
|
||||
this.handleNavigation(5);
|
||||
}
|
||||
|
||||
private renderPageSix() {
|
||||
private renderPageFive() {
|
||||
return html`
|
||||
<main class="welcome-container" ${animate()}>
|
||||
<arx-progress-steps
|
||||
.currentPage=${this.currentPage}
|
||||
.pageLabels=${this.pageLabels}
|
||||
></arx-progress-steps>
|
||||
<section>
|
||||
<h2>Done!</h2>
|
||||
<p>
|
||||
|
@ -528,19 +533,13 @@ export class InitialSetup extends LitElement {
|
|||
required to invite new members to the community.
|
||||
</p>
|
||||
<p>Your username is: <b>${this.userName}</b></p>
|
||||
${
|
||||
this.profileImage
|
||||
? html` <p>
|
||||
Your profile image is: <img .src=${this.profileImage} />
|
||||
</p>`
|
||||
: ''
|
||||
}
|
||||
${when(this.profileImage, () => html`<p>Your profile image is: <img .src=${this.profileImage} /></p>`)}
|
||||
<p>Your lightning address is: <b>${this.lightningAddress}</b></p>
|
||||
</section>
|
||||
|
||||
<div class="navigation">
|
||||
<arx-button
|
||||
@click=${() => this.handleNavigation(5)}
|
||||
@click=${() => this.handleNavigation(4)}
|
||||
variant="secondary"
|
||||
label="Back"
|
||||
>
|
||||
|
@ -581,8 +580,6 @@ export class InitialSetup extends LitElement {
|
|||
return this.renderPageFour();
|
||||
case 5:
|
||||
return this.renderPageFive();
|
||||
case 6:
|
||||
return this.renderPageSix();
|
||||
default:
|
||||
return html`<div class="welcome-container">
|
||||
<arx-loading-view></arx-loading-view>
|
||||
|
|
114
src/components/ProgressSteps.ts
Normal file
114
src/components/ProgressSteps.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
import { LitElement, css, html } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
|
||||
@customElement('arx-progress-steps')
|
||||
export class ProgressSteps extends LitElement {
|
||||
@property({ type: Number }) currentPage = 1;
|
||||
@property({ type: Array }) pageLabels: string[] = [];
|
||||
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: calc(var(--spacing-unit) * 8);
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--spacing-unit) * 2);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: var(--color-base-200);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--color-accent);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.page-indicators {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 calc(var(--spacing-unit) * 2);
|
||||
}
|
||||
|
||||
.page-indicator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: calc(var(--spacing-unit) * 1);
|
||||
}
|
||||
|
||||
.page-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-base-300);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.page-dot.active {
|
||||
background: var(--color-accent);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.page-dot.completed {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
.page-label {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-secondary);
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.page-label.active {
|
||||
color: var(--color-accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.page-label.completed {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
`;
|
||||
|
||||
override render() {
|
||||
const progress = ((this.currentPage - 1) / (this.pageLabels.length - 1)) * 100;
|
||||
|
||||
return html`
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar">
|
||||
<div
|
||||
class="progress-fill"
|
||||
style="width: ${progress}%"
|
||||
></div>
|
||||
</div>
|
||||
<div class="page-indicators">
|
||||
${this.pageLabels.map((label, index) => {
|
||||
const pageNum = index + 1;
|
||||
const isActive = pageNum === this.currentPage;
|
||||
const isCompleted = pageNum < this.currentPage;
|
||||
|
||||
return html`
|
||||
<div class="page-indicator">
|
||||
<div
|
||||
class="page-dot ${isActive ? 'active' : ''} ${isCompleted ? 'completed' : ''}"
|
||||
></div>
|
||||
<span
|
||||
class="page-label ${isActive ? 'active' : ''} ${isCompleted ? 'completed' : ''}"
|
||||
>${label}</span>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ export class RelayManager {
|
|||
private relayLogs: string[];
|
||||
private readonly maxLogs: number;
|
||||
private encryptionKey: string | null;
|
||||
private devRunning: boolean;
|
||||
|
||||
/**
|
||||
* Checks if the relay is currently running.
|
||||
|
@ -22,7 +23,9 @@ export class RelayManager {
|
|||
* @returns {boolean} True if the relay is running, otherwise false.
|
||||
*/
|
||||
get isRunning(): boolean {
|
||||
return !!this.process;
|
||||
if (!this.process) return false;
|
||||
if (is.dev) return this.devRunning;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,6 +49,7 @@ export class RelayManager {
|
|||
this.relayLogs = [];
|
||||
this.maxLogs = maxLogs;
|
||||
this.encryptionKey = null;
|
||||
this.devRunning = false;
|
||||
}
|
||||
|
||||
private detectEnvironment(): PackageEnvironment {
|
||||
|
@ -123,10 +127,39 @@ export class RelayManager {
|
|||
this.encryptionKey = encryptionKey;
|
||||
|
||||
if (is.dev) {
|
||||
this.relayLogs = [];
|
||||
|
||||
this.process = spawn('nc', ['localhost', '6942']);
|
||||
this.process.on('exit', () => {
|
||||
this.devRunning = true;
|
||||
|
||||
if (this.process.stdout) {
|
||||
this.process.stdout.on('data', (data: Buffer) => {
|
||||
const logLine = data.toString().trim();
|
||||
this.addLog(`[DEV] ${logLine}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.process.stderr) {
|
||||
this.process.stderr.on('data', (data: Buffer) => {
|
||||
const logLine = data.toString().trim();
|
||||
this.addLog(`[DEV] ERROR: ${logLine}`);
|
||||
});
|
||||
}
|
||||
|
||||
this.process.on('error', (err: Error) => {
|
||||
this.addLog(`[DEV] Failed to start netcat: ${err.message}`);
|
||||
this.process = null;
|
||||
this.start(encryptionKey);
|
||||
this.devRunning = false;
|
||||
this.restartProcess();
|
||||
});
|
||||
|
||||
this.process.on('exit', (code: number | null) => {
|
||||
this.addLog(`[DEV] Netcat exited with code ${code}`);
|
||||
this.process = null;
|
||||
this.devRunning = false;
|
||||
|
||||
if (code !== 1) this.start(encryptionKey);
|
||||
else this.addLog('[DEV] Connection failed - target port may not be open - relay is not running');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ export class EveSettings extends LitElement {
|
|||
</arx-fieldset>
|
||||
<arx-fieldset legend="Profile">
|
||||
${when(
|
||||
this.profile.picture,
|
||||
this.profile!.picture,
|
||||
() => html`
|
||||
<div class="profile-header">
|
||||
<img
|
||||
|
@ -143,7 +143,7 @@ export class EveSettings extends LitElement {
|
|||
label="Name"
|
||||
type="text"
|
||||
name="name"
|
||||
.value=${this.profile.name}
|
||||
.value=${this.profile!.name}
|
||||
@change=${(e: ArxInputChangeEvent) => this.handleInputChange('name', e)}
|
||||
placeholder="Your display name"
|
||||
></arx-input>
|
||||
|
@ -152,7 +152,7 @@ export class EveSettings extends LitElement {
|
|||
label="Profile Image URL"
|
||||
type="text"
|
||||
name="image"
|
||||
.value=${this.profile.picture}
|
||||
.value=${this.profile!.picture}
|
||||
@change=${(e: ArxInputChangeEvent) => this.handleInputChange('picture', e)}
|
||||
placeholder="https://example.com/your-image.jpg"
|
||||
></arx-input>
|
||||
|
@ -161,7 +161,7 @@ export class EveSettings extends LitElement {
|
|||
label="Banner URL"
|
||||
type="text"
|
||||
name="banner"
|
||||
.value=${this.profile.banner}
|
||||
.value=${this.profile!.banner}
|
||||
@change=${(e: ArxInputChangeEvent) => this.handleInputChange('banner', e)}
|
||||
placeholder="https://example.com/your-image.jpg"
|
||||
></arx-input>
|
||||
|
|
|
@ -32,5 +32,5 @@ export default function formatDateTime(date: Date | string | number): string {
|
|||
minute: formattingOptions.minute === '' ? undefined : formattingOptions.minute,
|
||||
second: formattingOptions.second === '' ? undefined : formattingOptions.second,
|
||||
hour12: formattingOptions.hour12,
|
||||
});
|
||||
} as unknown as Intl.DateTimeFormatOptions);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue