website/web/templates/account/settings.html

891 lines
40 KiB
HTML

{{define "title"}}User Settings{{end}}
{{define "content"}}
<div class="container">
<section class="hero is-info is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title">User Settings</h1>
</div>
</div>
</section>
{{ $User := .CurrentUser }}
<div class="block p-4">
<div class="tabs is-boxed">
<ul>
<li class="is-active">
<a href="/settings#profile" class="nonshy-tab-button">
Profile
</a>
</li>
<li>
<a href="/settings#prefs" class="nonshy-tab-button">
Preferences
</a>
</li>
<li>
<a href="/settings#location" class="nonshy-tab-button">
Location
</a>
</li>
<li>
<a href="/settings#account" class="nonshy-tab-button">
Account
</a>
</li>
</ul>
</div>
<!-- Profile -->
<div class="card" id="profile">
<header class="card-header has-background-link">
<p class="card-header-title has-text-light">
<i class="fa fa-user pr-2"></i>
My Profile
</p>
</header>
<form method="POST" action="/settings">
<input type="hidden" name="intent" value="profile">
{{InputCSRF}}
<div class="card-content">
<p class="block">
The fields here are shown on your <a href="/u/{{.CurrentUser.Username}}">profile page</a>
and are all optional. Fields with a <i class="fa fa-lock"></i> icon are not shown on
your page but may drive some data that is (e.g., your current age derived from your birthdate).
</p>
<div class="columns">
<div class="column field is-half">
<label class="label" for="display_name">Display Name</label>
<input type="text" class="input"
id="display_name"
name="display_name"
placeholder="John Doe"
value="{{or $User.Name ""}}">
</div>
<div class="column field is-half">
<label class="label" for="dob">Birthdate <i class="fa fa-lock"></i></label>
<input type="date" class="input{{if not $User.Birthdate.IsZero}} cursor-not-allowed{{end}}"
id="dob"
name="dob"
value="{{if not $User.Birthdate.IsZero}}{{$User.Birthdate.Format "2006-01-02"}}{{end}}"
required
{{if not $User.Birthdate.IsZero}}readonly{{end}}>
<p class="help">
Used to show your age on your profile.
{{if not $User.Birthdate.IsZero}}
If you entered a wrong birthdate, <a href="/contact">contact</a> support to change it.
{{end}}
</p>
<!-- Don't show age checkbox -->
<label class="checkbox">
<input type="checkbox"
name="hide_age"
value="true"
{{if eq ($User.GetProfileField "hide_age") "true"}}checked{{end}}>
Don't show my age on my profile
</label>
</div>
</div>
<div class="columns">
<div class="column field is-half">
<label class="label" for="gender">Gender</label>
<div class="select is-fullwidth">
<select id="gender" name="gender">
<option value="">No answer</option>
{{range .Enum.Gender}}
<option value="{{.}}"{{if eq ($User.GetProfileField "gender") .}} selected{{end}}>{{.}}</option>
{{end}}
</select>
</div>
</div>
<div class="column field is-half">
<label class="label" for="pronouns">Pronouns</label>
<input type="text" class="input"
id="pronouns"
name="pronouns"
maxlength="30"
value="{{$User.GetProfileField "pronouns"}}">
<p class="help">e.g.
<a href="#" onclick="document.querySelector('#pronouns').value='he/him'; return false">he/him</a>;
<a href="#" onclick="document.querySelector('#pronouns').value='she/her'; return false">she/her</a>;
<a href="#" onclick="document.querySelector('#pronouns').value='they/them'; return false">they/them</a>
</p>
</div>
</div>
<div class="columns">
<div class="column field is-half">
<label class="label" for="city">City/Location</label>
<input type="text" class="input"
id="city"
name="city"
value="{{$User.GetProfileField "city"}}">
</div>
<div class="column field is-half">
<label class="label" for="job">Job/Occupation</label>
<input type="text" class="input"
id="job"
name="job"
value="{{$User.GetProfileField "job"}}">
</div>
</div>
<div class="columns">
<div class="column field is-half">
<label class="label" for="marital_status">Marital Status</label>
<div class="select is-fullwidth">
<select id="marital_status" name="marital_status">
<option value="">No answer</option>
{{range .Enum.MaritalStatus}}
<option value="{{.}}"{{if eq ($User.GetProfileField "marital_status") .}} selected{{end}}>{{.}}</option>
{{end}}
</select>
</div>
</div>
<div class="column field is-half">
<label class="label" for="relationship_type">Relationship Type</label>
<div class="select is-fullwidth">
<select id="relationship_type" name="relationship_type">
<option value="">No answer</option>
{{range .Enum.RelationshipType}}
<option value="{{.}}"{{if eq ($User.GetProfileField "relationship_type") .}} selected{{end}}>{{.}}</option>
{{end}}
</select>
</div>
</div>
</div>
<div class="columns">
<div class="column field is-half">
<label class="label" for="orientation">Orientation</label>
<div class="select is-fullwidth">
<select id="orientation" name="orientation">
<option value="">No answer</option>
{{range .Enum.Orientation}}
<option value="{{.}}"{{if eq ($User.GetProfileField "orientation") .}} selected{{end}}>{{.}}</option>
{{end}}
</select>
</div>
</div>
</div>
<div class="field">
<label class="label">Here For</label>
<div class="columns is-multiline pb-5">
{{range .Enum.HereFor}}
<div class="column is-one-third pb-0">
<label class="checkbox">
<input type="checkbox"
name="here_for"
value="{{.}}"
{{if $User.ProfileFieldIn "here_for" .}}checked{{end}}>
{{.}}
</label>
</div>
{{end}}
</div>
</div>
<div class="field">
<label class="label" for="about_me">About Me</label>
<textarea class="textarea" cols="60" rows="4"
id="about_me"
name="about_me"
placeholder="A little blurb about myself">{{$User.GetProfileField "about_me"}}</textarea>
<p class="help">
Write a bit about yourself. <a href="/markdown" target="_blank">Markdown formatting</a> is supported here.
</p>
</div>
<div class="field">
<label class="label" for="interests">My Interests</label>
<textarea class="textarea" cols="60" rows="4"
id="interests"
name="interests"
placeholder="What kinds of things make you curious?">{{$User.GetProfileField "interests"}}</textarea>
</div>
<div class="field">
<label class="label" for="music_movies">Music/Bands/Movies</label>
<textarea class="textarea" cols="60" rows="4"
id="music_movies"
name="music_movies"
placeholder="What is your style of music or movie?">{{$User.GetProfileField "music_movies"}}</textarea>
</div>
<div class="field">
<button type="submit" class="button is-primary">
Save Profile Settings
</button>
</div>
</div>
</form>
</div>
<!-- Website Preferences -->
<form method="POST" action="/settings">
<input type="hidden" name="intent" value="preferences">
{{InputCSRF}}
<div class="card mb-5" id="prefs">
<header class="card-header has-background-success">
<p class="card-header-title has-text-dark-dark">
<i class="fa fa-square-check pr-2"></i>
Website Preferences
</p>
</header>
<div class="card-content">
<div class="field">
<label class="label">Explicit Content Filter</label>
<label class="checkbox">
<input type="checkbox"
name="explicit"
value="true"
{{if .CurrentUser.Explicit}}checked{{end}}>
Show explicit content <i class="fa fa-fire has-text-danger ml-1"></i>
</label>
<p class="help">
Check this box if you are OK seeing explicit content on this site, which may
include erections or sexually charged content. These may appear on the Site
Gallery as well as user profile pages.
</p>
</div>
<hr>
<div class="field">
<label class="label">Profile Visibility</label>
<label class="checkbox">
<input type="radio"
name="visibility"
value="public"
{{if eq .CurrentUser.Visibility "public"}}checked{{end}}>
Public + Login Required
<i class="fa fa-eye ml-2 has-text-info"></i>
</label>
<p class="help">
The default is that users must be logged-in to even look at your profile
page. If your profile URL (/u/{{.CurrentUser.Username}}) is visited by a
logged-out browser, they are prompted to log in.
</p>
<label class="checkbox mt-2">
<input type="radio"
name="visibility"
value="external"
{{if eq .CurrentUser.Visibility "external"}}checked{{end}}>
Public + Limited Logged-out View
<i class="fa fa-eye ml-2 has-text-danger"></i>
</label>
<p class="help">
Your profile is fully visible to logged-in users, but if a logged-out browser
visits your page they will see a very minimal view: only your profile picture
and display name are shown.
<a href="/u/{{.CurrentUser.Username}}?view=external" target="_blank">Preview <i class="fa fa-external-link"></i></a>
</p>
<label class="checkbox mt-2">
<input type="radio"
name="visibility"
value="private"
{{if eq .CurrentUser.Visibility "private"}}checked{{end}}>
Mark my profile page as "private"
<i class="fa fa-lock ml-2 has-text-private"></i>
</label>
<p class="help">
If you check this box then only friends who you have approved are able to
see your profile page and gallery. Your gallery photos also will NOT appear
on the Site Gallery page. If your profile page is visited by a logged-out
viewer, they are prompted to log in.
</p>
</div>
<hr>
<div class="field">
<label class="label mb-0">Who can send me the first <i class="fa fa-envelope"></i> Message?</label>
<div class="has-text-info ml-4">
<small><em>
Note: this refers to Direct Messages on the main website
(not inside the chat room).
</em></small>
{{.CurrentUser.GetProfileField "dm_privacy"}}
</div>
<label class="checkbox">
<input type="radio"
name="dm_privacy"
value=""
{{if eq (.CurrentUser.GetProfileField "dm_privacy") ""}}checked{{end}}>
Anybody on the site
</label>
<p class="help">
Almost any member of the site may send you a Direct Message from your profile
page (except for maybe <a href="/faq#shy-faqs" target="_blank">Shy Accounts</a>).
</p>
<label class="checkbox">
<input type="radio"
name="dm_privacy"
value="friends"
{{if eq (.CurrentUser.GetProfileField "dm_privacy") "friends"}}checked{{end}}>
Only people on my Friends list
</label>
<p class="help">
Nobody can slide into your DMs except for friends (and admins if needed). Anybody
may <em>reply</em> to messages that you send to them.
</p>
<label class="checkbox">
<input type="radio"
name="dm_privacy"
value="nobody"
{{if eq (.CurrentUser.GetProfileField "dm_privacy") "nobody"}}checked{{end}}>
Nobody (close my DMs)
</label>
<p class="help">
Nobody can start a Direct Message conversation with you on the main website
(except an admin if necessary). Anybody may <em>reply</em> to messages that you
sent to them first.
</p>
</div>
<!-- TODO: manually opt-in dark mode is hairy, look at
those media queries in bulma-prefers-dark.js!
<div class="field">
<label class="label">Website Theme</label>
<label class="checkbox">
<input type="radio"
name="theme"
value=""
{{if eq ($User.GetProfileField "theme") "" }}checked{{end}}>
Automatic
</label>
<p class="help">
Automatically chooses a theme based on your device settings.
</p>
<label class="checkbox">
<input type="radio"
name="theme"
value="light"
{{if eq ($User.GetProfileField "theme") "light" }}checked{{end}}>
Light
</label>
<label class="checkbox">
<input type="radio"
name="theme"
value="dark"
{{if eq ($User.GetProfileField "theme") "dark" }}checked{{end}}>
Dark
</label>
</div>
-->
<div class="field">
<button type="submit" class="button is-primary">
Save Website Preferences
</button>
</div>
</div>
</div>
</form>
<!-- Location Settings -->
<div id="location">
<form method="POST" action="/settings">
<input type="hidden" name="intent" value="location">
{{InputCSRF}}
<div class="card mb-5">
<header class="card-header has-background-link">
<p class="card-header-title has-text-light">
<i class="fa fa-globe mr-2"></i>
Location Settings
</p>
</header>
<div class="card-content">
<p class="block">
The settings on this page control your location for the <a href="/members?sort=distance"><strong>Who's Nearby</strong></a>
feature of {{PrettyTitle}}. Being discoverable by your location is an <strong>opt-in</strong>
feature and you have your choice of options how you want your location to be found.
</p>
<div class="field">
<label class="label">How do you want your location to be determined?</label>
<label class="checkbox">
<input type="radio"
name="source"
value=""
id="location-option-none"
{{if eq .UserLocation.Source ""}}checked{{end}}>
<i class="fa fa-ban ml-2 mr-1 has-text-danger"></i>
Do not share my location with {{PrettyTitle}}.
</label>
<p class="help mb-4">
This option will opt-out of the Who's Nearby feature and your profile will not be
discoverable by distance to other members. Any location data already stored by
the website will be erased if you choose this option.
</p>
<label class="checkbox">
<input type="radio"
name="source"
value="geoip"
id="location-option-geoip"
{{if eq .UserLocation.Source "geoip"}}checked{{end}}>
<i class="fa fa-network-wired ml-2 mr-1 has-text-info"></i>
Automatically detect my location by my IP address
</label>
<p class="help mb-4">
Course location data based on your IP address. Might be accurate to your
city level.
<!-- Do we have GeoIP insights? -->
{{if not .GeoIPInsights.IsZero}}
<br>
<strong>Currently:</strong>
{{.GeoIPInsights}}
{{end}}
</p>
<label class="checkbox">
<input type="radio"
name="source"
value="gps"
id="location-option-gps"
{{if eq .UserLocation.Source "gps"}}checked{{end}}>
<i class="fa fa-location-dot ml-2 mr-1 has-text-success"></i>
Automatically detect my current location by GPS (with your permission)
</label>
<p class="help mb-4">
Your web browser will prompt you to share your current location with {{PrettyTitle}}.
<br>
<strong>Currently:</strong>
<span id="location-status-gps">You have not granted permission.</span>
<a href="#" class="fa fa-arrows-rotate" id="gps-refresh" title="Refresh my location"></a>
</p>
<label class="checkbox">
<input type="radio"
name="source"
value="pin"
id="location-option-pin"
{{if eq .UserLocation.Source "pin"}}checked{{end}}>
<i class="fa fa-map-pin ml-2 mr-1 has-text-info"></i>
Drop a pin on the map myself
</label>
<p class="help mb-4">
Use the map below and drop a pin anywhere you like to set your location.
</p>
</div>
<div class="field">
<label class="label">
Your Current Location (click "Save" when you are satisfied)
</label>
<p class="block">
Your location will be saved as the following in the database:
</p>
<div class="columns is-mobile" style="max-width: 500px">
<div class="column is-half pr-1">
<label class="label mb-0">Latitude:</label>
<input type="text" class="input is-fullwidth"
name="latitude"
id="saveLatitude"
value="{{.UserLocation.Latitude}}"
placeholder="None"
readonly>
</div>
<div class="column is-half pl-1">
<label class="label mb-0">Longitude:</label>
<input type="text" class="input is-fullwidth"
name="longitude"
id="saveLongitude"
value="{{.UserLocation.Longitude}}"
placeholder="None"
readonly>
</div>
</div>
</div>
<div class="field">
<button type="submit" class="button is-success"
name="intent" value="location">
Save My Location Settings
</button>
</div>
<h3 class="is-size-3">Map</h3>
<p class="block">
This map shows your current location pin. To click and drop a pin manually,
select the "Drop a pin on the map myself" option above. Otherwise, the map will
center on your GPS location (if available) or your IP address location, depending
on your selection above.
</p>
<div id="map" class="block" style="width: 100%; height: 450px"></div>
<p class="block">
Map tiles provided by <a href="https://www.openstreetmap.org" target="_blank">OpenStreetMap.</a>
</p>
</div>
</div>
</form>
</div>
<!-- Account Settings -->
<div id="account">
<form method="POST" action="/settings">
<input type="hidden" name="intent" value="settings">
{{InputCSRF}}
<div class="card mb-5">
<header class="card-header has-background-warning">
<p class="card-header-title has-text-dark-dark">
<i class="fa fa-gear pr-2"></i>
Account Settings
</p>
</header>
<div class="card-content">
<div class="field">
<label class="label" for="old_password">
Current Password
</label>
<input type="password" class="input"
name="old_password"
id="old_password"
placeholder="Current password"
required>
<p class="help">
Enter your current password before making any changes to your
email address or setting a new password.
</p>
</div>
<div class="field">
<label class="label" for="change_email">Change Email</label>
<input type="email" class="input"
id="change_email"
name="change_email"
placeholder="name@domain.com"
value="{{.CurrentUser.Email}}">
</div>
<div class="field">
<label class="label">Change Password</label>
<input type="password" class="input mb-2"
name="new_password"
placeholder="New password">
<input type="password" class="input mb-2"
name="new_password2"
placeholder="Confirm new password">
</div>
<div class="field">
<button type="submit" class="button is-primary">
Save Account Settings
</button>
</div>
</div>
</div>
</form>
<!-- Delete Account -->
<div class="card mb-5">
<header class="card-header has-background-danger">
<p class="card-header-title has-text-light">
<i class="fa fa-gear pr-2"></i>
Delete Account
</p>
</header>
<div class="card-content">
<p class="block">
If you would like to delete your account, please click
on the button below.
</p>
<p class="block">
<a href="/account/delete" class="button is-danger">
Delete My Account
</a>
</p>
</div>
</div>
</div>
</div>
</div>
{{end}}
{{define "scripts"}}
<link rel="stylesheet" href="/static/js/openlayers-7.5.1/en/latest/ol/ol.css">
<script src="/static/js/openlayers-7.5.1/en/latest/ol/dist/ol.js"></script>
<script>
window.addEventListener("DOMContentLoaded", (event) => {
// The tabs
const $profile = document.querySelector("#profile"),
$prefs = document.querySelector("#prefs"),
$location = document.querySelector("#location"),
$account = document.querySelector("#account"),
buttons = Array.from(document.getElementsByClassName("nonshy-tab-button"));
// Hide all by default.
$profile.style.display = 'none';
$prefs.style.display = 'none';
$location.style.display = 'none';
$account.style.display = 'none';
// Current tab to select by default.
let $activeTab = $profile;
// Global function to toggle the active tab.
const showTab = (name) => {
name = name.replace(/\.$/, '');
if (!name) name = "profile";
$activeTab.style.display = 'none';
switch (name) {
case "prefs":
$activeTab = $prefs;
break;
case "location":
$activeTab = $location;
break;
case "account":
$activeTab = $account;
break;
default:
$activeTab = $profile;
}
// Update the is-active classes on all the tabs.
buttons.forEach(tab_ => {
let name_ = tab_.href.split("#").pop(),
parent = tab_.parentElement;
if (name !== name_) {
console.log("button: remove is-active", tab_);
parent.classList.remove("is-active");
} else {
console.log("button %s: ADD is-active", tab_);
parent.classList.add("is-active");
}
});
$activeTab.style.display = 'block';
history.replaceState(undefined, undefined, '#'+name);
};
// Wire the tab buttons up.
buttons.forEach(el => {
let name = el.href.split("#").pop();
el.addEventListener("click", (e) => {
showTab(name);
e.preventDefault();
});
})
// Show the requested tab on first page load.
showTab(window.location.hash.replace(/^#/, ''));
});
// Location tab scripts.
window.addEventListener("DOMContentLoaded", (event) => {
// Get useful controls from the tab.
const $optionGPS = document.querySelector("#location-option-gps"),
$optionGeoIP = document.querySelector("#location-option-geoip"),
$optionPin = document.querySelector("#location-option-pin"),
$optionNone = document.querySelector("#location-option-none"),
$gpsCurrentStatus = document.querySelector("#location-status-gps"),
$gpsRefreshButton = document.querySelector("#gps-refresh"),
$saveLatitude = document.querySelector("#saveLatitude"),
$saveLongitude = document.querySelector("#saveLongitude");
// Function to massage coordinates for saving in the DB.
const saveCoords = (latitude, longitude) => {
$saveLatitude.value = latitude === null ? "" : latitude.toFixed(2);
$saveLongitude.value = longitude === null ? "" : longitude.toFixed(2);
};
// A lazy-initialized function to handle setting a new pin on the OpenStreetMap widget.
let setMapPin = null; // function(lonLat)
// The user's GeoIP coordinates, if available.
let geoIPInsights = {{ToJSON .GeoIPInsights}},
savedLocation = {{.UserLocation}};
// Default GPS coords to select on the map on page load:
// whatever we get from the backend DB.
let defaultCoords = [12.5, 41.9];
if (savedLocation.Latitude != 0 || savedLocation.Longitude != 0) {
// Prefer the DB saved coords.
defaultCoords = [
savedLocation.Longitude,
savedLocation.Latitude,
];
} else if (geoIPInsights.Latitude != 0 || geoIPInsights.Longitude != 0) {
// Then fall back on GeoIP coords.
defaultCoords = [
geoIPInsights.Longitude,
geoIPInsights.Latitude,
];
}
// Is geolocation not supported?
if (navigator.geolocation == undefined) {
$gpsCurrentStatus.className = "";
$gpsCurrentStatus.classList.add("has-text-danger");
$gpsCurrentStatus.innerHTML = "Geolocation is not supported by your browser.";
}
// Geolocation callback funcs.
const onSuccess = (pos) => {
const crd = pos.coords;
$gpsCurrentStatus.className = "";
$gpsCurrentStatus.classList.add("has-text-success-dark");
$gpsCurrentStatus.innerHTML = `Lat: ${crd.latitude}; Long: ${crd.longitude}; Accuracy: ${crd.accuracy}`;
$gpsRefreshButton.style.display = "";
// Set the form fields.
saveCoords(crd.latitude, crd.longitude);
// Can we drop a pin?
if (setMapPin !== null) {
setMapPin([crd.longitude, crd.latitude]);
}
};
const onError = (err) => {
$gpsCurrentStatus.className = "";
$gpsCurrentStatus.classList.add("has-text-danger");
$gpsCurrentStatus.innerHTML = `${err.message} (code ${err.code})`;
$gpsRefreshButton.style.display = "";
};
// Get or refresh their location.
const getLocation = () => {
if (navigator.geolocation != undefined) {
$gpsRefreshButton.style.display = "none";
$gpsCurrentStatus.className = "";
$gpsCurrentStatus.classList.add("has-text-info");
$gpsCurrentStatus.innerHTML = "Requesting your current location...";
navigator.geolocation.getCurrentPosition(onSuccess, onError, {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0,
});
}
};
// Set an onClick handler for the GPS location tab, to request
// permission from the user's browser.
$optionGPS.addEventListener("click", (e) => {
getLocation();
});
// Wire the refresh button.
$gpsRefreshButton.addEventListener("click", (e) => {
e.preventDefault();
getLocation();
});
// If the user goes back to GeoIP coords, reinitialize the defaults.
$optionGeoIP.addEventListener("click", (e) => {
// Set the form fields.
// NOTE: defaultCoords are [lon, lat] we want [lat, lon]
saveCoords(defaultCoords[1], defaultCoords[0]);
if (setMapPin !== null) {
setMapPin(defaultCoords);
}
});
// If the user disables their geolocation.
$optionNone.addEventListener("click", (e) => {
saveCoords(null, null);
});
/***
* OpenLayers Map Widget
***/
let vectorSource = new ol.source.Vector(),
vectorLayer = new ol.layer.Vector({
source: vectorSource,
});
let map = new ol.Map({
target: 'map', // the <div id="map"> target
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
vectorLayer
],
// The view allows to specify the center, resolution, and rotation of the map.
view: new ol.View({
center: ol.proj.fromLonLat(defaultCoords),
zoom: 10
})
});
// Define the function to drop a pin.
let onMapClick = (coordinate, { center=true }) => {
vectorSource.clear();
let feature = new ol.Feature(new ol.geom.Point(coordinate));
vectorSource.addFeatures([feature]);
let prettyCoord = ol.coordinate.toStringHDMS(
ol.proj.transform(coordinate, 'EPSG:3857', 'EPSG:4326'),
2,
);
console.log("COORDINATE: " + prettyCoord);
// Center the map on the pin.
if (center) {
map.setView(new ol.View({
center: coordinate,
zoom: 10
}));
}
};
setMapPin = (lonLat) => {
let center = ol.proj.fromLonLat(lonLat);
onMapClick(ol.proj.fromLonLat(lonLat), {center: true});
};
map.on('click', (e) => {
// Do not drop a pin if the option isn't set.
if (!$optionPin.checked) return;
// Save the selected coords to the form.
let center = ol.proj.toLonLat(e.coordinate);
saveCoords(center[1], center[0]);
onMapClick(e.coordinate, {center: false});
});
// Drop the initial pin at the default coords.
setMapPin(defaultCoords);
});
</script>
{{end}}