From c5ef18aec5de023732eb6b86f768f206462fea93 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 4 Sep 2024 18:59:30 -0700 Subject: wip --- src/components/About.svelte | 5 +- src/components/Account.svelte | 53 + src/components/Balance.svelte | 6 +- src/components/Items.svelte | 209 ++ src/lib/english.ts | 2048 -------------------- src/lib/nostr.ts | 2 +- src/lib/store.ts | 1 + src/locales/en.json | 4 +- .../(app)/[username]/(profile)/+layout.svelte | 66 +- src/routes/(app)/[username]/(profile)/+page.svelte | 282 +-- .../(app)/[username]/account/setup/+page.svelte | 64 + .../[username]/account/setup/step2/+page.svelte | 46 + 12 files changed, 440 insertions(+), 2346 deletions(-) create mode 100644 src/components/Account.svelte create mode 100644 src/components/Items.svelte delete mode 100644 src/lib/english.ts create mode 100644 src/routes/(app)/[username]/account/setup/+page.svelte create mode 100644 src/routes/(app)/[username]/account/setup/step2/+page.svelte (limited to 'src') 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")}

+
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 @@ + + +
+
+ +
{type}
+
+ + +
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; {#if show && user.haspin && $pin?.length !== 6} @@ -27,12 +27,12 @@ {#if isNaN(rate)}
—
{:else} - {f((user.balance * rate) / sats, user.currency)} + {f((balance * rate) / sats, user.currency)} {/if}
- ⚡️{s(user.balance)} + ⚡️{s(balance)}
{/if} 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 @@ + + + + +{#if deleting} + +{/if} + +{#if items.length} +

Select items

+{/if} +
+ {#each items as i (i.id)} + + + + + + + +
+ {/if} + +
+
+
+
{i.name}
+
+
+ {f(i.price, currency)} +
+
+ {sat(btc(i.price, rate))} +
+
+
+
+ {#if i.quantity} + + {/if} +
+ {#if i.quantity}{i.quantity}{:else}—{/if} +
+ {#if i.quantity} + + {/if} +
+
+
+ + {/each} + 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(); 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 @@
-
-
{display || username}
-
+ {#if subject.address}
{$t("user.followers")}
- -
{#if showDetails} -
+
-
{$t("user.profilePage")}
+
{$t("user.lightningAddress")}
-
- {profile} +
+ {lnaddr}
- - +
-
{$t("user.lightningAddress")}
+
{$t("user.url")}
-
- {lnaddr} +
+ {profile}
- - +
-
{$t("user.nostrPubkey")}
+
{$t("user.nostrPubkey")}
-
+
{npub}
@@ -188,9 +170,9 @@
{#if user.id === subject.id}
-
{$t("user.nwc")}
+
{$t("user.nwc")}
-
+
{nwc}
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 @@ - + user.savings = 0; -{#if deleting} - + let accounts = [ + { balance: user.balance, setup: true, type: "Cash" }, + { balance: user.savings, setup: false, type: "Savings" }, + ]; + + +{#if !user.balance} +
+

+ {$t("user.welcome")} +

+
{/if}
{#if user?.id === subject.id} -
- -
- - {#if !user.balance} -
-

- {$t("user.welcome")} -

-
- {/if} - -
- - - - - - - - - {#if $installPrompt} - - {/if} -
- {/if} + {#each accounts as account} + + {/each} - {#if items.length} -

Select items

- {/if} -
- {#each items as i (i.id)} + {#if $installPrompt} - - - - - - -
- {/if} -
-
-
-
-
{i.name}
-
-
- {f(i.price, currency)} -
-
- {sat(btc(i.price, rate))} -
-
-
-
- {#if i.quantity} - - {/if} -
- {#if i.quantity}{i.quantity}{:else}—{/if} -
- {#if i.quantity} - - {/if} -
+
+ +
+ {$t("user.install")}
- {/each} -
+ {/if} + {/if} + + +
+ 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); + } + }; + + +
+

Account setup

+

Step 1

+ +
+

+ The keys to your savings account are derived from a seed phrase. When + you're ready, write it down and keep it safe. +

+ +

+ Whoever knows these words can withdraw funds from the account. We can't + help you recover them if you lose them. +

+ + {#if reveal} +
+ {$mnemonic} +
+ + + {:else} +
+ +
+ {/if} +
+
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 @@ + + +
+
+

Account setup

+

Step 2

+
+ +
+

+ 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. +

+ + + + +
+
-- cgit v1.2.3 From 1b7cc6d64fb2ee3340a18d0924c4b64afabbc1e1 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 6 Sep 2024 09:32:53 -0700 Subject: per account payment pages --- src/components/Account.svelte | 87 +++--- src/components/Numpad.svelte | 2 +- src/lib/types.ts | 1 + src/routes/(app)/[username]/(profile)/+page.svelte | 4 +- .../payments/[...page]/+page.server.ts | 26 ++ .../[...account]/payments/[...page]/+page.svelte | 315 +++++++++++++++++++++ .../[...account]/payments/[...page]/+server.ts | 18 ++ .../receive/[...param]/+page.server.ts | 22 ++ .../[...account]/receive/[...param]/+page.svelte | 116 ++++++++ .../(app)/[username]/account/setup/+page.svelte | 4 +- .../[username]/account/setup/step2/+page.svelte | 15 +- .../(app)/[username]/invoice/[id]/+page.svelte | 89 +++--- .../[username]/payments/[...page]/+page.server.ts | 26 -- .../[username]/payments/[...page]/+page.svelte | 315 --------------------- .../(app)/[username]/payments/[...page]/+server.ts | 18 -- .../[username]/receive/[...param]/+page.server.ts | 20 -- .../[username]/receive/[...param]/+page.svelte | 116 -------- 17 files changed, 594 insertions(+), 600 deletions(-) create mode 100644 src/routes/(app)/[username]/[...account]/payments/[...page]/+page.server.ts create mode 100644 src/routes/(app)/[username]/[...account]/payments/[...page]/+page.svelte create mode 100644 src/routes/(app)/[username]/[...account]/payments/[...page]/+server.ts create mode 100644 src/routes/(app)/[username]/[...account]/receive/[...param]/+page.server.ts create mode 100644 src/routes/(app)/[username]/[...account]/receive/[...param]/+page.svelte delete mode 100644 src/routes/(app)/[username]/payments/[...page]/+page.server.ts delete mode 100644 src/routes/(app)/[username]/payments/[...page]/+page.svelte delete mode 100644 src/routes/(app)/[username]/payments/[...page]/+server.ts delete mode 100644 src/routes/(app)/[username]/receive/[...param]/+page.server.ts delete mode 100644 src/routes/(app)/[username]/receive/[...param]/+page.svelte (limited to 'src') diff --git a/src/components/Account.svelte b/src/components/Account.svelte index 5286c4d3..a781e809 100644 --- a/src/components/Account.svelte +++ b/src/components/Account.svelte @@ -4,50 +4,55 @@ import { t } from "$lib/translations"; export let user, rate, account; - let { balance, setup, type } = account; + let { balance, setup, type, id } = account; -
-
- -
{type}
-
+ + + diff --git a/src/components/Numpad.svelte b/src/components/Numpad.svelte index 88b5b899..78322ff5 100644 --- a/src/components/Numpad.svelte +++ b/src/components/Numpad.svelte @@ -1,7 +1,7 @@ diff --git a/src/routes/(app)/[username]/[...account]/payments/[...page]/+page.server.ts b/src/routes/(app)/[username]/[...account]/payments/[...page]/+page.server.ts new file mode 100644 index 00000000..070349f8 --- /dev/null +++ b/src/routes/(app)/[username]/[...account]/payments/[...page]/+page.server.ts @@ -0,0 +1,26 @@ +import { get, auth } from "$lib/utils"; + +let limit = 25; + +export async function load({ cookies, depends, params }) { + depends("app:payments"); + + let parts = params.page.split("/"); + + let start, end, page; + if (parts.length === 1) [page] = parts; + if (parts.length === 2) [start, page] = parts; + if (parts.length === 3) [start, end, page] = parts; + if (!parseInt(page)) page = 1; + + let offset = (page - 1) * limit; + if (!start) start = Date.now() / 1000 - 24 * 60 * 60; + + let url = `/payments?start=${start * 1000}&limit=${limit}&offset=${offset}`; + if (end) url += `&end=${end * 1000}`; + + let { count, payments, incoming, outgoing } = await get(url, auth(cookies)); + let pages = new Array(Math.ceil(count / limit)); + + return { payments, page, pages, start, end, incoming, outgoing }; +} diff --git a/src/routes/(app)/[username]/[...account]/payments/[...page]/+page.svelte b/src/routes/(app)/[username]/[...account]/payments/[...page]/+page.svelte new file mode 100644 index 00000000..1efdc2ec --- /dev/null +++ b/src/routes/(app)/[username]/[...account]/payments/[...page]/+page.svelte @@ -0,0 +1,315 @@ + + +
+

+ {$t("payments.header")} +

+ +
+
+
+ {#each presets as { start, end, title }, i} + + + + {/each} +
+
+ +
+ {#if pages.length > 1} + {#each pages as _, i} + +
+ {i + 1} +
+
+ {/each} + {/if} +
+ +
+ {#each payments as p} + +
+
+
+ {f(Math.abs(p.amount) * (p.rate / sats), p.currency)} + {#if p.tip} + + +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% + + {/if} +
+ +
+ {sat(Math.abs(p.amount) + (p.tip || 0))} +
+
+ +
+ {#if p.type === types.pot} + +
+
+ Pot +
+ +
Pot
+
+
+ {:else if p.with} +
+
+ +
+
+ {p.with.username} +
+
+ {:else} +
+ {#if [types.ecash, types.lightning].includes(p.type)} +
⚡️
+ {:else if p.type === types.bitcoin} +
+ Bitcoin +
+ {:else if p.type === types.liquid} +
+ Liquid +
+ {/if} + +
+ {p.amount > 0 + ? p.confirmed + ? "Received" + : "Pending" + : "Sent"} +
+
+ {/if} +
+ +
+
+ {format(new Date(p.created), "h:mm aaa")} +
+
+ {format(new Date(p.created), "MMM d, yy")} +
+
+
+ + {:else} +

{$t("payments.empty")}

+ {/each} +
+ +
+ {#each Object.keys(incoming) as c} + + + + + + {#if tipsIn || tipsOut}{$t("payments.tips")}{/if} + + {$t("payments.total")} + + {@const totalIn = incoming[c]?.fiat || 0} + {@const tipsIn = incoming[c]?.fiatTips || 0} + {@const subtotalIn = totalIn - tipsIn} + + {@const totalOut = -outgoing[c]?.fiat || 0} + {@const tipsOut = outgoing[c]?.fiatTips || 0} + {@const subtotalOut = totalOut - tipsOut} + + {$t("payments.income")} + + {tipsIn ? f(tipsIn, c) : "-"} + {totalIn ? f(totalIn, c) : "-"} + + {$t("payments.expenditure")} + + {tipsOut ? f(tipsOut, c) : "-"} + {totalOut ? f(totalOut, c) : "-"} + {/each} +
+ + +
+
+ + diff --git a/src/routes/(app)/[username]/[...account]/payments/[...page]/+server.ts b/src/routes/(app)/[username]/[...account]/payments/[...page]/+server.ts new file mode 100644 index 00000000..a81faaf3 --- /dev/null +++ b/src/routes/(app)/[username]/[...account]/payments/[...page]/+server.ts @@ -0,0 +1,18 @@ +import { get, auth } from "$lib/utils"; +import { json } from "@sveltejs/kit"; + +export async function GET({ cookies, params }) { + let parts = params.page.split("/"); + + let start, end; + if (parts.length === 1) [start] = parts; + if (parts.length === 2) [start, end] = parts; + + if (!start) start = Date.now() / 1000 - 24 * 60 * 60; + + let url = `/payments?start=${start * 1000}`; + if (end) url += `&end=${end * 1000}`; + + let { payments } = await get(url, auth(cookies)); + return json({ payments }); +} diff --git a/src/routes/(app)/[username]/[...account]/receive/[...param]/+page.server.ts b/src/routes/(app)/[username]/[...account]/receive/[...param]/+page.server.ts new file mode 100644 index 00000000..13eb1381 --- /dev/null +++ b/src/routes/(app)/[username]/[...account]/receive/[...param]/+page.server.ts @@ -0,0 +1,22 @@ +import { redirect } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; +import type { Invoice } from "$lib/types"; + +export let load = async ({ cookies, params, parent }) => { + let { account } = params; + let {subject, user, rates } = await parent(); + + let invoice: Invoice = { + account, + type: account ? "bitcoin": "lightning", + rate: rates[user?.currency || subject?.currency], + }; + + if(!user) user = subject; + invoice = await post("/invoice", { invoice, account, user }, auth(cookies)); + let { id } = invoice; + + if (invoice.memoPrompt && !invoice.memo) { + redirect(307, `/${user.username}/invoice/${id}/memo`); + } else redirect(307, `/${user.username}/invoice/${id}`); +}; diff --git a/src/routes/(app)/[username]/[...account]/receive/[...param]/+page.svelte b/src/routes/(app)/[username]/[...account]/receive/[...param]/+page.svelte new file mode 100644 index 00000000..f9267105 --- /dev/null +++ b/src/routes/(app)/[username]/[...account]/receive/[...param]/+page.svelte @@ -0,0 +1,116 @@ + + + + + +
+

+ {$t("payments.send")} +

+ +
+ {#if form?.error} +
+
+ {$t("error.unrecognizedInput")} +
+
+ {/if} + +
+ -
- -
- -

- Coinos can automatically mark orders as Paid if you provide an API token. - -

-

- Create a new app in Apps > App Development -

- -

- Edit the Admin API scopes to enable write_orders permission then install the app into your store and copy the Admin API access - token here. -

-
- - diff --git a/src/routes/(app)/[username]/verify/[code]/+page.server.ts b/src/routes/(app)/[username]/verify/[code]/+page.server.ts deleted file mode 100644 index fec1a85c..00000000 --- a/src/routes/(app)/[username]/verify/[code]/+page.server.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { get } from "$lib/utils"; -import { redirect } from "@sveltejs/kit"; - -export let load = async ({ params: { code } }) => { - let user; - try { - user = await get(`/verify/${code}`); - } catch (e) { - console.log(e); - return { error: "Verification failed" }; - } - - redirect(307, `/${user.username}/settings/account?verified=true`); -}; diff --git a/src/routes/(app)/[username]/verify/[code]/+page.svelte b/src/routes/(app)/[username]/verify/[code]/+page.svelte deleted file mode 100644 index 1e099dae..00000000 --- a/src/routes/(app)/[username]/verify/[code]/+page.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - -
- {#if error} -

- Email verification failed -

- {:else if user.verified} -
- -
- -

- Email verified successfully! -

- {/if} -
- - -
- - -
- {$t("payments.tapAnywhere")} -
diff --git a/src/routes/(app)/account/setup/+page.svelte b/src/routes/(app)/account/setup/+page.svelte new file mode 100644 index 00000000..5a15316a --- /dev/null +++ b/src/routes/(app)/account/setup/+page.svelte @@ -0,0 +1,64 @@ + + +
+

Account setup

+

Step 1

+ +
+

+ The keys to your savings account are derived from a seed phrase. When + you're ready, write it down and keep it safe. +

+ +

+ Whoever knows these words in the correct order can withdraw funds from the account. We can't + recover them if you lose them. +

+ + {#if reveal} +
+ {$mnemonic} +
+ + + {:else} +
+ +
+ {/if} +
+
diff --git a/src/routes/(app)/account/setup/step2/+page.svelte b/src/routes/(app)/account/setup/step2/+page.svelte new file mode 100644 index 00000000..8169ce06 --- /dev/null +++ b/src/routes/(app)/account/setup/step2/+page.svelte @@ -0,0 +1,52 @@ + + +
+
+

Account setup

+

Step 2

+
+ + +

+ 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. +

+ + + + + +
diff --git a/src/routes/(app)/admin/+page.svelte b/src/routes/(app)/admin/+page.svelte index c61a2e45..214bf5b3 100644 --- a/src/routes/(app)/admin/+page.svelte +++ b/src/routes/(app)/admin/+page.svelte @@ -1,7 +1,7 @@ diff --git a/src/routes/(app)/ecash/[id]/[...version]/+layout.svelte b/src/routes/(app)/ecash/[id]/[...version]/+layout.svelte index 679fbe76..89de4b58 100644 --- a/src/routes/(app)/ecash/[id]/[...version]/+layout.svelte +++ b/src/routes/(app)/ecash/[id]/[...version]/+layout.svelte @@ -1,5 +1,4 @@ + +{#if loaded && user?.haspin && $pin?.length !== 6} + +{/if} + +
+

+ Generating new account keys +

+
+ +
+
diff --git a/src/routes/(app)/generate/+server.ts b/src/routes/(app)/generate/+server.ts new file mode 100644 index 00000000..5a442982 --- /dev/null +++ b/src/routes/(app)/generate/+server.ts @@ -0,0 +1,12 @@ +import { auth, post } from "$lib/utils"; +import { error, json } from "@sveltejs/kit"; + +export async function POST({ cookies, request }) { + try { + let body = await request.json(); + return json(await post(`/user`, body, auth(cookies))); + } catch (e: any) { + console.log(e); + error(500, e.message); + } +} diff --git a/src/routes/(app)/invoice/+page.server.ts b/src/routes/(app)/invoice/+page.server.ts new file mode 100644 index 00000000..83127457 --- /dev/null +++ b/src/routes/(app)/invoice/+page.server.ts @@ -0,0 +1,29 @@ +import { auth, post } from "$lib/utils"; +import { error, redirect } from "@sveltejs/kit"; +import type { Invoice } from "$lib/types"; + +export let load = async ({ cookies, params, parent }) => { + let { account } = params; + let { subject, user, rates } = await parent(); + + let invoice: Invoice = { + account, + type: account ? "bitcoin" : "lightning", + rate: rates[user?.currency || subject?.currency], + }; + + if (!user) user = subject; + + try { + invoice = await post("/invoice", { invoice, account, user }, auth(cookies)); + } catch (e) { + console.log(e); + error(500, "Failed to generate invoice"); + } + + let { id } = invoice; + + if (invoice.memoPrompt && !invoice.memo) { + redirect(307, `/invoice/${id}/memo`); + } else redirect(307, `/invoice/${id}`); +}; diff --git a/src/routes/(app)/invoice/+page.svelte b/src/routes/(app)/invoice/+page.svelte new file mode 100644 index 00000000..e69de29b diff --git a/src/routes/(app)/invoice/+server.ts b/src/routes/(app)/invoice/+server.ts new file mode 100644 index 00000000..5e3d0300 --- /dev/null +++ b/src/routes/(app)/invoice/+server.ts @@ -0,0 +1,7 @@ +import { auth, post } from "$lib/utils"; +import { json } from "@sveltejs/kit"; + +export let POST = async ({ cookies: c, request: r }) => { + let j = await r.json(); + return json(await post("/invoice", j, auth(c))); +}; diff --git a/src/routes/(app)/invoice/[id]/+layout.server.ts b/src/routes/(app)/invoice/[id]/+layout.server.ts new file mode 100644 index 00000000..e7144b15 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+layout.server.ts @@ -0,0 +1,36 @@ +import Qr from "qrcode-base64"; +import { get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; + +export async function load({ depends, params, url, parent }) { + depends("app:invoice"); + + let { subject, user } = await parent(); + let { id } = params; + let invoice = await get(`/invoice/${id}`); + let options = !!url.searchParams.get("options"); + + if ( + user && + invoice.uid !== user.id && + !(url.pathname.includes("tip") || url.pathname.includes("memo")) && + !options + ) { + redirect(307, `/send/invoice/${id}`); + } + + let { amount, pending, received } = invoice; + amount = parseInt(amount); + + let paid = + (!amount && (pending || received)) || + (amount > 0 && (pending >= amount || received >= amount)); + if (paid && !url.pathname.endsWith("paid")) { + redirect( 307, `/invoice/${id}/paid` + (options ? "?options=true" : "") + ); + } + + let src = Qr.drawImg(invoice.text || "", { size: 340 }); + + return { id, invoice, subject, src }; +} diff --git a/src/routes/(app)/invoice/[id]/+layout.svelte b/src/routes/(app)/invoice/[id]/+layout.svelte new file mode 100644 index 00000000..4fa864ce --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+layout.svelte @@ -0,0 +1 @@ + diff --git a/src/routes/(app)/invoice/[id]/+page.server.ts b/src/routes/(app)/invoice/[id]/+page.server.ts new file mode 100644 index 00000000..a57efa64 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+page.server.ts @@ -0,0 +1,5 @@ +import invoice from "$lib/invoice"; + +export const actions = { + default: invoice, +}; diff --git a/src/routes/(app)/invoice/[id]/+page.svelte b/src/routes/(app)/invoice/[id]/+page.svelte new file mode 100644 index 00000000..c4810889 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+page.svelte @@ -0,0 +1,351 @@ + + +
+ {#if $showQr} +
+ + {txt} + +
+ {:else} +
+ {txt} +
+ {/if} + + {#each invoice.items as i} +
+
{i.quantity}
+
+ {i.name} +
+
+ {f(i.price * i.quantity, currency)} +
+
+ {sat(btc(i.price * i.quantity, rate))} +
+
+ {/each} + + {#if amount > 0} +
+
+ {f(amountFiat, currency)} + + {#if tip} + + +{f(tip * (rate / sats), currency)} + + {/if} +
+
+ ⚡️{`${s(amount)}`} + + {#if tip} + + +⚡️{s(tip)} + + {/if} +
+
+ {/if} + +
+ + + {#if user?.id === invoice?.user?.id} + + {:else} + + + + {/if} + +
+ +
+
+ + {#if !account} +
+ + + + + +
+ {/if} + + {#if type === types.liquid} +
+
+ {$t("payments.onlyLbtc")} + L-BTC, + {$t("payments.dontDeposit")} +
+
+ {/if} + + {#if user?.id === invoice?.user?.id} + + + + {/if} +
+ +{#if settingAmount} +
+
+
+ +
+ + +
+ + +
+ {$t("payments.amountPrompt")} + +
+
+
+{/if} + + diff --git a/src/routes/(app)/invoice/[id]/memo/+page.server.ts b/src/routes/(app)/invoice/[id]/memo/+page.server.ts new file mode 100644 index 00000000..24dec1af --- /dev/null +++ b/src/routes/(app)/invoice/[id]/memo/+page.server.ts @@ -0,0 +1,13 @@ +import invoice from "$lib/invoice"; +import { redirect } from "@sveltejs/kit"; + +export async function load({ parent, params }) { + let { invoice } = await parent(); + if (!invoice.amount) { + redirect(307, `/invoice/${invoice.id}`); + } +} + +export const actions = { + default: invoice, +}; diff --git a/src/routes/(app)/invoice/[id]/memo/+page.svelte b/src/routes/(app)/invoice/[id]/memo/+page.svelte new file mode 100644 index 00000000..4d23ff19 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/memo/+page.svelte @@ -0,0 +1,95 @@ + + +
+ + + + + + + + + + +

{$t("invoice.addMemo")}

+
+ +
{invoice.memoPrompt}
+
+ + +
diff --git a/src/routes/(app)/invoice/[id]/paid/+page.svelte b/src/routes/(app)/invoice/[id]/paid/+page.svelte new file mode 100644 index 00000000..df05f12b --- /dev/null +++ b/src/routes/(app)/invoice/[id]/paid/+page.svelte @@ -0,0 +1,85 @@ + + +
+ {#if received} +
+ +
+ +

+ {$t("invoice.paymentSuccessful")} +

+ +

+ {f(fiat(received - tip, rate), currency)} + {#if tip} + + + {f(fiat(tip, rate), currency)} + + {/if} +

+

+ ⚡️{s(received - tip)} + + {#if tip} + + + ⚡️{s(tip)} + + {/if} +

+ {/if} + + {#if pending} +
+ +
+ +

Payment detected

+

+ {f(fiat(pending - tip, rate), currency)} + {#if tip} + + {f(fiat(tip, rate), currency)} + {/if} +

+ +

+ ⚡️{s(pending - tip)} + {#if tip} + + + ⚡️{s(tip)} + + {/if} +

+ {/if} +
+ + +
+ + +
+ {$t("payments.tapAnywhere")} +
diff --git a/src/routes/(app)/invoice/[id]/tip/+page.server.ts b/src/routes/(app)/invoice/[id]/tip/+page.server.ts new file mode 100644 index 00000000..8417de91 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/tip/+page.server.ts @@ -0,0 +1,12 @@ +import invoice from "$lib/invoice"; +import { redirect } from "@sveltejs/kit"; + +export async function load({ parent, params }) { + let { invoice } = await parent(); + if (!invoice.amount) + redirect(307, `/invoice/${invoice.id}`); +} + +export const actions = { + default: invoice, +}; diff --git a/src/routes/(app)/invoice/[id]/tip/+page.svelte b/src/routes/(app)/invoice/[id]/tip/+page.svelte new file mode 100644 index 00000000..35b24bf1 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/tip/+page.svelte @@ -0,0 +1,209 @@ + + +
+
+ + + + + + + + + + {#if invoice.memoPrompt} + + {/if} + +

{$t("invoice.addTipq")}

+ {#if !showCustomAmount} +
{Math.round(tipPercent)}%
+ + + +
+
+ + {f(amountFiat, currency)} + +{f(tipAmount, currency)} + +
+ +
+ {sat(amount)} + +{sat(tip)} +
+
+ + {#if tip} +
+ + +
+ {:else} + {#each tipAmounts as amount} + + {/each} + {/if} + +
+ +
+ {/if} + + +
+ + {/if} +
+ + diff --git a/src/routes/(app)/invoice/sent/+page.svelte b/src/routes/(app)/invoice/sent/+page.svelte new file mode 100644 index 00000000..b95bc85e --- /dev/null +++ b/src/routes/(app)/invoice/sent/+page.svelte @@ -0,0 +1,11 @@ + + +Invoice sent to +
+ +

{$request.requester.username} is ready to pay

+
diff --git a/src/routes/(app)/items/[id]/+page.server.ts b/src/routes/(app)/items/[id]/+page.server.ts new file mode 100644 index 00000000..67cebd93 --- /dev/null +++ b/src/routes/(app)/items/[id]/+page.server.ts @@ -0,0 +1,21 @@ +import { fd, auth, get, post } from "$lib/utils"; +import { fail, redirect } from "@sveltejs/kit"; + +export let load = async ({ params: { id } }) => { + let item = await get(`/items/${id}`); + return { item }; +}; + +export const actions = { + default: async ({ cookies, request, url }) => { + let form = await fd(request); + try { + await post(`/items`, form, auth(cookies)); + } catch (e: any) { + return fail(400, { message: e.message }); + } + + let success = `/${url.pathname.split("/")[1]}`; + redirect(307, success); + }, +}; diff --git a/src/routes/(app)/items/[id]/+page.svelte b/src/routes/(app)/items/[id]/+page.svelte new file mode 100644 index 00000000..65720350 --- /dev/null +++ b/src/routes/(app)/items/[id]/+page.svelte @@ -0,0 +1,18 @@ + + +
+

+ {$t("items.edit")} +

+ +
diff --git a/src/routes/(app)/items/create/+page.server.ts b/src/routes/(app)/items/create/+page.server.ts new file mode 100644 index 00000000..2e006a3e --- /dev/null +++ b/src/routes/(app)/items/create/+page.server.ts @@ -0,0 +1,16 @@ +import { fail, redirect } from "@sveltejs/kit"; +import { fd, auth, post } from "$lib/utils"; + +export const actions = { + default: async ({ cookies, request, url }) => { + let form = await fd(request); + try { + await post("/items", form, auth(cookies)); + } catch (e: any) { + return fail(400, { message: e.message }); + } + + let success = `/${url.pathname.split("/")[1]}`; + redirect(307, success); + }, +}; diff --git a/src/routes/(app)/items/create/+page.svelte b/src/routes/(app)/items/create/+page.svelte new file mode 100644 index 00000000..b82889c9 --- /dev/null +++ b/src/routes/(app)/items/create/+page.svelte @@ -0,0 +1,19 @@ + + +
+

+ {$t("items.add")} +

+ +
diff --git a/src/routes/(app)/ln/[lnurl]/+page.server.ts b/src/routes/(app)/ln/[lnurl]/+page.server.ts index d1b1b86e..c078ae8e 100644 --- a/src/routes/(app)/ln/[lnurl]/+page.server.ts +++ b/src/routes/(app)/ln/[lnurl]/+page.server.ts @@ -70,6 +70,6 @@ export const actions = { let c = callback.includes("?") ? "&" : "?"; await fetch(`${callback}${c}k1=${k1}&pr=${pr}`).then((r) => r.json()); - redirect(307, `/${username}/payments`); + redirect(307, `/payments`); }, }; diff --git a/src/routes/(app)/ln/withdrawal/sent/+page.svelte b/src/routes/(app)/ln/withdrawal/sent/+page.svelte index c1a2bdb2..90814960 100644 --- a/src/routes/(app)/ln/withdrawal/sent/+page.svelte +++ b/src/routes/(app)/ln/withdrawal/sent/+page.svelte @@ -19,7 +19,7 @@

Request Sent!

- +
diff --git a/src/routes/(app)/login/+page.server.ts b/src/routes/(app)/login/+page.server.ts deleted file mode 100644 index fd7fec8c..00000000 --- a/src/routes/(app)/login/+page.server.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { fail, redirect } from "@sveltejs/kit"; -import { login, fd } from "$lib/utils"; - -export const load = async ({ parent }) => { - let { user } = await parent(); - if (user?.pubkey) redirect(307, `/${user.username}`); -}; - -export const actions = { - default: async ({ cookies, request }) => { - let form = await fd(request); - let { username, password, token, loginRedirect } = form; - let user = { username, password, token }; - - if (loginRedirect === "undefined") loginRedirect = undefined; - - try { - await login( - user, - cookies, - request.headers.get("cf-connecting-ip") - ); - } catch (e: any) { - return fail(400, { error: "Login failed", message: e.message, ...form }); - } - - redirect(307, loginRedirect || `/${user.username}`); - }, -}; diff --git a/src/routes/(app)/login/+page.svelte b/src/routes/(app)/login/+page.svelte deleted file mode 100644 index 61ea23b7..00000000 --- a/src/routes/(app)/login/+page.svelte +++ /dev/null @@ -1,188 +0,0 @@ - - -{#if need2fa} - -{/if} - -
-
- - - -
- -
-
-

{$t("login.signIn")}

- - {#if form?.error} -
- {form.error} -
- {/if} - -
- - - -
- - -
- -
- - {#if revealPassword} - - {:else} - - {/if} - -
- - - - -
- -

- {$t("login.noAccount")} - - {$t("login.register")} - -

-
-
-
diff --git a/src/routes/(app)/messages/+page.server.ts b/src/routes/(app)/messages/+page.server.ts new file mode 100644 index 00000000..94659621 --- /dev/null +++ b/src/routes/(app)/messages/+page.server.ts @@ -0,0 +1,42 @@ +import { get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; + +interface Message { + created_at: number; +} + +interface Note { + created_at: number; + seen: number; +} + +export async function load({ params, parent, }) { + let { user } = await parent(); + if (user.username !== params.username) redirect(307, `/${params.username}`); + let { since = 0 } = params; + + let messages = [], + notes = []; + + if (user?.pubkey) { + let { pubkey } = user; + try { + messages = await get(`/${pubkey}/${since}/messages`); + messages = messages.sort((a: Message, b: Message) => b.created_at - a.created_at); + } catch (e) { + console.log(`failed to fetch nostr messages`, e); + } + + try { + notes = await get(`/${pubkey}/notes`); + } catch (e) { + console.log(`failed to fetch nostr notes for ${pubkey}`, e); + } + + notes.map((e: Note) => { + e.seen = e.created_at; + }); + } + + return { messages, notes }; +} diff --git a/src/routes/(app)/messages/+page.svelte b/src/routes/(app)/messages/+page.svelte new file mode 100644 index 00000000..547a1f21 --- /dev/null +++ b/src/routes/(app)/messages/+page.svelte @@ -0,0 +1,92 @@ + + +
+

+ {$t("user.messages")} +

+ {#if latest.length && user?.id === subject.id} +
+ {#each latest as { content, pubkey, author, recipient }} + {@const user = author.id === user.id ? recipient : author} + +
+
+ +
+
+
{user.username}
+
+ {content} +
+
+
+
+ {/each} +
+ {:else} +
{$t("user.noMessages")}
+ {/if} +
diff --git a/src/routes/(app)/messages/[recipient]/+page.server.ts b/src/routes/(app)/messages/[recipient]/+page.server.ts new file mode 100644 index 00000000..6be9f6ec --- /dev/null +++ b/src/routes/(app)/messages/[recipient]/+page.server.ts @@ -0,0 +1,24 @@ +import { get } from "$lib/utils"; +import type { Message } from "$lib/types"; + +export async function load({ params, parent }) { + let { user } = await parent(); + let { since = 0 } = params; + + let messages: Message[] = []; + let recipient = await get(`/users/${params.recipient}`); + + try { + messages = await get(`/${user.pubkey}/${since}/messages`); + + messages = messages + .filter( + (m: Message) => m.recipient?.id === recipient.id || m.author?.id === recipient.id + ) + .sort((a, b) => a.created_at - b.created_at); + } catch (e) { + console.log(`failed to fetch nostr messages`, e); + } + + return { messages, recipient, user }; +} diff --git a/src/routes/(app)/messages/[recipient]/+page.svelte b/src/routes/(app)/messages/[recipient]/+page.svelte new file mode 100644 index 00000000..98638389 --- /dev/null +++ b/src/routes/(app)/messages/[recipient]/+page.svelte @@ -0,0 +1,191 @@ + + +
+ + +
+ {#each messages as { id, author, message, created_at, pubkey }} + {@const ours = pubkey === user.pubkey} + {@const theirs = !ours} + + {#if message} +
+
+ {message} +
+
+ +
+
+
+ {format(new Date(created_at * 1000), "MMM d")}, + {format(new Date(created_at * 1000), "h:mm aa")} +
+ {/if} + {/each} +
+ +
+ + + +
+ + +
+ + diff --git a/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts b/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts index e9a0ec9e..7ccc23b0 100644 --- a/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts +++ b/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts @@ -29,7 +29,7 @@ export async function load({ cookies, params, parent }) { auth(cookies) ); - redirect(307, `/${username}/invoice/${id}`); + redirect(307, `/invoice/${id}`); } return { amount, rate, subject }; diff --git a/src/routes/(app)/pay/[username]/[...amount]/+page.svelte b/src/routes/(app)/pay/[username]/[...amount]/+page.svelte index 1652952a..f45218ea 100644 --- a/src/routes/(app)/pay/[username]/[...amount]/+page.svelte +++ b/src/routes/(app)/pay/[username]/[...amount]/+page.svelte @@ -3,7 +3,6 @@ import { goto } from "$app/navigation"; import { pin } from "$lib/store"; import { enhance } from "$app/forms"; - import AppHeader from "$comp/AppHeader.svelte"; import Avatar from "$comp/Avatar.svelte"; import Icon from "$comp/Icon.svelte"; import Numpad from "$comp/Numpad.svelte"; @@ -33,7 +32,7 @@ rate = fiat ? (sats * amountFiat) / amount : r; let id; - ({ id, hash } = await post(`/${subject.username}/invoice`, { + ({ id, hash } = await post(`/invoice`, { invoice: { amount, rate: rates[subject.currency], @@ -56,8 +55,6 @@ }; - - {#if form?.message}
{form.message} diff --git a/src/routes/(app)/payment/[id]/+page.svelte b/src/routes/(app)/payment/[id]/+page.svelte index ea07be14..9a278049 100644 --- a/src/routes/(app)/payment/[id]/+page.svelte +++ b/src/routes/(app)/payment/[id]/+page.svelte @@ -96,7 +96,7 @@ }); - + diff --git a/src/routes/(app)/payments/[...page]/+page.server.ts b/src/routes/(app)/payments/[...page]/+page.server.ts new file mode 100644 index 00000000..c2cd5683 --- /dev/null +++ b/src/routes/(app)/payments/[...page]/+page.server.ts @@ -0,0 +1,28 @@ +import { get, auth } from "$lib/utils"; + +let limit = 25; + +export async function load({ cookies, depends, params }) { + depends("app:payments"); + + let parts = params.page.split("/"); + + let start, end, page; + if (parts.length === 1) [page] = parts; + if (parts.length === 2) [start, page] = parts; + if (parts.length === 3) [start, end, page] = parts; + if (!parseInt(page)) page = 1; + + let offset = (page - 1) * limit; + if (!start) start = Date.now() / 1000 - 24 * 60 * 60; + + let url = `/payments?account=${params.account}&start=${ + start * 1000 + }&limit=${limit}&offset=${offset}`; + if (end) url += `&end=${end * 1000}`; + + let { count, payments, incoming, outgoing } = await get(url, auth(cookies)); + let pages = new Array(Math.ceil(count / limit)); + + return { payments, page, pages, start, end, incoming, outgoing }; +} diff --git a/src/routes/(app)/payments/[...page]/+page.svelte b/src/routes/(app)/payments/[...page]/+page.svelte new file mode 100644 index 00000000..b7ea7b6e --- /dev/null +++ b/src/routes/(app)/payments/[...page]/+page.svelte @@ -0,0 +1,315 @@ + + +
+

+ {$t("payments.header")} +

+ +
+ + +
+ {#if pages.length > 1} + {#each pages as _, i} + +
+ {i + 1} +
+
+ {/each} + {/if} +
+ +
+ {#each payments as p} + +
+
+
+ {f(Math.abs(p.amount) * (p.rate / sats), p.currency)} + {#if p.tip} + + +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% + + {/if} +
+ +
+ {sat(Math.abs(p.amount) + (p.tip || 0))} +
+
+ +
+ {#if p.type === types.pot} + +
+
+ Pot +
+ +
Pot
+
+
+ {:else if p.with} +
+
+ +
+
+ {p.with.username} +
+
+ {:else} +
+ {#if [types.ecash, types.lightning].includes(p.type)} +
⚡️
+ {:else if p.type === types.bitcoin} +
+ Bitcoin +
+ {:else if p.type === types.liquid} +
+ Liquid +
+ {/if} + +
+ {p.amount > 0 + ? p.confirmed + ? "Received" + : "Pending" + : "Sent"} +
+
+ {/if} +
+ +
+
+ {format(new Date(p.created), "h:mm aaa")} +
+
+ {format(new Date(p.created), "MMM d, yy")} +
+
+
+ + {:else} +

{$t("payments.empty")}

+ {/each} +
+ +
+ {#each Object.keys(incoming) as c} + + + + + + {#if tipsIn || tipsOut}{$t("payments.tips")}{/if} + + {$t("payments.total")} + + {@const totalIn = incoming[c]?.fiat || 0} + {@const tipsIn = incoming[c]?.fiatTips || 0} + {@const subtotalIn = totalIn - tipsIn} + + {@const totalOut = -outgoing[c]?.fiat || 0} + {@const tipsOut = outgoing[c]?.fiatTips || 0} + {@const subtotalOut = totalOut - tipsOut} + + {$t("payments.income")} + + {tipsIn ? f(tipsIn, c) : "-"} + {totalIn ? f(totalIn, c) : "-"} + + {$t("payments.expenditure")} + + {tipsOut ? f(tipsOut, c) : "-"} + {totalOut ? f(totalOut, c) : "-"} + {/each} +
+ + +
+
+ + diff --git a/src/routes/(app)/payments/[...page]/+server.ts b/src/routes/(app)/payments/[...page]/+server.ts new file mode 100644 index 00000000..a81faaf3 --- /dev/null +++ b/src/routes/(app)/payments/[...page]/+server.ts @@ -0,0 +1,18 @@ +import { get, auth } from "$lib/utils"; +import { json } from "@sveltejs/kit"; + +export async function GET({ cookies, params }) { + let parts = params.page.split("/"); + + let start, end; + if (parts.length === 1) [start] = parts; + if (parts.length === 2) [start, end] = parts; + + if (!start) start = Date.now() / 1000 - 24 * 60 * 60; + + let url = `/payments?start=${start * 1000}`; + if (end) url += `&end=${end * 1000}`; + + let { payments } = await get(url, auth(cookies)); + return json({ payments }); +} diff --git a/src/routes/(app)/receive/[...token]/+page.svelte b/src/routes/(app)/receive/[...token]/+page.svelte index 2fd4b879..7ff93fa9 100644 --- a/src/routes/(app)/receive/[...token]/+page.svelte +++ b/src/routes/(app)/receive/[...token]/+page.svelte @@ -5,7 +5,6 @@ import { tick } from "svelte"; import { browser } from "$app/environment"; import { t } from "$lib/translations"; - import AppHeader from "$comp/AppHeader.svelte"; import Avatar from "$comp/Avatar.svelte"; import Icon from "$comp/Icon.svelte"; import Spinner from "$comp/Spinner.svelte"; @@ -30,7 +29,6 @@ -

{$t("payments.voucher")} diff --git a/src/routes/(app)/register/+page.server.ts b/src/routes/(app)/register/+page.server.ts deleted file mode 100644 index 731530c4..00000000 --- a/src/routes/(app)/register/+page.server.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { fail, redirect } from "@sveltejs/kit"; -import { fd, post, login } from "$lib/utils"; - -export const load = async ({ parent }) => { - let { user } = await parent(); - if (user) redirect(307, `/${user.username}`); - - let index = Math.floor(Math.random() * 64) + 1; - - return { index }; -}; - -export const actions = { - default: async ({ cookies, request }) => { - let ip = request.headers.get("cf-connecting-ip"); - - let form = await fd(request); - let { profile, username, password, cipher, salt, pubkey, loginRedirect } = - form; - let user = { profile, username, password, cipher, salt, pubkey }; - let error; - - if (loginRedirect === "undefined") loginRedirect = undefined; - - try { - await post("/register", { user }, { "cf-connecting-ip": ip }); - } catch (e: any) { - error = e.message; - } - - try { - await login(user, cookies, ip); - error = null; - } catch (e: any) { - error ||= e.message; - } - - if (error) return fail(400, { error }); - redirect(307, loginRedirect || `/${user.username}`); - }, -}; diff --git a/src/routes/(app)/register/+page.svelte b/src/routes/(app)/register/+page.svelte deleted file mode 100644 index 0a761228..00000000 --- a/src/routes/(app)/register/+page.svelte +++ /dev/null @@ -1,314 +0,0 @@ - - -{#if need2fa} - -{/if} - -
-
- - - -
- -
-
- handleFile(e, "profile")} - /> - -
- - - - - -
- - {#if form?.error} -
- {form.error} -
- {/if} - -
- - - -
- - - -
- -
- - {#if revealPassword} - - {:else} - - {/if} - -
- -

- {$t("login.passwordRecommendation")} -

- - -
- -

- {$t("login.haveAccount")} - - {$t("login.signIn")} - -

-
-
-
diff --git a/src/routes/(app)/send/[...text]/+page.svelte b/src/routes/(app)/send/[...text]/+page.svelte index 780f70c1..d1b6cc23 100644 --- a/src/routes/(app)/send/[...text]/+page.svelte +++ b/src/routes/(app)/send/[...text]/+page.svelte @@ -5,7 +5,6 @@ import { tick } from "svelte"; import { browser } from "$app/environment"; import { t } from "$lib/translations"; - import AppHeader from "$comp/AppHeader.svelte"; import Avatar from "$comp/Avatar.svelte"; import Icon from "$comp/Icon.svelte"; import Spinner from "$comp/Spinner.svelte"; @@ -31,7 +30,6 @@ -

{$t("payments.send")} diff --git a/src/routes/(app)/send/invoice/[id]/+page.server.ts b/src/routes/(app)/send/invoice/[id]/+page.server.ts index ed0198cf..fa7d28dd 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.server.ts +++ b/src/routes/(app)/send/invoice/[id]/+page.server.ts @@ -7,15 +7,15 @@ export async function load({ params: { id }, parent }) { let invoice = await get(`/invoice/${id}`); if (invoice.prompt && invoice.tip === null) - redirect(307, `/${invoice.user.username}/invoice/${id}/tip`); + redirect(307, `/invoice/${id}/tip`); if (invoice.memoPrompt && invoice.memo === null) - redirect(307, `/${invoice.user.username}/invoice/${id}/memo`); + redirect(307, `/invoice/${id}/memo`); if (invoice.user.username === user?.username) error(500, { message: "Cannot send to self" }); - if (!user) redirect(307, `/${invoice.user.username}/invoice/${id}`); + if (!user) redirect(307, `/invoice/${id}`); return { invoice, rates, user }; } diff --git a/src/routes/(app)/send/invoice/[id]/+page.svelte b/src/routes/(app)/send/invoice/[id]/+page.svelte index 81a2c521..75e4e75a 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.svelte +++ b/src/routes/(app)/send/invoice/[id]/+page.svelte @@ -52,7 +52,7 @@ let toggle = () => (submitting = !submitting); let external = async () => { - let { id } = await post(`/${recipient.username}/invoice`, { + let { id } = await post(`/invoice`, { invoice: { ...invoice, type: "lightning", @@ -62,7 +62,7 @@ user: { username: recipient.username }, }); - goto(`/${recipient.username}/invoice/${id}?options=true`, { + goto(`/invoice/${id}?options=true`, { invalidateAll: true, }); }; diff --git a/src/routes/(app)/sent/+page.svelte b/src/routes/(app)/sent/+page.svelte index 136bd395..12e13c41 100644 --- a/src/routes/(app)/sent/+page.svelte +++ b/src/routes/(app)/sent/+page.svelte @@ -43,7 +43,7 @@

- +
diff --git a/src/routes/(app)/settings/[...tab]/+page.server.ts b/src/routes/(app)/settings/[...tab]/+page.server.ts new file mode 100644 index 00000000..42639b38 --- /dev/null +++ b/src/routes/(app)/settings/[...tab]/+page.server.ts @@ -0,0 +1,34 @@ +import { fail, redirect } from "@sveltejs/kit"; +import { fd, auth, get, post } from "$lib/utils"; + +export function load({ cookies, params, url }) { + if (url.pathname.endsWith("settings")) + redirect(307, url.pathname + "/account"); + params.cookies = cookies.getAll(); + return { cookies: cookies.getAll(), tab: params.tab }; +} + +export const actions = { + default: async ({ cookies, request }) => { + let form = await fd(request); + + if (form.tab === "pos") { + form.notify = form.notify === "on"; + form.nip5 = form.nip5 === "on"; + form.prompt = form.prompt === "on"; + form.autowithdraw = form.autowithdraw === "on"; + } + + let user = { ...(await get("/me", auth(cookies))), ...form }; + + try { + ({ user } = await post(`/user`, user, auth(cookies))); + } catch (e: any) { + return fail(400, { message: e.message }); + } + + if (user.language) cookies.set("lang", user.language, { path: "/" }); + + return { user, success: true }; + }, +}; diff --git a/src/routes/(app)/settings/[...tab]/+page.svelte b/src/routes/(app)/settings/[...tab]/+page.svelte new file mode 100644 index 00000000..c495a5cc --- /dev/null +++ b/src/routes/(app)/settings/[...tab]/+page.svelte @@ -0,0 +1,257 @@ + + +{#if user.haspin && $pin?.length !== 6} + +{/if} + +
+ + + +
+
+

+ {$t("user.settings.header")} +

+ +
+ {#each tabs.filter((t) => t.name !== "shopify") as { name, key }} + + + + {/each} +
+
+ + +
+ +
+ +
+
+ + diff --git a/src/routes/(app)/settings/[...tab]/_account.svelte b/src/routes/(app)/settings/[...tab]/_account.svelte new file mode 100644 index 00000000..8e3323c7 --- /dev/null +++ b/src/routes/(app)/settings/[...tab]/_account.svelte @@ -0,0 +1,190 @@ + + +
+ +
+ +
+
+ +
+ + +
+ +
+ +

+ {$t("user.settings.emailDescription")} +

+ +
+ + {#if user.verified} + + {:else if user.email} + + {/if} +
+
+ +
+ {$t("user.settings.profileImage")} + +
+ {#if $avatar || user.profile} +
+ {user.username} +
+ {:else} +
+ +
+ {/if} +
+ + + handleFile(e, "profile")} + /> +
+
+ + {#if tooLarge["avatar"]} +
Max file size 10MB
+ {/if} +
+ +
+
+ {$t("user.settings.bannerImage")} +
+ + {#if $banner || user.banner} + Banner + {:else} +
+ {/if} + + + handleFile(e, "banner")} + /> + + {#if tooLarge["banner"]} +
Max file size 10MB
+ {/if} +
+ +
+ + +
+ +
+ +

+ Coinos can automatically mark orders as Paid if you provide an API token. + +

+

+ Create a new app in Apps > App Development +

+ +

+ Edit the Admin API scopes to enable write_orders permission then install the app into your store and copy the Admin API access + token here. +

+
+ + diff --git a/src/routes/(app)/support/+page.svelte b/src/routes/(app)/support/+page.svelte deleted file mode 100644 index ad31ad1a..00000000 --- a/src/routes/(app)/support/+page.svelte +++ /dev/null @@ -1,139 +0,0 @@ - - - - -{#if user} - -{/if} - -
- {#if sent} -

Thank you!

-
- Someone will be in touch shortly. -
- - - {:else} -

- {$t("user.support.header")} -

- -

- Fill out this form or email us directly at support@coinos.io and we'll do our best to get back to you in a timely manner. -

- -
-
- - -
- -
- - -
- -
- -