feat: added change password to about/account view
This commit is contained in:
Generated
+9
-9
@@ -33,7 +33,7 @@
|
|||||||
"prettier": "3.9.5",
|
"prettier": "3.9.5",
|
||||||
"typescript": "~6.0.0",
|
"typescript": "~6.0.0",
|
||||||
"vite": "^8.1.5",
|
"vite": "^8.1.5",
|
||||||
"vite-plugin-pwa": "^1.2.0",
|
"vite-plugin-pwa": "^1.3.0",
|
||||||
"vite-plugin-vue-devtools": "^8.1.5",
|
"vite-plugin-vue-devtools": "^8.1.5",
|
||||||
"vitest": "^4.1.10",
|
"vitest": "^4.1.10",
|
||||||
"vue-eslint-parser": "^10.4.1",
|
"vue-eslint-parser": "^10.4.1",
|
||||||
@@ -9563,17 +9563,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite-plugin-pwa": {
|
"node_modules/vite-plugin-pwa": {
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.3.0.tgz",
|
||||||
"integrity": "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw==",
|
"integrity": "sha512-c5kMgN+ITrOtHXp8PAtk2uOIEea6XjP/unCGxOWWBzQ6qa65qj/awHg0wf+QF9E/2u9vh86LqxPwzEPNbM2r5A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.3.6",
|
"debug": "^4.3.6",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"tinyglobby": "^0.2.10",
|
"tinyglobby": "^0.2.10",
|
||||||
"workbox-build": "^7.4.0",
|
"workbox-build": "^7.4.1",
|
||||||
"workbox-window": "^7.4.0"
|
"workbox-window": "^7.4.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
@@ -9583,9 +9583,9 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@vite-pwa/assets-generator": "^1.0.0",
|
"@vite-pwa/assets-generator": "^1.0.0",
|
||||||
"vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0",
|
"vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
|
||||||
"workbox-build": "^7.4.0",
|
"workbox-build": "^7.4.1",
|
||||||
"workbox-window": "^7.4.0"
|
"workbox-window": "^7.4.1"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@vite-pwa/assets-generator": {
|
"@vite-pwa/assets-generator": {
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@
|
|||||||
"prettier": "3.9.5",
|
"prettier": "3.9.5",
|
||||||
"typescript": "~6.0.0",
|
"typescript": "~6.0.0",
|
||||||
"vite": "^8.1.5",
|
"vite": "^8.1.5",
|
||||||
"vite-plugin-pwa": "^1.2.0",
|
"vite-plugin-pwa": "^1.3.0",
|
||||||
"vite-plugin-vue-devtools": "^8.1.5",
|
"vite-plugin-vue-devtools": "^8.1.5",
|
||||||
"vitest": "^4.1.10",
|
"vitest": "^4.1.10",
|
||||||
"vue-eslint-parser": "^10.4.1",
|
"vue-eslint-parser": "^10.4.1",
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ const listsStore = useListsStore()
|
|||||||
}}</span>
|
}}</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink to="/about" class="nav-item" active-class="is-active">
|
<RouterLink to="/about" class="nav-item" active-class="is-active">
|
||||||
<span class="nav-icon">ⓘ</span>
|
<span class="nav-icon">⚙</span>
|
||||||
<span class="nav-label">About</span>
|
<span class="nav-label">Account</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
@@ -55,10 +55,6 @@ const listsStore = useListsStore()
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item.is-active {
|
|
||||||
color: var(--c-accent-strong);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-badge {
|
.nav-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.35rem;
|
top: 0.35rem;
|
||||||
|
|||||||
+11
-1
@@ -1,7 +1,8 @@
|
|||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { loginApi, refreshApi, logoutApi, logoutAllApi } from '@/api/auth'
|
import { loginApi, refreshApi, logoutApi, logoutAllApi } from '@/api/auth'
|
||||||
import type { LoginPayload, TokenPair } from '@/types/auth'
|
import type { AccessTokenClaims, LoginPayload, TokenPair } from '@/types/auth'
|
||||||
|
import { decodeJwtPayload } from '@/utils/jwt'
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
const accessToken = ref<string | null>(localStorage.getItem('access_token'))
|
const accessToken = ref<string | null>(localStorage.getItem('access_token'))
|
||||||
@@ -11,6 +12,12 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
|
|
||||||
const isAuthenticated = computed(() => !!accessToken.value)
|
const isAuthenticated = computed(() => !!accessToken.value)
|
||||||
|
|
||||||
|
const currentUser = computed(() =>
|
||||||
|
accessToken.value ? decodeJwtPayload<AccessTokenClaims>(accessToken.value) : null,
|
||||||
|
)
|
||||||
|
const email = computed(() => currentUser.value?.email ?? null)
|
||||||
|
const username = computed(() => currentUser.value?.name ?? null)
|
||||||
|
|
||||||
function setTokens(tokens: TokenPair) {
|
function setTokens(tokens: TokenPair) {
|
||||||
accessToken.value = tokens.access_token
|
accessToken.value = tokens.access_token
|
||||||
refreshToken.value = tokens.refresh_token
|
refreshToken.value = tokens.refresh_token
|
||||||
@@ -89,6 +96,9 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
|
currentUser,
|
||||||
|
email,
|
||||||
|
username,
|
||||||
setTokens,
|
setTokens,
|
||||||
clearTokens,
|
clearTokens,
|
||||||
login,
|
login,
|
||||||
|
|||||||
@@ -11,3 +11,15 @@ export interface TokenPair {
|
|||||||
access_token: string
|
access_token: string
|
||||||
refresh_token: string
|
refresh_token: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AccessTokenClaims {
|
||||||
|
user_id: string
|
||||||
|
email: string
|
||||||
|
name: string
|
||||||
|
exp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChangePasswordPayload {
|
||||||
|
old_password: string
|
||||||
|
new_password: string
|
||||||
|
}
|
||||||
|
|||||||
+37
-6
@@ -1,8 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useListsStore } from '@/stores/lists'
|
import { useListsStore } from '@/stores/lists'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import ChangePasswordModal from '@/components/ChangePasswordModal.vue'
|
||||||
|
|
||||||
const listsStore = useListsStore()
|
const listsStore = useListsStore()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
const showChangePassword = ref(false)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
listsStore.loadLists()
|
listsStore.loadLists()
|
||||||
@@ -11,11 +16,25 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="page about">
|
<main class="page about">
|
||||||
<h1>About dttmr</h1>
|
<h1 v-if="authStore.username">Hi, {{ authStore.username }}</h1>
|
||||||
<p>
|
<h1 v-else>Account</h1>
|
||||||
An offline-first progressive web app for shared lists. Every change you make is saved
|
|
||||||
instantly on this device and pushed to the server as soon as you're back online.
|
<section class="card info-card">
|
||||||
</p>
|
<h4>Users</h4>
|
||||||
|
<p class="row">
|
||||||
|
<span>Signed in as</span>
|
||||||
|
<strong>{{ authStore.email ?? 'Unknown' }}</strong>
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary change-password-btn"
|
||||||
|
@click="showChangePassword = true"
|
||||||
|
>
|
||||||
|
Change password
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<h1>Pending changes</h1>
|
||||||
|
|
||||||
<section class="card info-card">
|
<section class="card info-card">
|
||||||
<h4>Sync status</h4>
|
<h4>Sync status</h4>
|
||||||
@@ -28,6 +47,8 @@ onMounted(() => {
|
|||||||
<strong>{{ listsStore.isSyncing ? 'Yes' : 'No' }}</strong>
|
<strong>{{ listsStore.isSyncing ? 'Yes' : 'No' }}</strong>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<ChangePasswordModal v-if="showChangePassword" @close="showChangePassword = false" />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -47,6 +68,10 @@ onMounted(() => {
|
|||||||
padding: 1rem 1.1rem;
|
padding: 1rem 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info-card + .info-card {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.info-card h4 {
|
.info-card h4 {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
@@ -63,4 +88,10 @@ onMounted(() => {
|
|||||||
.row strong {
|
.row strong {
|
||||||
color: var(--c-heading);
|
color: var(--c-heading);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.change-password-btn {
|
||||||
|
width: auto;
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user