aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/components/About.svelte5
-rw-r--r--src/components/Account.svelte53
-rw-r--r--src/components/Balance.svelte6
-rw-r--r--src/components/Items.svelte209
-rw-r--r--src/lib/english.ts2048
-rw-r--r--src/lib/nostr.ts2
-rw-r--r--src/lib/store.ts1
-rw-r--r--src/locales/en.json4
-rw-r--r--src/routes/(app)/[username]/(profile)/+layout.svelte66
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte282
-rw-r--r--src/routes/(app)/[username]/account/setup/+page.svelte64
-rw-r--r--src/routes/(app)/[username]/account/setup/step2/+page.svelte46
12 files changed, 440 insertions, 2346 deletions
diff --git a/src/components/About.svelte b/src/components/About.svelte
index 1e60db72..01ff4c7c 100644
--- a/src/components/About.svelte
+++ b/src/components/About.svelte
@@ -91,14 +91,15 @@
{@html $t("about.locations.description")}
</p>
<!-- <LocationsMap {locations} /> -->
+
<a href="/map">
<div class="relative">
<img src="/map.png" class="mx-auto" />
<button
- class="bg-black text-white border rounded-full px-6 py-2 font-bold hover:opacity-80 absolute left-1/2 top-1/2 text-2xl"
+ class="bg-black text-white border border-4 rounded-full px-6 py-2 font-bold hover:bg-secondary absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 text-2xl"
>
- View map
+ {$t("about.locations.viewMap")}
</button>
</div>
</a>
diff --git a/src/components/Account.svelte b/src/components/Account.svelte
new file mode 100644
index 00000000..5286c4d3
--- /dev/null
+++ b/src/components/Account.svelte
@@ -0,0 +1,53 @@
+<script>
+ import Icon from "$comp/Icon.svelte";
+ import Balance from "$comp/Balance.svelte";
+ import { t } from "$lib/translations";
+
+ export let user, rate, account;
+ let { balance, setup, type } = account;
+</script>
+
+<div class="border rounded-xl shadow p-4 space-y-5">
+ <div class="flex justify-center lg:justify-start">
+ <Balance {balance} {user} {rate} />
+ <div class="ml-auto text-secondary">{type}</div>
+ </div>
+
+ <div class="flex flex-wrap gap-3 justify-center w-full text-xl">
+ {#if setup}
+ <a href={`/send`} class="contents grow">
+ <button
+ type="button"
+ class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow"
+ >
+ <div class="mx-auto flex gap-2">
+ <Icon icon="send" style="w-8" />
+ <div class="my-auto">{$t("user.dashboard.send")}</div>
+ </div>
+ </button>
+ </a>
+
+ <a href={`/${user.username}/receive`} class="contents">
+ <button
+ class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow"
+ >
+ <div class="mx-auto flex gap-2">
+ <Icon icon="receive" style="w-8" />
+ <div class="my-auto">{$t("user.dashboard.receive")}</div>
+ </div>
+ </button>
+ </a>
+ {:else}
+ <a href={`/${user.username}/account/setup`} class="contents">
+ <button
+ class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow"
+ >
+ <div class="mx-auto flex gap-2">
+ <Icon icon="settings" style="w-8" />
+ <div class="my-auto">{$t("user.dashboard.setup")}</div>
+ </div>
+ </button>
+ </a>
+ {/if}
+ </div>
+</div>
diff --git a/src/components/Balance.svelte b/src/components/Balance.svelte
index 4d5ac6bd..51c56555 100644
--- a/src/components/Balance.svelte
+++ b/src/components/Balance.svelte
@@ -6,7 +6,7 @@
import { t } from "$lib/translations";
let show;
- export let user, rate;
+ export let user, rate, balance;
</script>
{#if show && user.haspin && $pin?.length !== 6}
@@ -27,12 +27,12 @@
{#if isNaN(rate)}
<div class="text-gray-200">&mdash;</div>
{:else}
- {f((user.balance * rate) / sats, user.currency)}
+ {f((balance * rate) / sats, user.currency)}
{/if}
</div>
<div class="text-secondary text-2xl">
- ⚡️{s(user.balance)}
+ ⚡️{s(balance)}
</div>
{/if}
</div>
diff --git a/src/components/Items.svelte b/src/components/Items.svelte
new file mode 100644
index 00000000..f449215c
--- /dev/null
+++ b/src/components/Items.svelte
@@ -0,0 +1,209 @@
+<script>
+ import { btc, f, sat, post } from "$lib/utils";
+ import { browser } from "$app/environment";
+ import { goto, invalidate } from "$app/navigation";
+ import { fade } from "svelte/transition";
+ import { cubicIn } from "svelte/easing";
+ import { flip } from "svelte/animate";
+ import {
+ dndzone,
+ SHADOW_ITEM_MARKER_PROPERTY_NAME,
+ SOURCES,
+ TRIGGERS,
+ } from "svelte-dnd-action";
+ import DeleteItem from "$comp/DeleteItem.svelte";
+ import Icon from "$comp/Icon.svelte";
+
+ export let subject, user, items, total, rate, currency;
+
+ let flipDurationMs = 200;
+ let dropTargetStyle = {};
+
+ let dragDisabled =
+ subject?.id !== user?.id || (browser && window.innerWidth < 768);
+
+ let deleting;
+ let del = (item) => {
+ deleting = item;
+ };
+
+ function handleConsider(e) {
+ const {
+ items: newItems,
+ info: { source, trigger },
+ } = e.detail;
+ items = newItems;
+ if (source === SOURCES.KEYBOARD && trigger === TRIGGERS.DRAG_STOPPED)
+ reset();
+ }
+
+ async function handleFinalize(e) {
+ const {
+ items: newItems,
+ info: { source },
+ } = e.detail;
+ items = newItems;
+ if (source === SOURCES.POINTER) reset();
+
+ if (subject?.id === user?.id) {
+ await post("/api/items/sort", { items });
+ invalidate("app:items");
+ }
+ }
+
+ function startDrag(e) {
+ e.preventDefault();
+ dragDisabled = false;
+ }
+
+ function handleKeyDown(e) {
+ if ((e.key === "Enter" || e.key === " ") && dragDisabled)
+ dragDisabled = false;
+ }
+
+ $: if (total > 0 && browser) {
+ let payButton = document.querySelector("#payButton");
+ if (payButton) payButton.addEventListener("click", checkout);
+ }
+
+ let checkout = async () => {
+ let invoice = {
+ type: "lightning",
+ amount: btc(total, rate),
+ currency,
+ items: items.filter((i) => i.quantity > 0),
+ prompt: subject.prompt,
+ memoPrompt: subject.memoPrompt,
+ rate,
+ };
+
+ let { id } = await post(`/${subject.username}/invoice`, {
+ invoice,
+ user: { username: subject.username },
+ });
+
+ if (invoice.memoPrompt) {
+ return goto(`/${subject.username}/invoice/${id}/memo`);
+ }
+ goto(`/${subject.username}/invoice/${id}`);
+ };
+
+ let reset = () =>
+ (dragDisabled =
+ user?.username !== subject?.username || window.innerWidth < 768);
+</script>
+
+<svelte:window on:resize={reset} />
+
+{#if deleting}
+ <DeleteItem bind:item={deleting} />
+{/if}
+
+{#if items.length}
+ <h2 class="text-2xl font-bold">Select items</h2>
+{/if}
+<div
+ class="grid sm:grid-cols-2 gap-4 pb-20"
+ use:dndzone={{ items, flipDurationMs, dropTargetStyle, dragDisabled }}
+ on:consider={handleConsider}
+ on:finalize={handleFinalize}
+>
+ {#each items as i (i.id)}
+ <button
+ type="button"
+ class="w-full text-center cursor-pointer hover:opacity-80 mb-auto"
+ on:click={() => i.quantity++}
+ >
+ <div class="relative w-full h-64 overflow-hidden rounded-xl">
+ {#if i[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
+ <div
+ in:fade={{ duration: 200, easing: cubicIn }}
+ class="custom-shadow-item"
+ />
+ {:else if i.image}
+ <img
+ src={`/api/public/${i.image}.webp`}
+ alt={i.name}
+ class="mx-auto object-cover h-full w-full"
+ />
+ {:else}
+ <div
+ class="bg-gradient-to-r from-primary to-gradient mb-4 cursor-pointer hover:opacity-80 w-full h-full"
+ alt="Banner"
+ />
+ {/if}
+ {#if user?.username === subject.username}
+ <div class="flex gap-2 justify-center absolute top-2 right-2">
+ <button type="button" on:click|stopPropagation={() => {}}>
+ <a href={`/${user.username}/items/${i.id}`}>
+ <button
+ class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
+ >
+ <Icon icon="edit" style="w-8 mx-auto invert" />
+ </button>
+ </a>
+ </button>
+ <button
+ class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
+ on:click|stopPropagation={() => del(i)}
+ >
+ <Icon icon="trash" style="w-8 mx-auto invert" />
+ </button>
+ <button
+ type="button"
+ on:click|stopPropagation={() => {}}
+ class="sm:hidden"
+ on:mousedown={startDrag}
+ on:touchstart={startDrag}
+ on:keydown={handleKeyDown}
+ >
+ <a href={`/${user.username}/items/${i.id}`}>
+ <button
+ class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
+ >
+ <Icon icon="move" style="w-8 mx-auto invert" />
+ </button>
+ </a>
+ </button>
+ </div>
+ {/if}
+ </div>
+ <div class="bg-white rounded-xl py-2">
+ <div class="flex text-left gap-2">
+ <div class="overflow-hidden space-y-1">
+ <div class="text-2xl break-words">{i.name}</div>
+ <div class="flex gap-2">
+ <div class="font-semibold text-lg">
+ {f(i.price, currency)}
+ </div>
+ <div class="text-secondary my-auto">
+ {sat(btc(i.price, rate))}
+ </div>
+ </div>
+ </div>
+ <div class="flex gap-1 ml-auto mb-auto justify-around tabular-nums">
+ {#if i.quantity}
+ <button
+ type="button"
+ on:click|stopPropagation={() => i.quantity > 0 && i.quantity--}
+ >
+ <Icon icon="minus" style="w-10 min-w-10" />
+ </button>
+ {/if}
+ <div class="text-2xl my-auto text-center">
+ {#if i.quantity}{i.quantity}{:else}&mdash;{/if}
+ </div>
+ {#if i.quantity}
+ <button
+ type="button"
+ on:click|stopPropagation={() => i.quantity++}
+ >
+ <Icon icon="plus" style="w-10 min-w-10" />
+ </button>
+ {/if}
+ </div>
+ </div>
+ </div>
+ </button>
+ {/each}
+</div>
diff --git a/src/lib/english.ts b/src/lib/english.ts
deleted file mode 100644
index 52a0da82..00000000
--- a/src/lib/english.ts
+++ /dev/null
@@ -1,2048 +0,0 @@
-export default `abandon
-ability
-able
-about
-above
-absent
-absorb
-abstract
-absurd
-abuse
-access
-accident
-account
-accuse
-achieve
-acid
-acoustic
-acquire
-across
-act
-action
-actor
-actress
-actual
-adapt
-add
-addict
-address
-adjust
-admit
-adult
-advance
-advice
-aerobic
-affair
-afford
-afraid
-again
-age
-agent
-agree
-ahead
-aim
-air
-airport
-aisle
-alarm
-album
-alcohol
-alert
-alien
-all
-alley
-allow
-almost
-alone
-alpha
-already
-also
-alter
-always
-amateur
-amazing
-among
-amount
-amused
-analyst
-anchor
-ancient
-anger
-angle
-angry
-animal
-ankle
-announce
-annual
-another
-answer
-antenna
-antique
-anxiety
-any
-apart
-apology
-appear
-apple
-approve
-april
-arch
-arctic
-area
-arena
-argue
-arm
-armed
-armor
-army
-around
-arrange
-arrest
-arrive
-arrow
-art
-artefact
-artist
-artwork
-ask
-aspect
-assault
-asset
-assist
-assume
-asthma
-athlete
-atom
-attack
-attend
-attitude
-attract
-auction
-audit
-august
-aunt
-author
-auto
-autumn
-average
-avocado
-avoid
-awake
-aware
-away
-awesome
-awful
-awkward
-axis
-baby
-bachelor
-bacon
-badge
-bag
-balance
-balcony
-ball
-bamboo
-banana
-banner
-bar
-barely
-bargain
-barrel
-base
-basic
-basket
-battle
-beach
-bean
-beauty
-because
-become
-beef
-before
-begin
-behave
-behind
-believe
-below
-belt
-bench
-benefit
-best
-betray
-better
-between
-beyond
-bicycle
-bid
-bike
-bind
-biology
-bird
-birth
-bitter
-black
-blade
-blame
-blanket
-blast
-bleak
-bless
-blind
-blood
-blossom
-blouse
-blue
-blur
-blush
-board
-boat
-body
-boil
-bomb
-bone
-bonus
-book
-boost
-border
-boring
-borrow
-boss
-bottom
-bounce
-box
-boy
-bracket
-brain
-brand
-brass
-brave
-bread
-breeze
-brick
-bridge
-brief
-bright
-bring
-brisk
-broccoli
-broken
-bronze
-broom
-brother
-brown
-brush
-bubble
-buddy
-budget
-buffalo
-build
-bulb
-bulk
-bullet
-bundle
-bunker
-burden
-burger
-burst
-bus
-business
-busy
-butter
-buyer
-buzz
-cabbage
-cabin
-cable
-cactus
-cage
-cake
-call
-calm
-camera
-camp
-can
-canal
-cancel
-candy
-cannon
-canoe
-canvas
-canyon
-capable
-capital
-captain
-car
-carbon
-card
-cargo
-carpet
-carry
-cart
-case
-cash
-casino
-castle
-casual
-cat
-catalog
-catch
-category
-cattle
-caught
-cause
-caution
-cave
-ceiling
-celery
-cement
-census
-century
-cereal
-certain
-chair
-chalk
-champion
-change
-chaos
-chapter
-charge
-chase
-chat
-cheap
-check
-cheese
-chef
-cherry
-chest
-chicken
-chief
-child
-chimney
-choice
-choose
-chronic
-chuckle
-chunk
-churn
-cigar
-cinnamon
-circle
-citizen
-city
-civil
-claim
-clap
-clarify
-claw
-clay
-clean
-clerk
-clever
-click
-client
-cliff
-climb
-clinic
-clip
-clock
-clog
-close
-cloth
-cloud
-clown
-club
-clump
-cluster
-clutch
-coach
-coast
-coconut
-code
-coffee
-coil
-coin
-collect
-color
-column
-combine
-come
-comfort
-comic
-common
-company
-concert
-conduct
-confirm
-congress
-connect
-consider
-control
-convince
-cook
-cool
-copper
-copy
-coral
-core
-corn
-correct
-cost
-cotton
-couch
-country
-couple
-course
-cousin
-cover
-coyote
-crack
-cradle
-craft
-cram
-crane
-crash
-crater
-crawl
-crazy
-cream
-credit
-creek
-crew
-cricket
-crime
-crisp
-critic
-crop
-cross
-crouch
-crowd
-crucial
-cruel
-cruise
-crumble
-crunch
-crush
-cry
-crystal
-cube
-culture
-cup
-cupboard
-curious
-current
-curtain
-curve
-cushion
-custom
-cute
-cycle
-dad
-damage
-damp
-dance
-danger
-daring
-dash
-daughter
-dawn
-day
-deal
-debate
-debris
-decade
-december
-decide
-decline
-decorate
-decrease
-deer
-defense
-define
-defy
-degree
-delay
-deliver
-demand
-demise
-denial
-dentist
-deny
-depart
-depend
-deposit
-depth
-deputy
-derive
-describe
-desert
-design
-desk
-despair
-destroy
-detail
-detect
-develop
-device
-devote
-diagram
-dial
-diamond
-diary
-dice
-diesel
-diet
-differ
-digital
-dignity
-dilemma
-dinner
-dinosaur
-direct
-dirt
-disagree
-discover
-disease
-dish
-dismiss
-disorder
-display
-distance
-divert
-divide
-divorce
-dizzy
-doctor
-document
-dog
-doll
-dolphin
-domain
-donate
-donkey
-donor
-door
-dose
-double
-dove
-draft
-dragon
-drama
-drastic
-draw
-dream
-dress
-drift
-drill
-drink
-drip
-drive
-drop
-drum
-dry
-duck
-dumb
-dune
-during
-dust
-dutch
-duty
-dwarf
-dynamic
-eager
-eagle
-early
-earn
-earth
-easily
-east
-easy
-echo
-ecology
-economy
-edge
-edit
-educate
-effort
-egg
-eight
-either
-elbow
-elder
-electric
-elegant
-element
-elephant
-elevator
-elite
-else
-embark
-embody
-embrace
-emerge
-emotion
-employ
-empower
-empty
-enable
-enact
-end
-endless
-endorse
-enemy
-energy
-enforce
-engage
-engine
-enhance
-enjoy
-enlist
-enough
-enrich
-enroll
-ensure
-enter
-entire
-entry
-envelope
-episode
-equal
-equip
-era
-erase
-erode
-erosion
-error
-erupt
-escape
-essay
-essence
-estate
-eternal
-ethics
-evidence
-evil
-evoke
-evolve
-exact
-example
-excess
-exchange
-excite
-exclude
-excuse
-execute
-exercise
-exhaust
-exhibit
-exile
-exist
-exit
-exotic
-expand
-expect
-expire
-explain
-expose
-express
-extend
-extra
-eye
-eyebrow
-fabric
-face
-faculty
-fade
-faint
-faith
-fall
-false
-fame
-family
-famous
-fan
-fancy
-fantasy
-farm
-fashion
-fat
-fatal
-father
-fatigue
-fault
-favorite
-feature
-february
-federal
-fee
-feed
-feel
-female
-fence
-festival
-fetch
-fever
-few
-fiber
-fiction
-field
-figure
-file
-film
-filter
-final
-find
-fine
-finger
-finish
-fire
-firm
-first
-fiscal
-fish
-fit
-fitness
-fix
-flag
-flame
-flash
-flat
-flavor
-flee
-flight
-flip
-float
-flock
-floor
-flower
-fluid
-flush
-fly
-foam
-focus
-fog
-foil
-fold
-follow
-food
-foot
-force
-forest
-forget
-fork
-fortune
-forum
-forward
-fossil
-foster
-found
-fox
-fragile
-frame
-frequent
-fresh
-friend
-fringe
-frog
-front
-frost
-frown
-frozen
-fruit
-fuel
-fun
-funny
-furnace
-fury
-future
-gadget
-gain
-galaxy
-gallery
-game
-gap
-garage
-garbage
-garden
-garlic
-garment
-gas
-gasp
-gate
-gather
-gauge
-gaze
-general
-genius
-genre
-gentle
-genuine
-gesture
-ghost
-giant
-gift
-giggle
-ginger
-giraffe
-girl
-give
-glad
-glance
-glare
-glass
-glide
-glimpse
-globe
-gloom
-glory
-glove
-glow
-glue
-goat
-goddess
-gold
-good
-goose
-gorilla
-gospel
-gossip
-govern
-gown
-grab
-grace
-grain
-grant
-grape
-grass
-gravity
-great
-green
-grid
-grief
-grit
-grocery
-group
-grow
-grunt
-guard
-guess
-guide
-guilt
-guitar
-gun
-gym
-habit
-hair
-half
-hammer
-hamster
-hand
-happy
-harbor
-hard
-harsh
-harvest
-hat
-have
-hawk
-hazard
-head
-health
-heart
-heavy
-hedgehog
-height
-hello
-helmet
-help
-hen
-hero
-hidden
-high
-hill
-hint
-hip
-hire
-history
-hobby
-hockey
-hold
-hole
-holiday
-hollow
-home
-honey
-hood
-hope
-horn
-horror
-horse
-hospital
-host
-hotel
-hour
-hover
-hub
-huge
-human
-humble
-humor
-hundred
-hungry
-hunt
-hurdle
-hurry
-hurt
-husband
-hybrid
-ice
-icon
-idea
-identify
-idle
-ignore
-ill
-illegal
-illness
-image
-imitate
-immense
-immune
-impact
-impose
-improve
-impulse
-inch
-include
-income
-increase
-index
-indicate
-indoor
-industry
-infant
-inflict
-inform
-inhale
-inherit
-initial
-inject
-injury
-inmate
-inner
-innocent
-input
-inquiry
-insane
-insect
-inside
-inspire
-install
-intact
-interest
-into
-invest
-invite
-involve
-iron
-island
-isolate
-issue
-item
-ivory
-jacket
-jaguar
-jar
-jazz
-jealous
-jeans
-jelly
-jewel
-job
-join
-joke
-journey
-joy
-judge
-juice
-jump
-jungle
-junior
-junk
-just
-kangaroo
-keen
-keep
-ketchup
-key
-kick
-kid
-kidney
-kind
-kingdom
-kiss
-kit
-kitchen
-kite
-kitten
-kiwi
-knee
-knife
-knock
-know
-lab
-label
-labor
-ladder
-lady
-lake
-lamp
-language
-laptop
-large
-later
-latin
-laugh
-laundry
-lava
-law
-lawn
-lawsuit
-layer
-lazy
-leader
-leaf
-learn
-leave
-lecture
-left
-leg
-legal
-legend
-leisure
-lemon
-lend
-length
-lens
-leopard
-lesson
-letter
-level
-liar
-liberty
-library
-license
-life
-lift
-light
-like
-limb
-limit
-link
-lion
-liquid
-list
-little
-live
-lizard
-load
-loan
-lobster
-local
-lock
-logic
-lonely
-long
-loop
-lottery
-loud
-lounge
-love
-loyal
-lucky
-luggage
-lumber
-lunar
-lunch
-luxury
-lyrics
-machine
-mad
-magic
-magnet
-maid
-mail
-main
-major
-make
-mammal
-man
-manage
-mandate
-mango
-mansion
-manual
-maple
-marble
-march
-margin
-marine
-market
-marriage
-mask
-mass
-master
-match
-material
-math
-matrix
-matter
-maximum
-maze
-meadow
-mean
-measure
-meat
-mechanic
-medal
-media
-melody
-melt
-member
-memory
-mention
-menu
-mercy
-merge
-merit
-merry
-mesh
-message
-metal
-method
-middle
-midnight
-milk
-million
-mimic
-mind
-minimum
-minor
-minute
-miracle
-mirror
-misery
-miss
-mistake
-mix
-mixed
-mixture
-mobile
-model
-modify
-mom
-moment
-monitor
-monkey
-monster
-month
-moon
-moral
-more
-morning
-mosquito
-mother
-motion
-motor
-mountain
-mouse
-move
-movie
-much
-muffin
-mule
-multiply
-muscle
-museum
-mushroom
-music
-must
-mutual
-myself
-mystery
-myth
-naive
-name
-napkin
-narrow
-nasty
-nation
-nature
-near
-neck
-need
-negative
-neglect
-neither
-nephew
-nerve
-nest
-net
-network
-neutral
-never
-news
-next
-nice
-night
-noble
-noise
-nominee
-noodle
-normal
-north
-nose
-notable
-note
-nothing
-notice
-novel
-now
-nuclear
-number
-nurse
-nut
-oak
-obey
-object
-oblige
-obscure
-observe
-obtain
-obvious
-occur
-ocean
-october
-odor
-off
-offer
-office
-often
-oil
-okay
-old
-olive
-olympic
-omit
-once
-one
-onion
-online
-only
-open
-opera
-opinion
-oppose
-option
-orange
-orbit
-orchard
-order
-ordinary
-organ
-orient
-original
-orphan
-ostrich
-other
-outdoor
-outer
-output
-outside
-oval
-oven
-over
-own
-owner
-oxygen
-oyster
-ozone
-pact
-paddle
-page
-pair
-palace
-palm
-panda
-panel
-panic
-panther
-paper
-parade
-parent
-park
-parrot
-party
-pass
-patch
-path
-patient
-patrol
-pattern
-pause
-pave
-payment
-peace
-peanut
-pear
-peasant
-pelican
-pen
-penalty
-pencil
-people
-pepper
-perfect
-permit
-person
-pet
-phone
-photo
-phrase
-physical
-piano
-picnic
-picture
-piece
-pig
-pigeon
-pill
-pilot
-pink
-pioneer
-pipe
-pistol
-pitch
-pizza
-place
-planet
-plastic
-plate
-play
-please
-pledge
-pluck
-plug
-plunge
-poem
-poet
-point
-polar
-pole
-police
-pond
-pony
-pool
-popular
-portion
-position
-possible
-post
-potato
-pottery
-poverty
-powder
-power
-practice
-praise
-predict
-prefer
-prepare
-present
-pretty
-prevent
-price
-pride
-primary
-print
-priority
-prison
-private
-prize
-problem
-process
-produce
-profit
-program
-project
-promote
-proof
-property
-prosper
-protect
-proud
-provide
-public
-pudding
-pull
-pulp
-pulse
-pumpkin
-punch
-pupil
-puppy
-purchase
-purity
-purpose
-purse
-push
-put
-puzzle
-pyramid
-quality
-quantum
-quarter
-question
-quick
-quit
-quiz
-quote
-rabbit
-raccoon
-race
-rack
-radar
-radio
-rail
-rain
-raise
-rally
-ramp
-ranch
-random
-range
-rapid
-rare
-rate
-rather
-raven
-raw
-razor
-ready
-real
-reason
-rebel
-rebuild
-recall
-receive
-recipe
-record
-recycle
-reduce
-reflect
-reform
-refuse
-region
-regret
-regular
-reject
-relax
-release
-relief
-rely
-remain
-remember
-remind
-remove
-render
-renew
-rent
-reopen
-repair
-repeat
-replace
-report
-require
-rescue
-resemble
-resist
-resource
-response
-result
-retire
-retreat
-return
-reunion
-reveal
-review
-reward
-rhythm
-rib
-ribbon
-rice
-rich
-ride
-ridge
-rifle
-right
-rigid
-ring
-riot
-ripple
-risk
-ritual
-rival
-river
-road
-roast
-robot
-robust
-rocket
-romance
-roof
-rookie
-room
-rose
-rotate
-rough
-round
-route
-royal
-rubber
-rude
-rug
-rule
-run
-runway
-rural
-sad
-saddle
-sadness
-safe
-sail
-salad
-salmon
-salon
-salt
-salute
-same
-sample
-sand
-satisfy
-satoshi
-sauce
-sausage
-save
-say
-scale
-scan
-scare
-scatter
-scene
-scheme
-school
-science
-scissors
-scorpion
-scout
-scrap
-screen
-script
-scrub
-sea
-search
-season
-seat
-second
-secret
-section
-security
-seed
-seek
-segment
-select
-sell
-seminar
-senior
-sense
-sentence
-series
-service
-session
-settle
-setup
-seven
-shadow
-shaft
-shallow
-share
-shed
-shell
-sheriff
-shield
-shift
-shine
-ship
-shiver
-shock
-shoe
-shoot
-shop
-short
-shoulder
-shove
-shrimp
-shrug
-shuffle
-shy
-sibling
-sick
-side
-siege
-sight
-sign
-silent
-silk
-silly
-silver
-similar
-simple
-since
-sing
-siren
-sister
-situate
-six
-size
-skate
-sketch
-ski
-skill
-skin
-skirt
-skull
-slab
-slam
-sleep
-slender
-slice
-slide
-slight
-slim
-slogan
-slot
-slow
-slush
-small
-smart
-smile
-smoke
-smooth
-snack
-snake
-snap
-sniff
-snow
-soap
-soccer
-social
-sock
-soda
-soft
-solar
-soldier
-solid
-solution
-solve
-someone
-song
-soon
-sorry
-sort
-soul
-sound
-soup
-source
-south
-space
-spare
-spatial
-spawn
-speak
-special
-speed
-spell
-spend
-sphere
-spice
-spider
-spike
-spin
-spirit
-split
-spoil
-sponsor
-spoon
-sport
-spot
-spray
-spread
-spring
-spy
-square
-squeeze
-squirrel
-stable
-stadium
-staff
-stage
-stairs
-stamp
-stand
-start
-state
-stay
-steak
-steel
-stem
-step
-stereo
-stick
-still
-sting
-stock
-stomach
-stone
-stool
-story
-stove
-strategy
-street
-strike
-strong
-struggle
-student
-stuff
-stumble
-style
-subject
-submit
-subway
-success
-such
-sudden
-suffer
-sugar
-suggest
-suit
-summer
-sun
-sunny
-sunset
-super
-supply
-supreme
-sure
-surface
-surge
-surprise
-surround
-survey
-suspect
-sustain
-swallow
-swamp
-swap
-swarm
-swear
-sweet
-swift
-swim
-swing
-switch
-sword
-symbol
-symptom
-syrup
-system
-table
-tackle
-tag
-tail
-talent
-talk
-tank
-tape
-target
-task
-taste
-tattoo
-taxi
-teach
-team
-tell
-ten
-tenant
-tennis
-tent
-term
-test
-text
-thank
-that
-theme
-then
-theory
-there
-they
-thing
-this
-thought
-three
-thrive
-throw
-thumb
-thunder
-ticket
-tide
-tiger
-tilt
-timber
-time
-tiny
-tip
-tired
-tissue
-title
-toast
-tobacco
-today
-toddler
-toe
-together
-toilet
-token
-tomato
-tomorrow
-tone
-tongue
-tonight
-tool
-tooth
-top
-topic
-topple
-torch
-tornado
-tortoise
-toss
-total
-tourist
-toward
-tower
-town
-toy
-track
-trade
-traffic
-tragic
-train
-transfer
-trap
-trash
-travel
-tray
-treat
-tree
-trend
-trial
-tribe
-trick
-trigger
-trim
-trip
-trophy
-trouble
-truck
-true
-truly
-trumpet
-trust
-truth
-try
-tube
-tuition
-tumble
-tuna
-tunnel
-turkey
-turn
-turtle
-twelve
-twenty
-twice
-twin
-twist
-two
-type
-typical
-ugly
-umbrella
-unable
-unaware
-uncle
-uncover
-under
-undo
-unfair
-unfold
-unhappy
-uniform
-unique
-unit
-universe
-unknown
-unlock
-until
-unusual
-unveil
-update
-upgrade
-uphold
-upon
-upper
-upset
-urban
-urge
-usage
-use
-used
-useful
-useless
-usual
-utility
-vacant
-vacuum
-vague
-valid
-valley
-valve
-van
-vanish
-vapor
-various
-vast
-vault
-vehicle
-velvet
-vendor
-venture
-venue
-verb
-verify
-version
-very
-vessel
-veteran
-viable
-vibrant
-vicious
-victory
-video
-view
-village
-vintage
-violin
-virtual
-virus
-visa
-visit
-visual
-vital
-vivid
-vocal
-voice
-void
-volcano
-volume
-vote
-voyage
-wage
-wagon
-wait
-walk
-wall
-walnut
-want
-warfare
-warm
-warrior
-wash
-wasp
-waste
-water
-wave
-way
-wealth
-weapon
-wear
-weasel
-weather
-web
-wedding
-weekend
-weird
-welcome
-west
-wet
-whale
-what
-wheat
-wheel
-when
-where
-whip
-whisper
-wide
-width
-wife
-wild
-will
-win
-window
-wine
-wing
-wink
-winner
-winter
-wire
-wisdom
-wise
-wish
-witness
-wolf
-woman
-wonder
-wood
-wool
-word
-work
-world
-worry
-worth
-wrap
-wreck
-wrestle
-wrist
-write
-wrong
-yard
-year
-yellow
-you
-young
-youth
-zebra
-zero
-zone
-zoo`.split("\n");
diff --git a/src/lib/nostr.ts b/src/lib/nostr.ts
index 1a02114d..bd2e65fe 100644
--- a/src/lib/nostr.ts
+++ b/src/lib/nostr.ts
@@ -18,8 +18,8 @@ import {
} from "nostr-tools/nip49";
import { mnemonicToEntropy, entropyToMnemonic } from "@scure/bip39";
-import wordlist from "$lib/english";
import { bech32m } from "@scure/base";
+ import { wordlist } from "@scure/bip39/wordlists/english";
import { generateSeedWords, privateKeyFromSeedWords } from "nostr-tools/nip06";
diff --git a/src/lib/store.ts b/src/lib/store.ts
index 7609a67c..3c1ff321 100644
--- a/src/lib/store.ts
+++ b/src/lib/store.ts
@@ -48,6 +48,7 @@ export let invoice = writable();
export let last = writable();
export let loginRedirect = writable();
export let decrypted = persistLocal("decrypted", {});
+export let mnemonic = persistSession("mnemonic");
export let newPayment = persistLocal("newPayment");
export let password = writable<string | undefined>();
export let passwordPrompt = writable();
diff --git a/src/locales/en.json b/src/locales/en.json
index 29929c6b..ad42b8cc 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -19,6 +19,7 @@
}
},
"locations": {
+ "viewMap": "View map",
"header": "Locations",
"description": "Over 500 businesses worldwide are accepting bitcoin with Coinos. We hope that yours will be next!"
}
@@ -155,6 +156,7 @@
"subheader": "Latest Updates"
},
"user": {
+ "url": "Public URL",
"install": "Install app",
"moreDetails": "Profile details",
"noMessages": "You have not sent or received any messages",
@@ -182,6 +184,7 @@
"this": "This",
"lightningAddress": "Lightning address",
"dashboard": {
+ "setup": "Setup self-custody",
"payNow": "Pay now",
"checkout": "Checkout",
"go": "Go",
@@ -325,7 +328,6 @@
"voucher": "Payment code",
"qr": "QR",
"copy": "Copy",
- "copyPr": "Copy pay request",
"amountPrompt": "Always prompt for amount",
"shareLink": "Share link",
"copyLink": "Copy link",
diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte
index c4750581..2caf9b30 100644
--- a/src/routes/(app)/[username]/(profile)/+layout.svelte
+++ b/src/routes/(app)/[username]/(profile)/+layout.svelte
@@ -84,11 +84,10 @@
<div
class="w-full lg:w-[280px] xl:w-[360px] lg:absolute space-y-5 left-20 mx-auto mb-5"
>
- <div
- class="flex text-3xl font-bold text-center mx-auto justify-center gap-2"
- >
- <div class="my-auto break-words w-full">{display || username}</div>
- </div>
+ <button type="button" on:click={toggleDetails} class="flex text-3xl font-bold text-center mx-auto justify-center gap-2">
+ <div class="my-auto break-words">{display || username}</div>
+ <Icon icon="qr" style="w-8 invert my-auto" />
+ </button>
{#if subject.address}
<div
@@ -112,68 +111,51 @@
<span class="text-secondary">{$t("user.followers")}</span></a
>
</div>
-
- <button
- type="button"
- on:click={toggleDetails}
- class="flex text-base text-secondary mx-auto"
- >
- <div class="mt-1">
- {$t("user.moreDetails")}
- </div>
- <div
- class:-rotate-90={showDetails}
- class:rotate-180={!showDetails}
- class="my-auto"
- >
- <Icon icon="chevron-left" style="w-6 mr-2" />
- </div>
- </button>
</div>
{#if showDetails}
- <div class="text-xl space-y-5">
+ <div class="space-y-5 pb-20">
<div>
- <div class="font-bold">{$t("user.profilePage")}</div>
+ <div class="text-secondary">{$t("user.lightningAddress")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
- {profile}
+ <div class="break-all grow text-xl">
+ {lnaddr}
</div>
<div class="flex mb-auto gap-1">
- <button class="my-auto" on:click={() => copy(profile)}
+ <button class="my-auto" on:click={() => copy(lnaddr)}
><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button
>
- <a
- href={`/qr/${encodeURIComponent(
- `${$page.url.protocol}//${profile}`
- )}`}
- class="my-auto"
- >
+ <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto">
<Icon icon="qr" style="invert max-w-max min-w-[32px]" />
</a>
</div>
</div>
</div>
<div>
- <div class="font-bold">{$t("user.lightningAddress")}</div>
+ <div class="text-secondary">{$t("user.url")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
- {lnaddr}
+ <div class="break-all grow text-xl">
+ {profile}
</div>
<div class="flex mb-auto gap-1">
- <button class="my-auto" on:click={() => copy(lnaddr)}
+ <button class="my-auto" on:click={() => copy(profile)}
><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button
>
- <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto">
+ <a
+ href={`/qr/${encodeURIComponent(
+ `${$page.url.protocol}//${profile}`
+ )}`}
+ class="my-auto"
+ >
<Icon icon="qr" style="invert max-w-max min-w-[32px]" />
</a>
</div>
</div>
</div>
<div>
- <div class="font-bold">{$t("user.nostrPubkey")}</div>
+ <div class="text-secondary">{$t("user.nostrPubkey")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
+ <div class="break-all grow text-xl">
{npub}
</div>
<div class="flex my-auto gap-1">
@@ -188,9 +170,9 @@
</div>
{#if user.id === subject.id}
<div>
- <div class="font-bold">{$t("user.nwc")}</div>
+ <div class="text-secondary">{$t("user.nwc")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
+ <div class="break-all grow text-xl">
{nwc}
</div>
<div class="flex my-auto gap-1">
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte
index 32e6807a..afb4e58c 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -1,283 +1,67 @@
<script>
- import { browser } from "$app/environment";
- import { goto, invalidate } from "$app/navigation";
- import { btc, f, sat, post } from "$lib/utils";
+ import { btc, f, sat } from "$lib/utils";
import Icon from "$comp/Icon.svelte";
+ import Account from "$comp/Account.svelte";
import Balance from "$comp/Balance.svelte";
- import DeleteItem from "$comp/DeleteItem.svelte";
+ import Items from "$comp/Items.svelte";
import { t } from "$lib/translations";
- import { fade } from "svelte/transition";
- import { cubicIn } from "svelte/easing";
- import { flip } from "svelte/animate";
import { installPrompt } from "$lib/store";
- import {
- dndzone,
- SHADOW_ITEM_MARKER_PROPERTY_NAME,
- SOURCES,
- TRIGGERS,
- } from "svelte-dnd-action";
-
export let data;
- let flipDurationMs = 200;
- let dropTargetStyle = {};
-
let { subject, user, items, rates } = data;
$: ({ currency } = subject);
$: rate = rates[currency];
- $: refresh(data);
- let refresh = (d) => ({ items, subject, user, rates } = d);
- let dragDisabled =
- subject?.id !== user?.id || (browser && window.innerWidth < 768);
-
- let deleting;
$: total = items.reduce((a, b) => a + b.price * b.quantity, 0);
- let del = (item) => {
- deleting = item;
- };
-
- function handleConsider(e) {
- const {
- items: newItems,
- info: { source, trigger },
- } = e.detail;
- items = newItems;
- if (source === SOURCES.KEYBOARD && trigger === TRIGGERS.DRAG_STOPPED)
- reset();
- }
- async function handleFinalize(e) {
- const {
- items: newItems,
- info: { source },
- } = e.detail;
- items = newItems;
- if (source === SOURCES.POINTER) reset();
-
- if (subject?.id === user?.id) {
- await post("/api/items/sort", { items });
- invalidate("app:items");
- }
- }
-
- function startDrag(e) {
- e.preventDefault();
- dragDisabled = false;
- }
-
- function handleKeyDown(e) {
- if ((e.key === "Enter" || e.key === " ") && dragDisabled)
- dragDisabled = false;
- }
-
- $: if (total > 0 && browser) {
- let payButton = document.querySelector("#payButton");
- if (payButton) payButton.addEventListener("click", checkout);
- }
-
- let checkout = async () => {
- let invoice = {
- type: "lightning",
- amount: btc(total, rate),
- currency,
- items: items.filter((i) => i.quantity > 0),
- prompt: subject.prompt,
- memoPrompt: subject.memoPrompt,
- rate,
- };
-
- let { id } = await post(`/${subject.username}/invoice`, {
- invoice,
- user: { username: subject.username },
- });
-
- if (invoice.memoPrompt) {
- return goto(`/${subject.username}/invoice/${id}/memo`);
- }
- goto(`/${subject.username}/invoice/${id}`);
- };
-
- let reset = () =>
- (dragDisabled =
- user?.username !== subject?.username || window.innerWidth < 768);
+ let refresh = (d) => ({ items, subject, user, rates } = d);
+ $: refresh(data);
let install = async () => {
if (!$installPrompt) return;
await $installPrompt.prompt();
$installPrompt = null;
};
-</script>
-<svelte:window on:resize={reset} />
+ user.savings = 0;
-{#if deleting}
- <DeleteItem bind:item={deleting} />
+ let accounts = [
+ { balance: user.balance, setup: true, type: "Cash" },
+ { balance: user.savings, setup: false, type: "Savings" },
+ ];
+</script>
+
+{#if !user.balance}
+ <div class="mb-8">
+ <p class="text-secondary text-lg">
+ {$t("user.welcome")}
+ </p>
+ </div>
{/if}
<div class="space-y-5">
{#if user?.id === subject.id}
- <div class="flex justify-center lg:justify-start">
- <Balance {user} {rate} />
- </div>
-
- {#if !user.balance}
- <div class="mb-8">
- <p class="text-secondary text-lg">
- {$t("user.welcome")}
- </p>
- </div>
- {/if}
-
- <div class="flex flex-wrap gap-3 justify-center w-full text-xl">
- <a href={`/send`} class="contents grow">
- <button
- type="button"
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow"
- >
- <div class="mx-auto flex gap-2">
- <Icon icon="send" style="w-8" />
- <div class="my-auto">{$t("user.dashboard.send")}</div>
- </div>
- </button>
- </a>
-
- <a href={`/${user.username}/receive`} class="contents">
- <button
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow"
- >
- <div class="mx-auto flex gap-2">
- <Icon icon="receive" style="w-8" />
- <div class="my-auto">{$t("user.dashboard.receive")}</div>
- </div>
- </button>
- </a>
-
- {#if $installPrompt}
- <button
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white grow basis-full fixed bottom-16 xl:hidden"
- on:click={install}
- >
- <div class="mx-auto flex gap-2">
- <Icon icon="save" style="w-8 mx-auto invert" />
- <div class="my-auto text-xl whitespace-nowrap">
- {$t("user.install")}
- </div>
- </div>
- </button>
- {/if}
- </div>
- {/if}
+ {#each accounts as account}
+ <Account {user} {rate} {account} />
+ {/each}
- {#if items.length}
- <h2 class="text-2xl font-bold">Select items</h2>
- {/if}
- <div
- class="grid sm:grid-cols-2 gap-4 pb-20"
- use:dndzone={{ items, flipDurationMs, dropTargetStyle, dragDisabled }}
- on:consider={handleConsider}
- on:finalize={handleFinalize}
- >
- {#each items as i (i.id)}
+ {#if $installPrompt}
<button
- type="button"
- class="w-full text-center cursor-pointer hover:opacity-80 mb-auto"
- on:click={() => i.quantity++}
+ class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white grow basis-full fixed bottom-16 xl:hidden"
+ on:click={install}
>
- <div class="relative w-full h-64 overflow-hidden rounded-xl">
- {#if i[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
- <div
- in:fade={{ duration: 200, easing: cubicIn }}
- class="custom-shadow-item"
- />
- {:else if i.image}
- <img
- src={`/api/public/${i.image}.webp`}
- alt={i.name}
- class="mx-auto object-cover h-full w-full"
- />
- {:else}
- <div
- class="bg-gradient-to-r from-primary to-gradient mb-4 cursor-pointer hover:opacity-80 w-full h-full"
- alt="Banner"
- />
- {/if}
- {#if user?.username === subject.username}
- <div class="flex gap-2 justify-center absolute top-2 right-2">
- <button type="button" on:click|stopPropagation={() => {}}>
- <a href={`/${user.username}/items/${i.id}`}>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- >
- <Icon icon="edit" style="w-8 mx-auto invert" />
- </button>
- </a>
- </button>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- on:click|stopPropagation={() => del(i)}
- >
- <Icon icon="trash" style="w-8 mx-auto invert" />
- </button>
- <button
- type="button"
- on:click|stopPropagation={() => {}}
- class="sm:hidden"
- on:mousedown={startDrag}
- on:touchstart={startDrag}
- on:keydown={handleKeyDown}
- >
- <a href={`/${user.username}/items/${i.id}`}>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- >
- <Icon icon="move" style="w-8 mx-auto invert" />
- </button>
- </a>
- </button>
- </div>
- {/if}
- </div>
- <div class="bg-white rounded-xl py-2">
- <div class="flex text-left gap-2">
- <div class="overflow-hidden space-y-1">
- <div class="text-2xl break-words">{i.name}</div>
- <div class="flex gap-2">
- <div class="font-semibold text-lg">
- {f(i.price, currency)}
- </div>
- <div class="text-secondary my-auto">
- {sat(btc(i.price, rate))}
- </div>
- </div>
- </div>
- <div class="flex gap-1 ml-auto mb-auto justify-around tabular-nums">
- {#if i.quantity}
- <button
- type="button"
- on:click|stopPropagation={() =>
- i.quantity > 0 && i.quantity--}
- >
- <Icon icon="minus" style="w-10 min-w-10" />
- </button>
- {/if}
- <div class="text-2xl my-auto text-center">
- {#if i.quantity}{i.quantity}{:else}&mdash;{/if}
- </div>
- {#if i.quantity}
- <button
- type="button"
- on:click|stopPropagation={() => i.quantity++}
- >
- <Icon icon="plus" style="w-10 min-w-10" />
- </button>
- {/if}
- </div>
+ <div class="mx-auto flex gap-2">
+ <Icon icon="save" style="w-8 mx-auto invert" />
+ <div class="my-auto text-xl whitespace-nowrap">
+ {$t("user.install")}
</div>
</div>
</button>
- {/each}
- </div>
+ {/if}
+ {/if}
+
+ <Items {items} {subject} {user} {total} {currency} {rate} />
+
<div
class="fixed inset-x-0 mx-auto flex bottom-16"
class:static={!items.length}
diff --git a/src/routes/(app)/[username]/account/setup/+page.svelte b/src/routes/(app)/[username]/account/setup/+page.svelte
new file mode 100644
index 00000000..5c2102c7
--- /dev/null
+++ b/src/routes/(app)/[username]/account/setup/+page.svelte
@@ -0,0 +1,64 @@
+<script>
+ import { copy } from "$lib/utils";
+ import { t } from "$lib/translations";
+ import Icon from "$comp/Icon.svelte";
+ import { mnemonic } from "$lib/store";
+ import { generateMnemonic } from "@scure/bip39";
+ import { wordlist } from "@scure/bip39/wordlists/english";
+
+ export let data;
+ let { user } = data;
+
+ let reveal;
+
+ let toggleSeed = async () => {
+ try {
+ $mnemonic = await generateMnemonic(wordlist);
+ reveal = !reveal;
+ } catch (e) {
+ console.log(e);
+ }
+ };
+</script>
+
+<div class="space-y-5">
+ <h1 class="text-center text-3xl font-semibold">Account setup</h1>
+ <h2 class="text-center text-2xl text-secondary">Step 1</h2>
+
+ <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
+ <p class="text-secondary mb-1">
+ The keys to your savings account are derived from a seed phrase. When
+ you're ready, write it down and keep it safe.
+ </p>
+
+ <p class="text-secondary mb-1">
+ Whoever knows these words can withdraw funds from the account. We can't
+ help you recover them if you lose them.
+ </p>
+
+ {#if reveal}
+ <div class="text-2xl border p-4 text-center rounded-2xl">
+ {$mnemonic}
+ </div>
+
+ <div>
+ <a href={`/${user.username}/account/setup/step2`} class="contents">
+ <button
+ type="button"
+ class="bg-black text-white w-full px-5 py-6 text-xl rounded-2xl font-bold"
+ on:click={toggleSeed}
+ >
+ I've written down my seed phrase
+ </button>
+ </a>
+ </div>
+ {:else}
+ <div class="flex justify-center">
+ <button type="button" class="primary" on:click={toggleSeed}>
+ <Icon icon="warning" style="mr-2 w-6 my-auto" />
+ {$t("user.settings.revealSeed")}
+ </button>
+ </div>
+ {/if}
+ </div>
+</div>
diff --git a/src/routes/(app)/[username]/account/setup/step2/+page.svelte b/src/routes/(app)/[username]/account/setup/step2/+page.svelte
new file mode 100644
index 00000000..3b403295
--- /dev/null
+++ b/src/routes/(app)/[username]/account/setup/step2/+page.svelte
@@ -0,0 +1,46 @@
+<script lang="ts">
+ import { t } from "$lib/translations";
+ import { mnemonic } from "$lib/store";
+ import Icon from "$comp/Icon.svelte";
+ import { encrypt, decrypt } from "nostr-tools/nip49";
+ import { mnemonicToEntropy, entropyToMnemonic } from "@scure/bip39";
+ import { wordlist } from "@scure/bip39/wordlists/english";
+
+ export let data;
+ let { user } = data;
+
+ let password;
+
+ let submit = async () => {
+ console.log($mnemonic);
+ let t = await encrypt(mnemonicToEntropy($mnemonic, wordlist), password);
+
+ let m = await decrypt(t, password);
+ console.log(entropyToMnemonic(m, wordlist));
+ };
+</script>
+
+<div class="space-y-5">
+ <div>
+ <h1 class="text-center text-3xl font-semibold">Account setup</h1>
+ <h2 class="text-center text-2xl text-secondary">Step 2</h2>
+ </div>
+
+ <form
+ on:submit|preventDefault={submit}
+ class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"
+ >
+ <p class="text-secondary mb-1">
+ Add an additional password to your seed phrase. It should be different
+ than your login password. We can't reset it if you lose it.
+ </p>
+
+ <input type="text" placeholder="Password" bind:value={password} />
+
+ <button
+ class="bg-black text-white w-full px-5 py-6 text-xl rounded-2xl font-bold"
+ >
+ Submit
+ </button>
+ </form>
+</div>