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",
|
"@iconify/svelte": "^4.0.2",
|
||||||
"@sveltejs/adapter-node": "^5.2.9",
|
"@sveltejs/adapter-node": "^5.2.9",
|
||||||
"@sveltejs/adapter-auto": "^3.3.1",
|
"@sveltejs/adapter-auto": "^3.3.1",
|
||||||
"@sveltejs/kit": "^2.8.5",
|
"@sveltejs/kit": "^2.9.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.2",
|
"@sveltejs/vite-plugin-svelte": "^4.0.2",
|
||||||
"prettier": "^3.4.1",
|
"prettier": "^3.4.1",
|
||||||
"prettier-plugin-svelte": "^3.3.2",
|
"prettier-plugin-svelte": "^3.3.2",
|
||||||
"svelte": "^5.2.10",
|
"svelte": "^5.2.11",
|
||||||
"svelte-check": "^4.1.0",
|
"svelte-check": "^4.1.0",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^5.4.11"
|
"vite": "^5.4.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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",
|
"@gandlaf21/bc-ur": "^1.1.12",
|
||||||
"@nostr-dev-kit/ndk": "^2.10.7",
|
"@nostr-dev-kit/ndk": "^2.10.7",
|
||||||
"@nostr-dev-kit/ndk-cache-dexie": "^2.5.8",
|
"@nostr-dev-kit/ndk-cache-dexie": "^2.5.8",
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
<Icon icon="ph:user" />
|
<Icon icon="ph:user" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if npub !== displayString && !emailAddress}
|
{#if npub !== displayString}
|
||||||
<Tooltip position="bottom" content={npub}>
|
<Tooltip position="bottom" content={npub}>
|
||||||
<span class="user-text">{displayString}</span>
|
<span class="user-text">{displayString}</span>
|
||||||
</Tooltip>
|
</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();
|
const nip07signer = new NDKNip07Signer();
|
||||||
|
|
||||||
export const _ndk = new NDKSvelte({
|
export const _ndk = new NDKSvelte({
|
||||||
explicitRelayUrls: [
|
explicitRelayUrls: [
|
||||||
'wss://relay.primal.net',
|
'wss://relay.primal.net',
|
||||||
'wss://relay.damus.io',
|
'wss://relay.damus.io',
|
||||||
'wss://relay.nostr.band',
|
'wss://relay.nostr.band',
|
||||||
'wss://offchain.pub',
|
'wss://offchain.pub',
|
||||||
'wss://relay.snort.social'
|
'wss://relay.snort.social'
|
||||||
],
|
],
|
||||||
autoConnectUserRelays: true,
|
autoConnectUserRelays: false,
|
||||||
relayAuthDefaultPolicy: async (r) => true,
|
relayAuthDefaultPolicy: async (r) => true,
|
||||||
enableOutboxModel: true,
|
enableOutboxModel: true,
|
||||||
// signer: nip07signer,
|
// signer: nip07signer,
|
||||||
cacheAdapter: dexieAdapter
|
cacheAdapter: dexieAdapter
|
||||||
});
|
});
|
||||||
|
|
||||||
if (browser && localStorage.getItem('useNip07')) _ndk.signer = nip07signer;
|
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 validSortOptions = ['stamps', 'date', 'sender', 'subject'];
|
||||||
|
|
||||||
export const baseHue = writable(
|
export const baseHue = writable(
|
||||||
browser ? parseInt(<string>localStorage.getItem('baseHue')) || 200 : 200
|
browser ? parseInt(<string>localStorage.getItem('baseHue')) || 200 : 200
|
||||||
);
|
);
|
||||||
export const groupByStamps = writable(
|
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 sortBy = writable(browser ? localStorage.getItem('sortBy') || 'date' : 'date');
|
||||||
export const dateFormat = writable(
|
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(
|
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 pageTitle = writable('loading');
|
||||||
export let pageIcon = writable('');
|
export let pageIcon = writable('');
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
baseHue.subscribe((value) => {
|
baseHue.subscribe((value) => {
|
||||||
document.documentElement.style.setProperty('--base-hue', value.toString());
|
document.documentElement.style.setProperty('--base-hue', value.toString());
|
||||||
localStorage.setItem('baseHue', value.toString());
|
localStorage.setItem('baseHue', value.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
groupByStamps.subscribe((value) => {
|
groupByStamps.subscribe((value) => {
|
||||||
localStorage.setItem('groupByStamps', value.toString());
|
localStorage.setItem('groupByStamps', value.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
sortBy.subscribe((value) => {
|
sortBy.subscribe((value) => {
|
||||||
if (!validSortOptions.includes(value)) value = 'date';
|
if (!validSortOptions.includes(value)) value = 'date';
|
||||||
localStorage.setItem('sortBy', value);
|
localStorage.setItem('sortBy', value);
|
||||||
});
|
});
|
||||||
|
|
||||||
dateFormat.subscribe((value) => {
|
dateFormat.subscribe((value) => {
|
||||||
localStorage.setItem('dateFormat', value);
|
localStorage.setItem('dateFormat', value);
|
||||||
});
|
});
|
||||||
|
|
||||||
timeFormat.subscribe((value) => {
|
timeFormat.subscribe((value) => {
|
||||||
localStorage.setItem('timeFormat', value);
|
localStorage.setItem('timeFormat', value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,182 +3,22 @@
|
||||||
activeUser,
|
activeUser,
|
||||||
baseHue,
|
baseHue,
|
||||||
groupByStamps,
|
groupByStamps,
|
||||||
ndk,
|
|
||||||
pageIcon,
|
pageIcon,
|
||||||
pageTitle,
|
pageTitle,
|
||||||
sortBy,
|
sortBy,
|
||||||
validSortOptions
|
validSortOptions
|
||||||
} from '$lib/stores.svelte';
|
} 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 { onMount } from 'svelte';
|
||||||
import * as nip98 from 'nostr-tools/nip98';
|
import Checkbox from '../../components/Checkbox.svelte';
|
||||||
import { NDKEvent, type NostrEvent } from '@nostr-dev-kit/ndk';
|
import ColorPicker from '../../components/ColorPicker.svelte';
|
||||||
import { PUBLIC_API_BASE_URL } from '$env/static/public';
|
import DateFormatBuilder from '../../components/DateFormatBuilder.svelte';
|
||||||
import TimeCountdown from '../../components/TimeCountdown.svelte';
|
import Select from '../../components/Select.svelte';
|
||||||
import { TokenInfoWithMailSubscriptionDuration } from '@arx/utils';
|
import SettingsLine from './SettingsLine.svelte';
|
||||||
|
import SubscriptionSettings from './SubscriptionSettings.svelte';
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
$pageTitle = 'Settings';
|
$pageTitle = 'Settings';
|
||||||
$pageIcon = 'si:settings-cute-line';
|
$pageIcon = 'si:settings-cute-line';
|
||||||
await reloadAliases();
|
|
||||||
await reloadSubscriptionStatus();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -187,78 +27,19 @@
|
||||||
</SettingsLine>
|
</SettingsLine>
|
||||||
|
|
||||||
<SettingsLine title="Email Alias Time Blocks">
|
<SettingsLine title="Email Alias Time Blocks">
|
||||||
<p>
|
<SubscriptionSettings />
|
||||||
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}
|
|
||||||
</SettingsLine>
|
</SettingsLine>
|
||||||
|
|
||||||
<SettingsLine title="Sorting and Grouping">
|
<SettingsLine title="Sorting and Grouping">
|
||||||
<Checkbox bind:checked={$groupByStamps} label="Group by stamps" />
|
<Checkbox bind:checked={$groupByStamps} label="Group by stamps" />
|
||||||
<Select bind:value={$sortBy} label="Sort by"
|
<Select
|
||||||
options={validSortOptions.map(o => ({ value: o, label: o.charAt(0).toUpperCase() + o.slice(1).toLowerCase() }))} />
|
bind:value={$sortBy}
|
||||||
|
label="Sort by"
|
||||||
|
options={validSortOptions.map((o) => ({
|
||||||
|
value: o,
|
||||||
|
label: o.charAt(0).toUpperCase() + o.slice(1).toLowerCase()
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
</SettingsLine>
|
</SettingsLine>
|
||||||
|
|
||||||
<SettingsLine title="Date and Time">
|
<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