From aa1cef6e7b2b2f52bc0739e4ee75a921f55049ac Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Wed, 11 Mar 2026 00:58:24 -0400 Subject: [PATCH] new migration in apps.applets to seed wallet applet models; many expanded styles in wallet.js, chiefly concerned w. wallet-oriented FTs tbh; some intermittent Windows cache errors quashed in dash view ITs; apps.dash.views & .urls now support wallet applets; apps.lyric.models now discerns tithe coins (available for purchase soon); new styles across many scss files, again many concerning wallet applets but also applets more generally and also unorthodox media query parameters to make UX more usable; a slew of new wallet partials --- .../applets/migrations/0003_wallet_applets.py | 37 ++++++++ src/apps/applets/models.py | 2 + .../dashboard/static/apps/scripts/wallet.js | 70 +++++++++++++- .../tests/integrated/test_wallet_views.py | 94 ++++++++++++++++++- src/apps/dashboard/urls.py | 1 + src/apps/dashboard/views.py | 31 ++++-- src/apps/lyric/models.py | 2 + src/functional_tests/base.py | 10 ++ src/functional_tests/test_wallet.py | 34 ++++--- src/static_src/scss/_applets.scss | 8 +- src/static_src/scss/_base.scss | 4 +- src/static_src/scss/_gameboard.scss | 4 + src/static_src/scss/_wallet-tokens.scss | 57 +++++++++++ src/templates/apps/dashboard/wallet.html | 61 ++---------- .../_partials/_applet-wallet-balances.html | 19 ++++ .../_partials/_applet-wallet-payment.html | 10 ++ .../_partials/_applet-wallet-tokens.html | 53 +++++++++++ .../apps/wallet/_partials/_applets.html | 27 ++++++ 18 files changed, 441 insertions(+), 83 deletions(-) create mode 100644 src/apps/applets/migrations/0003_wallet_applets.py create mode 100644 src/templates/apps/wallet/_partials/_applet-wallet-balances.html create mode 100644 src/templates/apps/wallet/_partials/_applet-wallet-payment.html create mode 100644 src/templates/apps/wallet/_partials/_applet-wallet-tokens.html create mode 100644 src/templates/apps/wallet/_partials/_applets.html diff --git a/src/apps/applets/migrations/0003_wallet_applets.py b/src/apps/applets/migrations/0003_wallet_applets.py new file mode 100644 index 0000000..ff77edc --- /dev/null +++ b/src/apps/applets/migrations/0003_wallet_applets.py @@ -0,0 +1,37 @@ +from django.db import migrations, models + + +def seed_wallet_applets(apps, schema_editor): + Applet = apps.get_model('applets', 'Applet') + for slug, name, cols, rows in [ + ('wallet-balances', 'Wallet Balances', 3, 3), + ('wallet-tokens', 'Wallet Tokens', 3, 3), + ('wallet-payment', 'Payment Methods', 6, 2), + ]: + Applet.objects.get_or_create( + slug=slug, + defaults={'name': name, 'grid_cols': cols, 'grid_rows': rows, 'context': 'wallet'}, + ) + + +class Migration(migrations.Migration): + dependencies = [ + ('applets', '0002_seed_applets'), + ] + + operations = [ + migrations.AlterField( + model_name='applet', + name='context', + field=models.CharField( + choices=[ + ('dashboard', 'Dashboard'), + ('gameboard', 'Gameboard'), + ('wallet', 'Wallet'), + ], + default='dashboard', + max_length=20, + ), + ), + migrations.RunPython(seed_wallet_applets, migrations.RunPython.noop), + ] diff --git a/src/apps/applets/models.py b/src/apps/applets/models.py index a1bad13..95f4ea6 100644 --- a/src/apps/applets/models.py +++ b/src/apps/applets/models.py @@ -3,9 +3,11 @@ from django.db import models class Applet(models.Model): DASHBOARD = "dashboard" GAMEBOARD = "gameboard" + WALLET = "wallet" CONTEXT_CHOICES = [ (DASHBOARD, "Dashboard"), (GAMEBOARD, "Gameboard"), + (WALLET, "Wallet"), ] slug = models.SlugField(unique=True) diff --git a/src/apps/dashboard/static/apps/scripts/wallet.js b/src/apps/dashboard/static/apps/scripts/wallet.js index a5ddcc3..b3067bf 100644 --- a/src/apps/dashboard/static/apps/scripts/wallet.js +++ b/src/apps/dashboard/static/apps/scripts/wallet.js @@ -3,6 +3,7 @@ const initWallet = () => { const addBtn = document.getElementById('id_add_payment_method'); const saveBtn = document.getElementById('id_save_payment_method'); + const cancelBtn = document.getElementById('id_cancel_payment_method'); if (!addBtn) return; const getCsrf = () => document.cookie.match(/csrftoken=([^;]+)/)[1]; @@ -15,8 +16,25 @@ const initWallet = () => { const {client_secret, publishable_key} = await res.json(); stripe = Stripe(publishable_key); elements = stripe.elements({clientSecret: client_secret}); - elements.create('payment').mount('#id_stripe_payment_element'); + const paymentEl = elements.create('payment'); + paymentEl.mount('#id_stripe_payment_element'); saveBtn.hidden = false; + cancelBtn.hidden = false; + const section = addBtn.closest('section'); + const rowPx = 3 * parseFloat(getComputedStyle(document.documentElement).fontSize); + const updateRows = () => { + const rows = Math.ceil(section.scrollHeight / rowPx) + 1; + section.style.setProperty('--applet-rows', String(rows)); + }; + paymentEl.on('ready', () => { + updateRows(); + const iframe = document.querySelector('#id_stripe_payment_element iframe'); + if (iframe) { + const obs = new MutationObserver(updateRows); + obs.observe(iframe, { attributes: true, attributeFilter: ['style'] }); + section._stripeObs = obs; + } + }); }); saveBtn.addEventListener('click', async () => { @@ -37,7 +55,55 @@ const initWallet = () => { const pm = document.createElement('div'); pm.textContent = `${brand} ····${last4}`; document.getElementById('id_payment_methods').appendChild(pm); + elements.getElement('payment').unmount(); + elements = null; + stripe = null; + saveBtn.hidden = true; + cancelBtn.hidden = true; + const section = cancelBtn.closest('section'); + section.style.setProperty('--applet-rows', '2'); + if (section._stripeObs) { section._stripeObs.disconnect(); section._stripeObs = null; } + }); + + cancelBtn.addEventListener('click', () => { + if (elements) { + elements.getElement('payment').unmount(); + elements = null; + stripe = null; + } + saveBtn.hidden = true; + cancelBtn.hidden = true; + const section = cancelBtn.closest('section'); + section.style.setProperty('--applet-rows', '2'); + if (section._stripeObs) { section._stripeObs.disconnect(); section._stripeObs = null; } }); }; -document.addEventListener('DOMContentLoaded', initWallet); \ No newline at end of file +function initWalletTooltips() { + const portal = document.getElementById('id_tooltip_portal'); + if (!portal) return; + + document.querySelectorAll('.wallet-tokens .token').forEach(token => { + const tooltip = token.querySelector('.token-tooltip'); + if (!tooltip) return; + + token.addEventListener('mouseenter', () => { + const rect = token.getBoundingClientRect(); + portal.innerHTML = tooltip.innerHTML; + portal.classList.add('active'); + const halfW = portal.offsetWidth / 2; + const rawLeft = rect.left + rect.width / 2; + const clampedLeft = Math.max(halfW + 8, Math.min(rawLeft, window.innerWidth - halfW - 8)); + portal.style.left = Math.round(clampedLeft) + 'px'; + portal.style.top = Math.round(rect.top) + 'px'; + portal.style.transform = 'translate(-50%, calc(-100% - 0.5rem))'; + }); + + token.addEventListener('mouseleave', () => { + portal.classList.remove('active'); + }); + }); +} + +document.addEventListener('DOMContentLoaded', initWallet); +document.addEventListener('DOMContentLoaded', initWalletTooltips); \ No newline at end of file diff --git a/src/apps/dashboard/tests/integrated/test_wallet_views.py b/src/apps/dashboard/tests/integrated/test_wallet_views.py index 28c677a..27d5e8f 100644 --- a/src/apps/dashboard/tests/integrated/test_wallet_views.py +++ b/src/apps/dashboard/tests/integrated/test_wallet_views.py @@ -2,6 +2,7 @@ import lxml.html from django.test import override_settings, TestCase +from apps.applets.models import Applet, UserApplet from apps.lyric.models import Token, User, Wallet @@ -47,4 +48,95 @@ class WalletViewTest(TestCase): bundles = self.parsed.cssselect("#id_tithe_token_shop .token-bundle") self.assertGreater(len(bundles), 0) - + +@override_settings(COMPRESS_ENABLED=False) +class WalletViewAppletContextTest(TestCase): + def setUp(self): + self.user = User.objects.create(email="walletctx@test.io") + Applet.objects.get_or_create( + slug="wallet-balances", + defaults={"name": "Wallet Balances", "grid_cols": 3, "grid_rows": 3, "context": "wallet"}, + ) + Applet.objects.get_or_create( + slug="wallet-tokens", + defaults={"name": "Wallet Tokens", "grid_cols": 3, "grid_rows": 3, "context": "wallet"}, + ) + Applet.objects.get_or_create( + slug="wallet-payment", + defaults={"name": "Payment Methods", "grid_cols": 6, "grid_rows": 2, "context": "wallet"}, + ) + self.client.force_login(self.user) + + def test_wallet_view_passes_applets_context(self): + response = self.client.get("/dashboard/wallet/") + slugs = [e["applet"].slug for e in response.context["applets"]] + self.assertIn("wallet-balances", slugs) + self.assertIn("wallet-tokens", slugs) + self.assertIn("wallet-payment", slugs) + + def test_wallet_page_renders_applets_container(self): + response = self.client.get("/dashboard/wallet/") + parsed = lxml.html.fromstring(response.content) + [_] = parsed.cssselect("#id_wallet_applets_container") + + def test_wallet_page_renders_gear_button(self): + response = self.client.get("/dashboard/wallet/") + parsed = lxml.html.fromstring(response.content) + [_] = parsed.cssselect(".gear-btn") + + +@override_settings(COMPRESS_ENABLED=False) +class ToggleWalletAppletsTest(TestCase): + def setUp(self): + self.user = User.objects.create(email="wallettoggle@test.io") + self.balances = Applet.objects.get_or_create( + slug="wallet-balances", + defaults={"name": "Wallet Balances", "grid_cols": 3, "grid_rows": 3, "context": "wallet"}, + )[0] + self.tokens = Applet.objects.get_or_create( + slug="wallet-tokens", + defaults={"name": "Wallet Tokens", "grid_cols": 3, "grid_rows": 3, "context": "wallet"}, + )[0] + Applet.objects.get_or_create( + slug="wallet-payment", + defaults={"name": "Payment Methods", "grid_cols": 6, "grid_rows": 2, "context": "wallet"}, + ) + self.client.force_login(self.user) + + def test_toggle_requires_login(self): + self.client.logout() + response = self.client.post("/dashboard/wallet/toggle-applets", {}) + self.assertRedirects( + response, "/?next=/dashboard/wallet/toggle-applets", + fetch_redirect_response=False, + ) + + def test_toggle_redirects_to_wallet(self): + response = self.client.post( + "/dashboard/wallet/toggle-applets", {"applets": ["wallet-balances"]} + ) + self.assertRedirects(response, "/dashboard/wallet/", fetch_redirect_response=False) + + def test_toggle_hides_unchecked_applet(self): + self.client.post( + "/dashboard/wallet/toggle-applets", {"applets": ["wallet-balances"]} + ) + ua = UserApplet.objects.get(user=self.user, applet=self.tokens) + self.assertFalse(ua.visible) + + def test_toggle_shows_checked_applet(self): + UserApplet.objects.create(user=self.user, applet=self.balances, visible=False) + self.client.post( + "/dashboard/wallet/toggle-applets", {"applets": ["wallet-balances"]} + ) + ua = UserApplet.objects.get(user=self.user, applet=self.balances) + self.assertTrue(ua.visible) + + def test_toggle_htmx_returns_container_partial(self): + response = self.client.post( + "/dashboard/wallet/toggle-applets", + {"applets": ["wallet-balances"]}, + HTTP_HX_REQUEST="true", + ) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "id_wallet_applets_container") diff --git a/src/apps/dashboard/urls.py b/src/apps/dashboard/urls.py index fe34e6f..3006245 100644 --- a/src/apps/dashboard/urls.py +++ b/src/apps/dashboard/urls.py @@ -10,6 +10,7 @@ urlpatterns = [ path('users//', views.my_lists, name='my_lists'), path('toggle_applets', views.toggle_applets, name="toggle_applets"), path('wallet/', views.wallet, name='wallet'), + path('wallet/toggle-applets', views.toggle_wallet_applets, name='toggle_wallet_applets'), path('wallet/setup-intent', views.setup_intent, name='setup_intent'), path('wallet/save-payment-method', views.save_payment_method, name='save_payment_method'), ] diff --git a/src/apps/dashboard/views.py b/src/apps/dashboard/views.py index 9701661..8e4e901 100644 --- a/src/apps/dashboard/views.py +++ b/src/apps/dashboard/views.py @@ -144,15 +144,34 @@ def toggle_applets(request): @login_required(login_url="/") @ensure_csrf_cookie def wallet(request): - wallet = request.user.wallet - coin = request.user.tokens.filter(token_type=Token.COIN).first() - free_tokens = list(request.user.tokens.filter(token_type=Token.FREE)) return render(request, "apps/dashboard/wallet.html", { - "wallet": wallet, - "coin": coin, - "free_tokens": free_tokens, + "wallet": request.user.wallet, + "coin": request.user.tokens.filter(token_type=Token.COIN).first(), + "free_tokens": list(request.user.tokens.filter(token_type=Token.FREE)), + "tithe_tokens": list(request.user.tokens.filter(token_type=Token.TITHE)), + "applets": applet_context(request.user, "wallet"), + "page_class": "page-wallet", }) +@login_required(login_url="/") +def toggle_wallet_applets(request): + checked = request.POST.getlist("applets") + for applet in Applet.objects.filter(context="wallet"): + UserApplet.objects.update_or_create( + user=request.user, + applet=applet, + defaults={"visible": applet.slug in checked}, + ) + if request.headers.get("HX-Request"): + return render(request, "apps/wallet/_partials/_applets.html", { + "applets": applet_context(request.user, "wallet"), + "wallet": request.user.wallet, + "coin": request.user.tokens.filter(token_type=Token.COIN).first(), + "free_tokens": list(request.user.tokens.filter(token_type=Token.FREE)), + "tithe_tokens": list(request.user.tokens.filter(token_type=Token.TITHE)), + }) + return redirect("wallet") + @login_required(login_url="/") def setup_intent(request): stripe.api_key = settings.STRIPE_SECRET_KEY diff --git a/src/apps/lyric/models.py b/src/apps/lyric/models.py index 0d1641e..fe9c6a6 100644 --- a/src/apps/lyric/models.py +++ b/src/apps/lyric/models.py @@ -86,6 +86,8 @@ class Token(models.Model): def tooltip_description(self): if self.token_type in (self.COIN, self.FREE): return "Admit 1 Entry" + if self.token_type == self.TITHE: + return "+ Writ bonus" return "" def tooltip_expiry(self): diff --git a/src/functional_tests/base.py b/src/functional_tests/base.py index c1b4ba9..e89fbe3 100644 --- a/src/functional_tests/base.py +++ b/src/functional_tests/base.py @@ -81,6 +81,16 @@ class FunctionalTest(StaticLiveServerTestCase): @wait def wait_for(self, fn): return fn() + + def wait_for_slow(self, fn, timeout=30): + start_time = time.time() + while True: + try: + return fn() + except (AssertionError, WebDriverException) as e: + if time.time() - start_time > timeout: + raise e + time.sleep(0.5) def create_pre_authenticated_session(self, email): if self.test_server: diff --git a/src/functional_tests/test_wallet.py b/src/functional_tests/test_wallet.py index 2f650ce..b1cdb07 100644 --- a/src/functional_tests/test_wallet.py +++ b/src/functional_tests/test_wallet.py @@ -9,6 +9,15 @@ class WalletDisplayTest(FunctionalTest): def setUp(self): super().setUp() Applet.objects.get_or_create(slug="wallet", defaults={"name": "Wallet"}) + for slug, name, cols, rows in [ + ("wallet-balances", "Wallet Balances", 3, 3), + ("wallet-tokens", "Wallet Tokens", 3, 3), + ("wallet-payment", "Payment Methods", 6, 2), + ]: + Applet.objects.get_or_create( + slug=slug, + defaults={"name": name, "grid_cols": cols, "grid_rows": rows, "context": "wallet"}, + ) def test_new_user_wallet_shows_starting_balances(self): # 1. Log in as new user @@ -45,14 +54,10 @@ class WalletDisplayTest(FunctionalTest): ActionChains(self.browser).move_to_element(coin).perform() self.wait_for( lambda: self.assertTrue( - self.browser.find_element( - By.CSS_SELECTOR, "#id_coin_on_a_string .token-tooltip" - ).is_displayed() + self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed() ) ) - coin_tooltip = self.browser.find_element( - By.CSS_SELECTOR, "#id_coin_on_a_string .token-tooltip" - ).text + coin_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text self.assertIn("Coin-on-a-String", coin_tooltip) self.assertIn("Admit 1 Entry", coin_tooltip) self.assertIn("no expiry", coin_tooltip) @@ -62,14 +67,10 @@ class WalletDisplayTest(FunctionalTest): ActionChains(self.browser).move_to_element(free_token).perform() self.wait_for( lambda: self.assertTrue( - self.browser.find_element( - By.CSS_SELECTOR, "#id_free_token_0 .token-tooltip" - ).is_displayed() + self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed() ) ) - free_tooltip = self.browser.find_element( - By.CSS_SELECTOR, "#id_free_token_0 .token-tooltip" - ).text + free_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text self.assertIn("Free Token", free_tooltip) self.assertIn("Admit 1 Entry", free_tooltip) self.assertIn("Expires", free_tooltip) @@ -125,10 +126,15 @@ class WalletDisplayTest(FunctionalTest): ).send_keys("42424") # 6. Return to main doc & submit form self.browser.switch_to.default_content() + self.wait_for( + lambda: self.assertFalse( + self.browser.find_element(By.ID, "id_save_payment_method").get_attribute("hidden") + ) + ) self.browser.find_element(By.ID, "id_save_payment_method").click() # 7. Wait for saved card to appear in payment methods list - # Assert last 4 digits shown - self.wait_for( + # Assert last 4 digits shown (Stripe confirmSetup + server round-trip can be slow) + self.wait_for_slow( lambda: self.assertIn( "4242", self.browser.find_element(By.ID, "id_payment_methods").text, diff --git a/src/static_src/scss/_applets.scss b/src/static_src/scss/_applets.scss index b6c9748..9e7b9c5 100644 --- a/src/static_src/scss/_applets.scss +++ b/src/static_src/scss/_applets.scss @@ -72,8 +72,9 @@ } } -#id_dash_applet_menu { @extend %applet-menu; } -#id_game_applet_menu { @extend %applet-menu; } +#id_dash_applet_menu { @extend %applet-menu; } +#id_game_applet_menu { @extend %applet-menu; } +#id_wallet_applet_menu { @extend %applet-menu; } // ── Applets grid (shared across all boards) ──────────────── %applets-grid { @@ -101,7 +102,7 @@ 0.2rem solid rgba(var(--secUser), 0.5), ; box-shadow: - inset -1px -1px 0 rgba(255, 255, 255, 0.125), + inset -0.125rem -0.125rem 0 rgba(var(--ninUser), 0.125), inset 0.125rem 0.125rem 0 rgba(0, 0, 0, 0.8) ; border-radius: 0.75rem; @@ -119,3 +120,4 @@ #id_applets_container { @extend %applets-grid; } #id_game_applets_container { @extend %applets-grid; } +#id_wallet_applets_container { @extend %applets-grid; } diff --git a/src/static_src/scss/_base.scss b/src/static_src/scss/_base.scss index a1bcc0e..696048c 100644 --- a/src/static_src/scss/_base.scss +++ b/src/static_src/scss/_base.scss @@ -193,7 +193,7 @@ body { padding: 0.5rem 0; .col-lg-6 h2 { - font-size: 2.1rem; + font-size: 2rem; margin-bottom: 0.5rem; // text-justify: inter-character is Firefox-only; approximate for Safari/Chrome letter-spacing: 1em; @@ -234,7 +234,7 @@ body { text-align-last: center; letter-spacing: 0.25em; margin: 0 0 0.5rem; - font-size: 2.2rem; + font-size: 2rem; } } } diff --git a/src/static_src/scss/_gameboard.scss b/src/static_src/scss/_gameboard.scss index 8862aba..7414f5d 100644 --- a/src/static_src/scss/_gameboard.scss +++ b/src/static_src/scss/_gameboard.scss @@ -40,6 +40,10 @@ body.page-gameboard { .gameboard-page { min-width: 666px; } + + body.page-gameboard .container { + overflow: visible; + } } #id_applet_game_kit { diff --git a/src/static_src/scss/_wallet-tokens.scss b/src/static_src/scss/_wallet-tokens.scss index 951e8ca..d76eb3b 100644 --- a/src/static_src/scss/_wallet-tokens.scss +++ b/src/static_src/scss/_wallet-tokens.scss @@ -51,6 +51,58 @@ } } +.token--empty { + cursor: help; + + > i { opacity: 0.4; } +} + +html:has(body.page-wallet) { + overflow: hidden; +} + +body.page-wallet { + overflow: hidden; + + .container { + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + } + + .row { + flex-shrink: 0; + } +} + +.wallet-page { + position: relative; + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; +} + +.wallet-tokens { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-evenly; + overflow: visible; + + .token { + font-size: 1.5rem; + } + + .token:hover .token-tooltip { display: none; } +} + +#id_payment_methods { + overflow-y: auto; +} + @media (max-width: 768px) { .token .token-tooltip { width: 13rem; @@ -58,4 +110,9 @@ left: 0; transform: none; } + + .wallet-tokens .token-tooltip { + left: 50%; + transform: translateX(-50%); + } } diff --git a/src/templates/apps/dashboard/wallet.html b/src/templates/apps/dashboard/wallet.html index 5d45638..d1e705c 100644 --- a/src/templates/apps/dashboard/wallet.html +++ b/src/templates/apps/dashboard/wallet.html @@ -1,64 +1,15 @@ {% extends "core/base.html" %} {% load static %} -{% block title_text %}Dashboard{% endblock title_text %} -{% block header_text %}Dashboard{% endblock header_text %} +{% block title_text %}Dashwallet{% endblock title_text %} +{% block header_text %}Dashwallet{% endblock header_text %} {% block content %}
-

Wallet

- -
-
: {{ wallet.writs }}
-
Esteem: {{ wallet.esteem }}
-
- -
- {% if coin %} -
- -
-

{{ coin.tooltip_name }}

-

{{ coin.tooltip_description }}

- {% if coin.tooltip_shoptalk %} - {{ coin.tooltip_shoptalk }} - {% endif %} -

{{ coin.tooltip_expiry }}

-
-
- {% endif %} - {% for token in free_tokens %} -
- -
-

{{ token.tooltip_name }}

-

{{ token.tooltip_description }}

- {% if token.tooltip_shoptalk %} - {{ token.tooltip_shoptalk }} - {% endif %} -

{{ token.tooltip_expiry }}

-
-
- {% endfor %} -
- -
-

Payment Methods

- - -
- -
- -
-

Tithe Tokens

- -
- Tithe Token ×1 - + Writ bonus -
-
+ {% include "apps/applets/_partials/_gear.html" with menu_id="id_wallet_applet_menu" %} + {% include "apps/wallet/_partials/_applets.html" %}
+
-{% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/src/templates/apps/wallet/_partials/_applet-wallet-balances.html b/src/templates/apps/wallet/_partials/_applet-wallet-balances.html new file mode 100644 index 0000000..8357e97 --- /dev/null +++ b/src/templates/apps/wallet/_partials/_applet-wallet-balances.html @@ -0,0 +1,19 @@ +
+
: {{ wallet.writs }}
+
Esteem: {{ wallet.esteem }}
+
+
+ 1 Tithe Token + +144 Writs + $1.00 +
+
+ 5 Tithe Tokens + +750 Writs + $4.00 +
+
+
diff --git a/src/templates/apps/wallet/_partials/_applet-wallet-payment.html b/src/templates/apps/wallet/_partials/_applet-wallet-payment.html new file mode 100644 index 0000000..1d8e19e --- /dev/null +++ b/src/templates/apps/wallet/_partials/_applet-wallet-payment.html @@ -0,0 +1,10 @@ +
+

Payment Methods

+ +
+ + +
diff --git a/src/templates/apps/wallet/_partials/_applet-wallet-tokens.html b/src/templates/apps/wallet/_partials/_applet-wallet-tokens.html new file mode 100644 index 0000000..949d468 --- /dev/null +++ b/src/templates/apps/wallet/_partials/_applet-wallet-tokens.html @@ -0,0 +1,53 @@ +
+ {% if coin %} +
+ +
+

{{ coin.tooltip_name }}

+

{{ coin.tooltip_description }}

+ {% if coin.tooltip_shoptalk %} + {{ coin.tooltip_shoptalk }} + {% endif %} +

{{ coin.tooltip_expiry }}

+
+
+ {% endif %} + {% for token in free_tokens %} +
+ +
+

{{ token.tooltip_name }}

+

{{ token.tooltip_description }}

+ {% if token.tooltip_shoptalk %} + {{ token.tooltip_shoptalk }} + {% endif %} +

{{ token.tooltip_expiry }}

+
+
+ {% endfor %} + {% for token in tithe_tokens %} +
+ +
+

{{ token.tooltip_name }}

+

{{ token.tooltip_description }}

+ {% if token.tooltip_shoptalk %} + {{ token.tooltip_shoptalk }} + {% endif %} +

{{ token.tooltip_expiry }}

+
+
+ {% empty %} +
+ +
+

Tithe Token

+

0 owned

+

purchase one above

+
+
+ {% endfor %} +
diff --git a/src/templates/apps/wallet/_partials/_applets.html b/src/templates/apps/wallet/_partials/_applets.html new file mode 100644 index 0000000..4b08ff2 --- /dev/null +++ b/src/templates/apps/wallet/_partials/_applets.html @@ -0,0 +1,27 @@ +
+ + {% include "apps/applets/_partials/_applets.html" %} +