diff --git a/app/[locale]/contacts/page.tsx b/app/[locale]/contacts/page.tsx
index a7f47981..5f94ff13 100644
--- a/app/[locale]/contacts/page.tsx
+++ b/app/[locale]/contacts/page.tsx
@@ -606,6 +606,7 @@ export default function ContactsPage() {
{/* Panel 2: Contact list */}
- {children}
+
+ {children}
+
diff --git a/app/[locale]/login/page.tsx b/app/[locale]/login/page.tsx
index c527bbbf..4e7e7b94 100644
--- a/app/[locale]/login/page.tsx
+++ b/app/[locale]/login/page.tsx
@@ -11,7 +11,7 @@ import { useThemeStore } from "@/stores/theme-store";
import { useShallow } from "zustand/react/shallow";
import { useConfig } from "@/hooks/use-config";
import { cn } from "@/lib/utils";
-import { Mail, AlertCircle, Loader2, X, Info, Eye, EyeOff, LogIn, Sun, Moon, Monitor, Check, Shield } from "lucide-react";
+import { Mail, AlertCircle, Loader2, X, Info, Eye, EyeOff, LogIn, Sun, Moon, Monitor, Check, Shield, Play } from "lucide-react";
import { discoverOAuth, type OAuthMetadata } from "@/lib/oauth/discovery";
import { generateCodeVerifier, generateCodeChallenge, generateState } from "@/lib/oauth/pkce";
import { OAUTH_SCOPES } from "@/lib/oauth/tokens";
@@ -30,9 +30,9 @@ export default function LoginPage() {
const params = useParams();
const searchParams = useSearchParams();
const isAddAccountMode = searchParams.get("mode") === "add-account";
- const { login, isLoading, error, clearError, isAuthenticated } = useAuthStore();
+ const { login, loginDemo, isLoading, error, clearError, isAuthenticated } = useAuthStore();
const { theme, setTheme, initializeTheme } = useThemeStore(useShallow((s) => ({ theme: s.theme, setTheme: s.setTheme, initializeTheme: s.initializeTheme })));
- const { appName, jmapServerUrl: serverUrl, oauthEnabled, oauthOnly, oauthClientId, oauthIssuerUrl, rememberMeEnabled, devMode, loginLogoLightUrl, loginLogoDarkUrl, loginCompanyName, loginImprintUrl, loginPrivacyPolicyUrl, loginWebsiteUrl, isLoading: configLoading, error: configError } = useConfig();
+ const { appName, jmapServerUrl: serverUrl, oauthEnabled, oauthOnly, oauthClientId, oauthIssuerUrl, rememberMeEnabled, devMode, demoMode, loginLogoLightUrl, loginLogoDarkUrl, loginCompanyName, loginImprintUrl, loginPrivacyPolicyUrl, loginWebsiteUrl, isLoading: configLoading, error: configError } = useConfig();
const resolvedTheme = useThemeStore((s) => s.resolvedTheme);
const [formData, setFormData] = useState({
@@ -54,6 +54,7 @@ export default function LoginPage() {
const [oauthMetadata, setOauthMetadata] = useState
(null);
const [oauthDiscoveryDone, setOauthDiscoveryDone] = useState(false);
const [oauthLoading, setOauthLoading] = useState(false);
+ const [demoLoading, setDemoLoading] = useState(false);
const suggestionsRef = useRef(null);
const inputRef = useRef(null);
@@ -206,7 +207,7 @@ export default function LoginPage() {
);
}
- if (!serverUrl) {
+ if (!serverUrl && !demoMode) {
return (
@@ -353,9 +354,167 @@ export default function LoginPage() {
}
};
+ const handleDemoLogin = async () => {
+ setDemoLoading(true);
+ const success = await loginDemo();
+ if (success) {
+ router.push('/');
+ }
+ setDemoLoading(false);
+ };
+
const currentThemeOption = THEME_OPTIONS.find(o => o.value === theme) || THEME_OPTIONS[2];
const CurrentThemeIcon = currentThemeOption.icon;
+ // Demo-only mode: show only a large demo login button
+ if (demoMode && !isAddAccountMode) {
+ return (
+
+ {/* Theme toggle */}
+
+
+
+ {showThemeMenu && (
+
+ {THEME_OPTIONS.map((option) => {
+ const Icon = option.icon;
+ const isActive = theme === option.value;
+ return (
+
+ );
+ })}
+
+ )}
+
+
+
+
+ {/* Header with logo */}
+
+
+

+
+
+ {appName}
+
+
+ {t("demo_tagline")}
+
+
+
+ {/* Large demo button */}
+
+ {error && (
+
+
+
+ {t(`error.${error}`) || t("error.generic")}
+
+
+ )}
+
+
+
+
+ {t("demo_no_signup")}
+
+
+
+
+ {/* Footer */}
+
+ {loginCompanyName && (
+
+ {loginCompanyName}
+
+ )}
+ {(loginImprintUrl || loginPrivacyPolicyUrl || loginWebsiteUrl) && (
+
+ )}
+
+ v{APP_VERSION}
+
+
+
+
+ );
+ }
+
return (
{/* Theme toggle - top right, dropdown style */}
@@ -747,6 +906,34 @@ export default function LoginPage() {
)}
+
+ {/* Demo Mode Button */}
+ {demoMode && !isAddAccountMode && (
+
+
+
+ {t("demo_description")}
+
+
+ )}
diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx
index 520fbb56..c0b39e5a 100644
--- a/app/[locale]/page.tsx
+++ b/app/[locale]/page.tsx
@@ -1174,6 +1174,7 @@ export default function Home() {
onChange={(e) => setSearchQuery(e.target.value)}
className={cn("pl-9 h-9", searchQuery && "pr-8")}
data-search-input
+ data-tour="search-input"
/>
{searchQuery && (