[profile] better datepicker, better field validation

This commit is contained in:
Avris 2021-12-11 10:55:32 +01:00
parent 08718d65dc
commit 4d677d7af8
10 changed files with 70 additions and 14 deletions

View File

@ -0,0 +1,6 @@
-- Up
UPDATE profiles SET birthday = null WHERE birthday > '2008-12-11' OR birthday < '1900-01-01';
-- Down

View File

@ -117,6 +117,7 @@ export default {
{ src: '~/plugins/vue-matomo.js', ssr: false },
{ src: '~/plugins/globals.js' },
{ src: '~/plugins/auth.js' },
{ src: '~/plugins/datepicker.js', ssr: false },
],
components: true,
buildModules: [],

View File

@ -53,6 +53,7 @@
"vue-lazy-hydration": "^2.0.0-beta.4",
"vue-matomo": "^3.13.5-0",
"vuedraggable": "^2.24.3",
"vuejs-datepicker": "^1.6.2",
"webpack": "^5.0",
"zh_cn_zh_tw": "^1.0.7"
},

6
plugins/datepicker.js Normal file
View File

@ -0,0 +1,6 @@
import Vue from 'vue'
import VuejsDatePicker from 'vuejs-datepicker'
export default ({ app }) => {
Vue.component('datepicker', VuejsDatePicker);
}

View File

@ -168,15 +168,16 @@
<h3 class="h4">
<Icon v="birthday-cake"/>
<T>profile.birthday</T>
<button type="button" class="btn btn-outline-danger btn-sm" v-if="birthday !== null" @click="birthday = null">
<Icon v="times"/>
<T>crud.remove</T>
</button>
</h3>
<p class="small text-muted mb-0">
<p class="small text-muted">
<T>profile.birthdayInfo</T>
</p>
<div class="input-group mb-3">
<input type="date" class="form-control form-control-sm" v-model="birthday"/>
<button type="button" class="btn btn-outline-danger btn-sm" v-if="birthday !== null" @click="birthday = null">
<Icon v="times"/>
</button>
<datepicker v-model="birthday" inline :disabled-dates="disabledDates" initial-view="year"/>
</div>
</section>
@ -214,6 +215,7 @@
import { buildPronoun } from "../src/buildPronoun";
import config from '../data/config.suml';
import link from '../plugins/link';
import {minBirthdate, maxBirthdate, formatDate} from '../src/birthdate';
const defaultWords = config.profile.defaultWords.map(c => buildList(function* () {
for (let word of c) {
@ -226,6 +228,10 @@
data() {
return {
saving: false,
disabledDates: {
to: minBirthdate,
from: maxBirthdate,
},
};
},
async asyncData({ app, store }) {
@ -316,7 +322,7 @@
names: listToDict(this.names),
pronouns: listToDict(this.pronouns),
description: this.description,
birthday: this.birthday,
birthday: this.birthday ? formatDate(this.birthday) : null,
links: [...this.links],
flags: [...this.flags],
customFlags: {...this.customFlags},

View File

@ -6,6 +6,7 @@ import avatar from "../avatar";
import {handleErrorAsync} from "../../src/helpers";
import { caches } from "../../src/cache";
import fs from 'fs';
import { minBirthdate, maxBirthdate, formatDate, parseDate } from '../../src/birthdate';
const normalise = s => s.trim().toLowerCase();
@ -15,11 +16,7 @@ const calcAge = birthday => {
}
const now = new Date();
const birth = new Date(
parseInt(birthday.substring(0, 4)),
parseInt(birthday.substring(5, 7)) - 1,
parseInt(birthday.substring(8, 10))
);
const birth = parseDate(birthday);
const diff = now.getTime() - birth.getTime();
@ -112,6 +109,19 @@ router.get('/profile/get/:username', handleErrorAsync(async (req, res) => {
});
}));
const sanitiseBirthday = (bd) => {
if (!bd) { return null; }
const match = bd.match(/^(\d\d\d\d)-(\d\d)-(\d\d)$/);
if (!match) { return null; }
bd = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]));
if (bd < minBirthdate || bd > maxBirthdate) {
return null;
}
return formatDate(bd);
}
router.post('/profile/save', handleErrorAsync(async (req, res) => {
if (!req.user) {
return res.status(401).json({error: 'Unauthorised'});
@ -125,7 +135,7 @@ router.post('/profile/save', handleErrorAsync(async (req, res) => {
names = ${JSON.stringify(req.body.names)},
pronouns = ${JSON.stringify(req.body.pronouns)},
description = ${req.body.description},
birthday = ${req.body.birthday || null},
birthday = ${sanitiseBirthday(req.body.birthday || null)},
links = ${JSON.stringify(req.body.links.filter(x => !!x))},
flags = ${JSON.stringify(req.body.flags)},
customFlags = ${JSON.stringify(req.body.customFlags)},
@ -143,7 +153,7 @@ router.post('/profile/save', handleErrorAsync(async (req, res) => {
} else {
await req.db.get(SQL`INSERT INTO profiles (id, userId, locale, names, pronouns, description, birthday, links, flags, customFlags, words, active, teamName, footerName, footerAreas)
VALUES (${ulid()}, ${req.user.id}, ${global.config.locale}, ${JSON.stringify(req.body.names)}, ${JSON.stringify(req.body.pronouns)},
${req.body.description}, ${req.body.birthday || null}, ${JSON.stringify(req.body.links.filter(x => !!x))}, ${JSON.stringify(req.body.flags)}, ${JSON.stringify(req.body.customFlags)},
${req.body.description}, ${sanitiseBirthday(req.body.birthday || null)}, ${JSON.stringify(req.body.links.filter(x => !!x))}, ${JSON.stringify(req.body.flags)}, ${JSON.stringify(req.body.customFlags)},
${JSON.stringify(req.body.words)}, 1,
${req.isGranted() ? req.body.teamName || null : ''},
${req.isGranted() ? req.body.footerName || null : ''},

18
src/birthdate.js Normal file
View File

@ -0,0 +1,18 @@
const today = new Date();
const minBirthdate = new Date(1900, 0, 1);
const maxBirthdate = new Date(today.getFullYear()-13, today.getMonth(), today.getDate())
module.exports = {
minBirthdate,
maxBirthdate,
formatDate(bd) {
return `${bd.getFullYear()}-${('0' + (bd.getMonth() + 1)).slice(-2)}-${('0' + bd.getDate()).slice(-2)}`;
},
parseDate(bd) {
return new Date(
parseInt(bd.substring(0, 4)),
parseInt(bd.substring(5, 7)) - 1,
parseInt(bd.substring(8, 10))
)
}
}

View File

@ -37,7 +37,7 @@ const templates = {
base: {
subject: `[[title]] » {{content}}`,
html: `
<div style="margin: 36px auto; width: 100%; max-width: 480px; border: 1px solid #aaa;border-radius: 8px;overflow: hidden;font-family: Helvetica, sans-serif;font-size: 16px;box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15)">
<div style="margin: 36px auto; width: 100%; max-width: 480px; border: 1px solid #aaa;border-radius: 8px;overflow: hidden;font-family: Nunito, Quicksand, Helvetica, sans-serif;font-size: 16px;box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15)">
<div style="padding: 16px; padding-top: 10px; background: #f8f8f8; border-bottom: 1px solid #aaa;font-size: 20px;color: ${color};">
<img src="${logoEncoded}" style="height: 24px;width: 24px; position: relative; top: 6px; margin-right: 6px;" alt="Logo"/>
[[title]]

View File

@ -45,6 +45,9 @@ const buildChart = (rows) => {
chart[formatMonth(loop)] = 0;
loop = new Date(loop.setDate(loop.getDate() + 1));
}
if (!loop) {
return {};
}
chart[formatMonth(loop)] = 0;
for (let date of dates) {

View File

@ -10291,6 +10291,11 @@ vuedraggable@^2.24.3:
dependencies:
sortablejs "1.10.2"
vuejs-datepicker@^1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/vuejs-datepicker/-/vuejs-datepicker-1.6.2.tgz#83c1e8fd4108e7f1d01c061a7e344918f25e47ae"
integrity sha512-PkC4vxzFBo7i6FSCUAJfnaWOx6VkKbOqxijSGHHlWxh8FIUKEZVtFychkonVWtK3iwWfhmYtqHcwsmgxefLpLQ==
vuex@^3.6.2:
version "3.6.2"
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71"