parent
47e7b69af7
commit
ccd8977d40
@ -0,0 +1,13 @@ |
|||||||
|
package features_fakepay |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/gofiber/fiber/v2" |
||||||
|
) |
||||||
|
|
||||||
|
func PostApiPay(c *fiber.Ctx) error { |
||||||
|
return c.Redirect("/fakepay/complete") |
||||||
|
} |
||||||
|
|
||||||
|
func SetupApi(app *fiber.App) { |
||||||
|
app.Post("/api/fakepay/pay", PostApiPay) |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
package features_fakepay |
||||||
|
|
||||||
|
import ( |
||||||
|
// "MY/webapp/data"
|
||||||
|
// _ "github.com/mattn/go-sqlite3"
|
||||||
|
// sq "github.com/Masterminds/squirrel"
|
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,10 @@ |
|||||||
|
package features_fakepay |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/gofiber/fiber/v2" |
||||||
|
) |
||||||
|
|
||||||
|
func Setup(app *fiber.App) { |
||||||
|
SetupApi(app) |
||||||
|
SetupViews(app) |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
package features_fakepay |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/gofiber/fiber/v2" |
||||||
|
. "MY/webapp/common" |
||||||
|
) |
||||||
|
|
||||||
|
func SetupViews(app *fiber.App) { |
||||||
|
err := ConfigViews(app, "views/fakepay") |
||||||
|
if err != nil { panic(err) } |
||||||
|
} |
||||||
@ -0,0 +1,233 @@ |
|||||||
|
const $boot = (cb) => { |
||||||
|
document.addEventListener("DOMContentLoaded", cb); |
||||||
|
} |
||||||
|
|
||||||
|
const $sanitize = (badHTML) => { |
||||||
|
// this Option hack courtesy of stackoverflow
|
||||||
|
return new Option(badHTML).innerHTML; |
||||||
|
} |
||||||
|
|
||||||
|
const validate_sanitized = (sanitized) => { |
||||||
|
const check = JSON.stringify(sanitized); |
||||||
|
console.assert(!check.includes("<"), |
||||||
|
"YOU BEEN HACKED! Send Zed this:", sanitized); |
||||||
|
} |
||||||
|
|
||||||
|
const $sanitize_data = (data) => { |
||||||
|
const sanitized = $sanitize(JSON.stringify(data)); |
||||||
|
validate_sanitized(sanitized); |
||||||
|
return JSON.parse(sanitized); |
||||||
|
} |
||||||
|
|
||||||
|
const $render = (template, data) => { |
||||||
|
const sanitized = $sanitize_data(data); |
||||||
|
|
||||||
|
const args = Object.keys(sanitized); |
||||||
|
const values = Object.values(sanitized) |
||||||
|
|
||||||
|
const render_func = new Function(...args, |
||||||
|
`return \`${template.innerHTML}\``) |
||||||
|
|
||||||
|
const text = render_func(...values); |
||||||
|
|
||||||
|
return $html(text); |
||||||
|
} |
||||||
|
|
||||||
|
const $render_data = (template_id, target_id, data) => { |
||||||
|
const target = $id(target_id); |
||||||
|
const template = $id(template_id); |
||||||
|
let new_data = []; |
||||||
|
|
||||||
|
if(Array.isArray(data)) { |
||||||
|
new_data = data.map((item, i) => { |
||||||
|
return $render(template, {i, item}); |
||||||
|
}); |
||||||
|
} else if(data instanceof Object) { |
||||||
|
console.log("object rendering", data); |
||||||
|
new_data.push($render(template, data)); |
||||||
|
} else { |
||||||
|
console.error("$render only works with {} or [] data", data); |
||||||
|
new_data.push($html("<b>ERROR LOOK IN CONSOLE</b>")); |
||||||
|
} |
||||||
|
|
||||||
|
$replace_with(target, new_data); |
||||||
|
} |
||||||
|
|
||||||
|
const $get = (name, inChildren) => { |
||||||
|
if(inChildren) { |
||||||
|
return inChildren.querySelector(name); |
||||||
|
} else { |
||||||
|
return document.querySelector(name); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const $all = (name, inChildren) => { |
||||||
|
if(inChildren) { |
||||||
|
return inChildren.querySelectorAll(name); |
||||||
|
} else { |
||||||
|
return document.querySelectorAll(name); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const $id = (name, inChildren) => { |
||||||
|
if(inChildren) { |
||||||
|
return inChildren.children.namedItem(name); |
||||||
|
} else { |
||||||
|
return document.getElementById(name); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const $class = (name) => { |
||||||
|
return document.getElementsByClassName(name); |
||||||
|
} |
||||||
|
|
||||||
|
const $name = (name) => { |
||||||
|
return document.getElementsByTagName(name); |
||||||
|
} |
||||||
|
|
||||||
|
const $filter = (nodes, fn) => { |
||||||
|
return Array.prototype.filter.call(nodes, fn); |
||||||
|
} |
||||||
|
|
||||||
|
const $next = (node) => { |
||||||
|
return node.nextElementSibling; |
||||||
|
} |
||||||
|
|
||||||
|
const $previous = (node) => { |
||||||
|
return node.previousElementSibling; |
||||||
|
} |
||||||
|
|
||||||
|
const $siblings = (node) => { |
||||||
|
return $filter(node.parentNode.children,
|
||||||
|
child => { return child !== node; }); |
||||||
|
} |
||||||
|
|
||||||
|
const $style_toggle = (node, className) => { |
||||||
|
node.classList.toggle(className); |
||||||
|
} |
||||||
|
|
||||||
|
const $style_add = (node, className) => { |
||||||
|
node.classList.add(className); |
||||||
|
} |
||||||
|
|
||||||
|
const $style_del = (node, className) => { |
||||||
|
node.classList.remove(className); |
||||||
|
} |
||||||
|
|
||||||
|
const $style_of = (node, ruleName) => { |
||||||
|
return getComputedStyle(node)[ruleName] |
||||||
|
} |
||||||
|
|
||||||
|
const $new = (tag, id, html) => { |
||||||
|
let new_tag = document.createElement(tag); |
||||||
|
new_tag.id = id; |
||||||
|
new_tag.innerHTML = html; |
||||||
|
return new_tag.cloneNode(true); |
||||||
|
} |
||||||
|
|
||||||
|
const $append = (parent, child) => { |
||||||
|
parent.appendChild(child); |
||||||
|
} |
||||||
|
|
||||||
|
const $prepend = (parent, node) => { |
||||||
|
parent.insertBefore(node, parent.firstChild); |
||||||
|
} |
||||||
|
|
||||||
|
const $remove = (parent, child) => { |
||||||
|
parent.removeChild(child); |
||||||
|
} |
||||||
|
|
||||||
|
const $clone = (node) => { |
||||||
|
return node.cloneNode(true); |
||||||
|
} |
||||||
|
|
||||||
|
const $contains = (node, child) => { |
||||||
|
return node !== child && node.contains(child); |
||||||
|
} |
||||||
|
|
||||||
|
const $has = (node, selector) => { |
||||||
|
return node.querySelector(selector) !== null; |
||||||
|
} |
||||||
|
|
||||||
|
const $empty = (node) => { |
||||||
|
return node.innerHTML == ''; |
||||||
|
} |
||||||
|
|
||||||
|
const $attribute_of = (node, name) => { |
||||||
|
return node.getAttribute(name); |
||||||
|
} |
||||||
|
|
||||||
|
const $attribute = (node, name, value) => { |
||||||
|
return node.setAttribute(name, value); |
||||||
|
} |
||||||
|
|
||||||
|
const $contents_of = (node) => { |
||||||
|
return node.innerHTML; |
||||||
|
} |
||||||
|
|
||||||
|
const $contents = (node, newhtml) => { |
||||||
|
node.innerHTML = newhtml; |
||||||
|
} |
||||||
|
|
||||||
|
const $has_class = (node, className) => { |
||||||
|
return node.classList.contains(className); |
||||||
|
} |
||||||
|
|
||||||
|
const $outer_html = (node) => { |
||||||
|
return node.outerHTML; |
||||||
|
} |
||||||
|
|
||||||
|
const $replace_with = (node, newNodes) => { |
||||||
|
node.replaceChildren(...newNodes); |
||||||
|
} |
||||||
|
|
||||||
|
const $matches = (node, selector) => { |
||||||
|
return node.matches(selector); |
||||||
|
} |
||||||
|
|
||||||
|
const $parent = (node) => { |
||||||
|
return node.parentNode; |
||||||
|
} |
||||||
|
|
||||||
|
const $text_of = (node) => { |
||||||
|
return node.textContent; |
||||||
|
} |
||||||
|
|
||||||
|
const $text = (node, newtext) => { |
||||||
|
node.textContent = newtext; |
||||||
|
} |
||||||
|
|
||||||
|
const $off = (node, eventName, eventHandler) => { |
||||||
|
node.removeEventListener(eventName, eventHandler); |
||||||
|
} |
||||||
|
|
||||||
|
const $on = (node, eventName, eventHandler) => { |
||||||
|
node.addEventListener(eventName, eventHandler); |
||||||
|
} |
||||||
|
|
||||||
|
const $now = () => { |
||||||
|
return Date.now(); |
||||||
|
} |
||||||
|
|
||||||
|
const $html = (htmlString) => { |
||||||
|
const temp = document.createElement('template'); |
||||||
|
temp.innerHTML = htmlString; |
||||||
|
return temp.content; |
||||||
|
} |
||||||
|
|
||||||
|
const $observe = (node_id, cb) => { |
||||||
|
const options = { |
||||||
|
root: document, |
||||||
|
rootMargin: "0px", |
||||||
|
scrollMargin: "0px", |
||||||
|
threshold: 1.0, |
||||||
|
delay: 200, |
||||||
|
}; |
||||||
|
|
||||||
|
const callback = (entries, observer) => { |
||||||
|
cb(); |
||||||
|
} |
||||||
|
|
||||||
|
const observer = new IntersectionObserver(callback, options); |
||||||
|
observer.observe($id(node_id)); |
||||||
|
} |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
package tests |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
) |
||||||
|
|
||||||
|
func TestLogin(t *testing.T) { |
||||||
|
assert.Equal(true, false) |
||||||
|
} |
||||||
|
|
||||||
|
func TestMain(m *testing.M) { |
||||||
|
m.Run() |
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<h1>Thank You!</h1> |
||||||
|
|
||||||
|
<p>Your payment has been accepted and you should receive an email with your receipt.<p> |
||||||
|
|
||||||
|
<p><a href="/login/">Log In To Your Account</a></p> |
||||||
|
|
||||||
@ -0,0 +1,29 @@ |
|||||||
|
|
||||||
|
<card> |
||||||
|
<top> |
||||||
|
<h1>FakePay</h1> |
||||||
|
</top> |
||||||
|
<middle> |
||||||
|
<form action="/api/fakepay/pay" method="POST"> |
||||||
|
<label for="Card">Card</label> |
||||||
|
<input name="Card" id="Card" placeholder="Card" /> |
||||||
|
|
||||||
|
<label for="NameOnCard">Name on Card</label> |
||||||
|
<input name="NameOnCard" id="NameOnCard" placeholder="Name on Card" /> |
||||||
|
|
||||||
|
<label for="CVV">CVV</label> |
||||||
|
<input name="CVV" id="CVV" placeholder="CVV" /> |
||||||
|
|
||||||
|
<label for="Expiration">Expiration</label> |
||||||
|
<input name="Expiration" id="Expiration" placeholder="Expiration" /> |
||||||
|
|
||||||
|
<label for="PostalCode">Postal/Zip</label> |
||||||
|
<input name="PostalCode" id="PostalCode" placeholder="PostalCode" /> |
||||||
|
|
||||||
|
<button-group> |
||||||
|
<a href="/shopping/"><button type="button">Continue Shopping</button></a> |
||||||
|
<button type="submit">Submit</button> |
||||||
|
</button-group> |
||||||
|
</form> |
||||||
|
</middle> |
||||||
|
</card> |
||||||
@ -1,24 +1,104 @@ |
|||||||
<script> |
<script> |
||||||
let Data = new PaginateTable("/api/shopping/products") |
/* |
||||||
|
* Advantages of => over function: |
||||||
|
* - Easier transition from variables to callbacks. |
||||||
|
* - this variable is locked so isn't random |
||||||
|
* - can't accidentally rename the function because it's const |
||||||
|
* - callbacks with function are fucking stupid. |
||||||
|
*/ |
||||||
|
let Data = new ForeverScroll("/api/shopping/products"); |
||||||
|
|
||||||
|
const cartItems = []; |
||||||
|
|
||||||
|
const update = () => { |
||||||
|
// product list |
||||||
|
// cart contents |
||||||
|
const cart = $id('cart'); |
||||||
|
let total = cartItems.reduce((a, v) => a + v.Price, 0.0); |
||||||
|
|
||||||
|
if(cartItems.length > 0) { |
||||||
|
$style_del(cart, 'hidden'); |
||||||
|
$render_data('cart-row', 'cart-items', cartItems); |
||||||
|
} else { |
||||||
|
$style_add(cart, 'hidden'); |
||||||
|
} |
||||||
|
|
||||||
|
$text($id('cart-total'), `${total}`); |
||||||
|
} |
||||||
|
|
||||||
|
const remove = (item_number) => { |
||||||
|
cartItems.splice(item_number, 1); |
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
const addToCart = (item_i) => { |
||||||
|
cartItems.push(Data.items[item_i]); |
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
$boot(async () => { |
||||||
|
// kind of not good so revisit how Data works |
||||||
|
const items = await Data.init(); |
||||||
|
const list = $id('product-list'); |
||||||
|
const tmpl = $id('product-row'); |
||||||
|
|
||||||
|
for(let i in items) { |
||||||
|
$append(list, $render(tmpl, {i, item: items[i]})); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
</script> |
</script> |
||||||
|
|
||||||
|
<style> |
||||||
|
.hidden { |
||||||
|
visibility: hidden; |
||||||
|
} |
||||||
|
</style> |
||||||
|
|
||||||
<h1>Products</h1> |
<h1>Products</h1> |
||||||
|
|
||||||
<p>I have these products for you:</p> |
<div id="cart" class="hidden"> |
||||||
|
<aside> |
||||||
|
<table id='cart-items'> |
||||||
|
</table> |
||||||
|
|
||||||
|
<template id="cart-row"> |
||||||
|
<tr id='item-${i}'> |
||||||
|
<td>${item.Title}</td> |
||||||
|
<td>${item.Price}</td> |
||||||
|
<td> |
||||||
|
<button type="button" onclick='remove(${i})'>X</button> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
</template> |
||||||
|
|
||||||
|
<p>Cart total is <span id='cart-total'></span></p> |
||||||
|
<a href="/shopping/checkout"> |
||||||
|
<button type="button">Checkout</button> |
||||||
|
</a> |
||||||
|
</aside> |
||||||
|
</div> |
||||||
|
|
||||||
<block x-data="Data"> |
<block id='product-list'> |
||||||
<template x-for="item in contents"> |
<template id='product-row'> |
||||||
<bar class="bg-gray-800"> |
<bar class="bg-gray-800"> |
||||||
<shape x-text="item.Slug"></shape> |
<shape></shape> |
||||||
<div> |
<div> |
||||||
<h4 x-text="item.Title"></h4> |
<h4>${item.Title}</h4> |
||||||
<aside x-text="item.Description"></aside> |
<aside>${item.Description}</aside> |
||||||
|
|
||||||
<bar class="justify-between" style="padding:30px"> |
<bar class="justify-between" style="padding:30px"> |
||||||
<div class="text-2xl">Price: <span x-text="item.Price"></span></div> |
<div class="text-2xl">Price: <span>${item.Price}</span></div> |
||||||
<a href="/shopping/checkout"><button type="button">Buy</button></a> |
<button onclick="addToCart(${i})" id="cart-button" type="button"> |
||||||
|
<span> |
||||||
|
Add to Cart |
||||||
|
</span> |
||||||
|
</button> |
||||||
</bar> |
</bar> |
||||||
</div> |
</div> |
||||||
</bar> |
</bar> |
||||||
</template> |
</template> |
||||||
</block> |
</block> |
||||||
|
|
||||||
|
<div id='canary'></div> |
||||||
|
|
||||||
|
|||||||
Loading…
Reference in new issue