diff --git a/frontend/src/components/misc/NoAuthWrapper.vue b/frontend/src/components/misc/NoAuthWrapper.vue
index 1698f0f22..5db7cc19c 100644
--- a/frontend/src/components/misc/NoAuthWrapper.vue
+++ b/frontend/src/components/misc/NoAuthWrapper.vue
@@ -25,7 +25,7 @@
>
{{ title }}
-
+
(),
@@ -61,6 +62,11 @@ withDefaults(
showApiConfig: false,
},
)
+
+const isDesktop = isDesktopApp()
+const hasStoredApiUrl = isDesktop && localStorage.getItem('API_URL') !== null
+const shouldShowApiConfig = computed(() => props.showApiConfig && (!isDesktop || hasStoredApiUrl))
+
const configStore = useConfigStore()
const motd = computed(() => configStore.motd)
diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json
index 4bd8c723e..3dc16ad5d 100644
--- a/frontend/src/i18n/lang/en.json
+++ b/frontend/src/i18n/lang/en.json
@@ -55,6 +55,12 @@
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occurred while authenticating against the third party.",
"oauthMissingParams": "Missing required OAuth parameters: {params}",
+ "oauthRedirectedToApp": "You have been redirected to the app. You can close this tab now.",
+ "desktopTryDemo": "Try the Demo",
+ "desktopCustomServer": "Custom Server URL",
+ "desktopCustomServerDescription": "Enter the URL of your Vikunja server to get started.",
+ "desktopWaitingForAuth": "Waiting for authentication…",
+ "desktopOAuthError": "Authentication failed: {error}",
"logout": "Logout",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
@@ -156,8 +162,7 @@
"tokenCreated": "Here is your new token: {token}",
"wontSeeItAgain": "Write it down or save it securely — you will not be able to see it again.",
"mustUseToken": "You need to create a CalDAV token to use CalDAV with any third-party client. Enter the token in the password field of your client.",
- "usernameIs": "Your username for CalDAV is: {0}",
- "apiTokenHint": "You can also use an API token with CalDAV permission. Create one in {link}."
+ "usernameIs": "Your username for CalDAV is: {0}"
},
"avatar": {
"title": "Avatar",
diff --git a/frontend/src/stores/base.ts b/frontend/src/stores/base.ts
index d90ec8449..115950ce5 100644
--- a/frontend/src/stores/base.ts
+++ b/frontend/src/stores/base.ts
@@ -7,6 +7,7 @@ import {getBlobFromBlurHash} from '@/helpers/getBlobFromBlurHash'
import ProjectModel from '@/models/project'
import ProjectService from '@/services/project'
import {checkAndSetApiUrl, ERROR_NO_API_URL, InvalidApiUrlProvidedError, NoApiUrlProvidedError} from '@/helpers/checkAndSetApiUrl'
+import {isDesktopApp} from '@/helpers/desktopAuth'
import {useMenuActive} from '@/composables/useMenuActive'
@@ -146,6 +147,19 @@ export const useBaseStore = defineStore('base', () => {
async function loadApp() {
try {
+ if (isDesktopApp()) {
+ // On desktop, ignore the default window.API_URL (set by index.html)
+ // and only use a previously stored API URL from localStorage.
+ const storedApiUrl = localStorage.getItem('API_URL')
+ if (storedApiUrl) {
+ window.API_URL = storedApiUrl
+ await authStore.checkAuth()
+ }
+ await router.isReady()
+ ready.value = true
+ return
+ }
+
await checkAndSetApiUrl(window.API_URL)
await authStore.checkAuth()
await router.isReady()
diff --git a/frontend/src/views/user/DesktopLogin.vue b/frontend/src/views/user/DesktopLogin.vue
new file mode 100644
index 000000000..de20efc1e
--- /dev/null
+++ b/frontend/src/views/user/DesktopLogin.vue
@@ -0,0 +1,119 @@
+
+
+
+ {{ errorMessage }}
+
+
+ {{ $t('user.auth.desktopWaitingForAuth') }}
+
+
+
+
+ {{ $t('user.auth.login') }}
+
+
+
+
+ {{ $t('user.auth.desktopCustomServerDescription') }}
+
+
+
+
+
+
+ Vikunja Cloud
+
+
+ {{ $t('user.auth.desktopTryDemo') }}
+
+
+ {{ $t('user.auth.desktopCustomServer') }}
+
+
+
+
+
+
diff --git a/frontend/src/views/user/Login.vue b/frontend/src/views/user/Login.vue
index 29ee5c90f..503dd46e5 100644
--- a/frontend/src/views/user/Login.vue
+++ b/frontend/src/views/user/Login.vue
@@ -15,8 +15,11 @@
>
{{ errorMessage }}
+
+
+
configStore.auth.openidConnect)
const hasOpenIdProviders = computed(() => openidConnect.value.enabled && openidConnect.value.providers?.length > 0)
const isLoading = computed(() => authStore.isLoading)
+const isDesktop = isDesktopApp()
const confirmedEmailSuccess = ref(false)
const errorMessage = ref('')
@@ -189,6 +195,7 @@ const validateUsernameField = useDebounceFn(() => {
usernameValid.value = usernameRef.value?.value !== ''
}, 100)
+
const needsTotpPasscode = computed(() => authStore.needsTotpPasscode)
const totpPasscode = ref(null)