diff options
| author | Adam Soltys <[email protected]> | 2023-09-21 11:59:12 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2023-09-21 11:59:12 -0700 |
| commit | 0fc21acf8ba14a3f5e306c9829573f3ac7d0e05f (patch) | |
| tree | 59e749043ecdc3730d97e23ebfe56a2258361631 /src | |
| parent | 92ff34396242e2a22d0c3f8248dc08451afc7cab (diff) | |
| parent | 3d8943e5bc1fbe7904d04531521f5496962e6efc (diff) | |
| download | coinos-ui-0fc21acf8ba14a3f5e306c9829573f3ac7d0e05f.tar.gz coinos-ui-0fc21acf8ba14a3f5e306c9829573f3ac7d0e05f.zip | |
Merge branch 'master' into swapee
Diffstat (limited to 'src')
38 files changed, 2950 insertions, 295 deletions
diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte index b34409ac..5f6f02a5 100644 --- a/src/components/AppHeader.svelte +++ b/src/components/AppHeader.svelte @@ -119,7 +119,7 @@ </OutClick> </div> {:else} - <a href="/login"> + <a href={`/login?redirect=${$page.url.pathname}`}> <button class="bg-white px-5 py-2 rounded-xl font-semibold text-sm" >{$t('nav.signIn')}</button > diff --git a/src/components/Footer.svelte b/src/components/Footer.svelte index 41c4a507..7caaccc6 100644 --- a/src/components/Footer.svelte +++ b/src/components/Footer.svelte @@ -11,11 +11,10 @@ const companyLinks = [ { url: '/docs', titleID: 'documentation' }, { url: '/support', titleID: 'support' }, - { url: '/jobs', titleID: 'jobs' } ]; </script> -<footer class="space-y-10 md:space-y-0 text-xl block md:flex justify-around items-center mb-20"> +<footer class="space-y-10 md:space-y-0 text-xl block md:flex justify-around mb-20"> <div class="space-y-5"> <div class="text-center w-full"> <a href="/"><Icon icon="logo" style="w-48 mx-auto md:mx-0" /></a> diff --git a/src/components/LocationsMap.svelte b/src/components/LocationsMap.svelte index b3038ddc..277d75a6 100644 --- a/src/components/LocationsMap.svelte +++ b/src/components/LocationsMap.svelte @@ -4,29 +4,58 @@ import { onMount, onDestroy } from 'svelte'; import { browser } from '$app/environment'; + import html2canvas from 'html2canvas'; + + function captureHighResMap(mapElement, scale = 2) { + // Backup original size + let originalWidth = mapElement.offsetWidth; + let originalHeight = mapElement.offsetHeight; + + // Scale the map element + mapElement.style.transform = `scale(${scale})`; + mapElement.style.transformOrigin = 'top left'; + mapElement.style.width = originalWidth * scale + 'px'; + mapElement.style.height = originalHeight * scale + 'px'; + + return html2canvas(mapElement, { + width: originalWidth * scale, + height: originalHeight * scale, + scale: scale + }).then((canvas) => { + // Restore the original size + mapElement.style.transform = ''; + mapElement.style.width = originalWidth + 'px'; + mapElement.style.height = originalHeight + 'px'; + + return canvas; + }); + } + let mapElement; let map; onMount(async () => { if (browser) { const L = await import('leaflet'); - const { MarkerClusterGroup } = await import('leaflet.markercluster'); - map = L.map(mapElement, { attributionControl: false }).setView([0, 0], 0); + map = L.map(mapElement, { + zoomSnap: 0, + attributionControl: false, + }).setView([49.26, -123.05], 12); - const myCustomColour = 'orange'; + + const myCustomColour = '#F7931A'; const markerHtmlStyles = ` background-color: ${myCustomColour}; - width: 3rem; - height: 3rem; + width: 1.2rem; + height: 1.2rem; display: block; - left: -1.5rem; - top: -1.5rem; position: relative; - border-radius: 3rem 3rem 0; + border-radius: 100% 100% 0; transform: rotate(45deg); - border: 1px solid #FFFFFF`; + opacity: 0.9; + border: 1px solid #666`; const icon = L.divIcon({ className: 'my-custom-pin', @@ -67,12 +96,15 @@ } }; - let markers = new MarkerClusterGroup(); locations.forEach((location) => { if (location['deleted_at']) return; location = location['osm_json']; - let marker = L.marker([location.lat, location.lon]); + let marker = L.marker([location.lat, location.lon], { icon }); + + if (location.tags && location.tags.name) { + marker.bindTooltip(location.tags.name, { permanent: true }).openTooltip(); + } marker.bindPopup( `${ @@ -143,11 +175,10 @@ : '' }` ); - markers.addLayer(marker); + marker.addTo(map); }); - map.fitBounds(locations.map(({ osm_json: { lat, lon } }) => [lat, lon])); - map.addLayer(markers); + // map.fitBounds(locations.map(({ osm_json: { lat, lon } }) => [lat, lon])); } }); @@ -155,12 +186,20 @@ </script> <div class="container mx-auto max-w-4xl"> - <div class="flex w-full"> - <div class="mx-auto h-[450px] w-full z-0" bind:this={mapElement} /> - </div> + <div class="flex w-full"> + <div class="mx-auto h-[550px] w-full z-0" bind:this={mapElement} /> + </div> </div> <style> @import 'leaflet/dist/leaflet.css'; - @import 'leaflet.markercluster/dist/MarkerCluster.Default.css'; + + :global(.leaflet-tooltip) { + border: none; + border-radius: 4px; + font-size: 12px; + font-weight: bolder; + color: black; + background-color: rgba(255, 255, 255, 0.5); + } </style> diff --git a/src/lib/english.js b/src/lib/english.js new file mode 100644 index 00000000..d6c205d9 --- /dev/null +++ b/src/lib/english.js @@ -0,0 +1,2048 @@ +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.js b/src/lib/nostr.js index 1924643e..987b89d0 100644 --- a/src/lib/nostr.js +++ b/src/lib/nostr.js @@ -5,7 +5,7 @@ import { decrypted, password as pw, passwordPrompt, pin, loginRedirect } from '$ import { goto, invalidate } from '$app/navigation'; import { getPublicKey, signEvent, getEventHash, nip04, nip06 } from 'nostr-tools'; import { mnemonicToEntropy, entropyToMnemonic } from '@scure/bip39'; -import { wordlist } from '@scure/bip39/wordlists/english.js'; +import wordlist from '$lib/english'; import { bech32m } from '@scure/base'; const { generateSeedWords, privateKeyFromSeedWords } = nip06; diff --git a/src/lib/samples.js b/src/lib/samples.js index 12d68d21..ac9e2d7e 100644 --- a/src/lib/samples.js +++ b/src/lib/samples.js @@ -1,40 +1,55 @@ export default { - register: `curl "https://swapee.me/api/register" -H "content-type: application/json" -d '{ - "username": "demo", - "password": "hunter2" + register: `curl "https://coinos.io/api/register" -H "content-type: application/json" -d '{ + "user": { + "username": "demo", + "password": "hunter2" + } }'`, - login: `curl "https://swapee.me/api/login" -H "content-type: application/json" -d '{ + login: `curl "https://coinos.io/api/login" -H "content-type: application/json" -d '{ "username": "demo", "password": "hunter2" }'`, - payments: `curl "https://swapee.me/api/payments" + payments: `curl "https://coinos.io/api/payments" -H "content-type: application/json" -H "Authorization: Bearer $token" `, - lightning: `curl "https://swapee.me/api/payments" + lightning: `curl "https://coinos.io/api/payments" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ "payreq": "lnbc1pj94d8fsp5n77k340ps4m9jn7kp8he8yynpddvurv6mcsrrqpnq5l2jxdxzlwqpp5m96pqhc5nrlk8cqsu9ufdxxa43sarp8vwf9egvm2pg9nl0zu9r8qdq2vdhkjmn0wvxqztgcqpjrzjqwhmav82kntsppmkp8jp4vg4h9nns78tsy8mg7ve4lq5txrkp0h56zlarvqqdtcqqsqqqqqqqqqqp6cq9q9qyysgqzxlypywzphyujm3ga5j5csfcmqvlnae0fgnvymkaaw94eeg7py5rqzysyjkr3ev2snq63qpsc69vf54adkd0szvmvwt5cuadjnuy95sq4xfa40" }'`, - bitcoin: `curl "https://swapee.me/api/payments" + bitcoin: `curl "https://coinos.io/api/payments" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ "amount": 5000, "hash": "bc1q3unh97w4rmelflrm2hvdwz37d8kray3vn4d5ca" }'`, - internal: `curl "https://swapee.me/api/payments" + internal: `HASH=$(curl "https://coinos.io/api/invoice" + -H "content-type: application/json" + -H "Authorization: Bearer $token" + -d '{ + "invoice": { + "amount": 1000, + "type": "lightning" + }, + "user": { + "username": "alice" + } + }' | jq -r '.hash'); + + curl "https://coinos.io/api/payments" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ "amount": 5000, - "username": "alice" + "hash": $HASH }'`, - rates: `curl "https://swapee.me/api/rates" + rates: `curl "https://coinos.io/api/rates" -H "content-type: application/json" }'`, - lightningInvoice: `curl "https://swapee.me/api/invoice" + lightningInvoice: `curl "https://coinos.io/api/invoice" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ @@ -43,7 +58,7 @@ export default { "type": "lightning" } }'`, - bitcoinAddress: `curl "https://swapee.me/api/invoice" + bitcoinAddress: `curl "https://coinos.io/api/invoice" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ @@ -52,7 +67,7 @@ export default { "type": "bitcoin" } }'`, - webhook: `curl "https://swapee.me/api/invoice" + webhook: `curl "https://coinos.io/api/invoice" -H "content-type: application/json" -H "Authorization: Bearer $token" -d '{ @@ -62,7 +77,7 @@ export default { "secret": "webhooksecret" } }'`, - fetchInvoice: `curl "https://swapee.me/api/invoice/bc1qmhfk9stzffhd9umzmld92vff7zg3mdlh7rvvaj" + fetchInvoice: `curl "https://coinos.io/api/invoice/bc1qmhfk9stzffhd9umzmld92vff7zg3mdlh7rvvaj" -H "content-type: application/json"'`, invoiceResponse: `{ "amount":3141 @@ -77,5 +92,5 @@ export default { "received":0 "created":1677537428134 }`, - socketAuth: `echo '{"type":"login","data":{"username":"user","password":"password"}}' | websocat -n https://swapee.me/ws` + socketAuth: `echo '{"type":"login","data":{"username":"user","password":"password"}}' | websocat -n https://coinos.io/ws` }; diff --git a/src/lib/translations/index.js b/src/lib/translations/index.js index 8d1b9feb..5f4f1754 100644 --- a/src/lib/translations/index.js +++ b/src/lib/translations/index.js @@ -20,7 +20,7 @@ const loaders = availableLocales.map(createLocaleLoader); const config = { initLocale: 'en', fallbackLocale: 'en', - translations: translations, + translations, loaders: [ { locale: 'en', diff --git a/src/locales/de.json b/src/locales/de.json index 97d52771..55d647fc 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -5,8 +5,8 @@ }, "about": { "header": "Über uns", - "p1": "Swapee ist eine Open-Source-Bitcoin-Bezahl-App. Im September 2012 starteten wir die Entwicklung in Vancouver, um lokalen Händlern eine bequeme Möglichkeit zu bieten, Bitcoin-Zahlungen zu akzeptieren.", - "p2": "Sie können den Quellcode herunterladen, um die Swapee-App mit Ihrem eigenen Bitcoin- und Lightning-Node zu verbinden. Gerne helfen wir Ihnen dabei, ein wertvoller Dienstleister sowie Zahlungsanbieter für Ihre Kunden zu werden.", + "p1": "Coinos ist eine Open-Source-Bitcoin-Bezahl-App. Im September 2012 starteten wir die Entwicklung in Vancouver, um lokalen Händlern eine bequeme Möglichkeit zu bieten, Bitcoin-Zahlungen zu akzeptieren.", + "p2": "Sie können den Quellcode herunterladen, um die Coinos-App mit Ihrem eigenen Bitcoin- und Lightning-Node zu verbinden. Gerne helfen wir Ihnen dabei, ein wertvoller Dienstleister sowie Zahlungsanbieter für Ihre Kunden zu werden.", "p3": "Transaktionen zwischen Nutzerkonten auf unserem Server werden Off-Chain durchgeführt. Diese werden sofort abgewickelt und sind völlig kostenlos sowie unlimitiert.", "team": { "header": "Das Team", @@ -44,7 +44,7 @@ }, "locations": { "header": "Standorte", - "description": "Hier finden Sie einige Unternehmen, welche bereits Zahlungen mit Bitcoin über Swapee akzeptieren. Wir hoffen, dass Ihr Unternehmen das nächste sein wird!" + "description": "Hier finden Sie einige Unternehmen, welche bereits Zahlungen mit Bitcoin über Coinos akzeptieren. Wir hoffen, dass Ihr Unternehmen das nächste sein wird!" } }, "error": { @@ -54,17 +54,41 @@ }, "faq": { "header": "FAQ", + "why": { + "question": "Warum Bitcoin akzeptieren?", + "answer": "Mit Bitcoin sparen Händler bei den Gebühren, Verbraucher gewinnen an Privatsphäre und Sparer an Kaufkraft. Wir bewerben Ihr Geschäft bei einer begeisterten Gemeinschaft, die bevorzugt in Geschäften einkauft, die Bitcoin akzeptieren." + }, "cost": { - "question": "Welche Gebühren fallen an?", - "answer": "Die Registrierung eines Kontos sowie Zahlungen über das Lightning-Netzwerk zu erhalten ist vollkommen kostenlos. Wir erheben eine Gebühr von 0.1%, um Lightning-Bitcoin in Bitcoin umzuwandeln." + "question": "Was kostet das?", + "answer": "Zahlungen auf Ihr Konto zu erhalten ist kostenlos. Auszahlungen an externe Wallets verursachen eine geringe Gebühr, die je nach Netzwerkbedingungen variiert. Wir berechnen 0,1% für die Umwandlung zwischen Lightning und On-Chain Bitcoin. Das Senden zu anderen Coinos-Konten ist kostenlos." + }, + "individuals": { + "question": "Wer kann es nutzen?", + "answer": "Jeder, überall auf der Welt. Coinos ist sowohl für Einzelpersonen als auch für Unternehmen geeignet." }, "compatibility": { - "question": "Benötige ich ein bestimmtes Gerät oder eine spezielle Software dafür?", - "answer": "Nein. Swapee ist web-basiert, das bedeutet, solange Sie über eine aktive Internetverbindung verfügen, können Sie Swapee auf jedem Endgerät nutzen. Einzelhändlern empfehlen wir, ein Telefon oder Tablet zu verwenden." + "question": "Benötige ich ein spezielles Gerät oder eine spezielle Software?", + "answer": "Nein, Coinos ist eine Web-App, die überall funktioniert, wo es Internet gibt. Händler könnten es auf einem Tablet verwenden oder einen QR-Code anbieten, den Kunden mit ihrem Handy scannen können." }, "safety": { - "question": "Wann wird mein Guthaben ausgezahlt?", - "answer": "Lightning-Zahlungen werden sofort abgewickelt. Sie können Ihr Guthaben somit unmittelbar nach Zahlungseingang auf eine andere Wallet übertragen." + "question": "Wann wird mein Guthaben beglichen?", + "answer": "Lightning-Zahlungen werden sofort beglichen. Sie können Ihr Guthaben innerhalb von Sekunden auf eine andere Wallet übertragen. On-Chain Einzahlungen dauern im Durchschnitt 5-10 Minuten zur Bestätigung." + }, + "pos": { + "question": "Kann Coinos mit meinem vorhandenen PoS integriert werden?", + "answer": "Wir empfehlen, Verkäufe in Ihrem vorhandenen PoS als Bartransaktionen einzugeben." + }, + "accounting": { + "question": "Was ist mit der Buchhaltung?", + "answer": "Wir zeigen alle Zahlungen in Ihrer lokalen Währung an und ermöglichen es Ihnen, nach Datum zu filtern, sodass Sie Ihr Einkommen wie gewohnt melden können. Sie können den Verlauf Ihres Kontos in eine CSV-Datei exportieren. Wenn Sie Bitcoin mit Gewinn verkaufen, möchten Sie vielleicht Kapitalerträge melden." + }, + "offramp": { + "question": "Kann ich den erhaltenen Bitcoin in meine lokale Währung umwandeln?", + "answer": "Sie können Ihre Gelder jederzeit an eine Börse oder einen Makler abziehen, um Ihren Bitcoin zu verkaufen und eine Banküberweisung zu erhalten. Wir könnten bereit sein, direkte Abrechnungen per E-Transfer für ausgewählte kanadische Händler anzubieten." + }, + "bitcoin": { + "question": "Was kann ich mit Bitcoin machen?", + "answer": "Geben Sie es bei anderen Händlern aus oder decken Sie damit Ihre Ausgaben, indem Sie Ihr Personal, Ihre Lieferanten und Ihren Vermieter dazu bringen, es zu akzeptieren." } }, "footer": { @@ -153,7 +177,7 @@ "settings": "Einstellungen", "support": "Support" }, - "newSiteHeader": "Kommen Sie zu unserer <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">Eröffnungsfeier</a> in Vancouver am 26. Oktober! Auf der Suche nach der alten Swapee-Version? Besuchen Sie <a href=\"https://classic.swapee.me\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.swapee.me</a>", + "newSiteHeader": "Kommen Sie zu unserer <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">Eröffnungsfeier</a> in Vancouver am 26. Oktober! Auf der Suche nach der alten Coinos-Version? Besuchen Sie <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", "news": { "header": "News", "subheader": "Letzte Updates", @@ -195,7 +219,7 @@ "scan": "Scannen", "paste": "Einfügen", "next": "Weiter", - "placeholder": "Geben Sie eine Bitcoin-Adresse, eine Lightning-Rechnung oder einen Swapee-Benutzernamen ein" + "placeholder": "Geben Sie eine Bitcoin-Adresse, eine Lightning-Rechnung oder einen Coinos-Benutzernamen ein" }, "settings": { "bannerImage": "Logo", @@ -214,7 +238,7 @@ "emailNotifications": "E-Mail-Benachrichtigungen", "emailNotificationsDescription": "Sie werden per E-Mail benachrichtigt, wenn eine Änderung auf Ihrem Konto vorgenommen wurde oder Ihnen jemand Zahlungen sendet.", "smsNotifications": "SMS-Benachrichtigungen", - "smsNotificationsDescription": "Swapee wird Benachrichtigungen per SMS zusenden, wenn diese Funktion aktiviert ist.", + "smsNotificationsDescription": "Coinos wird Benachrichtigungen per SMS zusenden, wenn diese Funktion aktiviert ist.", "theme": "Farbdesign", "themeDescription": "Wählen Sie aus unseren Standard-Farbdesigns.", "localCurrency": "Landeswährung", diff --git a/src/locales/el.json b/src/locales/el.json index 7d43b786..054dcff1 100644 --- a/src/locales/el.json +++ b/src/locales/el.json @@ -29,20 +29,44 @@ "signIn": "Συνδεθείτε για να συνεχίσετε." }, "faq": { - "header": "Συχνές ερωτήσεις", + "header": "Συχνές Ερωτήσεις", + "why": { + "question": "Γιατί να δεχτώ Bitcoin;", + "answer": "Με το Bitcoin, οι εμπόροι εξοικονομούν σε τέλη, οι καταναλωτές αποκτούν ιδιωτικότητα και οι αποταμιευτές αυξάνουν την αγοραστική τους δύναμη. Θα προωθήσουμε την επιχείρησή σας σε μια ενθουσιασμένη κοινότητα που προτιμά να ψωνίζει σε μέρη που δέχονται Bitcoin." + }, "cost": { - "question": "Πόσο κοστίζει", - "answer": "Η εγγραφή λογαριασμού και η λήψη πληρωμών είναι δωρεάν. Χρεώνουμε ένα τέλος μετατροπής 0,1% εάν κάνετε ανάληψη κανονικού bitcoin σε Lightning ή το αντίστροφο." + "question": "Πόσο κοστίζει;", + "answer": "Η λήψη πληρωμών στον λογαριασμό σας είναι δωρεάν. Οι αναλήψεις σε εξωτερικά πορτοφόλια έχουν μικρή χρέωση που διαφέρει ανάλογα με τις συνθήκες του δικτύου. Χρεώνουμε 0,1% για τη μετατροπή μεταξύ lightning και on-chain Bitcoin. Η αποστολή σε άλλους λογαριασμούς Coinos είναι δωρεάν." + }, + "individuals": { + "question": "Ποιος μπορεί να το χρησιμοποιήσει;", + "answer": "Οποιοσδήποτε, οπουδήποτε στον κόσμο. Το Coinos είναι κατάλληλο τόσο για ιδιώτες όσο και για επιχειρήσεις." }, "compatibility": { "question": "Χρειάζομαι κάποια ειδική συσκευή ή λογισμικό;", - "answer": "Όχι. Το Coinos βασίζεται στον ιστό, επομένως, εφόσον έχετε σύνδεση στο Διαδίκτυο και πρόγραμμα περιήγησης ιστού, λειτουργεί οπουδήποτε. Οι έμποροι λιανικής ενδέχεται να το χρησιμοποιήσουν σε τηλέφωνο ή tablet." + "answer": "Όχι, το Coinos είναι μια εφαρμογή ιστού που λειτουργεί οπουδήποτε υπάρχει Ίντερνετ. Οι εμπόροι μπορεί να το χρησιμοποιούν σε tablet ή να παρέχουν έναν κώδικα QR για τους πελάτες να σκανάρουν με το τηλέφωνό τους." }, "safety": { - "question": "Πότε διακανονίζονται τα χρήματά μου;", - "answer": "Οι πληρωμές Lightning διακανονίζονται άμεσα. Μπορείτε να κάνετε ανάληψη του υπολοίπου σας σε άλλο πορτοφόλι ή να το ανταλλάξετε αμέσως μόλις λάβετε χρήματα." + "question": "Πότε εξοφλούνται τα χρήματά μου;", + "answer": "Οι πληρωμές μέσω Lightning εξοφλούνται άμεσα. Μπορείτε να αναλάβετε το υπόλοιπό σας σε άλλο πορτοφόλι μέσα σε δευτερόλεπτα. Οι καταθέσεις on-chain χρειάζονται κατά μέσο όρο 5-10 λεπτά για επιβεβαίωση." + }, + "pos": { + "question": "Μπορεί το Coinos να ενσωματωθεί στο υπάρχον PoS μου;", + "answer": "Συνιστούμε να καταχωρίσετε τις πωλήσεις στο υπάρχον PoS σας ως συναλλαγές μετρητών." + }, + "accounting": { + "question": "Τι γίνεται με τη λογιστική;", + "answer": "Εμφανίζουμε όλες τις πληρωμές σε όρους του τοπικού σας νομίσματος και σας επιτρέπουμε να φιλτράρετε ανά ημερομηνία, ώστε να μπορείτε να δηλώνετε το εισόδημά σας κανονικά. Μπορείτε να εξαγάγετε το ιστορικό του λογαριασμού σας σε ένα αρχείο csv. Εάν πουλήσετε Bitcoin με κέρδος, μπορεί να επιθυμείτε να αναφέρετε τα κερδηφόρα κεφάλαια." + }, + "offramp": { + "question": "Μπορώ να μετατρέψω το Bitcoin που λαμβάνω στο τοπικό μου νόμισμα;", + "answer": "Μπορείτε να αναλάβετε τα χρήματά σας ανά πάσα στιγμή σε ένα χρηματιστήριο ή σε έναν μεσίτη για να πουλήσετε το Bitcoin σας και να λάβετε μια τραπεζική μεταφορά. Ίσως είμαστε πρόθυμοι να προσφέρουμε άμεσες εκκαθαρίσεις μέσω E-Transfer για επιλεγμένους καναδικούς εμπόρους." + }, + "bitcoin": { + "question": "Τι μπορώ να κάνω με το Bitcoin;", + "answer": "Ξοδέψτε το σε άλλους εμπόρους ή καλύψτε τις δαπάνες σας παίρνοντας το προσωπικό σας, τους προμηθευτές σας και τον ιδιοκτήτη να το δεχτούν." } - }, + }, "footer": { "company": "Εταιρία", "followUs": "Ακολουθησε μας", diff --git a/src/locales/en.json b/src/locales/en.json index 402aa510..7b3fca1c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -12,8 +12,8 @@ "header": "The Team", "adam": { "name": "Adam Soltys", - "position": "Chief Technical Officer", - "bio": "Adam is a web developer and bitcoin enthusiast who believes sound money is key to increasing global prosperity. He lives in North Vancouver with his wife Chelsea and two children." + "position": "Founder and CTO", + "bio": "Adam is a software engineer who believes that the adoption of sound money will drive global prosperity. He lives in North Vancouver with his wife, daughter, and son." }, "cole": { "name": "Cole", @@ -33,9 +33,17 @@ }, "faq": { "header": "FAQ", + "why": { + "question": "Why accept Bitcoin?", + "answer": "With Bitcoin, merchants save on fees, consumers gain privacy and savers gain purchasing power. We'll promote your business to an enthusiastic community that prefers to shop at places that take Bitcoin." + }, "cost": { "question": "What does it cost?", - "answer": "It's free to register an account and receive payments. We charge a 0.1% conversion fee if you withdraw regular bitcoin to lightning or vice versa." + "answer": "Receiving payments into your account is free. Withdrawals to external wallets incur a small fee that varies depending on network conditions. We charge 0.1% to convert between lightning and on-chain Bitcoin. Sending to other Coinos accounts is free." + }, + "individuals": { + "question": "Who can use it?", + "answer": "Anyone, anywhere in the world. Coinos is suitable for individuals and businesses." }, "compatibility": { "question": "Do I need any special device or software?", @@ -43,8 +51,24 @@ }, "safety": { "question": "When do my funds settle?", - "answer": "Lightning payments are settled instantly. You can withdraw your balance to another wallet or exchange immediately once funds are received." - } + "answer": "Lightning payments are settled instantly. You can withdraw your balance to another wallet within seconds. On-chain deposits take 5-10 minutes to confirm on average." + }, + "pos": { + "question": "Can Coinos integrate with my existing PoS?", + "answer": "We recommend entering sales into your existing PoS as cash transactions." + }, + "accounting": { + "question": "What about accounting?", + "answer": "We display all payments in terms of your local currency and let you filter by date so you can report your income as normal. You can export your account history to a csv file. If you sell Bitcoin for a profit you may wish to report capital gains." + }, + "offramp": { + "question": "Can I convert the Bitcoin I receive to my local currency?", + "answer": "You can withdraw your funds at any time to an exchange or brokerage to sell your Bitcoin and receive a bank transfer. We may be willing to provide direct settlement via e-transfer to select Canadian merchants." + }, + "bitcoin": { + "question": "What can I do with Bitcoin?", + "answer": "Spend it at other merchants or pay your expenses with it by getting your staff, suppliers and landlord to take it." + } }, "footer": { "company": "Company", @@ -69,7 +93,7 @@ }, "step3": { "header": "3. That's all!", - "description": "Spend your coin immediately or save it and watch it grow, the choice is yours." + "description": "Spend your coin immediately or watch it grow in value." } }, "invoice": { @@ -93,10 +117,10 @@ "header_before_icon": "The", "header_with_icon": "easiest", "header_after_icon": "way to get started with bitcoin.", - "subheader": "A simple but powerful web wallet for everyone. No software or hardware required.", + "subheader": "A free web wallet and payment page for everyone.", "info1": { "title": "Instant payments via Lightning", - "description": "We make it easy for merchants to receive retail payments. No waiting for bank settlements or worrying about chargebacks. Instant settlement, in seconds. Say goodbye to 3% credit card processing fees." + "description": "We make it easy for merchants to receive retail payments. No waiting for bank settlements or worrying about chargebacks. Instant settlement, in seconds. Say goodbye to 3% card fees." }, "info2": { "title": "Simple to use", @@ -237,6 +261,16 @@ } }, "payments": { + "moneyPot": "Money Pot", + "addFunds": "Add Funds", + "takeFunds": "Take Funds", + "show": "Show", + "hide": "Hide", + "qr": "QR", + "copyLink": "Copy Link", + "copyText": "Copy Text", + "openLink": "Open Wallet", + "moreOptions": "More options", "total": "Total", "gainLoss": "Gain/Loss", "presentValue": "Present Value", diff --git a/src/locales/es.json b/src/locales/es.json index 75387f35..55e7e8f9 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -31,21 +31,45 @@ "message": "Vuelva a intentarlo o póngase en contacto con el soporte.", "signIn": "Por favor inicie sesión para continuar." }, - "faq": { - "header": "Preguntas frecuentes", - "cost": { - "question": "¿Cuánto cuesta?", - "answer": "Registrar una cuenta y recibir pagos a través de Red Lightning es gratis. Cobramos una tarifa del 0,1% por convertir de lightning a bitcoin." - }, - "compatibility": { - "question": "¿Necesito algún dispositivo o software especial?", - "answer": "No. Swapee es una aplicación web, por lo que siempre que tenga conexión a Internet, podrá usarlo desde cualquier dispositivo. Las tiendas minoristas pueden usarlo desde un teléfono o una tableta." - }, - "safety": { - "question": "¿Cuándo se liquidan mis fondos?", - "answer": "Los pagos en Red Lightning se liquidan al instante. Puede retirar su saldo a otra billetera o exchange inmediatamente después de recibir el pago." - } - }, + "faq": { + "header": "FAQ", + "why": { + "question": "¿Por qué aceptar Bitcoin?", + "answer": "Con Bitcoin, los comerciantes ahorran en comisiones, los consumidores obtienen privacidad y los ahorradores ganan poder adquisitivo. Promocionaremos tu negocio a una comunidad entusiasta que prefiere comprar en lugares que aceptan Bitcoin." + }, + "cost": { + "question": "¿Cuánto cuesta?", + "answer": "Recibir pagos en tu cuenta es gratis. Las retiradas a carteras externas incurren en una pequeña tarifa que varía según las condiciones de la red. Cobramos el 0.1% para convertir entre Bitcoin lightning y on-chain. Enviar a otras cuentas de Swapee es gratis." + }, + "individuals": { + "question": "¿Quién puede usarlo?", + "answer": "Cualquiera, en cualquier parte del mundo. Swapee es adecuado para individuos y negocios." + }, + "compatibility": { + "question": "¿Necesito algún dispositivo o software especial?", + "answer": "No, Swapee es una aplicación web que funciona en cualquier lugar que tenga Internet. Los comerciantes pueden usarla en una tableta o mostrar un código QR para que los clientes lo escaneen con sus teléfonos." + }, + "safety": { + "question": "¿Cuándo se liquidan mis fondos?", + "answer": "Los pagos lightning se liquidan al instante. Puedes retirar tu saldo a otra cartera en segundos. Los depósitos en cadena tardan en promedio de 5 a 10 minutos en confirmarse." + }, + "pos": { + "question": "¿Puede Swapee integrarse con mi PoS existente?", + "answer": "Recomendamos ingresar las ventas en tu PoS existente como transacciones en efectivo." + }, + "accounting": { + "question": "¿Qué pasa con la contabilidad?", + "answer": "Mostramos todos los pagos en términos de tu moneda local y te permitimos filtrar por fecha para que puedas informar tus ingresos como de costumbre. Puedes exportar tu historial de cuenta a un archivo csv. Si vendes Bitcoin con beneficio, es posible que desees informar las ganancias de capital." + }, + "offramp": { + "question": "¿Puedo convertir el Bitcoin que recibo a mi moneda local?", + "answer": "Puedes retirar tus fondos en cualquier momento a un exchange o correduría para vender tu Bitcoin y recibir una transferencia bancaria. Es posible que estemos dispuestos a proporcionar liquidación directa a través de e-transferencia a comerciantes canadienses seleccionados." + }, + "bitcoin": { + "question": "¿Qué puedo hacer con Bitcoin?", + "answer": "Gástalo en otros comerciantes o paga tus gastos con él al conseguir que tu personal, proveedores y arrendador lo acepten." + } + }, "footer": { "company": "Empresa", "followUs": "Síguenos", diff --git a/src/locales/fr.json b/src/locales/fr.json index 91d5fc9b..8097ce1f 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -5,9 +5,6 @@ }, "about": { "header": "À Propos", - "p1": "<strong>swapee</strong> est un portefeuille, point de vente, marché d'e-commerce et plateforme d'échange de bitcoin <strong>open source</strong>. Development a commencé en Vancouver le septembre de 2012 pour donner aux marchands du coin un façon pratique d'accepter des paiements en bitcoin.", - "p2": "Il a eu plus de fonctions au fil des ans pour satisfaire une grande variété de cas d'utilisation dans le but de pouvoir utiliser bitcoin rapidement, à bas prix, et en sécurité aussi façile que possible pour n'importe qui.", - "p3": "Tout le monde peux télécharger le code source et héberger leur propre instance de la plateforme soutenue par leur propres nœuds de bitcoin, liquid ou lightning, ou contactez-nous et nous l'installerons pour vous avec plaisir.", "locations": { "header": "Établissements", "description": "Il y a des entreprises qui acceptent bitcoin utilisant le point de vente swapee. Si vous utilisez notre appli, s'il vous plait <a href=\"mailto:[email protected]\" class=\"underline underline-offset-4\">fais-le-nous savoir!</a>" @@ -19,30 +16,10 @@ "position": "Officier Technique Principal", "bio": "Adam est développeur web et passionné de bitcoin. Il croit que l'argent sûr est la clé pour augmenter la prospérité planétaire. Il habite en Vancouver de Nord avec sa femme Chelsea et deux enfants." }, - "secondl1ght": { - "name": "secondl1ght", - "position": "Développeur Web", - "bio": "Secondl1ght est un développeur web autodidacte. Il a découvert bitcoin et quitté son job de monnaie fiduciaire pour développer bitcoin à temps plein. Il contribue aux projets open source, est défenseur de la vie privée, et il aime la liberté." - }, - "karnage": { - "name": "Karnage", - "position": "Designer des Produits", - "bio": "Ma mission est d'aider les fondateurs des start-ups à réussir. Je crée des produits pour achever cette mission. Designe des produits pixel-parfait. Aie-l'expédié." - }, - "shauna": { - "name": "Shauna", - "position": "Gestion & Marketing", - "bio": "Gestion de projets et strategie. Touche-à-tout. Peut toujours trouver le temps pour racontrer pour café." - }, "cole": { "name": "Cole Goodwin", "position": "Professionel de Bitcoin", "bio": "Cole est un professionel de bitcoin diplômé et un homme motivé avec passion pour la liberté. Il a découvert bitcoin en 2016 et l'a etudié en détail. Maintenant, il veux introduire le réseau Lightning à autant d'utilisateurs que possible." - }, - "john": { - "name": "John Barratt", - "position": "Développeur Web Full-Stack", - "bio": "Quand il ne programme pas, vous pouvez trouver John en forêt randonnant sur les sentiers de Colombie-Britannique." } } }, @@ -53,17 +30,41 @@ }, "faq": { "header": "FAQ", + "why": { + "question": "Pourquoi accepter le Bitcoin?", + "answer": "Avec le Bitcoin, les commerçants économisent sur les frais, les consommateurs bénéficient de la confidentialité et les épargnants gagnent en pouvoir d'achat. Nous promouvrons votre entreprise auprès d'une communauté enthousiaste qui préfère faire ses achats dans des lieux acceptant le Bitcoin." + }, "cost": { - "question": "Quel est le coût?", - "answer": "swapee est gratuit à utiliser. Vous payez seulment 1% quand vous convertissez vos fonds entre bitcoin, Liquid et Lightning. Comparez-ça aux frais typiques 3% des cartes de crédit." + "question": "Combien cela coûte-t-il?", + "answer": "Recevoir des paiements sur votre compte est gratuit. Les retraits vers des portefeuilles externes entraînent des frais minimes qui varient en fonction des conditions du réseau. Nous facturons 0,1% pour convertir entre le Bitcoin lightning et le Bitcoin on-chain. Envoyer à d'autres comptes Coinos est gratuit." + }, + "individuals": { + "question": "Qui peut l'utiliser?", + "answer": "N'importe qui, n'importe où dans le monde. Coinos convient aux particuliers comme aux entreprises." }, "compatibility": { - "question": "Est-ce que j'ai besoin de logiciel ou d'appareil special ?", - "answer": "Non. swapee est un site Web, vous pouvez donc l'utiliser sur n'importe quel appareil tant que vous aves un connexion d'Internet. La plupart des marchands l'utilise sur un téléphone ou une tablette." + "question": "Ai-je besoin d'un dispositif ou d'un logiciel spécial?", + "answer": "Non, Coinos est une application web qui fonctionne partout où il y a Internet. Les commerçants pourraient l'utiliser sur une tablette ou afficher un code QR pour que les clients puissent le scanner avec leur téléphone." }, "safety": { - "question": "Sont mes fonds saufs ?", - "answer": "Vos fonds sont entreposés sur notre serveur sécurisé, mais nous recommandons que vous les retiriez vers votre propre entreposage pour les sauvegarder à long terme." + "question": "Quand mes fonds sont-ils réglés?", + "answer": "Les paiements lightning sont réglés instantanément. Vous pouvez retirer votre solde vers un autre portefeuille en quelques secondes. Les dépôts on-chain prennent en moyenne 5-10 minutes pour être confirmés." + }, + "pos": { + "question": "Coinos peut-il s'intégrer à mon PoS existant?", + "answer": "Nous recommandons d'entrer les ventes dans votre PoS existant comme des transactions en espèces." + }, + "accounting": { + "question": "Qu'en est-il de la comptabilité?", + "answer": "Nous affichons tous les paiements en termes de votre monnaie locale et vous permettons de filtrer par date pour que vous puissiez déclarer vos revenus normalement. Vous pouvez exporter l'historique de votre compte dans un fichier csv. Si vous vendez du Bitcoin avec un bénéfice, vous voudrez peut-être déclarer les plus-values." + }, + "offramp": { + "question": "Puis-je convertir le Bitcoin que je reçois en ma monnaie locale?", + "answer": "Vous pouvez retirer vos fonds à tout moment vers une bourse ou un courtier pour vendre votre Bitcoin et recevoir un virement bancaire. Nous pourrions être prêts à fournir une liquidation directe par virement électronique à certains commerçants canadiens." + }, + "bitcoin": { + "question": "Que puis-je faire avec le Bitcoin?", + "answer": "Dépensez-le chez d'autres commerçants ou payez vos dépenses avec en incitant votre personnel, vos fournisseurs et votre propriétaire à l'accepter." } }, "footer": { @@ -74,7 +75,8 @@ "telegram": "Telegram", "support": "Assistance", "jobs": "Jobs", - "news": "Nouvelles" + "linktree": "Linktree", + "documentation": "Documentation" }, "howItWorks": { "header": "Comment cela Fonctionne", @@ -135,7 +137,6 @@ "support": "Assistance", "profile": "Profil" }, - "newSiteHeader": "Bienvenue sur <a href=\"/news\" class=\"underline underline-offset-4 text-black hover:opacity-80\">swapee nouveau et amélioré</a>, focalisé sur les marchants! Vous cherchez l'ancien swapee? Visitez <a href=\"https://classic.swapee.me\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.swapee.me</a>.", "invoice": { "addTip": "Ajoutez un pourboire", "addTipq": "Ajouter un pourboire?", @@ -160,11 +161,8 @@ "dashboard": { "dashboard": "Tableau de bord", "accountBalance": "Solde", - "bitcoinPrice": "Prix de bitcoin:", "conversionFeeWarning": "Il y a des frais de 1% pour convertir entre bitcoin, Liquid et Lightning.", - "dataFromBinance": "Données de <span class=\"text-black\">Binance</span>.", "fetchingRate": "cherchant taut de change...", - "optionalNote": "Note (optionel)", "send": "Envoie", "withdraw": "Retire", "go": "Allez", @@ -213,7 +211,14 @@ "select": "Choisir", "verifyPIN": "Verifiez votre NIP", "enablePIN": "Activer NIP", - "disablePIN": "Désactiver NIP" + "disablePIN": "Désactiver NIP", + "saveSettings": "Sauvegarder Paramètres", + "tipPrompt": "Demande pour Pourboires", + "tipPromptDescription": "Demander les gens s'ils voulent ajouter un pourboire avant payer leurs factures", + "displayName": "Nom d'Utilisateur", + "minutes": "minutes", + "hour": "heure", + "hours": "heures" }, "support": { "header": "Assistance", @@ -235,50 +240,76 @@ "scan": "Scannez", "paste": "Collez", "next": "Prochain", - "placeholder": "Collez une adresse de bitcoin, facture de lightning ou nom d'utilisateur swapee" - } + "placeholder": "Collez une adresse de bitcoin, facture de lightning ou nom d'utilisateur coinos" + }, + "requestPayment": "Demander un Paiement", + "welcome": "Bienvenue! Le solde de votre conte est vide. Vous pouvez commencer par déposer ou recevoir un paiement.", + "showBalance": "Montrer Solde", + "followers": "Abonnés", + "noNotes": "Pas de notes", + "message": "Envoyer", + "messages": "Messages", + "feed": "Flux", + "notes": "Notes", + "pay": "Payer" + }, + "news": { + "header": "Nouvelles", + "subheader": "Dernières Informations" }, - "transactions": { - "header": "Histoire des Transactions", - "AMOUNT": "QUANTITÉ", - "TYPE": "TYPE", - "TIME": "HEURE", + "payments": { + "moneyPot": "Pot d'argent", + "addFunds": "Ajouter d'argent", + "takeFunds": "Prendre d'argent", + "show": "Démontrer", + "hide": "Cacher", + "qr": "QR", + "copyLink": "Copier le Lien", + "copyText": "Copier le Texte", + "openLink": "Ouvrer le Portefeuille", + "moreOptions": "Plus d'options", + "total": "Total", + "gainLoss": "Hausse/Perte", + "presentValue": "Valuer au présent", + "amount": "Quantité", + "date": "Date", + "id": "ID de Transaction", + "details": "Détails de Transaction", + "header": "Paiements", + "preimage": "Preuve de paiement", + "type": "Type", + "time": "Heure", "sent": "Envoyé", - "received": "Recevé", - "empty": "Vous n'avez pas des transactions.", - "send": "Envoie", + "received": "Reçu", + "empty": "Vous n'avez pas de transactions.", + "send": "Envoyer", "next": "Prochain", - "sendersChoice": "Choix d'envoyeur", - "sendTo": "Envoie à", + "sendTo": "Envoyer à", "to": "à", - "pay": "Payez", + "pay": "Payer", "requesting": "demande", - "sentInvoiceTo": "a envoyé une facture à", - "sentRequestTo": "a envoyé une demande à", - "pleasePay": "S'il vous plaît payez", + "sentInvoiceTo": "Envoyé une facture à", + "sentRequestTo": "Demande envoyé!", + "pleasePay": "S'il vous plait payez", "day": "Jour", "week": "Semaine", "month": "Mois", - "all": "Tout", - "export": "Exportez", + "all": "Toutes", + "export": "Exporter", "invoice": "Facture", - "readyToPay": "est prêt à payer", + "readyToPay": "est prêt a payer", "describeOrder": "Utiliser ce champ pour décrire votre commande", - "requestAnInvoice": "Demandez une facture", + "requestAnInvoice": "Demander une facture", "submit": "Soumettre", "isRequesting": "demande", "invoiceFrom": "Facture de", - "awaiting": "Attendez pour", - "tapAnywhere": "Tapez n'importe où", + "awaiting": "Attendant", + "tapAnywhere": "Taper n'importe où pour continuer", "from": "de", - "redeem": "Échangez", - "redeemed": "Déjà échangé", - "redeemedBy": "Échangé par", - "sendMessage": "Message" - }, - "news": { - "header": "Nouvelles", - "subheader": "Dernières Informations", - "update1": "" + "redeem": "Racheter", + "redeemed": "Racheté", + "redeemedBy": "Racheté par", + "sendMessage": "Écrire une message...", + "fundsClaimed": "Les fonds sont réclamés!" } } diff --git a/src/locales/guided_translation.py b/src/locales/guided_translation.py index e5d6c26d..2a907f44 100644 --- a/src/locales/guided_translation.py +++ b/src/locales/guided_translation.py @@ -128,6 +128,8 @@ This could mean that this text was translated first, or it could mean this text untranslated_strings.append(string_id) if untranslated_strings: + print("There are %d pieces of text to translate: %s" + % (len(untranslated_strings), untranslated_strings)) # localize strings! try: for string_id in untranslated_strings: diff --git a/src/locales/pt.json b/src/locales/pt.json index 4e459c2a..1614ed36 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -31,21 +31,45 @@ "message": "Por favor tente novamente ou contate o suporte.", "signIn": "Por favor, faça o login para continuar." }, - "faq": { - "header": "FAQ", - "cost": { - "question": "Quanto custa?", - "answer": "É grátis para registrar uma conta e receber pagamentos. Cobramos uma taxa de conversão de 0,1% se você retirar o bitcoin da rede principal para a lightning ou vice-versa.." - }, - "compatibility": { - "question": "Eu preciso de algum dispositivo ou software especial?", - "answer": "Não. Swapee baseado na web, desde que você tenha uma conexão com a Internet e um navegador web, ele funciona em qualquer lugar. Os comerciantes de varejo podem usá-lo em um telefone, computador ou tablet." - }, - "safety": { - "question": "Como é a liquidez?", - "answer": "Os pagamentos pela lightning são liquidados instantaneamente. Você pode retirar seu saldo para outra carteira ou trocar imediatamente assim que os fundos forem recebidos." - } - }, + "faq": { + "header": "FAQ", + "why": { + "question": "¿Por qué aceptar Bitcoin?", + "answer": "Con Bitcoin, los comerciantes ahorran en comisiones, los consumidores obtienen privacidad y los ahorradores ganan poder adquisitivo. Promocionaremos tu negocio a una comunidad entusiasta que prefiere comprar en lugares que aceptan Bitcoin." + }, + "cost": { + "question": "¿Cuánto cuesta?", + "answer": "Recibir pagos en tu cuenta es gratis. Las retiradas a carteras externas incurren en una pequeña tarifa que varía según las condiciones de la red. Cobramos el 0.1% para convertir entre Bitcoin lightning y on-chain. Enviar a otras cuentas de Swapee es gratis." + }, + "individuals": { + "question": "¿Quién puede usarlo?", + "answer": "Cualquiera, en cualquier parte del mundo. Swapee es adecuado para individuos y negocios." + }, + "compatibility": { + "question": "¿Necesito algún dispositivo o software especial?", + "answer": "No, Swapee es una aplicación web que funciona en cualquier lugar que tenga Internet. Los comerciantes pueden usarla en una tableta o mostrar un código QR para que los clientes lo escaneen con sus teléfonos." + }, + "safety": { + "question": "¿Cuándo se liquidan mis fondos?", + "answer": "Los pagos lightning se liquidan al instante. Puedes retirar tu saldo a otra cartera en segundos. Los depósitos en cadena tardan en promedio de 5 a 10 minutos en confirmarse." + }, + "pos": { + "question": "¿Puede Swapee integrarse con mi PoS existente?", + "answer": "Recomendamos ingresar las ventas en tu PoS existente como transacciones en efectivo." + }, + "accounting": { + "question": "¿Qué pasa con la contabilidad?", + "answer": "Mostramos todos los pagos en términos de tu moneda local y te permitimos filtrar por fecha para que puedas informar tus ingresos como de costumbre. Puedes exportar tu historial de cuenta a un archivo csv. Si vendes Bitcoin con beneficio, es posible que desees informar las ganancias de capital." + }, + "offramp": { + "question": "¿Puedo convertir el Bitcoin que recibo a mi moneda local?", + "answer": "Puedes retirar tus fondos en cualquier momento a un exchange o correduría para vender tu Bitcoin y recibir una transferencia bancaria. Es posible que estemos dispuestos a proporcionar liquidación directa a través de e-transferencia a comerciantes canadienses seleccionados." + }, + "bitcoin": { + "question": "¿Qué puedo hacer con Bitcoin?", + "answer": "Gástalo en otros comerciantes o paga tus gastos con él al conseguir que tu personal, proveedores y arrendador lo acepten." + } + }, "footer": { "company": "Empresa", "followUs": "Siga-nos", diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 2ea556be..f93551b8 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -34,22 +34,22 @@ checkSocket(); expirePin(); - if (window.NDEFReader) { - try { - $ndef = new NDEFReader(); - await $ndef.scan(); - - $ndef.addEventListener('readingerror', (e) => { - console.log('nfc error', e); - }); - - $ndef.addEventListener('reading', ({ message, serialNumber }) => { - goto(`/${user.username}/card/${serialNumber.replace(/:/g, '-')}`); - }); - } catch (error) { - log('Argh! ' + error); - } - } + // if (window.NDEFReader) { + // try { + // $ndef = new NDEFReader(); + // await $ndef.scan(); + // + // $ndef.addEventListener('readingerror', (e) => { + // console.log('nfc error', e); + // }); + // + // $ndef.addEventListener('reading', ({ message, serialNumber }) => { + // goto(`/${user.username}/card/${serialNumber.replace(/:/g, '-')}`); + // }); + // } catch (error) { + // log('Argh! ' + error); + // } + // } } }); diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte index 27192bbe..0642b6fc 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.svelte +++ b/src/routes/(app)/[username]/(profile)/+layout.svelte @@ -56,6 +56,7 @@ class="text-secondary mx-auto text-center lg:text-left lg:mx-0" class:line-clamp-3={hideBio} on:click={toggleBio} + on:keydown={toggleBio} > {subject.address} </div> diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index ecdfeb1a..78465d73 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -87,7 +87,7 @@ <div class="space-y-8 mt-8"> {#if !user.balance} <p class="text-secondary text-lg"> - {$t("user.welcome")} + {$t('user.welcome')} </p> {/if} @@ -119,6 +119,43 @@ </a> </div> </div> + {/if} + + <div class="flex flex-wrap gap-4 justify-center w-full"> + <a href={`/${user.username}/receive`} class="w-full md:w-60"> + <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full md:w-60"> + <div class="mx-auto flex"> + <Icon icon="numpad" style="my-auto h-6 mr-2" /> + <div class="my-auto mt-1">{$t('user.requestPayment')}</div> + </div> + </button> + </a> + <a href={`/scan`} class="w-full md:w-60"> + <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full md:w-60"> + <div class="mx-auto flex"> + <Icon icon="scan" style="my-auto h-6 mr-2" /> + <div class="my-auto mt-1">{$t('user.send.scan')}</div> + </div> + </button> + </a> + + {#if user.eligible} + <a href={`/buy`}> + <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60"> + <div class="mx-auto flex"> + <!-- <Icon icon="plus" style="my-auto h-6 mr-2" /> --> + + <img + src="/images/bitcoin.svg" + class="w-8 border-4 border-transparent mr-2" + alt="Bitcoin" + /> + <div class="my-auto mt-1">Buy Bitcoin</div> + </div> + </button> + </a> + {/if} + </div> </div> {/if} diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/[username]/generate/+page.svelte index d8557a01..33bf64c4 100644 --- a/src/routes/(app)/[username]/generate/+page.svelte +++ b/src/routes/(app)/[username]/generate/+page.svelte @@ -13,13 +13,12 @@ let loaded; let gen = async () => { if (!browser) return; + if (user.pubkey) { goto($loginRedirect || `/${user.username}`, { invalidateAll: true }); return; } - setTimeout(() => (loaded = true), 50); - if (!$password) $passwordPrompt = true; try { @@ -51,9 +50,7 @@ } }; - onMount(async () => { - await gen(); - }); + onMount(() => browser && setTimeout(() => (loaded = true) && gen(), 50)); $: $pin?.length === 6 && gen(user); </script> @@ -61,3 +58,6 @@ {#if loaded && user?.haspin && $pin?.length !== 6} <Pin /> {/if} +{loaded} +{JSON.stringify(user)} +{$pin} diff --git a/src/routes/(app)/[username]/invoice/[hash]/+layout.server.js b/src/routes/(app)/[username]/invoice/[hash]/+layout.server.js index 875c4e11..eef24555 100644 --- a/src/routes/(app)/[username]/invoice/[hash]/+layout.server.js +++ b/src/routes/(app)/[username]/invoice/[hash]/+layout.server.js @@ -9,7 +9,7 @@ export async function load({ depends, params, url, parent }) { let { hash } = params; let invoice = await get(`/invoice/${hash}`); - if (user && invoice.uid !== user.id && !url.pathname.includes('tip')) + if (user && invoice.uid !== user.id && !url.pathname.includes('tip') && !url.searchParams.get('options')) throw redirect(307, `/send/invoice/${hash}`); let { amount, pending, received } = invoice; diff --git a/src/routes/(app)/[username]/invoice/[hash]/+page.svelte b/src/routes/(app)/[username]/invoice/[hash]/+page.svelte index bf30db99..508550ba 100644 --- a/src/routes/(app)/[username]/invoice/[hash]/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[hash]/+page.svelte @@ -35,7 +35,7 @@ user: { username, currency } } = invoice; - let showQr = !amount; + $: showQr = invoice.uid === user?.id; $: src = sm; @@ -58,20 +58,20 @@ tipPercent = (tip / amount) * 100; - if (browser && window.NDEFReader) { - try { - ndef = new NDEFReader(); - await ndef.scan(); - - ndef.removeEventListener('readingerror', readingerror); - ndef.removeEventListener('reading', reading); - - ndef.addEventListener('readingerror', readingerror); - ndef.addEventListener('reading', reading); - } catch (e) { - console.log(e); - } - } + // if (browser && window.NDEFReader) { + // try { + // ndef = new NDEFReader(); + // await ndef.scan(); + // + // ndef.removeEventListener('readingerror', readingerror); + // ndef.removeEventListener('reading', reading); + // + // ndef.addEventListener('readingerror', readingerror); + // ndef.addEventListener('reading', reading); + // } catch (e) { + // console.log(e); + // } + // } }; $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); @@ -110,7 +110,7 @@ }; </script> -<div class="container mx-auto max-w-lg px-4 space-y-5"> +<div class="container mx-auto max-w-lg px-4 space-y-8"> <div class="whitespace-nowrap my-auto ml-auto flex gap-2"> <button class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full" @@ -136,18 +136,25 @@ </button> </div> - <div> - <a href={invoice.type === 'bitcoin' ? invoice.text : 'lightning:' + invoice.text}> - <div class="relative flex"> - <div class="flex mx-auto w-[360px] h-[360px]"> - <img {src} class="w-[300px] h-[300px] mx-auto z-10 mt-[20px]" bind:this={qr} alt={txt} /> + {#if showQr} + <div> + <a href={invoice.type === 'bitcoin' ? invoice.text : 'lightning:' + invoice.text}> + <div class="relative flex"> + <div class="flex mx-auto w-[360px] h-[360px]"> + <img + {src} + class="w-[300px] h-[300px] mx-auto z-10 mt-[20px]" + bind:this={qr} + alt={txt} + /> + </div> + <div + class="absolute m-auto left-0 right-0 w-[340px] h-[340px] rounded-full bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] z-0" + /> </div> - <div - class="absolute m-auto left-0 right-0 w-[340px] h-[340px] rounded-full bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] z-0" - /> - </div> - </a> - </div> + </a> + </div> + {/if} {#if amount > 0} <div class="text-center font-bold text-2xl"> @@ -172,10 +179,39 @@ </div> {/if} - <div class="w-full flex justify-center"> - <button class="flex rounded-full border py-3 px-5 hover:opacity-80" on:click={() => copy(txt)}> + <div class="text-center break-all"> + <a href={link}> + {txt} + </a> + </div> + + <div class="w-full flex justify-center gap-2 flex-wrap"> + <a href={link} class="w-full md:w-auto"> + <button + class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80" + > + <Icon icon="mobile" style="mr-1 w-6" /> + <div class="text-secondary">{$t('payments.openLink')}</div> + </button> + </a> + + <button + class="flex rounded-full justify-center border py-3 px-5 hover:opacity-80 w-full md:w-auto" + on:click={() => copy(txt)} + > <Icon icon="copy" style="mr-1" /> - <div class="text-secondary">{txt.substr(0, 10)}...{txt.substr(-10)}</div></button + <div class="text-secondary">{$t('payments.copyText')}</div></button + > + + <button + class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80" + on:click={() => (showQr = !showQr)} + > + <Icon icon="qr" style="mr-1 invert" /> + <div class="text-secondary"> + {showQr ? $t('payments.hide') : $t('payments.show')} + {$t('payments.qr')} + </div></button > </div> </div> diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.svelte b/src/routes/(app)/[username]/payments/[...page]/+page.svelte index adfaf680..69f51f37 100644 --- a/src/routes/(app)/[username]/payments/[...page]/+page.svelte +++ b/src/routes/(app)/[username]/payments/[...page]/+page.svelte @@ -4,21 +4,13 @@ import { format } from 'date-fns'; import { newPayment, payments } from '$lib/store'; import { t } from '$lib/translations'; - import { get, f, s, sat, sats } from '$lib/utils'; + import { get, f, s, sat, sats, types } from '$lib/utils'; import { page } from '$app/stores'; import { differenceInDays, getUnixTime, sub } from 'date-fns'; import { goto, invalidate } from '$app/navigation'; export let data; - const types = { - internal: 'internal', - bitcoin: 'bitcoin', - lightning: 'lightning', - pot: 'pot', - classic: 'classic' - }; - let { start, end, user, rates } = data; let change = ({ target: { value } }) => goto(value); @@ -28,7 +20,7 @@ return `/p/${p.id}`; }; - let presets = [ + $: presets = [ { title: $t('payments.day'), start: sub(new Date(), { days: 1 }), end: null }, { title: $t('payments.week'), start: sub(new Date(), { days: 7 }), end: null }, { title: $t('payments.month'), start: sub(new Date(), { months: 1 }), end: null }, diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.server.js b/src/routes/(app)/[username]/settings/[...tab]/+page.server.js index 4e80a5ad..b0c849d0 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.server.js +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.server.js @@ -9,7 +9,7 @@ export function load({ params, url }) { export const actions = { default: async ({ cookies, request }) => { let form = await fd(request); - form.prompt = form.prompt === 'on'; + if (form.prompt) form.prompt = form.prompt === 'on'; let user = { ...(await get('/me', auth(cookies))), ...form }; try { diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte index 2bd465a7..842c2eba 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte @@ -15,6 +15,7 @@ import Account from './_account.svelte'; import Pos from './_pos.svelte'; import Security from './_security.svelte'; + import Shopify from './_shopify.svelte'; import { PUBLIC_COINOS_URL } from '$env/static/public'; @@ -33,7 +34,7 @@ $: form?.success && success('Settings saved!'); - $: form?.message && fail(form.message); + $: form?.message && fail(form.message); $: if (form?.message?.startsWith('Pin')) { fail('Wrong pin, try again'); @@ -43,7 +44,8 @@ let tabs = [ { name: 'account', key: 'ACCOUNT', comp: Account }, { name: 'pos', key: 'POINT_OF_SALE', comp: Pos }, - { name: 'security', key: 'SECURITY', comp: Security } + { name: 'security', key: 'SECURITY', comp: Security }, + { name: 'shopify', key: 'SHOPIFY', comp: Shopify } ]; $: ({ comp } = tabs.find((t) => t.name === tab)); @@ -134,9 +136,9 @@ </h1> <div class="font-bold flex justify-around items-center border-b pb-3 text-secondary"> - {#each tabs as { name, key }} + {#each tabs.filter((t) => t.name !== 'shopify') as { name, key }} <a href={`/${user.username}/settings/${name}`}> - <button class="hover:opacity-80" class:text-black={tab === name} + <button class="hover:opacity-80" class:text-black={tab === name || (tab === "shopify" && name === "pos")} >{$t(`user.settings.${key}`)}</button > </a> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte index 54e4597c..34399ad5 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte @@ -3,6 +3,7 @@ import { colorTheme, avatar, banner } from '$lib/store'; import { Icon } from '$comp'; import { t } from '$lib/translations'; + import { page } from '$app/stores'; export let user; let { id } = user; @@ -60,17 +61,42 @@ }; if (!user.display) user.display = user.username; + + $: url = `${$page.url.host}/${user.username}`; + $: full = `${$page.url.protocol}//${url}`; + $: addr = `${user.username}@${$page.url.host}`; </script> -<div class="relative"> - <div> - <label for="username" class="font-bold mb-1 block">{$t('user.settings.username')}</label> - <input type="text" name="username" bind:value={user.username} /> +<div> + <label for="username" class="font-bold mb-1 block">{$t('user.settings.username')}</label> + <div class="flex mb-2"> + <input + type="text" + name="username" + bind:value={user.username} + class="border-r-0 rounded-r-none w-auto min-w-0 pr-1 grow" + /> + <div class="text-gray-600 p-4 my-auto bg-gray-100 border border-r-0 pl-1">@{$page.url.host}</div> + <div class="text-gray-600 border-l-0 bg-gray-100 rounded-r-2xl p-4 my-auto border w-16" > + <a href={`/qr/${encodeURIComponent(addr)}`}> + <Icon icon="qr" style="invert" /> + </a> + </div> </div> - <div - class="absolute right-[2px] top-[30px] text-gray-600 rounded-r-2xl p-4 h-[54px] my-auto border-l " - > - @swapee.me + + <div class="flex mb-2"> + <div class="text-gray-600 rounded-l-2xl p-4 my-auto border bg-gray-100 pr-1">{$page.url.host}/</div> + <input + type="text" + name="username" + bind:value={user.username} + class="grow border-l-0 rounded-l-none border-r-0 rounded-r-none w-auto pl-1 min-w-0" + /> + <div class="text-gray-600 rounded-r-2xl p-4 my-auto border bg-gray-100 w-16"> + <a href={`/qr/${encodeURIComponent(full)}`}> + <Icon icon="qr" style="invert" /> + </a> + </div> </div> </div> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte index 8e2e2cc5..ef5b7869 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte @@ -31,3 +31,10 @@ {$t('user.settings.tipPromptDescription')} </p> </div> + +<a href="./shopify"> + <button class="flex rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full gap-2 my-5 justify-center"> + <img src="/icons/shopify.svg" class="w-8" /> + <div class="my-auto">Shopify Integration</div> + </button> +</a> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte b/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte new file mode 100644 index 00000000..7b9cc40b --- /dev/null +++ b/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte @@ -0,0 +1,54 @@ +<script> + import { t } from '$lib/translations'; + import { Icon } from '$comp'; + import { page } from '$app/stores'; + + export let user; + $: paymentsUrl = user.shopifyStore && "https://admin.shopify.com/store/" + user.shopifyStore + "/settings/payments"; + $: checkoutUrl = user.shopifyStore && "https://admin.shopify.com/store/" + user.shopifyStore + "/settings/checkout"; + $: appsUrl = user.shopifyStore && "https://admin.shopify.com/store/" + user.shopifyStore + "/settings/apps/development"; +</script> + +<div> + <label for="shopifyStore" class="font-bold mb-1 block">Store ID</label> + <div class="flex mb-2"> + <div class="text-gray-600 p-4 my-auto bg-gray-100 border rounded-l-2xl border-r-0 pr-1">admin.shopify.com/store/</div> + <input + type="text" + name="shopifyStore" + bind:value={user.shopifyStore} + class="border-l-0 rounded-l-none w-auto min-w-0 pl-1 grow" + /> + </div> + <p class="text-secondary my-2"> + Add Bitcoin under <a class:active={!!paymentsUrl} href={paymentsUrl || undefined}>Payments > Manual payment methods</a> with a description like "Pay with Bitcoin". + </p> +</div> + +<div> + <label for="shopifyToken" class="font-bold mb-1 block">Admin API token</label> + <input type="text" name="shopifyToken" bind:value={user.shopifyToken} /> + +<p class="text-secondary my-2"> +Create an app named Coinos under <a class:active={!!appsUrl} href={appsUrl || undefined}>Apps > App Development</a> +</p> + +<p class="text-secondary my-2"> +Edit the Admin API scopes to enable <code class="text-black">write_orders</code> then +install the app into your store and copy the Admin API access token here. +</div> + +<div> + <label for="display" class="font-bold mb-1 block">Additional scripts</label> + <textarea rows={5} type="text" name="shopifyScripts" readonly><script src="{`${$page.url.protocol}//${$page.url.host}/${user.username}/shopify.js`}"> </script></textarea> + + <p class="text-secondary my-2"> + Copy this into <a class:active={!!paymentsUrl} href={checkoutUrl}>Checkout -> Additional scripts</a> + </p> +</div> + +<style> + .active { + @apply text-blue-500; + } +</style> diff --git a/src/routes/(app)/[username]/shopify.js/+server.js b/src/routes/(app)/[username]/shopify.js/+server.js new file mode 100644 index 00000000..f5bb8d50 --- /dev/null +++ b/src/routes/(app)/[username]/shopify.js/+server.js @@ -0,0 +1,190 @@ +export async function GET({ cookies, params, url }) { + let { username } = params; + + let js = ` +var qrcode=function(r,_){var t=r,e=QRErrorCorrectLevel[_],n=null,$=0,o=null,a=[],f={},i=function(r,_){n=function(r){for(var _=Array(r),t=0;t<r;t+=1){_[t]=Array(r);for(var e=0;e<r;e+=1)_[t][e]=null}return _}($=4*t+17),u(0,0),u($-7,0),u(0,$-7),l(),g(),v(r,_),t>=7&&h(r),null==o&&(o=x(t,e,a)),s(o,_)},u=function(r,_){for(var t=-1;t<=7;t+=1)if(!(r+t<=-1)&&!($<=r+t))for(var e=-1;e<=7;e+=1)_+e<=-1||$<=_+e||(0<=t&&t<=6&&(0==e||6==e)||0<=e&&e<=6&&(0==t||6==t)||2<=t&&t<=4&&2<=e&&e<=4?n[r+t][_+e]=!0:n[r+t][_+e]=!1)},c=function(){for(var r=0,_=0,t=0;t<8;t+=1){i(!0,t);var e=QRUtil.getLostPoint(f);(0==t||r>e)&&(r=e,_=t)}return _},g=function(){for(var r=8;r<$-8;r+=1)null==n[r][6]&&(n[r][6]=r%2==0);for(var _=8;_<$-8;_+=1)null==n[6][_]&&(n[6][_]=_%2==0)},l=function(){for(var r=QRUtil.getPatternPosition(t),_=0;_<r.length;_+=1)for(var e=0;e<r.length;e+=1){var $=r[_],o=r[e];if(null==n[$][o])for(var a=-2;a<=2;a+=1)for(var f=-2;f<=2;f+=1)-2==a||2==a||-2==f||2==f||0==a&&0==f?n[$+a][o+f]=!0:n[$+a][o+f]=!1}},h=function(r){for(var _=QRUtil.getBCHTypeNumber(t),e=0;e<18;e+=1){var o=!r&&(_>>e&1)==1;n[Math.floor(e/3)][e%3+$-8-3]=o}for(var e=0;e<18;e+=1){var o=!r&&(_>>e&1)==1;n[e%3+$-8-3][Math.floor(e/3)]=o}},v=function(r,_){for(var t=QRUtil.getBCHTypeInfo(e<<3|_),o=0;o<15;o+=1){var a=!r&&(t>>o&1)==1;o<6?n[o][8]=a:o<8?n[o+1][8]=a:n[$-15+o][8]=a}for(var o=0;o<15;o+=1){var a=!r&&(t>>o&1)==1;o<8?n[8][$-o-1]=a:o<9?n[8][15-o-1+1]=a:n[8][15-o-1]=a}n[$-8][8]=!r},s=function(r,_){for(var t=-1,e=$-1,o=7,a=0,f=QRUtil.getMaskFunction(_),i=$-1;i>0;i-=2)for(6==i&&(i-=1);;){for(var u=0;u<2;u+=1)if(null==n[e][i-u]){var c=!1;a<r.length&&(c=(r[a]>>>o&1)==1),f(e,i-u)&&(c=!c),n[e][i-u]=c,-1==(o-=1)&&(a+=1,o=7)}if((e+=t)<0||$<=e){e-=t,t=-t;break}}},d=function(r,_){for(var t=0,e=0,n=0,$=Array(_.length),o=Array(_.length),a=0;a<_.length;a+=1){var f=_[a].dataCount,i=_[a].totalCount-f;e=Math.max(e,f),n=Math.max(n,i),$[a]=Array(f);for(var u=0;u<$[a].length;u+=1)$[a][u]=255&r.getBuffer()[u+t];t+=f;var c=QRUtil.getErrorCorrectPolynomial(i),g=qrPolynomial($[a],c.getLength()-1).mod(c);o[a]=Array(c.getLength()-1);for(var u=0;u<o[a].length;u+=1){var l=u+g.getLength()-o[a].length;o[a][u]=l>=0?g.getAt(l):0}}for(var h=0,u=0;u<_.length;u+=1)h+=_[u].totalCount;for(var v=Array(h),s=0,u=0;u<e;u+=1)for(var a=0;a<_.length;a+=1)u<$[a].length&&(v[s]=$[a][u],s+=1);for(var u=0;u<n;u+=1)for(var a=0;a<_.length;a+=1)u<o[a].length&&(v[s]=o[a][u],s+=1);return v},x=function(r,_,t){for(var e=QRRSBlock.getRSBlocks(r,_),n=qrBitBuffer(),$=0;$<t.length;$+=1){var o=t[$];n.put(o.getMode(),4),n.put(o.getLength(),QRUtil.getLengthInBits(o.getMode(),r)),o.write(n)}for(var a=0,$=0;$<e.length;$+=1)a+=e[$].dataCount;if(n.getLengthInBits()>8*a)throw Error("code length overflow. ("+n.getLengthInBits()+">"+8*a+")");for(n.getLengthInBits()+4<=8*a&&n.put(0,4);n.getLengthInBits()%8!=0;)n.putBit(!1);for(;!(n.getLengthInBits()>=8*a)&&(n.put(236,8),!(n.getLengthInBits()>=8*a));){;n.put(17,8)}return d(n,e)};return f.addData=function(r){var _=qr8BitByte(r);a.push(_),o=null},f.isDark=function(r,_){if(r<0||$<=r||_<0||$<=_)throw Error(r+","+_);return n[r][_]},f.getModuleCount=function(){return $},f.make=function(){i(!1,c())},f.createTableTag=function(r,_){r=r||2;var t="";t+='<table style="',t+=" border-width: 0px; border-style: none;",t+=" border-collapse: collapse;",t+=" padding: 0px; margin: "+(_=void 0===_?4*r:_)+"px;",t+='">',t+="<tbody>";for(var e=0;e<f.getModuleCount();e+=1){t+="<tr>";for(var n=0;n<f.getModuleCount();n+=1)t+='<td style="',t+=" border-width: 0px; border-style: none;",t+=" border-collapse: collapse;",t+=" padding: 0px; margin: 0px;",t+=" width: "+r+"px;",t+=" height: "+r+"px;",t+=" background-color: ",t+=f.isDark(e,n)?"#000000":"#ffffff",t+=";",t+='"/>';t+="</tr>"}return t+="</tbody>",t+="</table>"},f.createImgTag=function(r,_,t){r=r||2;var e=_=void 0===_?4*r:_,n=f.getModuleCount()*r+_;return createImgTag(t,t,function(_,t){if(!(e<=_)||!(_<n)||!(e<=t)||!(t<n))return 1;var $=Math.floor((_-e)/r),o=Math.floor((t-e)/r);return f.isDark(o,$)?0:1})},f};qrcode.stringToBytes=function(r){for(var _=[],t=0;t<r.length;t+=1){var e=r.charCodeAt(t);_.push(255&e)}return _},qrcode.createStringToBytes=function(r,_){var t=function(){for(var t=base64DecodeInputStream(r),e=function(){var r=t.read();if(-1==r)throw Error();return r},n=0,$={};;){var o=t.read();if(-1==o)break;var a=e(),f=e(),i=e(),u=String.fromCharCode(o<<8|a),c=f<<8|i;$[u]=c,n+=1}if(n!=_)throw Error(n+" != "+_);return $}();return function(r){for(var _=[],e=0;e<r.length;e+=1){var n=r.charCodeAt(e);if(n<128)_.push(n);else{var $=t[r.charAt(e)];"number"==typeof $?(255&$)==$?_.push($):(_.push($>>>8),_.push(255&$)):_.push(63)}}return _}};var QRMode={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},QRErrorCorrectLevel={L:1,M:0,Q:3,H:2},QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},QRUtil=function(){var r=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],_={},t=function(r){for(var _=0;0!=r;)_+=1,r>>>=1;return _};return _.getBCHTypeInfo=function(r){for(var _=r<<10;t(_)-t(1335)>=0;)_^=1335<<t(_)-t(1335);return(r<<10|_)^21522},_.getBCHTypeNumber=function(r){for(var _=r<<12;t(_)-t(7973)>=0;)_^=7973<<t(_)-t(7973);return r<<12|_},_.getPatternPosition=function(_){return r[_-1]},_.getMaskFunction=function(r){switch(r){case QRMaskPattern.PATTERN000:return function(r,_){return(r+_)%2==0};case QRMaskPattern.PATTERN001:return function(r,_){return r%2==0};case QRMaskPattern.PATTERN010:return function(r,_){return _%3==0};case QRMaskPattern.PATTERN011:return function(r,_){return(r+_)%3==0};case QRMaskPattern.PATTERN100:return function(r,_){return(Math.floor(r/2)+Math.floor(_/3))%2==0};case QRMaskPattern.PATTERN101:return function(r,_){return r*_%2+r*_%3==0};case QRMaskPattern.PATTERN110:return function(r,_){return(r*_%2+r*_%3)%2==0};case QRMaskPattern.PATTERN111:return function(r,_){return(r*_%3+(r+_)%2)%2==0};default:throw Error("bad maskPattern:"+r)}},_.getErrorCorrectPolynomial=function(r){for(var _=qrPolynomial([1],0),t=0;t<r;t+=1)_=_.multiply(qrPolynomial([1,QRMath.gexp(t)],0));return _},_.getLengthInBits=function(r,_){if(1<=_&&_<10)switch(r){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:case QRMode.MODE_KANJI:return 8;default:throw Error("mode:"+r)}else if(_<27)switch(r){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw Error("mode:"+r)}else if(_<41)switch(r){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw Error("mode:"+r)}else throw Error("type:"+_)},_.getLostPoint=function(r){for(var _=r.getModuleCount(),t=0,e=0;e<_;e+=1)for(var n=0;n<_;n+=1){for(var $=0,o=r.isDark(e,n),a=-1;a<=1;a+=1)if(!(e+a<0)&&!(_<=e+a))for(var f=-1;f<=1;f+=1)!(n+f<0)&&!(_<=n+f)&&(0!=a||0!=f)&&o==r.isDark(e+a,n+f)&&($+=1);$>5&&(t+=3+$-5)}for(var e=0;e<_-1;e+=1)for(var n=0;n<_-1;n+=1){var i=0;r.isDark(e,n)&&(i+=1),r.isDark(e+1,n)&&(i+=1),r.isDark(e,n+1)&&(i+=1),r.isDark(e+1,n+1)&&(i+=1),(0==i||4==i)&&(t+=3)}for(var e=0;e<_;e+=1)for(var n=0;n<_-6;n+=1)r.isDark(e,n)&&!r.isDark(e,n+1)&&r.isDark(e,n+2)&&r.isDark(e,n+3)&&r.isDark(e,n+4)&&!r.isDark(e,n+5)&&r.isDark(e,n+6)&&(t+=40);for(var n=0;n<_;n+=1)for(var e=0;e<_-6;e+=1)r.isDark(e,n)&&!r.isDark(e+1,n)&&r.isDark(e+2,n)&&r.isDark(e+3,n)&&r.isDark(e+4,n)&&!r.isDark(e+5,n)&&r.isDark(e+6,n)&&(t+=40);for(var u=0,n=0;n<_;n+=1)for(var e=0;e<_;e+=1)r.isDark(e,n)&&(u+=1);return t+10*(Math.abs(100*u/_/_-50)/5)},_}(),QRMath=function(){for(var r=Array(256),_=Array(256),t=0;t<8;t+=1)r[t]=1<<t;for(var t=8;t<256;t+=1)r[t]=r[t-4]^r[t-5]^r[t-6]^r[t-8];for(var t=0;t<255;t+=1)_[r[t]]=t;var e={};return e.glog=function(r){if(r<1)throw Error("glog("+r+")");return _[r]},e.gexp=function(_){for(;_<0;)_+=255;for(;_>=256;)_-=255;return r[_]},e}();function qrPolynomial(r,_){if(void 0===r.length)throw Error(r.length+"/"+_);var t=function(){for(var t=0;t<r.length&&0==r[t];)t+=1;for(var e=Array(r.length-t+_),n=0;n<r.length-t;n+=1)e[n]=r[n+t];return e}(),e={};return e.getAt=function(r){return t[r]},e.getLength=function(){return t.length},e.multiply=function(r){for(var _=Array(e.getLength()+r.getLength()-1),t=0;t<e.getLength();t+=1)for(var n=0;n<r.getLength();n+=1)_[t+n]^=QRMath.gexp(QRMath.glog(e.getAt(t))+QRMath.glog(r.getAt(n)));return qrPolynomial(_,0)},e.mod=function(r){if(e.getLength()-r.getLength()<0)return e;for(var _=QRMath.glog(e.getAt(0))-QRMath.glog(r.getAt(0)),t=Array(e.getLength()),n=0;n<e.getLength();n+=1)t[n]=e.getAt(n);for(var n=0;n<r.getLength();n+=1)t[n]^=QRMath.gexp(QRMath.glog(r.getAt(n))+_);return qrPolynomial(t,0).mod(r)},e}var QRRSBlock=function(){var r=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],_=function(r,_){var t={};return t.totalCount=r,t.dataCount=_,t},t={},e=function(_,t){switch(t){case QRErrorCorrectLevel.L:return r[(_-1)*4+0];case QRErrorCorrectLevel.M:return r[(_-1)*4+1];case QRErrorCorrectLevel.Q:return r[(_-1)*4+2];case QRErrorCorrectLevel.H:return r[(_-1)*4+3];default:return}};return t.getRSBlocks=function(r,t){var n=e(r,t);if(void 0===n)throw Error("bad rs block @ typeNumber:"+r+"/errorCorrectLevel:"+t);for(var $=n.length/3,o=[],a=0;a<$;a+=1)for(var f=n[3*a+0],i=n[3*a+1],u=n[3*a+2],c=0;c<f;c+=1)o.push(_(i,u));return o},t}(),qrBitBuffer=function(){var r=[],_=0,t={};return t.getBuffer=function(){return r},t.getAt=function(_){return(r[Math.floor(_/8)]>>>7-_%8&1)==1},t.put=function(r,_){for(var e=0;e<_;e+=1)t.putBit((r>>>_-e-1&1)==1)},t.getLengthInBits=function(){return _},t.putBit=function(t){var e=Math.floor(_/8);r.length<=e&&r.push(0),t&&(r[e]|=128>>>_%8),_+=1},t},qr8BitByte=function(r){for(var _=QRMode.MODE_8BIT_BYTE,t=r,e=[],n={},$=0,o=t.length;$<o;$++){var a=[],f=t.charCodeAt($);f>65536?(a[0]=240|(1835008&f)>>>18,a[1]=128|(258048&f)>>>12,a[2]=128|(4032&f)>>>6,a[3]=128|63&f):f>2048?(a[0]=224|(61440&f)>>>12,a[1]=128|(4032&f)>>>6,a[2]=128|63&f):f>128?(a[0]=192|(1984&f)>>>6,a[1]=128|63&f):a[0]=f,e.push(a)}(e=Array.prototype.concat.apply([],e)).length!=t.length&&(e.unshift(191),e.unshift(187),e.unshift(239));var i=e;return n.getMode=function(){return _},n.getLength=function(r){return i.length},n.write=function(r){for(var _=0;_<i.length;_+=1)r.put(i[_],8)},n},byteArrayOutputStream=function(){var r=[],_={};return _.writeByte=function(_){r.push(255&_)},_.writeShort=function(r){_.writeByte(r),_.writeByte(r>>>8)},_.writeBytes=function(r,t,e){t=t||0,e=e||r.length;for(var n=0;n<e;n+=1)_.writeByte(r[n+t])},_.writeString=function(r){for(var t=0;t<r.length;t+=1)_.writeByte(r.charCodeAt(t))},_.toByteArray=function(){return r},_.toString=function(){var _="";_+="[";for(var t=0;t<r.length;t+=1)t>0&&(_+=","),_+=r[t];return _+"]"},_},base64EncodeOutputStream=function(){var r=0,_=0,t=0,e="",n={},$=function(r){e+=String.fromCharCode(o(63&r))},o=function(r){if(r<0);else if(r<26)return 65+r;else if(r<52)return 97+(r-26);else if(r<62)return 48+(r-52);else if(62==r)return 43;else if(63==r)return 47;throw Error("n:"+r)};return n.writeByte=function(e){for(r=r<<8|255&e,_+=8,t+=1;_>=6;)$(r>>>_-6),_-=6},n.flush=function(){if(_>0&&($(r<<6-_),r=0,_=0),t%3!=0)for(var n=3-t%3,o=0;o<n;o+=1)e+="="},n.toString=function(){return e},n},base64DecodeInputStream=function(r){var _=r,t=0,e=0,n=0,$={};$.read=function(){for(;n<8;){if(t>=_.length){if(0==n)return -1;throw Error("unexpected end of file./"+n)}var r=_.charAt(t);if(t+=1,"="==r)return n=0,-1;!r.match(/^\s$/)&&(e=e<<6|o(r.charCodeAt(0)),n+=6)}var $=e>>>n-8&255;return n-=8,$};var o=function(r){if(65<=r&&r<=90)return r-65;if(97<=r&&r<=122)return r-97+26;if(48<=r&&r<=57)return r-48+52;if(43==r)return 62;if(47==r)return 63;else throw Error("c:"+r)};return $},gifImage=function(r,_){var t=r,e=_,n=Array(r*_),$={};$.setPixel=function(r,_,e){n[_*t+r]=e},$.write=function(r){r.writeString("GIF87a"),r.writeShort(t),r.writeShort(e),r.writeByte(128),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(255),r.writeByte(255),r.writeByte(255),r.writeString(","),r.writeShort(0),r.writeShort(0),r.writeShort(t),r.writeShort(e),r.writeByte(0);var _=a(2);r.writeByte(2);for(var n=0;_.length-n>255;)r.writeByte(255),r.writeBytes(_,n,255),n+=255;r.writeByte(_.length-n),r.writeBytes(_,n,_.length-n),r.writeByte(0),r.writeString(";")};var o=function(r){var _=r,t=0,e=0,n={};return n.write=function(r,n){if(r>>>n!=0)throw Error("length over");for(;t+n>=8;)_.writeByte(255&(r<<t|e)),n-=8-t,r>>>=8-t,e=0,t=0;e=r<<t|e,t+=n},n.flush=function(){t>0&&_.writeByte(e)},n},a=function(r){for(var _=1<<r,t=(1<<r)+1,e=r+1,$=f(),a=0;a<_;a+=1)$.add(String.fromCharCode(a));$.add(String.fromCharCode(_)),$.add(String.fromCharCode(t));var i=byteArrayOutputStream(),u=o(i);u.write(_,e);var c=0,g=String.fromCharCode(n[c]);for(c+=1;c<n.length;){var l=String.fromCharCode(n[c]);c+=1,$.contains(g+l)?g+=l:(u.write($.indexOf(g),e),4095>$.size()&&($.size()==1<<e&&(e+=1),$.add(g+l)),g=l)}return u.write($.indexOf(g),e),u.write(t,e),u.flush(),i.toByteArray()},f=function(){var r={},_=0,t={};return t.add=function(e){if(t.contains(e))throw Error("dup key:"+e);r[e]=_,_+=1},t.size=function(){return _},t.indexOf=function(_){return r[_]},t.contains=function(_){return void 0!==r[_]},t};return $},createImgTag=function(r,_,t,e){for(var n=gifImage(r,_),$=0;$<_;$+=1)for(var o=0;o<r;o+=1)n.setPixel(o,$,t(o,$));var a=byteArrayOutputStream();n.write(a);for(var f=base64EncodeOutputStream(),i=a.toByteArray(),u=0;u<i.length;u+=1)f.writeByte(i[u]);f.flush();var c="";return c+="data:image/gif;base64,",c+=f},drawImg=function(r,_){var t,e=(_=_||{}).typeNumber||4,n=_.errorCorrectLevel||"M",$=_.size||500;try{(t=qrcode(e,n||"M")).addData(r),t.make()}catch(o){if(!(e>=40))return drawImg(r,{size:$,errorCorrectLevel:n,typeNumber:e+1});throw Error("Text too long to encode")}var a=parseInt($/t.getModuleCount()),f=parseInt(($-t.getModuleCount()*a)/2);return t.createImgTag(a,f,$)}; + + +window.addEventListener('load', async () => { + let sleep = (n) => new Promise((r) => setTimeout(r, n)); + let wait = async (f, n = 100, s = 300) => { + let i = 0; + while (!(await f()) && i < s) (await sleep(n)) && i++; + if (i >= s) throw new Error('timeout'); + return f(); + }; + + await wait(() => ["coinos", "bitcoin", "btc", "btcpayserver", "btcpay server"].some(v => ~(document.querySelector(".payment-method-list__item__info").innerText.toLowerCase().indexOf(v)))); + + let sat = (s) => { + s = Math.abs(s); + let p = Math.floor(Math.log(s) / Math.LN10 + 0.000000001); + let d = Math.floor((p + 1) / 3); + + return ( + '⚡️' + + (parseInt(s) > 0 + ? new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 }).format( + s / Math.pow(1000, d) + ) + ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z'][d] + : 0 + ).toString() + ); + }; + + let copy = (text) => { + navigator.clipboard.writeText(text); + }; + + let f = (s, currency) => + new Intl.NumberFormat('en-US', { + style: 'currency', + currency + }) + .format(s) + .replace('CA', ''); + + let s = (s) => new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(s); + + let base64 = drawImg('test', { size: 300 }); + + let link = document.createElement('link'); + link.setAttribute('rel', 'stylesheet'); + link.setAttribute('type', 'text/css'); + link.setAttribute( + 'href', + 'https://fonts.googleapis.com/css?family=Public+Sans:400italic,400,300,700' + ); + + var style = document.createElement('style'); + style.innerHTML = \`#check { + transform: scale(0.5); + transition: transform 1s; + } + \`; + document.head.appendChild(style); + + document.head.appendChild(link); + document.title = 'Pay With Bitcoin'; + + let fiat = Shopify.checkout.payment_due; + let main = document.querySelector('main'); + main.style.fontFamily = 'Public Sans, sans-serif'; + main.innerHTML = 'Please Pay'; + + let data = await fetch('https://${url.host}/api/invoice', { + method: 'POST', + mode: 'cors', + headers: { + 'Content-Type': 'application/json', + accept: 'application/json', + }, + body: JSON.stringify({ + invoice: { + fiat, + type: 'lightning', + webhook: \`https://${url.host}/api/shopify/\${Shopify.checkout.order_id}\` + }, + user: { username: '${username}' } + }) + }) + .then(function (response) { + return response.json(); + }) + .catch(console.log); + + main.innerHTML = \`<a href="lightning:\${data.hash}"> + <div style="display: flex; margin-top: 50px; margin-bottom: 80px; position: relative;"> + <img id="qrcode" src="\${base64}" /> + <div id="bg"></div> + </div> + </a> + <div style="width: 100%; text-align: center; "> + <div style="font-size: 24px; font-weight: bold; margin-bottom: 12px;">\${f( + (data.amount * data.rate) / 100000000, + data.currency + )}</div> + <div style="color: #666; font-size: 18px;">\${sat(data.amount)}</div> + </div> + <div style="text-align: center; margin: 40px auto; max-width: 460px; word-break: break-all; line-height: 1.5rem">\${ + data.hash + }</div> + <div style="margin: 20px 0; display: flex; gap: 1rem; justify-content: center; align-items: center;"> + <a href="lightning:\${data.hash}" style="text-decoration: none"> + <div style="color: #999; font-size: 18px; border-radius: 9999px; padding: 0.5rem 1rem; border: 1px solid #ccc; background: white;">Open</div> + </a> + + <a id="copy" href="#" style="text-decoration: none"> + <div style="color: #999; font-size: 18px; border-radius: 9999px; padding: 0.5rem 1rem; border: 1px solid #ccc; background: white;">Copy</div></a> + </div> + \`; + + let copyButton = document.getElementById('copy'); + copyButton.addEventListener('click', (e) => { + e.preventDefault(); + copy(data.hash); + let copied = document.createElement('div'); + copied.textContent = 'Copied!'; + main.appendChild(copied); + setTimeout(() => copied.remove(), 3000); + }); + + let qr = document.getElementById('qrcode'); + qr.style.margin = 'auto'; + qr.style.marginTop = '40px'; + qr.style.border = '12px white solid'; + qr.style.zIndex = 10; + + new QRCode(qr, data.hash); + + let bg = document.getElementById('bg'); + bg.style.position = 'absolute'; + bg.style.margin = 'auto'; + bg.style.left = '0'; + bg.style.right = '0'; + bg.style.width = '400px'; + bg.style.height = '400px'; + bg.style.borderRadius = '50%'; + bg.style.background = 'linear-gradient(to right, #F2F6FC, #E1E3FF)'; + bg.style.zIndex = '0'; + + let ws = new WebSocket('wss://${url.host}/ws'); + ws.onmessage = ({ data }) => { + try { + let { type, data: d } = JSON.parse(data); + if (type === 'payment') { + main.innerHTML = + '<img id="check" src="https://${url.host}/icons/check.svg" alt="Check" />' + + \` + + <h1>Payment received!</h1> + + <div style="width: 100%; text-align: center; "> + <div style="font-size: 24px; font-weight: bold; margin-bottom: 12px;">\${f( + (d.amount * d.rate) / 100000000, + d.currency + )}</div> + <div style="color: #666; font-size: 18px;">\${sat(d.amount)}</div> + </div>\`; + + var image = document.getElementById('check'); + image.addEventListener('load', function () { + image.style.transform = 'scale(1)'; + }); + } + } catch (e) { + console.log(e); + } + }; + ws.onopen = () => { + ws.send(JSON.stringify({ type: 'subscribe', data })); + }; +}); + `; + + return new Response(js, { + headers: { + 'Content-Type': 'application/javascript' + } + }); +} diff --git a/src/routes/(app)/login/+page.svelte b/src/routes/(app)/login/+page.svelte index eb3ae26b..dce523cb 100644 --- a/src/routes/(app)/login/+page.svelte +++ b/src/routes/(app)/login/+page.svelte @@ -1,9 +1,10 @@ <script> import { RegisterOrLogin } from '$comp'; import { onMount } from 'svelte'; - import { pin } from '$lib/store'; + import { animatedRate, pin } from '$lib/store'; onMount(() => { + $animatedRate = 0; $pin = undefined; localStorage.clear(); sessionStorage.clear(); diff --git a/src/routes/(app)/payment/[id]/+page.svelte b/src/routes/(app)/payment/[id]/+page.svelte index 1da3f872..5037eec2 100644 --- a/src/routes/(app)/payment/[id]/+page.svelte +++ b/src/routes/(app)/payment/[id]/+page.svelte @@ -1,6 +1,6 @@ <script> import { t } from '$lib/translations'; - import { back, copy, f, s, fiat, post, sats, success } from '$lib/utils'; + import { back, copy, f, s, fiat, post, sats, success, types } from '$lib/utils'; import { Avatar, Icon } from '$comp'; import { format } from 'date-fns'; import { PUBLIC_EXPLORER as expl } from '$env/static/public'; @@ -42,6 +42,19 @@ </a> </div> {/if} + {#if p.type === types.pot} + <div> + <span class="text-lg text-secondary my-auto mr-2">{amount > 0 ? 'From' : 'To'}</span> + <a href={`/pot/${p.memo}`}> + <div class="flex"> + <div class="my-auto"> + <img src="/images/moneypot.png" class="w-12" alt="Pot" /> + </div> + <div class="my-auto ml-1">{p.memo}</div> + </div> + </a> + </div> + {/if} <div> <span class="text-lg text-secondary">Amount</span> <div> @@ -113,7 +126,7 @@ </div> {/if} - {#if ['laughingbean', 'burnabyartigiano', 'milanowestend'].includes(user.username.toLowerCase()) } + {#if ['laughingbean', 'burnabyartigiano', 'milanowestend', 'eotl', 'bigbite', 'edgemontartigiano'].includes(user.username.toLowerCase()) } <button class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px] bg-black text-white" on:click={print}>Print Receipt</button diff --git a/src/routes/(app)/pot/[name]/+page.svelte b/src/routes/(app)/pot/[name]/+page.svelte index aac70e8e..31e439d3 100644 --- a/src/routes/(app)/pot/[name]/+page.svelte +++ b/src/routes/(app)/pot/[name]/+page.svelte @@ -25,13 +25,7 @@ let toggle = () => (show = !show); </script> -<div class="container px-4 max-w-xl mx-auto mt-10 space-y-5"> - <div class="w-full flex"> - <a href="/" class="mx-auto"> - <Icon icon="logo" /> - </a> - </div> - +<div class="container px-4 max-w-4xl mx-auto mt-10 space-y-5"> <div class="flex justify-center items-center"> <div class="md:shadow-xl rounded-3xl md:px-10 pt-5 pb-10 space-y-5 w-full md:mx-5"> <div class="relative flex justify-center gap-2"> @@ -39,20 +33,15 @@ {#if name.split('-').length === 4} <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {name}</h1> {:else} - <img src="/images/moneypot.jpg" class="w-24" /> - <h1 class="text-2xl md:text-3xl font-semibold my-auto">Money Pot</h1> + <img src="/images/moneypot.jpg" class="w-24" alt="Pot" /> + <div class="my-auto"> + <h1 class="text-2xl font-semibold my-auto">{$t('payments.moneyPot')}</h1> + <h1 class="text-secondary my-auto">{name}</h1> + </div> {/if} </div> - <p class="text-secondary text-center"> - Anyone who knows the URL to this page can add or take out funds from the pot - </p> {#if show} <img {src} class="mx-auto" alt={name} /> - - <button class="flex mx-auto mt-5" on:click={() => copy($page.url.pathname)} - ><Icon icon="copy" style="w-10 max-w-none" /> - <div class="my-auto">Copy</div></button - > {/if} <div class="flex justify-center gap-4"> <div class="my-auto"> @@ -71,7 +60,7 @@ class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" > <Icon icon="plus" /> - Add funds + {$t('payments.addFunds')} </button> </a> </div> @@ -81,7 +70,7 @@ class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" > <Icon icon="minus" style="rotate-180" /> - Take funds + {$t('payments.takeFunds')} </button> </a> </div> @@ -92,14 +81,15 @@ on:click={toggle} > <Icon icon="qr" style="invert" /> - {show ? 'Hide' : 'Show'} QR + {show ? $t('payments.show') : $t('payments.hide')} QR + </button> + <button + class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" + on:click={() => copy($page.url.host + $page.url.pathname)} + > + <Icon icon="copy" style="w-6" /> + {$t('payments.copyLink')} </button> - <!-- <button --> - <!-- class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" --> - <!-- > --> - <!-- <Icon icon="nfc" style="w-6" /> --> - <!-- Write Tag --> - <!-- </button> --> </div> <div class="text-base"> {#each payments as p} diff --git a/src/routes/(app)/qr/[text]/+page.server.js b/src/routes/(app)/qr/[text]/+page.server.js index 9dd2b24d..3ab0725f 100644 --- a/src/routes/(app)/qr/[text]/+page.server.js +++ b/src/routes/(app)/qr/[text]/+page.server.js @@ -4,5 +4,5 @@ export async function load({ params }) { let { text } = params; let src = Qr.drawImg(text, { size: 600 }); - return { src }; + return { src, text }; } diff --git a/src/routes/(app)/qr/[text]/+page.svelte b/src/routes/(app)/qr/[text]/+page.svelte index 555f919c..d903da0e 100644 --- a/src/routes/(app)/qr/[text]/+page.svelte +++ b/src/routes/(app)/qr/[text]/+page.svelte @@ -1,7 +1,11 @@ <script> import { Qr } from '$comp'; export let data; - let { src } = data; + let { src, text } = data; </script> <Qr {src} /> + +<div class="text-center w-full font-bold"> + {text} +</div> diff --git a/src/routes/(app)/register/+page.server.js b/src/routes/(app)/register/+page.server.js index 4d740b5b..c90d4ee3 100644 --- a/src/routes/(app)/register/+page.server.js +++ b/src/routes/(app)/register/+page.server.js @@ -17,8 +17,6 @@ export const actions = { if (loginRedirect === 'undefined') loginRedirect = undefined; - console.log("OH", user) - try { await post('/register', { user }, { 'cf-connecting-ip': ip }); } catch (e) { diff --git a/src/routes/(app)/send/[...text]/+page.server.js b/src/routes/(app)/send/[...text]/+page.server.js index ff014315..cafce7c9 100644 --- a/src/routes/(app)/send/[...text]/+page.server.js +++ b/src/routes/(app)/send/[...text]/+page.server.js @@ -41,9 +41,7 @@ let parse = async (t, host) => { ({ id } = await get(`/invoice/${t}`)); } catch (e) { throw redirect(307, `/send/lightning/${t}`); - } - - if (id) throw redirect(307, `/send/${id}`); + } } // bitcoin diff --git a/src/routes/(app)/send/invoice/[id]/+page.svelte b/src/routes/(app)/send/invoice/[id]/+page.svelte index 81ae0e97..554eef6f 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.svelte +++ b/src/routes/(app)/send/invoice/[id]/+page.svelte @@ -1,16 +1,16 @@ <script> import { onDestroy, onMount } from 'svelte'; import { t } from '$lib/translations'; - import { goto } from '$app/navigation'; + import { goto, invalidateAll } from '$app/navigation'; import { pin } from '$lib/store'; import { enhance } from '$app/forms'; import { Avatar, Icon, Numpad, Spinner } from '$comp'; import { page } from '$app/stores'; - import { back, f, fiat as toFiat, s, sats } from '$lib/utils'; + import { post, back, f, fiat as toFiat, s, sats } from '$lib/utils'; export let data; export let form; - let { address, rate, payreq, recipient, tip, rates, user } = data; + let { address, hash, rate, payreq, recipient, tip, rates, user } = data; let { currency } = user; let amount = form?.amount || data.amount; @@ -27,6 +27,15 @@ let loading; let submit = () => (loading = true); + let external = async () => { + let invoice = { type: 'lightning', amount }; + let { hash } = await post(`/${recipient.username}/invoice`, { + invoice + }); + + goto(`/${recipient.username}/invoice/${hash}`, { invalidateAll: true }); + }; + $: rate = data.rate * (rates[user.currency] / rates[data.currency]); $: update(form); @@ -35,24 +44,24 @@ loading = false; }; - onMount(async () => { - if (browser && window.NDEFReader) { - try { - let ndef = new NDEFReader(); - await ndef.scan(); - - ndef.addEventListener('readingerror', (e) => { - console.log('nfc error', e); - }); - - ndef.addEventListener('reading', ({ message, serialNumber }) => { - console.log(message, serialNumber); - }); - } catch (e) { - console.log('NFC error', e); - } - } - }); + // onMount(async () => { + // if (browser && window.NDEFReader) { + // try { + // let ndef = new NDEFReader(); + // await ndef.scan(); + // + // ndef.addEventListener('readingerror', (e) => { + // console.log('nfc error', e); + // }); + // + // ndef.addEventListener('reading', ({ message, serialNumber }) => { + // console.log(message, serialNumber); + // }); + // } catch (e) { + // console.log('NFC error', e); + // } + // } + // }); </script> <button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}> @@ -130,4 +139,7 @@ {/if} </div> </form> + <div class="flex my-20"> + <button class="mx-auto" on:click={external}>{$t('payments.moreOptions')}</button> + </div> </div> diff --git a/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte b/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte index 9ce82d2e..32a13939 100644 --- a/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte +++ b/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte @@ -12,6 +12,7 @@ let { subject, rate, rates, user } = data; let currency = user.currency || subject.currency || 'USD'; + let next; let amount = form?.amount || data.amount; let a, @@ -59,7 +60,14 @@ {/if} <div class="container px-4 mt-20 max-w-xl mx-auto space-y-8"> - <Numpad bind:amount={a} bind:amountFiat={af} {currency} bind:fiat bind:rate={r} /> + <Numpad + bind:amount={a} + bind:amountFiat={af} + {currency} + bind:fiat + bind:rate={r} + bind:submit={next} + /> <form method="POST" use:enhance on:submit={submit}> <input name="amount" value={amount} type="hidden" /> @@ -82,8 +90,10 @@ {:else} <button type="button" + bind:this={next} class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" on:click={setAmount} + on:keydown={setAmount} > Next </button> diff --git a/src/routes/map/+page.server.js b/src/routes/map/+page.server.js new file mode 100644 index 00000000..7936e88b --- /dev/null +++ b/src/routes/map/+page.server.js @@ -0,0 +1,12 @@ +import { get } from '$lib/utils'; + +// export const prerender = true; +export let load = async () => { + let data = { locations: [] }; + + try { + data = await get('/locations'); + } catch (e) {} + + return data; +}; diff --git a/src/routes/map/+page.svelte b/src/routes/map/+page.svelte new file mode 100644 index 00000000..b5e4dfad --- /dev/null +++ b/src/routes/map/+page.svelte @@ -0,0 +1,8 @@ +<script> + import { LocationsMap } from "$comp"; + export let data; + + let { locations } = data; +</script> + +<LocationsMap {locations} /> |
