astrapay / templates /home.html
GamerC0der's picture
Update templates/home.html
e72423b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AstraPay</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Erica+One&display=swap" rel="stylesheet">
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<script src="https://cdn.jsdelivr.net/npm/turndown@7.1.3/dist/turndown.js"></script>
<style>
body {
margin: 0;
padding: 0;
min-height: 100vh;
background: linear-gradient(180deg,
#0f0514 0%,
#1a0a1a 15%,
#2d1a3d 35%,
#4a2a5a 50%,
#3d2a4a 60%,
#2d1a3d 70%,
#1a0a1a 80%,
#0f0514 85%,
#000000 100%
);
background-attachment: fixed;
font-family: Arial, sans-serif;
}
.main-content {
margin-top: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.container {
text-align: center;
}
.title {
color: #ffffff;
font-size: 4rem;
font-weight: normal;
font-family: 'Erica One', cursive;
text-shadow: 2px 2px 4px rgba(255, 255, 255, 0.3);
margin-bottom: 2rem;
letter-spacing: 0.02em;
}
.button {
background-color: #ffffff;
color: #000000;
border: none;
padding: 1rem 2rem;
font-size: 1.2rem;
font-weight: bold;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
}
.button:hover {
background-color: #f0f0f0;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(255, 255, 255, 0.2);
}
.modal {
display: none;
position: fixed;
z-index: 50;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
animation: fadeIn 0.15s ease-out;
}
.modal.show {
display: flex;
align-items: center;
justify-content: center;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
.modal-content {
background-color: #1a1a1a;
position: relative;
padding: 0;
border-radius: 12px;
width: 90%;
max-width: 500px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
animation: slideIn 0.2s ease-out;
display: flex;
flex-direction: column;
max-height: 90vh;
overflow: hidden;
}
.modal-header {
color: #ffffff;
padding: 1.5rem 1.5rem 0 1.5rem;
margin-bottom: 1.5rem;
font-size: 1.25rem;
font-weight: 600;
letter-spacing: -0.01em;
line-height: 1.4;
}
.modal-close {
position: absolute;
right: 1rem;
top: 1rem;
background: transparent;
border: none;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
padding: 0.5rem;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
width: 32px;
height: 32px;
}
.modal-close:hover {
background-color: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 1);
}
.modal-close:focus {
outline: 2px solid rgba(255, 255, 255, 0.5);
outline-offset: 2px;
}
.modal-close-icon {
width: 16px;
height: 16px;
stroke: currentColor;
stroke-width: 2;
fill: none;
}
.modal-body {
padding: 0 1.5rem 1.5rem 1.5rem;
overflow-y: auto;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-label {
display: block;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 0.5rem;
font-weight: 500;
font-size: 0.875rem;
}
.form-input {
width: 100%;
padding: 0.625rem 0.875rem;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
background-color: rgba(255, 255, 255, 0.05);
color: #ffffff;
font-size: 0.9375rem;
transition: all 0.2s ease;
box-sizing: border-box;
}
.form-input:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.3);
background-color: rgba(255, 255, 255, 0.08);
}
.form-input::placeholder {
color: rgba(255, 255, 255, 0.4);
}
.form-input-email {
width: 100%;
padding: 0.625rem 0.875rem;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.06);
color: #ffffff;
font-size: 0.9375rem;
transition: all 0.3s ease;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
}
.form-input-email:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.4);
background-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1);
}
.form-input-email::placeholder {
color: rgba(255, 255, 255, 0.5);
font-style: italic;
}
.form-input-email:hover:not(:focus) {
border-color: rgba(255, 255, 255, 0.25);
background-color: rgba(255, 255, 255, 0.08);
}
.form-textarea {
width: 100%;
padding: 0.625rem 0.875rem;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
background-color: rgba(255, 255, 255, 0.05);
color: #ffffff;
font-size: 0.9375rem;
min-height: 100px;
resize: vertical;
transition: all 0.2s ease;
box-sizing: border-box;
font-family: inherit;
}
.form-textarea:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.3);
background-color: rgba(255, 255, 255, 0.08);
}
.form-textarea::placeholder {
color: rgba(255, 255, 255, 0.4);
}
#description-editor-container {
margin-top: 0.5rem;
}
#description-editor-container .ql-container {
background-color: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0 0 6px 6px;
color: #ffffff;
font-family: inherit;
font-size: 0.9375rem;
}
#description-editor-container .ql-toolbar {
background-color: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px 6px 0 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
#description-editor-container .ql-toolbar .ql-stroke {
stroke: rgba(255, 255, 255, 0.7);
}
#description-editor-container .ql-toolbar .ql-fill {
fill: rgba(255, 255, 255, 0.7);
}
#description-editor-container .ql-toolbar button:hover,
#description-editor-container .ql-toolbar button.ql-active {
background-color: rgba(255, 255, 255, 0.1);
}
#description-editor-container .ql-toolbar button:hover .ql-stroke,
#description-editor-container .ql-toolbar button.ql-active .ql-stroke {
stroke: #ffffff;
}
#description-editor-container .ql-toolbar button:hover .ql-fill,
#description-editor-container .ql-toolbar button.ql-active .ql-fill {
fill: #ffffff;
}
#description-editor-container .ql-editor {
color: #ffffff;
min-height: 150px;
}
#description-editor-container .ql-editor.ql-blank::before {
color: rgba(255, 255, 255, 0.4);
font-style: italic;
}
#description-editor-container .ql-snow .ql-picker {
color: rgba(255, 255, 255, 0.7);
}
#description-editor-container .ql-snow .ql-picker-options {
background-color: rgba(30, 30, 30, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
}
#description-editor-container .ql-snow .ql-picker-item {
color: rgba(255, 255, 255, 0.7);
}
#description-editor-container .ql-snow .ql-picker-item:hover {
background-color: rgba(255, 255, 255, 0.1);
color: #ffffff;
}
#description-editor-container .ql-snow .ql-stroke {
stroke: rgba(255, 255, 255, 0.7);
}
#description-editor-container .ql-snow .ql-fill {
fill: rgba(255, 255, 255, 0.7);
}
.word-counter {
margin-top: 0.25rem;
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.6);
text-align: right;
font-weight: 400;
}
.modal-buttons {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.btn-secondary {
background-color: transparent;
color: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(255, 255, 255, 0.2);
padding: 0.625rem 1.25rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.9375rem;
font-weight: 500;
transition: all 0.2s ease;
}
.btn-secondary:hover {
background-color: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
}
.button {
background-color: #ffffff;
color: #000000;
border: none;
padding: 0.625rem 1.25rem;
font-size: 0.9375rem;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
display: inline-block;
}
.button:hover {
background-color: #f0f0f0;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.15);
}
.top-banner {
background: rgba(100, 100, 255, 0.15);
border: 1px solid rgba(100, 100, 255, 0.3);
border-radius: 8px;
padding: 0.75rem 1.5rem;
margin-bottom: 2rem;
color: #6464ff;
font-size: 0.9rem;
font-weight: 500;
text-align: center;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
.top-page-banner {
background: rgba(100, 100, 255, 0.15);
border-bottom: 1px solid rgba(100, 100, 255, 0.3);
padding: 0.75rem 1.5rem;
color: #6464ff;
font-size: 0.9rem;
font-weight: 500;
text-align: center;
width: 100%;
}
.banner {
background: rgba(255, 193, 7, 0.15);
border: 1px solid rgba(255, 193, 7, 0.3);
border-radius: 8px;
padding: 0.75rem 1.5rem;
margin-bottom: 2rem;
color: #ffc107;
font-size: 0.9rem;
font-weight: 500;
text-align: center;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
.toast-container {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
z-index: 100;
display: flex;
flex-direction: column;
gap: 0.75rem;
pointer-events: none;
}
.toast {
background-color: #1a1a1a;
color: #ffffff;
padding: 0.875rem 1rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
min-width: 300px;
max-width: 400px;
display: flex;
align-items: center;
gap: 0.75rem;
pointer-events: auto;
animation: toastSlideIn 0.3s ease-out;
font-size: 0.9375rem;
}
.toast.error {
border-color: rgba(239, 68, 68, 0.3);
background-color: rgba(239, 68, 68, 0.1);
}
.toast.success {
border-color: rgba(34, 197, 94, 0.3);
background-color: rgba(34, 197, 94, 0.1);
}
@keyframes toastSlideIn {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes toastSlideOut {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(100%);
}
}
.toast.hiding {
animation: toastSlideOut 0.2s ease-in forwards;
}
.toast-icon {
flex-shrink: 0;
width: 20px;
height: 20px;
}
.toast-message {
flex: 1;
line-height: 1.5;
}
.toast-close {
background: transparent;
border: none;
color: rgba(255, 255, 255, 0.6);
cursor: pointer;
padding: 0.25rem;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
flex-shrink: 0;
}
.toast-close:hover {
background-color: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 1);
}
.toast-close-icon {
width: 16px;
height: 16px;
stroke: currentColor;
stroke-width: 2;
fill: none;
}
.bottom-notification {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
max-width: 300px;
opacity: 1;
transition: opacity 0.3s ease;
}
.bottom-notification.fade-out {
opacity: 0;
pointer-events: none;
}
.notification-content {
background: rgba(0, 0, 0, 0.8);
color: #ffffff;
padding: 12px 16px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
font-size: 0.9rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.close-btn {
background: none;
border: none;
color: #ffffff;
cursor: pointer;
padding: 2px;
border-radius: 4px;
transition: background-color 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.close-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
</style>
</head>
<body>
<div class="top-page-banner">
To send astras to AstraPay, please friend astrapay@astranova.org, your friend request will be accepted instantly.
</div>
<div class="main-content">
<div class="container">
<h1 class="title">AstraPay</h1>
<div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
<button class="button" onclick="openModal()" style="display: flex; align-items: center; gap: 0.5rem;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect>
<line x1="1" y1="10" x2="23" y2="10"></line>
</svg>
Create Payment Link
</button>
<button class="button" onclick="openClaimModal()" style="background-color: rgba(255, 255, 255, 0.9); display: flex; align-items: center; gap: 0.5rem;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 12 20 22 4 22 4 12"></polyline>
<rect x="2" y="7" width="20" height="5"></rect>
<line x1="12" y1="22" x2="12" y2="7"></line>
<path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path>
<path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path>
</svg>
Create Claim Link
</button>
<button class="button" onclick="openEnterIdModal()" style="background-color: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.3); color: #ffffff; display: flex; align-items: center; gap: 0.5rem;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 21l-6-6m2-5a7 7 0 1 1-14 0 7 7 0 0 1 14 0z"></path>
</svg>
Enter ID
</button>
</div>
</div>
<div id="paymentModal" class="modal">
<div class="modal-content">
<button class="modal-close" onclick="closeModal()" aria-label="Close">
<svg class="modal-close-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div class="modal-header">Create Payment Link</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label" for="astras">Number of Astras</label>
<input type="number" id="astras" class="form-input" min="1" max="10000" placeholder="Enter number of astras" required oninput="validateAstrasAmount(this)">
</div>
<div class="form-group">
<label class="form-label" for="description">Description</label>
<textarea id="description" class="form-textarea" placeholder="Enter payment description" required></textarea>
<div id="description-editor-container"></div>
<div id="word-counter" class="word-counter">0 words</div>
</div>
<div class="form-group">
<label class="form-label" for="recipient-email">Your AstraNova Email</label>
<input type="email" id="recipient-email" class="form-input-email" placeholder="Enter your AstraNova email" required>
</div>
<div class="modal-buttons">
<button class="btn-secondary" onclick="closeModal()" style="display: flex; align-items: center; gap: 0.5rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
Cancel
</button>
<button class="button" onclick="createPaymentLink()" style="display: flex; align-items: center; gap: 0.5rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
Create Link
</button>
</div>
</div>
</div>
</div>
<div id="claimModal" class="modal">
<div class="modal-content">
<button class="modal-close" onclick="closeClaimModal()" aria-label="Close">
<svg class="modal-close-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div class="modal-header">Create Claim Link</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label" for="claim-astras">Number of Astras</label>
<input type="number" id="claim-astras" class="form-input" min="1" max="10000" placeholder="Enter number of astras" required oninput="validateClaimAstrasAmount(this)">
<div style="font-size: 0.8rem; color: rgba(255, 255, 255, 0.6); margin-top: 0.5rem;">
You'll send: <span id="claim-send-amount">-</span> Astras (<span id="claim-fee-percent">1</span>%)<br>
Claimable: <span id="claim-claim-amount">-</span> Astras<br>
Fee: <span id="claim-fee-amount">-</span> Astras
</div>
</div>
<div class="modal-buttons">
<button class="btn-secondary" onclick="closeClaimModal()" style="display: flex; align-items: center; gap: 0.5rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
Cancel
</button>
<button class="button" onclick="createClaimLink()" style="display: flex; align-items: center; gap: 0.5rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 12 20 22 4 22 4 12"></polyline>
<rect x="2" y="7" width="20" height="5"></rect>
<line x1="12" y1="22" x2="12" y2="7"></line>
<path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path>
<path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path>
</svg>
Create Claim Link
</button>
</div>
</div>
</div>
</div>
<div id="enterIdModal" class="modal">
<div class="modal-content">
<button class="modal-close" onclick="closeEnterIdModal()" aria-label="Close">
<svg class="modal-close-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div class="modal-header">Enter Link ID</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label" for="link-id">Enter Link ID</label>
<input type="text" id="link-id" class="form-input" placeholder="e.g., 550e8400-e29b-41d4-a716-446655440000" maxlength="36" style="font-size: 1rem; text-align: center;" required onkeypress="if(event.key === 'Enter') goToLink()">
</div>
<div class="modal-buttons">
<button class="btn-secondary" onclick="closeEnterIdModal()" style="display: flex; align-items: center; gap: 0.5rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
Cancel
</button>
<button class="button" onclick="goToLink()" style="display: flex; align-items: center; gap: 0.5rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
Go to Link
</button>
</div>
</div>
</div>
</div>
<div id="toastContainer" class="toast-container"></div>
<script>
let descriptionEditor = null;
function initDescriptionEditor() {
if (descriptionEditor) {
const container = document.getElementById('description-editor-container');
container.innerHTML = '';
descriptionEditor = null;
}
const textarea = document.getElementById('description');
textarea.style.display = 'none';
const container = document.getElementById('description-editor-container');
container.innerHTML = '<div id="quill-editor"></div>';
descriptionEditor = new Quill('#quill-editor', {
theme: 'snow',
placeholder: 'Enter payment description',
modules: {
toolbar: [
[{ 'header': [1, 2, 3, false] }],
['bold', 'italic', 'underline']
]
}
});
const quillContainer = document.querySelector('#description-editor-container .ql-container');
if (quillContainer) {
quillContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
quillContainer.style.borderColor = 'rgba(255, 255, 255, 0.1)';
}
function updateWordCount() {
if (!descriptionEditor) return;
const text = descriptionEditor.getText();
const words = text.trim() === '' ? 0 : text.trim().split(/\s+/).length;
const counter = document.getElementById('word-counter');
if (counter) {
counter.textContent = words + ' word' + (words !== 1 ? 's' : '');
}
}
descriptionEditor.on('text-change', updateWordCount);
updateWordCount();
}
function getQuillMarkdown() {
if (!descriptionEditor) return '';
try {
const html = descriptionEditor.root.innerHTML;
if (!html || html === '<p><br></p>' || html.trim() === '') return '';
const text = descriptionEditor.getText();
if (!text || text.trim() === '') return '';
if (typeof TurndownService === 'undefined') {
return text.trim();
}
const turndownService = new TurndownService({
headingStyle: 'atx',
codeBlockStyle: 'fenced',
emDelimiter: '*',
strongDelimiter: '**',
bulletListMarker: '-',
linkStyle: 'inlined'
});
turndownService.addRule('underline', {
filter: ['u'],
replacement: function(content) {
return '__' + content + '__';
}
});
turndownService.addRule('strikethrough', {
filter: ['del', 's', 'strike'],
replacement: function(content) {
return '~~' + content + '~~';
}
});
const markdown = turndownService.turndown(html);
return markdown && typeof markdown === 'string' ? markdown.trim() : text.trim();
} catch (error) {
console.error('Error converting to markdown:', error);
if (descriptionEditor) {
const text = descriptionEditor.getText();
return text ? text.trim() : '';
}
return '';
}
}
function showToast(message, type = 'error') {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
const iconSvg = type === 'error'
? '<svg class="toast-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
: '<svg class="toast-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
toast.innerHTML = `
${iconSvg}
<span class="toast-message">${message}</span>
<button class="toast-close" onclick="this.parentElement.remove()" aria-label="Close">
<svg class="toast-close-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
`;
container.appendChild(toast);
setTimeout(() => {
toast.classList.add('hiding');
setTimeout(() => {
if (toast.parentElement) {
toast.remove();
}
}, 200);
}, 4000);
}
function validateAstrasAmount(input) {
const value = parseInt(input.value);
if (!isNaN(value) && value > 10000) {
input.value = 10000;
showToast('Maximum 10,000 Astras per payment link');
}
}
function openModal() {
const modal = document.getElementById('paymentModal');
modal.style.display = 'flex';
setTimeout(() => {
modal.classList.add('show');
}, 10);
document.body.style.overflow = 'hidden';
setTimeout(() => {
initDescriptionEditor();
}, 50);
}
function closeModal() {
const modal = document.getElementById('paymentModal');
modal.classList.remove('show');
setTimeout(() => {
modal.style.display = 'none';
}, 200);
document.body.style.overflow = '';
document.getElementById('astras').value = '';
document.getElementById('recipient-email').value = '';
if (descriptionEditor) {
const container = document.getElementById('description-editor-container');
container.innerHTML = '';
descriptionEditor = null;
}
const counter = document.getElementById('word-counter');
if (counter) {
counter.textContent = '0 words';
}
const textarea = document.getElementById('description');
textarea.value = '';
textarea.style.display = 'block';
}
async function createPaymentLink() {
const astras = document.getElementById('astras').value;
let description = '';
if (descriptionEditor) {
const markdown = getQuillMarkdown();
description = markdown && typeof markdown === 'string' ? markdown.trim() : '';
if (!description) {
const text = descriptionEditor.getText();
description = text ? text.trim() : '';
}
} else {
description = document.getElementById('description').value.trim();
}
const recipientEmail = document.getElementById('recipient-email').value.trim();
if (!astras) {
showToast('Please enter the number of Astras');
document.getElementById('astras').focus();
return;
}
const astrasAmount = parseInt(astras);
if (astrasAmount > 10000) {
showToast('Maximum 10,000 Astras per payment link');
document.getElementById('astras').focus();
return;
}
if (!description) {
showToast('Please enter a description');
if (descriptionEditor) {
descriptionEditor.focus();
} else {
document.getElementById('description').focus();
}
return;
}
if (!recipientEmail) {
showToast('Please enter your AstraNova email');
document.getElementById('recipient-email').focus();
return;
}
try {
const response = await fetch('/create-payment-link', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: astrasAmount,
description: description.trim(),
recipient_email: recipientEmail
})
});
const data = await response.json();
if (response.ok) {
showToast('Payment link created successfully!', 'success');
setTimeout(() => {
top.location.href = data.payment_url;
}, 500);
} else {
showToast('Error creating payment link: ' + data.error);
}
} catch (error) {
showToast('Error creating payment link: ' + error.message);
}
}
function openClaimModal() {
const modal = document.getElementById('claimModal');
modal.style.display = 'flex';
setTimeout(() => {
modal.classList.add('show');
}, 10);
document.body.style.overflow = 'hidden';
}
function closeClaimModal() {
const modal = document.getElementById('claimModal');
modal.classList.remove('show');
setTimeout(() => {
modal.style.display = 'none';
}, 200);
document.body.style.overflow = '';
document.getElementById('claim-astras').value = '';
document.getElementById('claim-send-amount').textContent = '-';
document.getElementById('claim-claim-amount').textContent = '-';
document.getElementById('claim-fee-percent').textContent = '1';
document.getElementById('claim-fee-percent-negative').textContent = '-1';
document.getElementById('claim-fee-amount').textContent = '-';
}
function validateClaimAstrasAmount(input) {
const value = parseInt(input.value);
if (!isNaN(value) && value > 0) {
const fee = value > 100 ? 2 : 1;
const sendAmount = value + fee;
const claimAmount = value;
document.getElementById('claim-send-amount').textContent = sendAmount;
document.getElementById('claim-claim-amount').textContent = claimAmount;
document.getElementById('claim-fee-percent').textContent = fee;
document.getElementById('claim-fee-percent-negative').textContent = '-' + fee;
document.getElementById('claim-fee-amount').textContent = fee;
} else {
document.getElementById('claim-send-amount').textContent = '-';
document.getElementById('claim-claim-amount').textContent = '-';
document.getElementById('claim-fee-percent').textContent = '1';
document.getElementById('claim-fee-percent-negative').textContent = '-1';
document.getElementById('claim-fee-amount').textContent = '-';
}
if (!isNaN(value) && value > 10000) {
input.value = 10000;
showToast('Maximum 10,000 Astras per claim link');
}
}
async function createClaimLink() {
const astras = document.getElementById('claim-astras').value;
if (!astras) {
showToast('Please enter the number of Astras');
document.getElementById('claim-astras').focus();
return;
}
const astrasAmount = parseInt(astras);
if (astrasAmount > 10000) {
showToast('Maximum 10,000 Astras per claim link');
document.getElementById('claim-astras').focus();
return;
}
try {
const response = await fetch('/create-claim-link', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: astrasAmount
})
});
const data = await response.json();
if (response.ok) {
showToast('Claim link created successfully!', 'success');
setTimeout(() => {
top.location.href = data.claim_url;
}, 500);
} else {
showToast('Error creating claim link: ' + data.error);
}
} catch (error) {
showToast('Error creating claim link: ' + error.message);
}
}
function openEnterIdModal() {
const modal = document.getElementById('enterIdModal');
modal.style.display = 'flex';
setTimeout(() => {
modal.classList.add('show');
}, 10);
document.body.style.overflow = 'hidden';
document.getElementById('link-id').focus();
}
function closeEnterIdModal() {
const modal = document.getElementById('enterIdModal');
modal.classList.remove('show');
setTimeout(() => {
modal.style.display = 'none';
}, 200);
document.body.style.overflow = '';
document.getElementById('link-id').value = '';
}
async function goToLink() {
const linkId = document.getElementById('link-id').value.trim();
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!linkId || !uuidRegex.test(linkId)) {
showToast('Please enter a valid Link ID');
document.getElementById('link-id').focus();
return;
}
try {
const response = await fetch(`/check-link/${linkId}`);
const data = await response.json();
if (response.ok && data.exists) {
if (data.type === 'payment') {
top.location.href = `/pay/${linkId}`;
} else if (data.type === 'claim') {
top.location.href = `/claim/${linkId}`;
}
} else {
showToast('Link not found. Please check the code and try again.');
document.getElementById('link-id').focus();
}
} catch (error) {
showToast('Error checking link: ' + error.message);
}
}
window.onclick = function(event) {
const paymentModal = document.getElementById('paymentModal');
const claimModal = document.getElementById('claimModal');
const enterIdModal = document.getElementById('enterIdModal');
if (event.target === paymentModal) {
closeModal();
}
if (event.target === claimModal) {
closeClaimModal();
}
if (event.target === enterIdModal) {
closeEnterIdModal();
}
}
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
const paymentModal = document.getElementById('paymentModal');
const claimModal = document.getElementById('claimModal');
const enterIdModal = document.getElementById('enterIdModal');
if (paymentModal.classList.contains('show')) {
closeModal();
}
if (claimModal.classList.contains('show')) {
closeClaimModal();
}
if (enterIdModal.classList.contains('show')) {
closeEnterIdModal();
}
}
});
function closeNotification() {
const notification = document.getElementById('slack-notification');
notification.classList.add('fade-out');
setTimeout(() => {
notification.style.display = 'none';
}, 300);
}
</script>
<div id="slack-notification" class="bottom-notification">
<div class="notification-content">
Contact Matthew on Slack if any issues occur.
<button class="close-btn" onclick="closeNotification()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
</body>
</html>