diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 8e388fd6b..31497815c 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -39,7 +39,7 @@ import ContentLinkShare from '@/components/home/ContentLinkShare.vue'
import NoAuthWrapper from '@/components/misc/NoAuthWrapper.vue'
import Ready from '@/components/misc/Ready.vue'
-import {setLanguage} from '@/i18n'
+import {setLanguage, getBrowserLanguage} from '@/i18n'
import {useAuthStore} from '@/stores/auth'
import {useBaseStore} from '@/stores/base'
@@ -88,7 +88,7 @@ watch(userEmailConfirm, (userEmailConfirm) => {
router.push({name: 'user.login'})
}, { immediate: true })
-setLanguage(authStore.settings.language)
+setLanguage(authStore.settings.language ?? getBrowserLanguage())
useColorScheme()
diff --git a/frontend/src/components/base/BaseButton.story.vue b/frontend/src/components/base/BaseButton.story.vue
index 784d1bc1a..a0d68587c 100644
--- a/frontend/src/components/base/BaseButton.story.vue
+++ b/frontend/src/components/base/BaseButton.story.vue
@@ -1,10 +1,10 @@
diff --git a/frontend/src/views/user/RequestPasswordReset.vue b/frontend/src/views/user/RequestPasswordReset.vue
index 1d2b46bf8..e3607b8cd 100644
--- a/frontend/src/views/user/RequestPasswordReset.vue
+++ b/frontend/src/views/user/RequestPasswordReset.vue
@@ -79,9 +79,10 @@ async function requestPasswordReset() {
try {
await passwordResetService.requestResetPassword(passwordReset.value)
isSuccess.value = true
- } catch (e) {
- errorMsg.value = e.response.data.message
- }
+ } catch (e: unknown) {
+ const err = e as {response?: {data?: {message?: string}}}
+ errorMsg.value = err.response?.data?.message ?? ''
+ }
}
diff --git a/frontend/src/views/user/settings/ApiTokens.vue b/frontend/src/views/user/settings/ApiTokens.vue
index 70b474c35..2228948f5 100644
--- a/frontend/src/views/user/settings/ApiTokens.vue
+++ b/frontend/src/views/user/settings/ApiTokens.vue
@@ -18,19 +18,19 @@ const service = new ApiTokenService()
const tokens = ref([])
const apiDocsUrl = window.API_URL + '/docs'
const showCreateForm = ref(false)
-const availableRoutes = ref(null)
+const availableRoutes = ref> | null>(null)
const newToken = ref(new ApiTokenModel())
const newTokenExpiry = ref(30)
const newTokenExpiryCustom = ref(new Date())
-const newTokenPermissions = ref({})
-const newTokenPermissionsGroup = ref({})
+const newTokenPermissions = ref>>({})
+const newTokenPermissionsGroup = ref>({})
const newTokenTitleValid = ref(true)
const newTokenPermissionValid = ref(true)
const apiTokenTitle = ref()
const tokenCreatedSuccessMessage = ref('')
const showDeleteModal = ref(false)
-const tokenToDelete = ref()
+const tokenToDelete = ref(null)
const {t} = useI18n()
@@ -50,7 +50,7 @@ onMounted(async () => {
tokens.value = await service.getAll()
const allRoutes = await service.getAvailableRoutes()
- const routesAvailable = {}
+ const routesAvailable: Record> = {}
const keys = Object.keys(allRoutes)
keys.sort((a, b) => (a === 'other' ? 1 : b === 'other' ? -1 : 0))
keys.forEach(key => {
@@ -63,8 +63,8 @@ onMounted(async () => {
})
function resetPermissions() {
- newTokenPermissions.value = {}
- Object.entries(availableRoutes.value).forEach(entry => {
+ newTokenPermissions.value = {}
+ Object.entries(availableRoutes.value || {}).forEach(entry => {
const [group, routes] = entry
newTokenPermissions.value[group] = {}
Object.keys(routes).forEach(r => {
@@ -74,13 +74,16 @@ function resetPermissions() {
}
async function deleteToken() {
- await service.delete(tokenToDelete.value)
- showDeleteModal.value = false
- const index = tokens.value.findIndex(el => el.id === tokenToDelete.value.id)
- tokenToDelete.value = null
- if (index === -1) {
- return
- }
+ if (!tokenToDelete.value) {
+ return
+ }
+ await service.delete(tokenToDelete.value!)
+ showDeleteModal.value = false
+ const index = tokens.value.findIndex(el => el.id === tokenToDelete.value!.id)
+ tokenToDelete.value = null
+ if (index === -1) {
+ return
+ }
tokens.value.splice(index, 1)
}
@@ -127,22 +130,22 @@ async function createToken() {
showCreateForm.value = false
}
-function formatPermissionTitle(title: string): string {
- return title.replaceAll('_', ' ')
+function formatPermissionTitle(title: string | number): string {
+ return String(title).split('_').join(' ')
}
-function selectPermissionGroup(group: string, checked: boolean) {
- Object.entries(availableRoutes.value[group]).forEach(entry => {
+function selectPermissionGroup(group: string | number, checked: boolean) {
+ Object.entries(availableRoutes.value?.[group] || {}).forEach(entry => {
const [key] = entry
newTokenPermissions.value[group][key] = checked
})
}
-function toggleGroupPermissionsFromChild(group: string, checked: boolean) {
- if (checked) {
- // Check if all permissions of that group are checked and check the "select all" checkbox in that case
- let allChecked = true
- Object.entries(availableRoutes.value[group]).forEach(entry => {
+function toggleGroupPermissionsFromChild(group: string | number, checked: boolean) {
+ if (checked) {
+ // Check if all permissions of that group are checked and check the "select all" checkbox in that case
+ let allChecked = true
+ Object.entries(availableRoutes.value?.[group] || {}).forEach(entry => {
const [key] = entry
if (!newTokenPermissions.value[group][key]) {
allChecked = false
@@ -368,7 +371,7 @@ function toggleGroupPermissionsFromChild(group: string, checked: boolean) {
- {{ $t('user.settings.apiTokens.delete.text1', {token: tokenToDelete.title}) }}
+ {{ $t('user.settings.apiTokens.delete.text1', {token: tokenToDelete?.title}) }}
{{ $t('user.settings.apiTokens.delete.text2') }}
diff --git a/frontend/src/views/user/settings/Avatar.vue b/frontend/src/views/user/settings/Avatar.vue
index 69d957a91..a1c9ec8d4 100644
--- a/frontend/src/views/user/settings/Avatar.vue
+++ b/frontend/src/views/user/settings/Avatar.vue
@@ -33,7 +33,7 @@
{{ $t('user.settings.avatar.uploadAvatar') }}
@@ -87,6 +87,7 @@ import {useTitle} from '@/composables/useTitle'
import {success} from '@/message'
import {useAuthStore} from '@/stores/auth'
import Message from '@/components/misc/Message.vue'
+import type { AvatarProvider } from '@/modelTypes/IAvatar'
const {t} = useI18n({useScope: 'global'})
const authStore = useAuthStore()
@@ -106,11 +107,11 @@ const avatarService = shallowReactive(new AvatarService())
const loading = ref(false)
-const avatarProvider = ref('')
+const avatarProvider = ref('default')
async function avatarStatus() {
- const {avatarProvider: currentProvider} = await avatarService.get({})
- avatarProvider.value = currentProvider
+ const {avatarProvider: currentProvider} = await avatarService.get(new AvatarModel({}))
+ avatarProvider.value = currentProvider
}
avatarStatus()
@@ -134,9 +135,11 @@ async function uploadAvatar() {
return
}
- try {
- const blob = await new Promise(resolve => canvas.toBlob(blob => resolve(blob)))
- await avatarService.create(blob)
+ try {
+ const blob = await new Promise(resolve => canvas.toBlob((b: Blob | null) => resolve(b)))
+ if (blob) {
+ await avatarService.create(blob)
+ }
success({message: t('user.settings.avatar.setSuccess')})
authStore.reloadAvatar()
} finally {
@@ -145,24 +148,26 @@ async function uploadAvatar() {
}
}
-const avatarToCrop = ref()
-const avatarUploadInput = ref()
+const avatarToCrop = ref(null)
+const avatarUploadInput = ref(null)
function cropAvatar() {
- const avatar = avatarUploadInput.value.files
+ const files = avatarUploadInput.value?.files
- if (avatar.length === 0) {
- return
- }
+ if (!files || files.length === 0) {
+ return
+ }
loading.value = true
- const reader = new FileReader()
- reader.onload = e => {
- avatarToCrop.value = e.target.result
- isCropAvatar.value = true
- }
+ const reader = new FileReader()
+ reader.onload = e => {
+ const target = e.target as FileReader | null
+ if (!target) return
+ avatarToCrop.value = target.result
+ isCropAvatar.value = true
+ }
reader.onloadend = () => loading.value = false
- reader.readAsDataURL(avatar[0])
+ reader.readAsDataURL(files[0])
}
diff --git a/frontend/src/views/user/settings/Caldav.vue b/frontend/src/views/user/settings/Caldav.vue
index 858bb2497..81284698b 100644
--- a/frontend/src/views/user/settings/Caldav.vue
+++ b/frontend/src/views/user/settings/Caldav.vue
@@ -109,6 +109,7 @@ import {success} from '@/message'
import BaseButton from '@/components/base/BaseButton.vue'
import Message from '@/components/misc/Message.vue'
import CaldavTokenService from '@/services/caldavToken'
+import CaldavTokenModel from '@/models/caldavToken'
import { formatDateShort } from '@/helpers/time/formatDate'
import type {ICaldavToken} from '@/modelTypes/ICaldavToken'
import {useConfigStore} from '@/stores/config'
@@ -128,8 +129,10 @@ service.getAll().then((result: ICaldavToken[]) => {
const newToken = ref()
async function createToken() {
- newToken.value = await service.create({}) as ICaldavToken
- tokens.value.push(newToken.value)
+ // The API does not require any payload when creating a token.
+ // Create an empty model instance to satisfy the expected type.
+ newToken.value = await service.create(new CaldavTokenModel({}))
+ tokens.value.push(newToken.value)
}
async function deleteToken(token: ICaldavToken) {
diff --git a/frontend/src/views/user/settings/General.vue b/frontend/src/views/user/settings/General.vue
index 84cfe529a..3879ef535 100644
--- a/frontend/src/views/user/settings/General.vue
+++ b/frontend/src/views/user/settings/General.vue
@@ -26,7 +26,7 @@
v-if="isExternalUser"
class="help"
>
- {{ $t('user.settings.general.externalUserNameChange', {provider: authStore.info.authProvider}) }}
+ {{ $t('user.settings.general.externalUserNameChange', {provider: authStore.info?.authProvider}) }}
@@ -252,7 +252,7 @@ export default {name: 'UserSettingsGeneral'}
diff --git a/frontend/src/views/user/settings/TOTP.vue b/frontend/src/views/user/settings/TOTP.vue
index f10049c98..ad69465ad 100644
--- a/frontend/src/views/user/settings/TOTP.vue
+++ b/frontend/src/views/user/settings/TOTP.vue
@@ -135,7 +135,7 @@ async function totpStatus() {
return
}
try {
- totp.value = await totpService.get({})
+ totp.value = await totpService.get(new TotpModel())
totpSetQrCode()
} catch(e: unknown) {
// Error code 1016 means totp is not enabled, we don't need an error in that case.
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
index 8ef3f524f..c05a1cc2f 100644
--- a/frontend/tsconfig.app.json
+++ b/frontend/tsconfig.app.json
@@ -20,7 +20,8 @@
"strictNullChecks": true,
"paths": {
- "@/*": ["./src/*"]
+ "@/*": ["./src/*"],
+ "vite-plugin-sentry/client": ["./node_modules/vite-plugin-sentry/client.d.ts"]
},
"types": [
// https://github.com/ikenfin/vite-plugin-sentry#typescript
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index df08e5f1b..9d2201fce 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,5 +1,5 @@
///
-import {defineConfig, type PluginOption, loadEnv} from 'vite'
+import {defineConfig, type PluginOption, loadEnv, type ImportMetaEnv} from 'vite'
import {configDefaults} from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import {URL, fileURLToPath} from 'node:url'