-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: OPTIC-1359: Account Settings page moved to React
- Loading branch information
1 parent
21060c3
commit c96d83b
Showing
14 changed files
with
232 additions
and
375 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,374 +1,2 @@ | ||
{% extends 'base.html' %} | ||
{% load static %} | ||
|
||
{% block head %} | ||
<link rel="stylesheet" href="{{ settings.HOSTNAME }}{% static 'css/uikit.css' %}"> | ||
<link rel="stylesheet" href="{{ settings.HOSTNAME }}{% static 'css/users.css' %}"> | ||
{% endblock %} | ||
|
||
{% block divider %} | ||
{% endblock %} | ||
|
||
{% block frontend_settings %} | ||
{ | ||
breadcrumbs: [ | ||
{ | ||
title: "Account & Settings" | ||
} | ||
], | ||
} | ||
{% endblock %} | ||
|
||
{% block content %} | ||
<script> | ||
// Take closest form (or form by id) and submit it, | ||
// optional: prevent page refresh if done passed as function | ||
function smart_submit(done, form_id) { | ||
// use form id or take closest form to the event target | ||
var f = typeof form_id === 'string' ? $(form_id) : $(event.target).closest('form'); | ||
console.log('smart_submit found form to use', f); | ||
|
||
if (typeof f === 'undefined') { | ||
console.log('smart_submit event target', event.target); | ||
alert("Closest form not found for smart_submit") | ||
} | ||
console.log('YYYYYYY' + $(f).serialize()); | ||
var params = { | ||
url: $(f).attr('action'), | ||
type: $(f).attr('method'), | ||
data: $(f).serialize(), | ||
|
||
error: function (result, textStatus, errorThrown) { | ||
console.log('smart_submit ajax error', result); | ||
|
||
// call done function if it's defined and ignore all the rest | ||
if (typeof done === "function") { | ||
done(result) | ||
} | ||
// default error handle | ||
else { | ||
if (typeof result.responseText !== 'undefined') { | ||
alert("Error: " + message_from_response(result)); | ||
} else { | ||
alert("Request error: " + errorThrown); | ||
} | ||
window.location.reload(); | ||
} | ||
}, | ||
|
||
success: function (data, textStatus, result) { | ||
// call done function if it's defined and ignore all the rest | ||
if (typeof done === "function") { | ||
done(result) | ||
} | ||
// default success handle | ||
else { | ||
window.location.reload(); | ||
} | ||
} | ||
}; | ||
|
||
console.log('smart_submit ajax params', params); | ||
$.ajax(params); | ||
event.preventDefault(); | ||
return false; | ||
} | ||
|
||
function message_from_response(result) { | ||
console.log(result); | ||
|
||
// result is dict | ||
if (result.hasOwnProperty('detail') && result.detail) | ||
return result.detail; | ||
|
||
// result is object from XHR, check responseText first, it is always presented | ||
if (!result.responseText) | ||
return 'Critical error on server'; | ||
|
||
// grab responseJSON detail | ||
else if (result.responseJSON && result.responseJSON.hasOwnProperty('detail')) | ||
return result.responseJSON['detail']; | ||
|
||
// something strange inside of responseJSON | ||
else if (result.responseJSON) | ||
return JSON.stringify(result.responseJSON); | ||
|
||
else | ||
return JSON.stringify(result) | ||
} | ||
</script> | ||
|
||
<div class="full_content"> | ||
<div class="account-page"> | ||
<form action="{% url 'user-detail' pk=user.pk %}" method="patch" class="user__info"> | ||
<input type="hidden" name="_method" value="patch"/> | ||
|
||
<header>Account info</header> | ||
<ul> | ||
<li class="field"> | ||
<label for="">E-mail</label> | ||
<input type="text" class="lsf-input-ls" value="{{user.email}}" disabled /> | ||
</li> | ||
<li class="field"> | ||
<label for="">First Name</label> | ||
<input type="text" class="lsf-input-ls" name="first_name" value="{{user.first_name}}" /> | ||
</li> | ||
<li class="field"> | ||
<label for="">Last Name</label> | ||
<input type="text" class="lsf-input-ls" name="last_name" value="{{user.last_name}}" /> | ||
</li> | ||
<li class="field"> | ||
<label for="">Phone</label> | ||
<input type="text" class="lsf-input-ls" name="phone" value="{{user.phone}}" /> | ||
<!-- <span>We'll send you sms with code if you change your number</span> | ||
<span class="error">Incorrect phone number!</span> --> | ||
</li> | ||
</ul> | ||
<div class="user-some-actions"> | ||
|
||
<div class="user-pic {{ user.avatar|yesno:'can_delete,can_upload' }}"> | ||
<div class="userpic userpic--big"> | ||
{% if user.avatar %} | ||
<img src="{{user.avatar_url}}" alt="User photo" width="92" /> | ||
{% endif %} | ||
|
||
{% if user.get_initials %} | ||
<span>{{user.get_initials}}</span> | ||
{% else %} | ||
<span>{{user.username}}</span> | ||
{% endif %} | ||
</div> | ||
|
||
<button class="lsf-button-ls lsf-button-ls_look_danger" name="delete-avatar" type="button"> | ||
Delete | ||
</button> | ||
|
||
<input class="file-input" type="file" name="avatar" value="Choose" | ||
accept="image/png, image/jpeg, image/jpg"/> | ||
</div> | ||
|
||
<!-- <div class="user-activity"> | ||
<p>Inspect all your actions<br/>performed on the platform</p> | ||
<button type="button">Activity Log<img src="" /></button> | ||
</div> --> | ||
</div> | ||
<footer> | ||
<p class="secondary">Registered {{ user.date_joined|date:"M j, Y" }}, user ID {{ user.id }}</p> | ||
<button class="lsf-button-ls lsf-button-ls_look_primary" onclick="smart_submit()">Save</button> | ||
</footer> | ||
</form> | ||
|
||
<!-- Token --> | ||
<form action="" class="access_token__info"> | ||
<header>Access Token</header> | ||
<div class="field field--wide"> | ||
<label for="access_token">Use this token to authenticate with our API:</label> | ||
<input id="access_token" class="lsf-input-ls" name="access_token" type="text" value="{{token}}" readonly /> | ||
<p class="actions"> | ||
<button type="button" class="blinking-status lsf-button-ls" data-copy="access_token">Copy</button> | ||
<button type="button" class="blinking-status lsf-button-ls" name="renew">Renew</button> | ||
</p> | ||
</div> | ||
<!-- Example --> | ||
<div class="field field--wide"> | ||
<label for="example_fetch">Example fetch projects data:</label> | ||
<textarea id="example_fetch" class="example_code ls-textarea" type="text" readonly | ||
style="height: 92px; font-size: 14px"> | ||
{% if settings.HOSTNAME %} | ||
curl -X GET {{ settings.HOSTNAME }}/api/projects/ -H 'Authorization: Token {{token}}' | ||
{% else %} | ||
curl -X GET {{ request.scheme }}://{{ request.get_host }}/api/projects/ -H 'Authorization: Token {{token}}' | ||
{% endif %} | ||
</textarea> | ||
<p class="actions"> | ||
<button type="button" class="blinking-status lsf-button-ls" data-copy="example_fetch">Copy</button> | ||
|
||
{% block api_docs %} | ||
<a | ||
class="lsf-button-ls" | ||
href="https://labelstud.io/guide/api.html" | ||
target="_blank" | ||
> | ||
Documentation | ||
</a> | ||
{% endblock %} | ||
</p> | ||
</div> | ||
</form> | ||
|
||
|
||
<!-- Organization --> | ||
<form action="" class="organization block-info" id="organization"> | ||
<header> | ||
{{ user.active_organization.title }} | ||
<br> | ||
<sub>Your active organization</sub> | ||
</header> | ||
|
||
<table> | ||
{% with user.get_pretty_role as role %} | ||
{% if role %} | ||
<tr><td>Your role</td><td>{{ user.get_pretty_role }}</td></tr> | ||
{% endif %} | ||
{% endwith %} | ||
<tr><td>Annotations completed by you</td><td>{{ user.active_organization_annotations.count }}</td></tr> | ||
<tr><td>Projects contributed by you</td><td>{{ user.active_organization_contributed_project_number }}</td></tr> | ||
<tr><td></td><td></td></tr> | ||
<tr><td style="min-width: 75px">Organization ID</td><td>{{ user.active_organization.id }}</td></tr> | ||
<tr><td>Organization owner</td><td>{{ user.active_organization.created_by }}</td></tr> | ||
<tr><td>Organization created at</td><td>{{ user.active_organization.created_at }}</td></tr> | ||
</table> | ||
|
||
</form> | ||
|
||
<!-- Notifications --> | ||
{% block notifications %} | ||
<form action="{% url 'user-detail' pk=user.pk %}?update-notifications=1" method="patch" class="notifications block-info" id="notifications"> | ||
<header> | ||
Notifications | ||
<br> | ||
<sub>Email and other notifications</sub> | ||
</header> | ||
|
||
<table> | ||
<tr><td style="{% if user.allow_newsletters is None %}border: 1px red solid; border-radius: 5px{% endif %}"> | ||
|
||
<input name="allow_newsletters" type="hidden" | ||
value="{% if user.allow_newsletters is None %}true{% else %}{{ user.allow_newsletters|yesno:"false,true" }}{% endif %}"> | ||
|
||
<input name="allow_newsletters_visual" id="allow_newsletters_visual" type="checkbox" | ||
style="width: auto;" | ||
{% if user.allow_newsletters %}checked="true"{% endif %} | ||
onclick="smart_submit()"> | ||
|
||
<label for="allow_newsletters_visual" style="display: inline-block; cursor: pointer; margin-top: -10px"> | ||
Get the latest news & tips from Heidi | ||
<img src="{{ settings.HOSTNAME }}{% static 'images/heidi.png' %}" alt="Heidi" | ||
width="25" style="display: inline; margin: 0; position: relative; top: 5px; left: 0"> | ||
</label> | ||
|
||
</td></tr> | ||
</table> | ||
|
||
</form> | ||
{% endblock %} | ||
|
||
|
||
</div> | ||
|
||
<script> | ||
(() => { | ||
{% if settings.HOSTNAME %} | ||
const hostname = '{{ settings.HOSTNAME }}'; | ||
{% else %} | ||
const hostname = '{{ request.scheme }}://{{ request.get_host }}'; | ||
{% endif %} | ||
|
||
document.querySelectorAll('[data-copy]').forEach(button => { | ||
button.onclick = e => { | ||
const input = document.getElementById(e.target.dataset.copy); | ||
input.select(); | ||
document.execCommand("copy"); | ||
input.setSelectionRange(0, 0); | ||
input.blur(); | ||
button.classList.add('blink'); | ||
setTimeout(() => button.classList.remove('blink')) | ||
} | ||
}); | ||
|
||
document.querySelector('[name=renew]').onclick = e => { | ||
const button = e.target; | ||
const input = document.getElementById("access_token"); | ||
const example = document.getElementById("example_fetch"); | ||
|
||
fetch("{% url 'current-user-reset-token' %}", { method: "POST" }) | ||
.then(res => res.json()) | ||
.then(res => { | ||
input.value = res.token; | ||
example.value = `curl -X GET ${hostname}/api/projects/ -H 'Authorization: Token ${res.token}'` | ||
button.classList.add('blink'); | ||
setTimeout(() => button.classList.remove('blink')) | ||
}); | ||
}; | ||
|
||
$('[name=avatar]').on('change', async (e) => { | ||
const formData = new FormData; | ||
|
||
formData.append(e.target.name, e.target.files[0]); | ||
|
||
try { | ||
const response = await fetch("{% url 'user-avatar' pk=user.pk %}", { | ||
method: "post", | ||
body: formData | ||
}); | ||
|
||
if (!response.ok) { | ||
handleResponseError(response) | ||
} else { | ||
updateAvatar(true) | ||
} | ||
} catch (err) { | ||
console.error(err) | ||
} | ||
}); | ||
|
||
$('[name=delete-avatar]').on('click', async (e) => { | ||
try { | ||
const response = await fetch("{% url 'user-avatar' pk=user.pk %}", { | ||
method: "delete" | ||
}) | ||
|
||
if (!response.ok) { | ||
handleResponseError(response) | ||
} else { | ||
updateAvatar(false) | ||
} | ||
} catch (err) { | ||
console.err(err) | ||
} | ||
}) | ||
|
||
/** | ||
* @param {Response} response | ||
*/ | ||
const handleResponseError = (response) => { | ||
response.json().then(data => { | ||
alert(message_from_response(data)); | ||
}) | ||
} | ||
|
||
const updateAvatar = async (setAvatar = true) => { | ||
if (setAvatar) { | ||
const response = await fetch("{% url 'current-user-whoami' %}") | ||
|
||
if (response.ok) { | ||
const {avatar} = await response.json() | ||
const userpic = document.querySelector('.userpic') | ||
|
||
let userpicImage = userpic.querySelector('img') | ||
|
||
if (!userpicImage) { | ||
userpicImage = document.createElement('img') | ||
userpic.insertBefore(userpicImage, userpic.firstChild); | ||
} | ||
|
||
userpicImage.src = avatar | ||
|
||
const userpicRoot = document.querySelector('.user-pic'); | ||
userpicRoot.classList.remove('can_delete', 'can_upload') | ||
userpicRoot.classList.add('can_delete') | ||
} | ||
} else { | ||
const userpic = document.querySelector('.user-pic') | ||
const userpicImage = userpic.querySelector('img') | ||
if (userpicImage) userpicImage.remove(); | ||
|
||
userpic.classList.remove('can_delete', 'can_upload') | ||
userpic.classList.add('can_upload') | ||
} | ||
} | ||
})(); | ||
</script> | ||
</div> | ||
|
||
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.