first commit
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<section>
|
||||
<div class="flex between center">
|
||||
<h1>Admin · Tickets</h1>
|
||||
<div class="flex center">
|
||||
<a class="btn" href="/admin/users">Manage Users</a>
|
||||
<form method="get" action="/admin" class="filters">
|
||||
<label>Status
|
||||
<select name="status" onchange="this.form.submit()">
|
||||
<option value="" <%= !status ? 'selected':'' %>>All</option>
|
||||
<option value="open" <%= status==='open'?'selected':'' %>>open</option>
|
||||
<option value="answered" <%= status==='answered'?'selected':'' %>>answered</option>
|
||||
<option value="closed" <%= status==='closed'?'selected':'' %>>closed</option>
|
||||
</select>
|
||||
</label>
|
||||
<noscript><button type="submit" class="btn">Apply</button></noscript>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (!tickets || tickets.length === 0) { %>
|
||||
<p>No tickets.</p>
|
||||
<% } else { %>
|
||||
<div class="table">
|
||||
<div class="row head">
|
||||
<div>Subject</div>
|
||||
<div>User</div>
|
||||
<div>Status</div>
|
||||
<div>Updated</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<% tickets.forEach(t => { %>
|
||||
<div class="row">
|
||||
<div><strong><%= t.subject %></strong></div>
|
||||
<div class="muted small"><%= t.user_name || '' %> <%= t.user_email %></div>
|
||||
<div><span class="badge <%= t.status %>"><%= t.status %></span></div>
|
||||
<div class="muted small"><%= t.updated_at || t.created_at %></div>
|
||||
<div class="actions">
|
||||
<a class="btn" href="/tickets/<%= t.id %>">View</a>
|
||||
<form method="post" action="/admin/tickets/<%= t.id %>/delete" style="display:inline" onsubmit="return confirm('Delete this ticket? This cannot be undone.')">
|
||||
<button type="submit" class="btn danger">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
</div>
|
||||
<% } %>
|
||||
</section>
|
||||
@@ -0,0 +1,102 @@
|
||||
<section>
|
||||
<div class="flex between center">
|
||||
<h1>Admin · Users</h1>
|
||||
<a class="btn" href="/admin">Tickets</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Grant admin by email</h3>
|
||||
<form method="post" action="/admin/users/grant-admin" class="flex">
|
||||
<label style="flex:1">Name (optional)
|
||||
<input type="text" name="name" placeholder="Name" />
|
||||
</label>
|
||||
<label style="flex:1">Email
|
||||
<input type="email" name="email" required placeholder="user@example.com" />
|
||||
</label>
|
||||
<div style="align-self:flex-end">
|
||||
<button type="submit" class="btn primary">Grant admin</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<% if (!users || users.length === 0) { %>
|
||||
<p>No users found.</p>
|
||||
<% } else { %>
|
||||
<div class="table">
|
||||
<div class="row head">
|
||||
<div>Name</div>
|
||||
<div>Email · Auth</div>
|
||||
<div>Role</div>
|
||||
<div>Created</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<% users.forEach(u => { %>
|
||||
<div class="row">
|
||||
<div><%= (u.name && u.name.trim()) ? u.name : '—' %></div>
|
||||
<div class="small muted">
|
||||
<%= u.email || '—' %>
|
||||
<% if (typeof u.has_google !== 'undefined' || typeof u.has_password !== 'undefined') { %>
|
||||
·
|
||||
<% if (u.has_google && u.has_password) { %>
|
||||
<span class="badge">Google + Email</span>
|
||||
<% } else if (u.has_google) { %>
|
||||
<span class="badge">Google</span>
|
||||
<% } else if (u.has_password) { %>
|
||||
<span class="badge">Email</span>
|
||||
<% } else { %>
|
||||
<span class="badge">No login set</span>
|
||||
<% } %>
|
||||
·
|
||||
<% if (u.email_verified) { %>
|
||||
<span class="badge verified" title="Email verified">Verified</span>
|
||||
<% } else { %>
|
||||
<span class="badge unverified" title="Email not yet verified">Unverified</span>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
<div>
|
||||
<% if (u.role === 'admin') { %>
|
||||
<span class="badge admin">admin</span>
|
||||
<% } else { %>
|
||||
<span class="badge"><%= u.role %></span>
|
||||
<% } %>
|
||||
<% if (u.banned) { %>
|
||||
<span class="badge banned" title="This user is banned">banned</span>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="small muted"><%= u.created_at || '' %></div>
|
||||
<div class="actions">
|
||||
<% if (u.role !== 'admin') { %>
|
||||
<form method="post" action="/admin/users/<%= u.id %>/make-admin" onsubmit="return confirm('Grant admin rights to <%= u.email %>?')">
|
||||
<button type="submit" class="btn">Make admin</button>
|
||||
</form>
|
||||
<% } else { %>
|
||||
<% if (!currentUser || currentUser.id !== u.id) { %>
|
||||
<form method="post" action="/admin/users/<%= u.id %>/unadmin" onsubmit="return confirm('Remove admin rights from <%= u.email %>?')">
|
||||
<button type="submit" class="btn danger">Remove admin</button>
|
||||
</form>
|
||||
<% } else { %>
|
||||
<span class="small muted">(You)</span>
|
||||
<% } %>
|
||||
<% } %>
|
||||
|
||||
<% if (!currentUser || currentUser.id !== u.id) { %>
|
||||
<% if (u.banned) { %>
|
||||
<form method="post" action="/admin/users/<%= u.id %>/unban" onsubmit="return confirm('Unban <%= u.email %>?')">
|
||||
<button type="submit" class="btn">Unban</button>
|
||||
</form>
|
||||
<% } else { %>
|
||||
<form method="post" action="/admin/users/<%= u.id %>/ban" onsubmit="return confirm('Ban <%= u.email %>? They will be unable to sign in.')">
|
||||
<button type="submit" class="btn danger">Ban</button>
|
||||
</form>
|
||||
<% } %>
|
||||
<form method="post" action="/admin/users/<%= u.id %>/delete" onsubmit="return confirm('Permanently delete <%= u.email %>? All their tickets and responses will be removed. This cannot be undone.')">
|
||||
<button type="submit" class="btn danger">Delete user</button>
|
||||
</form>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
</div>
|
||||
<% } %>
|
||||
</section>
|
||||
@@ -0,0 +1,22 @@
|
||||
<section>
|
||||
<div class="flex between center">
|
||||
<h1>My Tickets</h1>
|
||||
<a class="btn primary" href="/tickets/new">New Ticket</a>
|
||||
</div>
|
||||
|
||||
<% if (!tickets || tickets.length === 0) { %>
|
||||
<p>No tickets yet. Create your first one.</p>
|
||||
<% } else { %>
|
||||
<div class="list">
|
||||
<% tickets.forEach(t => { %>
|
||||
<a class="list-item" href="/tickets/<%= t.id %>">
|
||||
<div>
|
||||
<div class="subject"><%= t.subject %></div>
|
||||
<div class="muted">Updated: <%= t.updated_at || t.created_at %></div>
|
||||
</div>
|
||||
<span class="badge <%= t.status %>"><%= t.status %></span>
|
||||
</a>
|
||||
<% }) %>
|
||||
</div>
|
||||
<% } %>
|
||||
</section>
|
||||
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title><%= typeof title !== 'undefined' ? title + ' · ' : '' %>TicketSupport</title>
|
||||
<link rel="stylesheet" href="/public/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="navbar">
|
||||
<div class="container nav-inner">
|
||||
<a href="/" class="brand">TicketSupport</a>
|
||||
<nav>
|
||||
<% if (currentUser) { %>
|
||||
<a href="/dashboard">My Tickets</a>
|
||||
<% if (currentUser.role === 'admin') { %>
|
||||
<a href="/admin">Admin</a>
|
||||
<% } %>
|
||||
<form method="post" action="/logout" style="display:inline">
|
||||
<button type="submit" class="linklike">Logout (<%= currentUser.name || currentUser.email || 'Account' %>)</button>
|
||||
</form>
|
||||
<% } else { %>
|
||||
<a href="/login">Login</a>
|
||||
<a href="/register">Register</a>
|
||||
<% } %>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<% if (message) { %>
|
||||
<div class="flash"><%= message %></div>
|
||||
<% } %>
|
||||
<!-- content -->
|
||||
<%- body %>
|
||||
</main>
|
||||
|
||||
<footer class="footer container">
|
||||
<p>© <%= new Date().getFullYear() %> TicketSupport</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
<section class="auth">
|
||||
<h1>Login</h1>
|
||||
<% if (typeof query !== 'undefined' && query.error) { %>
|
||||
<div class="flash error">Invalid email or password.</div>
|
||||
<% } %>
|
||||
<% if (typeof query !== 'undefined' && query.unverified) { %>
|
||||
<div class="flash warn">
|
||||
Your email is not verified yet. Please check your inbox.
|
||||
<% if (query.email) { %>
|
||||
You can also <a href="/verify/resend?email=<%= encodeURIComponent(query.email) %>">resend the verification email</a>.
|
||||
<% } else { %>
|
||||
You can also <a href="/verify/resend">resend the verification email</a>.
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (typeof query !== 'undefined' && query.oauth_error) { %>
|
||||
<div class="flash error">Google sign-in failed. Please try again.</div>
|
||||
<% } %>
|
||||
<form method="post" action="/login" class="card">
|
||||
<label>Email
|
||||
<input type="email" name="email" required value="<%= (query && query.email) ? query.email : '' %>" />
|
||||
</label>
|
||||
<label>Password
|
||||
<input type="password" name="password" required />
|
||||
</label>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<% if (googleEnabled) { %>
|
||||
<div class="oauth">
|
||||
<a class="btn google" href="/auth/google">
|
||||
<img src="https://www.gstatic.com/images/branding/product/2x/googleg_48dp.png" alt="" class="g-icon" />
|
||||
<span>Continue with Google</span>
|
||||
</a>
|
||||
</div>
|
||||
<% } %>
|
||||
<p>Don't have an account? <a href="/register">Register</a></p>
|
||||
</section>
|
||||
@@ -0,0 +1,16 @@
|
||||
<section class="auth">
|
||||
<h1>Register</h1>
|
||||
<form method="post" action="/register" class="card">
|
||||
<label>Name
|
||||
<input type="text" name="name" />
|
||||
</label>
|
||||
<label>Email
|
||||
<input type="email" name="email" required />
|
||||
</label>
|
||||
<label>Password
|
||||
<input type="password" name="password" required minlength="6" />
|
||||
</label>
|
||||
<button type="submit">Create account</button>
|
||||
</form>
|
||||
<p>Already have an account? <a href="/login">Login</a></p>
|
||||
</section>
|
||||
@@ -0,0 +1,12 @@
|
||||
<section>
|
||||
<h1>New Ticket</h1>
|
||||
<form method="post" action="/tickets" class="card">
|
||||
<label>Subject
|
||||
<input type="text" name="subject" required />
|
||||
</label>
|
||||
<label>Description
|
||||
<textarea name="description" rows="6" required></textarea>
|
||||
</label>
|
||||
<button type="submit" class="btn primary">Create Ticket</button>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,61 @@
|
||||
<section>
|
||||
<div class="flex between center">
|
||||
<h1><span class="muted">Ticket:</span> <%= ticket.subject %></h1>
|
||||
<span class="badge <%= ticket.status %>"><%= ticket.status %></span>
|
||||
</div>
|
||||
<div class="muted small">
|
||||
Created: <%= ticket.created_at %> · Updated: <%= ticket.updated_at %>
|
||||
<% if (currentUser && currentUser.role === 'admin') { %>
|
||||
· From: <%= ticket.user_name || '' %> (<%= ticket.user_email || '' %>)
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<article class="card">
|
||||
<p><%= ticket.description %></p>
|
||||
</article>
|
||||
|
||||
<h2>Conversation</h2>
|
||||
<div class="thread">
|
||||
<% if (responses && responses.length) { %>
|
||||
<% responses.forEach(r => { %>
|
||||
<div class="message <%= r.is_admin_response ? 'admin' : 'user' %>">
|
||||
<div class="meta">
|
||||
<span class="who"><%= r.name || r.email %></span>
|
||||
<span class="when"><%= r.created_at %></span>
|
||||
<% if (r.is_admin_response) { %><span class="badge admin">admin</span><% } %>
|
||||
</div>
|
||||
<div class="body"><%= r.message %></div>
|
||||
</div>
|
||||
<% }) %>
|
||||
<% } else { %>
|
||||
<p class="muted">No responses yet.</p>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<h3>Add a response</h3>
|
||||
<form method="post" action="/tickets/<%= ticket.id %>/respond" class="card">
|
||||
<textarea name="message" rows="4" required placeholder="Type your message..."></textarea>
|
||||
<div class="flex end">
|
||||
<button type="submit" class="btn">Send</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="flex between center" style="margin-top:1rem">
|
||||
<a href="<%= currentUser.role === 'admin' ? '/admin' : '/dashboard' %>">← Back</a>
|
||||
<% if (currentUser.role === 'admin') { %>
|
||||
<form method="post" action="/admin/tickets/<%= ticket.id %>/status">
|
||||
<label>Set status
|
||||
<select name="status">
|
||||
<option value="open" <%= ticket.status==='open'?'selected':'' %>>open</option>
|
||||
<option value="answered" <%= ticket.status==='answered'?'selected':'' %>>answered</option>
|
||||
<option value="closed" <%= ticket.status==='closed'?'selected':'' %>>closed</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="submit" class="btn">Update</button>
|
||||
</form>
|
||||
<form method="post" action="/admin/tickets/<%= ticket.id %>/delete" onsubmit="return confirm('Delete this ticket? This cannot be undone.')">
|
||||
<button type="submit" class="btn danger">Delete Ticket</button>
|
||||
</form>
|
||||
<% } %>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,15 @@
|
||||
<section class="auth">
|
||||
<h1>Resend verification email</h1>
|
||||
<div class="card">
|
||||
<form method="post" action="/verify/resend">
|
||||
<label>Email
|
||||
<input type="email" name="email" required value="<%= email || '' %>" placeholder="you@example.com" />
|
||||
</label>
|
||||
<div class="flex end">
|
||||
<button type="submit" class="btn">Send verification email</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<p class="small muted">If your email exists and is not verified yet, we will send a new verification link. Links expire after 24 hours.</p>
|
||||
<p class="small"><a href="/login">Back to login</a></p>
|
||||
</section>
|
||||
Reference in New Issue
Block a user