Improve invitation acceptance flow
- Add GET /api/invitations/info/:code endpoint to fetch role before accepting - Show role and permissions on accept page BEFORE clicking Accept - Simplify success page: remove permissions list, add link to wellnuo.com - Minimalist design: light header background, logo only
This commit is contained in:
parent
d0c4930d38
commit
d9fcdf1751
@ -23,6 +23,56 @@ function authMiddleware(req, res, next) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUBLIC: Get invitation info without auth
|
||||
* GET /api/invitations/info/:code
|
||||
* Used to show role info before accepting
|
||||
*/
|
||||
router.get('/info/:code', async (req, res) => {
|
||||
try {
|
||||
const code = req.params.code;
|
||||
|
||||
if (!code) {
|
||||
return res.status(400).json({ error: 'Code is required' });
|
||||
}
|
||||
|
||||
// Find invitation by code
|
||||
const formattedCode = code.toUpperCase().replace(/-/g, '').replace(/(.{3})/g, '$1-').slice(0, 11);
|
||||
|
||||
let { data: invitation } = await supabase
|
||||
.from('invitations')
|
||||
.select('role, accepted_at')
|
||||
.eq('token', formattedCode)
|
||||
.single();
|
||||
|
||||
// Try without formatting
|
||||
if (!invitation) {
|
||||
const { data: inv2 } = await supabase
|
||||
.from('invitations')
|
||||
.select('role, accepted_at')
|
||||
.eq('token', code.toUpperCase())
|
||||
.single();
|
||||
invitation = inv2;
|
||||
}
|
||||
|
||||
if (!invitation) {
|
||||
return res.status(404).json({ error: 'Invitation not found' });
|
||||
}
|
||||
|
||||
if (invitation.accepted_at) {
|
||||
return res.status(400).json({ error: 'This invitation has already been accepted' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
role: invitation.role
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('[INVITE] Get info error:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PUBLIC: Accept invitation without auth
|
||||
* POST /api/invitations/accept-public
|
||||
|
||||
@ -153,8 +153,24 @@
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- Loading Section -->
|
||||
<div id="loadingSection">
|
||||
<p style="color: #666;">Loading invitation...</p>
|
||||
</div>
|
||||
|
||||
<!-- Accept Section -->
|
||||
<div id="acceptSection">
|
||||
<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()">
|
||||
@ -171,23 +187,17 @@
|
||||
</div>
|
||||
<h2 class="success-title">You're all set!</h2>
|
||||
|
||||
<div id="roleBadge" class="role-badge"></div>
|
||||
|
||||
<p class="success-text">
|
||||
You have been granted access to monitor a family member through WellNuo.
|
||||
Your invitation has been accepted. Download the WellNuo app to get started.
|
||||
</p>
|
||||
|
||||
<div id="permissionsBlock">
|
||||
<div class="permissions-title">With your role you can:</div>
|
||||
<ul class="permissions-list" id="permissionsList"></ul>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div class="next-step">
|
||||
<div class="next-step-title">What's next?</div>
|
||||
<div class="next-step-text">
|
||||
Download the WellNuo app and sign in with the email address this invitation was sent to. You'll see the beneficiary in your dashboard.
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
@ -199,38 +209,29 @@
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const inviteCode = params.get('code') || '';
|
||||
|
||||
// Show error if no 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) {
|
||||
document.getElementById('errorMessage').textContent = 'Invalid invitation link.';
|
||||
document.getElementById('errorMessage').style.display = 'block';
|
||||
document.getElementById('acceptBtn').disabled = true;
|
||||
loadingSection.innerHTML = '<p style="color: #c00;">Invalid invitation link.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
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 response = await fetch(`${API_BASE}/invitations/info/${inviteCode}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to accept invitation');
|
||||
throw new Error(data.error || 'Invalid invitation');
|
||||
}
|
||||
|
||||
// Show success with role-specific permissions
|
||||
// Show role info before accepting
|
||||
const isGuardian = data.role === 'guardian';
|
||||
const roleBadge = document.getElementById('roleBadge');
|
||||
const permissionsList = document.getElementById('permissionsList');
|
||||
const roleBadge = document.getElementById('roleBadgeAccept');
|
||||
const permissionsList = document.getElementById('permissionsListAccept');
|
||||
|
||||
if (isGuardian) {
|
||||
roleBadge.textContent = 'Guardian';
|
||||
@ -254,6 +255,40 @@
|
||||
`;
|
||||
}
|
||||
|
||||
// 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';
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user