Implement project invitations.
[?]
Feb 9, 2021, 6:02 AM
46PUXHTYRNWQEELXOM7M7NTAFABN5JYQSB5HPN5VF4HWBDQWJU7QCDependencies
- [2]
AKM2VYBLFix errors with project ID persistence. - [3]
I5MPORH4Autofill signup form from query string parameters. - [4]
7TQPQW3NBegin adding parsing for project detail. - [5]
QAC2QJ32Add project overview page to client. - [6]
I4W76IFVRender recaptcha explicitly. - [7]
6L5BK5EHUse generic SMTP rather than Sendmail-specific mail client. - [8]
U256ZALIAdd captcha check to register route. - [9]
GLQSD33YUse mock capability for overview init. - [10]
O2BZOX7MAdd signup form, captcha check. - [11]
QMRKFEPGRefactor QDB to use a free monad algebra instead. - [12]
FBFDB2ZQWe can render QR codes now. - [13]
X3ES7NUAFine. I'll use ormolu. At least it doesn't break the code. - [14]
NEDDHXUKReformat via stylish-haskell - [15]
O5FVTOM6Undo JSON silliness, enable a couple more routes. - [16]
NAFJ6RB3Minor module reorg. - [17]
Z5KNL332Add skeleton of project overview HTML. - [18]
IPG33FAWAdd billing daemon - [19]
M4PWY5RUPreliminary work to add support for Zcash payments. - [20]
IR75ZMX3Return actual events for interval ends, not just timestamps. - [21]
ENNZIQJGUse live signup API for client. - [22]
YBLHJFCNImplement billing modal. - [23]
EFSXYZPOAutoformat everything with brittany. - [24]
T2DN23M7Factor out billing create component. - [25]
HMDM3B55Implement core of payments/billing infrastructure. - [26]
MJ6R42RCUtility methods for reading key & cert data. - [27]
H2ABVZI2Add endpoint for payment request creation. - [28]
2XQD6KKKAdd invitation logic and clean up DBProg error handling. - [29]
ZHV75AENbasic cleanup - [30]
V54JCKJXPayment request creation. - [31]
DAPLYXHYSuccessfully rendering QR codes sometimes. - [32]
SAESJLLYInitial experiments in hash routing. - [33]
4354Y4PEAdd endpoint to list project contributors. - [34]
RV7ZIULZUpdate overview to have access to the real project detail capability. - [35]
KET5QGQPAdd billable list (in-progress) - [36]
V2VDN77HEnable postgres configuration via environment variable for Heroku. - [37]
JFOEOFGAstylish-haskell formatting. - [38]
QH4UB73NFormat with purty. - [39]
5IDB3IWSIntegrate zcashd-based zaddr validation. - [40]
U7YAT2ZKAdd error reporting to signup form. - [41]
5R2Z7FSXInitial rendering for signup controls. - [42]
PPW6ROC5Render project data - [43]
ANDJ6GEYAdd billing component skeleton. - [44]
APOATM4XAdd getProjectDetail call to project API - [45]
XXJFUZOVAdd first revenue date to project payout computation. - [46]
KKJSBWO6Add createPaymentRequestHandler - [*]
3HTCTHHUAdd halogen-portal dependency and update argonaut. - [*]
4GOBY5NQWIP on modals. - [*]
EA5BFM5GSplit Login component into its own module. - [*]
MU6WOCCJUpdate auctions to permit zcash as a funding currency. - [*]
PBD7LZYQPostgres & auth are beginning to function. - [*]
ADMKQQGCInitial empty Snap project.
Change contents
- edit in client/src/Aftok/Api/Project.purs at line 9
import Data.Argonaut.Encode (encodeJson) - replacement in client/src/Aftok/Api/Project.purs at line 16
import Data.Maybe (Maybe)import Data.Maybe (Maybe(..)) - replacement in client/src/Aftok/Api/Project.purs at line 26
import Affjax (get)import Affjax (get, post) - edit in client/src/Aftok/Api/Project.purs at line 28
import Affjax.RequestBody as RB - replacement in client/src/Aftok/Api/Project.purs at line 35
(APIError)(APIError, CommsAddress(..), Zip321Request(..)) - edit in client/src/Aftok/Api/Project.purs at line 39
, parseResponse - edit in client/src/Aftok/Api/Project.purs at line 194[4.2250]
encodeInviteBy :: CommsAddress -> JsonencodeInviteBy = case _ ofEmailCommsAddr email -> encodeJson ({ email: email })ZcashCommsAddr zaddr -> encodeJson ({ zaddr: zaddr })type Invitation' by ={ greetName :: String, message :: Maybe String, inviteBy :: by}type Invitation = Invitation' CommsAddressencodeInvitation :: Invitation' Json -> JsonencodeInvitation = encodeJsontype InvResult ={ zip321_request :: Maybe String}decodeInvResult :: Json -> Either JsonDecodeError InvResultdecodeInvResult = decodeJsoninvite :: ProjectId -> Invitation -> Aff (Either APIError (Maybe Zip321Request))invite pid inv = dolet inv' = inv { inviteBy = encodeInviteBy inv.inviteBy }let body = RB.json $ encodeInvitation inv'response <- post RF.json ("/api/projects/" <> pidStr pid <> "/invite") (Just body)map (\r -> Zip321Request <$> r.zip321_request) <$> parseResponse decodeInvResult response - edit in client/src/Aftok/Api/Types.purs at line 7
import Data.Newtype (class Newtype) - edit in client/src/Aftok/Api/Types.purs at line 24[4.452]
data CommsType= EmailComms| ZcashCommsderive instance commsTypeEq :: Eq CommsTypedata CommsAddress= EmailCommsAddr String| ZcashCommsAddr Stringnewtype Zip321Request = Zip321Request Stringderive instance zip321RequestNewtype :: Newtype Zip321Request _ - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 9
import Data.Newtype (unwrap) - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 24
import Aftok.Api.Types (APIError(..))import Aftok.Api.Types (APIError(..), Zip321Request(..)) - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 27
, PaymentRequest'(..) - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 202
type QrInput = Maybe PaymentRequesttype QrInput = Maybe Zip321Request - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 205
{ req :: Maybe PaymentRequest{ req :: Maybe Zip321Request - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 210
= QrRender PaymentRequest a= QrRender Zip321Request a - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 249
, HH.div_[ HH.span[ P.classes (ClassName <$> ["code", "zip321uri"]) ](HH.text <<< unwrap <$> U.fromMaybe st.req)] - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 260
H.modify_ (_ { dataUrl = Just dataUrl })H.modify_ (_ { req = Just r, dataUrl = Just dataUrl }) - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 271[4.14505]→[4.987:1028](∅→∅),[4.1028]→[4.8432:8465](∅→∅),[4.8432]→[4.8432:8465](∅→∅),[4.8465]→[4.1029:1103](∅→∅)
renderQR :: PaymentRequest -> m StringrenderQR (PaymentRequest r) =system.renderQR { value: r.native_request.zip321_request, size: 300 }[4.14505]renderQR :: Zip321Request -> m StringrenderQR (Zip321Request r) =system.renderQR { value: r, size: 300 } - replacement in client/src/Aftok/Billing.purs at line 28
import Aftok.Api.Types (APIError(..))import Aftok.Api.Types (APIError(..), Zip321Request(..)) - edit in client/src/Aftok/Billing.purs at line 35
, PaymentRequest'(..) - replacement in client/src/Aftok/Billing.purs at line 230
PaymentRequestCreated req -> dolift $ system.log "Created payment request, closing modal."PaymentRequestCreated (PaymentRequest req) -> do - edit in client/src/Aftok/Billing.purs at line 232
lift $ system.log "About to show QR code modal" - replacement in client/src/Aftok/Billing.purs at line 233
_ <- H.query _showPaymentRequest unit $ H.tell (PaymentRequest.QrRender req)let req' = Zip321Request req.native_request.zip321_request_ <- H.query _showPaymentRequest unit $ H.tell (PaymentRequest.QrRender req') - file addition: Forms.purs[49.3158]
module Aftok.HTML.Forms whereimport Preludeimport Data.Maybe (Maybe(..), fromMaybe)import Halogen.HTML.Core (AttrName(..), ClassName(..))import Halogen.HTML as HHimport Halogen.HTML.CSS as CSSimport Halogen.HTML.Events as Eimport Halogen.HTML.Properties as Pimport CSS.Display (display, flex)import CSS.Flexbox (flexFlow, row, nowrap)import Aftok.Api.Types (CommsType(..))type CommsState r ={ recoveryType :: CommsType, recoveryEmail :: Maybe String, recoveryZAddr :: Maybe String| r }type SetCommsType action = CommsType -> actiontype SetEmail action = String -> actiontype SetZaddr action = String -> actioncommsSwitch :: forall i a. SetCommsType a -> CommsType -> HH.HTML i acommsSwitch setCommsType rt =HH.div[ P.classes (ClassName <$> [ "form-group", "mb-3" ]) ][ HH.label[ P.for "commsSwitch" ][ HH.text "Choose a communications method" ], HH.div[ P.classes (ClassName <$> [ "form-group", "mb-3" ]), CSS.style dodisplay flexflexFlow row nowrap][ HH.span[ P.classes (ClassName <$> [ if rt == EmailComms then "text-success" else "text-muted" ]) ]$ [ HH.text "Email" ], HH.div[ P.classes (ClassName <$> [ "custom-control", "custom-switch", "custom-switch-light", "mx-3" ]) ][ HH.input[ P.type_ P.InputCheckbox, P.classes (ClassName <$> [ "custom-control-input" ]), P.id_ "commsSwitch", P.checked (rt == ZcashComms), E.onChecked (\b -> Just <<< setCommsType $ if b then ZcashComms else EmailComms)], HH.label [ P.classes (ClassName <$> [ "custom-control-label" ]), P.for "commsSwitch" ] []], HH.span[ P.classes (ClassName <$> [ if rt == ZcashComms then "text-success" else "text-muted" ]) ][ HH.text "Z-Address" ]]]type CommsErrors i a = CommsType -> Array (HH.HTML i a)commsField ::forall i a r.SetEmail a ->SetZaddr a ->CommsState r ->CommsErrors i a ->HH.HTML i acommsField setEmail setZAddr st errs = case st.recoveryType ofEmailComms ->HH.div[ P.id_ "recoveryEmail" ]$ [ HH.label [ P.for "email" ] [ HH.text "Email Address" ], HH.input[ P.type_ P.InputEmail, P.classes (ClassName <$> [ "form-control" ]), P.id_ "email", P.placeholder "name@address.com", P.value (fromMaybe "" st.recoveryEmail), E.onValueInput (Just <<< setEmail)]]<> errs EmailCommsZcashComms ->HH.div[ P.id_ "recoveryZAddr" ]$ [ HH.label[ P.for "zaddr" ][ HH.text "Zcash Shielded Address", HH.a[ P.attr (AttrName "data-toggle") "modal", P.href "#modalAboutZAddr"][ HH.img [ P.src "/assets/img/icons/duotone-icons/Code/Info-circle.svg" ]]], HH.input[ P.type_ P.InputText, P.classes (ClassName <$> [ "form-control" ]), P.id_ "email", P.placeholder "Enter a Zcash shielded address", P.value (fromMaybe "" st.recoveryZAddr), E.onValueInput (Just <<< setZAddr)]]<> errs ZcashComms - edit in client/src/Aftok/Overview.purs at line 26
import Aftok.Billing.PaymentRequest as PaymentRequestimport Aftok.Modals as Modalsimport Aftok.Modals.ModalFFI as ModalFFI - edit in client/src/Aftok/Overview.purs at line 30
import Aftok.Projects.Invite as Invite - replacement in client/src/Aftok/Overview.purs at line 32
import Aftok.Api.Types (APIError)import Aftok.Api.Types (APIError, Zip321Request) - edit in client/src/Aftok/Overview.purs at line 59
| InvitationCreated (Maybe Zip321Request) - edit in client/src/Aftok/Overview.purs at line 66
, invitationModal :: Invite.Slot Unit, inviteQRModal :: PaymentRequest.QrSlot Unit - edit in client/src/Aftok/Overview.purs at line 71
_invitationModal = SProxy :: SProxy "invitationModal"_inviteQRModal = SProxy :: SProxy "inviteQRModal" - edit in client/src/Aftok/Overview.purs at line 76
, invitationCaps :: Invite.Capability m - edit in client/src/Aftok/Overview.purs at line 168
<>[ HH.div[ P.classes (ClassName <$> [ "row", "pt-3", "font-weight-bold" ]) ][ HH.div[ P.classes (ClassName <$> [ "col-md-2" ]) ][ Modals.modalButton Invite.modalId "Invite a collaborator" Nothing], system.portal_invitationModalunit(Invite.component system caps.invitationCaps)project.projectIdNothing(Just <<< InvitationCreated), system.portal_inviteQRModalunit(PaymentRequest.qrcomponent system)NothingNothing(const Nothing)]] - edit in client/src/Aftok/Overview.purs at line 222
-- </section>-- <!-- Map payouts -->-- <div class="row font-weight-bold">-- <div class="col-md-2">-- </div>-- <div class="col-md-4">-- Payments-- </div>-- <div class="col-md-6">---- </div>-- </div>-- <div class="row">-- <div class="col-md-2">-- </div>-- <div class="col-md-2">-- Oct 20 2020-- </div>-- <div class="col-md-2">-- 100 zec-- </div>-- <div class="col-md-2">-- Acme PaidUsRight-- </div>-- <div class="col-md-4">-- </div>-- </div>-- <!-- map payout creditTos-->-- <div class="row pt-3">-- <div class="col-md-4">-- </div>-- <div class="col-md-2">-- Freuline Fred-- </div>-- <div class="col-md-2">-- 2.4 zec-- </div>-- <div class="col-md-2">-- 2.4 %-- </div>-- <div class="col-md-2">-- </div>-- </div>-- <div class="row pt-3">-- <div class="col-md-4">-- </div>-- <div class="col-md-2">-- Goobie Works A Lot-- </div>-- <div class="col-md-2">-- 50 zec-- </div>-- <div class="col-md-2">-- 50 %-- </div>-- <div class="col-md-2">-- </div>-- </div> <div class="row pt-3">-- <div class="col-md-4">-- </div>-- <div class="col-md-2">-- Average Fella-- </div>-- <div class="col-md-2">-- 25 zec-- </div>-- <div class="col-md-2">-- 25 %-- </div>-- <div class="col-md-2">-- </div>-- </div> <div class="row pt-3">-- <div class="col-md-4">-- </div>-- <div class="col-md-2">-- Cool Kid-- </div>-- <div class="col-md-2">-- 24.6 zec-- </div>-- <div class="col-md-2">-- 24.6 %-- </div>-- <div class="col-md-2">-- </div>-- </div>---- </section>------ <!-- New Project form-->-- <section id="addProject">---- <div class="row pt-3">-- <div class="col-md-4">-- <span class="float-right">Project Name</span>-- </div>-- <div class="col-md-4">-- <input type="text" id="projectName" name="projectName" />-- </div>-- </div>---- <div class="row pt-3">-- <div class="col-md-4">-- <span class="float-right">Undepreciated Period ( Months )</span>-- </div>-- <div class="col-md-4">-- <input type="text" id="undepreciatedPeriod" name="undepreciatedPeriod" />-- </div>-- </div>---- <div class="row pt-3">-- <div class="col-md-4">-- <span class="float-right">Depreciation Duration ( Months )</span>-- </div>-- <div class="col-md-4">-- <input type="text" id="depreciationDuration" name="depreciationDuration" />-- </div>-- </div>---- <div class="row pt-3 pb-3">-- <div class="col-md-2">-- </div>-- <div class="col-md-10">-- <button class="btn btn-sm btn-primary lift ml-auto">Add Project</button>-- </div>-- </div>---- </section> - edit in client/src/Aftok/Overview.purs at line 237
InvitationCreated req -> dolift $ system.toggleModal Invite.modalId ModalFFI.HideModallift $ system.toggleModal PaymentRequest.qrModalId ModalFFI.ShowModaltraverse_ (\r -> H.query _inviteQRModal unit $ H.tell (PaymentRequest.QrRender r)) reqpure unit - edit in client/src/Aftok/Overview.purs at line 253
, invitationCaps: Invite.apiCapability - edit in client/src/Aftok/Overview.purs at line 282
, invitationCaps: Invite.apiCapability - file addition: Projects[50.1]
- file addition: Invite.purs[0.7504]
module Aftok.Projects.Invite whereimport Preludeimport Control.Monad.Trans.Class (lift)import Data.Array (filter)import Data.Either (Either(..), note)import Data.Foldable (any)import Data.Maybe (Maybe(..))import Data.Validation.Semigroup (V(..), toEither)import Effect.Aff (Aff)import Halogen as Himport Halogen.HTML as HHimport Halogen.HTML.Core (ClassName(..))import Halogen.HTML.Events as Eimport Halogen.HTML.Properties as Pimport Aftok.Api.Account as Accimport Aftok.Api.Project as Projectimport Aftok.Api.Project (Invitation')import Aftok.Api.Types (APIError, CommsType(..), CommsAddress(..), Zip321Request)import Aftok.HTML.Forms (commsSwitch, commsField)import Aftok.HTML.Classes as Cimport Aftok.Modals as Modalsimport Aftok.Modals.ModalFFI as ModalFFIimport Aftok.Types (System, ProjectId)data Field= NameField| EmailField| ZAddrFieldderive instance fieldEq :: Eq Fieldderive instance fieldOrd :: Ord Fieldtype CState ={ projectId :: ProjectId, greetName :: Maybe String, message :: Maybe String, recoveryType :: CommsType, recoveryEmail :: Maybe String, recoveryZAddr :: Maybe String, fieldErrors :: Array Field}type Input = ProjectIdtype Output = Maybe Zip321Requestdata Action= ProjectChanged ProjectId| SetGreetName String| SetMessage String| SetCommsType CommsType| SetEmail String| SetZAddr String| CreateInvitationtype Slot id= forall query. H.Slot query Output idtype Capability (m :: Type -> Type)= { createInvitation :: ProjectId -> Invitation' CommsAddress -> m (Either APIError (Maybe Zip321Request)), checkZAddr :: String -> m Acc.ZAddrCheckResponse}modalId :: StringmodalId = "createInvitation"component ::forall query m.Monad m =>System m ->Capability m ->H.Component HH.HTML query Input Output mcomponent system caps =H.mkComponent{ initialState, render, eval:H.mkEval$ H.defaultEval{ handleAction = eval, receive = Just <<< ProjectChanged}}whereinitialState :: Input -> CStateinitialState input ={ projectId: input, greetName : Nothing, message : Nothing, recoveryType: EmailComms, recoveryEmail: Nothing, recoveryZAddr: Nothing, fieldErrors : []}render :: forall slots. CState -> H.ComponentHTML Action slots mrender st =Modals.modalWithSave modalId "Invite a collaborator" CreateInvitation[ HH.form_[ formGroup st[ NameField ][ HH.label[ P.for "greetName"][ HH.text "Name" ], HH.input[ P.type_ P.InputText, P.classes [ C.formControl, C.formControlSm ], P.id_ "greetName", P.placeholder "Who are you inviting?", E.onValueInput (Just <<< SetGreetName)]], formGroup st[ ][ HH.label[ P.for "message"][ HH.text "Message" ], HH.input[ P.type_ P.InputText, P.classes [C.formControl, C.formControlSm], P.id_ "message", P.placeholder "Enter your message here", E.onValueInput (Just <<< SetMessage)]], commsSwitch SetCommsType st.recoveryType, commsField SetEmail SetZAddr st $ case _ ofEmailComms -> fieldError st EmailFieldZcashComms -> fieldError st ZAddrField]]formGroup :: forall i a. CState -> Array Field -> Array (HH.HTML i a) -> HH.HTML i aformGroup st fields body =HH.div[ P.classes [C.formGroup] ](body <> (fieldError st =<< fields))fieldError :: forall i a. CState -> Field -> Array (HH.HTML i a)fieldError st field =if any (_ == field) st.fieldErrorsthen case field ofNameField -> err "The name field is required"EmailField -> err "The email field is when email comms are selected"ZAddrField -> err "Not a valid Zcash shielded address."else []whereerr str = [ HH.div_ [ HH.span [ P.classes (ClassName <$> [ "badge", "badge-danger-soft" ]) ] [ HH.text str ] ] ]setZAddr addr = dozres <- lift $ caps.checkZAddr addrH.modify_ (_ { recoveryZAddr = Just addr })case zres ofAcc.ZAddrCheckValid ->H.modify_ (\st -> st { fieldErrors = filter (_ /= ZAddrField) st.fieldErrors, recoveryType = ZcashComms })Acc.ZAddrCheckInvalid ->H.modify_ (\st -> st { fieldErrors = st.fieldErrors <> [ZAddrField] })-- eval :: forall slots. Action -> H.HalogenM CState Action slots Output m Uniteval = case _ ofProjectChanged pid ->H.modify_ (_ { projectId = pid })SetGreetName name ->H.modify_ (_ { greetName = Just name })SetMessage msg ->H.modify_ (_ { message = Just msg })SetCommsType t ->H.modify_ (_ { recoveryType = t })SetEmail email ->H.modify_ (_ { recoveryEmail = Just email })SetZAddr addr ->when (addr /= "") (setZAddr addr)CreateInvitation -> donameV <- V <<< note [NameField] <$> H.gets (_.greetName)message <- H.gets (_.message)addrType <- H.gets (_.recoveryType)addrV <-case addrType ofEmailComms -> map EmailCommsAddr <<< V <<< note [EmailField] <$> H.gets (_.recoveryEmail)ZcashComms -> map ZcashCommsAddr <<< V <<< note [ZAddrField] <$> H.gets (_.recoveryZAddr)let reqV :: V (Array Field) (Invitation' CommsAddress)reqV = { greetName: _, message: _, inviteBy: _ }<$> nameV<*> pure message<*> addrVcase toEither reqV ofLeft errors -> doH.modify_ (_ { fieldErrors = errors })Right invitation -> dopid <- H.gets (_.projectId)res <- lift $ caps.createInvitation pid invitationcase res ofRight result -> doH.raise resultlift $ system.toggleModal modalId ModalFFI.HideModalLeft errs ->lift $ system.error (show errs)apiCapability :: Capability AffapiCapability= { createInvitation: Project.invite, checkZAddr: Acc.checkZAddr} - edit in client/src/Aftok/Signup.purs at line 18
-- import Affjax (post, get, printError) - edit in client/src/Aftok/Signup.purs at line 19
-- import Affjax.RequestBody as RB-- import Affjax.ResponseFormat as RF - replacement in client/src/Aftok/Signup.purs at line 20
import Halogen.HTML.Core (AttrName(..), ClassName(..))import Halogen.HTML.Core (ClassName(..)) - edit in client/src/Aftok/Signup.purs at line 22
import Halogen.HTML.CSS as CSS - edit in client/src/Aftok/Signup.purs at line 25
-- import CSS (backgroundImage, url)import CSS.Display (display, flex)import CSS.Flexbox (flexFlow, row, nowrap) - edit in client/src/Aftok/Signup.purs at line 30
import Aftok.Api.Types (CommsType(..))import Aftok.HTML.Forms (commsSwitch, commsField) - edit in client/src/Aftok/Signup.purs at line 60
data RecoveryType= RecoveryEmail| RecoveryZAddrderive instance recoveryTypeEq :: Eq RecoveryType - replacement in client/src/Aftok/Signup.purs at line 65
, recoveryType :: RecoveryType, recoveryType :: CommsType - replacement in client/src/Aftok/Signup.purs at line 77
| SetRecoveryType RecoveryType| SetRecoveryType CommsType - replacement in client/src/Aftok/Signup.purs at line 121
, recoveryType: RecoveryEmail, recoveryType: EmailComms - replacement in client/src/Aftok/Signup.purs at line 165
<> signupErrors UsernameField st<> signupErrors st UsernameField - replacement in client/src/Aftok/Signup.purs at line 179
<> signupErrors PasswordField st<> signupErrors st PasswordField - replacement in client/src/Aftok/Signup.purs at line 190
<> signupErrors ConfirmField st, recoverySwitch st.recoveryType, recoveryField st<> signupErrors st ConfirmField, commsSwitch SetRecoveryType st.recoveryType, commsField SetRecoveryEmail SetRecoveryZAddr st $case _ ofEmailComms -> signupErrors st EmailFieldZcashComms -> signupErrors st ZAddrField - replacement in client/src/Aftok/Signup.purs at line 207
] <> signupErrors InvCodesField st] <> signupErrors st InvCodesField - replacement in client/src/Aftok/Signup.purs at line 232
H.modify_ (\st -> st { signupErrors = M.delete ZAddrField st.signupErrors, recoveryType = RecoveryZAddr })H.modify_ (\st -> st { signupErrors = M.delete ZAddrField st.signupErrors, recoveryType = ZcashComms }) - replacement in client/src/Aftok/Signup.purs at line 272
SetRecoveryType t -> H.modify_ (_ { recoveryType = t })SetRecoveryEmail email -> H.modify_ (_ { recoveryEmail = Just email })SetRecoveryType t ->H.modify_ (_ { recoveryType = t })SetRecoveryEmail email ->H.modify_ (_ { recoveryEmail = Just email }) - replacement in client/src/Aftok/Signup.purs at line 289
RecoveryEmail -> V <<< note [ EmailRequired ] <<< map Acc.RecoverByEmail <$> H.gets (_.recoveryEmail)RecoveryZAddr -> V <<< note [ ZAddrRequired ] <<< map Acc.RecoverByZAddr <$> H.gets (_.recoveryZAddr)EmailComms -> V <<< note [ EmailRequired ] <<< map Acc.RecoverByEmail <$> H.gets (_.recoveryEmail)ZcashComms -> V <<< note [ ZAddrRequired ] <<< map Acc.RecoverByZAddr <$> H.gets (_.recoveryZAddr) - replacement in client/src/Aftok/Signup.purs at line 333
signupErrors :: forall i a. SignupField -> SignupState -> Array (HH.HTML i a)signupErrors field st = case M.lookup field st.signupErrors ofsignupErrors :: forall i a. SignupState -> SignupField -> Array (HH.HTML i a)signupErrors st field = case M.lookup field st.signupErrors of - edit in client/src/Aftok/Signup.purs at line 348[4.6158]→[4.6158:6225](∅→∅),[4.6225]→[4.23216:23236](∅→∅),[4.23236]→[4.6246:6255](∅→∅),[4.6246]→[4.6246:6255](∅→∅),[4.6255]→[4.23237:23393](∅→∅),[4.23393]→[4.6407:6420](∅→∅),[4.6407]→[4.6407:6420](∅→∅),[4.6420]→[4.23394:23670](∅→∅),[4.23670]→[4.18015:18049](∅→∅),[4.18049]→[4.23702:24007](∅→∅),[4.23702]→[4.23702:24007](∅→∅),[4.24007]→[3.3753:3803](∅→∅),[3.3803]→[4.24007:24415](∅→∅),[4.24007]→[4.24007:24415](∅→∅),[4.1257]→[4.7015:7025](∅→∅),[4.24415]→[4.7015:7025](∅→∅),[4.7015]→[4.7015:7025](∅→∅),[4.7184]→[4.7184:7299](∅→∅),[4.7299]→[4.24416:24446](∅→∅),[4.24446]→[4.7331:7363](∅→∅),[4.7331]→[4.7331:7363](∅→∅),[4.7363]→[4.18050:18474](∅→∅),[4.18474]→[4.7716:7735](∅→∅),[4.7716]→[4.7716:7735](∅→∅),[4.7735]→[4.24745:24756](∅→∅),[4.24756]→[4.7747:7779](∅→∅),[4.7747]→[4.7747:7779](∅→∅),[4.7779]→[4.18475:19204](∅→∅),[4.19204]→[4.2735:2736](∅→∅),[4.8397]→[4.2735:2736](∅→∅)
recoverySwitch :: forall i. RecoveryType -> HH.HTML i SignupActionrecoverySwitch rt =HH.div[ P.classes (ClassName <$> [ "form-group", "mb-3" ]) ][ HH.label[ P.for "recoverySwitch" ][ HH.text "Choose a recovery method" ], HH.div[ P.classes (ClassName <$> [ "form-group", "mb-3" ]), CSS.style dodisplay flexflexFlow row nowrap][ HH.span[ P.classes (ClassName <$> [ if rt == RecoveryEmail then "text-success" else "text-muted" ]) ]$ [ HH.text "Email" ], HH.div[ P.classes (ClassName <$> [ "custom-control", "custom-switch", "custom-switch-light", "mx-3" ]) ][ HH.input[ P.type_ P.InputCheckbox, P.classes (ClassName <$> [ "custom-control-input" ]), P.id_ "recoverySwitch", P.checked (rt == RecoveryZAddr), E.onChecked (\b -> Just <<< SetRecoveryType $ if b then RecoveryZAddr else RecoveryEmail)], HH.label [ P.classes (ClassName <$> [ "custom-control-label" ]), P.for "recoverySwitch" ] []], HH.span[ P.classes (ClassName <$> [ if rt == RecoveryZAddr then "text-success" else "text-muted" ]) ][ HH.text "Z-Address" ]]]recoveryField :: forall i. SignupState -> HH.HTML i SignupActionrecoveryField st = case st.recoveryType ofRecoveryEmail ->HH.div[ P.id_ "recoveryEmail" ]$ [ HH.label [ P.for "email" ] [ HH.text "Email Address" ], HH.input[ P.type_ P.InputEmail, P.classes (ClassName <$> [ "form-control" ]), P.id_ "email", P.placeholder "name@address.com", P.value (fromMaybe "" st.recoveryEmail), E.onValueInput (Just <<< SetRecoveryEmail)]]<> signupErrors EmailField stRecoveryZAddr ->HH.div[ P.id_ "recoveryZAddr" ]$ [ HH.label[ P.for "zaddr" ][ HH.text "Zcash Shielded Address", HH.a[ P.attr (AttrName "data-toggle") "modal", P.href "#modalAboutZAddr"][ HH.img [ P.src "/assets/img/icons/duotone-icons/Code/Info-circle.svg" ]]], HH.input[ P.type_ P.InputText, P.classes (ClassName <$> [ "form-control" ]), P.id_ "email", P.placeholder "Enter a Zcash shielded address", P.value (fromMaybe "" st.recoveryZAddr), E.onValueInput (Just <<< SetRecoveryZAddr)]]<> signupErrors ZAddrField st - edit in daemon/AftokD/AftokM.hs at line 26
import qualified Aftok.Payments.Types as P - edit in daemon/AftokD/AftokM.hs at line 27
import qualified Aftok.Payments.Types as P - edit in lib/Aftok/Config.hs at line 6
import Aftok.Project (projectName) - replacement in lib/Aftok/Config.hs at line 8
import Aftok.Currency.Zcash (Zatoshi(..))import Aftok.Currency.Zcash.Types (Memo(..))import Aftok.Currency.Zcash (Zatoshi (..))import Aftok.Currency.Zcash.Types (Memo (..)) - replacement in lib/Aftok/Config.hs at line 11
import Aftok.Payments (PaymentsConfig(..))import Aftok.Payments (PaymentsConfig (..)) - edit in lib/Aftok/Config.hs at line 14
import Aftok.Project (projectName) - replacement in lib/Aftok/Config.hs at line 93
{ _bitcoinConfig :: BitcoinConfig, _zcashConfig :: Zcash.PaymentsConfig{ _bitcoinConfig :: BitcoinConfig,_zcashConfig :: Zcash.PaymentsConfig - replacement in lib/Aftok/Config.hs at line 141[4.15373]→[4.3273:3369](∅→∅),[4.3273]→[4.3273:3369](∅→∅),[4.3369]→[4.15374:15412](∅→∅),[4.15412]→[4.3369:3419](∅→∅),[4.3369]→[4.3369:3419](∅→∅)
pure $ PaymentsConfig {_bitcoinBillingOps = btcOps,_bitcoinPaymentsConfig = btcCfg,_zcashBillingOps = _zcashMemoGen,_zcashPaymentsConfig = cfg ^. zcashConfig}pure $PaymentsConfig{ _bitcoinBillingOps = btcOps,_bitcoinPaymentsConfig = btcCfg,_zcashBillingOps = _zcashMemoGen,_zcashPaymentsConfig = cfg ^. zcashConfig} - edit in lib/Aftok/Config.hs at line 180
- edit in lib/Aftok/Config.hs at line 188
- replacement in lib/Aftok/Config.hs at line 194
Bitcoin.PaymentKey-> m (Maybe URI)Bitcoin.PaymentKey ->m (Maybe URI) - edit in lib/Aftok/Config.hs at line 199
- edit in lib/Aftok/Currency/Zcash.hs at line 15
Z.Memo (..), - replacement in lib/Aftok/Payments/Util.hs at line 20
import Aftok.Types (ProjectId, AccountId)import Aftok.Types (AccountId, ProjectId) - replacement in lib/Aftok/Payments/Zcash.hs at line 13
import Aftok.Currency.Zcash.Types (Memo(..))import Aftok.Currency.Zcash.Types (Memo (..)) - replacement in lib/Aftok/Payments/Zcash.hs at line 75[4.18117]→[4.18117:18142](∅→∅),[4.18142]→[4.84386:84507](∅→∅),[4.84386]→[4.84386:84507](∅→∅),[4.84507]→[4.18143:18167](∅→∅),[4.18167]→[4.84597:84629](∅→∅),[4.84597]→[4.84597:84629](∅→∅)
pure $ PaymentItem{ _address = a,_label = Nothing,_message = billable ^. messageText,_amount = z,_memo = memo,_other = []}[4.18117]pure $PaymentItem{ _address = a,_label = Nothing,_message = billable ^. messageText,_amount = z,_memo = memo,_other = []} - edit in server/Aftok/ServerConfig.hs at line 27[4.6910]→[4.4838:4839](∅→∅),[4.7028]→[4.4838:4839](∅→∅),[4.56477]→[4.4838:4839](∅→∅),[4.4838]→[4.4838:4839](∅→∅)
- edit in server/Aftok/Snaplet/Billing.hs at line 25
import qualified Aftok.Currency.Zcash.Zip321 as Zip321import Aftok.Database.PostgreSQL (QDBM) - edit in server/Aftok/Snaplet/Billing.hs at line 31
import Aftok.Database.PostgreSQL (QDBM) - edit in server/Aftok/Snaplet/Billing.hs at line 48
zcashBillingOps, - edit in server/Aftok/Snaplet/Billing.hs at line 50
zcashBillingOps - edit in server/Aftok/Snaplet/Billing.hs at line 56
nativeRequest, - edit in server/Aftok/Snaplet/Billing.hs at line 57
nativeRequest, - edit in server/Aftok/Snaplet/Billing.hs at line 62
qdbmEval, - edit in server/Aftok/Snaplet/Billing.hs at line 68
qdbmEval - edit in server/Aftok/Snaplet/Billing.hs at line 70
import Aftok.Snaplet.Json (zip321PaymentRequestJSON) - edit in server/Aftok/Snaplet/Billing.hs at line 201
zip321PaymentRequestJSON :: Zip321.PaymentRequest -> Valuezip321PaymentRequestJSON r =v1 . obj $["zip321_request" .= (toJSON . Zip321.toURI $ r)] - edit in server/Aftok/Snaplet/Json.hs at line 3
zip321PaymentRequestJSON, - edit in server/Aftok/Snaplet/Json.hs at line 7
import qualified Aftok.Currency.Zcash.Zip321 as Zip321 - replacement in server/Aftok/Snaplet/Json.hs at line 10
import Data.Aeson ((.=), Value)import Data.Aeson ((.=), Value, toJSON) - edit in server/Aftok/Snaplet/Json.hs at line 15[4.27384]
zip321PaymentRequestJSON :: Zip321.PaymentRequest -> Valuezip321PaymentRequestJSON r =v1 . obj $["zip321_request" .= (toJSON . Zip321.toURI $ r)] - edit in server/Aftok/Snaplet/Projects.hs at line 18
projectInviteResponseJSON, - edit in server/Aftok/Snaplet/Projects.hs at line 23
import qualified Aftok.Currency.Zcash as Zcashimport qualified Aftok.Currency.Zcash.Zip321 as Zip321 - edit in server/Aftok/Snaplet/Projects.hs at line 31
import Aftok.Snaplet.Json (zip321PaymentRequestJSON) - replacement in server/Aftok/Snaplet/Projects.hs at line 47
import Data.Aeson ((.:), (.=), Value (..), object)import Data.Attoparsec.ByteString (takeByteString)import Data.Aeson ((.:), (.:?), (.=), Value (..), object) - edit in server/Aftok/Snaplet/Projects.hs at line 153
data CommsAddress= EmailComms Text| ZcashComms Textdata ProjectInviteRequest= PIR{ greetName :: Text,message :: Maybe Text,inviteBy :: CommsAddress} - replacement in server/Aftok/Snaplet/Projects.hs at line 165
projectInviteHandler :: ServerConfig -> S.Handler App App ()instance A.FromJSON ProjectInviteRequest whereparseJSON (A.Object v) = doname <- v .: "greetName"message <- v .:? "message"comms <- v .: "inviteBy"emailComms <- fmap EmailComms <$> (comms .:? "email")zcashComms <- fmap ZcashComms <$> (comms .:? "zaddr")case emailComms <|> zcashComms ofNothing -> mzeroJust addr -> pure $ PIR name message addrparseJSON _ = mzerodata ProjectInviteResponse= ProjectInviteResponse{ zip321URI :: Maybe Zip321.PaymentRequest}projectInviteResponseJSON :: ProjectInviteResponse -> ValueprojectInviteResponseJSON resp =case zip321URI resp ofJust r -> zip321PaymentRequestJSON rNothing -> object []projectInviteHandler :: ServerConfig -> S.Handler App App ProjectInviteResponse - replacement in server/Aftok/Snaplet/Projects.hs at line 192[4.4242]→[4.9070:9145](∅→∅),[4.9718]→[4.9070:9145](∅→∅),[4.53951]→[4.9070:9145](∅→∅),[4.64472]→[4.9070:9145](∅→∅),[4.9070]→[4.9070:9145](∅→∅)
toEmail <- parseParam "email" (fmap (Email . decodeUtf8) takeByteString)requestBody <- readRequestBody 4096req <- either (snapError 400 . show) pure $ A.eitherDecode requestBody - replacement in server/Aftok/Snaplet/Projects.hs at line 195
(Just p, invCode) <-snapEval $(,)<$> (runMaybeT $ findUserProject uid pid)<*> createInvitation pid uid toEmail tliftIO $sendProjectInviteEmailcfg(p ^. projectName)(Email "noreply@aftok.com")toEmailinvCodelet invite email =snapEval $(,)<$> (runMaybeT $ findUserProject uid pid)<*> createInvitation pid uid email tcase inviteBy req ofEmailComms email -> do(Just p, invCode) <- invite (Email email)liftIO $sendProjectInviteEmailcfg(p ^. projectName)(Email "noreply@aftok.com")(Email email)invCodepure (ProjectInviteResponse Nothing)ZcashComms zaddr -> do(Just p, invCode) <- invite (Email "")pure . ProjectInviteResponse . Just$ Zip321.PaymentRequest . pure$ Zip321.PaymentItem{ _address = Zcash.Address zaddr,_amount = Zcash.Zatoshi 1000,_memo =Just . Zcash.Memo . encodeUtf8 $"Welcome to the " <> (p ^. projectName) <> " aftok, " <> greetName req <> "\n"<> maybe "" (<> "\n") (message req)<> "https://aftok.com/app/?invcode="<> renderInvCode invCode<> "&zaddr="<> zaddr,_message = Nothing,_label = Nothing,_other = []} - replacement in server/Main.hs at line 85
inviteRoute = void $ method POST (projectInviteHandler cfg)acceptInviteRoute = void $ method POST acceptInvitationHandlerinviteRoute =serveJSON (projectInviteResponseJSON) $ method POST (projectInviteHandler cfg)acceptInviteRoute =void $ method POST acceptInvitationHandler