mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-11-04 03:03:18 -05:00 
			
		
		
		
	Refactor/backend routers (#388)
* update router * update caddy file * setup depends in docker-fole * make changes for serving on subpath * set dev config * fix router signups * consolidate links * backup-functionality to dashboard * new user card * consolidate theme into profile * fix theme tests * fix pg tests * fix pg tests * remove unused import * mobile margin Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
		
							
								
								
									
										215
									
								
								frontend/src/pages/Admin/Profile/ThemeCard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								frontend/src/pages/Admin/Profile/ThemeCard.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,215 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <StatCard icon="mdi-format-color-fill" :color="color">
 | 
			
		||||
      <template v-slot:after-heading>
 | 
			
		||||
        <div class="ml-auto text-right">
 | 
			
		||||
          <div class="body-3 grey--text font-weight-light" v-text="$t('general.themes')" />
 | 
			
		||||
 | 
			
		||||
          <h3 class="display-2 font-weight-light text--primary">
 | 
			
		||||
            <small> {{ selectedTheme.name }} </small>
 | 
			
		||||
          </h3>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
      <template v-slot:actions>
 | 
			
		||||
        <v-btn-toggle v-model="darkMode" color="primary " mandatory>
 | 
			
		||||
          <v-btn small value="system">
 | 
			
		||||
            <v-icon>mdi-desktop-tower-monitor</v-icon>
 | 
			
		||||
            <span class="ml-1" v-show="$vuetify.breakpoint.smAndUp">
 | 
			
		||||
              {{ $t("settings.theme.default-to-system") }}
 | 
			
		||||
            </span>
 | 
			
		||||
          </v-btn>
 | 
			
		||||
 | 
			
		||||
          <v-btn small value="light">
 | 
			
		||||
            <v-icon>mdi-white-balance-sunny</v-icon>
 | 
			
		||||
            <span class="ml-1" v-show="$vuetify.breakpoint.smAndUp">
 | 
			
		||||
              {{ $t("settings.theme.light") }}
 | 
			
		||||
            </span>
 | 
			
		||||
          </v-btn>
 | 
			
		||||
 | 
			
		||||
          <v-btn small value="dark">
 | 
			
		||||
            <v-icon>mdi-weather-night</v-icon>
 | 
			
		||||
            <span class="ml-1" v-show="$vuetify.breakpoint.smAndUp">
 | 
			
		||||
              {{ $t("settings.theme.dark") }}
 | 
			
		||||
            </span>
 | 
			
		||||
          </v-btn>
 | 
			
		||||
        </v-btn-toggle>
 | 
			
		||||
      </template>
 | 
			
		||||
      <template v-slot:bottom>
 | 
			
		||||
        <v-virtual-scroll height="290" item-height="70" :items="availableThemes" class="mt-2">
 | 
			
		||||
          <template v-slot:default="{ item }">
 | 
			
		||||
            <v-list-item @click="selectedTheme = item">
 | 
			
		||||
              <v-list-item-avatar>
 | 
			
		||||
                <v-icon large dark :color="item.colors.primary">
 | 
			
		||||
                  mdi-format-color-fill
 | 
			
		||||
                </v-icon>
 | 
			
		||||
              </v-list-item-avatar>
 | 
			
		||||
 | 
			
		||||
              <v-list-item-content>
 | 
			
		||||
                <v-list-item-title v-text="item.name"></v-list-item-title>
 | 
			
		||||
 | 
			
		||||
                <v-row flex align-center class="mt-2 justify-space-around px-4 pb-2">
 | 
			
		||||
                  <v-sheet
 | 
			
		||||
                    class="rounded flex mx-1"
 | 
			
		||||
                    v-for="(item, index) in item.colors"
 | 
			
		||||
                    :key="index"
 | 
			
		||||
                    :color="item"
 | 
			
		||||
                    height="20"
 | 
			
		||||
                  >
 | 
			
		||||
                  </v-sheet>
 | 
			
		||||
                </v-row>
 | 
			
		||||
              </v-list-item-content>
 | 
			
		||||
 | 
			
		||||
              <v-list-item-action class="ml-auto">
 | 
			
		||||
                <v-btn large icon @click.stop="editTheme(item)">
 | 
			
		||||
                  <v-icon color="accent">mdi-square-edit-outline</v-icon>
 | 
			
		||||
                </v-btn>
 | 
			
		||||
              </v-list-item-action>
 | 
			
		||||
            </v-list-item>
 | 
			
		||||
          </template>
 | 
			
		||||
        </v-virtual-scroll>
 | 
			
		||||
        <v-divider></v-divider>
 | 
			
		||||
        <v-card-actions>
 | 
			
		||||
          <v-spacer class="mx-2"></v-spacer>
 | 
			
		||||
          <v-btn class="my-1 mb-n1" :color="color" @click="createTheme">
 | 
			
		||||
            <v-icon left> mdi-plus </v-icon> {{ $t("general.create") }}
 | 
			
		||||
          </v-btn>
 | 
			
		||||
        </v-card-actions>
 | 
			
		||||
      </template>
 | 
			
		||||
    </StatCard>
 | 
			
		||||
    <BaseDialog
 | 
			
		||||
      :loading="loading"
 | 
			
		||||
      :title="modalLabel.title"
 | 
			
		||||
      title-icon="mdi-format-color-fill"
 | 
			
		||||
      modal-width="700"
 | 
			
		||||
      ref="themeDialog"
 | 
			
		||||
      :submit-text="modalLabel.button"
 | 
			
		||||
      @submit="processSubmit"
 | 
			
		||||
      @delete="deleteTheme"
 | 
			
		||||
    >
 | 
			
		||||
      <v-card-text class="mt-3">
 | 
			
		||||
        <v-text-field
 | 
			
		||||
          :label="$t('settings.theme.theme-name')"
 | 
			
		||||
          v-model="defaultData.name"
 | 
			
		||||
          :rules="[rules.required]"
 | 
			
		||||
        ></v-text-field>
 | 
			
		||||
        <v-row dense dflex wrap justify-content-center v-if="defaultData.colors">
 | 
			
		||||
          <v-col cols="12" sm="6" v-for="(_, key) in defaultData.colors" :key="key">
 | 
			
		||||
            <ColorPickerDialog :button-text="labels[key]" v-model="defaultData.colors[key]" />
 | 
			
		||||
          </v-col>
 | 
			
		||||
        </v-row>
 | 
			
		||||
      </v-card-text>
 | 
			
		||||
    </BaseDialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { api } from "@/api";
 | 
			
		||||
import ColorPickerDialog from "@/components/FormHelpers/ColorPickerDialog";
 | 
			
		||||
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
 | 
			
		||||
import StatCard from "@/components/UI/StatCard";
 | 
			
		||||
export default {
 | 
			
		||||
  components: { StatCard, BaseDialog, ColorPickerDialog },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      availableThemes: [],
 | 
			
		||||
      color: "accent",
 | 
			
		||||
      newTheme: false,
 | 
			
		||||
      loading: false,
 | 
			
		||||
      defaultData: {
 | 
			
		||||
        name: "",
 | 
			
		||||
        colors: {
 | 
			
		||||
          primary: "#E58325",
 | 
			
		||||
          accent: "#00457A",
 | 
			
		||||
          secondary: "#973542",
 | 
			
		||||
          success: "#43A047",
 | 
			
		||||
          info: "#4990BA",
 | 
			
		||||
          warning: "#FF4081",
 | 
			
		||||
          error: "#EF5350",
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      rules: {
 | 
			
		||||
        required: val => !!val || this.$t("settings.theme.theme-name-is-required"),
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    labels() {
 | 
			
		||||
      return {
 | 
			
		||||
        primary: this.$t("settings.theme.primary"),
 | 
			
		||||
        secondary: this.$t("settings.theme.secondary"),
 | 
			
		||||
        accent: this.$t("settings.theme.accent"),
 | 
			
		||||
        success: this.$t("settings.theme.success"),
 | 
			
		||||
        info: this.$t("settings.theme.info"),
 | 
			
		||||
        warning: this.$t("settings.theme.warning"),
 | 
			
		||||
        error: this.$t("settings.theme.error"),
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    modalLabel() {
 | 
			
		||||
      if (this.newTheme) {
 | 
			
		||||
        return {
 | 
			
		||||
          title: this.$t("settings.add-a-new-theme"),
 | 
			
		||||
          button: this.$t("general.create"),
 | 
			
		||||
        };
 | 
			
		||||
      } else {
 | 
			
		||||
        return {
 | 
			
		||||
          title: "Update Theme",
 | 
			
		||||
          button: this.$t("general.update"),
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    selectedTheme: {
 | 
			
		||||
      set(val) {
 | 
			
		||||
        this.$store.commit("setTheme", val);
 | 
			
		||||
      },
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.$store.getters.getActiveTheme;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    darkMode: {
 | 
			
		||||
      set(val) {
 | 
			
		||||
        this.$store.commit("setDarkMode", val);
 | 
			
		||||
      },
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.$store.getters.getDarkMode;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  async mounted() {
 | 
			
		||||
    await this.getAllThemes();
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    async getAllThemes() {
 | 
			
		||||
      this.availableThemes = await api.themes.requestAll();
 | 
			
		||||
    },
 | 
			
		||||
    editTheme(theme) {
 | 
			
		||||
      console.log(theme);
 | 
			
		||||
      this.defaultData = theme;
 | 
			
		||||
      this.newTheme = false;
 | 
			
		||||
      this.$refs.themeDialog.open();
 | 
			
		||||
    },
 | 
			
		||||
    createTheme() {
 | 
			
		||||
      this.newTheme = true;
 | 
			
		||||
      this.$refs.themeDialog.open();
 | 
			
		||||
      console.log("Create Theme");
 | 
			
		||||
    },
 | 
			
		||||
    async processSubmit() {
 | 
			
		||||
      if (this.newTheme) {
 | 
			
		||||
        console.log("New Theme");
 | 
			
		||||
        await api.themes.create(this.defaultData);
 | 
			
		||||
      } else {
 | 
			
		||||
        await api.themes.update(this.defaultData);
 | 
			
		||||
      }
 | 
			
		||||
      this.getAllThemes();
 | 
			
		||||
    },
 | 
			
		||||
    async deleteTheme() {
 | 
			
		||||
      console.log(this.defaultData);
 | 
			
		||||
      await api.themes.delete(this.defaultData.id);
 | 
			
		||||
      this.getAllThemes();
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										190
									
								
								frontend/src/pages/Admin/Profile/UserCard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								frontend/src/pages/Admin/Profile/UserCard.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <StatCard icon="mdi-account">
 | 
			
		||||
    <template v-slot:avatar>
 | 
			
		||||
      <v-avatar color="accent" size="120" class="white--text headline mt-n16">
 | 
			
		||||
        <img :src="userProfileImage" v-if="!hideImage" @error="hideImage = true" />
 | 
			
		||||
        <div v-else>
 | 
			
		||||
          {{ initials }}
 | 
			
		||||
        </div>
 | 
			
		||||
      </v-avatar>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template v-slot:after-heading>
 | 
			
		||||
      <div class="ml-auto text-right">
 | 
			
		||||
        <div class="body-3 grey--text font-weight-light" v-text="$t('user.user-id-with-value', { id: user.id })" />
 | 
			
		||||
 | 
			
		||||
        <h3 class="display-2 font-weight-light text--primary">
 | 
			
		||||
          <small> {{ $t("group.group") }}: {{ user.group }} </small>
 | 
			
		||||
        </h3>
 | 
			
		||||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template v-slot:actions>
 | 
			
		||||
      <BaseDialog
 | 
			
		||||
        :title="$t('user.reset-password')"
 | 
			
		||||
        title-icon="mdi-lock"
 | 
			
		||||
        :submit-text="$t('settings.change-password')"
 | 
			
		||||
        @submit="changePassword"
 | 
			
		||||
        :loading="loading"
 | 
			
		||||
        :top="true"
 | 
			
		||||
      >
 | 
			
		||||
        <template v-slot:open="{ open }">
 | 
			
		||||
          <v-btn color="primary" class="mr-1" small @click="open">
 | 
			
		||||
            <v-icon left>mdi-lock</v-icon>
 | 
			
		||||
            Change Password
 | 
			
		||||
          </v-btn>
 | 
			
		||||
        </template>
 | 
			
		||||
 | 
			
		||||
        <v-card-text>
 | 
			
		||||
          <v-form ref="passChange">
 | 
			
		||||
            <v-text-field
 | 
			
		||||
              v-model="password.current"
 | 
			
		||||
              prepend-icon="mdi-lock"
 | 
			
		||||
              :label="$t('user.current-password')"
 | 
			
		||||
              :rules="[existsRule]"
 | 
			
		||||
              validate-on-blur
 | 
			
		||||
              :type="showPassword ? 'text' : 'password'"
 | 
			
		||||
              @click:append="showPassword.current = !showPassword.current"
 | 
			
		||||
            ></v-text-field>
 | 
			
		||||
            <v-text-field
 | 
			
		||||
              v-model="password.newOne"
 | 
			
		||||
              prepend-icon="mdi-lock"
 | 
			
		||||
              :label="$t('user.new-password')"
 | 
			
		||||
              :rules="[minRule]"
 | 
			
		||||
              :type="showPassword ? 'text' : 'password'"
 | 
			
		||||
              @click:append="showPassword.newOne = !showPassword.newOne"
 | 
			
		||||
            ></v-text-field>
 | 
			
		||||
            <v-text-field
 | 
			
		||||
              v-model="password.newTwo"
 | 
			
		||||
              prepend-icon="mdi-lock"
 | 
			
		||||
              :label="$t('user.confirm-password')"
 | 
			
		||||
              :rules="[password.newOne === password.newTwo || $t('user.password-must-match')]"
 | 
			
		||||
              validate-on-blur
 | 
			
		||||
              :type="showPassword ? 'text' : 'password'"
 | 
			
		||||
              @click:append="showPassword.newTwo = !showPassword.newTwo"
 | 
			
		||||
            ></v-text-field>
 | 
			
		||||
          </v-form>
 | 
			
		||||
        </v-card-text>
 | 
			
		||||
      </BaseDialog>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template v-slot:bottom>
 | 
			
		||||
      <v-card-text>
 | 
			
		||||
        <v-form>
 | 
			
		||||
          <v-text-field
 | 
			
		||||
            :label="$t('user.full-name')"
 | 
			
		||||
            required
 | 
			
		||||
            v-model="user.fullName"
 | 
			
		||||
            :rules="[existsRule]"
 | 
			
		||||
            validate-on-blur
 | 
			
		||||
          >
 | 
			
		||||
          </v-text-field>
 | 
			
		||||
          <v-text-field :label="$t('user.email')" :rules="[emailRule]" validate-on-blur required v-model="user.email">
 | 
			
		||||
          </v-text-field>
 | 
			
		||||
        </v-form>
 | 
			
		||||
      </v-card-text>
 | 
			
		||||
      <v-divider></v-divider>
 | 
			
		||||
      <v-card-actions class="pb-1 pt-3">
 | 
			
		||||
        <TheUploadBtn
 | 
			
		||||
          icon="mdi-image-area"
 | 
			
		||||
          :text="$t('user.upload-photo')"
 | 
			
		||||
          :url="userProfileImage"
 | 
			
		||||
          file-name="profile_image"
 | 
			
		||||
        />
 | 
			
		||||
        <v-spacer></v-spacer>
 | 
			
		||||
        <v-btn color="success" @click="updateUser">
 | 
			
		||||
          <v-icon left> mdi-content-save </v-icon>
 | 
			
		||||
          {{ $t("general.update") }}
 | 
			
		||||
        </v-btn>
 | 
			
		||||
      </v-card-actions>
 | 
			
		||||
    </template>
 | 
			
		||||
  </StatCard>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
 | 
			
		||||
import StatCard from "@/components/UI/StatCard";
 | 
			
		||||
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
 | 
			
		||||
import { api } from "@/api";
 | 
			
		||||
import { validators } from "@/mixins/validators";
 | 
			
		||||
import { initials } from "@/mixins/initials";
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    BaseDialog,
 | 
			
		||||
    TheUploadBtn,
 | 
			
		||||
    StatCard,
 | 
			
		||||
  },
 | 
			
		||||
  mixins: [validators, initials],
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      hideImage: false,
 | 
			
		||||
      passwordLoading: false,
 | 
			
		||||
      password: {
 | 
			
		||||
        current: "",
 | 
			
		||||
        newOne: "",
 | 
			
		||||
        newTwo: "",
 | 
			
		||||
      },
 | 
			
		||||
      showPassword: false,
 | 
			
		||||
      loading: false,
 | 
			
		||||
      user: {
 | 
			
		||||
        fullName: "",
 | 
			
		||||
        email: "",
 | 
			
		||||
        group: "",
 | 
			
		||||
        admin: false,
 | 
			
		||||
        id: 0,
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  computed: {
 | 
			
		||||
    userProfileImage() {
 | 
			
		||||
      this.resetImage();
 | 
			
		||||
      return `api/users/${this.user.id}/image`;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  async mounted() {
 | 
			
		||||
    this.refreshProfile();
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  methods: {
 | 
			
		||||
    resetImage() {
 | 
			
		||||
      this.hideImage = false;
 | 
			
		||||
    },
 | 
			
		||||
    async refreshProfile() {
 | 
			
		||||
      this.user = await api.users.self();
 | 
			
		||||
    },
 | 
			
		||||
    openAvatarPicker() {
 | 
			
		||||
      this.showAvatarPicker = true;
 | 
			
		||||
    },
 | 
			
		||||
    selectAvatar(avatar) {
 | 
			
		||||
      this.user.avatar = avatar;
 | 
			
		||||
    },
 | 
			
		||||
    async updateUser() {
 | 
			
		||||
      this.loading = true;
 | 
			
		||||
 | 
			
		||||
      const response = await api.users.update(this.user);
 | 
			
		||||
      if (response) {
 | 
			
		||||
        this.$store.commit("setToken", response.data.access_token);
 | 
			
		||||
        this.refreshProfile();
 | 
			
		||||
        this.loading = false;
 | 
			
		||||
        this.$store.dispatch("requestUserData");
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    async changePassword() {
 | 
			
		||||
      this.paswordLoading = true;
 | 
			
		||||
      let data = {
 | 
			
		||||
        currentPassword: this.password.current,
 | 
			
		||||
        newPassword: this.password.newOne,
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      if (this.$refs.passChange.validate()) {
 | 
			
		||||
        if (await api.users.changePassword(this.user.id, data)) {
 | 
			
		||||
          this.$emit("refresh");
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      this.paswordLoading = false;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style></style>
 | 
			
		||||
@@ -1,206 +1,27 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <v-row dense>
 | 
			
		||||
    <v-col cols="12" md="8" sm="12">
 | 
			
		||||
      <v-card>
 | 
			
		||||
        <v-card-title class="headline">
 | 
			
		||||
          <span>
 | 
			
		||||
            <v-progress-circular v-if="loading" indeterminate color="primary" large class="mr-2"> </v-progress-circular>
 | 
			
		||||
          </span>
 | 
			
		||||
          {{ $t("settings.profile") }}
 | 
			
		||||
          <v-spacer></v-spacer>
 | 
			
		||||
          {{ $t("user.user-id-with-value", { id: user.id }) }}
 | 
			
		||||
        </v-card-title>
 | 
			
		||||
        <v-divider></v-divider>
 | 
			
		||||
        <v-card-text>
 | 
			
		||||
          <v-row>
 | 
			
		||||
            <v-col cols="12" md="3" align="center" justify="center">
 | 
			
		||||
              <v-avatar color="accent" size="120" class="white--text headline mr-2">
 | 
			
		||||
                <img :src="userProfileImage" v-if="!hideImage" @error="hideImage = true" />
 | 
			
		||||
                <div v-else>
 | 
			
		||||
                  {{ initials }}
 | 
			
		||||
                </div>
 | 
			
		||||
              </v-avatar>
 | 
			
		||||
            </v-col>
 | 
			
		||||
            <v-col cols="12" md="9">
 | 
			
		||||
              <v-form>
 | 
			
		||||
                <v-text-field
 | 
			
		||||
                  :label="$t('user.full-name')"
 | 
			
		||||
                  required
 | 
			
		||||
                  v-model="user.fullName"
 | 
			
		||||
                  :rules="[existsRule]"
 | 
			
		||||
                  validate-on-blur
 | 
			
		||||
                >
 | 
			
		||||
                </v-text-field>
 | 
			
		||||
                <v-text-field
 | 
			
		||||
                  :label="$t('user.email')"
 | 
			
		||||
                  :rules="[emailRule]"
 | 
			
		||||
                  validate-on-blur
 | 
			
		||||
                  required
 | 
			
		||||
                  v-model="user.email"
 | 
			
		||||
                >
 | 
			
		||||
                </v-text-field>
 | 
			
		||||
                <v-text-field
 | 
			
		||||
                  :label="$t('group.group')"
 | 
			
		||||
                  readonly
 | 
			
		||||
                  v-model="user.group"
 | 
			
		||||
                  persistent-hint
 | 
			
		||||
                  :hint="$t('group.groups-can-only-be-set-by-administrators')"
 | 
			
		||||
                >
 | 
			
		||||
                </v-text-field>
 | 
			
		||||
              </v-form>
 | 
			
		||||
            </v-col>
 | 
			
		||||
          </v-row>
 | 
			
		||||
        </v-card-text>
 | 
			
		||||
 | 
			
		||||
        <v-card-actions>
 | 
			
		||||
          <TheUploadBtn
 | 
			
		||||
            icon="mdi-image-area"
 | 
			
		||||
            :text="$t('user.upload-photo')"
 | 
			
		||||
            :url="userProfileImage"
 | 
			
		||||
            file-name="profile_image"
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <v-spacer></v-spacer>
 | 
			
		||||
          <v-btn color="success" class="mr-2" @click="updateUser">
 | 
			
		||||
            <v-icon left> mdi-content-save </v-icon>
 | 
			
		||||
            {{ $t("general.save") }}
 | 
			
		||||
          </v-btn>
 | 
			
		||||
        </v-card-actions>
 | 
			
		||||
      </v-card>
 | 
			
		||||
    </v-col>
 | 
			
		||||
    <v-col cols="12" md="4" sm="12">
 | 
			
		||||
      <v-card height="100%">
 | 
			
		||||
        <v-card-title class="headline">
 | 
			
		||||
          {{ $t("user.reset-password") }}
 | 
			
		||||
          <v-spacer></v-spacer>
 | 
			
		||||
        </v-card-title>
 | 
			
		||||
        <v-divider></v-divider>
 | 
			
		||||
        <v-card-text>
 | 
			
		||||
          <v-form ref="passChange">
 | 
			
		||||
            <v-text-field
 | 
			
		||||
              v-model="password.current"
 | 
			
		||||
              prepend-icon="mdi-lock"
 | 
			
		||||
              :label="$t('user.current-password')"
 | 
			
		||||
              :rules="[existsRule]"
 | 
			
		||||
              validate-on-blur
 | 
			
		||||
              :type="showPassword ? 'text' : 'password'"
 | 
			
		||||
              @click:append="showPassword.current = !showPassword.current"
 | 
			
		||||
            ></v-text-field>
 | 
			
		||||
            <v-text-field
 | 
			
		||||
              v-model="password.newOne"
 | 
			
		||||
              prepend-icon="mdi-lock"
 | 
			
		||||
              :label="$t('user.new-password')"
 | 
			
		||||
              :rules="[minRule]"
 | 
			
		||||
              :type="showPassword ? 'text' : 'password'"
 | 
			
		||||
              @click:append="showPassword.newOne = !showPassword.newOne"
 | 
			
		||||
            ></v-text-field>
 | 
			
		||||
            <v-text-field
 | 
			
		||||
              v-model="password.newTwo"
 | 
			
		||||
              prepend-icon="mdi-lock"
 | 
			
		||||
              :label="$t('user.confirm-password')"
 | 
			
		||||
              :rules="[password.newOne === password.newTwo || $t('user.password-must-match')]"
 | 
			
		||||
              validate-on-blur
 | 
			
		||||
              :type="showPassword ? 'text' : 'password'"
 | 
			
		||||
              @click:append="showPassword.newTwo = !showPassword.newTwo"
 | 
			
		||||
            ></v-text-field>
 | 
			
		||||
          </v-form>
 | 
			
		||||
        </v-card-text>
 | 
			
		||||
        <v-card-actions>
 | 
			
		||||
          <v-btn icon @click="showPassword = !showPassword" :loading="passwordLoading">
 | 
			
		||||
            <v-icon v-if="!showPassword">mdi-eye-off</v-icon>
 | 
			
		||||
            <v-icon v-else> mdi-eye </v-icon>
 | 
			
		||||
          </v-btn>
 | 
			
		||||
          <v-spacer></v-spacer>
 | 
			
		||||
          <v-btn color="accent" class="mr-2" @click="changePassword">
 | 
			
		||||
            <v-icon left> mdi-lock </v-icon>
 | 
			
		||||
            {{ $t("settings.change-password") }}
 | 
			
		||||
          </v-btn>
 | 
			
		||||
        </v-card-actions>
 | 
			
		||||
      </v-card>
 | 
			
		||||
    </v-col>
 | 
			
		||||
  </v-row>
 | 
			
		||||
  <div class="mt-10">
 | 
			
		||||
    <v-row>
 | 
			
		||||
      <v-col cols="12" sm="12" lg="6">
 | 
			
		||||
        <UserCard />
 | 
			
		||||
      </v-col>
 | 
			
		||||
      <v-col cols="12" sm="12" lg="6"> </v-col>
 | 
			
		||||
    </v-row>
 | 
			
		||||
    <v-row class="mt-7">
 | 
			
		||||
      <v-col cols="12" sm="12" lg="6">
 | 
			
		||||
        <ThemeCard />
 | 
			
		||||
      </v-col>
 | 
			
		||||
      <v-col cols="12" sm="12" lg="6"> </v-col>
 | 
			
		||||
    </v-row>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
// import AvatarPicker from '@/components/AvatarPicker'
 | 
			
		||||
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
 | 
			
		||||
import { api } from "@/api";
 | 
			
		||||
import { validators } from "@/mixins/validators";
 | 
			
		||||
import { initials } from "@/mixins/initials";
 | 
			
		||||
import ThemeCard from "./ThemeCard";
 | 
			
		||||
import UserCard from "./UserCard";
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    TheUploadBtn,
 | 
			
		||||
  },
 | 
			
		||||
  mixins: [validators, initials],
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      hideImage: false,
 | 
			
		||||
      passwordLoading: false,
 | 
			
		||||
      password: {
 | 
			
		||||
        current: "",
 | 
			
		||||
        newOne: "",
 | 
			
		||||
        newTwo: "",
 | 
			
		||||
      },
 | 
			
		||||
      showPassword: false,
 | 
			
		||||
      loading: false,
 | 
			
		||||
      user: {
 | 
			
		||||
        fullName: "",
 | 
			
		||||
        email: "",
 | 
			
		||||
        group: "",
 | 
			
		||||
        admin: false,
 | 
			
		||||
        id: 0,
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  computed: {
 | 
			
		||||
    userProfileImage() {
 | 
			
		||||
      this.resetImage();
 | 
			
		||||
      return `api/users/${this.user.id}/image`;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  async mounted() {
 | 
			
		||||
    this.refreshProfile();
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  methods: {
 | 
			
		||||
    resetImage() {
 | 
			
		||||
      this.hideImage = false;
 | 
			
		||||
    },
 | 
			
		||||
    async refreshProfile() {
 | 
			
		||||
      this.user = await api.users.self();
 | 
			
		||||
    },
 | 
			
		||||
    openAvatarPicker() {
 | 
			
		||||
      this.showAvatarPicker = true;
 | 
			
		||||
    },
 | 
			
		||||
    selectAvatar(avatar) {
 | 
			
		||||
      this.user.avatar = avatar;
 | 
			
		||||
    },
 | 
			
		||||
    async updateUser() {
 | 
			
		||||
      this.loading = true;
 | 
			
		||||
      const response = await api.users.update(this.user);
 | 
			
		||||
      if (response) {
 | 
			
		||||
        this.$store.commit("setToken", response.data.access_token);
 | 
			
		||||
        this.refreshProfile();
 | 
			
		||||
        this.loading = false;
 | 
			
		||||
        this.$store.dispatch("requestUserData");
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    async changePassword() {
 | 
			
		||||
      this.paswordLoading = true;
 | 
			
		||||
      let data = {
 | 
			
		||||
        currentPassword: this.password.current,
 | 
			
		||||
        newPassword: this.password.newOne,
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      if (this.$refs.passChange.validate()) {
 | 
			
		||||
        if (await api.users.changePassword(this.user.id, data)) {
 | 
			
		||||
          this.$emit("refresh");
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      this.paswordLoading = false;
 | 
			
		||||
    },
 | 
			
		||||
    UserCard,
 | 
			
		||||
    ThemeCard,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user