From 5b8851b6c268d0e93c158908fbfae9f8473db5ff Mon Sep 17 00:00:00 2001 From: Orangerot Date: Wed, 19 Jun 2024 00:14:49 +0200 Subject: Initial commit --- pse-dashboard/src/App.vue | 13 + pse-dashboard/src/api/gpodder.js | 97 ++++++ pse-dashboard/src/api/gpodder.test.js | 29 ++ pse-dashboard/src/api/pse-squared.js | 61 ++++ pse-dashboard/src/assets/logo.svg | 211 +++++++++++++ pse-dashboard/src/components/DashboardLayout.vue | 10 + pse-dashboard/src/components/EpisodeEntry.vue | 53 ++++ pse-dashboard/src/components/ErrorLog.vue | 36 +++ .../src/components/FloatingLabelInput.vue | 35 +++ pse-dashboard/src/components/FormLayout.vue | 24 ++ pse-dashboard/src/components/HelpModal.vue | 44 +++ pse-dashboard/src/components/LastUpdate.vue | 46 +++ .../src/components/LoadingConditional.vue | 18 ++ pse-dashboard/src/components/NavBar.vue | 134 ++++++++ pse-dashboard/src/components/PasswordInput.vue | 45 +++ pse-dashboard/src/components/PasswordValidator.vue | 112 +++++++ pse-dashboard/src/components/ProgressTime.vue | 23 ++ pse-dashboard/src/components/SubscriptionEntry.vue | 118 +++++++ pse-dashboard/src/components/index.js | 30 ++ pse-dashboard/src/i18n.js | 11 + pse-dashboard/src/locales/de.help.html | 10 + pse-dashboard/src/locales/de.json | 77 +++++ pse-dashboard/src/locales/en.help.html | 10 + pse-dashboard/src/locales/en.json | 77 +++++ pse-dashboard/src/locales/index.js | 13 + pse-dashboard/src/logger.js | 84 +++++ pse-dashboard/src/main.js | 20 ++ pse-dashboard/src/router.js | 116 +++++++ pse-dashboard/src/store.js | 54 ++++ pse-dashboard/src/style.css | 33 ++ pse-dashboard/src/views/EpisodesView.vue | 42 +++ pse-dashboard/src/views/ForgotPasswordView.vue | 52 +++ pse-dashboard/src/views/LoginView.vue | 91 ++++++ pse-dashboard/src/views/RegistrationView.vue | 78 +++++ pse-dashboard/src/views/ResetPasswordView.vue | 72 +++++ pse-dashboard/src/views/SettingsView.vue | 347 +++++++++++++++++++++ pse-dashboard/src/views/SubscriptionsView.vue | 270 ++++++++++++++++ pse-dashboard/src/views/index.js | 18 ++ 38 files changed, 2614 insertions(+) create mode 100644 pse-dashboard/src/App.vue create mode 100644 pse-dashboard/src/api/gpodder.js create mode 100644 pse-dashboard/src/api/gpodder.test.js create mode 100644 pse-dashboard/src/api/pse-squared.js create mode 100644 pse-dashboard/src/assets/logo.svg create mode 100644 pse-dashboard/src/components/DashboardLayout.vue create mode 100644 pse-dashboard/src/components/EpisodeEntry.vue create mode 100644 pse-dashboard/src/components/ErrorLog.vue create mode 100644 pse-dashboard/src/components/FloatingLabelInput.vue create mode 100644 pse-dashboard/src/components/FormLayout.vue create mode 100644 pse-dashboard/src/components/HelpModal.vue create mode 100644 pse-dashboard/src/components/LastUpdate.vue create mode 100644 pse-dashboard/src/components/LoadingConditional.vue create mode 100644 pse-dashboard/src/components/NavBar.vue create mode 100644 pse-dashboard/src/components/PasswordInput.vue create mode 100644 pse-dashboard/src/components/PasswordValidator.vue create mode 100644 pse-dashboard/src/components/ProgressTime.vue create mode 100644 pse-dashboard/src/components/SubscriptionEntry.vue create mode 100644 pse-dashboard/src/components/index.js create mode 100644 pse-dashboard/src/i18n.js create mode 100644 pse-dashboard/src/locales/de.help.html create mode 100644 pse-dashboard/src/locales/de.json create mode 100644 pse-dashboard/src/locales/en.help.html create mode 100644 pse-dashboard/src/locales/en.json create mode 100644 pse-dashboard/src/locales/index.js create mode 100644 pse-dashboard/src/logger.js create mode 100644 pse-dashboard/src/main.js create mode 100644 pse-dashboard/src/router.js create mode 100644 pse-dashboard/src/store.js create mode 100644 pse-dashboard/src/style.css create mode 100644 pse-dashboard/src/views/EpisodesView.vue create mode 100644 pse-dashboard/src/views/ForgotPasswordView.vue create mode 100644 pse-dashboard/src/views/LoginView.vue create mode 100644 pse-dashboard/src/views/RegistrationView.vue create mode 100644 pse-dashboard/src/views/ResetPasswordView.vue create mode 100644 pse-dashboard/src/views/SettingsView.vue create mode 100644 pse-dashboard/src/views/SubscriptionsView.vue create mode 100644 pse-dashboard/src/views/index.js (limited to 'pse-dashboard/src') diff --git a/pse-dashboard/src/App.vue b/pse-dashboard/src/App.vue new file mode 100644 index 0000000..bd387ff --- /dev/null +++ b/pse-dashboard/src/App.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/pse-dashboard/src/api/gpodder.js b/pse-dashboard/src/api/gpodder.js new file mode 100644 index 0000000..057e5a7 --- /dev/null +++ b/pse-dashboard/src/api/gpodder.js @@ -0,0 +1,97 @@ +import axios from 'axios'; + +// export default function useGpodder({ +export default function useGPodder({ + baseURL, + throwHandler = (err) => err, + gPodderUser, + useCredentials + }) { + + const gpodder = axios.create({ + baseURL, + credentials: useCredentials ? "include" : "omit", + headers: { + 'Content-Type': 'application/json', + } + }); + + let auth = { + username: gPodderUser?.username || "", + password: gPodderUser?.password || "" + }; + + gpodder.interceptors.response.use((response) => response, (error) => { + // whatever you want to do with the error + throwHandler(error); + throw error; + }); + + return { + + /******************************************************************************/ + /* Authentication API */ + /******************************************************************************/ + + async register(gPodderUser) { + return gpodder.post(`/api/2/auth/register.json`, gPodderUser); + }, + + async login(gPodderUserData) { + auth = gPodderUserData; + gPodderUser = gPodderUserData + + return gpodder.post(`/api/2/auth/${gPodderUser.username}/login.json`, {}, {auth}); + }, + + async logout() { + return gpodder.post(`/api/2/auth/${gPodderUser.username}/logout.json`, {}, {auth}); + }, + + async changePassword(passwordChange) { + return gpodder.put(`/api/2/auth/${gPodderUser.username}/changepassword.json`, passwordChange, {auth}); + }, + + async forgotPassword({email}) { + return gpodder.post(`/api/2/auth/${email}/forgot.json`, {}); + }, + + // no auth! + async resetPassword({username, password, token}) { + return gpodder.put(`/api/2/auth/${username}/resetpassword.json?token=${token}`, {password}); + }, + + async deleteAccount(passwordData) { + return gpodder.delete(`/api/2/auth/${gPodderUser.username}/delete.json`, {auth, data: passwordData}); + }, + + /******************************************************************************/ + /* Subscription API */ + /******************************************************************************/ + + async getTitles() { + return gpodder.get(`/subscriptions/titles/${gPodderUser.username}.json`, {auth}); + }, + + async putSubscriptions(subscriptions) { + return gpodder.put(`/subscriptions/${gPodderUser.username}/device.json`, subscriptions, {auth}); + }, + + async postSubscriptions(subscriptions) { + return gpodder.post(`/api/2/subscriptions/${gPodderUser.username}/device.json`, subscriptions, {auth}); + }, + + /******************************************************************************/ + /* EpisodeActions API */ + /******************************************************************************/ + + async getEpisodeActions() { + return gpodder.get(`/api/2/episodes/${gPodderUser.username}.json`, {auth}); + }, + + async postEpisodeActions(episodeActions) { + return gpodder.post(`/api/2/episodes/${gPodderUser.username}.json`, episodeActions, {auth}); + }, + } +} + diff --git a/pse-dashboard/src/api/gpodder.test.js b/pse-dashboard/src/api/gpodder.test.js new file mode 100644 index 0000000..bf88ddd --- /dev/null +++ b/pse-dashboard/src/api/gpodder.test.js @@ -0,0 +1,29 @@ +import * as GPodder from './gpodder.js' + +const user = new GPodder.GPodderUser({ + username: "iam@not.real", + password: "12345678aB@" +}); + +console.log(user) +console.log(user.username, user.password); + +GPodder.init({ + baseURL: "http://localhost:8080", + throwHandler: err => err +}) + +async function testGPodder() { + const register = await GPodder.register(user); + console.log(register.status); + + const login = await GPodder.login(user) + console.log(login.status, login.headers); + + const response = await GPodder.getTitles(); + const json = await response.text(); + console.log(`${response.status} "${json}"`); + +} +testGPodder(); + diff --git a/pse-dashboard/src/api/pse-squared.js b/pse-dashboard/src/api/pse-squared.js new file mode 100644 index 0000000..641542b --- /dev/null +++ b/pse-dashboard/src/api/pse-squared.js @@ -0,0 +1,61 @@ +import useGPodder from '@/api/gpodder.js' +import { useLogger } from '@/logger.js' + +const logger = useLogger(); + +function errorHandler(error) { + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + console.log(error.response.data); + console.log(error.response.status); + console.log(error.response.headers); + + switch (error.response.status) { + case 400: logger.badRequestError(); break; + case 401: logger.unauthorizedError(); break; + case 404: logger.notFoundError(); break; + } + + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + logger.connectionLostError() + + console.log(error.request); + } else { + // Something happened in setting up the request that triggered an Error + console.log('Error', error.message); + logger.append({ + type: "danger", + message: err.message + }); + } +} + +const backendURL = import.meta.env.VITE_BACKEND_URL || "http://localhost:8080"; +console.log("Backend-URL", backendURL); + +const pseSquared = useGPodder({ + // baseURL: process.env.VUE_APP_BASE_URL || "http://localhost:8080", + // baseURL: "http://api.pse-squared.de", + baseURL: backendURL, + throwHandler: error => errorHandler(error) +}); + +export const { + changePassword, + deleteAccount, + forgotPassword, + getEpisodeActions, + getTitles, + login, + logout, + postEpisodeActions, + postSubscriptions, + putSubscriptions, + register, + resetPassword, +} = pseSquared; + diff --git a/pse-dashboard/src/assets/logo.svg b/pse-dashboard/src/assets/logo.svg new file mode 100644 index 0000000..1609066 --- /dev/null +++ b/pse-dashboard/src/assets/logo.svg @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Podcast Synchronisation made Efficient + + + + diff --git a/pse-dashboard/src/components/DashboardLayout.vue b/pse-dashboard/src/components/DashboardLayout.vue new file mode 100644 index 0000000..55695ae --- /dev/null +++ b/pse-dashboard/src/components/DashboardLayout.vue @@ -0,0 +1,10 @@ + + + + diff --git a/pse-dashboard/src/components/EpisodeEntry.vue b/pse-dashboard/src/components/EpisodeEntry.vue new file mode 100644 index 0000000..c651484 --- /dev/null +++ b/pse-dashboard/src/components/EpisodeEntry.vue @@ -0,0 +1,53 @@ + + + + diff --git a/pse-dashboard/src/components/ErrorLog.vue b/pse-dashboard/src/components/ErrorLog.vue new file mode 100644 index 0000000..3c36359 --- /dev/null +++ b/pse-dashboard/src/components/ErrorLog.vue @@ -0,0 +1,36 @@ + + + + diff --git a/pse-dashboard/src/components/FloatingLabelInput.vue b/pse-dashboard/src/components/FloatingLabelInput.vue new file mode 100644 index 0000000..6f718fd --- /dev/null +++ b/pse-dashboard/src/components/FloatingLabelInput.vue @@ -0,0 +1,35 @@ + + + + diff --git a/pse-dashboard/src/components/FormLayout.vue b/pse-dashboard/src/components/FormLayout.vue new file mode 100644 index 0000000..549339f --- /dev/null +++ b/pse-dashboard/src/components/FormLayout.vue @@ -0,0 +1,24 @@ + + + + diff --git a/pse-dashboard/src/components/HelpModal.vue b/pse-dashboard/src/components/HelpModal.vue new file mode 100644 index 0000000..b4f45ef --- /dev/null +++ b/pse-dashboard/src/components/HelpModal.vue @@ -0,0 +1,44 @@ + + + + diff --git a/pse-dashboard/src/components/LastUpdate.vue b/pse-dashboard/src/components/LastUpdate.vue new file mode 100644 index 0000000..b30b254 --- /dev/null +++ b/pse-dashboard/src/components/LastUpdate.vue @@ -0,0 +1,46 @@ + + + + diff --git a/pse-dashboard/src/components/LoadingConditional.vue b/pse-dashboard/src/components/LoadingConditional.vue new file mode 100644 index 0000000..2bf233c --- /dev/null +++ b/pse-dashboard/src/components/LoadingConditional.vue @@ -0,0 +1,18 @@ + + + + diff --git a/pse-dashboard/src/components/NavBar.vue b/pse-dashboard/src/components/NavBar.vue new file mode 100644 index 0000000..2cc36e5 --- /dev/null +++ b/pse-dashboard/src/components/NavBar.vue @@ -0,0 +1,134 @@ + + + + diff --git a/pse-dashboard/src/components/PasswordInput.vue b/pse-dashboard/src/components/PasswordInput.vue new file mode 100644 index 0000000..599f438 --- /dev/null +++ b/pse-dashboard/src/components/PasswordInput.vue @@ -0,0 +1,45 @@ + + + + diff --git a/pse-dashboard/src/components/PasswordValidator.vue b/pse-dashboard/src/components/PasswordValidator.vue new file mode 100644 index 0000000..a269426 --- /dev/null +++ b/pse-dashboard/src/components/PasswordValidator.vue @@ -0,0 +1,112 @@ + + + + diff --git a/pse-dashboard/src/components/ProgressTime.vue b/pse-dashboard/src/components/ProgressTime.vue new file mode 100644 index 0000000..61bd421 --- /dev/null +++ b/pse-dashboard/src/components/ProgressTime.vue @@ -0,0 +1,23 @@ + + + + diff --git a/pse-dashboard/src/components/SubscriptionEntry.vue b/pse-dashboard/src/components/SubscriptionEntry.vue new file mode 100644 index 0000000..db3b45c --- /dev/null +++ b/pse-dashboard/src/components/SubscriptionEntry.vue @@ -0,0 +1,118 @@ + + + + diff --git a/pse-dashboard/src/components/index.js b/pse-dashboard/src/components/index.js new file mode 100644 index 0000000..d78c3d7 --- /dev/null +++ b/pse-dashboard/src/components/index.js @@ -0,0 +1,30 @@ +import DashboardLayout from './DashboardLayout.vue' +import EpisodeEntry from './EpisodeEntry.vue' +import ErrorLog from './ErrorLog.vue' +import FloatingLabelInput from './FloatingLabelInput.vue' +import FormLayout from './FormLayout.vue' +import HelpModal from './HelpModal.vue' +import LastUpdate from './LastUpdate.vue' +import LoadingConditional from './LoadingConditional.vue' +import NavBar from './NavBar.vue' +import PasswordInput from './PasswordInput.vue' +import PasswordValidator from './PasswordValidator.vue' +import ProgressTime from './ProgressTime.vue' +import SubscriptionEntry from './SubscriptionEntry.vue' + +export { + DashboardLayout, + EpisodeEntry, + ErrorLog, + FloatingLabelInput, + FormLayout, + HelpModal, + LastUpdate, + LoadingConditional, + NavBar, + PasswordInput, + PasswordValidator, + ProgressTime, + SubscriptionEntry, +} + diff --git a/pse-dashboard/src/i18n.js b/pse-dashboard/src/i18n.js new file mode 100644 index 0000000..e722eab --- /dev/null +++ b/pse-dashboard/src/i18n.js @@ -0,0 +1,11 @@ +import { createI18n } from 'vue-i18n' +import * as locales from '@/locales' + +const i18n = createI18n({ + legacy: false, + locale: 'de', + messages: {...locales} +}); + +export default i18n + diff --git a/pse-dashboard/src/locales/de.help.html b/pse-dashboard/src/locales/de.help.html new file mode 100644 index 0000000..f2fca3a --- /dev/null +++ b/pse-dashboard/src/locales/de.help.html @@ -0,0 +1,10 @@ +

+Hier stehen Hilfestellungen. +

+










+

Bis hier unten.

+










+










+










+

Und noch viel weiter!

+ diff --git a/pse-dashboard/src/locales/de.json b/pse-dashboard/src/locales/de.json new file mode 100644 index 0000000..430e844 --- /dev/null +++ b/pse-dashboard/src/locales/de.json @@ -0,0 +1,77 @@ +{ + "message": { + "addSubscription": "Abonnement hinzufügen", + "changePassword": "Passwort ändern", + "close": "Schließen", + "deleteAccount": "Account löschen", + "deleteAccountWarning": "Bist du sicher, dass du dein Konto mit dem Namen '{username}' löschen möchtest? Dabei gehen alle deine Abonnements und gehörten Episoden verloren. Du kannst aber jederzeit ein neues Konto erstellen. ", + "emailAddressRequest": "Bitte E-Mail-Adresse angeben", + "episode": "keine Episoden | eine Episode | {n} Episoden", + "exportData": "Daten exportieren", + "forgotPassword": "Passwort vergessen", + "gpodderInstanceRequest": "Gpodder-Instanz eingeben", + "help": "Hilfe", + "import": "Importieren", + "importData": "Daten importieren", + "instance": "Gpodder-Instanz", + "login": "Anmelden", + "loginRequest": "Bitte anmelden", + "logout": "Abmelden", + "mostRecentlyHeardEpisodes": "Zuletzt gehörte Episoden", + "mostRecentlyHeared": "Zuletzt gehört", + "newPassword": "Neues Passwort", + "newSubscription": "Neues Abonnement", + "noAccountYet": "Noch keinen Account", + "noEpisodes": "Du hast noch keine Episode angehört. ", + "noSubscriptions": "Du hast noch keine Abonnements hinzugefügt. ", + "oldPassword": "Altes Passwort", + "passwordRequest": "Passwort eingeben", + "personalData": "Personenbezogene Daten", + "podcast": "Podcast | Podcasts", + "registration": "Registrierung", + "rememberMe": "Angemeldet bleiben", + "repeat": "Wiederholen", + "repeatPassword": "Passwort wiederholen", + "selectAll": "Alle auswählen", + "send": "Absenden", + "setNewPassword": "Neues Passwort festlegen", + "settings": "Einstellungen", + "signUp": "Registrieren", + "userNameRequest": "Nutzername eingeben", + "unsubscribePodcasts": "Podcasts deabonnieren", + "unsubscribePodcastsWarning": "Bist du sicher, dass du folgende Podcast deabonnieren möchtest? Dabei werden auch alle Hörfortschritte der Abonnements gelöscht. ", + "unsubscribeSelected": "Ausgewählte deabonnieren", + "yourSubscriptions": "Deine abonnierten Podcasts" + }, + "passwordRequirements": { + "passwordLength": "Mindestens {n} Zeichen Lang", + "passwordMatch": "Passwörter sind identisch", + "passwordNumbers": "Zahl", + "passwordSpecialChar": "Sonderzeichen", + "passwordUpperLower": "Klein- und Großbuchstaben" + }, + "form": { + "emailAddress": "E-Mail-Adresse", + "password": "Passwort", + "username": "Nutzername" + }, + "error": { + "accountCreated": "Konto wurde erstellt! Verifiziere deine E-Mail. ", + "accountDeleted": "Konto wurde erfolgreich gelöscht. Auf Wiedersehen. ", + "copiedPodcast": "Podcast wurde in die Zwischenablage kopiert!", + "copiedPodcastError": "Kann nichts in die Zwischenablage legen. ", + "gpodderImport": "Daten erfolgreich von GPodder-Instanz importiert. ", + "passwordChanged": "Passwort wurde erfolgreich geändert! ", + "passwordForgot": "E-Mail wurde gesendet. Schau in dein Postfach!", + "passwordRequirements": "Passwortanforderungen werden nicht erfüllt.", + "passwordReset": "Passwort wurde zurückgesetzt! Teste Dein neues Passwort. ", + "subscriptionAdded": "Abonnement wurde hinzugefügt!", + "400BadRequest": "Eingaben sind falsch.", + "401Unauthorized": "Nutzername oder Kennwort ist falsch. ", + "404NotFound": "Kein Nutzer mit diesen Eingaben gefunden.", + "connectionLost": "Kann keine Verbindung zum Server aufbauen. ", + "axiosError": "Huch, der Programmierer hat einen Fehler gemacht. ", + "pageNotFound": "Seite nicht gefunden. " + } +} + diff --git a/pse-dashboard/src/locales/en.help.html b/pse-dashboard/src/locales/en.help.html new file mode 100644 index 0000000..ac7dedd --- /dev/null +++ b/pse-dashboard/src/locales/en.help.html @@ -0,0 +1,10 @@ +

+Help is available here. +

+










+

To down here.

+










+










+










+

And way beyond!

+ diff --git a/pse-dashboard/src/locales/en.json b/pse-dashboard/src/locales/en.json new file mode 100644 index 0000000..acc7f2d --- /dev/null +++ b/pse-dashboard/src/locales/en.json @@ -0,0 +1,77 @@ +{ + "message": { + "addSubscription": "Add Subscription", + "changePassword": "Change Password", + "close": "Close", + "deleteAccount": "Delete Account", + "deleteAccountWarning": "Are you sure you want to delete your account named '{username}'? You will lose all your subscriptions and listened episodes. However, you can always create a new account. ", + "episode": "no episodes | one episode | {n} episodes", + "emailAddressRequest": "Please enter your Email Address", + "exportData": "Export Data", + "forgotPassword": "Forgot Password", + "gpodderInstanceRequest": "Enter Gpodder-Instance ", + "help": "Help", + "import": "Import", + "importData": "Import Data", + "instance": "GPodder Instance", + "login": "Login", + "loginRequest": "Login Please", + "logout": "Logout", + "mostRecentlyHeardEpisodes": "Recently heard episodes", + "mostRecentlyHeared": "Recently Heard", + "newPassword": "New Password", + "newSubscription": "New Subscription", + "noAccountYet": "No Account Yet", + "noEpisodes": "Looks like you don't have listened to something yet. ", + "noSubscriptions": "Looks like you don't have any subscriptions yet. ", + "oldPassword": "Old Password", + "passwordRequest": "Enter Password", + "personalData": "Personal Data", + "podcast": "Podcast | Podcasts", + "registration": "Registration", + "rememberMe": "Remember Me", + "repeat": "Repeat", + "repeatPassword": "Repeat Password", + "selectAll": "Select All", + "send": "Send", + "setNewPassword": "Set new Password", + "settings": "Settings", + "signUp": "Sign Up", + "userNameRequest": "Enter Username", + "unsubscribePodcasts": "Unsubscribe from Podcasts", + "unsubscribePodcastsWarning": "Are you sure you want to unsubscribe from the following podcast? This will also delete all listening progress of the subscriptions. ", + "unsubscribeSelected": "Unsubscribe from Selected", + "yourSubscriptions": "Your Podcast Subscriptions" + }, + "passwordRequirements": { + "passwordLength": "At least {n} characters long", + "passwordMatch": "Passwords are Identical", + "passwordNumbers": "Digit", + "passwordSpecialChar": "Symbol", + "passwordUpperLower": "Lowercase and Uppercase Letter" + }, + "form": { + "emailAddress": "Email Address", + "password": "Password", + "username": "Username" + }, + "error": { + "accountCreated": "Account created! Validate your mail. ", + "accountDeleted": "Account got deleted. We are sorry you go. ", + "copiedPodcast": "Copied Podcast to Clipboard!", + "copiedPodcastError": "Can't share Podcast. ", + "gpodderImport": "Imported data from GPodder-Instance. ", + "passwordChanged": "Password got changed! ", + "passwordForgot": "E-Mail was send. Look into your invoice!", + "passwordRequirements": "Password requirements are not met. ", + "passwordReset": "Password got reset! Test you new Password. ", + "subscriptionAdded": "Subscription got added to your list!", + "400BadRequest": "Inputs are incorrect. ", + "401Unauthorized": "Wrong Credentials.", + "404NotFound": "No user found with these inputs.", + "connectionLost": "Cannot establish a connection to the server.", + "axiosError": "Oops, the programmer made a mistake. ", + "pageNotFound": "Page not found." + } +} + diff --git a/pse-dashboard/src/locales/index.js b/pse-dashboard/src/locales/index.js new file mode 100644 index 0000000..64176a9 --- /dev/null +++ b/pse-dashboard/src/locales/index.js @@ -0,0 +1,13 @@ +import de from './de.json' +import de_help from './de.help.html?raw' +import en from './en.json' +import en_help from './en.help.html?raw' + +de.message.helpModal = de_help; +en.message.helpModal = en_help; + +export { + de, + en +} + diff --git a/pse-dashboard/src/logger.js b/pse-dashboard/src/logger.js new file mode 100644 index 0000000..8398fd8 --- /dev/null +++ b/pse-dashboard/src/logger.js @@ -0,0 +1,84 @@ +import { reactive } from 'vue' +import i18n from '@/i18n' +// import { useI18n } from 'vue-i18n' + +// const { t } = i18n.global; +export const Logger = reactive({ + items: [], + append(item) { + this.items.push(item); + }, + delete(item) { + this.items = this.items.filter(e => e != item); + } +}); + + +// error {type: "success" | "info" | "warning" | "danger", message: String, lifetime: number} +export function useLogger() { + // const { t } = useI18n(); + const { t } = i18n.global; + + return { + append(item) { + Logger.append(item); + }, + delete(item) { + Logger.delete(item); + }, + passwordRequirementsError() { + Logger.append({type: "warning", message: t('form.password')}) + }, + passwordRequirements() { + Logger.append({type: "warning", message: t("error.passwordRequirements")}); + }, + passwordChanged() { + Logger.append({type: "success", message: t("error.passwordChanged")}); + }, + accountDeleted() { + Logger.append({type: "info", message: t("error.accountDeleted")}); + }, + gpodderImport() { + Logger.append({type: "info", message: t("error.gpodderImport")}); + }, + passwordReset() { + Logger.append({type: "info", message: t("error.passwordReset")}); + }, + passwordForgot() { + Logger.append({type: "info", message: t("error.passwordForgot")}); + }, + subscriptionAdded() { + Logger.append({type: "info", message: t("error.subscriptionAdded")}); + }, + accountCreated() { + Logger.append({type: "success", message: t("error.accountCreated")}); + }, + copiedPodcast() { + Logger.append({type: "info", message: t("error.copiedPodcast")}); + }, + copiedPodcastError() { + Logger.append({type: "warning", message: t("error.copiedPodcastError")}); + }, + + badRequestError() { + Logger.append({type: "danger", message: t("error.400BadRequest")}); + }, + unauthorizedError() { + Logger.append({type: "danger", message: t("error.401Unauthorized")}); + }, + notFoundError() { + Logger.append({type: "danger", message: t("error.404NotFound")}); + }, + connectionLostError() { + Logger.append({type: "danger", message: t("error.connectionLost")}); + }, + axiosError() { + Logger.append({type: "danger", message: t("error.axiosError")}); + }, + pageNotFound() { + Logger.append({type: "warning", message: t("error.pageNotFound")}); + } + } +} + + diff --git a/pse-dashboard/src/main.js b/pse-dashboard/src/main.js new file mode 100644 index 0000000..04dd3c2 --- /dev/null +++ b/pse-dashboard/src/main.js @@ -0,0 +1,20 @@ +import { createApp } from 'vue' +import router from '@/router' +import i18n from '@/i18n' + +import App from '@/App.vue' + +import "bootstrap/dist/css/bootstrap.css" +import '@/style.css' +import "@fortawesome/fontawesome-free/css/all.css" + +try { + navigator.registerProtocolHandler("web+pod", "/subscriptions?add=%s", "Podcast"); +} catch (err) { + console.error(err); +} + +createApp(App).use(router).use(i18n).mount('#app') + +// import "bootstrap/dist/js/bootstrap.js" + diff --git a/pse-dashboard/src/router.js b/pse-dashboard/src/router.js new file mode 100644 index 0000000..573b0d7 --- /dev/null +++ b/pse-dashboard/src/router.js @@ -0,0 +1,116 @@ +import { createRouter, createWebHistory } from 'vue-router' +import { store } from '@/store.js' +import { + LoginView, + SubscriptionsView, + EpisodesView, + ForgotPasswordView, + SettingsView, + RegistrationView, + ResetPasswordView +} from '@/views' +import { useLogger } from '@/logger.js' + +const logger = useLogger(); + +const routes = [ + { + path: '/', + redirect: to => { + return store.isLoggedIn ? '/subscriptions' : '/login'; + }, + meta: { requiresAuth: false }, + }, + { + path: '/login', + name: 'Login', + component: LoginView, + meta: { requiresAuth: false }, + }, + { + path: '/forgotPassword', + name: 'ForgotPassword', + component: ForgotPasswordView, + meta: { requiresAuth: false }, + }, + { + path: '/registration', + name: 'Registration', + component: RegistrationView, + meta: { requiresAuth: false }, + }, + { + path: '/resetPassword', + name: 'ResetPassword', + component: ResetPasswordView, + props: router => ({ + token: router.query.token, + username: router.query.username + }), + meta: { requiresAuth: false }, + }, + { + path: '/subscriptions', + name: 'Subscriptions', + component: SubscriptionsView, + meta: { requiresAuth: true } + }, + { + path: '/episodes', + name: 'Episodes', + component: EpisodesView, + meta: { requiresAuth: true } + }, + { + path: '/settings', + name: 'Settings', + component: SettingsView, + meta: { requiresAuth: true } + }, + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + redirect: to => { + logger.pageNotFound(); + return "/"; + }, + meta: { requiresAuth: false }, + } +] + +const baseURL = import.meta.env.BASE_URL || "/"; +console.log("Base-URL", baseURL); + +const router = createRouter({ + history: createWebHistory(baseURL), + routes +}) + +router.beforeEach((to, from, next) => { + // instead of having to check every route record with + // to.matched.some(record => record.meta.requiresAuth) + if (to.meta.requiresAuth && !store.isLoggedIn) { + // this route requires auth, check if logged in + // if not, redirect to login page. + next({ + path: '/', + // save the location we were at to come back later + query: { redirect: to.fullPath }, + }); + } else if (!to.meta.requiresAuth && store.isLoggedIn) { + next({ + path: '/' + }); + } else if (store.isLoggedIn && from.query.redirect) { + // user is logged in and there's a saved location in the query + // redirect them to that location + const redirect = from.query.redirect; + delete from.query.redirect; + next(redirect); + } else { + next(); + } +}); + +export default router + diff --git a/pse-dashboard/src/store.js b/pse-dashboard/src/store.js new file mode 100644 index 0000000..5f838e6 --- /dev/null +++ b/pse-dashboard/src/store.js @@ -0,0 +1,54 @@ +import { reactive } from 'vue' +import { login, logout } from '@/api/pse-squared.js' + +const username = sessionStorage.getItem("username") || localStorage.getItem("username"); +const password = sessionStorage.getItem("password") || localStorage.getItem("password"); + +export const store = reactive({ + isLoggedIn: username && password, + username, + password, + async login({username, password}, persistant) { + try { + await login({username, password}); + this.username = username; + this.password = password; + this.isLoggedIn = true; + + setStorage(this, persistant); + + return true; + } catch(err) { + console.error(err); + return false; + } + }, + async logout() { + logout(); + this.username = ""; + this.password = ""; + this.isLoggedIn = false; + clearStorage(); + return true; + } +}); + +if (username && password) { + store.login({username, password}); +} + +function setStorage(data, persistant) { + if (persistant) { + localStorage.setItem("username", data.username); + localStorage.setItem("password", data.password); + } + + sessionStorage.setItem("username", data.username); + sessionStorage.setItem("password", data.password); +} + +function clearStorage() { + sessionStorage.clear(); + localStorage.clear(); +} + diff --git a/pse-dashboard/src/style.css b/pse-dashboard/src/style.css new file mode 100644 index 0000000..15fb716 --- /dev/null +++ b/pse-dashboard/src/style.css @@ -0,0 +1,33 @@ +:root { + --bs-body-bg: #f5f5f5 !important; +} + +html, body { + height: 100%; +} + +/* Style der Eingabefelder */ +form .form-control, form .form-input label { + border-radius: 0; + margin-bottom: -1px; +} + + +form > .form-input:first-of-type .form-control { + border-top-right-radius: 0.375rem; + border-top-left-radius: 0.375rem; +} + +form > .form-input:first-of-type label { + border-top-right-radius: 0.375rem; +} + +form > .form-input:last-of-type .form-control { + border-bottom-right-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +form > .form-input:last-of-type label { + border-bottom-right-radius: 0.375rem; +} + diff --git a/pse-dashboard/src/views/EpisodesView.vue b/pse-dashboard/src/views/EpisodesView.vue new file mode 100644 index 0000000..a7027b1 --- /dev/null +++ b/pse-dashboard/src/views/EpisodesView.vue @@ -0,0 +1,42 @@ + + + + diff --git a/pse-dashboard/src/views/ForgotPasswordView.vue b/pse-dashboard/src/views/ForgotPasswordView.vue new file mode 100644 index 0000000..03d0ff1 --- /dev/null +++ b/pse-dashboard/src/views/ForgotPasswordView.vue @@ -0,0 +1,52 @@ + + + + diff --git a/pse-dashboard/src/views/LoginView.vue b/pse-dashboard/src/views/LoginView.vue new file mode 100644 index 0000000..303db3a --- /dev/null +++ b/pse-dashboard/src/views/LoginView.vue @@ -0,0 +1,91 @@ + + + + diff --git a/pse-dashboard/src/views/RegistrationView.vue b/pse-dashboard/src/views/RegistrationView.vue new file mode 100644 index 0000000..2d561f3 --- /dev/null +++ b/pse-dashboard/src/views/RegistrationView.vue @@ -0,0 +1,78 @@ + + + diff --git a/pse-dashboard/src/views/ResetPasswordView.vue b/pse-dashboard/src/views/ResetPasswordView.vue new file mode 100644 index 0000000..a617af3 --- /dev/null +++ b/pse-dashboard/src/views/ResetPasswordView.vue @@ -0,0 +1,72 @@ + + + diff --git a/pse-dashboard/src/views/SettingsView.vue b/pse-dashboard/src/views/SettingsView.vue new file mode 100644 index 0000000..4e5e486 --- /dev/null +++ b/pse-dashboard/src/views/SettingsView.vue @@ -0,0 +1,347 @@ + + + + diff --git a/pse-dashboard/src/views/SubscriptionsView.vue b/pse-dashboard/src/views/SubscriptionsView.vue new file mode 100644 index 0000000..045b5f1 --- /dev/null +++ b/pse-dashboard/src/views/SubscriptionsView.vue @@ -0,0 +1,270 @@ + + + + diff --git a/pse-dashboard/src/views/index.js b/pse-dashboard/src/views/index.js new file mode 100644 index 0000000..e4706f8 --- /dev/null +++ b/pse-dashboard/src/views/index.js @@ -0,0 +1,18 @@ +import EpisodesView from './EpisodesView.vue' +import ForgotPasswordView from './ForgotPasswordView.vue' +import LoginView from './LoginView.vue' +import RegistrationView from './RegistrationView.vue' +import ResetPasswordView from './ResetPasswordView.vue' +import SettingsView from './SettingsView.vue' +import SubscriptionsView from './SubscriptionsView.vue' + +export { + EpisodesView, + ForgotPasswordView, + LoginView, + RegistrationView, + ResetPasswordView, + SettingsView, + SubscriptionsView, +} + -- cgit v1.2.3