WellNuo/web/accept-invite.html
Sergei f4ff281bcc Improve invitation UI and fix access removal
- Move role selector (Caretaker/Guardian) above email input in Access screen
- Remove "(view only)" suffix from Caretaker role in email templates
- Remove "expires in 7 days" text from invitation emails
- Remove expires_at field from invitation creation (invitations never expire)
- Fix deletion of accepted invitations (now also removes user_access record)
- Add favicon to accept-invite.html page
2026-01-04 10:28:36 -08:00

306 lines
9.2 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Accept Invitation - WellNuo</title>
<link rel="icon" type="image/png" href="/favicon.png">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
max-width: 400px;
width: 100%;
overflow: hidden;
}
.header {
background: #f8f9fa;
padding: 32px;
text-align: center;
border-bottom: 1px solid #eee;
}
.logo-img { width: 160px; height: auto; }
.content { padding: 32px; text-align: center; }
.btn {
width: 100%;
padding: 16px;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: linear-gradient(135deg, #4A90D9 0%, #357ABD 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(74, 144, 217, 0.4);
}
.btn-primary:disabled { opacity: 0.7; cursor: not-allowed; transform: none; }
.error-message {
background: #fee;
color: #c00;
padding: 12px;
border-radius: 8px;
font-size: 14px;
margin-bottom: 16px;
display: none;
}
.success-container { display: none; }
.success-icon {
width: 80px;
height: 80px;
background: #e8f5e9;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 24px;
}
.success-icon svg { width: 40px; height: 40px; color: #4caf50; }
.success-title { font-size: 24px; font-weight: 600; color: #333; margin-bottom: 12px; }
.success-text { color: #666; font-size: 15px; line-height: 1.5; margin-bottom: 16px; }
.success-info {
background: #f0f7ff;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
}
.role-badge {
display: inline-block;
padding: 6px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
margin-bottom: 20px;
}
.role-caretaker { background: #e3f2fd; color: #1976d2; }
.role-guardian { background: #fff3e0; color: #f57c00; }
.permissions-title {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
text-align: left;
}
.permissions-list {
text-align: left;
margin-bottom: 20px;
}
.permissions-list li {
font-size: 14px;
color: #555;
margin-bottom: 8px;
padding-left: 24px;
position: relative;
list-style: none;
}
.permissions-list li:before {
content: "✓";
position: absolute;
left: 0;
color: #4caf50;
font-weight: bold;
}
.next-step {
background: #f5f7fa;
border-radius: 12px;
padding: 16px;
margin-top: 20px;
}
.next-step-title {
font-size: 13px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.next-step-text {
font-size: 13px;
color: #666;
line-height: 1.5;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid rgba(255,255,255,0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s linear infinite;
margin-right: 8px;
vertical-align: middle;
}
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<div class="container">
<div class="header">
<img src="/logo.png" alt="WellNuo" class="logo-img">
</div>
<div class="content">
<!-- Loading Section -->
<div id="loadingSection">
<p style="color: #666;">Loading invitation...</p>
</div>
<!-- Accept Section -->
<div id="acceptSection" style="display: none;">
<div id="roleBadgeAccept" class="role-badge"></div>
<p class="success-text">
You've been invited to monitor a family member through WellNuo.
</p>
<div id="permissionsBlockAccept">
<div class="permissions-title">With this role you will be able to:</div>
<ul class="permissions-list" id="permissionsListAccept"></ul>
</div>
<div id="errorMessage" class="error-message"></div>
<button class="btn btn-primary" id="acceptBtn" onclick="acceptInvitation()">
Accept Invitation
</button>
</div>
<!-- Success Section -->
<div id="successSection" class="success-container">
<div class="success-icon">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
<h2 class="success-title">You're all set!</h2>
<p class="success-text">
Your invitation has been accepted. Download the WellNuo app to get started.
</p>
<a href="https://wellnuo.com" target="_blank" class="btn btn-primary" style="display: block; text-decoration: none; margin-top: 20px;">
Go to WellNuo.com
</a>
<p style="margin-top: 16px; font-size: 13px; color: #666;">
Sign in with the email address this invitation was sent to.
</p>
</div>
</div>
</div>
<script>
const API_BASE = 'https://wellnuo.smartlaunchhub.com/api';
// Get code from URL
const params = new URLSearchParams(window.location.search);
const inviteCode = params.get('code') || '';
// Load invitation info on page load
async function loadInvitationInfo() {
const loadingSection = document.getElementById('loadingSection');
const acceptSection = document.getElementById('acceptSection');
const errorMessage = document.getElementById('errorMessage');
if (!inviteCode) {
loadingSection.innerHTML = '<p style="color: #c00;">Invalid invitation link.</p>';
return;
}
try {
const response = await fetch(`${API_BASE}/invitations/info/${inviteCode}`);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Invalid invitation');
}
// Show role info before accepting
const isGuardian = data.role === 'guardian';
const roleBadge = document.getElementById('roleBadgeAccept');
const permissionsList = document.getElementById('permissionsListAccept');
if (isGuardian) {
roleBadge.textContent = 'Guardian';
roleBadge.classList.add('role-guardian');
permissionsList.innerHTML = `
<li>View real-time health data and activity status</li>
<li>Receive alerts and notifications about health changes</li>
<li>Access daily, weekly, and monthly health reports</li>
<li>Manage device settings and alert preferences</li>
<li>Invite other family members to join as Caretakers</li>
<li>Update beneficiary profile information</li>
`;
} else {
roleBadge.textContent = 'Caretaker';
roleBadge.classList.add('role-caretaker');
permissionsList.innerHTML = `
<li>View real-time health data and activity status</li>
<li>Receive alerts and notifications about health changes</li>
<li>Access daily, weekly, and monthly health reports</li>
<li>Check in on your loved one's wellbeing anytime</li>
`;
}
// Show accept section
loadingSection.style.display = 'none';
acceptSection.style.display = 'block';
} catch (error) {
loadingSection.innerHTML = `<p style="color: #c00;">${error.message}</p>`;
}
}
// Load info on page load
loadInvitationInfo();
async function acceptInvitation() {
const btn = document.getElementById('acceptBtn');
const errorEl = document.getElementById('errorMessage');
btn.disabled = true;
btn.innerHTML = '<span class="loading"></span>Accepting...';
errorEl.style.display = 'none';
try {
const response = await fetch(`${API_BASE}/invitations/accept-public`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: inviteCode })
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to accept invitation');
}
// Show success
document.getElementById('acceptSection').style.display = 'none';
document.getElementById('successSection').style.display = 'block';
} catch (error) {
errorEl.textContent = error.message;
errorEl.style.display = 'block';
btn.disabled = false;
btn.innerHTML = 'Accept Invitation';
}
}
</script>
</body>
</html>