3fdae1d8d7
* Demographics page: * Show percents with up to 1 decimal place of precision. * On tablets+ align the percent text to the right. * On photo counts, only include certified active user photos. * On gender/orientation demographics, pad the remaining "No answer" counts with the set of users who have no profile_fields set in the database yet. * Admin certification page: * Add additional "common rejection reasons" * Add a confirm prompt when viewing the Rejected list to avoid accidental approval of previously rejected cert photos.
258 lines
14 KiB
HTML
258 lines
14 KiB
HTML
{{define "title"}}Admin - Certification Photos{{end}}
|
|
{{define "content"}}
|
|
{{$Root := .}}
|
|
<div class="container">
|
|
<section class="hero is-danger is-bold">
|
|
<div class="hero-body">
|
|
<div class="container">
|
|
<h1 class="title">
|
|
Admin / Certification Photos
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="block p-4">
|
|
<div class="columns">
|
|
<div class="column">
|
|
{{if .Pager}}
|
|
There {{Pluralize64 .Pager.Total "is" "are"}} <strong>{{.Pager.Total}}</strong> Certification Photo{{Pluralize64 .Pager.Total}}
|
|
{{if eq .View "pending"}}
|
|
needing approval.
|
|
{{else}}
|
|
at status "{{.View}}."
|
|
{{end}}
|
|
{{else if .FoundUser}}
|
|
Found user <strong><a href="/u/{{.FoundUser.Username}}" class="has-text-dark">{{.FoundUser.Username}}</a></strong>
|
|
(<a href="mailto:{{.FoundUser.Email}}">{{.FoundUser.Email}}</a>)
|
|
{{end}}
|
|
</div>
|
|
<div class="column is-narrow">
|
|
<div class="tabs is-toggle">
|
|
<ul>
|
|
<li{{if eq .View "pending"}} class="is-active"{{end}}>
|
|
<a href="{{.Request.URL.Path}}?view=pending">Needing Approval</a>
|
|
</li>
|
|
<li{{if eq .View "approved"}} class="is-active"{{end}}>
|
|
<a href="{{.Request.URL.Path}}?view=approved">Approved</a>
|
|
</li>
|
|
<li{{if eq .View "rejected"}} class="is-active"{{end}}>
|
|
<a href="{{.Request.URL.Path}}?view=rejected">Rejected</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="block">
|
|
<form method="GET" action="{{.Request.URL.Path}}">
|
|
<div class="field block">
|
|
<div class="label" for="username">Search username or email:</div>
|
|
<input type="text" class="input"
|
|
name="username"
|
|
id="username"
|
|
placeholder="Press Enter to search">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
{{if .Pager}}
|
|
{{SimplePager .Pager}}
|
|
{{end}}
|
|
|
|
<div class="columns is-multiline">
|
|
{{range .Photos}}
|
|
<div class="column is-one-third">
|
|
{{$User := $Root.UserMap.Get .UserID}}
|
|
<form action="{{$Root.Request.URL.Path}}" method="POST">
|
|
{{InputCSRF}}
|
|
<input type="hidden" name="user_id" value="{{$User.ID}}">
|
|
|
|
<div class="card" style="max-width: 512px">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<span class="icon"><i class="fa fa-user"></i></span>
|
|
<span>{{or $User.Username "[deleted]"}}</span>
|
|
</p>
|
|
</header>
|
|
{{if .Filename}}
|
|
<div class="card-image">
|
|
<figure class="image">
|
|
<img src="{{PhotoURL .Filename}}">
|
|
</figure>
|
|
</div>
|
|
{{end}}
|
|
<div class="card-content">
|
|
<div class="media block">
|
|
<div class="media-left">
|
|
<figure class="image is-48x48">
|
|
{{if $User.ProfilePhoto.ID}}
|
|
<img src="{{PhotoURL $User.ProfilePhoto.CroppedFilename}}">
|
|
{{else}}
|
|
<img src="/static/img/shy.png">
|
|
{{end}}
|
|
</figure>
|
|
</div>
|
|
<div class="media-content">
|
|
<p class="title is-4">{{$User.NameOrUsername}}</p>
|
|
<p class="subtitle is-6">
|
|
<span class="icon"><i class="fa fa-user"></i></span>
|
|
<a href="/u/{{$User.Username}}" target="_blank">{{$User.Username}}</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="block">
|
|
<div class="columns">
|
|
<div class="column is-narrow">
|
|
<strong>Status:</strong>
|
|
</div>
|
|
<div class="column">
|
|
{{if eq .Status "pending"}}
|
|
<strong class="has-text-warning">Pending Approval</strong>
|
|
{{else if eq .Status "approved"}}
|
|
<strong class="has-text-success">Approved</strong>
|
|
{{else if eq .Status "rejected"}}
|
|
<strong class="has-text-danger">Rejected</strong>
|
|
{{else}}
|
|
<strong>{{.Status}}</strong>
|
|
{{end}}
|
|
|
|
<!-- Secondary approved? -->
|
|
{{if .SecondaryVerified}}
|
|
<strong class="has-text-info">
|
|
<i class="fa fa-id-card ml-3 mr-1"></i> ID Verified
|
|
</strong>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Secondary photo ID attached? -->
|
|
{{if .SecondaryFilename}}
|
|
<div class="block">
|
|
<div class="mb-2">
|
|
<strong class="has-text-success">
|
|
<i class="fa fa-id-card"></i>
|
|
Secondary Photo ID Attached
|
|
</strong>
|
|
</div>
|
|
<img src="{{PhotoURL .SecondaryFilename}}">
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="block">
|
|
<strong><i class="fa fa-location-dot mr-1"></i> GeoIP Insights:</strong>
|
|
<div>
|
|
{{$Insights := $Root.InsightsMap.Get .IPAddress}}
|
|
{{if $Insights.IsZero}}
|
|
<span class="has-text-danger">No GeoIP insights available for this IP address!</span>
|
|
{{else}}
|
|
{{$Insights.Medium}}
|
|
{{end}}
|
|
</div>
|
|
<div>
|
|
IP: {{.IPAddress}}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Secondary ID was needed in the past for this user -->
|
|
{{if .SecondaryNeeded}}
|
|
<div class="block is-size-7 has-text-warning">
|
|
<i class="fa fa-id-card"></i> A secondary form of ID was requested from
|
|
this user once before. They will always be asked for a secondary ID if
|
|
they replace their cert photo in the future.
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="field">
|
|
<textarea class="textarea" name="comment"
|
|
cols="60" rows="2"
|
|
placeholder="Admin comment (for rejection)">{{.AdminComment}}</textarea>
|
|
|
|
<div class="select is-fullwidth">
|
|
<select class="common-reasons">
|
|
<option value="">(Common Rejection Reasons)</option>
|
|
<optgroup label="Common Rejection Reasons">
|
|
<option value="Your certification pic should depict you holding onto a sheet of paper with your username, site name, and current date written on it.">
|
|
Didn't follow directions
|
|
</option>
|
|
<option value="The sheet of paper must also include the website name: nonshy.">Website name not visible</option>
|
|
<option value="Please take a clearer picture that shows your arm and hand holding onto the sheet of paper.">Unclear picture (hand not visible enough)</option>
|
|
<option value="This photo has been digitally altered, please take a new certification picture and upload it as it comes off your camera.">Photoshopped or digitally altered</option>
|
|
<option value="Your certification photo should feature a hand written message on paper so we know you're a real person. It looks like you added text digitally to this picture, which isn't acceptable because anybody could have downloaded a picture like this from online and added text to it in that way.\n\nPlease take a picture with your certification message hand written on paper and upload it how it came off the camera.">Text appears added digitally</option>
|
|
<option value="The sheet of paper that you are holding in this picture is not readable. Please take a clearer picture that shows you holding onto a sheet of paper which has the website's name (nonshy), your username, and today's date written and be sure that it is readable.">Cert message is not legible</option>
|
|
<option value="You had a previous account on nonshy which was suspended and you are not welcome with a new account.">User was previously banned from nonshy</option>
|
|
<option value="This is not an acceptable certification photo.">Not acceptable</option>
|
|
</optgroup>
|
|
<optgroup label="Secondary Verification">
|
|
<option value="(secondary)">
|
|
User appears underage, ask for secondary ID
|
|
</option>
|
|
</optgroup>
|
|
<optgroup label="Other Actions">
|
|
<option value="(ignore)">(Silently reject this photo without sending an e-mail)</option>
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<a href="/admin/changelog?table_name=certification_photos&about_user_id={{$User.ID}}" class="button is-small has-text-warning">
|
|
<span class="icon"><i class="fa fa-clipboard-list"></i></span>
|
|
<span>Change Log</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<footer class="card-footer">
|
|
{{if not (eq .Status "rejected")}}
|
|
<button type="submit" name="verdict" value="reject" class="card-footer-item button is-danger">
|
|
<span class="icon"><i class="fa fa-xmark"></i></span>
|
|
<span>Reject</span>
|
|
</button>
|
|
{{end}}
|
|
|
|
{{if not (eq .Status "approved")}}
|
|
<button type="submit" name="verdict" value="approve" class="card-footer-item button is-success"
|
|
{{if eq $Root.View "rejected"}}onclick="return confirm('Are you SURE you want to mark this photo as Approved?\n\nKeep in mind you are currently viewing the Rejected list of photos!')"{{end}}>
|
|
<span class="icon"><i class="fa fa-check"></i></span>
|
|
<span>Approve</span>
|
|
</button>
|
|
{{end}}
|
|
</footer>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
{{end}}
|
|
{{define "scripts"}}
|
|
<script>
|
|
window.addEventListener("DOMContentLoaded", (event) => {
|
|
document.querySelectorAll("select.common-reasons").forEach(elem => {
|
|
let textarea = elem.parentNode.parentNode.getElementsByTagName("textarea")[0],
|
|
approveButton = elem.parentNode.parentNode.parentNode.parentNode.querySelector("button.is-success");
|
|
|
|
// Grey out the Approve button if a rejection reason is filled out.
|
|
let setApproveState = () => {
|
|
if (textarea.value.length > 0) {
|
|
approveButton.disabled = "disabled";
|
|
} else {
|
|
approveButton.disabled = null;
|
|
}
|
|
};
|
|
|
|
textarea.addEventListener("change", setApproveState);
|
|
textarea.addEventListener("keyup", setApproveState);
|
|
elem.addEventListener("change", (e) => {
|
|
textarea.value = elem.value.replaceAll("\\n", "\n");
|
|
setApproveState();
|
|
});
|
|
})
|
|
});
|
|
</script>
|
|
{{end}}
|