SyuanYu 2 年之前
父节点
当前提交
20c5751968

+ 19 - 11
frontend/index.html

@@ -1,13 +1,21 @@
 <!DOCTYPE html>
 <html lang="en">
-  <head>
-    <meta charset="UTF-8">
-    <link rel="icon" href="/favicon.ico">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Vite App</title>
-  </head>
-  <body>
-    <div id="app"></div>
-    <script type="module" src="/src/main.ts"></script>
-  </body>
-</html>
+
+<head>
+  <meta charset="UTF-8">
+  <link rel="icon" href="/favicon.ico">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <!-- Cache -->
+  <meta http-equiv="cache-control" content="no-cache">
+  <meta http-equiv="pragma" content="no-cache">
+  <meta http-equiv="expires" content="0">
+
+  <title>Vite App</title>
+</head>
+
+<body>
+  <div id="app"></div>
+  <script type="module" src="/src/main.ts"></script>
+</body>
+
+</html>

+ 1 - 1
frontend/src/components/NotificationsManager.vue

@@ -78,7 +78,7 @@ async function setNotification(notification: AppNotification | false) {
       ></v-progress-circular
       >{{ currentNotificationContent }}
       <template v-slot:actions>
-        <v-btn flat @click.native="close">{{ $t("close") }}</v-btn>
+        <v-btn flat @click.native="close">{{ t("close") }}</v-btn>
       </template>
     </v-snackbar>
   </div>

+ 16 - 3
frontend/src/language/en.json

@@ -1,5 +1,6 @@
 {
     "login" : "Login",
+    "loggedIn" : "Logged in",
     "loginLink" : "Login",
     "logout": "Logout",
     "submit": "Submit",
@@ -14,11 +15,14 @@
     "haveAccount": "Have an account?",
     "haventAccount": "Don't have an account?",
     "register": "Register",
+    "signingUp": "signing up...",
     "registerLink": "Register",
+    "registerSuccess": "Successfully registered",
     "privacy_term_1": "Registeration implies acception of \n \n \n",
     "privacy_term_2": "terms of service and privacy policy",
     "forgotPsd": "Forgot Password",
     "passwordRecovery": "Password Recovery",
+    "passwordRecoveryMail": "A password recovery email will be sent to the registered account",
     "describe_1": "Make your first video for promotion, creation and life today",
     "describe_2": "Let's get started with AI Presentors",
     "dashboard": "Dashboard",
@@ -29,7 +33,16 @@
     "changePassword": "Change Password",
     "collapse": "Collapse",
     "language": "Language",
-    "sginingUp": "Sgining up...",
-    "successfully": "Successfully registered",
-    "close": "Close"
+    "close": "Close",
+    "save": "Save",
+    "reset": "Reset",
+    "clear": "Clear",
+    "edit": "Edit",
+    "send": "Send",
+    "title": "Title",
+    "state": "State",
+    "download": "Download",
+    "fileInput": "File input",
+    "profileUpdated": "Profile successfully updated",
+    "editUserProfile": "Edit User Profile"
 }

+ 16 - 3
frontend/src/language/zh.json

@@ -1,5 +1,6 @@
 {
     "login" : "登入",
+    "loggedIn" : "登入成功",
     "loginLink" : "立即登入",
     "logout": "登出",
     "submit": "送出",
@@ -14,11 +15,14 @@
     "haveAccount": "已經有帳號?",
     "haventAccount": "還沒有帳號?",
     "register": "註冊",
+    "signingUp": "註冊中...",
     "registerLink": "立即註冊",
+    "registerSuccess": "註冊成功",
     "privacy_term_1": "註冊即表示您已閱讀並同意",
     "privacy_term_2": "服務條款及隱私權政策",
     "forgotPsd": "忘記密碼",
     "passwordRecovery": "忘記您的密碼嗎?",
+    "passwordRecoveryMail": "請輸入註冊時填入的電子信箱,我們將寄出重設密碼的信件給您。",
     "describe_1": "將您的生活、創作、宣傳做成影片",
     "describe_2": "開始使用 AI Presentors",
     "dashboard": "首頁",
@@ -29,7 +33,16 @@
     "changePassword": "變更密碼",
     "collapse": "收合",
     "language": "語言",
-    "sginingUp": "註冊中...",
-    "successfully": "註冊成功",
-    "close": "關閉"
+    "close": "關閉",
+    "save": "儲存",
+    "reset": "重置",
+    "clear": "清除",
+    "edit": "編輯",
+    "send": "送出",
+    "title": "標題",
+    "state": "狀態",
+    "download": "下載",
+    "fileInput": "檔案上傳",
+    "profileUpdated": "更新成功",
+    "editUserProfile": "編輯資料"
 }

+ 3 - 1
frontend/src/views/Login.vue

@@ -39,7 +39,9 @@ function submit() {
 }
 
 // lifecycle
-onMounted(() => {});
+onMounted(() => {
+  console.log('onMounted');
+});
 </script>
 
 <template>

+ 1 - 1
frontend/src/views/PasswordRecovery.vue

@@ -41,7 +41,7 @@ function submit() {
           </v-toolbar>
           <v-card-text>
             <p class="subheading mb-3">
-              A password recovery email will be sent to the registered account
+              {{ t("passwordRecoveryMail") }}
             </p>
             <v-form
               @keyup.enter="submit"

+ 7 - 5
frontend/src/views/ResetPassword.vue

@@ -9,17 +9,17 @@
           <v-card-text>
             <p class="subheading">Enter your new password below</p>
             <v-form @keyup.enter="submit" v-model="valid" ref="form" @submit.prevent="" lazy-validation>
-              <v-text-field type="password" ref="password" label="Password" v-model="password1" :rules="password1Rules">
+              <v-text-field type="password" ref="password" :label="$t('password')" v-model="password1" :rules="password1Rules">
               </v-text-field>
-              <v-text-field type="password" label="Confirm Password"  v-model="password2" :rules="password2Rules">
+              <v-text-field type="password" :label="$t('confirmPassword')" v-model="password2" :rules="password2Rules">
               </v-text-field>
             </v-form>
           </v-card-text>
           <v-card-actions>
             <v-spacer></v-spacer>
-            <v-btn @click="cancel">Cancel</v-btn>
-            <v-btn @click="reset">Clear</v-btn>
-            <v-btn @click="submit" :disabled="!valid">Save</v-btn>
+            <v-btn @click="cancel">{{ t("cancel") }}</v-btn>
+            <v-btn @click="reset">{{ t("clear") }}</v-btn>
+            <v-btn @click="submit" :disabled="!valid">{{ t("save") }}</v-btn>
           </v-card-actions>
         </v-card>
       </v-col>
@@ -35,7 +35,9 @@ import router from '@/router';
 import { useMainStore } from '@/stores/main';
 import { password1Rules, usePassword2Rules } from '@/utils';
 import { useDisplay } from 'vuetify';
+import { useI18n } from "vue-i18n";
 
+const { t } = useI18n();
 const valid = ref(true);
 const password1 = ref('');
 const password2 = ref('');

+ 43 - 10
frontend/src/views/main/Dashboard.vue

@@ -1,15 +1,18 @@
 <script setup lang="ts">
-import { computed } from 'vue';
-import { useMainStore } from '@/stores/main';
-import { storeToRefs } from 'pinia';
+import { computed } from "vue";
+import { useMainStore } from "@/stores/main";
+import { storeToRefs } from "pinia";
+import { useI18n } from "vue-i18n";
 
+const { t } = useI18n();
 const mainStore = useMainStore();
 const mainStoreRef = storeToRefs(mainStore);
+const userProfile = mainStoreRef.readUserProfile;
 
 const greetedUser = computed(() => {
   const userProfile = mainStoreRef.readUserProfile;
   if (userProfile.value) {
-    if (userProfile.value!.full_name){
+    if (userProfile.value!.full_name) {
       return userProfile.value!.full_name;
     } else {
       return userProfile.value.email;
@@ -22,22 +25,52 @@ const greetedUser = computed(() => {
   <v-container fluid>
     <v-card class="ma-3 pa-3">
       <v-card-title primary-title>
-        <div class="headline primary--text">Dashboard</div>
+        <h3>Welcome {{ greetedUser }}</h3>
       </v-card-title>
-      <v-card-text>
-        <div class="headline font-weight-light ma-5">Welcome {{ greetedUser }}</div>
+      <v-card-text class="my-3">
+          <h4 class="">
+            {{ t("userName") }}
+          </h4>
+          <span v-if="userProfile && userProfile.full_name">
+            {{ userProfile.full_name }}
+          </span>
+          <div v-else>-</div>
+
+          <h4 class="mt-3">
+            {{ t("emailAddress") }}
+          </h4>
+          <span v-if="userProfile && userProfile.email">
+            {{ userProfile.email }}
+          </span>
+          <div v-else>-</div>
       </v-card-text>
       <v-card-actions>
+        <v-btn to="/main/profile/edit" variant="outlined" color="primary">{{
+          t("edit")
+        }}</v-btn>
+        <v-btn
+          to="/main/profile/password"
+          variant="flat"
+          color="primary"
+          class="ms-3"
+          >{{ t("changePassword") }}</v-btn
+        >
+      </v-card-actions>
+      <!-- <v-card-actions>
         <v-btn to="/main/profile/view">View Profile</v-btn>
         <v-btn to="/main/profile/edit">Edit Profile</v-btn>
         <v-btn to="/main/profile/password">Change Password</v-btn>
-      </v-card-actions>
+      </v-card-actions> -->
     </v-card>
   </v-container>
 </template>
 
 <style lang="scss" scoped>
 .v-toolbar__content {
-  background-image: linear-gradient(-225deg, rgb(234, 84, 19) 35%, rgb(178, 69, 146) 100%);
+  background-image: linear-gradient(
+    -225deg,
+    rgb(234, 84, 19) 35%,
+    rgb(178, 69, 146) 100%
+  );
 }
-</style>
+</style>

+ 59 - 34
frontend/src/views/main/Main.vue

@@ -1,4 +1,4 @@
-<script setup lang="ts">
+<script lang="ts">
 import { appName } from "@/env";
 import { reactive } from "vue";
 import type { RouteLocationNormalized, NavigationGuardNext } from "vue-router";
@@ -7,14 +7,17 @@ import { useMainStore } from "@/stores/main";
 import { storeToRefs } from "pinia";
 import { useI18n } from "vue-i18n";
 
-
+export default {
+  setup() {
+    onBeforeRouteUpdate((to, from, next) => {
+      routeGuardAdmin(to, from, next);
+    });
 
     const { t, locale } = useI18n();
     const mainStore = useMainStore();
     const mainStoreRef = storeToRefs(mainStore);
 
     const hasAdminAccess = mainStoreRef.readhasAdminAccess;
-
     const miniDrawer = mainStoreRef.readDashboardMiniDrawer;
     const showDrawer = mainStoreRef.readDashboardShowDrawer;
 
@@ -34,33 +37,43 @@ import { useI18n } from "vue-i18n";
       mainStore.logOut();
     }
 
-    onBeforeRouteUpdate((to, from, next) => {
-      routeGuardMain(to, from, next);
-    });
-
     const lang = reactive([
-  { title: "English", text: "en" },
-  { title: "中文", text: "zh" },
-]);
-
-function setLang(lang: String) {
-  locale.value = `${lang}`;
-  localStorage.setItem("lang", `${lang}`);
-}
+      { title: "English", text: "en" },
+      { title: "中文", text: "zh" },
+    ]);
 
-    
-  
-  // beforeRouteEnter((to, from, next) =>{
-  //   routeGuardMain(to, from, next);
-  // })
+    function setLang(lang: String) {
+      locale.value = `${lang}`;
+      localStorage.setItem("lang", `${lang}`);
+    }
 
-const routeGuardMain = async (
+    return {
+      t,
+      locale,
+      hasAdminAccess,
+      miniDrawer,
+      showDrawer,
+      switchMiniDrawer,
+      switchShowDrawer,
+      logout,
+      lang,
+      setLang,
+      appName
+    };
+  },
+  beforeRouteEnter(to, from, next) {
+    routeGuardAdmin(to, from, next);
+  },
+};
+const routeGuardAdmin = async (
   to: RouteLocationNormalized,
   from: RouteLocationNormalized,
   next: NavigationGuardNext
 ) => {
-  if (to.path === "/main") {
-    next("/main/dashboard");
+  const mainStore = useMainStore();
+  const mainStoreRef = storeToRefs(mainStore);
+  if (!mainStoreRef.readhasAdminAccess) {
+    next("/main");
   } else {
     next();
   }
@@ -71,7 +84,10 @@ const routeGuardMain = async (
   <div>
     <v-navigation-drawer persistent :rail="miniDrawer" v-model="showDrawer">
       <v-sheet class="d-flex flex-column fill-height">
-        <v-sheet class="">
+        <v-sheet>
+          <a class="d-flex justify-center mt-3">
+            <img src="@/assets/img/Choozmo-logo.png" alt="" class="logo-img" />
+          </a>
           <v-list>
             <!-- <v-list-subheader><span v-show="!miniDrawer">Main menu</span></v-list-subheader> -->
             <v-list-item to="/main/dashboard" prepend-icon="dashboard">
@@ -144,9 +160,10 @@ const routeGuardMain = async (
             <v-btn icon="more_vert" v-bind="props" />
           </template>
           <v-list>
-            <v-list-item to="/main/profile" append-icon="person">
+            <!-- <v-list-item to="/main/profile" append-icon="person">
               <v-list-item-title>{{ t("userProfile") }}</v-list-item-title>
-            </v-list-item>
+            </v-list-item> -->
+            
             <!-- <v-list-item to="/main/profile" append-icon="language">
               <v-list-item-title>{{ t("language") }}</v-list-item-title>
             </v-list-item> -->
@@ -159,13 +176,13 @@ const routeGuardMain = async (
               </template>
 
               <v-list-item
-            v-for="(item, index) in lang"
-            :key="index"
-            :value="item.text"
-            @click="setLang(item.text)"
-          >
-            <v-list-item-title>{{ item.title }}</v-list-item-title>
-          </v-list-item>
+                v-for="(item, index) in lang"
+                :key="index"
+                :value="item.text"
+                @click="setLang(item.text)"
+              >
+                <v-list-item-title>{{ item.title }}</v-list-item-title>
+              </v-list-item>
             </v-list-group>
 
             <v-list-item @click="logout" append-icon="logout">
@@ -184,7 +201,7 @@ const routeGuardMain = async (
   </div>
 </template>
 
-<style lang="scss" scoped>
+<style lang="scss">
 .navbar {
   color: #fff;
   background-image: linear-gradient(
@@ -193,4 +210,12 @@ const routeGuardMain = async (
     rgb(178, 69, 146) 100%
   );
 }
+
+.logo-img {
+  max-width: 200px;
+}
+
+.card-title {
+  letter-spacing: 1px !important;
+}
 </style>

+ 52 - 42
frontend/src/views/main/Progress.vue

@@ -1,59 +1,69 @@
-<template>
-  <div>
-    <v-toolbar light>
-      <v-toolbar-title>
-        Manage Users
-      </v-toolbar-title>
-      <v-spacer></v-spacer>
-      <v-btn color="primary" to="/main/admin/users/create">Create User</v-btn>
-    </v-toolbar>
-    <v-data-table :headers="headers" :items="videos">
-      
-      <template v-slot:item.is_active="{ item }">
-        <v-icon v-if=item.columns.is_active icon="check"/>
-      </template>
-      <template v-slot:item.is_superuser="{ item }">
-        <v-icon v-if=item.columns.is_superuser icon="check"/>
-      </template>
-      <template v-slot:item.id="{ item }">
-        <v-btn flat :to="{name: 'main-admin-users-edit', params: {id: item.columns.id}}">
-          <v-icon icon="edit"/>
-          <v-tooltip location="top" activator="parent">Edit</v-tooltip>
-        </v-btn>
-      </template>
-    </v-data-table>
-  </div>
-</template>
 <script setup lang="ts">
-import { useMainStore } from '@/stores/main';
-import { storeToRefs} from 'pinia';
-import { onMounted } from 'vue';
+import { useMainStore } from "@/stores/main";
+import { storeToRefs } from "pinia";
+import { onMounted } from "vue";
+import { useI18n } from "vue-i18n";
 
+const { t } = useI18n();
 const mainStore = useMainStore();
 const mainStoreRef = storeToRefs(mainStore);
-
 const videos = mainStoreRef.readVideos;
-
 const headers = [
   {
-    title: 'Title',
+    title: t("title"),
     sortable: true,
-    key: 'title',
-    align: 'left',
+    key: "title",
+    align: "left",
   },
   {
-    title: 'Progress',
+    title: t("state"),
     sortable: true,
-    key: 'progress',
-    align: 'left',
+    key: "progress_state",
+    align: "left",
   },
   {
-    title: 'Actions',
-    key: 'id',
+    title: t("download"),
+    key: "id",
   },
 ];
 
-onMounted(async () =>{
+onMounted(async () => {
   await mainStore.actionGetVideos();
-})
-</script>
+});
+</script>
+
+<template>
+  <div>
+    <v-toolbar light>
+      <v-toolbar-title>
+        <h3>{{ t("progress") }}</h3>
+      </v-toolbar-title>
+    </v-toolbar>
+    <v-data-table :headers="headers" :items="videos">
+      <!-- <template v-slot:item.is_active="{ item }">
+        <v-icon v-if="item.columns.is_active" icon="check" />
+      </template>
+      <template v-slot:item.is_superuser="{ item }">
+        <v-icon v-if="item.columns.is_superuser" icon="check" />
+      </template> -->
+      <template v-slot:item.id="{ item }">
+        <v-btn
+          flat
+          :to="{
+            name: 'main-admin-users-edit',
+            params: { id: item.columns.id },
+          }"
+          :disabled="item.progress_state !== 'completed'"
+        >
+          <v-icon icon="file_download" />
+        </v-btn>
+      </template>
+    </v-data-table>
+  </div>
+</template>
+
+<style>
+.v-data-table-footer {
+  margin-top: 10px;
+}
+</style>

+ 41 - 39
frontend/src/views/main/Upload.vue

@@ -1,47 +1,15 @@
-<template>
-  <v-container fluid>
-    <v-card class="ma-3 pa-3">
-      <v-card-title primary-title>
-        <div class="headline primary--text">Make Video</div>
-      </v-card-title>
-      <v-card-text>
-          <v-form v-model="valid" ref="Form">
-            <v-text-field 
-              label="Title" v-model="title" 
-              :rules="required"
-              prepend-icon="title">
-            </v-text-field>
-            <v-file-input
-              v-model="zipFiles"
-              :rules="[v=> v.length || 'select zip file.']"
-              accept=".zip"
-              label="File input"
-              prepend-icon="folder_zip"
-            ></v-file-input>
-          </v-form>
-      </v-card-text>
-      <v-card-actions>
-        <v-spacer></v-spacer>
-        <v-btn @click="Submit" :disabled="!valid">
-              Send
-        </v-btn>
-      </v-card-actions>
-    </v-card>
-  </v-container>
-</template>
 <script setup lang="ts">
-import { ref} from 'vue';
-import { useMainStore } from '@/stores/main';
-import { required } from '@/utils';
-
-
+import { ref } from "vue";
+import { useMainStore } from "@/stores/main";
+import { required } from "@/utils";
+import { useI18n } from "vue-i18n";
 
+const { t } = useI18n();
 const valid = ref(true);
-const title = ref('');
+const title = ref("");
 const zipFiles = ref();
 const Form = ref();
 
-
 const mainStore = useMainStore();
 
 async function Submit() {
@@ -51,4 +19,38 @@ async function Submit() {
     (Form as any).value.reset();
   }
 }
-</script>
+</script>
+
+<template>
+  <v-container fluid>
+    <v-card class="ma-3 pa-3">
+      <v-card-title primary-title>
+        <h3 class="card-title mb-3">{{ t("makeVideo") }}</h3>
+      </v-card-title>
+      <v-card-text>
+        <v-form v-model="valid" ref="Form">
+          <v-text-field
+            :label="$t('title')"
+            v-model="title"
+            :rules="required"
+            prepend-icon="title"
+          >
+          </v-text-field>
+          <v-file-input
+            v-model="zipFiles"
+            :rules="[(v) => v.length || 'select zip file.']"
+            accept=".zip"
+            :label="$t('fileInput')"
+            prepend-icon="folder_zip"
+          ></v-file-input>
+        </v-form>
+      </v-card-text>
+      <v-card-actions>
+        <v-spacer></v-spacer>
+        <v-btn @click="Submit" :disabled="!valid">
+          {{ t("send") }}
+        </v-btn>
+      </v-card-actions>
+    </v-card>
+  </v-container>
+</template>

+ 31 - 23
frontend/src/views/main/profile/UserProfile.vue

@@ -1,34 +1,42 @@
+<script setup lang="ts">
+import { useMainStore } from "@/stores/main";
+import { storeToRefs } from "pinia";
+import { useI18n } from "vue-i18n";
+
+const { t } = useI18n();
+const mainStore = useMainStore();
+const mainStoreRef = storeToRefs(mainStore);
+const userProfile = mainStoreRef.readUserProfile;
+</script>
+
 <template>
   <v-container fluid>
     <v-card class="ma-3 pa-3">
       <v-card-title primary-title>
-        <div class="headline primary--text">User Profile</div>
+        <h3 class="card-title">{{ t("userProfile") }}</h3>
       </v-card-title>
-      <v-card-text>
-        <div class="my-4">
-          <div class="subheading secondary--text text--lighten-3">Full Name</div>
-          <div class="title primary--text text--darken-2" v-if="userProfile && userProfile.full_name">{{userProfile.full_name}}</div>
-          <div class="title primary--text text--darken-2" v-else>-----</div>
-        </div>
-        <div class="my-3">
-          <div class="subheading secondary--text text--lighten-3">Email</div>
-          <div class="title primary--text text--darken-2" v-if="userProfile && userProfile.email">{{userProfile.email}}</div>
-          <div class="title primary--text text--darken-2" v-else>-----</div>
-        </div>
+      <v-card-text class="my-3">
+          <h4 class="">
+            {{ t("userName") }}
+          </h4>
+          <span v-if="userProfile && userProfile.full_name">
+            {{ userProfile.full_name }}
+          </span>
+          <div v-else>-</div>
+
+          <h4 class="mt-3">
+            {{ t("emailAddress") }}
+          </h4>
+          <span v-if="userProfile && userProfile.email">
+            {{ userProfile.email }}
+          </span>
+          <div v-else>-</div>
       </v-card-text>
       <v-card-actions>
-        <v-btn to="/main/profile/edit">Edit</v-btn>
-        <v-btn to="/main/profile/password">Change password</v-btn>
+        <v-btn to="/main/profile/edit" variant="outlined" color="primary">{{ t("edit") }}</v-btn>
+        <v-btn to="/main/profile/password"  variant="flat"
+      color="primary" class="ms-3">{{ t("changePassword") }}</v-btn>
       </v-card-actions>
     </v-card>
   </v-container>
 </template>
-<script setup lang="ts">
-  import { useMainStore } from '@/stores/main';
-  import { storeToRefs } from 'pinia';
-
-  const mainStore = useMainStore();
-  const mainStoreRef = storeToRefs(mainStore);
-  const userProfile = mainStoreRef.readUserProfile;
-
-</script>

+ 86 - 88
frontend/src/views/main/profile/UserProfileEdit.vue

@@ -1,104 +1,102 @@
-
-<template>
-  <v-container fluid>
-    <v-card class="ma-3 pa-3">
-      <v-card-title primary-title>
-        <div class="headline primary--text">Edit User Profile</div>
-      </v-card-title>
-      <v-card-text>
-
-          <v-form
-            v-model="valid"
-            ref="form"
-          >
-            <v-text-field
-              label="Full Name"
-              v-model="fullName"
-              :rules="nameRules"
-              required
-            ></v-text-field>
-            <v-text-field
-              label="E-mail"
-              type="email"
-              v-model="email"
-              :rules="emailRules"
-              required
-            ></v-text-field>
-          </v-form>
-
-      </v-card-text>
-      <v-card-actions>
-        <v-spacer></v-spacer>
-        <v-btn @click="cancel">Cancel</v-btn>
-        <v-btn @click="reset">Reset</v-btn>
-        <v-btn 
-          @click="submit" 
-          :disabled="!valid"
-          >
-          Save
-        </v-btn>
-      </v-card-actions>
-    </v-card>
-  </v-container>
-</template>
-
 <script setup lang="ts">
-  import { ref , onMounted} from 'vue';
-  import { useMainStore } from '@/stores/main';
-  import { storeToRefs } from 'pinia';
-  import router from '@/router';
-  import type { IUserProfileUpdate } from '@/interfaces';
-  import  { emailRules, nameRules } from '@/utils';
+import { ref, onMounted } from "vue";
+import { useMainStore } from "@/stores/main";
+import { storeToRefs } from "pinia";
+import router from "@/router";
+import type { IUserProfileUpdate } from "@/interfaces";
+import { emailRules, nameRules } from "@/utils";
+import { useI18n } from "vue-i18n";
 
-  const valid = ref(true);
-  const fullName = ref('');
-  const email = ref('');
-  const form = ref(null);
+const { t } = useI18n();
+const valid = ref(true);
+const fullName = ref("");
+const email = ref("");
+const form = ref(null);
 
+const mainStore = useMainStore();
+const mainStoreRef = storeToRefs(mainStore);
+const userProfile = mainStoreRef.readUserProfile;
+if (userProfile) {
+  if (typeof userProfile.value?.full_name === "string") {
+    fullName.value = userProfile.value.full_name;
+  } else {
+    fullName.value = "";
+  }
+  email.value = userProfile.value!.email;
+}
 
-  const mainStore = useMainStore();
-  const mainStoreRef = storeToRefs(mainStore);
-  const userProfile = mainStoreRef.readUserProfile;
+function reset() {
   if (userProfile) {
-    if (typeof userProfile.value?.full_name === 'string'){
+    if (typeof userProfile.value?.full_name === "string") {
       fullName.value = userProfile.value.full_name;
     } else {
-      fullName.value = '';
+      fullName.value = "";
     }
     email.value = userProfile.value!.email;
   }
+}
 
-  function reset(){
-    if (userProfile) {
-      if (typeof userProfile.value?.full_name === 'string'){
-      fullName.value = userProfile.value.full_name;
-      } else {
-        fullName.value = '';
-      }
-      email.value = userProfile.value!.email;
+function cancel() {
+  router.back();
+}
+
+async function submit() {
+  await (form as any).value.validate();
+  if (valid.value) {
+    const updateProfile: IUserProfileUpdate = {};
+    if (fullName.value) {
+      updateProfile.full_name = fullName.value;
+    }
+    if (email.value) {
+      updateProfile.email = email.value;
     }
+    await mainStore.updateUserProfile(updateProfile);
+    router.push("/main/profile");
   }
+}
 
-  function cancel() {
-    router.back();
-  }
+onMounted(() => {
+  (form as any).value.validate();
+});
+</script>
 
-  async function submit(){
-    await (form as any).value.validate()
-    if (valid.value){
-      const updateProfile: IUserProfileUpdate = {};
-      if (fullName.value) {
-        updateProfile.full_name = fullName.value;
-      }
-      if (email.value) {
-        updateProfile.email = email.value;
-      }
-      await mainStore.updateUserProfile(updateProfile);
-      router.push('/main/profile');
-    }
-  }
+<template>
+  <v-container fluid>
+    <v-card class="ma-3 pa-3">
+      <v-card-title>
+        <h3 class="card-title">{{ t("editUserProfile") }}</h3>
+      </v-card-title>
+      <v-card-text class="pt-3">
+        <v-form v-model="valid" ref="form">
+          <v-text-field
+            :label="$t('userName')"
+            v-model="fullName"
+            :rules="nameRules"
+            required
+          ></v-text-field>
+          <v-text-field
+            :label="$t('emailAddress')"
+            type="email"
+            v-model="email"
+            :rules="emailRules"
+            required
+          ></v-text-field>
+        </v-form>
+      </v-card-text>
+      <v-card-actions>
+        <v-spacer></v-spacer>
+        <v-btn @click="cancel">{{ t("cancel") }}</v-btn>
+        <v-btn @click="reset">{{ t("reset") }}</v-btn>
+        <v-btn @click="submit" :disabled="!valid">{{ t("save") }}</v-btn>
+      </v-card-actions>
+    </v-card>
+  </v-container>
+</template>
 
-  onMounted(()=>{
-    (form as any).value.validate();
-  })
-</script>
+<style lang="scss" scoped>
+// .v-card-title {
+//   h3 {
+//     letter-spacing: 1px !important;
+//   }
+// }
+</style>

+ 60 - 52
frontend/src/views/main/profile/UserProfileEditPassword.vue

@@ -1,54 +1,16 @@
-<template>
-  <v-container fluid>
-    <v-card class="ma-3 pa-3">
-      <v-card-title primary-title>
-        <div class="headline primary--text">Set Password</div>
-      </v-card-title>
-      <v-card-text>
-          <div class="my-3">
-            <div class="subheading secondary--text text--lighten-2">User</div>
-            <div class="title primary--text text--darken-2" v-if="userProfile && userProfile.full_name">{{userProfile.full_name}}</div>
-            <div class="title primary--text text--darken-2" v-else>{{userProfile!.email}}</div>
-          </div>
-          <v-form ref="form" v-model="valid">
-            <v-text-field 
-              type="password"
-              ref="password"
-              label="Password"
-              v-model="password1"
-              :rules="password1Rules"
-              >
-            </v-text-field>
-            <v-text-field
-              type="password"
-              label="Confirm Password"
-              :rules="password2Rules"
-              v-model="password2"
-              >
-            </v-text-field>
-          </v-form>
-      </v-card-text>
-      <v-card-actions>
-        <v-spacer></v-spacer>
-        <v-btn @click="cancel">Cancel</v-btn>
-        <v-btn @click="reset">Reset</v-btn>
-        <v-btn @click="submit" :disabled="!valid">Save</v-btn>
-      </v-card-actions>
-    </v-card>
-  </v-container>
-</template>
-
 <script setup lang="ts">
-import { ref } from 'vue';
-import { useMainStore } from '@/stores/main';
-import { storeToRefs } from 'pinia';
-import router from '@/router';
-import { password1Rules, usePassword2Rules } from '@/utils';
-import type { IUserProfileUpdate } from '@/interfaces';
+import { ref } from "vue";
+import { useMainStore } from "@/stores/main";
+import { storeToRefs } from "pinia";
+import router from "@/router";
+import { password1Rules, usePassword2Rules } from "@/utils";
+import type { IUserProfileUpdate } from "@/interfaces";
+import { useI18n } from "vue-i18n";
 
+const { t } = useI18n();
 const valid = ref(true);
-const password1 = ref('');
-const password2 = ref('');
+const password1 = ref("");
+const password2 = ref("");
 const form = ref(null);
 const mainStore = useMainStore();
 const mainStoreRef = storeToRefs(mainStore);
@@ -57,8 +19,8 @@ const userProfile = mainStoreRef.readUserProfile;
 const password2Rules = usePassword2Rules(password1);
 
 function reset() {
-  password1.value = '';
-  password2.value = '';
+  password1.value = "";
+  password2.value = "";
 }
 
 function cancel() {
@@ -71,8 +33,54 @@ async function submit() {
     const updatedProfile: IUserProfileUpdate = {};
     updatedProfile.password = password1.value;
     await mainStore.updateUserProfile(updatedProfile);
-    router.push('/main/profile');
+    router.push("/main/profile");
   }
 }
+</script>
 
-</script>
+<template>
+  <v-container fluid>
+    <v-card class="ma-3 pa-3">
+      <v-card-title primary-title class="mb-3">
+        <h3 class="headline primary--text">{{ t("changePassword") }}</h3>
+      </v-card-title>
+      <v-card-text>
+        <!-- <div class="my-3">
+          <div class="subheading secondary--text text--lighten-2">User</div>
+          <div
+            class="title primary--text text--darken-2"
+            v-if="userProfile && userProfile.full_name"
+          >
+            {{ userProfile.full_name }}
+          </div>
+          <div class="title primary--text text--darken-2" v-else>
+            {{ userProfile!.email }}
+          </div>
+        </div> -->
+        <v-form ref="form" v-model="valid">
+          <v-text-field
+            type="password"
+            ref="password"
+            :label="$t('password')"
+            v-model="password1"
+            :rules="password1Rules"
+          >
+          </v-text-field>
+          <v-text-field
+            type="password"
+            :label="$t('confirmPassword')"
+            :rules="password2Rules"
+            v-model="password2"
+          >
+          </v-text-field>
+        </v-form>
+      </v-card-text>
+      <v-card-actions>
+        <v-spacer></v-spacer>
+        <v-btn @click="cancel">{{ t("cancel") }}</v-btn>
+        <v-btn @click="reset">{{ t("clear") }}</v-btn>
+        <v-btn @click="submit" :disabled="!valid">{{ t("save") }}</v-btn>
+      </v-card-actions>
+    </v-card>
+  </v-container>
+</template>