mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-03-21 17:24:28 -05:00
feat(account): add basic account management page + Discord OAuth
This commit is contained in:
parent
b4ddffac50
commit
aef4036212
|
|
@ -1,10 +1,15 @@
|
|||
{
|
||||
"http": {
|
||||
"port": 80
|
||||
"port": 80,
|
||||
"base_url": "http://localhost:80"
|
||||
},
|
||||
"trello": {
|
||||
"api_key": "key",
|
||||
"api_token": "token",
|
||||
"board_name": "name"
|
||||
},
|
||||
"discord": {
|
||||
"client_id": "client_id",
|
||||
"client_secret": "client_secret"
|
||||
}
|
||||
}
|
||||
27
package-lock.json
generated
27
package-lock.json
generated
|
|
@ -378,6 +378,11 @@
|
|||
"which": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"data-uri-to-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
|
||||
"integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
|
|
@ -718,6 +723,14 @@
|
|||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||
"dev": true
|
||||
},
|
||||
"fetch-blob": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.2.tgz",
|
||||
"integrity": "sha512-hunJbvy/6OLjCD0uuhLdp0mMPzP/yd2ssd1t2FCJsaA7wkWhpbp9xfuNVpv7Ll4jFhzp6T4LAupSiV9uOeg0VQ==",
|
||||
"requires": {
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"file-entry-cache": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||
|
|
@ -1168,6 +1181,15 @@
|
|||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
|
||||
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0.tgz",
|
||||
"integrity": "sha512-bKMI+C7/T/SPU1lKnbQbwxptpCrG9ashG+VkytmXCPZyuM9jB6VU+hY0oi4lC8LxTtAeWdckNCTa3nrGsAdA3Q==",
|
||||
"requires": {
|
||||
"data-uri-to-buffer": "^3.0.1",
|
||||
"fetch-blob": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
|
@ -1668,6 +1690,11 @@
|
|||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"web-streams-polyfill": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.1.1.tgz",
|
||||
"integrity": "sha512-Czi3fG883e96T4DLEPRvufrF2ydhOOW1+1a6c3gNjH2aIh50DNFBdfwh2AKoOf1rXvpvavAoA11Qdq9+BKjE0Q=="
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
"ioredis": "^4.26.0",
|
||||
"marked": "^3.0.4",
|
||||
"morgan": "^1.10.0",
|
||||
"node-fetch": "^3.0.0",
|
||||
"redis-json": "^5.0.0",
|
||||
"trello": "^0.10.0"
|
||||
},
|
||||
|
|
|
|||
226
public/assets/css/account.css
Normal file
226
public/assets/css/account.css
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
.account-wrapper {
|
||||
display: grid;
|
||||
column-gap: 60px;
|
||||
margin-top: 80px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Account settings navbar */
|
||||
.account-sidebar .user {
|
||||
text-align: center;
|
||||
margin: 55px auto;
|
||||
width: fit-content;
|
||||
}
|
||||
.account-sidebar .user .miiname {
|
||||
font-size: 1.3rem;
|
||||
color: var(--text);
|
||||
margin: 8px 0 4px;
|
||||
}
|
||||
.account-sidebar .user .username {
|
||||
margin: 0px;
|
||||
}
|
||||
.account-sidebar .user .mii {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border-radius: 100%;
|
||||
background: var(--btn-secondary);
|
||||
}
|
||||
|
||||
/* Settings */
|
||||
.settings-wrapper {
|
||||
display: grid;
|
||||
grid-column-start: 2;
|
||||
grid-template-columns: 50%;
|
||||
column-gap: 20px;
|
||||
}
|
||||
.settings-wrapper a {
|
||||
color: #9d6ff3;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
.settings-wrapper a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.settings-wrapper h2.section-header {
|
||||
margin-top: 40px;
|
||||
grid-column: 1 / 3;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.setting-card {
|
||||
display: grid;
|
||||
grid-template-rows: 35px repeat(2, auto);
|
||||
row-gap: 24px;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
background: #2a2f50;
|
||||
padding: 48px 60px;
|
||||
}
|
||||
.setting-card * {
|
||||
margin: 0;
|
||||
}
|
||||
.setting-card .edit {
|
||||
color: var(--text-secondary);
|
||||
background: #383f6b;
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
top: 42px;
|
||||
right: 48px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 12px;
|
||||
}
|
||||
.setting-card .edit:hover {
|
||||
background: #383f6b;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.setting-card .header {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.setting-card .setting-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, auto);
|
||||
gap: 24px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.setting-card .setting-list p.label {
|
||||
color: var(--text);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
height: min-content;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.setting-card .server-selection {
|
||||
display: flex;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
background: #383f6b;
|
||||
}
|
||||
.setting-card .server-selection input {
|
||||
display: none;
|
||||
}
|
||||
.server-selection input + label {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
flex: 50%;
|
||||
color: var(--text-secondary);
|
||||
padding: 40px;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
}
|
||||
.server-selection input + label h2 {
|
||||
margin-top: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.server-selection input:checked + label,
|
||||
.server-selection input:checked + label h2 {
|
||||
background: var(--theme);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.setting-card.sign-in-history a button {
|
||||
width: 100%;
|
||||
padding: 12px 48px;
|
||||
cursor: pointer;
|
||||
background: #383f6b;
|
||||
}
|
||||
.setting-card input[type="checkbox"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background: #383f6b;
|
||||
padding: 12px;
|
||||
margin: 4px;
|
||||
margin-left: 0;
|
||||
border-radius: 4px;
|
||||
vertical-align: -65%;
|
||||
}
|
||||
.setting-card input[type="checkbox"]:checked {
|
||||
background: no-repeat center/contain url(../images/check.svg), var(--theme);
|
||||
}
|
||||
|
||||
.setting-card.span-both-columns {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
.account-link-notice {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: -150px;
|
||||
width: 100%;
|
||||
animation: account-link-notice 5s;
|
||||
}
|
||||
|
||||
@keyframes account-link-notice {
|
||||
0% {top: -150px}
|
||||
20% {top: 35px}
|
||||
80% {top: 35px}
|
||||
100% {top: -150px}
|
||||
}
|
||||
|
||||
.account-link-notice div {
|
||||
background: #37A985;
|
||||
padding: 4px 36px;
|
||||
border-radius: 5px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1500px) {
|
||||
.account-wrapper {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.settings-wrapper {
|
||||
grid-column-start: 1;
|
||||
}
|
||||
|
||||
.account-sidebar {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.account-sidebar .user .mii {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1050px) {
|
||||
.settings-wrapper {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.setting-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.setting-card {
|
||||
padding: 24px;
|
||||
}
|
||||
.setting-card .edit {
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
|
||||
.setting-card .setting-list {
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
|
||||
.setting-card .server-selection {
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
185
src/routers/account.js
Normal file
185
src/routers/account.js
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
const { Router } = require('express');
|
||||
const util = require('../util');
|
||||
const router = new Router();
|
||||
const fetch = (...args) =>
|
||||
import('node-fetch').then(({ default: fetch }) => fetch(...args));
|
||||
const config = require('../../config.json');
|
||||
|
||||
// A test PNID
|
||||
const account = {
|
||||
access_level: 0, // 0: standard, 1: tester, 2: mod?, 3. dev?, 4. god
|
||||
server_access_level: 'prod', // prod, test, dev
|
||||
username: 'PN_Monty',
|
||||
password: 'thelegend27',
|
||||
birthdate: '13/04/2004', // what format
|
||||
gender: 'what even is this', // what format
|
||||
country: 'Italy', // what format
|
||||
language: 'JS smh', // what format
|
||||
email: {
|
||||
address: 'notascam@freecreditreport.com',
|
||||
primary: true, // do we need to let the user change anything?
|
||||
parent: true,
|
||||
reachable: true,
|
||||
validated: true,
|
||||
},
|
||||
region: 'don\'t know the numbers',
|
||||
timezone: {
|
||||
name: 'Europe/Rome',
|
||||
offset: 3600, // haven't checked if this is correct, just an assumption
|
||||
},
|
||||
mii: {
|
||||
name: 'Monty',
|
||||
image_url:
|
||||
'https://studio.mii.nintendo.com/miis/image.png?data=00080f50595a606a6268696f757883969b9aa1a8b1b8b7bebdc5cccbd1d8620a121a181119111916222d3444484c4b&type=face&expression=normal&width=512',
|
||||
},
|
||||
flags: {
|
||||
marketing: true,
|
||||
off_device: true, // Forgot what this does
|
||||
},
|
||||
devices: [
|
||||
{
|
||||
is_emulator: {
|
||||
type: false,
|
||||
},
|
||||
console_type: {
|
||||
type: 'wup',
|
||||
},
|
||||
device_attributes: {
|
||||
created_date: '23/07/2021', // what format
|
||||
name: 'Wii U', // ?
|
||||
value: 'what', // what format
|
||||
},
|
||||
},
|
||||
{
|
||||
is_emulator: {
|
||||
type: false,
|
||||
},
|
||||
console_type: {
|
||||
type: 'wup',
|
||||
},
|
||||
device_attributes: {
|
||||
created_date: '24/05/2020',
|
||||
name: 'Windows', // ?
|
||||
value: 'what',
|
||||
},
|
||||
},
|
||||
],
|
||||
connections: { // This needs to be added to the schema
|
||||
discord: {
|
||||
id: '406125028065804289',
|
||||
access_token: 'GiJ2Osi6LpYS7uLgZNyvCbRtpRopv1', // This only has the identify scope so eh, who cares if it gets leaked
|
||||
expires_on: 604800,
|
||||
refresh_token: 'VeQMa6zp2Rx77PjhiNFJbpKrpz2gX2',
|
||||
scope: 'identify',
|
||||
token_type: 'Bearer',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
const justLinked = request.cookies.justLinked;
|
||||
const reqLocale = request.locale;
|
||||
const locale = util.getLocale(reqLocale.region, reqLocale.language);
|
||||
|
||||
let discordUser;
|
||||
|
||||
if (account.connections.discord.access_token) {
|
||||
// TODO: check if the token is valid, if not refresh it.
|
||||
const discord = account.connections.discord;
|
||||
const fetchDiscordUser = await fetch('https://discord.com/api/users/@me', {
|
||||
method: 'get',
|
||||
headers: {
|
||||
'Authorization': `${discord.token_type} ${discord.access_token}`
|
||||
},
|
||||
});
|
||||
discordUser = await fetchDiscordUser.json();
|
||||
}
|
||||
|
||||
// test user
|
||||
discordUser = {
|
||||
id: '406125028065804289',
|
||||
username: 'montylion',
|
||||
avatar: '5184b3dc388bdad1a93eb58694a58398',
|
||||
discriminator: '3581',
|
||||
public_flags: 64,
|
||||
flags: 64,
|
||||
banner: null,
|
||||
banner_color: '#ff7081',
|
||||
accent_color: 16740481,
|
||||
locale: 'en-US',
|
||||
mfa_enabled: true,
|
||||
};
|
||||
|
||||
const isTester = () => {
|
||||
return account.access_level > 0;
|
||||
};
|
||||
|
||||
response.clearCookie('justLinked');
|
||||
|
||||
// TODO: make the sign in history only show the first 2/4 devices
|
||||
|
||||
response.render('account', {
|
||||
layout: 'main',
|
||||
locale,
|
||||
localeString: reqLocale.toString(),
|
||||
account,
|
||||
isTester,
|
||||
discordUser,
|
||||
justLinked
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/connect/discord', async (request, response) => {
|
||||
//const reqLocale = request.locale;
|
||||
//const locale = util.getLocale(reqLocale.region, reqLocale.language);
|
||||
|
||||
const codeExchange = await fetch('https://discord.com/api/oauth2/token', {
|
||||
method: 'post',
|
||||
body: new URLSearchParams({
|
||||
client_id: config.discord.client_id,
|
||||
client_secret: config.discord.client_secret,
|
||||
grant_type: 'authorization_code',
|
||||
code: request.query.code,
|
||||
redirect_uri: `${config.http.base_url}/account/connect/discord`,
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
});
|
||||
const token = await codeExchange.json();
|
||||
|
||||
/*
|
||||
// Send the oauth tokens to the account server
|
||||
fetch('account server endpoint', {
|
||||
method: 'post',
|
||||
body: {
|
||||
access_token: token.access_token,
|
||||
token_type: token.token_type,
|
||||
expires_on: new Date().getTime() + token.expires_in,
|
||||
refresh_token: token.refresh_token,
|
||||
scope: token.scope,
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
/* Token refresh function (should be moved to util.js)
|
||||
const refreshTokens = await fetch('https://discord.com/api/oauth2/token', {
|
||||
method: 'post',
|
||||
body: new URLSearchParams({
|
||||
client_id: config.discord.client_id,
|
||||
client_secret: config.discord.client_secret,
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: account.connections.discord.refresh_token
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
});
|
||||
const newTokens = await refreshTokens.json();
|
||||
console.log(newTokens);
|
||||
*/
|
||||
|
||||
// This sets a cookie to tell the account page to show the "Account linked successfully" notice, and redirects.
|
||||
response.cookie('justLinked', 'discord').redirect('/account');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -23,6 +23,7 @@ const routers = {
|
|||
home: require('./routers/home'),
|
||||
faq: require('./routers/faq'),
|
||||
progress: require('./routers/progress'),
|
||||
account: require('./routers/account'),
|
||||
blog: require('./routers/blog'),
|
||||
localization: require('./routers/localization')
|
||||
};
|
||||
|
|
@ -70,6 +71,7 @@ app.use(expressLocale({
|
|||
app.use('/', routers.home);
|
||||
app.use('/faq', routers.faq);
|
||||
app.use('/progress', routers.progress);
|
||||
app.use('/account', routers.account);
|
||||
app.use('/localization', routers.localization);
|
||||
app.use('/blog', routers.blog);
|
||||
|
||||
|
|
|
|||
152
views/account.handlebars
Normal file
152
views/account.handlebars
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<link rel="stylesheet" href="/assets/css/account.css" />
|
||||
|
||||
<div class="wrapper">
|
||||
|
||||
{{> header}}
|
||||
|
||||
<div class="account-wrapper">
|
||||
<div class="account-sidebar">
|
||||
<div class="user">
|
||||
<img src="{{account.mii.image_url}}&bgColor=3E446600" class="mii" />
|
||||
<p class="miiname">{{account.mii.name}}</p>
|
||||
<p class="username">PNID: {{account.username}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-wrapper">
|
||||
<h2 class="section-header" id="user-settings">User settings</h2>
|
||||
<div class="setting-card">
|
||||
<h2 class="header">Profile</h2>
|
||||
<a href="/account/edit/profile" class="edit">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<p class="label">Nickname</p>
|
||||
<p class="value">{{account.mii.name}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">Birth date</p>
|
||||
<p class="value">{{account.birthdate}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">Gender</p>
|
||||
<p class="value">{{account.gender}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">Country/region</p>
|
||||
<p class="value">{{account.country}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">Timezone</p>
|
||||
<p class="value">{{account.timezone.name}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="setting-card">
|
||||
<h2 class="header">Servers</h2>
|
||||
<fieldset {{#if isTester}}{{else}}disabled{{/if}}>
|
||||
<form class="server-selection" id="server">
|
||||
<input type="radio" id="prod" name="server_selection" value="prod">
|
||||
<label for="prod">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||||
<polyline points="7.5 4.21 12 6.81 16.5 4.21"></polyline>
|
||||
<polyline points="7.5 19.79 7.5 14.6 3 12"></polyline>
|
||||
<polyline points="21 12 16.5 14.6 16.5 19.79"></polyline>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
|
||||
<line x1="12" y1="22.08" x2="12" y2="12"></line>
|
||||
</svg>
|
||||
<h2>Production</h2>
|
||||
</label>
|
||||
<input type="radio" id="beta" name="server_selection" value="beta">
|
||||
<label for="beta">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="2,21 22,21 14,11.5 14,5 10,3 10,11.5"></polygon>
|
||||
</svg>
|
||||
<h2>Beta</h2>
|
||||
</label>
|
||||
</form>
|
||||
</fieldset>
|
||||
{{#if discordUser}}
|
||||
{{#if isTester }}
|
||||
<p>Connected as {{ discordUser.username }}#{{ discordUser.discriminator }}.</p>
|
||||
{{else}}
|
||||
<p>{{ discordUser.username }}#{{ discordUser.discriminator }} doesn't appear to be a @Tester account. Check that you have the role on Discord. [should probably fix the wording]</p>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<p>Beta servers are exclusive to testers. If you're a tester, connect your Discord account <a href="https://discord.com/api/oauth2/authorize?client_id=896508671742345266&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Faccount%2Fconnect%2Fdiscord&response_type=code&scope=identify">here</a>.</p>
|
||||
{{/if}}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<h2 class="section-header" id="security">Sign in and security</h2>
|
||||
<div class="setting-card">
|
||||
<h2 class="header">Account</h2>
|
||||
<a href="/account/edit/login-info" class="edit">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<p class="label">Email</p>
|
||||
<p class="value">{{account.email.address}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">Password</p>
|
||||
<p class="value">●●●●●●●●</p> <!-- Should remain hardcoded -->
|
||||
</li>
|
||||
</ul>
|
||||
<p>After changing your password, you will be signed out from all devices.</p>
|
||||
</div>
|
||||
|
||||
<div class="setting-card sign-in-history">
|
||||
<h2 class="header">Sign in history</h2>
|
||||
<ul class="setting-list">
|
||||
{{#each account.devices }}
|
||||
<li>
|
||||
<p class="label">{{this.device_attributes.name}}</p>
|
||||
<p class="value">{{this.device_attributes.created_date}}</p>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<a href="/account/sign-in-history">
|
||||
<button class="button secondary">View full sign in history</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 class="section-header" id="other">Other settings</h2>
|
||||
<div class="setting-card span-both-columns">
|
||||
<h2 class="header">Newsletter</h2>
|
||||
<form id="other">
|
||||
<input type="checkbox" id="marketing" name="marketing" {{#if account.flags.marketing}}checked{{/if}}>
|
||||
<label for="marketing">Receive project updates via email (you can opt-out at any time)</label>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{> footer }}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{{#if justLinked}}
|
||||
<div class="account-link-notice">
|
||||
<div>
|
||||
<p>Account linked successfully.</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<script>
|
||||
// Selectes the server level from the user's PNID
|
||||
{{ account.server_access_level }}.setAttribute('checked', true);
|
||||
</script>
|
||||
Loading…
Reference in New Issue
Block a user