make error/success notices global, add framework for confirmation modals
This commit is contained in:
parent
9a5ed65f04
commit
b42f4fc983
@ -391,3 +391,7 @@ div.modal {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mt-1 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
62
assets/scripts/confirm_modal.js
Normal file
62
assets/scripts/confirm_modal.js
Normal file
@ -0,0 +1,62 @@
|
||||
class ConfirmModal {
|
||||
constructor(options = {}) {
|
||||
this.modal = document.querySelector(options.modalSelector || "#confirm-modal")
|
||||
this.message = this.modal.querySelector(options.messageSelector || "#msg")
|
||||
this.confirmBtn = this.modal.querySelector(options.confirmSelector || "#confirm")
|
||||
this.cancelBtn = this.modal.querySelector(options.cancelSelector || "#cancel")
|
||||
this.linkSelector = options.linkSelector || '.confirm-link'
|
||||
this.messageGenerator = options.messageGenerator || this.defaultMessageGenerator.bind(this)
|
||||
this.currentUrl = null
|
||||
|
||||
if (options.confirmText) this.confirmBtn.textContent = options.confirmText
|
||||
if (options.cancelText) this.cancelBtn.textContent = options.cancelText
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
document.querySelectorAll(this.linkSelector).forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
const message = this.messageGenerator(link)
|
||||
this.show(link.href, message)
|
||||
})
|
||||
})
|
||||
|
||||
this.confirmBtn.addEventListener('click', () => this.confirm())
|
||||
this.cancelBtn.addEventListener('click', () => this.hide())
|
||||
|
||||
this.modal.addEventListener('click', (e) => {
|
||||
if (e.target === this.modal) this.hide()
|
||||
})
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && this.modal.style.display === 'block') {
|
||||
this.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defaultMessageGenerator(link) {
|
||||
return link.dataset.message || 'Are you sure you want to continue?'
|
||||
}
|
||||
|
||||
show(url, message) {
|
||||
this.currentUrl = url
|
||||
this.message.textContent = message
|
||||
this.modal.style.display = 'block'
|
||||
this.confirmBtn.focus()
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal.style.display = 'none'
|
||||
this.currentUrl = null
|
||||
}
|
||||
|
||||
confirm() {
|
||||
if (this.currentUrl) {
|
||||
window.location.href = this.currentUrl
|
||||
}
|
||||
this.hide()
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import (
|
||||
"dk/internal/csrf"
|
||||
"dk/internal/middleware"
|
||||
"dk/internal/router"
|
||||
"dk/internal/session"
|
||||
"dk/internal/template"
|
||||
)
|
||||
|
||||
@ -24,6 +25,8 @@ func RenderPage(ctx router.Ctx, title, tmplPath string, additionalData map[strin
|
||||
return fmt.Errorf("failed to load layout template: %w", err)
|
||||
}
|
||||
|
||||
sess := ctx.UserValue("session").(*session.Session)
|
||||
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
@ -36,6 +39,8 @@ func RenderPage(ctx router.Ctx, title, tmplPath string, additionalData map[strin
|
||||
"_build": "dev",
|
||||
"user": auth.GetCurrentUser(ctx),
|
||||
"_memalloc": m.Alloc / 1024 / 1024,
|
||||
"_errormsg": sess.GetFlashMessage("error"),
|
||||
"_successmsg": sess.GetFlashMessage("success"),
|
||||
}
|
||||
|
||||
maps.Copy(data, LeftAside(ctx))
|
||||
|
@ -1,23 +0,0 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"dk/internal/auth"
|
||||
"dk/internal/csrf"
|
||||
"dk/internal/router"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// GenerateTopNav generates the top navigation HTML based on authentication status
|
||||
func GenerateTopNav(ctx router.Ctx) string {
|
||||
if auth.IsAuthenticated(ctx) {
|
||||
return fmt.Sprintf(`<form action="/logout" method="post" class="logout">
|
||||
%s
|
||||
<button class="img-button" type="submit"><img src="/assets/images/button_logout.gif" alt="Log Out" title="Log Out"></button>
|
||||
</form>
|
||||
<a href="/help"><img src="/assets/images/button_help.gif" alt="Help" title="Help"></a>`, csrf.HiddenField(ctx))
|
||||
} else {
|
||||
return `<a href="/login"><img src="/assets/images/button_login.gif" alt="Log In" title="Log In"></a>
|
||||
<a href="/register"><img src="/assets/images/button_register.gif" alt="Register" title="Register"></a>
|
||||
<a href="/help"><img src="/assets/images/button_help.gif" alt="Help" title="Help"></a>`
|
||||
}
|
||||
}
|
@ -99,5 +99,6 @@ func Teleport(ctx router.Ctx, params []string) {
|
||||
user.Currently = "In Town"
|
||||
user.Save()
|
||||
|
||||
sess.SetFlash("success", "You teleported to "+town.Name+" successfully!")
|
||||
ctx.Redirect("/town", 302)
|
||||
}
|
||||
|
@ -71,6 +71,16 @@ func (s *Session) GetFlash(key string) (any, bool) {
|
||||
return value, exists
|
||||
}
|
||||
|
||||
// GetFlashMessage retrieves and removes a flash message as string or empty string
|
||||
func (s *Session) GetFlashMessage(key string) string {
|
||||
if flash, exists := s.GetFlash(key); exists {
|
||||
if msg, ok := flash.(string); ok {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// RegenerateID creates a new session ID and updates storage
|
||||
func (s *Session) RegenerateID() {
|
||||
oldID := s.ID
|
||||
|
@ -3,8 +3,6 @@
|
||||
{block "content"}
|
||||
<h1>Log In</h1>
|
||||
|
||||
{error_message}
|
||||
|
||||
<form class="standard mb-1" action="/login" method="post">
|
||||
{csrf}
|
||||
|
||||
|
@ -3,8 +3,6 @@
|
||||
{block "content"}
|
||||
<h1>Register</h1>
|
||||
|
||||
{error_message}
|
||||
|
||||
<form class="standard" action="/register" method="post">
|
||||
{csrf}
|
||||
|
||||
|
7
templates/flashes.html
Normal file
7
templates/flashes.html
Normal file
@ -0,0 +1,7 @@
|
||||
{if _errormsg != ""}
|
||||
<div style="color: red; margin-bottom: 1rem;">{_errormsg}</div>
|
||||
{/if}
|
||||
|
||||
{if _successmsg != ""}
|
||||
<div style="color: green; margin-bottom: 1rem;">{_successmsg}</div>
|
||||
{/if}
|
@ -1,5 +1,5 @@
|
||||
{include "layout.html"}
|
||||
|
||||
{block "content"}
|
||||
Hey there, Guest!
|
||||
Hey there!
|
||||
{/block}
|
@ -8,6 +8,8 @@
|
||||
<link rel="stylesheet" href="/assets/reset.css">
|
||||
<link rel="stylesheet" href="/assets/dk.css">
|
||||
|
||||
<script src="/assets/scripts/confirm_modal.js"></script>
|
||||
|
||||
<script>
|
||||
function open_char_popup()
|
||||
{
|
||||
@ -33,7 +35,10 @@
|
||||
{include "leftside.html"}
|
||||
{/if}
|
||||
</aside>
|
||||
<main>{yield "content"}</main>
|
||||
<main>
|
||||
{include "flashes.html"}
|
||||
{yield "content"}
|
||||
</main>
|
||||
<aside id="right">
|
||||
{if authenticated}
|
||||
{include "rightside.html"}
|
||||
@ -49,6 +54,16 @@
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div id="confirm-modal" class="modal">
|
||||
<div class="content">
|
||||
<p id="msg">Are you sure you want to continue?</p>
|
||||
<div class="buttons">
|
||||
<button id="confirm" class="btn btn-primary">Yes</button>
|
||||
<button id="cancel" class="btn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{yield "scripts"}
|
||||
</body>
|
||||
</html>
|
||||
|
@ -31,7 +31,11 @@
|
||||
{if town != nil and t.Name == town.Name}
|
||||
<span>{t.Name} <i>(here)</i></span>
|
||||
{else}
|
||||
<a href="/teleport/{t.ID}">{t.Name}</a>
|
||||
{if user.TP < t.TPCost}
|
||||
<span class="light">{t.Name}</span>
|
||||
{else}
|
||||
<a class="teleport-link" href="/teleport/{t.ID}" data-town="{t.Name}" data-cost="{t.TPCost}">{t.Name}</a>
|
||||
{/if}
|
||||
{/if}
|
||||
{/for}
|
||||
{else}
|
||||
@ -47,3 +51,14 @@
|
||||
<a href="#">Log Out</a>
|
||||
<a href="/help">Help</a>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const teleportModal = new ConfirmModal({
|
||||
linkSelector: '.teleport-link',
|
||||
messageGenerator: (link) => `Are you sure you want to teleport to ${link.dataset.town} for ${link.dataset.cost} TP?`,
|
||||
confirmText: 'Teleport',
|
||||
cancelText: 'Stay'
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
@ -33,6 +33,7 @@
|
||||
|
||||
<section>
|
||||
<div class="title"><img src="/assets/images/button_inventory.gif" alt="Inventory" title="Inventory"></div>
|
||||
|
||||
<div>
|
||||
<img src="/assets/images/icon_weapon.gif" alt="Weapon" title="Weapon">
|
||||
{if user.WeaponName != ""}
|
||||
@ -60,6 +61,13 @@
|
||||
{if user.Slot1Name != ""}{slot1name}{/if}
|
||||
{if user.Slot2Name != ""}{slot2name}{/if}
|
||||
{if user.Slot3Name != ""}{slot3name}{/if}
|
||||
|
||||
<ul class="unstyled mt-1">
|
||||
<li>Strength: {user.Strength}</li>
|
||||
<li>Dexterity: {user.Dexterity}</li>
|
||||
<li>Attack: {user.Attack}</li>
|
||||
<li>Defense: {user.Defense}</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
@ -3,7 +3,6 @@
|
||||
{block "content"}
|
||||
<div class="town shop">
|
||||
<div class="title"><h3>{town.Name} Maps</h3></div>
|
||||
{error_message}
|
||||
<section>
|
||||
<p>Buying maps will put the town in your Travel To box, and it won't cost you as many TP to get there.</p>
|
||||
<p>Click a town name to purchase its map.</p>
|
||||
@ -16,7 +15,7 @@
|
||||
<td>
|
||||
{if map.Owned == false}
|
||||
{if user.Gold >= map.Cost}
|
||||
<a class="buy-item" data-item="{map.Name} Map" data-cost="{map.Cost}" href="/town/maps/buy/{map.ID}">{map.Name}</a>
|
||||
<a class="buy-item" data-town="{map.Name}" data-cost="{map.Cost}" href="/town/maps/buy/{map.ID}">{map.Name}</a>
|
||||
{else}
|
||||
<span class="light">{map.Name}</span>
|
||||
{/if}
|
||||
@ -44,76 +43,18 @@
|
||||
<section>
|
||||
<p>If you've changed your mind, you may also return back to <a href="/town">town</a>.</p>
|
||||
</section>
|
||||
|
||||
<div id="shop-modal" class="modal">
|
||||
<div class="content">
|
||||
<p id="msg">Are you sure you want to buy this item?</p>
|
||||
<div class="buttons">
|
||||
<button id="confirm" class="btn btn-primary">Buy Now</button>
|
||||
<button id="cancel" class="btn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block "scripts"}
|
||||
<script>
|
||||
class ShopModal {
|
||||
constructor() {
|
||||
this.modal = document.querySelector("#shop-modal")
|
||||
this.message = this.modal.querySelector("#msg")
|
||||
this.confirmBtn = this.modal.querySelector("#confirm")
|
||||
this.cancelBtn = this.modal.querySelector("#cancel")
|
||||
this.currentUrl = null
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
document.querySelectorAll('.buy-item').forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
this.show(link.href, link.dataset.item, link.dataset.cost)
|
||||
})
|
||||
})
|
||||
|
||||
this.confirmBtn.addEventListener('click', () => this.confirm())
|
||||
this.cancelBtn.addEventListener('click', () => this.hide())
|
||||
|
||||
this.modal.addEventListener('click', (e) => {
|
||||
if (e.target === this.modal) this.hide()
|
||||
})
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && this.modal.style.display === 'block') {
|
||||
this.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
show(url, itemName, itemCost) {
|
||||
this.currentUrl = url
|
||||
this.message.textContent = `Are you sure you want to buy ${itemName} for ${itemCost}G?`
|
||||
this.modal.style.display = 'block'
|
||||
this.confirmBtn.focus()
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal.style.display = 'none'
|
||||
this.currentUrl = null
|
||||
}
|
||||
|
||||
confirm() {
|
||||
if (this.currentUrl) {
|
||||
window.location.href = this.currentUrl
|
||||
}
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ShopModal()
|
||||
const shopModal = new ConfirmModal({
|
||||
linkSelector: '.buy-item',
|
||||
messageGenerator: (link) => `Are you sure you want to buy the map to ${link.dataset.town} for ${link.dataset.cost}G?`,
|
||||
confirmText: 'Buy Now',
|
||||
cancelText: 'Nevermind'
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{/block}
|
||||
|
@ -3,11 +3,10 @@
|
||||
{block "content"}
|
||||
<div class="town shop">
|
||||
<div class="title"><h3>{town.Name} Shop</h3></div>
|
||||
{error_message}
|
||||
<section>
|
||||
<p>Buying weapons will increase your Attack. Buying armor and shields will increase your Defense.</p>
|
||||
<p>Click an item name to purchase it.</p>
|
||||
<p>The following items are available at this town:</p>
|
||||
<p>The following items are available in {town.Name}:</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@ -38,7 +37,7 @@
|
||||
</td>
|
||||
<td>
|
||||
{item.Att}
|
||||
<span class="light">{if item.Type == 1}Attack{else}Defense{/if}</span>
|
||||
{if item.Type == 1}Attack{else}Defense{/if}
|
||||
</td>
|
||||
<td>
|
||||
{if user.WeaponID == item.ID or user.ArmorID == item.ID or user.ShieldID == item.ID}
|
||||
@ -59,76 +58,18 @@
|
||||
<section>
|
||||
<p>If you've changed your mind, you may also return back to <a href="/town">town</a>.</p>
|
||||
</section>
|
||||
|
||||
<div id="shop-modal" class="modal">
|
||||
<div class="content">
|
||||
<p id="msg">Are you sure you want to buy this item?</p>
|
||||
<div class="buttons">
|
||||
<button id="confirm" class="btn btn-primary">Buy Now</button>
|
||||
<button id="cancel" class="btn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block "scripts"}
|
||||
<script>
|
||||
class ShopModal {
|
||||
constructor() {
|
||||
this.modal = document.querySelector("#shop-modal")
|
||||
this.message = this.modal.querySelector("#msg")
|
||||
this.confirmBtn = this.modal.querySelector("#confirm")
|
||||
this.cancelBtn = this.modal.querySelector("#cancel")
|
||||
this.currentUrl = null
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
document.querySelectorAll('.buy-item').forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
this.show(link.href, link.dataset.item, link.dataset.cost)
|
||||
})
|
||||
})
|
||||
|
||||
this.confirmBtn.addEventListener('click', () => this.confirm())
|
||||
this.cancelBtn.addEventListener('click', () => this.hide())
|
||||
|
||||
this.modal.addEventListener('click', (e) => {
|
||||
if (e.target === this.modal) this.hide()
|
||||
})
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && this.modal.style.display === 'block') {
|
||||
this.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
show(url, itemName, itemCost) {
|
||||
this.currentUrl = url
|
||||
this.message.textContent = `Are you sure you want to buy ${itemName} for ${itemCost}G?`
|
||||
this.modal.style.display = 'block'
|
||||
this.confirmBtn.focus()
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal.style.display = 'none'
|
||||
this.currentUrl = null
|
||||
}
|
||||
|
||||
confirm() {
|
||||
if (this.currentUrl) {
|
||||
window.location.href = this.currentUrl
|
||||
}
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ShopModal()
|
||||
const shopModal = new ConfirmModal({
|
||||
linkSelector: '.buy-item',
|
||||
messageGenerator: (link) => `Are you sure you want to buy ${link.dataset.item} for ${link.dataset.cost}G?`,
|
||||
confirmText: 'Buy Now',
|
||||
cancelText: 'Nevermind'
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{/block}
|
||||
|
Loading…
x
Reference in New Issue
Block a user