SyuanYu il y a 2 ans
Parent
commit
b2b9092186

+ 146 - 27
frontend/src/App.vue

@@ -1,40 +1,159 @@
 <template>
-  
-  
   <v-app>
-    <LoadingView v-if="loggedIn===null"/>
+    <LoadingView v-if="loggedIn === null" />
     <RouterView v-else />
-    <NotificationsManager/>
+    <NotificationsManager />
   </v-app>
- 
 </template>
 
 <script setup lang="ts">
-  import { RouterView } from "vue-router";
-  import { useMainStore } from "@/stores/main"
-  import { onMounted } from "vue";
-  import LoadingView from "./views/LoadingView.vue";
-  import { storeToRefs } from "pinia";
-  import NotificationsManager from "@/components/NotificationsManager.vue";
-  //store
-  const mainStore = useMainStore();
-  const mainStoreRef = storeToRefs(mainStore);
-  //variable
-  const loggedIn = mainStoreRef.readIsLoggedIn; 
-  //getter
-
-  //action
-  
-  //excute in setup 
-  
-  //lifecycle
-  onMounted(()=>{
-    mainStore.checkLoggedIn();
-  });
+import { RouterView } from "vue-router";
+import { useMainStore } from "@/stores/main";
+import { onMounted } from "vue";
+import LoadingView from "./views/LoadingView.vue";
+import { storeToRefs } from "pinia";
+import NotificationsManager from "@/components/NotificationsManager.vue";
+//store
+const mainStore = useMainStore();
+const mainStoreRef = storeToRefs(mainStore);
+//variable
+const loggedIn = mainStoreRef.readIsLoggedIn;
+//getter
 
+//action
+
+//excute in setup
+
+//lifecycle
+onMounted(() => {
+  mainStore.checkLoggedIn();
+});
 </script>
 
-<style>
+<style lang="scss">
+:root {
+  --main-color: #ea5413;
+}
+
+a {
+  transition: 0.3s;
+  color: var(--main-color);
+  text-decoration: none;
+  &:hover {
+    opacity: 0.7;
+  }
+}
+
+.login-form {
+  margin: auto;
+  max-width: 500px;
+  position: relative;
+
+  .v-input__details {
+    padding: 3px;
+
+    .v-messages {
+      color: #b00020 !important;
+      text-align: end;
+    }
+  }
+
+  .login-btn {
+    display: flex;
+    margin: 50px auto 0;
+    padding: 20px 40px;
+    font-size: 16px;
+  }
+
+  .v-alert {
+    position: absolute;
+    width: 100%;
+    bottom: -110px;
+    margin-bottom: 10px;
+    padding: 10px !important;
+    .v-alert__content {
+      text-align: start;
+    }
+    .v-icon--size-default {
+      padding: 0;
+    }
+  }
+
+  .v-input__append {
+    position: relative;
+    margin-inline-start: 0 !important;
+    .material-icons {
+      position: absolute !important;
+      right: 10px;
+    }
+  }
+}
+
+.banner-item {
+  position: relative;
+  img {
+    width: 100%;
+    height: 100vh;
+    object-fit: cover;
+    margin-bottom: -7px;
+    animation: scale 3s ease-in-out;
+    filter: brightness(60%); // 圖片遮罩
+    @media (max-width: 959px) {
+      height: 40vh;
+    }
+  }
+  h2 {
+    width: 100%;
+    padding: 0 10px;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    color: #fff;
+    font-size: 30px;
+    font-weight: bold;
+    text-align: center;
+    letter-spacing: 1px;
+    @media (max-width: 959px) {
+      top: 55%;
+    }
+    @media (max-width: 575px) {
+      top: 60%;
+      font-size: 22px;
+    }
+  }
+}
+
+@keyframes scale {
+  0% {
+    transform: scale(1.3);
+  }
+
+  100% {
+    transform: scale(1);
+  }
+}
+
+.form-title {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
 
+  h3 {
+    font-size: 32px;
+    font-weight: bold;
+    letter-spacing: 3px;
+    @media (max-width: 575px) {
+      font-size: 28px;
+    }
+  }
 
+  span {
+    width: 60px;
+    height: 3px;
+    margin: 20px auto 35px;
+    display: block;
+    background: var(--main-color);
+  }
+}
 </style>

BIN
frontend/src/assets/img/Choozmo-logo.png


BIN
frontend/src/assets/img/banner.png


+ 91 - 0
frontend/src/components/Navbar.vue

@@ -0,0 +1,91 @@
+<script setup lang="ts">
+import { reactive, ref, onMounted } from "vue";
+let items = reactive([{ title: "English" }, { title: "中文" }]);
+
+let menu = reactive([
+  // { title: '首頁', link: '/' },
+  { title: "登入", link: "/login" },
+  { title: "註冊", link: "/signup" },
+]);
+</script>
+
+<template>
+  <v-app-bar prominent class="px-3 navbar">
+    <!-- <v-app-bar-nav-icon variant="text" @click.stop="drawer = !drawer"></v-app-bar-nav-icon> -->
+    <v-toolbar-title>
+      <a href="https://ai.choozmo.com/ai-presenter/info/">
+        <img src="../assets/img/Choozmo-logo.png" alt="" class="logo" />
+      </a>
+    </v-toolbar-title>
+
+    <v-spacer></v-spacer>
+
+    <v-toolbar-items>
+      <v-btn v-for="item in menu" :to="item.link" color="gray">{{
+        item.title
+      }}</v-btn>
+      <v-menu>
+        <template v-slot:activator="{ props }">
+          <v-btn color="gray" v-bind="props">
+            <span>中/en</span>
+            <svg
+              xmlns="http://www.w3.org/2000/svg"
+              width="16"
+              height="16"
+              fill="currentColor"
+              class="bi bi-chevron-down mt-1 ms-1"
+              viewBox="0 0 16 16"
+            >
+              <path
+                fill-rule="evenodd"
+                d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"
+              />
+            </svg>
+          </v-btn>
+        </template>
+        <v-list>
+          <v-list-item
+            v-for="(item, index) in items"
+            :key="index"
+            :value="index"
+          >
+            <v-list-item-title>{{ item.title }}</v-list-item-title>
+          </v-list-item>
+        </v-list>
+      </v-menu>
+    </v-toolbar-items>
+  </v-app-bar>
+</template>
+
+<style lang="scss" scoped>
+ul {
+  list-style: none;
+}
+
+.logo {
+  width: 180px;
+  max-width: 100%;
+  margin-bottom: -10px;
+  @media (max-width: 575px) {
+    width: 140px;
+    margin-bottom: -15px;
+  }
+}
+
+.navbar {
+  a,
+  span {
+    font-size: 16px;
+    @media (max-width: 575px) {
+      font-size: 14px;
+    }
+  }
+}
+
+.v-toolbar-title {
+  flex: none !important;
+  @media (max-width: 575px) {
+    margin-inline-start: 0;
+  }
+}
+</style>

+ 0 - 2
frontend/src/main.ts

@@ -1,8 +1,6 @@
 import { createApp } from "vue";
 import router from "./router";
 import App from "./App.vue";
-
-
 import { vuetify } from "./plugins/vuetify";
 import { pinia } from "./plugins/pinia";
 

+ 11 - 0
frontend/src/plugins/vuetify.ts

@@ -29,4 +29,15 @@ export const vuetify = createVuetify({
       md,
     },
   },
+  theme: {
+    themes: {
+      light: {
+        dark: false,
+        colors: {
+          primary: '#ea5413',
+          gray: '#888888'
+        },
+      },
+    },
+  },
 })

+ 1 - 1
frontend/src/stores/main.ts

@@ -163,7 +163,7 @@ export const useMainStore = defineStore("MainStoreId", {
         },
         routeLoggedIn() {
             if (router.currentRoute.value.path === "/login" || router.currentRoute.value.path === "/") {
-                router.push("/main");
+                router.push("/main/dashboard");
             }
         },
         async dispatchRemoveNotification(payload: {notification: AppNotification; timeout: number },) {

+ 134 - 44
frontend/src/views/Login.vue

@@ -1,25 +1,151 @@
+<script setup lang="ts">
+import { appName, openRegisration } from "@/env";
+import { ref, computed, onMounted } from "vue";
+import { useMainStore } from "@/stores/main";
+import { useDisplay } from "vuetify";
+import { storeToRefs } from "pinia";
+import Navbar from "@/components/Navbar.vue";
+
+const mainStore = useMainStore();
+const mainStoreRef = storeToRefs(mainStore);
+
+// variable
+const email = ref("");
+const password = ref("");
+const { name } = useDisplay();
+let showPassword = ref(false);
+
+// getter
+const loginError = mainStoreRef.readLoginError;
+const width = computed(() => {
+  // name is reactive and
+  // must use .value
+  switch (name.value) {
+    case "xs":
+      return 12;
+    case "sm":
+      return 12;
+    // case "md":
+    //   return 6;
+  }
+  return 6;
+});
+
+// action
+function submit() {
+  mainStore.logIn(email.value, password.value);
+}
+
+// lifecycle
+onMounted(() => {});
+</script>
+
 <template>
-  <v-container fluid class="d-flex fill-height">
+  <Navbar />
+  <v-container fluid class="pa-0">
+    <v-row align="center" no-gutters class="overflow-hidden">
+      <v-col :cols="width">
+        <section class="overflow-hidden banner-item">
+          <img src="../assets/img/banner.png" alt="" />
+          <h2>
+            將您的生活、創作、宣傳做成影片
+            <br />
+            開始使用 AI Presentors
+          </h2>
+        </section>
+      </v-col>
+      <v-col :cols="width" class="px-6 my-8 my-md-0">
+        <div class="form-title">
+          <h3>登入</h3>
+          <span></span>
+        </div>
+        <v-form ref="form" class="login-form" lazy-validation>
+          <v-text-field
+            v-model="email"
+            name="email"
+            prepend-icon="person"
+            :rules="[(v) => !!v || '請輸入您的帳號']"
+            label="使用者名稱"
+            required
+          ></v-text-field>
+
+          <v-text-field
+            v-model="password"
+            name="password"
+            id="password"
+            prepend-icon="key"
+            :append-icon="showPassword ? 'visibility' : 'visibility_off'"
+            :rules="[(v) => !!v || '請輸入您的密碼']"
+            :type="showPassword ? 'text' : 'password'"
+            label="密碼"
+            hint="4-12 位數密碼"
+            @click:append="showPassword = !showPassword"
+            required
+          ></v-text-field>
+
+          <p class="text-center">
+            還沒有帳號?
+            <router-link to="/signup">註冊</router-link>/<router-link
+              to="/recover-password"
+              >忘記密碼</router-link
+            >
+          </p>
+
+          <v-btn
+            rounded="pill"
+            color="primary"
+            @click.prevent="submit"
+            class="login-btn"
+          >
+            立即登入
+          </v-btn>
+        </v-form>
+      </v-col>
+    </v-row>
+  </v-container>
+
+  <!-- <v-container fluid class="d-flex fill-height">
     <v-row align="center" justify="center">
       <v-col :cols="width">
         <v-card class="elevation-12">
           <v-toolbar dark color="primary">
-            <v-toolbar-title>{{appName}}</v-toolbar-title>
+            <v-toolbar-title>{{ appName }}</v-toolbar-title>
             <v-spacer></v-spacer>
             <v-btn to="/signup">SignUp</v-btn>
           </v-toolbar>
           <v-card-text>
             <v-form @keyup.enter="submit">
-              <v-text-field @keyup.enter="submit" v-model="email" prepend-icon="person" name="email" label="Email" type="text"></v-text-field>
-              <v-text-field @keyup.enter="submit" v-model="password" prepend-icon="key" name="password" label="Password" id="password" type="password"></v-text-field>
+              <v-text-field
+                @keyup.enter="submit"
+                v-model="email"
+                prepend-icon="person"
+                name="email"
+                label="Email"
+                type="text"
+              ></v-text-field>
+              <v-text-field
+                @keyup.enter="submit"
+                v-model="password"
+                prepend-icon="key"
+                name="password"
+                label="Password"
+                id="password"
+                type="password"
+              ></v-text-field>
             </v-form>
             <div v-if="loginError">
-              <v-alert :value="loginError" transition="fade-transition" type="error">
+              <v-alert
+                :value="loginError"
+                transition="fade-transition"
+                type="error"
+              >
                 Incorrect email or password
               </v-alert>
             </div>
             <div class="d-flex align-end flex-column">
-              <router-link to="/recover-password">Forgot your password?</router-link>
+              <router-link to="/recover-password"
+                >Forgot your password?</router-link
+              >
             </div>
           </v-card-text>
           <v-card-actions>
@@ -29,41 +155,5 @@
         </v-card>
       </v-col>
     </v-row>
-  </v-container>
-</template> 
-
-<script setup lang="ts">
-  import { appName, openRegisration } from '@/env';
-  import { ref, computed, onMounted } from 'vue';
-  import { useMainStore } from '@/stores/main';
-  import { useDisplay } from 'vuetify';
-  import { storeToRefs } from 'pinia';
-
-  const mainStore = useMainStore(); 
-  const mainStoreRef = storeToRefs(mainStore);
-  // variable
-  const email = ref("");
-  const password = ref("");
-  const { name } = useDisplay();
-  // getter
-  const loginError = mainStoreRef.readLoginError;
-  const width = computed(() => {
-        // name is reactive and
-        // must use .value
-        switch (name.value) {
-          case 'xs': return 12
-          case 'sm': return 8
-          case 'md': return 4
-        }
-        return 4
-      })
-  // action
-  function submit() { 
-    mainStore.logIn(email.value, password.value); 
-  }
-  // lifecycle
-  onMounted(() => {
-  }) 
-  
-</script>
-
+  </v-container> -->
+</template>

+ 245 - 35
frontend/src/views/Signup.vue

@@ -1,52 +1,262 @@
+<script setup lang="ts">
+import { appName, openRegisration } from "@/env";
+import { ref, reactive, computed, onMounted } from "vue";
+import { useMainStore } from "@/stores/main";
+import { useDisplay } from "vuetify";
+import { storeToRefs } from "pinia";
+import Navbar from "@/components/Navbar.vue";
+
+const mainStore = useMainStore();
+const mainStoreRef = storeToRefs(mainStore);
+
+// variable
+const email = ref("");
+const password = ref("");
+const { name } = useDisplay();
+const confirmPassword = ref("");
+let data = reactive({
+  email: "",
+  password: "",
+});
+let dialog = ref(false);
+let confirmState = ref(false);
+let showPassword = ref(false);
+let showConfirmPassword = ref(false);
+
+// getter
+const loginError = mainStoreRef.readLoginError;
+const width = computed(() => {
+  // name is reactive and
+  // must use .value
+  switch (name.value) {
+    case "xs":
+      return 12;
+    case "sm":
+      return 12;
+  }
+  return 6;
+});
+
+// action
+async function submit() {
+  if (confirmPassword.value === data.password) {
+    console.log("data", data.password);
+    mainStore.register(data);
+  } else {
+    confirmState.value = true;
+    setTimeout(() => {
+      confirmState.value = false;
+    }, 5000);
+  }
+}
+</script>
+
 <template>
-  <v-container fluid class="d-flex fill-height">
+  <Navbar />
+
+  <v-container fluid class="pa-0">
+    <v-row align="center" no-gutters class="overflow-hidden">
+      <v-col :cols="width">
+        <section class="overflow-hidden banner-item">
+          <img src="../assets/img/banner.png" alt="" />
+          <h2>
+            將您的生活、創作、宣傳做成影片
+            <br />
+            開始使用 AI Presentors
+          </h2>
+        </section>
+      </v-col>
+      <v-col :cols="width" class="px-6 my-8 my-md-0">
+        <div class="form-title">
+          <h3>註冊</h3>
+          <span></span>
+        </div>
+        <v-form ref="form" class="login-form" lazy-validation>
+          <!-- <v-text-field
+            v-model="data.full_name"
+            :rules="[(v) => !!v || '請輸入您的帳號']"
+            label="使用者名稱"
+            required
+          ></v-text-field> -->
+
+          <v-text-field
+            v-model="data.email"
+            :rules="[(v) => !!v || '請輸入您的電子信箱']"
+            label="電子信箱"
+            required
+          ></v-text-field>
+
+          <v-text-field
+            v-model="data.password"
+            :append-icon="showPassword ? 'visibility' : 'visibility_off'"
+            :rules="[(v) => !!v || '請輸入您的密碼']"
+            :type="showPassword ? 'text' : 'password'"
+            label="設定密碼"
+            hint="4-12 位數密碼"
+            @click:append="showPassword = !showPassword"
+            required
+          ></v-text-field>
+
+          <v-text-field
+            v-model="confirmPassword"
+            :append-icon="showConfirmPassword ? 'visibility' : 'visibility_off'"
+            :rules="[(v) => !!v || '請輸入您的密碼']"
+            :type="showConfirmPassword ? 'text' : 'password'"
+            label="確認密碼"
+            hint="再次輸入您的密碼"
+            @click:append="showConfirmPassword = !showConfirmPassword"
+            required
+          ></v-text-field>
+
+          <v-alert v-model="confirmState" type="error" variant="outlined">
+            該密碼與您輸入的確認密碼不一致
+          </v-alert>
+
+          <p class="text-center">
+            已經有帳號? <router-link to="/login">登入</router-link>
+          </p>
+
+          <v-btn
+            rounded="pill"
+            color="primary"
+            @click.prevent="submit"
+            class="login-btn"
+          >
+            立即註冊
+          </v-btn>
+
+          <section
+            class="mt-5 d-flex align-center justify-center dialog-content"
+          >
+            <p>註冊即表示您已閱讀並同意</p>
+            <v-dialog v-model="dialog" max-width="700" scrollable>
+              <template v-slot:activator="{ props }">
+                <v-btn
+                  variant="text"
+                  color="primary"
+                  v-bind="props"
+                  class="px-1"
+                >
+                  服務條款及隱私權政策
+                </v-btn>
+              </template>
+
+              <v-card class="terms-card">
+                <v-card-title>
+                  <v-spacer></v-spacer>
+                  <v-btn icon @click="dialog = false">
+                    <v-icon icon="md:close"></v-icon>
+                  </v-btn>
+                </v-card-title>
+                <v-card-text>
+                  <h3 class="text-h5 text-center font-weight-bold mb-5">
+                    使用者的守法義務
+                  </h3>
+                  您承諾絕不為任何非法目的或以任何非法方式使用本服務,並承諾遵守中華民國相關法規及一切使用網際網路之國際慣例。您若係中華民國以外之使用者,並同意遵守所屬國家或地域之法令。您同意並保證不得利用本服務從事侵害他人權益或違法之行為,包括但不限於:
+                  <ul class="mb-3">
+                    <li>
+                      上載、張貼、公布或傳送任何誹謗、侮辱、具威脅性、攻擊性、不雅、猥褻、不實、違反公共秩序或善良風俗或其他不法之文字、圖片或任何形式的檔案於本服務上
+                    </li>
+                    <li>
+                      侵害他人名譽、隱私權、營業秘密、商標權、著作權、專利權、其他智慧財產權及其他權利
+                    </li>
+                    <li>違反依法律或契約所應負之保密義務</li>
+                    <li>冒用他人名義使用本服務</li>
+                  </ul>
+                  <v-divider></v-divider>
+                  <h3 class="text-h5 text-center font-weight-bold mt-7 mb-5">
+                    免責聲明
+                  </h3>
+                  您明確了解並同意:ChoozMo
+                  對本服務不提供任何明示或默示的擔保,包含但不限於權利完整、商業適售性、特定目的之適用性及未侵害他人權利。本服務乃依其「現狀」及「提供使用時」之基礎提供,您使用本服務時,須自行承擔相關風險。ChoozMo
+                  不保證以下事項:
+                  <ul>
+                    <li>本服務將符合您的需求</li>
+                    <li>本服務不受干擾、及時提供、安全可靠或無錯誤</li>
+                    <li>由本服務之使用而取得之結果為正確或可靠</li>
+                  </ul>
+
+                  是否經由本服務之使用下載或取得任何資料應由您自行考量且自負風險,並拋棄因前開任何資料之下載而導致您電腦系統、網路存取、下載或播放設備之任何損壞或資料流失,對
+                  ChoozMo 提出任何請求或採取法律行動,您應自負完全責任。
+                </v-card-text>
+              </v-card>
+            </v-dialog>
+          </section>
+        </v-form>
+      </v-col>
+    </v-row>
+  </v-container>
+
+  <!-- <v-container fluid class="d-flex fill-height">
     <v-row align="center" justify="center">
       <v-col :cols="width">
         <v-card class="elevation-12">
           <v-toolbar dark color="primary">
-            <v-toolbar-title>{{appName}}</v-toolbar-title>
+            <v-toolbar-title>{{ appName }}</v-toolbar-title>
             <v-spacer></v-spacer>
             <v-btn to="/login">LogIn</v-btn>
           </v-toolbar>
           <v-card-text>
             <v-form @keyup.enter="submit">
-              <v-text-field @keyup.enter="submit" v-model="email" prepend-icon="person" name="email" label="Email" type="text"></v-text-field>
-              <v-text-field @keyup.enter="submit" v-model="password" prepend-icon="key" name="password" label="Password" id="password" type="password"></v-text-field>
+              <v-text-field
+                @keyup.enter="submit"
+                v-model="email"
+                prepend-icon="person"
+                name="email"
+                label="Email"
+                type="text"
+              ></v-text-field>
+              <v-text-field
+                @keyup.enter="submit"
+                v-model="password"
+                prepend-icon="key"
+                name="password"
+                label="Password"
+                id="password"
+                type="password"
+              ></v-text-field>
             </v-form>
-          </v-card-text>  
+          </v-card-text>
         </v-card>
       </v-col>
     </v-row>
-  </v-container>
+  </v-container> -->
 </template>
 
-<script setup lang="ts">
-  import { appName, openRegisration } from '@/env';
-  import { ref, computed, onMounted } from 'vue';
-  import { useMainStore } from '@/stores/main';
-  import { useDisplay } from 'vuetify';
-  import { storeToRefs } from 'pinia';
-
-  const mainStore = useMainStore(); 
-  const mainStoreRef = storeToRefs(mainStore);
-  // variable
-  const email = ref("");
-  const password = ref("");
-  const { name } = useDisplay();
-  // getter
-  const loginError = mainStoreRef.readLoginError;
-  const width = computed(() => {
-        // name is reactive and
-        // must use .value
-        switch (name.value) {
-          case 'xs': return 12
-          case 'sm': return 8
-          case 'md': return 4
-        }
-        return 4
-      })
-
-  async function submit() {
-
+<style lang="scss">
+.dialog-content {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 14px;
+  letter-spacing: 1px;
+  .v-btn {
+    &:hover > .v-btn__overlay {
+      opacity: 0;
+    }
+    &:hover > .v-btn__content {
+      color: rgba(234, 84, 19, 0.8);
+    }
+  }
+  .v-btn__content {
+    font-size: 14px;
+    font-weight: bold;
+  }
+}
+.terms-card {
+  margin: auto;
+  font-size: 16px;
+  ul {
+    padding: 20px;
+  }
+  .v-card-title {
+    display: flex;
+    .v-btn {
+      box-shadow: none;
+    }
+  }
+  .v-card-text {
+    padding: 0px 50px 50px !important;
   }
-</script>
+}
+</style>