Noah
93c13882aa
Finish implementing the basic forum features: * Pinned threads (admin or board owner only) * Edit Thread settings when you edit the top-most comment. * NoReply threads remove all the reply buttons. * Explicit forums and threads are filtered out unless opted-in (admins always see them). * Count the unique members who participated in each forum. * Get the most recently updated thread to show on forum list page. * Contact/Report page: handle receiving a comment ID to report on. Implement Likes & Notifications * Like buttons added to Photos and Profile Pages. Implemented via simple vanilla JS (likes.js) to make ajax requests to back-end to like/unlike. * Notifications: for your photo or profile being liked. If you unlike, the existing notifications about the like are revoked. * The notifications appear as an alert number in the nav bar and are read on the User Dashboard. Click to mark a notification as "read" or click the "mark all as read" button. Update DeleteUser to scrub likes, notifications, threads, and comments.
292 lines
14 KiB
HTML
292 lines
14 KiB
HTML
{{define "title"}}My Dashboard{{end}}
|
|
{{define "content"}}
|
|
<div class="container">
|
|
<section class="hero is-info is-bold">
|
|
<div class="hero-body">
|
|
<div class="container">
|
|
<h1 class="title">User Dashboard</h1>
|
|
<h2 class="subtitle">to your account</h2>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="block p-4">
|
|
<div class="columns">
|
|
<div class="column">
|
|
<!-- Onboarding Checklist -->
|
|
{{if or (not .CurrentUser.Certified) (not .CurrentUser.ProfilePhoto.ID)}}
|
|
<div class="card block">
|
|
<header class="card-header has-background-danger">
|
|
<p class="card-header-title has-text-light">
|
|
<span class="icon"><i class="fa fa-check"></i></span>
|
|
<span>Onboarding Checklist</span>
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<p class="block">
|
|
You're almost there! Please review the following checklist items to gain
|
|
full access to this website. Members are expected to have a face picture
|
|
as their default Profile Pic and upload a Verification Photo to become
|
|
certified as being a real person.
|
|
</p>
|
|
|
|
<ul class="menu-list block">
|
|
<li>
|
|
<a href="/photo/upload?intent=profile_pic">
|
|
{{if .CurrentUser.ProfilePhoto.ID}}
|
|
<span class="icon"><i class="fa fa-circle-check has-text-success"></i></span>
|
|
{{else}}
|
|
<span class="icon"><i class="fa fa-circle has-text-danger"></i></span>
|
|
{{end}}
|
|
<span>
|
|
Add a Profile Picture
|
|
{{if not .CurrentUser.ProfilePhoto.ID}}
|
|
<span class="icon"><i class="fa fa-external-link"></i></span>
|
|
{{end}}
|
|
</span>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/photo/certification">
|
|
{{if .CurrentUser.Certified}}
|
|
<span class="icon"><i class="fa fa-circle-check has-text-success"></i></span>
|
|
{{else}}
|
|
<span class="icon"><i class="fa fa-circle has-text-danger"></i></span>
|
|
{{end}}
|
|
<span>
|
|
Get certified by uploading a verification selfie
|
|
{{if not .CurrentUser.Certified}}
|
|
<span class="icon"><i class="fa fa-external-link"></i></span>
|
|
{{end}}
|
|
</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="card block">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">My Account</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<ul class="menu-list">
|
|
<li>
|
|
<a href="/u/{{.CurrentUser.Username}}">
|
|
<span class="icon"><i class="fa fa-user"></i></span>
|
|
My Profile
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="/photo/u/{{.CurrentUser.Username}}">
|
|
<span class="icon"><i class="fa fa-image"></i></span>
|
|
My Photos
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="/photo/upload">
|
|
<span class="icon"><i class="fa fa-upload"></i></span>
|
|
Upload Photos
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="/settings">
|
|
<span class="icon"><i class="fa fa-edit"></i></span>
|
|
Edit Profile & Settings
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="/photo/certification">
|
|
<span class="icon"><i class="fa fa-certificate"></i></span>
|
|
Certification Photo
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="/users/blocked">
|
|
<span class="icon"><i class="fa fa-hand"></i></span>
|
|
Blocked Users
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="/logout">
|
|
<span class="icon"><i class="fa fa-arrow-right-from-bracket"></i></span>
|
|
Log out
|
|
</a>
|
|
</li>
|
|
{{if .SessionImpersonated}}
|
|
<li>
|
|
<a href="/admin/unimpersonate" class="has-text-danger">
|
|
<span class="icon"><i class="fa fa-ghost"></i></span>
|
|
<span>Unimpersonate</span>
|
|
</a>
|
|
</li>
|
|
{{end}}
|
|
<li>
|
|
<a href="/account/delete">
|
|
<span class="icon"><i class="fa fa-trash"></i></span>
|
|
Delete account
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{$Root := .}}
|
|
|
|
<div class="column">
|
|
<div class="card" id="notifications">
|
|
<header class="card-header has-background-warning">
|
|
<p class="card-header-title">Notifications</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<div class="columns">
|
|
<div class="column">
|
|
{{if gt .NavUnreadNotifications 0}}
|
|
{{.NavUnreadNotifications}} unread notification{{Pluralize64 .NavUnreadNotifications}}.
|
|
{{else}}
|
|
No unread notifications.
|
|
{{end}}
|
|
</div>
|
|
<div class="column is-narrow">
|
|
<a href="/me?intent=read-notifications" class="button is-link is-light is-small">
|
|
<span class="icon-text">
|
|
<span class="icon"><i class="fa fa-check"></i></span>
|
|
<span>Mark all as read</span>
|
|
</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<table class="table is-striped is-fullwidth is-hoverable">
|
|
<tbody>
|
|
{{range .Notifications}}
|
|
{{$Body := $Root.NotifMap.Get .ID}}
|
|
<tr>
|
|
<td class="nonshy-notification-row" data-notification-id="{{.ID}}">
|
|
<div class="columns">
|
|
<div class="column is-narrow has-text-centered">
|
|
{{if not .Read}}
|
|
<div class="mb-2 nonshy-notification-new">
|
|
<strong class="tag is-success">NEW!</strong>
|
|
</div>
|
|
{{end}}
|
|
<a href="/u/{{.User.Username}}">
|
|
<figure class="image is-48x48 is-inline-block">
|
|
{{if .User.ProfilePhoto.ID}}
|
|
<img src="{{PhotoURL .User.ProfilePhoto.CroppedFilename}}">
|
|
{{else}}
|
|
<img src="/static/img/shy.png">
|
|
{{end}}
|
|
</figure>
|
|
</a>
|
|
</div>
|
|
<div class="column">
|
|
<div class="mb-1">
|
|
{{if eq .Type "like"}}
|
|
<span class="icon"><i class="fa fa-heart has-text-danger"></i></span>
|
|
<span>
|
|
<a href="/u/{{.User.Username}}"><strong>{{.User.Username}}</strong></a>
|
|
liked your
|
|
{{if eq .TableName "photos"}}
|
|
photo.
|
|
{{else if eq .TableName "users"}}
|
|
profile page.
|
|
{{else}}
|
|
{{.TableName}}.
|
|
{{end}}
|
|
</span>
|
|
{{else}}
|
|
{{.User.Username}} {{.Type}} {{.TableName}} {{.TableID}}
|
|
{{end}}
|
|
</div>
|
|
|
|
<!-- Photo caption? -->
|
|
{{if $Body.Photo}}
|
|
<div class="block">
|
|
<em>{{or $Body.Photo.Caption "No caption."}}</em>
|
|
</div>
|
|
{{end}}
|
|
|
|
<hr class="has-background-light mb-1">
|
|
<small title="{{.CreatedAt.Format "2006-01-02 15:04:05"}}">
|
|
{{SincePrettyCoarse .CreatedAt}} ago
|
|
</small>
|
|
</div>
|
|
|
|
<!-- Attached photo? -->
|
|
{{if $Body.PhotoID}}
|
|
<div class="column is-one-quarter">
|
|
<img src="{{PhotoURL $Body.Photo.Filename}}">
|
|
|
|
{{if $Body.Photo.Caption}}
|
|
<small>{{$Body.Photo.Caption}}</small>
|
|
{{else}}
|
|
<small><em>No caption.</em></small>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
|
|
{{if .Pager.HasNext}}
|
|
<div class="has-text-centered">
|
|
<a href="{{.Request.URL.Path}}?page={{.Pager.Next}}" class="button">View older notifications</a>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
// Notifications helper.
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
let busy = false;
|
|
|
|
// Bind to the notification table rows.
|
|
(document.querySelectorAll(".nonshy-notification-row") || []).forEach(node => {
|
|
node.addEventListener("click", (e) => {
|
|
if (busy) return;
|
|
|
|
let $newBadge = node.querySelector(".nonshy-notification-new"),
|
|
ID = node.dataset.notificationId;
|
|
$newBadge.style.display = "none";
|
|
|
|
return fetch("/v1/notifications/read", {
|
|
method: "POST",
|
|
mode: "same-origin",
|
|
cache: "no-cache",
|
|
credentials: "same-origin",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
"id": parseInt(ID),
|
|
}),
|
|
})
|
|
.then((response) => response.json())
|
|
.then((data) => {
|
|
console.log(data);
|
|
}).catch(resp => {
|
|
window.alert(resp);
|
|
}).finally(() => {
|
|
busy = false;
|
|
})
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{{end}} |