Release v1.2.1 - Add quick-add mint list to Cashu wallet
- Add suggested mints list (Minibits, Coinos, 21Mint, Macadamia, Stablenut) - Show quick-add menu on empty Cashu page with + icon and descriptions - Add collapsible "Quick Add" disclosure when mints exist - Hide already-added mints from the list - Closes #6 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "plebeian-signer",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "plebeian-signer",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^19.0.0",
|
||||
"@angular/common": "^19.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "plebeian-signer",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"custom": {
|
||||
"chrome": {
|
||||
"version": "v1.1.6"
|
||||
|
||||
@@ -122,6 +122,31 @@
|
||||
<button class="show-info-btn" (click)="showCashuInfo = true">
|
||||
Show storage info
|
||||
</button>
|
||||
|
||||
<!-- Suggested mints for quick-add -->
|
||||
<div class="quick-add-section">
|
||||
<div class="quick-add-label">Quick Add a Mint</div>
|
||||
<div class="quick-add-menu">
|
||||
@for (mint of suggestedMints; track mint.url) {
|
||||
@if (!isMintAlreadyAdded(mint.url)) {
|
||||
<button
|
||||
class="quick-add-item"
|
||||
[disabled]="addingMint"
|
||||
(click)="quickAddMint(mint)"
|
||||
>
|
||||
<span class="mint-row">
|
||||
<span class="add-icon">+</span>
|
||||
<span class="mint-name">{{ mint.name }}</span>
|
||||
</span>
|
||||
<span class="mint-desc">{{ mint.description }}</span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (mintError) {
|
||||
<div class="error-message small">{{ mintError }}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -134,6 +159,33 @@
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Quick add disclosure when mints exist -->
|
||||
@if (hasUnavailableMints()) {
|
||||
<details class="quick-add-disclosure">
|
||||
<summary>Quick Add</summary>
|
||||
<div class="quick-add-menu">
|
||||
@for (mint of suggestedMints; track mint.url) {
|
||||
@if (!isMintAlreadyAdded(mint.url)) {
|
||||
<button
|
||||
class="quick-add-item"
|
||||
[disabled]="addingMint"
|
||||
(click)="quickAddMint(mint)"
|
||||
>
|
||||
<span class="mint-row">
|
||||
<span class="add-icon">+</span>
|
||||
<span class="mint-name">{{ mint.name }}</span>
|
||||
</span>
|
||||
<span class="mint-desc">{{ mint.description }}</span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (mintError) {
|
||||
<div class="error-message small">{{ mintError }}</div>
|
||||
}
|
||||
</details>
|
||||
}
|
||||
}
|
||||
<button class="add-wallet-btn" (click)="showAddMint()">
|
||||
<span class="emoji">+</span>
|
||||
@@ -210,6 +262,31 @@
|
||||
<!-- Cashu add mint form -->
|
||||
@else if (activeSection === 'cashu-add') {
|
||||
<div class="add-wallet-form">
|
||||
<!-- Suggested mints -->
|
||||
<div class="suggested-mints">
|
||||
<div class="suggested-label">Quick Add</div>
|
||||
<div class="suggested-list">
|
||||
@for (mint of suggestedMints; track mint.url) {
|
||||
<button
|
||||
class="suggested-mint-btn"
|
||||
[class.already-added]="isMintAlreadyAdded(mint.url)"
|
||||
[disabled]="isMintAlreadyAdded(mint.url) || addingMint"
|
||||
(click)="selectSuggestedMint(mint)"
|
||||
[title]="mint.description"
|
||||
>
|
||||
<span class="mint-name">{{ mint.name }}</span>
|
||||
@if (isMintAlreadyAdded(mint.url)) {
|
||||
<span class="added-badge">✓</span>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-divider">
|
||||
<span>or enter manually</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="mintName">Mint Name</label>
|
||||
<input
|
||||
|
||||
@@ -1134,3 +1134,182 @@
|
||||
padding: var(--size);
|
||||
}
|
||||
}
|
||||
|
||||
// Suggested mints quick-add
|
||||
.suggested-mints {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--size-h);
|
||||
|
||||
&.centered {
|
||||
align-items: center;
|
||||
margin-top: var(--size);
|
||||
|
||||
.suggested-list {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.suggested-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted-foreground);
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.suggested-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--size-h);
|
||||
}
|
||||
|
||||
.suggested-mint-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 12px;
|
||||
background: var(--background-light);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
color: var(--foreground);
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--background-light-hover);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.already-added {
|
||||
opacity: 0.5;
|
||||
background: transparent;
|
||||
|
||||
.added-badge {
|
||||
color: var(--success, #22c55e);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.mint-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--size);
|
||||
color: var(--muted-foreground);
|
||||
font-size: 0.75rem;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
// Quick add section (empty state)
|
||||
.quick-add-section {
|
||||
width: 100%;
|
||||
margin-top: var(--size);
|
||||
|
||||
.quick-add-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted-foreground);
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
margin-bottom: var(--size-h);
|
||||
}
|
||||
}
|
||||
|
||||
// Quick add disclosure (when mints exist)
|
||||
.quick-add-disclosure {
|
||||
margin-top: var(--size-h);
|
||||
|
||||
summary {
|
||||
font-size: 0.8rem;
|
||||
color: var(--muted-foreground);
|
||||
cursor: pointer;
|
||||
padding: var(--size-h);
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
&::marker {
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
&[open] summary {
|
||||
margin-bottom: var(--size-h);
|
||||
}
|
||||
}
|
||||
|
||||
// Quick add menu (shared by both)
|
||||
.quick-add-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
background: var(--background-light);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quick-add-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
padding: var(--size-h) var(--size);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--background-light-hover);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.mint-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--size-h);
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--success, #22c55e);
|
||||
}
|
||||
|
||||
.mint-name {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.mint-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted-foreground);
|
||||
padding-left: calc(1rem + var(--size-h));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,15 @@ export class WalletComponent extends NavComponent implements OnInit, OnDestroy {
|
||||
refreshingMint = false;
|
||||
refreshError = '';
|
||||
|
||||
// Suggested mints for quick-add
|
||||
readonly suggestedMints = [
|
||||
{ name: 'Minibits', url: 'https://mint.minibits.cash', description: 'Well-established mobile wallet mint' },
|
||||
{ name: 'Coinos', url: 'https://mint.coinos.io', description: 'Lightning wallet with Cashu integration' },
|
||||
{ name: '21Mint', url: 'https://21mint.me', description: 'Community mint' },
|
||||
{ name: 'Macadamia', url: 'https://mint.macadamia.cash', description: 'Reliable community mint' },
|
||||
{ name: 'Stablenut (USD)', url: 'https://stablenut.umint.cash', unit: 'usd', description: 'USD-denominated mint' },
|
||||
];
|
||||
|
||||
get title(): string {
|
||||
switch (this.activeSection) {
|
||||
case 'cashu':
|
||||
@@ -499,6 +508,35 @@ export class WalletComponent extends NavComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
selectSuggestedMint(mint: { name: string; url: string }) {
|
||||
this.newMintName = mint.name;
|
||||
this.newMintUrl = mint.url;
|
||||
this.mintError = '';
|
||||
this.mintTestResult = '';
|
||||
}
|
||||
|
||||
isMintAlreadyAdded(mintUrl: string): boolean {
|
||||
return this.mints.some(m => m.mintUrl === mintUrl);
|
||||
}
|
||||
|
||||
hasUnavailableMints(): boolean {
|
||||
return this.suggestedMints.some(m => !this.isMintAlreadyAdded(m.url));
|
||||
}
|
||||
|
||||
async quickAddMint(mint: { name: string; url: string }) {
|
||||
this.addingMint = true;
|
||||
this.mintError = '';
|
||||
|
||||
try {
|
||||
await this.cashuService.addMint(mint.name, mint.url);
|
||||
} catch (error) {
|
||||
this.mintError =
|
||||
error instanceof Error ? error.message : 'Failed to add mint';
|
||||
} finally {
|
||||
this.addingMint = false;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMint() {
|
||||
if (!this.selectedMintId) return;
|
||||
|
||||
|
||||
@@ -122,6 +122,31 @@
|
||||
<button class="show-info-btn" (click)="showCashuInfo = true">
|
||||
Show storage info
|
||||
</button>
|
||||
|
||||
<!-- Suggested mints for quick-add -->
|
||||
<div class="quick-add-section">
|
||||
<div class="quick-add-label">Quick Add a Mint</div>
|
||||
<div class="quick-add-menu">
|
||||
@for (mint of suggestedMints; track mint.url) {
|
||||
@if (!isMintAlreadyAdded(mint.url)) {
|
||||
<button
|
||||
class="quick-add-item"
|
||||
[disabled]="addingMint"
|
||||
(click)="quickAddMint(mint)"
|
||||
>
|
||||
<span class="mint-row">
|
||||
<span class="add-icon">+</span>
|
||||
<span class="mint-name">{{ mint.name }}</span>
|
||||
</span>
|
||||
<span class="mint-desc">{{ mint.description }}</span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (mintError) {
|
||||
<div class="error-message small">{{ mintError }}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -134,6 +159,33 @@
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Quick add disclosure when mints exist -->
|
||||
@if (hasUnavailableMints()) {
|
||||
<details class="quick-add-disclosure">
|
||||
<summary>Quick Add</summary>
|
||||
<div class="quick-add-menu">
|
||||
@for (mint of suggestedMints; track mint.url) {
|
||||
@if (!isMintAlreadyAdded(mint.url)) {
|
||||
<button
|
||||
class="quick-add-item"
|
||||
[disabled]="addingMint"
|
||||
(click)="quickAddMint(mint)"
|
||||
>
|
||||
<span class="mint-row">
|
||||
<span class="add-icon">+</span>
|
||||
<span class="mint-name">{{ mint.name }}</span>
|
||||
</span>
|
||||
<span class="mint-desc">{{ mint.description }}</span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (mintError) {
|
||||
<div class="error-message small">{{ mintError }}</div>
|
||||
}
|
||||
</details>
|
||||
}
|
||||
}
|
||||
<button class="add-wallet-btn" (click)="showAddMint()">
|
||||
<span class="emoji">+</span>
|
||||
@@ -210,6 +262,31 @@
|
||||
<!-- Cashu add mint form -->
|
||||
@else if (activeSection === 'cashu-add') {
|
||||
<div class="add-wallet-form">
|
||||
<!-- Suggested mints -->
|
||||
<div class="suggested-mints">
|
||||
<div class="suggested-label">Quick Add</div>
|
||||
<div class="suggested-list">
|
||||
@for (mint of suggestedMints; track mint.url) {
|
||||
<button
|
||||
class="suggested-mint-btn"
|
||||
[class.already-added]="isMintAlreadyAdded(mint.url)"
|
||||
[disabled]="isMintAlreadyAdded(mint.url) || addingMint"
|
||||
(click)="selectSuggestedMint(mint)"
|
||||
[title]="mint.description"
|
||||
>
|
||||
<span class="mint-name">{{ mint.name }}</span>
|
||||
@if (isMintAlreadyAdded(mint.url)) {
|
||||
<span class="added-badge">✓</span>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-divider">
|
||||
<span>or enter manually</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="mintName">Mint Name</label>
|
||||
<input
|
||||
|
||||
@@ -1134,3 +1134,173 @@
|
||||
padding: var(--size);
|
||||
}
|
||||
}
|
||||
|
||||
// Suggested mints quick-add
|
||||
.suggested-mints {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--size-h);
|
||||
|
||||
.suggested-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted-foreground);
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.suggested-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--size-h);
|
||||
}
|
||||
|
||||
.suggested-mint-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 12px;
|
||||
background: var(--background-light);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
color: var(--foreground);
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--background-light-hover);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.already-added {
|
||||
opacity: 0.5;
|
||||
background: transparent;
|
||||
|
||||
.added-badge {
|
||||
color: var(--success, #22c55e);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.mint-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--size);
|
||||
color: var(--muted-foreground);
|
||||
font-size: 0.75rem;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
// Quick add section (empty state)
|
||||
.quick-add-section {
|
||||
width: 100%;
|
||||
margin-top: var(--size);
|
||||
|
||||
.quick-add-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted-foreground);
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
margin-bottom: var(--size-h);
|
||||
}
|
||||
}
|
||||
|
||||
// Quick add disclosure (when mints exist)
|
||||
.quick-add-disclosure {
|
||||
margin-top: var(--size-h);
|
||||
|
||||
summary {
|
||||
font-size: 0.8rem;
|
||||
color: var(--muted-foreground);
|
||||
cursor: pointer;
|
||||
padding: var(--size-h);
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
&::marker {
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
&[open] summary {
|
||||
margin-bottom: var(--size-h);
|
||||
}
|
||||
}
|
||||
|
||||
// Quick add menu (shared by both)
|
||||
.quick-add-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
background: var(--background-light);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quick-add-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
padding: var(--size-h) var(--size);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--background-light-hover);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.mint-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--size-h);
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--success, #22c55e);
|
||||
}
|
||||
|
||||
.mint-name {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.mint-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted-foreground);
|
||||
padding-left: calc(1rem + var(--size-h));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,15 @@ export class WalletComponent extends NavComponent implements OnInit, OnDestroy {
|
||||
refreshingMint = false;
|
||||
refreshError = '';
|
||||
|
||||
// Suggested mints for quick-add
|
||||
readonly suggestedMints = [
|
||||
{ name: 'Minibits', url: 'https://mint.minibits.cash', description: 'Well-established mobile wallet mint' },
|
||||
{ name: 'Coinos', url: 'https://mint.coinos.io', description: 'Lightning wallet with Cashu integration' },
|
||||
{ name: '21Mint', url: 'https://21mint.me', description: 'Community mint' },
|
||||
{ name: 'Macadamia', url: 'https://mint.macadamia.cash', description: 'Reliable community mint' },
|
||||
{ name: 'Stablenut (USD)', url: 'https://stablenut.umint.cash', unit: 'usd', description: 'USD-denominated mint' },
|
||||
];
|
||||
|
||||
get title(): string {
|
||||
switch (this.activeSection) {
|
||||
case 'cashu':
|
||||
@@ -499,6 +508,35 @@ export class WalletComponent extends NavComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
selectSuggestedMint(mint: { name: string; url: string }) {
|
||||
this.newMintName = mint.name;
|
||||
this.newMintUrl = mint.url;
|
||||
this.mintError = '';
|
||||
this.mintTestResult = '';
|
||||
}
|
||||
|
||||
isMintAlreadyAdded(mintUrl: string): boolean {
|
||||
return this.mints.some(m => m.mintUrl === mintUrl);
|
||||
}
|
||||
|
||||
hasUnavailableMints(): boolean {
|
||||
return this.suggestedMints.some(m => !this.isMintAlreadyAdded(m.url));
|
||||
}
|
||||
|
||||
async quickAddMint(mint: { name: string; url: string }) {
|
||||
this.addingMint = true;
|
||||
this.mintError = '';
|
||||
|
||||
try {
|
||||
await this.cashuService.addMint(mint.name, mint.url);
|
||||
} catch (error) {
|
||||
this.mintError =
|
||||
error instanceof Error ? error.message : 'Failed to add mint';
|
||||
} finally {
|
||||
this.addingMint = false;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMint() {
|
||||
if (!this.selectedMintId) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user