diff --git a/package.json b/package.json index 683e21d..8b2e8e7 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,13 @@ { - "name": "frontend", + "name": "lucia-frontend", "version": "0.2.0", "private": true, "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview", + "test": "vitest", + "coverage": "vitest run --coverage", "test:unit": "vitest --environment jsdom --root src/", "test:e2e": "start-server-and-test preview :4173 'cypress run --e2e'", "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress open --e2e'", @@ -13,10 +15,12 @@ }, "dependencies": { "autoprefixer": "^10.4.13", + "axios": "^1.2.2", "pinia": "^2.0.28", "postcss": "^8.4.20", "tailwindcss": "^3.2.4", "vue": "^3.2.45", + "vue-axios": "^3.5.2", "vue-router": "^4.1.6" }, "devDependencies": { diff --git a/postcss.config.cjs b/postcss.config.cjs index f1c8dac..12a703d 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -2,5 +2,5 @@ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, - } -} + }, +}; diff --git a/src/App.vue b/src/App.vue index d5e94fc..f92b4b7 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,8 +2,7 @@ - + diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue new file mode 100644 index 0000000..13b090f --- /dev/null +++ b/src/components/Navbar.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/icons/IconEyeClose.vue b/src/components/icons/IconEyeClose.vue new file mode 100644 index 0000000..96b7d06 --- /dev/null +++ b/src/components/icons/IconEyeClose.vue @@ -0,0 +1,5 @@ + diff --git a/src/components/icons/IconEyeOpen.vue b/src/components/icons/IconEyeOpen.vue new file mode 100644 index 0000000..75bcf23 --- /dev/null +++ b/src/components/icons/IconEyeOpen.vue @@ -0,0 +1,5 @@ + diff --git a/src/components/icons/IconLockKey.vue b/src/components/icons/IconLockKey.vue new file mode 100644 index 0000000..79fd207 --- /dev/null +++ b/src/components/icons/IconLockKey.vue @@ -0,0 +1,5 @@ + diff --git a/src/components/icons/IconMember.vue b/src/components/icons/IconMember.vue new file mode 100644 index 0000000..c98d08c --- /dev/null +++ b/src/components/icons/IconMember.vue @@ -0,0 +1,6 @@ + diff --git a/src/components/icons/IconWarnTriangle.vue b/src/components/icons/IconWarnTriangle.vue new file mode 100644 index 0000000..8d5c62c --- /dev/null +++ b/src/components/icons/IconWarnTriangle.vue @@ -0,0 +1,5 @@ + diff --git a/src/components/icons/dspLogo.vue b/src/components/icons/dspLogo.vue new file mode 100644 index 0000000..d8fcd1b --- /dev/null +++ b/src/components/icons/dspLogo.vue @@ -0,0 +1,60 @@ + diff --git a/src/main.js b/src/main.js index 2e0a28d..1179864 100644 --- a/src/main.js +++ b/src/main.js @@ -1,14 +1,23 @@ -import { createApp } from "vue"; +import { createApp, markRaw } from "vue"; import { createPinia } from "pinia"; import App from "./App.vue"; import router from "./router"; +import axios from 'axios' +import VueAxios from 'vue-axios' -import "./assets/tailwind.css"; +import "./assets/main.css"; +const pinia = createPinia(); const app = createApp(App); -app.use(createPinia()); +pinia.use(({ store }) => { + store.$router = markRaw(router) +}); + +app.use(pinia); app.use(router); +app.use(VueAxios, axios); + app.mount("#app"); diff --git a/src/router/index.js b/src/router/index.js index d2e8abf..5ad5231 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,25 +1,76 @@ import { createRouter, createWebHistory } from "vue-router"; -import MainContainer from '../views/MainContainer.vue'; +import AuthContainer from '@/views/AuthContainer.vue'; +import MainContainer from '@/views/MainContainer.vue'; import Login from '@/views/Login/index.vue'; +import Files from '@/views/Files/index.vue'; +import MemberArea from '@/views/MemberArea/index.vue'; +import NotFound404 from '@/views/NotFound404.vue'; const routes = [ { - path: "", - component: MainContainer, - childeren: [ + path: '/', // 預設進入路由 + redirect: '/files' //重定向 + }, + { + path: '/', + name: "AuthContainer", + component: AuthContainer, + children: [ { - path: "/Login", + path: "login", name: "Login", component: Login, - } + }, ] }, + { + path: "/", + name: "MainContainer", + component: MainContainer, + children: [ + { + path: "member-area", + name: "MemberArea", + component: MemberArea, + }, + { + path: "files", + name: "Files", + component: Files, + }, + ] + }, + { + path: "/:pathMatch(.*)*", + name: "NotFound404", + component: NotFound404, + }, ]; + + const base_url = import.meta.env.BASE_URL; const router = createRouter({ history: createWebHistory(base_url), routes }); +// 全域性路由守衛 +router.beforeEach((to, from) => { + // to: Route: 即將要進入的目標 路由物件 + // from: Route: 當前導航正要離開的路由 + + const nextRoute = ['MemberArea', 'Files']; + let isCookie = document.cookie.split(';').some(c => c.trim().startsWith('luciaToken=')); // 是否登入 + + // 未登入狀態;當路由到nextRoute指定頁時,跳轉至login + if (nextRoute.indexOf(to.name) >= 0) { + if (!isCookie) router.push({ name: 'Login' }); + } + // 已登入狀態;當路由到login時,跳轉至home + if (to.name === 'Login') { + if (isCookie) router.push({ name: 'Files' }); + } + }); + export default router; diff --git a/src/stores/counter.js b/src/stores/counter.js deleted file mode 100644 index 95da786..0000000 --- a/src/stores/counter.js +++ /dev/null @@ -1,19 +0,0 @@ -import { ref, computed } from "vue"; -import { defineStore } from "pinia"; - -const useCounterStore = defineStore("counter", () => { - const count = ref(0); - const doubleCount = computed(() => count.value * 2); - - function increment() { - count.value++; - } - - return { - count, - doubleCount, - increment - }; -}) - -export default useCounterStore; diff --git a/src/stores/login.js b/src/stores/login.js new file mode 100644 index 0000000..dd9df05 --- /dev/null +++ b/src/stores/login.js @@ -0,0 +1,87 @@ +import { defineStore } from "pinia"; +import axios from 'axios'; + +export default defineStore('loginStore', { + // data, methods, computed + // state, actions, getters + state: () => ({ + auth: { + username: '', + password: '', + }, + isInvalid: false, + userData: {}, + }), + + // 讓元件取用相關的資料狀態 + getters: { + }, + + actions: { + /** + * fetch Login For Access Token api + */ + async signIn() { + const api = 'api/oauth/token'; + const config = { + headers: { + // http post 預設的 url 編碼,非 json 格式 + 'Content-Type':'application/x-www-form-urlencoded', + }, + }; + + try { + const response = await axios.post(api, this.auth, config); + + if(response.status === 200){ + // 將 token 儲存在 cookie + const token = response.data.access_token; + + document.cookie = `luciaToken=${token}`; + this.$router.push('/files') + } + } catch(error) { + this.isInvalid = true; + }; + }, + /** + * log out, tooken expired + */ + logOut() { + let isCookie = document.cookie.split(';').some(c => c.trim().startsWith('luciaToken=')); + let expires = new Date(); + expires.setTime(expires.getTime() - 60000); + + if(isCookie){ + document.cookie = `luciaToken=; expires=${expires.toGMTString()}`; + this.$router.push('/login'); + } + }, + /** + * get user detail for 'my-account' api + */ + async getUserData() { + const api = 'api/my-account'; + + try { + const response = await axios.get(api); + + this.userData = response.data; + } catch(error) { + }; + }, + /** + * check login for 'my-account' api + */ + async checkLogin() { + const api = 'api/my-account'; + + try { + const response = await axios.get(api); + if(response.status !== 200) this.$router.push('/login'); + } catch(error) { + this.$router.push('/login'); + }; + }, + } +}) diff --git a/src/views/AuthContainer.vue b/src/views/AuthContainer.vue new file mode 100644 index 0000000..efab18f --- /dev/null +++ b/src/views/AuthContainer.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/views/Files/index.vue b/src/views/Files/index.vue new file mode 100644 index 0000000..4fd5a13 --- /dev/null +++ b/src/views/Files/index.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/views/Login/index.vue b/src/views/Login/index.vue index f152275..9716c08 100644 --- a/src/views/Login/index.vue +++ b/src/views/Login/index.vue @@ -1,3 +1,114 @@ + + + + diff --git a/src/views/MainContainer.vue b/src/views/MainContainer.vue index 4e9bbac..65a4980 100644 --- a/src/views/MainContainer.vue +++ b/src/views/MainContainer.vue @@ -1,16 +1,42 @@ diff --git a/src/views/MemberArea/index.vue b/src/views/MemberArea/index.vue new file mode 100644 index 0000000..4fbcf4c --- /dev/null +++ b/src/views/MemberArea/index.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/views/NotFound404.vue b/src/views/NotFound404.vue new file mode 100644 index 0000000..4303204 --- /dev/null +++ b/src/views/NotFound404.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/views/header/index.vue b/src/views/header/index.vue deleted file mode 100644 index ac87cb1..0000000 --- a/src/views/header/index.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 20993d8..d6963e8 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -1,11 +1,8 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: [ - "./index.html", - "./src/**/*.{js,ts,jsx,tsx,vue}", - ], + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx,vue}"], theme: { extend: {}, }, plugins: [], -} +}; diff --git a/vite.config.js b/vite.config.js index 96f5bd8..465cbf3 100644 --- a/vite.config.js +++ b/vite.config.js @@ -32,10 +32,18 @@ export default defineConfig(({ mode }) => { // protocol: 'wss', // } }, + optimizeDeps: { + include: ['vue', 'vue-router', 'pinia', 'axios'] + }, build: { commonjsOptions: { transformMixedEsModules: true, // Enable @walletconnect/web3-provider which has some code in CommonJS } }, + test: { + globals:true, + environment: 'jsdom', + // reporter: ['text', 'json', 'html', 'vue'], + }, } });