Rewrote the shopping cart to use my own funky JS thing and it works.

master
Zed A. Shaw 2 weeks ago
parent 47e7b69af7
commit ccd8977d40
  1. 13
      features/fakepay/api.go
  2. 9
      features/fakepay/db.go
  3. 10
      features/fakepay/init.go
  4. 11
      features/fakepay/views.go
  5. 2
      features/init.go
  6. 7
      features/shopping/api.go
  7. 8
      static/input_style.css
  8. 3
      static/js/code.js
  9. 233
      static/js/jzed.js
  10. 14
      tests/fakepay/example_test.go
  11. 6
      views/fakepay/complete.html
  12. 29
      views/fakepay/index.html
  13. 1
      views/layouts/main.html
  14. 5
      views/shopping/checkout.html
  15. 116
      views/shopping/index.html

@ -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) }
}

@ -5,10 +5,12 @@ import (
"MY/webapp/features/email" "MY/webapp/features/email"
"MY/webapp/features/paypal" "MY/webapp/features/paypal"
"MY/webapp/features/shopping" "MY/webapp/features/shopping"
"MY/webapp/features/fakepay"
) )
func Setup(app *fiber.App) { func Setup(app *fiber.App) {
features_email.Setup(app) features_email.Setup(app)
features_paypal.Setup(app) features_paypal.Setup(app)
features_shopping.Setup(app) features_shopping.Setup(app)
features_fakepay.Setup(app)
} }

@ -16,13 +16,16 @@ func GetApiProducts(c *fiber.Ctx) error {
return data.SelectJson[data.Product](c, err, sql, args...) return data.SelectJson[data.Product](c, err, sql, args...)
} }
func GetApiCart(c *fiber.Ctx) error { func GetApiCart(c *fiber.Ctx) error {
return c.JSON(fiber.Map{}) return c.JSON(fiber.Map{})
} }
func GetApiCartRemove(c *fiber.Ctx) error {
return c.Redirect("/shopping/checkout")
}
func SetupApi(app *fiber.App) { func SetupApi(app *fiber.App) {
app.Get("/api/shopping/products", GetApiProducts) app.Get("/api/shopping/products", GetApiProducts)
app.Get("/api/shopping/cart", GetApiCart) app.Get("/api/shopping/cart", GetApiCart)
app.Get("/api/shopping/cart/remove/:item_id", GetApiCartRemove)
} }

@ -237,6 +237,14 @@ hr {
visibility: hidden; visibility: hidden;
} }
form {
@apply flex flex-col;
}
form > button-group {
@apply flex text-gray-50 p-3 justify-stretch *:flex-1 gap-2;
}
select { select {
@apply text-red-50 bg-gray-800 rounded-lg border-1 border-gray-600 p-1; @apply text-red-50 bg-gray-800 rounded-lg border-1 border-gray-600 p-1;
} }

@ -8,6 +8,7 @@ class PaginateTable {
} }
async contents() { async contents() {
console.log("CONTENTS RUN");
if(this.page < 0) this.page = 0; if(this.page < 0) this.page = 0;
let url = `${this.url}?page=${this.page}`; let url = `${this.url}?page=${this.page}`;
@ -45,6 +46,8 @@ class ForeverScroll {
const items = await resp.json(); const items = await resp.json();
if(items) this.items = items; if(items) this.items = items;
return items;
} }
async load() { async load() {

@ -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>

@ -13,6 +13,7 @@
<!-- then alpine runs, triggers init, and tada you get no-build plugins --> <!-- then alpine runs, triggers init, and tada you get no-build plugins -->
<script defer src="/js/alpine.js"></script> <script defer src="/js/alpine.js"></script>
<script src="/js/code.js"></script> <script src="/js/code.js"></script>
<script src="/js/jzed.js"></script>
<title>Go Web Dev Starter Kit</title> <title>Go Web Dev Starter Kit</title>
</head> </head>
<body id="top" data-testid="{{.PageId}}"> <body id="top" data-testid="{{.PageId}}">

@ -5,7 +5,7 @@
<bar class="justify-between" style="padding:30px"> <bar class="justify-between" style="padding:30px">
<div class="text-2xl">Price: $100.00</div> <div class="text-2xl">Price: $100.00</div>
<a href="/shopping/remove/1"><button type="button">X</button></a> <a href="/api/shopping/cart/remove/1"><button type="button">X</button></a>
</bar> </bar>
</bar> </bar>
@ -16,6 +16,5 @@
<bar> <bar>
<a href="/shopping/"><button type="button">Continue Shopping</button></a> <a href="/shopping/"><button type="button">Continue Shopping</button></a>
<a href="/paypal/"><button type="button">Pay with Paypal</button></a> <a href="/fakepay/"><button type="button">Pay with FakePay</button></a>
<a href="/paypal/"><button type="button">Pay with Credit Card</button></a>
</bar> </bar>

@ -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>
<block x-data="Data"> <table id='cart-items'>
<template x-for="item in contents"> </table>
<bar class="bg-gray-800">
<shape x-text="item.Slug"></shape> <template id="cart-row">
<div> <tr id='item-${i}'>
<h4 x-text="item.Title"></h4> <td>${item.Title}</td>
<aside x-text="item.Description"></aside> <td>${item.Price}</td>
<td>
<bar class="justify-between" style="padding:30px"> <button type="button" onclick='remove(${i})'>X</button>
<div class="text-2xl">Price: <span x-text="item.Price"></span></div> </td>
<a href="/shopping/checkout"><button type="button">Buy</button></a> </tr>
</bar> </template>
</div>
</bar> <p>Cart total is <span id='cart-total'></span></p>
</template> <a href="/shopping/checkout">
<button type="button">Checkout</button>
</a>
</aside>
</div>
<block id='product-list'>
<template id='product-row'>
<bar class="bg-gray-800">
<shape></shape>
<div>
<h4>${item.Title}</h4>
<aside>${item.Description}</aside>
<bar class="justify-between" style="padding:30px">
<div class="text-2xl">Price: <span>${item.Price}</span></div>
<button onclick="addToCart(${i})" id="cart-button" type="button">
<span>
Add to Cart
</span>
</button>
</bar>
</div>
</bar>
</template>
</block> </block>
<div id='canary'></div>

Loading…
Cancel
Save