feat: add delete alias functionality & code cleanup (settings)
This commit is contained in:
parent
c9b6f02fd3
commit
e959809a0e
10 changed files with 553 additions and 271 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -15,17 +15,17 @@
|
|||
"@iconify/svelte": "^4.0.2",
|
||||
"@sveltejs/adapter-node": "^5.2.9",
|
||||
"@sveltejs/adapter-auto": "^3.3.1",
|
||||
"@sveltejs/kit": "^2.8.5",
|
||||
"@sveltejs/kit": "^2.9.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.2",
|
||||
"prettier": "^3.4.1",
|
||||
"prettier-plugin-svelte": "^3.3.2",
|
||||
"svelte": "^5.2.10",
|
||||
"svelte": "^5.2.11",
|
||||
"svelte-check": "^4.1.0",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^5.4.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@arx/utils": "git+ssh://git@git.arx-ccn.com:222/Arx/ts-utils",
|
||||
"@arx/utils": "git+ssh://git@git.arx-ccn.com:222/Arx/ts-utils#03163e9f4a07b011a28a8f97c90852ecfc806ddd",
|
||||
"@gandlaf21/bc-ur": "^1.1.12",
|
||||
"@nostr-dev-kit/ndk": "^2.10.7",
|
||||
"@nostr-dev-kit/ndk-cache-dexie": "^2.5.8",
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
<Icon icon="ph:user" />
|
||||
{/if}
|
||||
</div>
|
||||
{#if npub !== displayString && !emailAddress}
|
||||
{#if npub !== displayString}
|
||||
<Tooltip position="bottom" content={npub}>
|
||||
<span class="user-text">{displayString}</span>
|
||||
</Tooltip>
|
||||
|
|
244
src/lib/random.ts
Normal file
244
src/lib/random.ts
Normal file
|
@ -0,0 +1,244 @@
|
|||
export const adjectives = [
|
||||
// Colors
|
||||
'crimson',
|
||||
'azure',
|
||||
'golden',
|
||||
'silver',
|
||||
'emerald',
|
||||
'violet',
|
||||
'cobalt',
|
||||
'scarlet',
|
||||
'obsidian',
|
||||
'jade',
|
||||
'amber',
|
||||
'coral',
|
||||
'indigo',
|
||||
'sapphire',
|
||||
'ruby',
|
||||
'onyx',
|
||||
// Elements
|
||||
'frost',
|
||||
'flame',
|
||||
'storm',
|
||||
'thunder',
|
||||
'crystal',
|
||||
'shadow',
|
||||
'lunar',
|
||||
'solar',
|
||||
'plasma',
|
||||
'terra',
|
||||
'aether',
|
||||
'void',
|
||||
'cosmic',
|
||||
'astral',
|
||||
'nebula',
|
||||
'nova',
|
||||
// Power
|
||||
'mega',
|
||||
'ultra',
|
||||
'hyper',
|
||||
'super',
|
||||
'prime',
|
||||
'apex',
|
||||
'elite',
|
||||
'omega',
|
||||
'alpha',
|
||||
'delta',
|
||||
'sigma',
|
||||
'gamma',
|
||||
'beta',
|
||||
'epsilon',
|
||||
'zeta',
|
||||
'theta',
|
||||
// Tech
|
||||
'cyber',
|
||||
'techno',
|
||||
'digital',
|
||||
'binary',
|
||||
'neural',
|
||||
'crypto',
|
||||
'matrix',
|
||||
'vector',
|
||||
'quantum',
|
||||
'nano',
|
||||
'laser',
|
||||
'cyber',
|
||||
'data',
|
||||
'pixel',
|
||||
'sonic',
|
||||
'hyper',
|
||||
// Mystical
|
||||
'mystic',
|
||||
'arcane',
|
||||
'ethereal',
|
||||
'divine',
|
||||
'phantom',
|
||||
'spirit',
|
||||
'ancient',
|
||||
'chaos',
|
||||
'astral',
|
||||
'eldritch',
|
||||
'occult',
|
||||
'mythic',
|
||||
'sacred',
|
||||
'cursed',
|
||||
'blessed',
|
||||
'doom',
|
||||
// Nature
|
||||
'savage',
|
||||
'primal',
|
||||
'feral',
|
||||
'wild',
|
||||
'fierce',
|
||||
'rapid',
|
||||
'swift',
|
||||
'silent',
|
||||
'deadly',
|
||||
'stealth',
|
||||
'shadow',
|
||||
'night',
|
||||
'dark',
|
||||
'light',
|
||||
'bright',
|
||||
'dawn',
|
||||
// Epic
|
||||
'epic',
|
||||
'legendary',
|
||||
'mythic',
|
||||
'eternal',
|
||||
'immortal',
|
||||
'infinite',
|
||||
'supreme',
|
||||
'ultimate',
|
||||
'grand',
|
||||
'mighty',
|
||||
'noble',
|
||||
'royal',
|
||||
'heroic',
|
||||
'valor',
|
||||
'glory',
|
||||
'honor'
|
||||
];
|
||||
|
||||
|
||||
export const animals = [
|
||||
// Classic Predators
|
||||
'wolf',
|
||||
'tiger',
|
||||
'lion',
|
||||
'eagle',
|
||||
'hawk',
|
||||
'bear',
|
||||
'panther',
|
||||
'falcon',
|
||||
'jaguar',
|
||||
'leopard',
|
||||
'lynx',
|
||||
'cobra',
|
||||
'viper',
|
||||
'python',
|
||||
'raptor',
|
||||
'shark',
|
||||
// Mythical Dragons
|
||||
'dragon',
|
||||
'wyvern',
|
||||
'drake',
|
||||
'wyrm',
|
||||
'hydra',
|
||||
'basilisk',
|
||||
'tiamat',
|
||||
'ryuu',
|
||||
'fafnir',
|
||||
'bahamut',
|
||||
'ryu',
|
||||
'draco',
|
||||
'naga',
|
||||
'ouroboros',
|
||||
'lindworm',
|
||||
'lung',
|
||||
// Fantasy
|
||||
'phoenix',
|
||||
'griffin',
|
||||
'unicorn',
|
||||
'pegasus',
|
||||
'chimera',
|
||||
'manticore',
|
||||
'sphinx',
|
||||
'kraken',
|
||||
'behemoth',
|
||||
'leviathan',
|
||||
'titan',
|
||||
'giant',
|
||||
'colossus',
|
||||
'golem',
|
||||
'gargoyle',
|
||||
'djinn',
|
||||
// Norse
|
||||
'fenrir',
|
||||
'jormungandr',
|
||||
'sleipnir',
|
||||
'valkyrie',
|
||||
'einherjar',
|
||||
'huginn',
|
||||
'muninn',
|
||||
'garmr',
|
||||
'nidhogg',
|
||||
'ratatoskr',
|
||||
'hraesvelgr',
|
||||
'gullinkambi',
|
||||
'eikthyrnir',
|
||||
'duneyrr',
|
||||
'dvalinn',
|
||||
'dainn',
|
||||
// Eastern
|
||||
'kitsune',
|
||||
'kirin',
|
||||
'byakko',
|
||||
'suzaku',
|
||||
'genbu',
|
||||
'seiryu',
|
||||
'oni',
|
||||
'tengu',
|
||||
'raiju',
|
||||
'baku',
|
||||
'nekomata',
|
||||
'tanuki',
|
||||
'kappa',
|
||||
'tsukumogami',
|
||||
'yokai',
|
||||
'orochi',
|
||||
// Ancient
|
||||
'cyclops',
|
||||
'minotaur',
|
||||
'cerberus',
|
||||
'scylla',
|
||||
'typhon',
|
||||
'charybdis',
|
||||
'medusa',
|
||||
'harpy',
|
||||
'siren',
|
||||
'gorgon',
|
||||
'centaur',
|
||||
'satyr',
|
||||
'triton',
|
||||
'echidna',
|
||||
'lamia',
|
||||
'sphinx',
|
||||
// Cosmic
|
||||
'nova',
|
||||
'pulsar',
|
||||
'quasar',
|
||||
'nebula',
|
||||
'vortex',
|
||||
'cosmic',
|
||||
'astral',
|
||||
'celestial',
|
||||
'starborn',
|
||||
'solaris',
|
||||
'lunaris',
|
||||
'eclipse',
|
||||
'meteor',
|
||||
'comet',
|
||||
'galaxy',
|
||||
'cosmos'
|
||||
];
|
|
@ -9,18 +9,18 @@ const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'npub-email-cache' });
|
|||
const nip07signer = new NDKNip07Signer();
|
||||
|
||||
export const _ndk = new NDKSvelte({
|
||||
explicitRelayUrls: [
|
||||
'wss://relay.primal.net',
|
||||
'wss://relay.damus.io',
|
||||
'wss://relay.nostr.band',
|
||||
'wss://offchain.pub',
|
||||
'wss://relay.snort.social'
|
||||
],
|
||||
autoConnectUserRelays: true,
|
||||
relayAuthDefaultPolicy: async (r) => true,
|
||||
enableOutboxModel: true,
|
||||
// signer: nip07signer,
|
||||
cacheAdapter: dexieAdapter
|
||||
explicitRelayUrls: [
|
||||
'wss://relay.primal.net',
|
||||
'wss://relay.damus.io',
|
||||
'wss://relay.nostr.band',
|
||||
'wss://offchain.pub',
|
||||
'wss://relay.snort.social'
|
||||
],
|
||||
autoConnectUserRelays: false,
|
||||
relayAuthDefaultPolicy: async (r) => true,
|
||||
enableOutboxModel: true,
|
||||
// signer: nip07signer,
|
||||
cacheAdapter: dexieAdapter
|
||||
});
|
||||
|
||||
if (browser && localStorage.getItem('useNip07')) _ndk.signer = nip07signer;
|
||||
|
@ -33,42 +33,42 @@ if (browser && _ndk.activeUser) activeUser.set(_ndk.activeUser);
|
|||
export const validSortOptions = ['stamps', 'date', 'sender', 'subject'];
|
||||
|
||||
export const baseHue = writable(
|
||||
browser ? parseInt(<string>localStorage.getItem('baseHue')) || 200 : 200
|
||||
browser ? parseInt(<string>localStorage.getItem('baseHue')) || 200 : 200
|
||||
);
|
||||
export const groupByStamps = writable(
|
||||
browser ? localStorage.getItem('groupByStamps') === 'true' : false
|
||||
browser ? localStorage.getItem('groupByStamps') === 'true' : false
|
||||
);
|
||||
export const sortBy = writable(browser ? localStorage.getItem('sortBy') || 'date' : 'date');
|
||||
export const dateFormat = writable(
|
||||
browser ? localStorage.getItem('dateFormat') || 'y-m-d' : 'y-m-d'
|
||||
browser ? localStorage.getItem('dateFormat') || 'y-m-d' : 'y-m-d'
|
||||
);
|
||||
export const timeFormat = writable(
|
||||
browser ? localStorage.getItem('timeFormat') || 'h:m:s' : 'h:m:s'
|
||||
browser ? localStorage.getItem('timeFormat') || 'h:m:s' : 'h:m:s'
|
||||
);
|
||||
|
||||
export let pageTitle = writable('loading');
|
||||
export let pageIcon = writable('');
|
||||
|
||||
if (browser) {
|
||||
baseHue.subscribe((value) => {
|
||||
document.documentElement.style.setProperty('--base-hue', value.toString());
|
||||
localStorage.setItem('baseHue', value.toString());
|
||||
});
|
||||
baseHue.subscribe((value) => {
|
||||
document.documentElement.style.setProperty('--base-hue', value.toString());
|
||||
localStorage.setItem('baseHue', value.toString());
|
||||
});
|
||||
|
||||
groupByStamps.subscribe((value) => {
|
||||
localStorage.setItem('groupByStamps', value.toString());
|
||||
});
|
||||
groupByStamps.subscribe((value) => {
|
||||
localStorage.setItem('groupByStamps', value.toString());
|
||||
});
|
||||
|
||||
sortBy.subscribe((value) => {
|
||||
if (!validSortOptions.includes(value)) value = 'date';
|
||||
localStorage.setItem('sortBy', value);
|
||||
});
|
||||
sortBy.subscribe((value) => {
|
||||
if (!validSortOptions.includes(value)) value = 'date';
|
||||
localStorage.setItem('sortBy', value);
|
||||
});
|
||||
|
||||
dateFormat.subscribe((value) => {
|
||||
localStorage.setItem('dateFormat', value);
|
||||
});
|
||||
dateFormat.subscribe((value) => {
|
||||
localStorage.setItem('dateFormat', value);
|
||||
});
|
||||
|
||||
timeFormat.subscribe((value) => {
|
||||
localStorage.setItem('timeFormat', value);
|
||||
});
|
||||
timeFormat.subscribe((value) => {
|
||||
localStorage.setItem('timeFormat', value);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,182 +3,22 @@
|
|||
activeUser,
|
||||
baseHue,
|
||||
groupByStamps,
|
||||
ndk,
|
||||
pageIcon,
|
||||
pageTitle,
|
||||
sortBy,
|
||||
validSortOptions
|
||||
} from '$lib/stores.svelte';
|
||||
import ColorPicker from '../../components/ColorPicker.svelte';
|
||||
import SettingsLine from '../../components/SettingsLine.svelte';
|
||||
import Checkbox from '../../components/Checkbox.svelte';
|
||||
import Select from '../../components/Select.svelte';
|
||||
import DateFormatBuilder from '../../components/DateFormatBuilder.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import * as nip98 from 'nostr-tools/nip98';
|
||||
import { NDKEvent, type NostrEvent } from '@nostr-dev-kit/ndk';
|
||||
import { PUBLIC_API_BASE_URL } from '$env/static/public';
|
||||
import TimeCountdown from '../../components/TimeCountdown.svelte';
|
||||
import { TokenInfoWithMailSubscriptionDuration } from '@arx/utils';
|
||||
|
||||
let subscribed = $state(false);
|
||||
let hasUnlimitedSubscription = $state(false);
|
||||
let subscriptionTill = $state(new Date(0));
|
||||
let aliases = $state([]);
|
||||
let newAlias = $state('');
|
||||
|
||||
let cashuTokenForBuy = $state('');
|
||||
let tokenInfo = $state();
|
||||
let tokenInfoError = $state('');
|
||||
let buyTimeForNpub = $state('');
|
||||
|
||||
$effect(() => {
|
||||
try {
|
||||
tokenInfo = new TokenInfoWithMailSubscriptionDuration(cashuTokenForBuy);
|
||||
tokenInfoError = '';
|
||||
} catch (e) {
|
||||
tokenInfoError = (e as Error).message;
|
||||
}
|
||||
});
|
||||
|
||||
async function buyTime() {
|
||||
if (!tokenInfo) return;
|
||||
if (tokenInfoError) return;
|
||||
if (!buyTimeForNpub) return;
|
||||
try {
|
||||
const response = await fetch(PUBLIC_API_BASE_URL + '/addTime/' + buyTimeForNpub, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tokenString: cashuTokenForBuy
|
||||
})
|
||||
});
|
||||
if (!response.ok) throw new Error(await response.json().then(r => r.message));
|
||||
reloadSubscriptionStatus();
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadSubscriptionStatus() {
|
||||
const subscriptionStatusURL = PUBLIC_API_BASE_URL + '/subscription/' + $ndk.activeUser.npub;
|
||||
const subscriptionStatus = await fetch(subscriptionStatusURL).then(r => r.json<{
|
||||
subscribed: boolean,
|
||||
subscribedUntil: number | null
|
||||
}>());
|
||||
if (subscriptionStatus.subscribed) {
|
||||
subscribed = true;
|
||||
if (subscriptionStatus.subscribedUntil == null)
|
||||
hasUnlimitedSubscription = true;
|
||||
else {
|
||||
subscriptionTill = new Date(subscriptionStatus.subscribedUntil * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadAliases() {
|
||||
const aliasesURL = PUBLIC_API_BASE_URL + '/aliases/' + $ndk.activeUser.npub;
|
||||
const auth = await nip98.getToken(aliasesURL, 'post', async (e: NostrEvent) => {
|
||||
const event = new NDKEvent($ndk, e);
|
||||
await event.sign();
|
||||
return event.rawEvent();
|
||||
},
|
||||
true
|
||||
);
|
||||
aliases = await fetch(aliasesURL, {
|
||||
headers: {
|
||||
Authorization: auth
|
||||
}
|
||||
}).then(r => r.json());
|
||||
}
|
||||
|
||||
async function addAlias() {
|
||||
const addAliasURL = PUBLIC_API_BASE_URL + '/addAlias';
|
||||
const auth = await nip98.getToken(addAliasURL, 'post', async (e: NostrEvent) => {
|
||||
const event = new NDKEvent($ndk, e);
|
||||
await event.sign();
|
||||
return event.rawEvent();
|
||||
},
|
||||
true
|
||||
);
|
||||
await fetch(addAliasURL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: auth,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
alias: newAlias
|
||||
})
|
||||
}).then(r => r.json()).then(console.log);
|
||||
newAlias = '';
|
||||
await reloadAliases();
|
||||
alert('Alias added');
|
||||
}
|
||||
|
||||
function randomAlias() {
|
||||
const adjectives = [
|
||||
// Colors
|
||||
'crimson', 'azure', 'golden', 'silver', 'emerald', 'violet', 'cobalt', 'scarlet',
|
||||
'obsidian', 'jade', 'amber', 'coral', 'indigo', 'sapphire', 'ruby', 'onyx',
|
||||
// Elements
|
||||
'frost', 'flame', 'storm', 'thunder', 'crystal', 'shadow', 'lunar', 'solar',
|
||||
'plasma', 'terra', 'aether', 'void', 'cosmic', 'astral', 'nebula', 'nova',
|
||||
// Power
|
||||
'mega', 'ultra', 'hyper', 'super', 'prime', 'apex', 'elite', 'omega',
|
||||
'alpha', 'delta', 'sigma', 'gamma', 'beta', 'epsilon', 'zeta', 'theta',
|
||||
// Tech
|
||||
'cyber', 'techno', 'digital', 'binary', 'neural', 'crypto', 'matrix', 'vector',
|
||||
'quantum', 'nano', 'laser', 'cyber', 'data', 'pixel', 'sonic', 'hyper',
|
||||
// Mystical
|
||||
'mystic', 'arcane', 'ethereal', 'divine', 'phantom', 'spirit', 'ancient', 'chaos',
|
||||
'astral', 'eldritch', 'occult', 'mythic', 'sacred', 'cursed', 'blessed', 'doom',
|
||||
// Nature
|
||||
'savage', 'primal', 'feral', 'wild', 'fierce', 'rapid', 'swift', 'silent',
|
||||
'deadly', 'stealth', 'shadow', 'night', 'dark', 'light', 'bright', 'dawn',
|
||||
// Epic
|
||||
'epic', 'legendary', 'mythic', 'eternal', 'immortal', 'infinite', 'supreme', 'ultimate',
|
||||
'grand', 'mighty', 'noble', 'royal', 'heroic', 'valor', 'glory', 'honor'
|
||||
];
|
||||
|
||||
const animals = [
|
||||
// Classic Predators
|
||||
'wolf', 'tiger', 'lion', 'eagle', 'hawk', 'bear', 'panther', 'falcon',
|
||||
'jaguar', 'leopard', 'lynx', 'cobra', 'viper', 'python', 'raptor', 'shark',
|
||||
// Mythical Dragons
|
||||
'dragon', 'wyvern', 'drake', 'wyrm', 'hydra', 'basilisk', 'tiamat', 'ryuu',
|
||||
'fafnir', 'bahamut', 'ryu', 'draco', 'naga', 'ouroboros', 'lindworm', 'lung',
|
||||
// Fantasy
|
||||
'phoenix', 'griffin', 'unicorn', 'pegasus', 'chimera', 'manticore', 'sphinx', 'kraken',
|
||||
'behemoth', 'leviathan', 'titan', 'giant', 'colossus', 'golem', 'gargoyle', 'djinn',
|
||||
// Norse
|
||||
'fenrir', 'jormungandr', 'sleipnir', 'valkyrie', 'einherjar', 'huginn', 'muninn', 'garmr',
|
||||
'nidhogg', 'ratatoskr', 'hraesvelgr', 'gullinkambi', 'eikthyrnir', 'duneyrr', 'dvalinn', 'dainn',
|
||||
// Eastern
|
||||
'kitsune', 'kirin', 'byakko', 'suzaku', 'genbu', 'seiryu', 'oni', 'tengu',
|
||||
'raiju', 'baku', 'nekomata', 'tanuki', 'kappa', 'tsukumogami', 'yokai', 'orochi',
|
||||
// Ancient
|
||||
'cyclops', 'minotaur', 'cerberus', 'scylla', 'typhon', 'charybdis', 'medusa', 'harpy',
|
||||
'siren', 'gorgon', 'centaur', 'satyr', 'triton', 'echidna', 'lamia', 'sphinx',
|
||||
// Cosmic
|
||||
'nova', 'pulsar', 'quasar', 'nebula', 'vortex', 'cosmic', 'astral', 'celestial',
|
||||
'starborn', 'solaris', 'lunaris', 'eclipse', 'meteor', 'comet', 'galaxy', 'cosmos'
|
||||
];
|
||||
|
||||
const adjective = adjectives[Math.floor(Math.random() * adjectives.length)];
|
||||
const animal = animals[Math.floor(Math.random() * animals.length)];
|
||||
const digits = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
|
||||
|
||||
newAlias = `${adjective}-${animal}${digits}`;
|
||||
}
|
||||
import Checkbox from '../../components/Checkbox.svelte';
|
||||
import ColorPicker from '../../components/ColorPicker.svelte';
|
||||
import DateFormatBuilder from '../../components/DateFormatBuilder.svelte';
|
||||
import Select from '../../components/Select.svelte';
|
||||
import SettingsLine from './SettingsLine.svelte';
|
||||
import SubscriptionSettings from './SubscriptionSettings.svelte';
|
||||
|
||||
onMount(async () => {
|
||||
$pageTitle = 'Settings';
|
||||
$pageIcon = 'si:settings-cute-line';
|
||||
await reloadAliases();
|
||||
await reloadSubscriptionStatus();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -187,78 +27,19 @@
|
|||
</SettingsLine>
|
||||
|
||||
<SettingsLine title="Email Alias Time Blocks">
|
||||
<p>
|
||||
npub.email offers email aliases that connect to your nostr account, converting incoming emails into Letters.<br />
|
||||
These aliases can also serve as your nip 05 identifier.
|
||||
</p>
|
||||
|
||||
<h3>Pricing:</h3>
|
||||
|
||||
<ul>
|
||||
<li><b>210 sats</b> per day</li>
|
||||
<li>Minimum purchase: <b>21 sats</b> (2.4 hours)</li>
|
||||
<li>Flexible duration: Purchase any length of time you need</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Purchase time blocks to activate your email alias service for yourself or gift them to another user. Once the time
|
||||
expires, you'll need to purchase additional time to continue using the service. Note: emails received while the
|
||||
service is inactive will not be processed.
|
||||
</p>
|
||||
|
||||
<SettingsLine title="Available time">
|
||||
{#if hasUnlimitedSubscription}
|
||||
<h4 style:text-align="center" style:width="100%">You are officially awesome!</h4>
|
||||
<p>
|
||||
The Arx team has granted you an unlimited subscription to npub.email for your valuable contributions to Arx,
|
||||
nostr or bitcoin. <br />
|
||||
Keep up your great work and thank you!
|
||||
</p>
|
||||
{:else if subscribed && subscriptionTill.getTime() > 1000}
|
||||
Your subscription will end in: <br />
|
||||
<TimeCountdown bind:time={subscriptionTill} />
|
||||
{:else}
|
||||
You are not currently subscribed to npub.email
|
||||
{/if}
|
||||
</SettingsLine>
|
||||
|
||||
<SettingsLine title="Buy time">
|
||||
{#if cashuTokenForBuy !== ''}
|
||||
{#if tokenInfoError}
|
||||
<p class="error">{tokenInfoError}</p>
|
||||
{:else}
|
||||
<h4>{tokenInfo.amount} sats</h4>
|
||||
|
||||
<TimeCountdown time={new Date(Date.now() + tokenInfo.duration * 1000)} isStopped />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<input bind:value={cashuTokenForBuy} placeholder="Enter cashu token" type="text" />
|
||||
<h4>Buy for:</h4>
|
||||
<input bind:value={buyTimeForNpub} placeholder="Enter npub" type="text" />
|
||||
<button onclick={() => buyTimeForNpub = $ndk.activeUser.npub}>Set to your own</button>
|
||||
<button onclick={buyTime}>Buy</button>
|
||||
</SettingsLine>
|
||||
|
||||
{#if subscribed}
|
||||
<SettingsLine title="Your aliases">
|
||||
{#each aliases as alias}
|
||||
<code>{alias}@npub.email</code>
|
||||
{:else}
|
||||
<p>No aliases yet</p>
|
||||
{/each}
|
||||
|
||||
<input bind:value={newAlias} placeholder="Enter alias" type="text" />
|
||||
<button onclick={randomAlias}>Random</button>
|
||||
<button onclick={addAlias}>Add</button>
|
||||
</SettingsLine>
|
||||
{/if}
|
||||
<SubscriptionSettings />
|
||||
</SettingsLine>
|
||||
|
||||
<SettingsLine title="Sorting and Grouping">
|
||||
<Checkbox bind:checked={$groupByStamps} label="Group by stamps" />
|
||||
<Select bind:value={$sortBy} label="Sort by"
|
||||
options={validSortOptions.map(o => ({ value: o, label: o.charAt(0).toUpperCase() + o.slice(1).toLowerCase() }))} />
|
||||
<Select
|
||||
bind:value={$sortBy}
|
||||
label="Sort by"
|
||||
options={validSortOptions.map((o) => ({
|
||||
value: o,
|
||||
label: o.charAt(0).toUpperCase() + o.slice(1).toLowerCase()
|
||||
}))}
|
||||
/>
|
||||
</SettingsLine>
|
||||
|
||||
<SettingsLine title="Date and Time">
|
||||
|
|
124
src/routes/settings/AliasSettings.svelte
Normal file
124
src/routes/settings/AliasSettings.svelte
Normal file
|
@ -0,0 +1,124 @@
|
|||
<script lang="ts">
|
||||
import { PUBLIC_API_BASE_URL } from '$env/static/public';
|
||||
import * as nip98 from 'nostr-tools/nip98';
|
||||
import { NDKEvent, type NostrEvent } from '@nostr-dev-kit/ndk';
|
||||
import { adjectives, animals } from '$lib/random';
|
||||
import { onMount } from 'svelte';
|
||||
import { ndk } from '$lib/stores.svelte';
|
||||
import SettingsLine from './SettingsLine.svelte';
|
||||
import IconButton from '../../components/IconButton.svelte';
|
||||
|
||||
async function reloadAliases() {
|
||||
const aliasesURL = PUBLIC_API_BASE_URL + '/aliases/' + $ndk.activeUser.npub;
|
||||
const auth = await nip98.getToken(
|
||||
aliasesURL,
|
||||
'post',
|
||||
async (e: NostrEvent) => {
|
||||
const event = new NDKEvent($ndk, e);
|
||||
await event.sign();
|
||||
return event.rawEvent();
|
||||
},
|
||||
true
|
||||
);
|
||||
aliases = await fetch(aliasesURL, {
|
||||
headers: {
|
||||
Authorization: auth
|
||||
}
|
||||
}).then((r) => r.json());
|
||||
}
|
||||
|
||||
async function addAlias() {
|
||||
const addAliasURL = PUBLIC_API_BASE_URL + '/addAlias';
|
||||
const auth = await nip98.getToken(
|
||||
addAliasURL,
|
||||
'post',
|
||||
async (e: NostrEvent) => {
|
||||
const event = new NDKEvent($ndk, e);
|
||||
await event.sign();
|
||||
return event.rawEvent();
|
||||
},
|
||||
true
|
||||
);
|
||||
await fetch(addAliasURL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: auth,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
alias: newAlias
|
||||
})
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then(console.log);
|
||||
newAlias = '';
|
||||
await reloadAliases();
|
||||
alert('Alias added');
|
||||
}
|
||||
|
||||
async function deleteAlias(alias: string) {
|
||||
if (!confirm(`Delete ${alias}?`)) return;
|
||||
const deleteAliasURL = PUBLIC_API_BASE_URL + '/alias/' + alias;
|
||||
const auth = await nip98.getToken(
|
||||
deleteAliasURL,
|
||||
'DELETE',
|
||||
async (e: NostrEvent) => {
|
||||
const event = new NDKEvent($ndk, e);
|
||||
await event.sign();
|
||||
return event.rawEvent();
|
||||
},
|
||||
true
|
||||
);
|
||||
await fetch(deleteAliasURL, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: auth
|
||||
}
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then(console.log);
|
||||
await reloadAliases();
|
||||
}
|
||||
|
||||
function randomAlias() {
|
||||
const adjective = adjectives[Math.floor(Math.random() * adjectives.length)];
|
||||
const animal = animals[Math.floor(Math.random() * animals.length)];
|
||||
const digits = Math.floor(Math.random() * 10000)
|
||||
.toString()
|
||||
.padStart(4, '0');
|
||||
|
||||
newAlias = `${adjective}-${animal}${digits}`;
|
||||
}
|
||||
|
||||
onMount(reloadAliases);
|
||||
|
||||
let aliases = $state([]);
|
||||
let newAlias = $state('');
|
||||
</script>
|
||||
|
||||
<SettingsLine title="Your aliases">
|
||||
<table>
|
||||
<tbody>
|
||||
{#each aliases as alias}
|
||||
<tr>
|
||||
<td>
|
||||
<code>{alias}@npub.email</code>
|
||||
</td>
|
||||
<td>
|
||||
<IconButton
|
||||
icon="icon-park-twotone:delete"
|
||||
text="Delete"
|
||||
onclick={() => deleteAlias(alias)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
{:else}
|
||||
<tr><td>No aliases yet</td></tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<input bind:value={newAlias} placeholder="Enter alias" type="text" />
|
||||
<button onclick={randomAlias}>Random</button>
|
||||
<button onclick={addAlias}>Add</button>
|
||||
</SettingsLine>
|
62
src/routes/settings/BuyTime.svelte
Normal file
62
src/routes/settings/BuyTime.svelte
Normal file
|
@ -0,0 +1,62 @@
|
|||
<script lang="ts">
|
||||
import { PUBLIC_API_BASE_URL } from '$env/static/public';
|
||||
import { TokenInfoWithMailSubscriptionDuration } from '@arx/utils';
|
||||
import TimeCountdown from '../../components/TimeCountdown.svelte';
|
||||
import { ndk } from '$lib/stores.svelte';
|
||||
|
||||
let { onReloadNeeded } = $props<{
|
||||
onReloadNeeded: () => void;
|
||||
}>();
|
||||
|
||||
let cashuTokenForBuy = $state('');
|
||||
let tokenInfo = $state<TokenInfoWithMailSubscriptionDuration | null>(null);
|
||||
let tokenInfoError = $state('');
|
||||
let buyTimeForNpub = $state('');
|
||||
|
||||
async function buyTime() {
|
||||
if (!tokenInfo) return;
|
||||
if (tokenInfoError) return;
|
||||
if (!buyTimeForNpub) return;
|
||||
try {
|
||||
const response = await fetch(PUBLIC_API_BASE_URL + '/addTime/' + buyTimeForNpub, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tokenString: cashuTokenForBuy
|
||||
})
|
||||
});
|
||||
if (!response.ok) throw new Error(await response.json().then((r) => r.message));
|
||||
onReloadNeeded();
|
||||
alert(`Time added!`);
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
try {
|
||||
tokenInfo = new TokenInfoWithMailSubscriptionDuration(cashuTokenForBuy);
|
||||
tokenInfoError = '';
|
||||
} catch (e) {
|
||||
tokenInfoError = (e as Error).message;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if cashuTokenForBuy !== ''}
|
||||
{#if tokenInfoError}
|
||||
<p class="error">{tokenInfoError}</p>
|
||||
{:else if tokenInfo}
|
||||
<h4>{tokenInfo.amount} sats</h4>
|
||||
|
||||
<TimeCountdown time={new Date(Date.now() + tokenInfo.duration * 1000)} isStopped />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<input bind:value={cashuTokenForBuy} placeholder="Enter cashu token" type="text" />
|
||||
<h4>Buy for:</h4>
|
||||
<input bind:value={buyTimeForNpub} placeholder="Enter npub" type="text" />
|
||||
<button onclick={() => (buyTimeForNpub = $ndk.activeUser.npub)}>Set to your own</button>
|
||||
<button onclick={buyTime}>Buy</button>
|
71
src/routes/settings/SubscriptionSettings.svelte
Normal file
71
src/routes/settings/SubscriptionSettings.svelte
Normal file
|
@ -0,0 +1,71 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import SettingsLine from './SettingsLine.svelte';
|
||||
import TimeCountdown from '../../components/TimeCountdown.svelte';
|
||||
import AliasSettings from './AliasSettings.svelte';
|
||||
import BuyTime from './BuyTime.svelte';
|
||||
import { PUBLIC_API_BASE_URL } from '$env/static/public';
|
||||
import { ndk } from '$lib/stores.svelte';
|
||||
|
||||
let subscribed = $state(false);
|
||||
let hasUnlimitedSubscription = $state(false);
|
||||
let subscriptionTill = $state(new Date(0));
|
||||
|
||||
async function reloadSubscriptionStatus() {
|
||||
const subscriptionStatusURL = PUBLIC_API_BASE_URL + '/subscription/' + $ndk.activeUser.npub;
|
||||
const subscriptionStatus = await fetch(subscriptionStatusURL).then((r) => r.json());
|
||||
if (subscriptionStatus.subscribed) {
|
||||
subscribed = true;
|
||||
if (subscriptionStatus.subscribedUntil == null) hasUnlimitedSubscription = true;
|
||||
else {
|
||||
subscriptionTill = new Date(subscriptionStatus.subscribedUntil * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMount(reloadSubscriptionStatus);
|
||||
</script>
|
||||
|
||||
<p>
|
||||
npub.email offers email aliases that connect to your nostr account, converting incoming emails
|
||||
into Letters.<br />
|
||||
These aliases can also serve as your nip 05 identifier.
|
||||
</p>
|
||||
|
||||
<h3>Pricing:</h3>
|
||||
|
||||
<ul>
|
||||
<li><b>210 sats</b> per day</li>
|
||||
<li>Minimum purchase: <b>21 sats</b> (2.4 hours)</li>
|
||||
<li>Flexible duration: Purchase any length of time you need</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Purchase time blocks to activate your email alias service for yourself or gift them to another
|
||||
user. Once the time expires, you'll need to purchase additional time to continue using the
|
||||
service. Note: emails received while the service is inactive will not be processed.
|
||||
</p>
|
||||
|
||||
<SettingsLine title="Available time">
|
||||
{#if hasUnlimitedSubscription}
|
||||
<h4 style:text-align="center" style:width="100%">You are officially awesome!</h4>
|
||||
<p>
|
||||
The Arx team has granted you an unlimited subscription to npub.email for your valuable
|
||||
contributions to Arx, nostr or bitcoin. <br />
|
||||
Keep up your great work and thank you!
|
||||
</p>
|
||||
{:else if subscribed && subscriptionTill.getTime() > 1000}
|
||||
Your subscription will end in: <br />
|
||||
<TimeCountdown bind:time={subscriptionTill} />
|
||||
{:else}
|
||||
You are not currently subscribed to npub.email
|
||||
{/if}
|
||||
</SettingsLine>
|
||||
|
||||
<SettingsLine title="Buy time">
|
||||
<BuyTime onReloadNeeded={reloadSubscriptionStatus} />
|
||||
</SettingsLine>
|
||||
|
||||
{#if subscribed}
|
||||
<AliasSettings />
|
||||
{/if}
|
Loading…
Add table
Reference in a new issue