diff --git a/src/components/crowdnode-card.js b/src/components/crowdnode-card.js
new file mode 100644
index 0000000..d0bceb2
--- /dev/null
+++ b/src/components/crowdnode-card.js
@@ -0,0 +1,221 @@
+import {
+ formDataEntries,
+ createSignal,
+} from '../helpers/utils.js'
+
+import {
+ lit as html,
+} from '../helpers/lit.js'
+
+export const CrowdNodeCard = (() => {
+ const initCfg = {
+ state: {},
+ slugs: {},
+ events: {},
+ elements: {},
+ markup: {},
+ }
+ const initialState = {
+ id: 'Card',
+ name: 'Card',
+ withdrawTxt: 'Withdraw',
+ withdrawAlt: 'Withdraw from Crowdnode',
+ depositTxt: 'Deposit',
+ depositAlt: `Deposit to Crowdnode`,
+ signupTxt: 'Signup',
+ signupAlt: `Signup for Crowdnode`,
+ placement: 'center',
+ rendered: null,
+ responsive: true,
+ }
+
+ const cn = function Crowdnode(
+ config = {}
+ ) {
+ config = {
+ ...initCfg,
+ ...config,
+ }
+
+ this.appElement = document.body
+
+ this.api = createSignal({})
+
+ this.state = {
+ ...initialState,
+ ...config.state,
+ }
+
+ this.slugs = {
+ form: this.state.name?.toLowerCase().replaceAll(' ', '_'),
+ ...config.slugs,
+ }
+
+ this.elements = {
+ ...config.elements,
+ }
+
+ this.markup = {}
+ this.markup.content = () => html`
+
+
+
+ ${!this.api.value?.acceptedToS && html`
+ Start earning interest by staking your Dash at CrowdNode
+ `}
+ ${this.api.value?.acceptedToS && html`
+
+ Balance: Ð ${this.api.value?.balance}
+
+ `}
+ ${this.api.value?.acceptedToS && html`
+
+ Earned: Ð ${this.api.value?.earned}
+
+ `}
+
+
+
+ ${!this.api.value?.acceptedToS && html`
+
+ ${this.state.signupTxt}
+
+ `}
+ ${this.api.value?.acceptedToS && html`
+
+ ${this.state.depositTxt}
+
+ `}
+ ${this.api.value?.acceptedToS && this.api.value?.balance > 0 && html`
+
+ ${this.state.withdrawTxt}
+
+ `}
+
+ `
+ this.markup = {
+ ...this.markup,
+ ...config.markup,
+ }
+
+ this.events = {
+ submit: event => {
+ event.preventDefault()
+ event.stopPropagation()
+
+ this.elements.form?.removeEventListener('submit', this.events.submit)
+
+ let fde = formDataEntries(event)
+
+ console.log(
+ `${this.slugs.form} submit`,
+ {event, fde},
+ )
+ },
+ ...config.events,
+ }
+
+ const $d = document
+
+ const form = $d.createElement('form')
+
+ this.elements.form = form
+
+ form.name = `${this.slugs.form}`
+ form.classList.add('flex', 'col', 'card')
+ form.innerHTML = this.markup.content()
+
+ this.api.on((apiChange) => {
+ console.log('CN Card API Change', apiChange)
+ this.render?.({})
+ })
+
+ /**
+ * Update the config of the CN Card
+ * @function
+ */
+ this.updateConfig = (config = {}) => {
+ console.log('CN Card updateConfig TOP', config)
+
+ for (let param in config) {
+ this[param] = {
+ ...this[param],
+ ...(config[param] || {}),
+ }
+ }
+
+ console.log('CN Card updateConfig BOT', this)
+ }
+
+ /**
+ * Trigger the rendering of the CN Card
+ * @function
+ */
+ this.render = ({
+ cfg = {},
+ position = 'afterend',
+ el = this.appElement,
+ }) => {
+ console.log('crowdnode render', this)
+
+ this.elements.form?.removeEventListener?.(
+ 'submit',
+ this.events.submit,
+ )
+
+ if (el !== this.appElement) {
+ this.appElement = el
+ }
+
+ this.updateConfig(cfg)
+
+ this.elements.form.name = this.slugs.form
+ this.elements.form.innerHTML = this.markup.content()
+
+ this.elements.form.addEventListener(
+ 'submit',
+ this.events.submit,
+ )
+
+ console.log('CARD RENDER', this, cfg)
+
+ if (!this.state.rendered) {
+ el.insertAdjacentElement(position, this.elements.form)
+ this.state.rendered = this.elements.form
+ }
+
+ this.events?.render?.(this)
+ }
+
+ return this
+ }
+
+ return cn
+})();
+
+export default CrowdNodeCard
diff --git a/src/components/dialog.js b/src/components/dialog.js
index b133a09..4551c39 100644
--- a/src/components/dialog.js
+++ b/src/components/dialog.js
@@ -4,6 +4,10 @@ import {
envoy,
} from '../helpers/utils.js'
+import {
+ DIALOG_STATUS,
+} from '../helpers/constants.js'
+
let modal = envoy(
{
rendered: {},
@@ -26,7 +30,8 @@ const initialState = {
rendered: null,
responsive: true,
delay: 500,
- async render () {},
+ status: DIALOG_STATUS.NOT_LOADING,
+ async render () { return this },
addListener () {},
addListeners () {},
removeAllListeners (targets) {},
@@ -210,6 +215,9 @@ const initialState = {
// event.target === state.elements.dialog,
// state.elements.dialog.returnValue
// )
+ state.status = DIALOG_STATUS.NOT_LOADING
+ state.elements.progress?.remove()
+ state.elements.progress = null
if (state.elements.dialog.returnValue !== 'cancel') {
resolve(state.elements.dialog.returnValue)
@@ -298,30 +306,30 @@ export async function setupDialog(
.replaceAll(/[^a-zA-Z _]/g, '')
.replaceAll(' ', '_')
- const dialog = document.createElement('dialog')
- const form = document.createElement('form')
- const progress = document.createElement('progress')
+ const dialogElement = document.createElement('dialog')
+ const formElement = document.createElement('form')
+ const progressElement = document.createElement('progress')
- state.elements.dialog = dialog
- state.elements.form = form
- state.elements.progress = progress
+ state.elements.dialog = dialogElement
+ state.elements.form = formElement
+ state.elements.progress = progressElement
- progress.classList.add('pending')
+ progressElement.classList.add('pending')
- dialog.innerHTML = ``
- dialog.id = state.slugs.dialog
+ dialogElement.innerHTML = ``
+ dialogElement.id = state.slugs.dialog
if (state.responsive) {
- dialog.classList.add('responsive')
+ dialogElement.classList.add('responsive')
}
- dialog.classList.add(state.placement)
+ dialogElement.classList.add(...(state.placement.split(' ')))
- form.name = `${state.slugs.form}`
- form.method = 'dialog'
- form.innerHTML = await state.content(state)
+ formElement.name = `${state.slugs.form}`
+ formElement.method = 'dialog'
+ formElement.innerHTML = await state.content(state)
- dialog.insertAdjacentElement(
+ dialogElement.insertAdjacentElement(
'afterbegin',
- form
+ formElement
)
function addListener(
@@ -340,74 +348,74 @@ export async function setupDialog(
) {
if (resolve && reject) {
addListener(
- dialog,
+ dialogElement,
'close',
state.events.handleClose(state, resolve, reject),
)
addListener(
- dialog,
+ dialogElement,
'click',
state.events.handleClick(state),
)
}
addListener(
- form,
+ formElement,
'blur',
state.events.handleBlur(state),
)
addListener(
- form,
+ formElement,
'focusout',
state.events.handleFocusOut(state),
)
addListener(
- form,
+ formElement,
'focusin',
state.events.handleFocusIn(state),
)
addListener(
- form,
+ formElement,
'change',
state.events.handleChange(state),
)
- // let updrop = form.querySelector('.updrop')
+ // let updrop = formElement.querySelector('.updrop')
// state.elements.updrop = updrop
// if (updrop) {
addListener(
- form,
+ formElement,
'drop',
state.events.handleDrop(state),
)
addListener(
- form,
+ formElement,
'dragover',
state.events.handleDragOver(state),
)
addListener(
- form,
+ formElement,
'dragend',
state.events.handleDragEnd(state),
)
addListener(
- form,
+ formElement,
'dragleave',
state.events.handleDragLeave(state),
)
// }
addListener(
- form,
+ formElement,
'input',
state.events.handleInput(state),
)
addListener(
- form,
+ formElement,
'reset',
state.events.handleReset(state),
)
addListener(
- form,
+ formElement,
'submit',
state.events.handleSubmit(state),
)
@@ -416,7 +424,7 @@ export async function setupDialog(
state.addListeners = addListeners
function removeAllListeners(
- targets = [dialog,form],
+ targets = [dialogElement,formElement],
) {
if (state.elements.updrop) {
targets.push(state.elements.updrop)
@@ -456,29 +464,56 @@ export async function setupDialog(
}
}
- dialog.id = state.slugs.dialog
- form.name = `${state.slugs.form}`
- form.innerHTML = await state.content(state)
+ dialogElement.id = state.slugs.dialog
+ formElement.name = `${state.slugs.form}`
+ formElement.innerHTML = await state.content(state)
// console.log('DIALOG RENDER', state, position, state.slugs.dialog, modal.rendered)
+ if (
+ state.status === DIALOG_STATUS.LOADING &&
+ state.elements.progress
+ ) {
+ state.elements.form.insertAdjacentElement(
+ 'beforebegin',
+ state.elements.progress,
+ )
+
+ // document.body.insertAdjacentHTML(
+ // 'afterbegin',
+ // ` `,
+ // )
+ }
+
+ if (
+ state.status === DIALOG_STATUS.SUCCESS ||
+ state.status === DIALOG_STATUS.ERROR
+ ) {
+ // document.getElementById('pageLoader')?.remove()
+ state.elements.progress?.remove()
+ }
+
if (!modal.rendered[state.slugs.dialog]) {
- el.insertAdjacentElement(position, dialog)
- modal.rendered[state.slugs.dialog] = dialog
+ el.insertAdjacentElement(position, dialogElement)
+ modal.rendered[state.slugs.dialog] = dialogElement
}
state.events.handleRender(state)
+
+ return state
}
state.render = render
return {
- element: dialog,
+ element: dialogElement,
+ state,
+ elements: state.elements,
show: (callback) => new Promise((resolve, reject) => {
removeAllListeners()
addListeners(resolve, reject)
- // console.log('dialog show', dialog)
- dialog.show()
+ // console.log('dialog show', dialogElement)
+ dialogElement.show()
state.events.handleShow?.(state)
callback?.()
}),
@@ -486,11 +521,11 @@ export async function setupDialog(
removeAllListeners()
addListeners(resolve, reject)
// console.log('dialog showModal', dialog)
- dialog.showModal()
+ dialogElement.showModal()
state.events.handleShow?.(state)
callback?.()
}),
- close: returnVal => dialog.close(returnVal),
+ close: returnVal => dialogElement.close(returnVal),
render,
}
}
diff --git a/src/components/modal.js b/src/components/modal.js
new file mode 100644
index 0000000..781e7a5
--- /dev/null
+++ b/src/components/modal.js
@@ -0,0 +1,543 @@
+import { lit as html } from '../helpers/lit.js'
+import {
+ formDataEntries,
+ createSignal,
+ effect,
+ toSlug,
+ addListener,
+ addListeners,
+ removeAllListeners,
+} from '../helpers/utils.js'
+
+import {
+ DIALOG_STATUS,
+} from '../helpers/constants.js'
+
+/**
+ * Create a new HTML Dialog
+ */
+export const DialogContructor = (() => {
+ const initCfg = {
+ state: {},
+ slugs: {},
+ events: {},
+ elements: {},
+ templates: {},
+ }
+ const initialState = {
+ id: 'Dialog',
+ name: 'Dialog',
+ submitTxt: 'Submit',
+ submitAlt: 'Submit Form',
+ cancelTxt: 'Cancel',
+ cancelAlt: `Cancel Form`,
+ closeTxt: 'X',
+ closeAlt: `Close`,
+ placement: 'center',
+ rendered: null,
+ responsive: true,
+ delay: 500,
+ status: DIALOG_STATUS.NOT_LOADING,
+ }
+
+ /**
+ * Create a new HTML Dialog
+ *
+ * @param {Object} config
+ */
+ const dl = function DialogModal(
+ config = {}
+ ) {
+ config = {
+ ...initCfg,
+ ...config,
+ }
+
+ this.eventHandlers = []
+
+ this.state = createSignal({
+ ...initialState,
+ ...config.state,
+ })
+
+ this.slugs = {
+ dialog: toSlug(this.state.value.name, this.state.value.id),
+ form: toSlug(this.state.value.id, this.state.value.name),
+ ...config.slugs,
+ }
+
+ this.appElement = document.body
+
+ this.elements = {
+ ...config.elements,
+ }
+
+ this.markup = {}
+
+ effect(() => {
+ this.markup.header = html`
+
+ ${this.state.value.name}
+ ${
+ this.state.value.closeTxt && html`${this.state.value.closeTxt} `
+ }
+
+ `
+ this.markup.footer = html`
+
+
+ ${this.state.value.cancelTxt}
+
+
+ ${this.state.value.submitTxt}
+
+
+ `
+ })
+
+ this.markup.fields = html`
+
+ Thing
+
+
+
+
Some instructions
+ `
+
+ this.markup.content = () => html`
+ ${this.markup.header}
+
+
+ ${this.markup.fields}
+
+
+
+
+ ${this.markup.footer}
+ `
+
+ this.markup = {
+ ...this.markup,
+ ...config.markup,
+ }
+
+ this.events = {
+ input: event => {
+ // let {
+ // // @ts-ignore
+ // name: fieldName, form,
+ // } = event?.target
+
+ // console.log('handle input', {
+ // fieldName,
+ // form,
+ // })
+
+ if (
+ event?.target?.validity?.patternMismatch &&
+ event?.target?.type !== 'checkbox'
+ ) {
+ event.preventDefault()
+ let label = event.target?.previousElementSibling?.textContent?.trim()
+ if (label) {
+ event.target.setCustomValidity(`Invalid ${label}`)
+ }
+ } else {
+ event.target.setCustomValidity('')
+ }
+ event.target.reportValidity()
+ },
+ change: event => {
+ // let {
+ // // @ts-ignore
+ // name: fieldName, form,
+ // } = event?.target
+
+ // console.log('handle change', {
+ // fieldName,
+ // form,
+ // })
+
+ if (
+ event?.target?.validity?.patternMismatch &&
+ event?.target?.type !== 'checkbox'
+ ) {
+ event.preventDefault()
+ let label = event.target?.previousElementSibling?.textContent?.trim()
+ if (label) {
+ event.target.setCustomValidity(`Invalid ${label}`)
+ }
+ } else {
+ event.target.setCustomValidity('')
+ }
+ event.target.reportValidity()
+ },
+ blur: event => {
+ // event.preventDefault()
+ // console.log(
+ // 'handle blur',
+ // event,
+ // )
+ },
+ focusout: event => {
+ // event.preventDefault()
+ // console.log(
+ // 'handle focus out',
+ // event,
+ // )
+ },
+ focusin: event => {
+ // event.preventDefault()
+ // console.log(
+ // 'handle focus in',
+ // event,
+ // )
+ },
+ drop: event => {
+ event.preventDefault()
+ },
+ dragover: event => {
+ event.preventDefault()
+ },
+ dragend: event => {
+ event.preventDefault()
+ },
+ dragleave: event => {
+ event.preventDefault()
+ },
+ render: (
+ state,
+ ) => {
+ // console.log(
+ // 'handle dialog render',
+ // state,
+ // )
+ },
+ show: (
+ state,
+ ) => {
+ // console.log(
+ // 'handle dialog show',
+ // state,
+ // )
+
+ // focus first input
+ this.elements.form.querySelector(
+ 'input'
+ )?.focus()
+ },
+ close: (
+ resolve = res=>{},
+ reject = res=>{},
+ ) => async event => {
+ event.preventDefault()
+ removeAllListeners.bind(this)
+
+ // console.log(
+ // 'handle dialog close',
+ // event,
+ // event.target === this.elements.dialog,
+ // this.elements.dialog.returnValue
+ // )
+ this.state.value.status = DIALOG_STATUS.NOT_LOADING
+ this.elements.dialog?.querySelector('progress')?.remove()
+
+ if (this.elements.dialog.returnValue !== 'cancel') {
+ resolve(this.elements.dialog.returnValue)
+ } else {
+ resolve('cancel')
+ }
+ // console.log(
+ // 'DIALOG handleClose',
+ // modal.rendered[this.slugs.dialog],
+ // )
+
+ setTimeout(t => {
+ // modal.rendered[this.slugs.dialog] = null
+ this.state.value.rendered = null
+ event?.target?.remove()
+ // console.log(
+ // 'DIALOG handleClose setTimeout',
+ // this.state.value.delay,
+ // // modal.rendered[this.slugs.dialog],
+ // modal.rendered,
+ // )
+ }, this.state.value.delay)
+ },
+ submit: event => {
+ event.preventDefault()
+
+ let fde = formDataEntries(event)
+
+ this.elements.dialog.returnValue = String(fde.intent)
+
+ // console.log(
+ // 'handleSubmit',
+ // [event],
+ // )
+
+ this.elements.dialog.close(String(fde.intent))
+ },
+ reset: event => {
+ event.preventDefault()
+ this.elements.form?.removeEventListener(
+ 'close',
+ this.events.reset
+ )
+ // console.log(
+ // 'handleReset',
+ // [event.target],
+ // )
+ this.elements.dialog.close('cancel')
+ },
+ click: event => {
+ if (event.target === this.elements.dialog) {
+ // console.log(
+ // 'handle dialog backdrop click',
+ // event,
+ // event.target === this.elements.dialog
+ // )
+ this.elements.dialog.close('cancel')
+ }
+ },
+ }
+
+ const dialogElement = document.createElement('dialog')
+ const formElement = document.createElement('form')
+ const progressElement = document.createElement('progress')
+
+ this.elements.dialog = dialogElement
+ this.elements.form = formElement
+ this.elements.progress = progressElement
+
+
+ this.element = this.elements.dialog
+
+ progressElement.classList.add('pending')
+
+ dialogElement.innerHTML = ``
+ dialogElement.id = this.slugs.dialog
+ if (this.state.value.responsive) {
+ dialogElement.classList.add('responsive')
+ }
+ dialogElement.classList.add(...(this.state.value.placement.split(' ')))
+
+ formElement.name = `${this.slugs.form}`
+ formElement.method = 'dialog'
+ // formElement.innerHTML = this.markup.content
+ formElement.innerHTML = this.markup.content()
+
+ dialogElement.insertAdjacentElement(
+ 'afterbegin',
+ formElement
+ )
+
+ /**
+ * Show the dialog
+ * @function
+ */
+ this.show = (callback, el = this.appElement) => new Promise((resolve, reject) => {
+ removeAllListeners.call(this)
+ addListeners.call(this, resolve, reject)
+ console.log('modal.js dialog show', this.elements.dialog)
+ // if (
+ // !this.state.value.rendered &&
+ // !el.contains(this.elements.dialog)
+ // ) {
+ this.render({
+ el,
+ // cfg: config,
+ position: 'afterend'
+ })
+ // }
+ this.elements.dialog.show()
+ this.events.show?.(this)
+ callback?.()
+ })
+
+ /**
+ * Show the Modal form of the dialog
+ * @function
+ */
+ this.showModal = (callback, el = this.appElement) => new Promise((resolve, reject) => {
+ removeAllListeners.call(this)
+ addListeners.call(this, resolve, reject)
+ console.log('modal.js dialog showModal', this, this.elements.dialog)
+ // if (
+ // !this.state.value.rendered &&
+ // !el.contains(this.elements.dialog)
+ // ) {
+ this.render({
+ el,
+ // cfg: config,
+ position: 'afterend'
+ })
+ // }
+ this.elements.dialog.showModal()
+ this.events.show?.(this)
+ callback?.()
+ })
+
+ /**
+ * Close the dialog
+ * @function
+ */
+ this.close = returnVal => this.elements.dialog.close(returnVal)
+
+ /**
+ * Update the config of the dialog
+ * @function
+ */
+ this.updateConfig = (config = {}) => {
+ console.log('Dialog updateConfig TOP', config)
+
+ for (let param in config) {
+ // if (param === 'markup') {
+ // this[param].value = {
+ // ...this[param].value,
+ // ...(config[param] || {}),
+ // }
+ // } else {
+ if ('value' in this[param]) {
+ this[param].value = {
+ ...this[param].value,
+ ...(config[param] || {}),
+ }
+ } else {
+ this[param] = {
+ ...this[param],
+ ...(config[param] || {}),
+ }
+ }
+ // }
+ }
+
+ console.log('Dialog updateConfig BOT', this)
+ }
+
+ /**
+ * Trigger the rendering of the dialog
+ * @function
+ */
+ this.render = ({
+ cfg = {},
+ position = 'afterend',
+ el = this.appElement,
+ }) => {
+ console.log('dialog render', this, dl)
+
+ // this.elements.form?.removeEventListener?.(
+ // 'submit',
+ // this.events.submit,
+ // )
+
+ if (el !== this.appElement) {
+ this.appElement = el
+ }
+
+ this.updateConfig(cfg)
+
+ console.log('DIALOG elements', this, this.elements)
+
+ this.elements.dialog.id = this.slugs.dialog
+ this.elements.form.name = this.slugs.form
+ // this.elements.form.innerHTML = this.markup.content
+ this.elements.form.innerHTML = this.markup.content()
+
+ // this.elements.form.addEventListener(
+ // 'submit',
+ // this.events.submit,
+ // )
+
+ console.log('DIALOG RENDER STATE', this.state.value, cfg)
+
+ console.log('DIALOG RENDER', position, this.slugs.dialog)
+
+ if (
+ this.state.value.status === DIALOG_STATUS.LOADING
+ ) {
+ this.elements.form.insertAdjacentElement(
+ 'beforebegin',
+ this.elements.progress,
+ )
+
+ // document.body.insertAdjacentHTML(
+ // 'afterbegin',
+ // ` `,
+ // )
+ }
+
+ if (
+ this.state.value.status === DIALOG_STATUS.SUCCESS ||
+ this.state.value.status === DIALOG_STATUS.ERROR
+ ) {
+ // document.getElementById('pageLoader')?.remove()
+ this.elements.dialog.querySelector('progress')?.remove()
+ }
+
+ if (!this.state.value.rendered) {
+ console.log('!this.state.rendered el', el, this.appElement, this.elements.dialog)
+ // @ts-ignore
+ el?.insertAdjacentElement(position, this.elements.dialog)
+ this.state.value.rendered = this.elements.dialog
+
+ this.elements.dialog.addEventListener(
+ 'close',
+ this.events.close
+ )
+ }
+
+ // console.log('DIALOG RENDER', state, position, state.slugs.dialog, modal.rendered)
+
+ this.events.render(this.state)
+
+ return this
+ }
+
+ // const updateSelf = (cfg, methods = []) => {
+ // for (let mthd of methods) {
+ // this[mthd] = {
+ // ...this[mthd],
+ // ...(cfg[mthd] || {}),
+ // }
+ // }
+ // }
+ // updateSelf(cfg, [
+ // 'state', 'slugs', 'events', 'elements', 'templates'
+ // ])
+
+ // state.render = render
+ }
+
+ return dl
+})();
+
+// export function Modal(config) {
+// let dialog = new DialogContructor(config)
+
+// console.log('Modal.js Dialog', dialog)
+
+// return dialog
+// }
+
+export default DialogContructor
diff --git a/src/components/svg-sprite.js b/src/components/svg-sprite.js
index 3fa2055..0c7184a 100644
--- a/src/components/svg-sprite.js
+++ b/src/components/svg-sprite.js
@@ -7,6 +7,10 @@ const initialState = {
+
+
+
+
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index 629a956..e85f271 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -142,3 +142,15 @@ export const USAGE = {
RECEIVE,
CHANGE,
}
+
+const NOT_LOADING = 0
+const LOADING = 1
+const SUCCESS = 2
+const ERROR = 3
+
+export const DIALOG_STATUS = {
+ NOT_LOADING,
+ LOADING,
+ SUCCESS,
+ ERROR
+}
\ No newline at end of file
diff --git a/src/helpers/lit.js b/src/helpers/lit.js
index f976e5a..fe7792f 100644
--- a/src/helpers/lit.js
+++ b/src/helpers/lit.js
@@ -1,8 +1,24 @@
/**
+ * Code Highlighting for String Literals.
+ *
+ * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#raw_strings MDN Reference}
+ *
+ * @example
+ * import { lit as html, lit as css } from './utils.js'
+ * let h = html`${example}
`
+ * let c = css`div > span { color: #bad; }`
+ *
+ * // falsey values now default to empty string
+ * let falsey = html`${doesNotExist && html`
`}
`
+
+ * // falsey === '
'
+ * // instead of
+ * // falsey === 'undefined
'
*
* @param {TemplateStringsArray} s
* @param {...any} v
*
* @returns {string}
*/
-export const lit = (s, ...v) => String.raw({ raw: s }, ...v)
+export const lit = (s, ...v) => String.raw({ raw: s }, ...(v.map(x => x || '')))
+// export const lit = (s, ...v) => String.raw({ raw: s }, ...v)
diff --git a/src/helpers/utils.js b/src/helpers/utils.js
index 493808f..f613de1 100644
--- a/src/helpers/utils.js
+++ b/src/helpers/utils.js
@@ -13,6 +13,10 @@ import {
SECONDS, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR,
} from './constants.js'
+let currentSignal;
+let globalDerivedValue;
+let eventHandlers = []
+
/**
*
* @param {String} [phraseOrXkey]
@@ -446,14 +450,14 @@ export function envoy(obj, ...initListeners) {
* document.querySelector("body").innerHTML = value;
* });
*
- * off(); // unsubscribe
+ * off();
*
* @param {Object} initialValue inital value
*/
export function createSignal(initialValue) {
let _value = initialValue;
let _last = _value;
- const subs = [];
+ let subs = [];
function pub() {
for (let s of subs) {
@@ -461,18 +465,186 @@ export function createSignal(initialValue) {
}
}
+ function unsub(fn) {
+ for (let i in subs) {
+ if (subs[i] === fn) {
+ subs[i] = 0;
+ // break;
+ }
+ }
+ }
+
+ function on(s) {
+ const i = subs.push(s)-1;
+ return () => { subs[i] = 0; };
+ }
+
+ function once(s) {
+ const i = subs.length
+
+ subs.push((_value, _last) => {
+ s && s(_value, _last);
+ subs[i] = 0;
+ });
+ }
+
return {
- get value() { return _value; },
+ get value() {
+ if (
+ currentSignal //&&
+ // currentSignal !== globalDerivedValue
+ ) {
+ console.log(
+ 'currentSignal === derived',
+ {currentSignal, derived, globalDerivedValue},
+ currentSignal === globalDerivedValue,
+ )
+ on(currentSignal)
+ }
+ return _value;
+ },
set value(v) {
_last = _value
_value = v;
pub();
},
- on: s => {
- const i = subs.push(s)-1;
- return () => { subs[i] = 0; };
+ on,
+ once,
+ unsub,
+ }
+}
+
+/**
+ * Use a reactive signal in hook fashion
+ *
+ * @example
+ * let [count, setCount, on] = useSignal(0)
+ * console.log(count()) // 0
+ * setCount(2)
+ * console.log(count()) // 2
+ *
+ * let off = on(value => {
+ * document.querySelector("body").innerHTML = value;
+ * });
+ *
+ * off()
+ *
+ * @param {Object} initialValue inital value
+*/
+export function useSignal(initialValue) {
+ let _value = initialValue;
+ let _last = _value;
+ let subs = [];
+
+ function pub() {
+ for (let s of subs) {
+ s && s(_value, _last);
+ }
+ }
+
+ function unsub(fn) {
+ for (let i in subs) {
+ if (subs[i] === fn) {
+ subs[i] = 0;
+ // break;
+ }
}
}
+
+ function getValue(v) {
+ if (
+ currentSignal //&&
+ // currentSignal !== globalDerivedValue
+ ) {
+ on(currentSignal)
+ }
+ return _value;
+ }
+
+ function setValue(v) {
+ _last = _value
+ _value = v;
+ pub();
+ }
+
+ function on(s) {
+ const i = subs.push(s)-1;
+ return () => { subs[i] = 0; };
+ }
+
+ function once(s) {
+ const i = subs.length
+
+ subs.push((_value, _last) => {
+ s && s(_value, _last);
+ subs[i] = 0;
+ });
+ }
+
+ return [
+ // _value,
+ getValue,
+ setValue,
+ on,
+ once,
+ unsub,
+ ]
+}
+
+/**
+ * {@link https://youtu.be/t18Kzj9S8-M?t=351 Understanding Signals}
+ *
+ * {@link https://youtu.be/1TSLEzNzGQM Learn Why JavaScript Frameworks Love Signals By Implementing Them}
+ *
+ * @example
+ * const [count, setCount] = useSignal(10)
+ * effect(() => console.log(count()))
+ * setCount(25)
+ *
+ * let letter = createSignal('a')
+ * effect(() => console.log(letter.value))
+ * letter.value = 'b'
+ *
+ * @param {Function} fn
+ */
+export function effect(fn) {
+ currentSignal = fn;
+
+ fn();
+
+ currentSignal = null;
+
+ return fn
+}
+
+/**
+ * {@link https://youtu.be/1TSLEzNzGQM Learn Why JavaScript Frameworks Love Signals By Implementing Them}
+ *
+ * @example
+ * let count = createSignal(10)
+ * let double = derived(() => count.value * 2)
+ *
+ * effect(
+ * () => console.log(
+ * count.value,
+ * double.value,
+ * )
+ * )
+ *
+ * count.value = 25
+ *
+ * @param {Function} fn
+ */
+export function derived(fn) {
+ const derived = createSignal()
+
+ globalDerivedValue = function derivedValue() {
+ derived.value = fn()
+ }
+
+ effect(globalDerivedValue)
+
+ return derived
}
export async function restate(
@@ -934,6 +1106,20 @@ export function nobounce(callback, delay = 300) {
}
}
+
+/**
+ * @example
+ * await forIt(500);
+ * nowDoThis()
+ *
+ * @param {number} [delay]
+*
+* @returns {Promise}
+*/
+export function forIt(delay) {
+ return new Promise(resolve => setTimeout(resolve, delay));
+}
+
export function timeago(ms, locale = TIMEAGO_LOCALE_EN) {
var ago = Math.floor(ms / 1000);
var part = 0;
@@ -1213,3 +1399,118 @@ export function getAddressIndexFromUsage(wallet, account, usageIdx) {
addressIndex,
}
}
+
+export function toSlug(...slugs) {
+ return slugs.join('_').toLowerCase()
+ .replaceAll(/[^a-zA-Z _]/g, '')
+ .replaceAll(' ', '_')
+}
+
+export function addListener(
+ node,
+ event,
+ handler,
+ capture = false,
+ handlers = this?.eventHandlers || eventHandlers,
+) {
+ console.log('addListener', this, { node, event, handler, capture })
+ handlers.push({ node, event, handler, capture })
+ node.addEventListener(event, handler, capture)
+}
+
+export function addListeners(
+ resolve,
+ reject,
+) {
+ if (resolve && reject) {
+ addListener(
+ this.elements.dialog,
+ 'close',
+ this.events.close(resolve, reject),
+ )
+
+ addListener(
+ this.elements.dialog,
+ 'click',
+ this.events.click,
+ )
+ }
+
+ addListener(
+ this.elements.form,
+ 'blur',
+ this.events.blur,
+ )
+ addListener(
+ this.elements.form,
+ 'focusout',
+ this.events.focusout,
+ )
+ addListener(
+ this.elements.form,
+ 'focusin',
+ this.events.focusin,
+ )
+ addListener(
+ this.elements.form,
+ 'change',
+ this.events.change,
+ )
+ // if (updrop) {
+ addListener(
+ this.elements.form,
+ 'drop',
+ this.events.drop,
+ )
+ addListener(
+ this.elements.form,
+ 'dragover',
+ this.events.dragover,
+ )
+ addListener(
+ this.elements.form,
+ 'dragend',
+ this.events.dragend,
+ )
+ addListener(
+ this.elements.form,
+ 'dragleave',
+ this.events.dragleave,
+ )
+ // }
+ addListener(
+ this.elements.form,
+ 'input',
+ this.events.input,
+ )
+ addListener(
+ this.elements.form,
+ 'reset',
+ this.events.reset,
+ )
+ addListener(
+ this.elements.form,
+ 'submit',
+ this.events.submit,
+ )
+}
+
+export function removeAllListeners(
+ targets = [
+ this?.elements.dialog,
+ this?.elements.form,
+ ],
+ handlers = this?.eventHandlers || eventHandlers,
+) {
+ if (this.elements.updrop) {
+ targets.push(this.elements.updrop)
+ }
+ handlers = handlers
+ .filter(({ node, event, handler, capture }) => {
+ if (targets.includes(node)) {
+ node.removeEventListener(event, handler, capture)
+ return false
+ }
+ return true
+ })
+}
diff --git a/src/imports.js b/src/imports.js
index 9057285..81fb104 100644
--- a/src/imports.js
+++ b/src/imports.js
@@ -11,6 +11,7 @@
*
* See https://github.com/jojobyte/browser-import-rabbit-hole
*/
+import './secp.js';
import '../node_modules/dashtx/dashtx.js';
import '../node_modules/dashkeys/dashkeys.js';
@@ -20,7 +21,7 @@ import '../node_modules/dashsight/dashsight.js';
import '../node_modules/dashsight/dashsocket.js';
import '../node_modules/@dashincubator/base58check/base58check.js';
import '../node_modules/@dashincubator/ripemd160/ripemd160.js';
-import '../node_modules/@dashincubator/secp256k1/secp256k1.js';
+// import '../node_modules/@dashincubator/secp256k1/secp256k1.js';
import '../node_modules/crypticstorage/cryptic.js';
import '../node_modules/dashwallet/dashwallet.js';
import '../node_modules/localforage/dist/localforage.js';
@@ -56,7 +57,10 @@ export let Base58Check = window?.Base58Check || globalThis?.Base58Check;
/** @type {RIPEMD160Types} */
export let RIPEMD160 = window?.RIPEMD160 || globalThis?.RIPEMD160;
/** @type {Secp256k1Types} */
-export let Secp256k1 = window?.nobleSecp256k1 || globalThis?.nobleSecp256k1;
+export let Secp256k1 = (
+ window?.nobleSecp256k1 || globalThis?.nobleSecp256k1 ||
+ window?.Secp256k1 || globalThis?.Secp256k1
+);
/** @type {CrypticTypes} */
export let Cryptic =
window?.Cryptic || globalThis?.Cryptic;
diff --git a/src/index.css b/src/index.css
index afa2fa0..11382a1 100644
--- a/src/index.css
+++ b/src/index.css
@@ -96,16 +96,35 @@ figure h4 {
font-size: 2rem;
}
-.mh-25 {
+.min-h-auto {
+ min-height: auto;
+}
+.min-h-0 {
+ min-height: 0;
+}
+.min-h-25 {
+ min-height: 25%;
+}
+.min-h-50 {
+ min-height: 50%;
+}
+.min-h-75 {
+ min-height: 75%;
+}
+.min-h-100 {
+ min-height: 100%;
+}
+
+.max-h-25 {
max-height: 25%;
}
-.mh-50 {
+.max-h-50 {
max-height: 50%;
}
-.mh-75 {
+.max-h-75 {
max-height: 75%;
}
-.mh-100 {
+.max-h-100 {
max-height: 100%;
}
@@ -167,9 +186,24 @@ figure h4 {
.jc-start {
justify-content: start;
}
+.jc-center {
+ justify-content: center;
+}
+.jc-right {
+ justify-content: right;
+}
.jc-end {
justify-content: end;
}
+.jc-around {
+ justify-content: space-around;
+}
+.jc-between {
+ justify-content: space-between;
+}
+.jc-evenly {
+ justify-content: space-evenly;
+}
.as-start {
align-self: start;
}
@@ -320,6 +354,28 @@ figure h4 {
font-size: 1.5rem;
}
+.p-0 {
+ padding: 0;
+}
+.p-1 {
+ padding: .25rem;
+}
+.p-2 {
+ padding: .75rem;
+}
+.p-3 {
+ padding: 1rem;
+}
+.p-4 {
+ padding: 1.5rem;
+}
+.p-5 {
+ padding: 2rem;
+}
+.p-6 {
+ padding: 2.5rem;
+}
+
.px-0 {
padding-left: 0;
padding-right: 0;
diff --git a/src/main.js b/src/main.js
index 3f06a08..f23ab8e 100644
--- a/src/main.js
+++ b/src/main.js
@@ -6,10 +6,16 @@ import {
getStoreData,
loadStoreObject,
formDataEntries,
+ // forIt,
+ // useSignal,
+ // createSignal,
+ // effect,
+ // derived,
} from './helpers/utils.js'
import {
DUFFS,
+ DIALOG_STATUS,
} from './helpers/constants.js'
import {
@@ -79,6 +85,8 @@ import pairQrRig from './rigs/pair-qr.js'
import txInfoRig from './rigs/tx-info.js'
import showErrorDialog from './rigs/show-error.js'
+import crowdnodeTransactionRig from './rigs/crowdnode-tx.js'
+
// app/data state
let accounts
let wallets
@@ -431,6 +439,40 @@ async function showNotification({
console.log('notification', {type, title, msg, sticky})
}
+async function showQrCode(state = {}) {
+ let initState = {
+ name: 'Share to receive funds',
+ submitTxt: `Edit Amount or Contact`,
+ submitAlt: `Change the currently selected contact`,
+ footer: state => html`
+
+
+ ${state.submitTxt}
+
+
+ `,
+ amount: 0,
+ wallet,
+ contacts: appState.contacts,
+ ...state,
+ }
+
+ let showRequestQRRender = await appDialogs.requestQr.render(
+ initState,
+ 'afterend',
+ )
+
+ let showRequestQR = await appDialogs.requestQr.showModal()
+
+ return showRequestQRRender
+}
+
async function main() {
appState.encryptionPassword = window.atob(
sessionStorage.encryptionPassword || ''
@@ -673,32 +715,9 @@ async function main() {
// }
// )
- await appDialogs.requestQr.render(
- {
- name: 'Share to receive funds',
- submitTxt: `Edit Amount or Contact`,
- submitAlt: `Change the currently selected contact`,
- footer: state => html`
-
-
- ${state.submitTxt}
-
-
- `,
- amount: 0,
- wallet,
- contacts: appState.contacts,
- },
- 'afterend',
- )
+ showQrCode()
- let showRequestQR = await appDialogs.requestQr.showModal()
+ // let showRequestQR = await appDialogs.requestQr.showModal()
}
} else {
await appDialogs.sendOrReceive.render({
@@ -766,6 +785,7 @@ async function main() {
res.push(await appTools.storedData?.decryptData?.(v) || v)
},
)
+ console.log('appState.contacts', appState.contacts)
await contactsList.render({
userInfo,
@@ -790,23 +810,301 @@ async function main() {
walletFunds,
})
})
+ import('./components/crowdnode-card.js')
+ .then(async ({ CrowdNodeCard }) => {
+ // let crowdnodeTransaction = crowdnodeTransactionRig
+ console.log(
+ 'crowdnodeTransactionRig',
+ crowdnodeTransactionRig,
+ crowdnodeTransactionRig.markup.fields,
+ )
+ // crowdnodeTransactionRig.markup.fields
+ let cfg = {
+ state: {
+ // name: '',
+ },
+ events: {
+ submit: async event => {
+ event.preventDefault()
+ event.stopPropagation()
- integrationsSection.insertAdjacentHTML('beforeend', html`
-
-
- Coming soon
- Earn interest with
-
-
-
- `)
+ // this.elements.form?.removeEventListener('submit', this.events.submit)
+
+ let fde = formDataEntries(event)
+
+ console.log(
+ `Crowdnode Card submit`,
+ {event, fde},
+ )
+
+ if (fde.intent === 'signup') {
+ let confAct = await appDialogs.confirmAction.render({
+ name: 'Signup for Crowdnode',
+ actionTxt: 'Signup',
+ actionAlt: 'Signup for Crowdnode',
+ action: '',
+ actionType: 'infoo',
+ placement: 'center auto-height',
+ // status: DIALOG_STATUS.LOADING,
+ acceptedToS: false,
+ submitIcon: () => ``,
+ alert: state => html`
+
+
+
+ I accept the CrowdNode Terms and Conditions
+
+ This process may take a while, please be patient.
+
+ `,
+ fields: () => html`
+
+
+ To stake your Dash and begin earning interest, read and accept the CrowdNode Terms and Conditions.
+ Funds are required to complete the signup process.
+
+
+ `,
+ callback: async (state, fde) => {
+ state.status = DIALOG_STATUS.LOADING
+
+ if (fde?.acceptToS === 'on') {
+ state.acceptedToS = true
+ }
+
+ let cbConfAct = await appDialogs.confirmAction.render(state)
+
+ console.log(
+ `confirm action`,
+ {state, fde, cbConfAct},
+ )
+
+ let cnFunding = await showQrCode({
+ name: 'CrowdNode Funding',
+ amount: 1.1,
+ status: DIALOG_STATUS.LOADING,
+ // wallet,
+ // contacts: appState.contacts,
+ // submitTxt: `Fund your wallet`,
+ // submitAlt: `Change the currently selected contact`,
+ generateNextAddress: state => {
+ // return html``
+ return html`
+ Send 1.1 Dash or more
+ to signup & fund your CrowdNode account.
+ `
+ },
+ footer: state => html`
+
+ `,
+ })
+
+ // await forIt(5000)
+
+ state.status = DIALOG_STATUS.SUCCESS
+
+ cbConfAct = await appDialogs.confirmAction.render(state)
+
+ console.log('CN Card Funding Callback', cnCard)
+
+ cnCard.api.value = {
+ acceptedToS: true,
+ balance: 1.234,
+ earned: 0.987,
+ }
+
+ cnCard.render({
+ cfg,
+ el: integrationsSection,
+ position: 'beforeend'
+ })
+
+ console.log(
+ `confirm action SUCCESS`,
+ {state, fde, cnFunding},
+ )
+
+ return { state, fde }
+ },
+ })
+
+ // let confAct = appDialogs.confirmAction.render({
+ // el: mainApp,
+ // cfg: {
+ // appElement: mainApp,
+ // state: {
+ // name: 'Signup for Crowdnode',
+ // actionTxt: 'Signup',
+ // actionAlt: 'Signup for Crowdnode',
+ // action: '',
+ // actionType: 'infoo',
+ // placement: 'center auto-height',
+ // // status: DIALOG_STATUS.LOADING,
+ // },
+ // markup: {
+ // submitIcon: ``,
+ // alert: html`
+ //
+ //
+ //
+ // I accept the CrowdNode Terms and Conditions
+ //
+ // This process may take a while, please be patient.
+ //
+ // `,
+ // fields: html`
+ //
+ //
+ // To stake your Dash and begin earning interest, read and accept the CrowdNode Terms and Conditions.
+ // Funds are required to complete the signup process.
+ //
+ //
+ // `,
+ // },
+ // callback: async (state, fde) => {
+ // state.status = DIALOG_STATUS.LOADING
+
+ // let cbConfAct = await appDialogs.confirmAction.render(state)
+
+ // console.log(
+ // `confirm action`,
+ // {state, fde, cbConfAct},
+ // )
+
+ // await forIt(5000)
+
+ // state.status = DIALOG_STATUS.SUCCESS
+
+ // console.log(
+ // `confirm action SUCCESS`,
+ // {state, fde},
+ // )
+
+ // return { state, fde }
+ // },
+ // },
+ // })
+
+ console.log('CN Card confAct Submit Event', cnCard)
+ console.log('confAct', confAct, appDialogs.confirmAction)
+
+ confAct?.elements?.form?.classList.add?.('min-h-auto')
+
+ appDialogs.confirmAction.showModal()
+ }
+
+ console.log(
+ `Crowdnode Card submit TX`,
+ fde.intent,
+ {event, fde},
+ )
+
+ if (fde.intent === 'deposit') {
+ appDialogs.sendOrReceive?.elements?.form?.classList.add?.('min-h-auto')
+ await appDialogs.sendOrReceive.render({
+ name: 'Deposit to CrowdNode',
+ cashSend: () => html``,
+ hideAddressee: true,
+ action: fde.intent,
+ wallet,
+ account: appState.account,
+ userInfo,
+ contacts: appState.contacts,
+ to: '@crowdnode',
+ })
+ appDialogs.sendOrReceive.showModal()
+ }
+ if (fde.intent === 'withdraw') {
+ crowdnodeTransactionRig.markup.fields = html`
+
+
+
+
+ Enter the percentage you wish to unstake.
+ `
+
+ let cnWithdraw = crowdnodeTransactionRig.render({
+ el: mainApp,
+ cfg: {
+ state: {
+ name: 'Withdraw from CrowdNode',
+ submitTxt: 'Withdraw',
+ submitAlt: 'Withdraw funds from CrowdNode',
+ cancelTxt: 'Cancel',
+ cancelAlt: `Cancel Withdraw`,
+ },
+ },
+ })
+ cnWithdraw?.elements?.form?.classList.add?.('min-h-auto')
+
+ console.log(
+ `Crowdnode Card TX`,
+ fde.intent,
+ {cnWithdraw},
+ )
+ crowdnodeTransactionRig.showModal()
+ // cnWithdraw.showModal()
+ }
+ }
+ },
+ }
+
+ let cnCard = new CrowdNodeCard(cfg)
+ console.log('CN Card Outer', cnCard)
+
+ // cfg.events.submit =
+
+ // cnCard.updateConfig(cfg)
+
+ cnCard.render({
+ cfg,
+ el: integrationsSection,
+ position: 'beforeend'
+ })
+ })
+
+
+ // integrationsSection.insertAdjacentHTML('beforeend', html`
+ //
+ //
+ // Coming soon
+ // Earn interest with
+ //
+ //
+ //
+ // `)
let txs = await getTxs(
appState,
@@ -895,7 +1193,7 @@ async function main() {
action: 'lock',
actionType: 'warn',
alert: state => html``,
- callback: () => {
+ callback: async () => {
sessionStorage.clear()
window.location.reload()
},
@@ -925,7 +1223,7 @@ async function main() {
This is an irreversable action which removes all wallet data from your browser, make sure to backup your data first. WE RETAIN NO BACKUPS OF YOUR WALLET DATA.
`,
- callback: () => {
+ callback: async () => {
localStorage.clear()
sessionStorage.clear()
// @ts-ignore
diff --git a/src/rigs/confirm-action.js b/src/rigs/confirm-action.js
index 1e43ca1..4d220a1 100644
--- a/src/rigs/confirm-action.js
+++ b/src/rigs/confirm-action.js
@@ -3,6 +3,10 @@ import {
formDataEntries,
} from '../helpers/utils.js'
+import {
+ DIALOG_STATUS,
+} from '../helpers/constants.js'
+
export let confirmActionRig = (async function (globals) {
'use strict';
@@ -26,6 +30,7 @@ export let confirmActionRig = (async function (globals) {
actionType: 'warn',
actionClasses: {
info: 'bg-info dark bg-info-hover',
+ infoo: 'outline brd-info info dark light-hover bg-info-hover',
warn: 'outline brd-warn warn dark-hover bg-warn-hover',
dang: 'outline brd-dang dang light-hover bg-dang-hover',
},
@@ -79,16 +84,7 @@ export let confirmActionRig = (async function (globals) {
`,
alert: state => html``,
- // alert: state => html`
- //
- //
- // This is an irreversable action, make sure to backup first.
- //
- //
- // `,
- content: state => html`
- ${state.header(state)}
-
+ fields: state => html`
Are you sure you want to ${state.action} ${
@@ -96,6 +92,11 @@ export let confirmActionRig = (async function (globals) {
}?
+ `,
+ content: state => html`
+ ${state.header(state)}
+
+ ${state.fields(state)}
${state.footer(state)}
`,
@@ -108,8 +109,17 @@ export let confirmActionRig = (async function (globals) {
if (fde?.intent === 'act') {
// state.elements.dialog.returnValue = String(fde.intent)
- state.callback?.(state, fde)
- confirmAction.close(fde.intent)
+ let res = await state.callback?.(state, fde)
+
+ if (
+ res.state.status === DIALOG_STATUS.SUCCESS ||
+ res.state.status === DIALOG_STATUS.ERROR
+ ) {
+ // state.elements.dialog?.querySelector('progress')?.remove()
+ state.elements.progress?.remove?.()
+
+ confirmAction.close(fde.intent)
+ }
}
},
},
diff --git a/src/rigs/crowdnode-tx.js b/src/rigs/crowdnode-tx.js
new file mode 100644
index 0000000..66b86c4
--- /dev/null
+++ b/src/rigs/crowdnode-tx.js
@@ -0,0 +1,135 @@
+import { lit as html } from '../helpers/lit.js'
+import {
+ formDataEntries,
+} from '../helpers/utils.js'
+
+import {
+ DIALOG_STATUS,
+} from '../helpers/constants.js'
+
+import { DialogContructor } from '../components/modal.js'
+
+export const crowdnodeTransactionRig = (() => {
+ 'use strict';
+
+ // let {
+ // mainApp, setupDialog,
+ // } = globals
+
+ let dialogConfig = {
+ state: {
+ name: 'CrowdNode Deposit / Withdraw',
+ actionTxt: 'Do It!',
+ actionAlt: 'Yeah, really do this!',
+ cancelTxt: 'Cancel',
+ cancelAlt: `Cancel`,
+ closeTxt: html` `,
+ closeAlt: `Cancel & Close`,
+ action: '',
+ target: '',
+ targetFallback: 'this wallet',
+ actionType: 'warn',
+ actionClasses: {
+ info: 'bg-info dark bg-info-hover',
+ infoo: 'outline brd-info info dark light-hover bg-info-hover',
+ warn: 'outline brd-warn warn dark-hover bg-warn-hover',
+ dang: 'outline brd-dang dang light-hover bg-dang-hover',
+ },
+ showCancelBtn: true,
+ showActBtn: true,
+ },
+ markup: {},
+ events: {},
+ // appElement: mainApp,
+ appElement: document.body,
+ }
+
+ let crowdnodeTransaction = new DialogContructor(dialogConfig)
+
+ console.log('Modal.js Dialog', crowdnodeTransaction)
+
+ dialogConfig.events.submit = async function (event) {
+ event.preventDefault()
+ event.stopPropagation()
+
+ let fde = formDataEntries(event)
+
+ if (fde?.intent === 'act') {
+ // state.elements.dialog.returnValue = String(fde.intent)
+ let res = await this.state.value.callback?.(this.state.value, fde)
+
+ if (
+ res.state.value.status === DIALOG_STATUS.SUCCESS ||
+ res.state.value.status === DIALOG_STATUS.ERROR
+ ) {
+ // this.elements.dialog?.querySelector('progress')?.remove()
+ this.elements.progress?.remove?.()
+
+ this.elements.dialog?.close(fde.intent)
+ }
+ }
+ }
+
+ dialogConfig.markup.alert = html``
+ dialogConfig.markup.submitIcon = ``
+ // dialogConfig.markup.submitIcon = html`
+ //
+ //
+ //
+ // `
+ // dialogConfig.markup.cancelBtn = html`
+ //
+ // ${crowdnodeTransaction.state.value.cancelTxt}
+ //
+ // `
+ // dialogConfig.markup.actionBtn = html`
+ //
+ // ${crowdnodeTransaction.markup.submitIcon}
+ // ${crowdnodeTransaction.state.value.actionTxt}
+ //
+ // `
+ // dialogConfig.markup.footer = html`
+ //
+ // `
+ // dialogConfig.markup.fields = html`
+ //
+ //
+ // Are you sure you want to ${crowdnodeTransaction.state.value.action} ${
+ // crowdnodeTransaction.state.value.target || crowdnodeTransaction.state.value.targetFallback
+ // }?
+ //
+ //
+ // `
+ // dialogConfig.markup.content = () => html`
+ // ${crowdnodeTransaction.markup.header}
+
+ // ${crowdnodeTransaction.markup.fields}
+
+ // ${crowdnodeTransaction.markup.footer}
+ // `
+
+ crowdnodeTransaction.updateConfig(dialogConfig)
+
+ return crowdnodeTransaction
+})();
+
+export default crowdnodeTransactionRig
\ No newline at end of file
diff --git a/src/rigs/send-or-request.js b/src/rigs/send-or-request.js
index a2d94ac..ac290f2 100644
--- a/src/rigs/send-or-request.js
+++ b/src/rigs/send-or-request.js
@@ -38,6 +38,7 @@ export let sendOrReceiveRig = (async function (globals) {
closeTxt: html` `,
closeAlt: `Close`,
action: 'send',
+ hideAddressee: false,
submitIcon: state => {
const icon = {
send: html`
@@ -180,26 +181,56 @@ export let sendOrReceiveRig = (async function (globals) {
`
},
+ cashSend: state => html`
+
+
+ Use CashSend
+
+
+
+
+ `,
+ addressee: state => {
+ if (state.hideAddressee) {
+ return html`
+
+ `
+ }
+
+ return html`
+
+
+
+ ${state.qrScanBtn(state)}
+
+ `
+ },
content: state => html`
${state.header(state)}
-
-
-
- ${state.qrScanBtn(state)}
-
+ ${state.addressee(state)}
@@ -228,18 +259,7 @@ export let sendOrReceiveRig = (async function (globals) {
${state.fundAmountBtns(state)}
-
-
- Use CashSend
-
-
-
-
+ ${state.cashSend(state)}
@@ -444,6 +464,10 @@ export let sendOrReceiveRig = (async function (globals) {
inWallet = Object.values(contact?.incoming)?.[0]
}
+ console.log('send or request', {
+ to, contact, outWallet, inWallet
+ })
+
if (!inWallet) {
// state.wallet.addressIndex = (
// state.wallet?.addressIndex ?? -1
@@ -518,7 +542,7 @@ export let sendOrReceiveRig = (async function (globals) {
}
let leftoverBalance = walletFunds.balance - amount
- let fullTransfer = leftoverBalance <= 0.0010_0200
+ let fullTransfer = leftoverBalance > 0 && leftoverBalance <= 0.0010_0200
// let fullTransfer = leftoverBalance <= 0.0001_0200
if (
@@ -542,6 +566,7 @@ export let sendOrReceiveRig = (async function (globals) {
state.wallet.accountIndex,
state.wallet.addressIndex,
)
+
let amountNeeded = fixedDash(roundUsing(Math.floor, Math.abs(
walletFunds.balance - Number(fde.amount)
)))
diff --git a/src/rigs/show-error.js b/src/rigs/show-error.js
index 0e590d5..b59d84f 100644
--- a/src/rigs/show-error.js
+++ b/src/rigs/show-error.js
@@ -60,7 +60,7 @@ export async function showErrorDialog(options) {
content: state => html`
${state.header(state)}
-
+
diff --git a/src/secp.js b/src/secp.js
new file mode 100644
index 0000000..f49b0e8
--- /dev/null
+++ b/src/secp.js
@@ -0,0 +1,11 @@
+import '../node_modules/@dashincubator/secp256k1/secp256k1.js';
+
+// @ts-ignore
+const secp = window?.nobleSecp256k1 || globalThis?.nobleSecp256k1 || window?.Secp256k1 || globalThis?.Secp256k1
+
+// @ts-ignore
+window.Secp256k1 = secp
+
+export const Secp256k1 = secp
+
+export default secp
diff --git a/src/styles/components.css b/src/styles/components.css
index 43c7933..6751960 100644
--- a/src/styles/components.css
+++ b/src/styles/components.css
@@ -116,6 +116,9 @@
display: flex;
}
+.integration-sect {
+ padding: 0 1rem;
+}
.integration-sect > section > header {
flex-direction: column;
align-items: start;
@@ -140,6 +143,34 @@
text-decoration: none;
}
+.card {
+ border-radius: 1rem;
+ overflow: auto;
+ flex: 1 1 auto;
+ max-width: 49%;
+ overflow: hidden;
+}
+.card header,
+.card footer {
+ padding: 1rem 0;
+}
+
+.card header a,
+.card header a:active,
+.card header a:focus,
+.card header a:hover {
+ color: var(--fc);
+ text-decoration: none;
+}
+.card section {
+ padding: .25rem 0;
+}
+.card fieldset {
+ min-width: auto;
+}
+.card footer button {
+}
+
@@ -150,6 +181,21 @@
.cols > section {
padding: 0;
}
+
+ .card {
+ max-width: 32.5%;
+ padding: 0 1rem;
+ }
+ .card header,
+ .card footer {
+ padding: 1rem 0;
+ }
+ .card section {
+ padding: .25rem 0;
+ }
+ .integration-sect {
+ padding: 0;
+ }
}
@media (min-width: 980px) {
.cols {
diff --git a/src/styles/dialog.css b/src/styles/dialog.css
index 75c3b1b..25ecf42 100644
--- a/src/styles/dialog.css
+++ b/src/styles/dialog.css
@@ -197,7 +197,7 @@ dialog > form > fieldset input + p {
font-weight: 400;
margin: .5rem 0;
}
-dialog > form > fieldset p {
+dialog > form fieldset p {
display: flex;
padding: 0;
margin: .8rem;
@@ -217,7 +217,7 @@ dialog > form > fieldset div + p {
margin: 0 0 0 1rem;
text-align: left;
}
-dialog > form > fieldset p > span {
+dialog > form fieldset p > span {
padding: .25rem;
border: 1px solid var(--dark-500);
}
diff --git a/src/styles/form.css b/src/styles/form.css
index c1fc1b1..d1a4aca 100644
--- a/src/styles/form.css
+++ b/src/styles/form.css
@@ -494,7 +494,7 @@ label {
}
label > input[type=checkbox],
label + input[type=checkbox] {
- width: 2rem;
+ width: 1.25rem;
}
div.switch {
@@ -739,6 +739,25 @@ form[name="network"] {
cursor: default;
}
+main form header {
+ margin: 0 auto;
+ min-height: auto;
+ /* width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ flex-direction: column-reverse; */
+}
+
+.card footer {
+ gap: .25rem;
+}
+
+.card footer button {
+ padding: 0.63rem .25rem;
+ flex: 1 1 50%;
+}
+
@media (min-width: 650px) {
main form {
@@ -776,4 +795,8 @@ form[name="network"] {
form > article > figure figcaption + div.big {
font-size: 3.75rem;
} */
+
+ .card footer button {
+ padding: 0.63rem 1rem;
+ }
}
\ No newline at end of file
diff --git a/src/styles/motion.css b/src/styles/motion.css
index 26aa428..85cff62 100644
--- a/src/styles/motion.css
+++ b/src/styles/motion.css
@@ -1,16 +1,16 @@
@-webkit-keyframes await-progress {
- 0% {
- background-position: -322px;
+ 0% {
+ background-position: -150%;
}
100% {
- background-position: 322px;
+ background-position: 200%;
}
}
@keyframes await-progress {
- 0% {
- background-position: -322px;
+ 0% {
+ background-position: -150%;
}
100% {
- background-position: 322px;
+ background-position: 200%;
}
}
diff --git a/src/styles/progress.css b/src/styles/progress.css
index 1ad2c4f..6d30564 100644
--- a/src/styles/progress.css
+++ b/src/styles/progress.css
@@ -1,19 +1,3 @@
-progress:indeterminate {
- background: linear-gradient(
- 90deg,
- #0000 0%,
- #0000 50%,
- var(--info) 100%
- );
-}
-progress.recording:indeterminate {
- background: linear-gradient(
- 90deg,
- var(--livea) 0%,
- var(--live) 50%,
- var(--livea) 100%
- );
-}
progress.pending,
progress.pending[role] {
position: absolute;
@@ -26,14 +10,15 @@ progress.pending[role] {
padding: 0;
border: none;
background-color: transparent;
- background-size: auto;
+ /* background-size: auto; */
+ background-size: 50% 100%;
background-repeat: no-repeat;
background-position: 0 0;
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
border: 0 solid transparent;
- visibility: hidden;
+ /* visibility: hidden; */
}
progress.recording,
progress.recording[role] {
@@ -52,11 +37,3 @@ progress.pending:indeterminate {
-webkit-animation: await-progress 2.5s ease-in-out infinite;
animation: await-progress 2.5s ease-in-out infinite;
}
-
-
-/* form[name=toggle_relay]:has(input[type=checkbox]:checked) {
- border-bottom: .25rem solid var(--dang);
-} */
-form[name=toggle_relay]:has(input[type=checkbox]:checked) progress {
- visibility: visible;
-}
diff --git a/src/styles/theme.css b/src/styles/theme.css
index c8e5830..55d1c2e 100644
--- a/src/styles/theme.css
+++ b/src/styles/theme.css
@@ -19,6 +19,10 @@
--light-500: #004470;
--light-600: #003e66;
+ --card-100: hsl(200deg 80% 60% / 15%);
+ --card-100: #00447066;
+
+
--grayscale-gray-400: #9DA6A6;
--c: var(--dark-900);
@@ -140,11 +144,11 @@ a:hover {
}
svg {
- fill: currentColor;
vertical-align: bottom;
}
-svg path {
+svg g:not(.lock),
+svg path:not(.lock) {
fill: currentColor;
}
@@ -316,6 +320,11 @@ th {
border-radius: 6.25rem;
}
+.card {
+ background-color: var(--card-100);
+ /* background-color: var(--dark-600); */
+}
+
.cols > section > div {
background-color: var(--nav-bg);
}