From 91ede955300a9c30886e997ed5b6b1c5be6eda0b Mon Sep 17 00:00:00 2001
From: Julian Krauser <jkrauser209@gmail.com>
Date: Tue, 29 Apr 2025 18:32:08 +0200
Subject: [PATCH] Image Upload

---
 .../admin/management/setting/BaseSetting.vue  |  10 +-
 .../management/setting/ClubImageSetting.vue   | 100 ++++++++++++++----
 .../admin/management/setting/MailSetting.vue  |   2 +-
 src/stores/admin/management/setting.ts        |  39 +++++--
 4 files changed, 117 insertions(+), 34 deletions(-)

diff --git a/src/components/admin/management/setting/BaseSetting.vue b/src/components/admin/management/setting/BaseSetting.vue
index 96c1308..a879de1 100644
--- a/src/components/admin/management/setting/BaseSetting.vue
+++ b/src/components/admin/management/setting/BaseSetting.vue
@@ -9,7 +9,14 @@
         <button type="submit" class="!w-fit !h-fit !p-0">
           <CheckIcon class="h-5 w-5 cursor-pointer" />
         </button>
-        <button type="reset" class="!w-fit !h-fit !p-0" @click="enableEdit = false">
+        <button
+          type="reset"
+          class="!w-fit !h-fit !p-0"
+          @click="
+            enableEdit = false;
+            $emit('reset');
+          "
+        >
           <XMarkIcon class="h-5 w-5 cursor-pointer" />
         </button>
       </div>
@@ -48,6 +55,7 @@ export default defineComponent({
       required: true,
     },
   },
+  emits: ["reset"],
   data() {
     return {
       enableEdit: false as boolean,
diff --git a/src/components/admin/management/setting/ClubImageSetting.vue b/src/components/admin/management/setting/ClubImageSetting.vue
index e2ab129..1a1d675 100644
--- a/src/components/admin/management/setting/ClubImageSetting.vue
+++ b/src/components/admin/management/setting/ClubImageSetting.vue
@@ -1,31 +1,48 @@
 <template>
-  <BaseSetting title="Vereins-Auftritt Einstellungen" :submit-function="submit" v-slot="{ enableEdit }">
+  <BaseSetting title="Vereins-Auftritt Einstellungen" :submit-function="submit" v-slot="{ enableEdit }" @reset="reset">
     <div class="w-full">
       <p>Vereins-Icon</p>
-      <AppIcon v-if="clubSettings['club.icon'] != '' && !overwriteIcon" class="h-10! max-w-full mx-auto" />
-      <img v-else-if="overwriteIcon" ref="icon_img" class="hidden w-full h-20 object-contain" />
-      <div
-        v-else
-        class="flex h-10 w-full border-2 border-gray-300 rounded-md items-center justify-center text-sm cursor-pointer"
-        @click="($refs.icon as HTMLInputElement).click()"
-      >
-        Kein eigenes Icon ausgewählt
+      <div class="flex flex-row gap-2">
+        <AppIcon v-if="icon != '' && !overwriteIcon" class="h-10! max-w-full mx-auto" />
+        <div
+          v-else-if="!overwriteIcon"
+          class="flex h-10 w-full border-2 border-gray-300 rounded-md items-center justify-center text-sm"
+          :class="{ 'cursor-pointer': enableEdit }"
+          @click="enableEdit ? ($refs.icon as HTMLInputElement).click() : null"
+        >
+          Kein eigenes Icon ausgewählt
+        </div>
+        <img ref="icon_img" class="hidden w-full h-20 object-contain" />
+        <XMarkIcon
+          v-if="enableEdit && (icon != '' || overwriteIcon)"
+          class="h-5 w-5 cursor-pointer"
+          @click="resetImage('icon')"
+        />
       </div>
       <input class="hidden!" type="file" ref="icon" accept="image/*" @change="previewImage('icon')" />
     </div>
     <div class="w-full">
       <p>Vereins-Logo</p>
-      <AppLogo v-if="clubSettings['club.logo'] != '' && !overwriteLogo" class="h-10! max-w-full mx-auto" />
-      <img v-else-if="overwriteLogo" ref="logo_img" class="hidden w-full h-20 object-contain" />
-      <div
-        v-else
-        class="flex h-10 w-full border-2 border-gray-300 rounded-md items-center justify-center text-sm cursor-pointer"
-        @click="($refs.logo as HTMLInputElement).click()"
-      >
-        Kein eigenes Logo ausgewählt
+      <div class="flex flex-row gap-2">
+        <AppLogo v-if="logo != '' && !overwriteLogo" class="h-10! max-w-full mx-auto" />
+        <div
+          v-else-if="!overwriteLogo"
+          class="flex h-10 w-full border-2 border-gray-300 rounded-md items-center justify-center text-sm"
+          :class="{ 'cursor-pointer': enableEdit }"
+          @click="enableEdit ? ($refs.logo as HTMLInputElement).click() : null"
+        >
+          Kein eigenes Logo ausgewählt
+        </div>
+        <img ref="logo_img" class="hidden w-full h-20 object-contain" />
+        <XMarkIcon
+          v-if="enableEdit && (logo != '' || overwriteLogo)"
+          class="h-5 w-5 cursor-pointer"
+          @click="resetImage('logo')"
+        />
       </div>
-      <input class="hidden!" type="file" ref="logo" accept="image/*" @change="previewImage('logo')" /></div
-  ></BaseSetting>
+      <input class="hidden!" type="file" ref="logo" accept="image/*" @change="previewImage('logo')" />
+    </div>
+  </BaseSetting>
 </template>
 
 <script setup lang="ts">
@@ -37,12 +54,20 @@ import AppLogo from "@/components/AppLogo.vue";
 import { useAbilityStore } from "@/stores/ability";
 import type { SettingString } from "@/types/settingTypes";
 import BaseSetting from "./BaseSetting.vue";
+import { XMarkIcon } from "@heroicons/vue/24/outline";
 </script>
 
 <script lang="ts">
 export default defineComponent({
+  watch: {
+    clubSettings() {
+      this.reset();
+    },
+  },
   data() {
     return {
+      logo: "",
+      icon: "",
       overwriteIcon: false as boolean,
       overwriteLogo: false as boolean,
     };
@@ -54,8 +79,35 @@ export default defineComponent({
       return this.readByTopic("club");
     },
   },
+  mounted() {
+    this.reset();
+  },
   methods: {
     ...mapActions(useSettingStore, ["updateSettings", "uploadImage"]),
+    reset() {
+      this.icon = this.clubSettings["club.icon"];
+      this.overwriteIcon = false;
+      (this.$refs.icon_img as HTMLImageElement).style.display = "none";
+      (this.$refs.icon as HTMLInputElement).value = "";
+
+      this.logo = this.clubSettings["club.logo"];
+      this.overwriteLogo = false;
+      (this.$refs.logo_img as HTMLImageElement).style.display = "none";
+      (this.$refs.logo as HTMLInputElement).value = "";
+    },
+    resetImage(inputname: "icon" | "logo") {
+      if (inputname == "icon") {
+        this.icon = "";
+        this.overwriteIcon = false;
+        (this.$refs.icon_img as HTMLImageElement).style.display = "none";
+        (this.$refs.icon as HTMLInputElement).value = "";
+      } else {
+        this.logo = "";
+        this.overwriteLogo = false;
+        (this.$refs.logo_img as HTMLImageElement).style.display = "none";
+        (this.$refs.logo as HTMLInputElement).value = "";
+      }
+    },
     previewImage(inputname: "icon" | "logo") {
       let input = this.$refs[inputname] as HTMLInputElement;
       let previewElement = this.$refs[inputname + "_img"] as HTMLImageElement;
@@ -68,6 +120,12 @@ export default defineComponent({
         };
 
         reader.readAsDataURL(input.files[0]);
+
+        if (inputname == "icon") {
+          this.overwriteIcon = true;
+        } else {
+          this.overwriteLogo = true;
+        }
       } else {
         previewElement.src = "";
         previewElement.style.display = "none";
@@ -76,11 +134,11 @@ export default defineComponent({
     submit(e: any) {
       return this.uploadImage([
         {
-          key: "club.icon" as SettingString,
+          key: "club.icon",
           value: (this.$refs.icon as HTMLInputElement).files?.[0],
         },
         {
-          key: "club.logo" as SettingString,
+          key: "club.logo",
           value: (this.$refs.logo as HTMLInputElement).files?.[0],
         },
       ]);
diff --git a/src/components/admin/management/setting/MailSetting.vue b/src/components/admin/management/setting/MailSetting.vue
index 64ca03d..4b09057 100644
--- a/src/components/admin/management/setting/MailSetting.vue
+++ b/src/components/admin/management/setting/MailSetting.vue
@@ -34,7 +34,7 @@
       <label for="secure">Secure-Verbindung (setzen bei Port 465)</label>
     </div>
     <div class="w-full">
-      <label for="password">Passwort (optional)</label>
+      <label for="password">Passwort (optional - leeres Feld setzt Passwort nicht zurück)</label>
       <input id="password" type="password" :readonly="!enableEdit" autocomplete="new-password" />
     </div>
   </BaseSetting>
diff --git a/src/stores/admin/management/setting.ts b/src/stores/admin/management/setting.ts
index 20c39bc..98bd8a8 100644
--- a/src/stores/admin/management/setting.ts
+++ b/src/stores/admin/management/setting.ts
@@ -54,26 +54,43 @@ export const useSettingStore = defineStore("setting", {
       key: K,
       value: SettingValueMapping[K]
     ): Promise<AxiosResponse<any, any>> {
-      return await http.put("/admin/setting", {
-        setting: key,
-        value: value,
-      });
+      return await http
+        .put("/admin/setting", {
+          setting: key,
+          value: value,
+        })
+        .then((res) => {
+          this.settings[key] = value;
+          return res;
+        });
     },
     async updateSettings<K extends SettingString>(
       data: { key: K; value: SettingValueMapping[K] }[]
     ): Promise<AxiosResponse<any, any>> {
-      return await http.put("/admin/setting/multi", data);
+      return await http.put("/admin/setting/multi", data).then((res) => {
+        for (const element of data) {
+          this.settings[element.key] = element.value;
+        }
+        return res;
+      });
     },
-    async uploadImage(data: { key: SettingString; value?: File }[]): Promise<AxiosResponse<any, any>> {
+    async uploadImage(data: { key: "club.logo" | "club.icon"; value?: File }[]): Promise<AxiosResponse<any, any>> {
       const formData = new FormData();
       for (let entry of data) {
         if (entry.value) formData.append(entry.key, entry.value);
       }
-      return await http.put("/admin/setting/images", formData, {
-        headers: {
-          "Content-Type": "multipart/form-data",
-        },
-      });
+      return await http
+        .put("/admin/setting/images", formData, {
+          headers: {
+            "Content-Type": "multipart/form-data",
+          },
+        })
+        .then((res) => {
+          for (const element of data) {
+            this.settings[element.key] = element.value ? "configured" : "";
+          }
+          return res;
+        });
     },
     async resetSetting(key: SettingString): Promise<AxiosResponse<any, any>> {
       return await http.delete(`/admin/setting/${key}`);