[profile] better datepicker, better field validation
This commit is contained in:
parent
08718d65dc
commit
4d677d7af8
|
@ -0,0 +1,6 @@
|
|||
-- Up
|
||||
|
||||
UPDATE profiles SET birthday = null WHERE birthday > '2008-12-11' OR birthday < '1900-01-01';
|
||||
|
||||
-- Down
|
||||
|
|
@ -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: [],
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import Vue from 'vue'
|
||||
import VuejsDatePicker from 'vuejs-datepicker'
|
||||
|
||||
export default ({ app }) => {
|
||||
Vue.component('datepicker', VuejsDatePicker);
|
||||
}
|
|
@ -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},
|
||||
|
|
|
@ -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 : ''},
|
||||
|
|
|
@ -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))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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]]
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
Reference in New Issue