Skip to content

Commit

Permalink
fix(AccountView): Save account changes
Browse files Browse the repository at this point in the history
- Added new view for managing user account settings
- Added form fields for updating first name, last name, username, and email
- Implemented saveAccount method to update the user account in the database
- Displayed current user data in the form fields for easy editing
  • Loading branch information
realashleybailey committed Sep 14, 2023
1 parent 6fe2d22 commit bed963a
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 55 deletions.
9 changes: 8 additions & 1 deletion backend/api/routes/accounts_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from flask import request
from flask_jwt_extended import jwt_required
from flask_jwt_extended import jwt_required, current_user
from flask_restx import Namespace, Resource

from helpers.api import convert_to_form
Expand Down Expand Up @@ -43,6 +43,13 @@ def post(self):
return create_account(**request.form), 200


@api.doc(description="Update account")
@api.response(200, "Successfully updated account")
@api.response(500, "Internal server error")
def put(self):
"""Update account"""
return update_account(current_user['id'], **request.form), 200


@api.route("/<int:account_id>")
@api.route("/<int:account_id>/", doc=False)
Expand Down
6 changes: 6 additions & 0 deletions backend/helpers/universal.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@ def global_invite_user_to_media_server(**kwargs) -> dict[str]:
# pylint: disable=no-value-for-parameter
db_user.execute()

# Set the invite to used
invite.used = True
invite.used_at = datetime.now()
invite.used_by = user.id if server_type == "plex" else user["Id"]
invite.save()

# Emit done
socketio_emit("done", None)

Expand Down
5 changes: 2 additions & 3 deletions frontend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import ToastPlugin from "vue-toastification";
import ToastOptions from "./assets/configs/DefaultToasts";
import i18n from "./i18n";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
import sentry from "./sentry";
import VueProgressBar from "@aacassandra/vue3-progressbar";
import FloatingVue from "floating-vue";
import router from "./router";
import Sentry from "./plugins/sentry";

import Toast, { piniaPluginToast } from "./plugins/toasts";
import Axios, { piniaPluginAxios } from "./plugins/axios";
Expand Down Expand Up @@ -41,15 +41,14 @@ app.use(FloatingVue);
app.use(plugin, defaultConfig(formkitConfig));
app.use(Socket, { uri: window.location.origin });
app.use(Filters);
app.use(Sentry);

pinia.use(piniaPluginPersistedstate);
pinia.use(piniaPluginToast);
pinia.use(piniaPluginAxios);
pinia.use(piniaPluginSocketIO);
pinia.use(piniaPluginFilters);

sentry(app, router);

app.mount("#app");

export { app, pinia };
44 changes: 44 additions & 0 deletions frontend/src/plugins/sentry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { BrowserTracing, Replay, init, vueRouterInstrumentation } from "@sentry/vue";
import DefaultToast from "@/components/Toasts/DefaultToast.vue";

import type { Options, TracingOptions } from "@sentry/vue/types/types";
import type { App } from "vue";

type SentryOptions = Partial<
Omit<Options, "tracingOptions"> & {
tracingOptions: Partial<TracingOptions>;
}
>;

const vuePluginSentry = {
install: (app: App, options?: SentryOptions) => {
init({
dsn: "https://d1994be8f88578e14f1a4ac06ae65e89@o4505748808400896.ingest.sentry.io/4505780347666432",
integrations: [
new BrowserTracing({
tracePropagationTargets: [location.origin],
routingInstrumentation: vueRouterInstrumentation(app.config.globalProperties.$router),
}),
new Replay({
maskAllText: false,
blockAllMedia: false,
maskAllInputs: false,
}),
],
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 1.0,
beforeSend(event, hint) {
if (event.exception) {
const errorMessage = event.message ?? "Unknown error";
app.config.globalProperties.$toast.error(DefaultToast("Detected Error", `${errorMessage}, this can generally be ignored.`));
}
return event;
},
...options,
});
},
};

export default vuePluginSentry;
8 changes: 7 additions & 1 deletion frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,13 @@ const router = createRouter({
path: "media",
name: "admin-settings-media",
component: () => import("@/views/SettingsViews/MediaView.vue"),
meta: { header: "Manage Media", subheader: "Configure server media" },
meta: { header: "Manage Media", subheader: "Configure media server" },
},
{
path: "account",
name: "admin-settings-account",
component: () => import("@/views/SettingsViews/AccountView.vue"),
meta: { header: "Manage Account", subheader: "Configure your account" },
},
{
path: "sessions",
Expand Down
34 changes: 0 additions & 34 deletions frontend/src/sentry.ts

This file was deleted.

15 changes: 15 additions & 0 deletions frontend/src/stores/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ export const useUserStore = defineStore("user", {
setUser(user: Partial<APIUser>) {
this.user = user;
},
updateUser(user: Partial<APIUser>) {
// Create a new form data object
const formData = new FormData();
if (user.display_name) formData.append("display_name", user.display_name);
if (user.username) formData.append("username", user.username);
if (user.email) formData.append("email", user.email);

// Update the user in the database
this.$axios.put("/api/accounts", formData).then((response) => {
this.user = response.data;
});

// Update the user in the store
this.user = { ...this.user, ...user };
},
},
persist: true,
});
6 changes: 2 additions & 4 deletions frontend/src/views/AdminViews/AdminView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
<AdminNavBar />

<!-- Page Content -->
<div id="content" class="max-w-screen-xl mx-auto mt-[64px]">
<Transition name="fade" mode="out-in">
<RouterView />
</Transition>
<div id="content" class="max-w-screen-xl mx-auto mt-[64px] md:px-10">
<RouterView />
</div>

<!-- Footer -->
Expand Down
14 changes: 2 additions & 12 deletions frontend/src/views/JoinViews/CarouselView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,6 @@ export default defineComponent({
if (response?.data?.room == undefined) {
this.showError(this.__("Uh oh!"), this.__("Could not create the account."));
}
// Socket step operation
this.socket?.on("step", (step: number) => {
this.eventBus.emit("step", step);
if (step == 3) setTimeout(() => this.currentView++, 1000);
});
},
async jellyfinCreateAccount(value: EmitterRecords["jellyfinCreateAccount"]) {
// Show the next screen
Expand All @@ -223,12 +217,6 @@ export default defineComponent({
if (response?.data?.room == undefined) {
this.showError(this.__("Uh oh!"), this.__("Could not create the account."));
}
// Socket step operation
this.socket?.on("step", (step: number) => {
this.eventBus.emit("step", step);
if (step == 3) setTimeout(() => this.currentView++, 1000);
});
},
},
async mounted() {
Expand All @@ -239,6 +227,8 @@ export default defineComponent({
this.socket.on("error", (message) => this.showError(this.__("Uh oh!"), message));
this.socket.on("error", this.$toast.error);
this.socket.on("message", this.$toast.info);
this.socket.on("step", (step: number) => this.eventBus.emit("step", step));
this.socket.on("done", () => setTimeout(() => this.currentView++, 1000));
// Initialize the event bus
this.eventBus.on("join", this.join);
Expand Down
53 changes: 53 additions & 0 deletions frontend/src/views/SettingsViews/AccountView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<template>
<div class="flex flex-col">
<FormKit type="form" @submit="saveAccount" :submit-label="__('Save Account')" :submit-attrs="{ wrapperClass: 'flex justify-end' }">
<!-- Name -->
<div class="flex flex-row flex-wrap gap-2">
<FormKit type="text" name="firstName" :value="firstName" :label="__('First name')" :placeholder="__('Marvin')" :classes="{ outer: 'flex-grow' }" />
<FormKit type="text" name="lastName" :value="lastName" :label="__('Last name')" :placeholder="__('Martian')" :classes="{ outer: 'flex-grow' }" />
</div>

<!-- Username -->
<FormKit type="text" name="username" :value="user?.username" :label="__('Username')" :placeholder="__('marvin')" />

<!-- Email -->
<FormKit type="email" name="email" :value="user?.email" :label="__('Email')" :placeholder="__('[email protected]')" />
</FormKit>
</div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { useUserStore } from "@/stores/user";
import { mapState, mapActions } from "pinia";
interface SaveAccountData {
firstName: string;
lastName: string;
username: string;
email: string;
}
export default defineComponent({
name: "AccountView",
computed: {
firstName() {
return this.user?.display_name?.split(" ")[0];
},
lastName() {
return this.user?.display_name?.split(" ")[1];
},
...mapState(useUserStore, ["user"]),
},
methods: {
saveAccount(data: SaveAccountData) {
this.updateUser({
display_name: `${data.firstName} ${data.lastName}`,
username: data.username,
email: data.email,
});
},
...mapActions(useUserStore, ["updateUser"]),
},
});
</script>
1 change: 1 addition & 0 deletions frontend/src/views/SettingsViews/MediaView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export default defineComponent({
// Hide the scan servers button and show the test connection button
this.buttonHidden.testConnection = false;
this.buttonHidden.saveServer = true;
// Set the button to not loading
this.buttonLoading.detectServer = false;
Expand Down

0 comments on commit bed963a

Please sign in to comment.