This commit is contained in:
Jamie Curnow
2025-09-02 23:56:00 +10:00
parent 330993f028
commit fadec9751e
355 changed files with 9308 additions and 17813 deletions

View File

@@ -0,0 +1,89 @@
import { getUnixTime, parseISO } from "date-fns";
import type { TokenResponse } from "src/api/backend";
export const TOKEN_KEY = "authentications";
export class AuthStore {
// Get all tokens from stack
get tokens() {
const t = localStorage.getItem(TOKEN_KEY);
let tokens = [];
if (t !== null) {
try {
tokens = JSON.parse(t);
} catch (e) {
console.error("Failed to parse tokens from localStorage", e);
}
}
return tokens;
}
// Get last token from stack
get token() {
const t = this.tokens;
if (t.length) {
return t[t.length - 1];
}
return null;
}
// Get expires from last token
get expires() {
const t = this.token;
if (t && typeof t.expires !== "undefined") {
const expires = Number(t.expires);
if (expires && !Number.isNaN(expires)) {
return expires;
}
}
return null;
}
// Filter out invalid tokens and return true if we find one that is valid
// hasActiveToken() {
// const t = this.tokens;
// return t.length > 0;
// }
hasActiveToken() {
const t = this.tokens;
if (!t.length) {
return false;
}
const now = Math.round(Date.now() / 1000);
const oneMinuteBuffer = 60;
for (let i = t.length - 1; i >= 0; i--) {
const dte = getUnixTime(parseISO(t[i].expires));
const valid = dte - oneMinuteBuffer > now;
if (valid) {
return true;
}
this.drop();
}
return false;
}
// Set a single token on the stack
set({ token, expires }: TokenResponse) {
localStorage.setItem(TOKEN_KEY, JSON.stringify([{ token, expires }]));
}
// Add a token to the stack
add({ token, expires }: TokenResponse) {
const t = this.tokens;
t.push({ token, expires });
localStorage.setItem(TOKEN_KEY, JSON.stringify(t));
}
// Drop a token from the stack
drop() {
const t = this.tokens;
localStorage.setItem(TOKEN_KEY, JSON.stringify(t.splice(-1, 1)));
}
clear() {
localStorage.removeItem(TOKEN_KEY);
}
}
export default new AuthStore();

View File

@@ -0,0 +1,51 @@
const validateString = (minLength = 0, maxLength = 0) => {
if (minLength <= 0 && maxLength <= 0) {
// this doesn't require translation
console.error("validateString() must be called with a min or max or both values in order to work!");
}
return (value: string): string | undefined => {
if (minLength && (typeof value === "undefined" || !value.length)) {
return "This is required";
}
if (minLength && value.length < minLength) {
return `Minimum length is ${minLength} character${minLength === 1 ? "" : "s"}`;
}
if (maxLength && (typeof value === "undefined" || value.length > maxLength)) {
return `Maximum length is ${maxLength} character${maxLength === 1 ? "" : "s"}`;
}
};
};
const validateNumber = (min = -1, max = -1) => {
if (min === -1 && max === -1) {
// this doesn't require translation
console.error("validateNumber() must be called with a min or max or both values in order to work!");
}
return (value: string): string | undefined => {
const int: number = +value;
if (min > -1 && !int) {
return "This is required";
}
if (min > -1 && int < min) {
return `Minimum is ${min}`;
}
if (max > -1 && int > max) {
return `Maximum is ${max}`;
}
};
};
const validateEmail = () => {
return (value: string): string | undefined => {
if (!value.length) {
return "This is required";
}
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
return "Invalid email address";
}
};
};
export { validateEmail, validateNumber, validateString };