
- Updated `.env` and added `.env.test` for environment variables. - Introduced API documentation in `API_DOCUMENTATION.md`. - Added authentication setup guide in `AUTHENTICATION_SETUP.md`. - Implemented user authentication with JWT and email verification. - Created new routes for user management and form submissions. - Added middleware for API key authentication and error handling. - Set up Redis for rate limiting and notifications. - Removed obsolete files and configurations related to the previous Rust implementation.
357 lines
12 KiB
Plaintext
357 lines
12 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>User Dashboard - Formies</title>
|
|
<!-- Basic styling - replace with a proper CSS framework or custom styles later -->
|
|
<style>
|
|
body {
|
|
font-family: sans-serif;
|
|
margin: 0;
|
|
background-color: #f4f7f6;
|
|
}
|
|
.navbar {
|
|
background-color: #333;
|
|
color: white;
|
|
padding: 1rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.navbar a {
|
|
color: white;
|
|
text-decoration: none;
|
|
margin-left: 1rem;
|
|
}
|
|
.navbar .logo {
|
|
font-size: 1.5rem;
|
|
font-weight: bold;
|
|
}
|
|
.container {
|
|
padding: 2rem;
|
|
}
|
|
.header-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
.header-bar h1 {
|
|
margin: 0;
|
|
}
|
|
.btn {
|
|
background-color: #007bff;
|
|
color: white;
|
|
padding: 0.5rem 1rem;
|
|
text-decoration: none;
|
|
border-radius: 0.25rem;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
.btn-secondary {
|
|
background-color: #6c757d;
|
|
}
|
|
/* Add more styles as needed */
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<nav class="navbar">
|
|
<div class="logo"><a href="/dashboard">Formies</a></div>
|
|
<div>
|
|
<a href="/dashboard">My Forms</a>
|
|
<a href="/dashboard/create-form">Create New Form</a>
|
|
<a href="/dashboard/api-keys">API Keys</a>
|
|
<!-- Placeholder for Account Settings -->
|
|
<a href="#">Hi, <%= user ? user.email : 'User' %> ▾</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="container">
|
|
<!-- Main content will be injected or decided by the route -->
|
|
<% if (view === 'my_forms') { %>
|
|
<div class="header-bar">
|
|
<h1>My Forms</h1>
|
|
<a href="/dashboard/create-form" class="btn">+ Create New Form</a>
|
|
</div>
|
|
<%- include('partials/_forms_table', { forms: forms, appUrl: appUrl }) %>
|
|
<% } else if (view === 'create_form') { %>
|
|
<h1>Create New Form</h1>
|
|
<form action="/dashboard/forms/create" method="POST">
|
|
<div>
|
|
<label for="formName">Form Name:</label>
|
|
<input
|
|
type="text"
|
|
id="formName"
|
|
name="formName"
|
|
value="<%= typeof formNameValue !== 'undefined' ? formNameValue : 'Untitled Form' %>"
|
|
required />
|
|
</div>
|
|
<button type="submit" class="btn">Create Form</button>
|
|
<% if (typeof error !== 'undefined' && error) { %>
|
|
<p style="color: red; margin-top: 1rem"><%= error %></p>
|
|
<% } %>
|
|
</form>
|
|
<% } else if (view === 'form_submissions') { %> <%-
|
|
include('partials/_submissions_view', { submissions: submissions,
|
|
formUuid: formUuid, formName: formName, pagination: pagination, appUrl:
|
|
appUrl }) %> <% } else if (view === 'account_settings') { %>
|
|
<h1>Account Settings</h1>
|
|
<p>Account settings will be here.</p>
|
|
<% } else if (view === 'form_settings') { %>
|
|
<div class="header-bar">
|
|
<h1>
|
|
Settings for <span style="font-weight: normal"><%= formName %></span>
|
|
</h1>
|
|
<a href="/dashboard" class="btn btn-secondary">Back to My Forms</a>
|
|
</div>
|
|
|
|
<% if (typeof successMessage !== 'undefined' && successMessage) { %>
|
|
<div
|
|
style="
|
|
background-color: #d4edda;
|
|
color: #155724;
|
|
padding: 0.75rem 1.25rem;
|
|
margin-bottom: 1rem;
|
|
border: 1px solid #c3e6cb;
|
|
border-radius: 0.25rem;
|
|
">
|
|
<%= successMessage %>
|
|
</div>
|
|
<% } %> <% if (typeof errorMessage !== 'undefined' && errorMessage) { %>
|
|
<div
|
|
style="
|
|
background-color: #f8d7da;
|
|
color: #721c24;
|
|
padding: 0.75rem 1.25rem;
|
|
margin-bottom: 1rem;
|
|
border: 1px solid #f5c6cb;
|
|
border-radius: 0.25rem;
|
|
">
|
|
<%= errorMessage %>
|
|
</div>
|
|
<% } %>
|
|
|
|
<form
|
|
action="/dashboard/forms/<%= formUuid %>/settings/update"
|
|
method="POST"
|
|
style="
|
|
background-color: white;
|
|
padding: 1.5rem;
|
|
border-radius: 0.25rem;
|
|
border: 1px solid #ddd;
|
|
margin-bottom: 2rem;
|
|
">
|
|
<h3 style="margin-top: 0; margin-bottom: 1rem">General Settings</h3>
|
|
<div style="margin-bottom: 1rem">
|
|
<label
|
|
for="formNameInput"
|
|
style="display: block; margin-bottom: 0.5rem"
|
|
>Form Name:</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="formNameInput"
|
|
name="formName"
|
|
value="<%= currentFormName %>"
|
|
required
|
|
style="
|
|
width: 100%;
|
|
padding: 0.5rem;
|
|
border: 1px solid #ccc;
|
|
border-radius: 0.25rem;
|
|
" />
|
|
</div>
|
|
|
|
<h3 style="margin-top: 2rem; margin-bottom: 1rem">Email Notifications</h3>
|
|
<div style="margin-bottom: 1rem">
|
|
<input type="checkbox" id="emailNotificationsEnabled"
|
|
name="emailNotificationsEnabled" value="on" <%=
|
|
currentEmailNotificationsEnabled ? 'checked' : '' %>
|
|
style="margin-right: 0.5rem;">
|
|
<label for="emailNotificationsEnabled"
|
|
>Enable Email Notifications for new submissions</label
|
|
>
|
|
</div>
|
|
<div style="margin-bottom: 1rem">
|
|
<label
|
|
for="notificationEmailAddress"
|
|
style="display: block; margin-bottom: 0.5rem"
|
|
>Notification Email Address:</label
|
|
>
|
|
<input
|
|
type="email"
|
|
id="notificationEmailAddress"
|
|
name="notificationEmailAddress"
|
|
value="<%= currentNotificationEmailAddress || '' %>"
|
|
placeholder="Defaults to your account email (<%= user.email %>)"
|
|
style="
|
|
width: 100%;
|
|
padding: 0.5rem;
|
|
border: 1px solid #ccc;
|
|
border-radius: 0.25rem;
|
|
" />
|
|
<small style="display: block; margin-top: 0.25rem; color: #6c757d"
|
|
>If left blank, notifications will be sent to your account email:
|
|
<%= user.email %></small
|
|
>
|
|
</div>
|
|
|
|
<h3 style="margin-top: 2rem; margin-bottom: 1rem">Spam Protection</h3>
|
|
<div style="margin-bottom: 1rem">
|
|
<input type="checkbox" id="recaptchaEnabled"
|
|
name="recaptchaEnabled" value="on" <%=
|
|
currentRecaptchaEnabled ? 'checked' : '' %>
|
|
style="margin-right: 0.5rem;">
|
|
<label for="recaptchaEnabled"
|
|
>Enable Google reCAPTCHA v2 ("I'm not a robot")</label
|
|
>
|
|
<small style="display: block; margin-top: 0.25rem; color: #6c757d"
|
|
>Uses the globally configured site and secret keys. Ensure these are set in your server's .env file.</small
|
|
>
|
|
</div>
|
|
|
|
<h3 style="margin-top: 2rem; margin-bottom: 1rem">Thank You Page</h3>
|
|
<div style="margin-bottom: 1rem;">
|
|
<label for="thankYouUrl" style="display: block; margin-bottom: 0.5rem;">Thank You URL (Optional):</label>
|
|
<input
|
|
type="url"
|
|
id="thankYouUrl"
|
|
name="thankYouUrl"
|
|
value="<%= currentThankYouUrl || '' %>"
|
|
placeholder="e.g., https://example.com/thank-you"
|
|
style="
|
|
width: 100%;
|
|
padding: 0.5rem;
|
|
border: 1px solid #ccc;
|
|
border-radius: 0.25rem;
|
|
" />
|
|
</div>
|
|
<div style="margin-bottom: 1rem;">
|
|
<label for="thankYouMessage" style="display: block; margin-bottom: 0.5rem;">Custom Thank You Message (Optional):</label>
|
|
<textarea
|
|
id="thankYouMessage"
|
|
name="thankYouMessage"
|
|
rows="3"
|
|
placeholder="e.g., Thanks for your submission!"
|
|
style="
|
|
width: 100%;
|
|
padding: 0.5rem;
|
|
border: 1px solid #ccc;
|
|
border-radius: 0.25rem;
|
|
"><%= currentThankYouMessage || '' %></textarea>
|
|
<small style="display: block; margin-top: 0.25rem; color: #6c757d;">
|
|
If a "Thank You URL" is provided, it will be used. Otherwise, this custom message will be shown. If both are blank, a default message is used.
|
|
</small>
|
|
</div>
|
|
|
|
<h3 style="margin-top: 2rem; margin-bottom: 1rem">Allowed Domains</h3>
|
|
<div style="margin-bottom: 1rem;">
|
|
<label for="allowedDomains" style="display: block; margin-bottom: 0.5rem;">Allowed Domains:</label>
|
|
<textarea
|
|
id="allowedDomains"
|
|
name="allowedDomains"
|
|
rows="3"
|
|
placeholder="e.g., example.com, app.example.com"
|
|
style="
|
|
width: 100%;
|
|
padding: 0.5rem;
|
|
border: 1px solid #ccc;
|
|
border-radius: 0.25rem;
|
|
"><%= currentAllowedDomains || '' %></textarea>
|
|
<small style="display: block; margin-top: 0.25rem; color: #6c757d;">
|
|
Comma-separated list of domains. Leave blank to allow submissions from any domain.
|
|
</small>
|
|
</div>
|
|
|
|
<button type="submit" class="btn" style="margin-top: 1.5rem;">Save All Settings</button>
|
|
</form>
|
|
|
|
<% } else if (view === 'api_keys') { %>
|
|
<div class="header-bar">
|
|
<h1>API Keys</h1>
|
|
</div>
|
|
|
|
<% if (typeof successMessage !== 'undefined' && successMessage) { %>
|
|
<div style="background-color: #d4edda; color: #155724; padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid #c3e6cb; border-radius: 0.25rem;">
|
|
<%= successMessage %>
|
|
</div>
|
|
<% } %>
|
|
<% if (typeof errorMessage !== 'undefined' && errorMessage) { %>
|
|
<div style="background-color: #f8d7da; color: #721c24; padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid #f5c6cb; border-radius: 0.25rem;">
|
|
<%= errorMessage %>
|
|
</div>
|
|
<% } %>
|
|
|
|
<% if (typeof newlyGeneratedApiKey !== 'undefined' && newlyGeneratedApiKey) { %>
|
|
<div style="background-color: #fff3cd; color: #856404; padding: 1rem; margin-bottom: 1.5rem; border: 1px solid #ffeeba; border-radius: 0.25rem;">
|
|
<h3 style="margin-top:0;">New API Key Generated: <%= newlyGeneratedApiKeyName %></h3>
|
|
<p><strong>Important:</strong> This is the only time you will see this API key. Copy it now and store it securely.</p>
|
|
<pre style="background-color: #e9ecef; padding: 0.5rem; border-radius: 0.25rem; word-break: break-all;"><code id="newApiKey"><%= newlyGeneratedApiKey %></code></pre>
|
|
<button onclick="copyApiKeyToClipboard()" class="btn btn-secondary" style="margin-top:0.5rem;">Copy to Clipboard</button>
|
|
</div>
|
|
<% } %>
|
|
|
|
<div style="background-color: white; padding: 1.5rem; border-radius: 0.25rem; border: 1px solid #ddd; margin-bottom: 2rem;">
|
|
<h3 style="margin-top: 0; margin-bottom: 1rem">Generate New API Key</h3>
|
|
<form action="/dashboard/api-keys/generate" method="POST">
|
|
<div style="margin-bottom: 1rem;">
|
|
<label for="keyName" style="display: block; margin-bottom: 0.5rem;">Key Name (for your reference):</label>
|
|
<input type="text" id="keyName" name="keyName" required style="width: 100%; padding: 0.5rem; border: 1px solid #ccc; border-radius: 0.25rem;">
|
|
</div>
|
|
<button type="submit" class="btn">Generate Key</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div style="background-color: white; padding: 1.5rem; border-radius: 0.25rem; border: 1px solid #ddd;">
|
|
<h3 style="margin-top: 0; margin-bottom: 1rem">Your API Keys</h3>
|
|
<% if (apiKeys && apiKeys.length > 0) { %>
|
|
<table style="width: 100%; border-collapse: collapse;">
|
|
<thead>
|
|
<tr style="text-align: left; border-bottom: 1px solid #dee2e6;">
|
|
<th style="padding: 0.75rem;">Name</th>
|
|
<th style="padding: 0.75rem;">Identifier (Prefix)</th>
|
|
<th style="padding: 0.75rem;">Created At</th>
|
|
<th style="padding: 0.75rem;">Last Used</th>
|
|
<th style="padding: 0.75rem;">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<% apiKeys.forEach(key => { %>
|
|
<tr style="border-bottom: 1px solid #f1f1f1;">
|
|
<td style="padding: 0.75rem;"><%= key.key_name %></td>
|
|
<td style="padding: 0.75rem;"><%= key.api_key_identifier %></td>
|
|
<td style="padding: 0.75rem;"><%= new Date(key.created_at).toLocaleDateString() %></td>
|
|
<td style="padding: 0.75rem;"><%= key.last_used_at ? new Date(key.last_used_at).toLocaleDateString() : 'Never' %></td>
|
|
<td style="padding: 0.75rem;">
|
|
<form action="/dashboard/api-keys/<%= key.uuid %>/revoke" method="POST" onsubmit="return confirm('Are you sure you want to revoke this API key? This cannot be undone.');" style="display: inline;">
|
|
<button type="submit" class="btn btn-secondary" style="background-color: #dc3545;">Revoke</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<% }) %>
|
|
</tbody>
|
|
</table>
|
|
<% } else { %>
|
|
<p>You have not generated any API keys yet.</p>
|
|
<% } %>
|
|
</div>
|
|
|
|
<% } %>
|
|
</div>
|
|
|
|
<script>
|
|
// Client-side JS for dynamic interactions will go here
|
|
function copyApiKeyToClipboard() {
|
|
const apiKeyText = document.getElementById('newApiKey').innerText;
|
|
navigator.clipboard.writeText(apiKeyText).then(() => {
|
|
alert('API Key copied to clipboard!');
|
|
}).catch(err => {
|
|
console.error('Failed to copy API key: ', err);
|
|
alert('Failed to copy API key. Please copy it manually.');
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|