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) { 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'