Align frontend with InboxConverge brand

This commit is contained in:
Christian Krakau-Louis
2026-05-22 19:13:14 +02:00
parent 8d15fa8602
commit eae610cc95
8 changed files with 341 additions and 222 deletions
+31 -28
View File
@@ -76,7 +76,7 @@ function ProviderLogoBanner({ providerName, email }: { providerName?: string | n
if (!icon) return null;
const label = providerName ?? email?.split('@')[1] ?? 'provider';
return (
<div className="relative h-16 w-full bg-gray-50 border-b border-gray-100 overflow-hidden">
<div className="relative h-16 w-full overflow-hidden border-b border-[#edf2f8] bg-[#f7faff]">
<Image
src={`/providers/${icon}.svg`}
alt={`${label} logo`}
@@ -177,11 +177,14 @@ export default function AccountsPage() {
<AuthGuard>
<DashboardLayout>
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold text-gray-900">Mail Accounts</h1>
<div className="flex items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-extrabold text-slate-950">Mail Accounts</h1>
<p className="mt-1 text-sm text-slate-500">Connect and monitor the external inboxes feeding Gmail.</p>
</div>
<button
onClick={() => setIsModalOpen(true)}
className="flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
className="ic-button-primary"
>
<Plus className="h-5 w-5 mr-2" />
Add Account
@@ -190,15 +193,15 @@ export default function AccountsPage() {
{isLoading ? (
<div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-[#0b63f6]"></div>
</div>
) : accounts && accounts.length > 0 ? (
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{accounts.map((account) => (
<div
key={account.id}
className={`bg-white rounded-lg shadow-md border overflow-hidden transition-opacity ${
account.is_enabled ? 'border-gray-200' : 'border-gray-200 opacity-60'
className={`ic-card overflow-hidden transition-opacity ${
account.is_enabled ? '' : 'opacity-60'
}`}
>
{/* Provider logo banner full-width strip that accommodates any aspect ratio */}
@@ -207,19 +210,19 @@ export default function AccountsPage() {
<div className="p-6">
<div className="flex items-start justify-between mb-4">
<div className="min-w-0 flex-1">
<h3 className="text-lg font-semibold text-gray-900 mb-1 truncate">
<h3 className="text-lg font-bold text-slate-950 mb-1 truncate">
{account.name}
</h3>
<p className="text-sm text-gray-500 truncate">{account.email_address}</p>
<p className="text-sm text-slate-500 truncate">{account.email_address}</p>
</div>
<div className="flex items-center gap-2 ml-2 flex-shrink-0">
{account.is_enabled ? (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700">
<span className="inline-flex items-center gap-1 rounded-md bg-[#e8f7ef] px-2 py-0.5 text-xs font-bold text-[#11834d]">
<CheckCircle className="h-3 w-3" />
Enabled
</span>
) : (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-500">
<span className="inline-flex items-center gap-1 rounded-md bg-slate-100 px-2 py-0.5 text-xs font-bold text-slate-500">
<XCircle className="h-3 w-3" />
Disabled
</span>
@@ -229,31 +232,31 @@ export default function AccountsPage() {
<div className="space-y-2 mb-4">
<div className="flex items-center text-sm">
<span className="text-gray-500 w-20">Protocol:</span>
<span className="text-gray-900 font-medium">
<span className="w-20 text-slate-500">Protocol:</span>
<span className="font-semibold text-slate-950">
{account.protocol.toUpperCase()}
</span>
</div>
<div className="flex items-center text-sm">
<span className="text-gray-500 w-20">Host:</span>
<span className="text-gray-900">{account.host}:{account.port}</span>
<span className="w-20 text-slate-500">Host:</span>
<span className="text-slate-950">{account.host}:{account.port}</span>
</div>
<div className="flex items-center text-sm">
<span className="text-gray-500 w-20">SSL:</span>
<span className="text-gray-900">
<span className="w-20 text-slate-500">SSL:</span>
<span className="text-slate-950">
{account.use_ssl ? 'Yes' : 'No'}
</span>
</div>
<div className="flex items-center text-sm">
<span className="text-gray-500 w-20">Interval:</span>
<span className="text-gray-900">
<span className="w-20 text-slate-500">Interval:</span>
<span className="text-slate-950">
Every {account.check_interval_minutes} min
</span>
</div>
</div>
{account.last_check_at && (
<div className="mb-4 text-xs text-gray-500">
<div className="mb-4 text-xs text-slate-500">
Last checked: {new Date(account.last_check_at).toLocaleString()}
</div>
)}
@@ -278,15 +281,15 @@ export default function AccountsPage() {
</div>
)}
<div className="flex items-center gap-2 pt-4 border-t border-gray-200">
<div className="flex items-center gap-2 border-t border-[#edf2f8] pt-4">
<button
onClick={() => handleToggle(account.id)}
disabled={toggleMutation.isPending}
title={account.is_enabled ? 'Disable account' : 'Enable account'}
className={`flex items-center justify-center px-3 py-2 text-sm font-medium rounded-md transition-colors disabled:opacity-50 ${
account.is_enabled
? 'text-yellow-600 bg-yellow-50 hover:bg-yellow-100'
: 'text-green-600 bg-green-50 hover:bg-green-100'
? 'text-yellow-700 bg-yellow-50 hover:bg-yellow-100'
: 'text-[#11834d] bg-[#e8f7ef] hover:bg-green-100'
}`}
>
<Power className="h-4 w-4" />
@@ -299,7 +302,7 @@ export default function AccountsPage() {
className={`flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium rounded-md transition-colors disabled:opacity-50 ${
successIds.has(account.id)
? 'text-green-600 bg-green-50 hover:bg-green-100'
: 'text-indigo-600 bg-indigo-50 hover:bg-indigo-100'
: 'text-[#0b63f6] bg-[#e7f0ff] hover:bg-blue-100'
}`}
>
<RefreshCw className={`h-4 w-4 ${pullingIds.has(account.id) ? 'animate-spin' : ''}`} />
@@ -313,7 +316,7 @@ export default function AccountsPage() {
</button>
<button
onClick={() => handleEdit(account)}
className="flex-1 flex items-center justify-center px-3 py-2 text-sm font-medium text-blue-600 bg-blue-50 rounded-md hover:bg-blue-100 transition-colors"
className="flex-1 flex items-center justify-center px-3 py-2 text-sm font-bold text-[#0b63f6] bg-[#e7f0ff] rounded-md hover:bg-blue-100 transition-colors"
>
<Edit2 className="h-4 w-4 mr-1" />
Edit
@@ -332,11 +335,11 @@ export default function AccountsPage() {
))}
</div>
) : (
<div className="text-center py-12 bg-white rounded-lg shadow">
<p className="text-gray-500 mb-4">No mail accounts configured yet</p>
<div className="ic-card py-12 text-center">
<p className="text-slate-500 mb-4">No mail accounts configured yet</p>
<button
onClick={() => setIsModalOpen(true)}
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
className="ic-button-primary"
>
<Plus className="h-5 w-5 mr-2" />
Add Your First Account
+26 -26
View File
@@ -27,13 +27,13 @@ interface StatCardProps {
function StatCard({ title, value, icon: Icon, iconColor }: StatCardProps) {
return (
<div className="bg-white rounded-lg shadow p-6">
<div className="ic-card p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">{title}</p>
<p className="mt-2 text-3xl font-semibold text-gray-900">{value}</p>
<p className="text-sm font-semibold text-slate-500">{title}</p>
<p className="mt-2 text-3xl font-bold text-slate-950">{value}</p>
</div>
<div className={`p-3 rounded-full ${iconColor}`}>
<div className={`rounded-lg p-3 ${iconColor}`}>
<Icon className="h-8 w-8 text-white" />
</div>
</div>
@@ -54,36 +54,36 @@ function AccountStatusRow({ account }: { account: MailAccount }) {
});
return (
<div className="px-5 py-4 border-b border-gray-100 last:border-b-0">
<div className="px-5 py-4 border-b border-[#edf2f8] last:border-b-0">
<div className="flex items-start justify-between gap-4">
{/* Left: name + email */}
<div className="flex items-center gap-3 min-w-0">
<Inbox className="h-4 w-4 text-blue-400 shrink-0" />
<Inbox className="h-4 w-4 text-blue-500 shrink-0" />
<div className="min-w-0">
<p className="text-sm font-semibold text-gray-900 truncate">{account.name}</p>
<p className="text-xs text-gray-400 truncate">{account.email_address}</p>
<p className="text-sm font-semibold text-slate-950 truncate">{account.name}</p>
<p className="text-xs text-slate-400 truncate">{account.email_address}</p>
</div>
</div>
{/* Right: status badge + last check */}
<div className="text-right shrink-0">
{hasError ? (
<span className="inline-flex items-center gap-1 text-xs font-medium text-red-600">
<span className="inline-flex items-center gap-1 text-xs font-bold text-red-600">
<XCircle className="h-3.5 w-3.5" />
Error
</span>
) : lastChecked ? (
<span className="inline-flex items-center gap-1 text-xs font-medium text-green-600">
<span className="inline-flex items-center gap-1 text-xs font-bold text-[#11834d]">
<CheckCircle className="h-3.5 w-3.5" />
OK
</span>
) : (
<span className="inline-flex items-center gap-1 text-xs font-medium text-gray-400">
<span className="inline-flex items-center gap-1 text-xs font-bold text-slate-400">
<Clock className="h-3.5 w-3.5" />
Pending
</span>
)}
<p className="text-xs text-gray-400 mt-0.5">
<p className="text-xs text-slate-400 mt-0.5">
{formatRelative(lastChecked)}
</p>
</div>
@@ -91,7 +91,7 @@ function AccountStatusRow({ account }: { account: MailAccount }) {
{/* Error message */}
{hasError && (
<div className="mt-2 flex items-start gap-1.5 p-2 bg-red-50 border border-red-200 rounded">
<div className="mt-2 flex items-start gap-1.5 rounded-md border border-red-200 bg-red-50 p-2">
<AlertTriangle className="h-3.5 w-3.5 text-red-500 shrink-0 mt-0.5" />
<p className="text-xs text-red-700 line-clamp-2 flex-1">{account.last_error_message}</p>
<button
@@ -108,7 +108,7 @@ function AccountStatusRow({ account }: { account: MailAccount }) {
{/* Lifetime counters (only when there's activity) */}
{(account.total_emails_processed > 0 || account.total_emails_failed > 0) && (
<div className="mt-2 flex items-center gap-4 text-xs text-gray-500">
<div className="mt-2 flex items-center gap-4 text-xs text-slate-500">
<span>{account.total_emails_processed.toLocaleString()} processed</span>
{account.total_emails_failed > 0 && (
<span className="text-red-500">{account.total_emails_failed.toLocaleString()} failed</span>
@@ -142,40 +142,40 @@ export default function DashboardPage() {
title="Total Accounts"
value={stats.totalAccounts}
icon={Mail}
iconColor="bg-blue-500"
iconColor="bg-[#0b63f6]"
/>
<StatCard
title="Emails Processed"
value={stats.totalProcessed.toLocaleString()}
icon={Send}
iconColor="bg-green-500"
iconColor="bg-[#11834d]"
/>
<StatCard
title="Active Accounts"
value={stats.activeAccounts}
icon={CheckCircle}
iconColor="bg-purple-500"
iconColor="bg-[#10213f]"
/>
<StatCard
title="Accounts with Errors"
value={stats.accountsWithErrors}
icon={AlertCircle}
iconColor={stats.accountsWithErrors > 0 ? 'bg-red-500' : 'bg-gray-400'}
iconColor={stats.accountsWithErrors > 0 ? 'bg-red-500' : 'bg-slate-400'}
/>
</div>
{/* Mailbox Status Overview */}
<div className="bg-white rounded-lg shadow">
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900">Mailbox Status</h3>
<Link href="/logs" className="text-sm text-blue-600 hover:text-blue-800 font-medium">
<div className="ic-card overflow-hidden">
<div className="flex items-center justify-between border-b border-[#d9e3f2] bg-[#fbfdff] px-6 py-4">
<h3 className="text-lg font-bold text-slate-950">Mailbox Status</h3>
<Link href="/logs" className="text-sm font-bold text-[#0b63f6] hover:text-[#0649bf]">
View activity & history
</Link>
</div>
{accountsLoading ? (
<div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-[#0b63f6]" />
</div>
) : accounts && accounts.length > 0 ? (
<div>
@@ -185,11 +185,11 @@ export default function DashboardPage() {
</div>
) : (
<div className="text-center py-12">
<Clock className="mx-auto h-10 w-10 text-gray-300 mb-3" />
<p className="text-sm text-gray-500">No mail accounts configured yet.</p>
<Clock className="mx-auto h-10 w-10 text-slate-300 mb-3" />
<p className="text-sm text-slate-500">No mail accounts configured yet.</p>
<Link
href="/accounts"
className="mt-3 inline-block text-sm text-blue-600 hover:text-blue-800 font-medium"
className="mt-3 inline-block text-sm font-bold text-[#0b63f6] hover:text-[#0649bf]"
>
Add your first account
</Link>
+96 -10
View File
@@ -2,7 +2,19 @@
:root {
--background: #ffffff;
--foreground: #171717;
--foreground: #10213f;
--ic-bg-soft: #f7faff;
--ic-bg-blue: #eef5ff;
--ic-ink: #10213f;
--ic-ink-strong: #07162f;
--ic-muted: #52617a;
--ic-line: #d9e3f2;
--ic-line-strong: #b8c8df;
--ic-blue: #0b63f6;
--ic-blue-deep: #0649bf;
--ic-green: #11834d;
--ic-green-soft: #e8f7ef;
--ic-shadow: 0 24px 70px rgba(16, 33, 63, 0.11);
}
@theme inline {
@@ -12,15 +24,89 @@
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
body {
background:
linear-gradient(180deg, rgba(238, 245, 255, 0.72), rgba(255, 255, 255, 0) 420px),
var(--background);
color: var(--foreground);
font-family: var(--font-geist-sans), Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
letter-spacing: 0;
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
button,
input,
select,
textarea {
font: inherit;
}
::selection {
background: rgba(11, 99, 246, 0.16);
}
.ic-panel {
border: 1px solid var(--ic-line);
border-radius: 8px;
background: rgba(255, 255, 255, 0.96);
box-shadow: var(--ic-shadow);
}
.ic-card {
border: 1px solid var(--ic-line);
border-radius: 8px;
background: #ffffff;
box-shadow: 0 14px 40px rgba(16, 33, 63, 0.07);
}
.ic-button-primary {
display: inline-flex;
min-height: 42px;
align-items: center;
justify-content: center;
gap: 0.5rem;
border-radius: 8px;
background: var(--ic-blue);
padding: 0.625rem 1rem;
color: #ffffff;
font-size: 0.875rem;
font-weight: 750;
transition: background 160ms ease, box-shadow 160ms ease, transform 160ms ease;
}
.ic-button-primary:hover {
background: var(--ic-blue-deep);
box-shadow: 0 14px 28px rgba(11, 99, 246, 0.18);
transform: translateY(-1px);
}
.ic-button-secondary {
display: inline-flex;
min-height: 42px;
align-items: center;
justify-content: center;
gap: 0.5rem;
border: 1px solid var(--ic-line-strong);
border-radius: 8px;
background: #ffffff;
padding: 0.625rem 1rem;
color: var(--ic-ink);
font-size: 0.875rem;
font-weight: 750;
transition: background 160ms ease, border-color 160ms ease, transform 160ms ease;
}
.ic-button-secondary:hover {
border-color: #8aa5ca;
background: #f9fbff;
transform: translateY(-1px);
}
.ic-focus {
outline: 2px solid transparent;
outline-offset: 2px;
}
.ic-focus:focus-visible {
outline-color: var(--ic-blue);
box-shadow: 0 0 0 4px rgba(11, 99, 246, 0.14);
}
+31 -23
View File
@@ -4,6 +4,7 @@ import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { authApi } from '@/lib/api';
import { BrandMark } from '@/components/BrandMark';
export default function LoginPage() {
const router = useRouter();
@@ -42,25 +43,31 @@ export default function LoginPage() {
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div className="flex min-h-screen items-center justify-center bg-[#f7faff] px-4 py-12 sm:px-6 lg:px-8">
<div className="w-full max-w-md">
<div className="mb-8 flex justify-center">
<Link href="/">
<BrandMark />
</Link>
</div>
<div className="ic-panel p-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
InboxConverge
<h2 className="text-center text-3xl font-extrabold text-slate-950">
Sign in
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Sign in to your account
<p className="mt-2 text-center text-sm text-slate-500">
Continue to your mailbox delivery dashboard.
</p>
</div>
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
{error && (
<div className="rounded-md bg-red-50 p-4">
<div className="rounded-md border border-red-200 bg-red-50 p-4">
<p className="text-sm text-red-800">{error}</p>
</div>
)}
<div className="rounded-md shadow-sm -space-y-px">
<div className="space-y-4">
<div>
<label htmlFor="email" className="sr-only">
<label htmlFor="email" className="block text-sm font-bold text-slate-700">
Email address
</label>
<input
@@ -71,12 +78,12 @@ export default function LoginPage() {
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
className="ic-focus mt-1 relative block w-full rounded-md border border-[#b8c8df] px-3 py-2.5 text-slate-950 placeholder-slate-400 focus:border-[#0b63f6] sm:text-sm"
placeholder="Email address"
/>
</div>
<div>
<label htmlFor="password" className="sr-only">
<label htmlFor="password" className="block text-sm font-bold text-slate-700">
Password
</label>
<input
@@ -87,7 +94,7 @@ export default function LoginPage() {
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
className="ic-focus mt-1 relative block w-full rounded-md border border-[#b8c8df] px-3 py-2.5 text-slate-950 placeholder-slate-400 focus:border-[#0b63f6] sm:text-sm"
placeholder="Password"
/>
</div>
@@ -97,7 +104,7 @@ export default function LoginPage() {
<button
type="submit"
disabled={loading}
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
className="ic-button-primary w-full disabled:opacity-50"
>
{loading ? 'Signing in...' : 'Sign in'}
</button>
@@ -105,10 +112,10 @@ export default function LoginPage() {
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300" />
<div className="w-full border-t border-[#d9e3f2]" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-gray-50 text-gray-500">Or continue with</span>
<span className="bg-white px-2 text-slate-500">Or continue with</span>
</div>
</div>
@@ -116,7 +123,7 @@ export default function LoginPage() {
<button
type="button"
onClick={handleGoogleLogin}
className="w-full flex justify-center items-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
className="ic-button-secondary w-full"
>
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
<path
@@ -141,26 +148,27 @@ export default function LoginPage() {
</div>
<div className="text-center">
<p className="text-sm text-gray-600">
<p className="text-sm text-slate-600">
Don&apos;t have an account?{' '}
<Link href="/register" className="font-medium text-blue-600 hover:text-blue-500">
<Link href="/register" className="font-bold text-[#0b63f6] hover:text-[#0649bf]">
Sign up
</Link>
</p>
</div>
</form>
</div>
<div className="mt-6 flex justify-center gap-4 text-xs text-gray-400">
<Link href="/privacy" className="hover:text-gray-600 transition-colors">
<div className="mt-6 flex justify-center gap-4 text-xs text-slate-400">
<Link href="/privacy" className="hover:text-slate-600 transition-colors">
Privacy Policy
</Link>
<Link href="/terms" className="hover:text-gray-600 transition-colors">
<Link href="/terms" className="hover:text-slate-600 transition-colors">
Terms
</Link>
<Link href="/impressum" className="hover:text-gray-600 transition-colors">
<Link href="/impressum" className="hover:text-slate-600 transition-colors">
Impressum
</Link>
<Link href="/datenschutz" className="hover:text-gray-600 transition-colors">
<Link href="/datenschutz" className="hover:text-slate-600 transition-colors">
Datenschutz
</Link>
</div>
+58 -61
View File
@@ -7,7 +7,8 @@ import { useAuthStore } from '@/store/authStore';
import { userApi, SubscriptionPlan } from '@/lib/api';
import { useQuery } from '@tanstack/react-query';
import api from '@/lib/api';
import { Mail, ArrowRight, Shield, Zap, Clock, Check } from 'lucide-react';
import { ArrowRight, Shield, Zap, Clock, Check } from 'lucide-react';
import { BrandMark } from '@/components/BrandMark';
async function fetchPublicPlans(): Promise<SubscriptionPlan[]> {
const res = await api.get<SubscriptionPlan[]>('/subscriptions/plans');
@@ -20,39 +21,39 @@ function PricingCard({ plan }: { plan: SubscriptionPlan }) {
: null;
return (
<div className="bg-white rounded-xl shadow-md p-8 flex flex-col border border-gray-100 hover:shadow-lg transition-shadow">
<h3 className="text-xl font-bold text-gray-900">{plan.name}</h3>
<p className="mt-2 text-sm text-gray-500 flex-1">{plan.description}</p>
<div className="ic-card flex flex-col p-8 transition-transform hover:-translate-y-0.5">
<h3 className="text-xl font-bold text-slate-950">{plan.name}</h3>
<p className="mt-2 text-sm text-slate-500 flex-1">{plan.description}</p>
<div className="mt-6">
<span className="text-4xl font-extrabold text-gray-900">
<span className="text-4xl font-extrabold text-slate-950">
{plan.price_monthly.toFixed(2)}
</span>
<span className="text-gray-500">/month</span>
<span className="text-slate-500">/month</span>
{yearlyMonthly && (
<p className="text-xs text-green-600 mt-1">
<p className="text-xs text-[#11834d] mt-1">
or {yearlyMonthly}/mo billed yearly ({plan.price_yearly?.toFixed(2)})
</p>
)}
</div>
<ul className="mt-6 space-y-2 text-sm text-gray-600">
<ul className="mt-6 space-y-2 text-sm text-slate-600">
<li className="flex items-center gap-2">
<Check className="h-4 w-4 text-green-500 shrink-0" />
<Check className="h-4 w-4 text-[#11834d] shrink-0" />
{plan.max_mail_accounts === 1
? '1 mailbox'
: `Up to ${plan.max_mail_accounts} mailboxes`}
</li>
<li className="flex items-center gap-2">
<Check className="h-4 w-4 text-green-500 shrink-0" />
<Check className="h-4 w-4 text-[#11834d] shrink-0" />
Checked every {plan.check_interval_minutes} minute{plan.check_interval_minutes > 1 ? 's' : ''}
</li>
<li className="flex items-center gap-2">
<Check className="h-4 w-4 text-green-500 shrink-0" />
<Check className="h-4 w-4 text-[#11834d] shrink-0" />
Up to {plan.max_emails_per_day.toLocaleString()} emails/day
</li>
</ul>
<Link
href="/register"
className="mt-8 block text-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium"
className="ic-button-primary mt-8 w-full"
>
Get started
</Link>
@@ -99,25 +100,22 @@ export default function Home() {
const hasPaidPlans = plans && plans.length > 0;
return (
<div className="min-h-screen bg-gradient-to-b from-blue-50 to-white">
<div className="min-h-screen bg-white">
{/* Header */}
<header className="border-b border-gray-200 bg-white">
<header className="border-b border-[#d9e3f2] bg-white/90 backdrop-blur-xl">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center py-4">
<div className="flex items-center">
<Mail className="h-8 w-8 text-blue-600 mr-2" />
<h1 className="text-2xl font-bold text-gray-900">InboxConverge</h1>
</div>
<div className="flex items-center justify-between py-4">
<BrandMark />
<div className="flex items-center gap-4">
<Link
href="/login"
className="text-gray-700 hover:text-gray-900 font-medium"
className="font-semibold text-slate-700 hover:text-slate-950"
>
Sign In
</Link>
<Link
href="/register"
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
className="ic-button-primary"
>
Sign Up Free
</Link>
@@ -129,32 +127,31 @@ export default function Home() {
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
{/* Hero */}
<div className="text-center">
<h2 className="text-4xl sm:text-5xl font-bold text-gray-900 mb-6">
Your old inboxes,{' '}
<span className="text-blue-600">delivered to Gmail.</span>
<div className="mx-auto max-w-4xl text-center">
<h2 className="mb-6 text-4xl font-extrabold leading-tight text-slate-950 sm:text-6xl">
Your old inboxes, delivered to Gmail.
</h2>
<p className="text-xl text-gray-600 mb-4 max-w-2xl mx-auto">
<p className="mx-auto mb-4 max-w-2xl text-xl leading-8 text-slate-600">
You know the ones that GMX account from 2009, the old ISP address your
bank still sends to, the Hotmail you gave out in school. InboxConverge
quietly polls them all and drops everything into your Gmail. Set it once,
forget it exists.
</p>
<p className="text-base text-gray-500 mb-8 max-w-xl mx-auto">
<p className="text-base text-slate-500 mb-8 max-w-xl mx-auto">
No forwarding rules to configure. No email clients to keep open.
Just your mail, where you actually read it.
</p>
<div className="flex items-center justify-center gap-4 flex-wrap">
<Link
href="/register"
className="flex items-center px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-lg font-medium"
className="ic-button-primary min-h-12 px-6 text-base"
>
Get Started it&apos;s free
<ArrowRight className="ml-2 h-5 w-5" />
</Link>
<Link
href="/login"
className="px-6 py-3 bg-white border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors text-lg font-medium"
className="ic-button-secondary min-h-12 px-6 text-base"
>
Sign In
</Link>
@@ -163,40 +160,40 @@ export default function Home() {
{/* Features */}
<div className="mt-20 grid md:grid-cols-3 gap-8">
<div className="bg-white p-6 rounded-lg shadow-md">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-blue-500 text-white mb-4">
<div className="ic-card p-6">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-[#0b63f6] text-white mb-4">
<Zap className="h-6 w-6" />
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">
<h3 className="text-xl font-bold text-slate-950 mb-2">
Auto-detects everything
</h3>
<p className="text-gray-600">
<p className="text-slate-600">
Type your old email address and InboxConverge figures out the server
settings. No Googling port numbers required.
</p>
</div>
<div className="bg-white p-6 rounded-lg shadow-md">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-green-500 text-white mb-4">
<div className="ic-card p-6">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-[#11834d] text-white mb-4">
<Clock className="h-6 w-6" />
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">
<h3 className="text-xl font-bold text-slate-950 mb-2">
Runs in the background
</h3>
<p className="text-gray-600">
<p className="text-slate-600">
Checks your old inboxes on a schedule you choose from every minute
to once a day. New mail appears in Gmail as if it was always there.
</p>
</div>
<div className="bg-white p-6 rounded-lg shadow-md">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-purple-500 text-white mb-4">
<div className="ic-card p-6">
<div className="flex items-center justify-center h-12 w-12 rounded-md bg-[#10213f] text-white mb-4">
<Shield className="h-6 w-6" />
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">
<h3 className="text-xl font-bold text-slate-950 mb-2">
Your passwords stay yours
</h3>
<p className="text-gray-600">
<p className="text-slate-600">
Credentials are encrypted at rest and never shared. All connections
use SSL/TLS and Gmail delivery uses OAuth2 no app passwords needed.
</p>
@@ -205,36 +202,36 @@ export default function Home() {
{/* How It Works */}
<div className="mt-20">
<h3 className="text-3xl font-bold text-center text-gray-900 mb-12">
<h3 className="text-3xl font-extrabold text-center text-slate-950 mb-12">
Three steps and you&apos;re done
</h3>
<div className="grid md:grid-cols-3 gap-8">
<div className="text-center">
<div className="flex items-center justify-center h-16 w-16 rounded-full bg-blue-100 text-blue-600 text-2xl font-bold mx-auto mb-4">
<div className="flex items-center justify-center h-16 w-16 rounded-full bg-[#e7f0ff] text-[#0b63f6] text-2xl font-bold mx-auto mb-4">
1
</div>
<h4 className="text-xl font-semibold text-gray-900 mb-2">Add your old inbox</h4>
<p className="text-gray-600">
<h4 className="text-xl font-bold text-slate-950 mb-2">Add your old inbox</h4>
<p className="text-slate-600">
Paste the email address InboxConverge auto-detects the POP3/IMAP
settings in seconds.
</p>
</div>
<div className="text-center">
<div className="flex items-center justify-center h-16 w-16 rounded-full bg-blue-100 text-blue-600 text-2xl font-bold mx-auto mb-4">
<div className="flex items-center justify-center h-16 w-16 rounded-full bg-[#e7f0ff] text-[#0b63f6] text-2xl font-bold mx-auto mb-4">
2
</div>
<h4 className="text-xl font-semibold text-gray-900 mb-2">Connect Gmail</h4>
<p className="text-gray-600">
<h4 className="text-xl font-bold text-slate-950 mb-2">Connect Gmail</h4>
<p className="text-slate-600">
Sign in with Google once. InboxConverge delivers mail directly into
your inbox using the Gmail API no SMTP relay needed.
</p>
</div>
<div className="text-center">
<div className="flex items-center justify-center h-16 w-16 rounded-full bg-blue-100 text-blue-600 text-2xl font-bold mx-auto mb-4">
<div className="flex items-center justify-center h-16 w-16 rounded-full bg-[#e7f0ff] text-[#0b63f6] text-2xl font-bold mx-auto mb-4">
3
</div>
<h4 className="text-xl font-semibold text-gray-900 mb-2">Close the tab</h4>
<p className="text-gray-600">
<h4 className="text-xl font-bold text-slate-950 mb-2">Close the tab</h4>
<p className="text-slate-600">
Seriously, that&apos;s it. Your mail arrives automatically from now on.
Check the dashboard whenever you like, but you won&apos;t need to.
</p>
@@ -245,10 +242,10 @@ export default function Home() {
{/* Pricing — only rendered when paid plans exist (hidden in enterprise/all-free mode) */}
{hasPaidPlans && (
<div className="mt-24">
<h3 className="text-3xl font-bold text-center text-gray-900 mb-4">
<h3 className="text-3xl font-extrabold text-center text-slate-950 mb-4">
Pricing
</h3>
<p className="text-center text-gray-500 mb-12 max-w-xl mx-auto">
<p className="text-center text-slate-500 mb-12 max-w-xl mx-auto">
Start free. Upgrade if you need more inboxes or faster checks.
Cancel any time no questions, no fuss.
</p>
@@ -257,7 +254,7 @@ export default function Home() {
<PricingCard key={plan.id} plan={plan} />
))}
</div>
<p className="mt-8 text-center text-sm text-gray-500">
<p className="mt-8 text-center text-sm text-slate-500">
All paid plans include a{' '}
<span className="font-medium">free tier</span> when you first sign up
no credit card required.
@@ -267,22 +264,22 @@ export default function Home() {
</main>
{/* Footer */}
<footer className="mt-20 border-t border-gray-200 bg-white">
<footer className="mt-20 border-t border-[#d9e3f2] bg-[#fbfdff]">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<p className="text-center text-gray-600 text-sm mb-3">
<p className="text-center text-slate-600 text-sm mb-3">
© {new Date().getFullYear()} InboxConverge made for people, not enterprises.
</p>
<div className="flex justify-center gap-4 text-xs text-gray-400">
<Link href="/privacy" className="hover:text-gray-600 transition-colors">
<div className="flex justify-center gap-4 text-xs text-slate-400">
<Link href="/privacy" className="hover:text-slate-600 transition-colors">
Privacy Policy
</Link>
<Link href="/terms" className="hover:text-gray-600 transition-colors">
<Link href="/terms" className="hover:text-slate-600 transition-colors">
Terms of Service
</Link>
<Link href="/impressum" className="hover:text-gray-600 transition-colors">
<Link href="/impressum" className="hover:text-slate-600 transition-colors">
Impressum
</Link>
<Link href="/datenschutz" className="hover:text-gray-600 transition-colors">
<Link href="/datenschutz" className="hover:text-slate-600 transition-colors">
Datenschutz
</Link>
</div>
+34 -26
View File
@@ -4,6 +4,7 @@ import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { authApi } from '@/lib/api';
import { BrandMark } from '@/components/BrandMark';
export default function RegisterPage() {
const router = useRouter();
@@ -56,25 +57,31 @@ export default function RegisterPage() {
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div className="flex min-h-screen items-center justify-center bg-[#f7faff] px-4 py-12 sm:px-6 lg:px-8">
<div className="w-full max-w-md">
<div className="mb-8 flex justify-center">
<Link href="/">
<BrandMark />
</Link>
</div>
<div className="ic-panel p-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
<h2 className="text-center text-3xl font-extrabold text-slate-950">
Create your account
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Start forwarding your emails
<p className="mt-2 text-center text-sm text-slate-500">
Start consolidating external mailboxes into Gmail.
</p>
</div>
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
{error && (
<div className="rounded-md bg-red-50 p-4">
<div className="rounded-md border border-red-200 bg-red-50 p-4">
<p className="text-sm text-red-800">{error}</p>
</div>
)}
<div className="rounded-md shadow-sm space-y-4">
<div className="space-y-4">
<div>
<label htmlFor="full_name" className="block text-sm font-medium text-gray-700">
<label htmlFor="full_name" className="block text-sm font-bold text-slate-700">
Full Name
</label>
<input
@@ -84,12 +91,12 @@ export default function RegisterPage() {
required
value={formData.full_name}
onChange={(e) => setFormData({ ...formData, full_name: e.target.value })}
className="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
className="ic-focus mt-1 relative block w-full rounded-md border border-[#b8c8df] px-3 py-2.5 text-slate-950 placeholder-slate-400 focus:border-[#0b63f6] sm:text-sm"
placeholder="John Doe"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
<label htmlFor="email" className="block text-sm font-bold text-slate-700">
Email address
</label>
<input
@@ -100,12 +107,12 @@ export default function RegisterPage() {
required
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
className="ic-focus mt-1 relative block w-full rounded-md border border-[#b8c8df] px-3 py-2.5 text-slate-950 placeholder-slate-400 focus:border-[#0b63f6] sm:text-sm"
placeholder="you@example.com"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
<label htmlFor="password" className="block text-sm font-bold text-slate-700">
Password
</label>
<input
@@ -116,12 +123,12 @@ export default function RegisterPage() {
required
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
className="ic-focus mt-1 relative block w-full rounded-md border border-[#b8c8df] px-3 py-2.5 text-slate-950 placeholder-slate-400 focus:border-[#0b63f6] sm:text-sm"
placeholder="••••••••"
/>
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700">
<label htmlFor="confirmPassword" className="block text-sm font-bold text-slate-700">
Confirm Password
</label>
<input
@@ -132,7 +139,7 @@ export default function RegisterPage() {
required
value={formData.confirmPassword}
onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })}
className="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
className="ic-focus mt-1 relative block w-full rounded-md border border-[#b8c8df] px-3 py-2.5 text-slate-950 placeholder-slate-400 focus:border-[#0b63f6] sm:text-sm"
placeholder="••••••••"
/>
</div>
@@ -142,45 +149,46 @@ export default function RegisterPage() {
<button
type="submit"
disabled={loading}
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
className="ic-button-primary w-full disabled:opacity-50"
>
{loading ? 'Creating account...' : 'Sign up'}
</button>
</div>
<div className="text-center">
<p className="text-sm text-gray-600">
<p className="text-sm text-slate-600">
Already have an account?{' '}
<Link href="/login" className="font-medium text-blue-600 hover:text-blue-500">
<Link href="/login" className="font-bold text-[#0b63f6] hover:text-[#0649bf]">
Sign in
</Link>
</p>
</div>
<p className="text-xs text-center text-gray-400">
<p className="text-xs text-center text-slate-400">
By creating an account you agree to our{' '}
<Link href="/terms" className="underline hover:text-gray-600">
<Link href="/terms" className="underline hover:text-slate-600">
Terms of Service
</Link>{' '}
and{' '}
<Link href="/privacy" className="underline hover:text-gray-600">
<Link href="/privacy" className="underline hover:text-slate-600">
Privacy Policy
</Link>
.
</p>
</form>
</div>
<div className="mt-4 flex justify-center gap-4 text-xs text-gray-400">
<Link href="/privacy" className="hover:text-gray-600 transition-colors">
<div className="mt-4 flex justify-center gap-4 text-xs text-slate-400">
<Link href="/privacy" className="hover:text-slate-600 transition-colors">
Privacy Policy
</Link>
<Link href="/terms" className="hover:text-gray-600 transition-colors">
<Link href="/terms" className="hover:text-slate-600 transition-colors">
Terms
</Link>
<Link href="/impressum" className="hover:text-gray-600 transition-colors">
<Link href="/impressum" className="hover:text-slate-600 transition-colors">
Impressum
</Link>
<Link href="/datenschutz" className="hover:text-gray-600 transition-colors">
<Link href="/datenschutz" className="hover:text-slate-600 transition-colors">
Datenschutz
</Link>
</div>
+16
View File
@@ -0,0 +1,16 @@
import { Mail } from 'lucide-react';
interface BrandMarkProps {
compact?: boolean;
}
export function BrandMark({ compact = false }: BrandMarkProps) {
return (
<span className="inline-flex items-center gap-2.5 text-slate-950">
<span className="inline-grid h-8 w-8 place-items-center rounded-md border-2 border-blue-600 bg-white text-blue-600">
<Mail className="h-5 w-5" aria-hidden="true" />
</span>
{!compact && <span className="text-xl font-extrabold tracking-normal">InboxConverge</span>}
</span>
);
}
+49 -48
View File
@@ -6,6 +6,7 @@ import { usePathname, useRouter } from 'next/navigation';
import { useQuery } from '@tanstack/react-query';
import { useAuthStore } from '@/store/authStore';
import { versionApi } from '@/lib/api';
import { BrandMark } from '@/components/BrandMark';
import {
LayoutDashboard,
Mail,
@@ -63,35 +64,35 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
const allNavItems = [...navigation, ...adminNavigation];
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-[#f7faff] text-slate-900">
{/* Sidebar for desktop */}
<div className="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col">
<div className="flex flex-col flex-grow bg-white border-r border-gray-200">
<div className="flex items-center h-16 flex-shrink-0 px-4 border-b border-gray-200">
<h1 className="text-xl font-bold text-gray-900">InboxConverge</h1>
<div className="flex flex-col flex-grow border-r border-[#d9e3f2] bg-white/95 shadow-[14px_0_42px_rgba(16,33,63,0.04)]">
<div className="flex h-18 flex-shrink-0 items-center px-5 border-b border-[#d9e3f2]">
<BrandMark />
</div>
<nav className="flex-1 px-2 py-4 space-y-1">
<nav className="flex-1 px-3 py-5 space-y-1">
{navigation.map((item) => {
const isActive = pathname === item.href;
return (
<Link
key={item.name}
href={item.href}
className={`flex items-center px-4 py-2 text-sm font-medium rounded-md transition-colors ${
className={`flex items-center px-3.5 py-2.5 text-sm font-semibold rounded-lg transition-colors ${
isActive
? 'bg-blue-50 text-blue-600'
: 'text-gray-700 hover:bg-gray-50 hover:text-gray-900'
? 'bg-[#e7f0ff] text-[#0649bf]'
: 'text-slate-600 hover:bg-[#f7faff] hover:text-slate-950'
}`}
>
<item.icon className={`mr-3 h-5 w-5 ${isActive ? 'text-blue-600' : 'text-gray-400'}`} />
<item.icon className={`mr-3 h-5 w-5 ${isActive ? 'text-[#0b63f6]' : 'text-slate-400'}`} />
{item.name}
</Link>
);
})}
{adminNavigation.length > 0 && (
<>
<div className="pt-4 pb-1 px-4">
<p className="text-xs font-semibold text-gray-400 uppercase tracking-wider">Admin</p>
<div className="pt-5 pb-1 px-3.5">
<p className="text-xs font-bold uppercase text-slate-400">Admin</p>
</div>
{adminNavigation.map((item) => {
const isActive = pathname === item.href || pathname.startsWith(item.href + '/');
@@ -99,13 +100,13 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
<Link
key={item.name}
href={item.href}
className={`flex items-center px-4 py-2 text-sm font-medium rounded-md transition-colors ${
className={`flex items-center px-3.5 py-2.5 text-sm font-semibold rounded-lg transition-colors ${
isActive
? 'bg-purple-50 text-purple-600'
: 'text-gray-700 hover:bg-gray-50 hover:text-gray-900'
? 'bg-[#e8f7ef] text-[#11834d]'
: 'text-slate-600 hover:bg-[#f7faff] hover:text-slate-950'
}`}
>
<item.icon className={`mr-3 h-5 w-5 ${isActive ? 'text-purple-600' : 'text-gray-400'}`} />
<item.icon className={`mr-3 h-5 w-5 ${isActive ? 'text-[#11834d]' : 'text-slate-400'}`} />
{item.name}
</Link>
);
@@ -113,12 +114,12 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
</>
)}
</nav>
<div className="flex-shrink-0 border-t border-gray-200 p-4">
<div className="flex-shrink-0 border-t border-[#d9e3f2] p-4">
<button
onClick={handleLogout}
className="flex items-center w-full px-4 py-2 text-sm font-medium text-gray-700 rounded-md hover:bg-gray-50 hover:text-gray-900 transition-colors"
className="flex items-center w-full px-3.5 py-2.5 text-sm font-semibold text-slate-600 rounded-lg hover:bg-[#f7faff] hover:text-slate-950 transition-colors"
>
<LogOut className="mr-3 h-5 w-5 text-gray-400" />
<LogOut className="mr-3 h-5 w-5 text-slate-400" />
Logout
</button>
</div>
@@ -128,15 +129,15 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
{/* Mobile sidebar */}
{sidebarOpen && (
<div className="fixed inset-0 z-40 lg:hidden">
<div className="fixed inset-0 bg-gray-600/75" onClick={() => setSidebarOpen(false)} />
<div className="fixed inset-y-0 left-0 flex w-64 flex-col bg-white">
<div className="flex items-center justify-between h-16 px-4 border-b border-gray-200">
<h1 className="text-xl font-bold text-gray-900">InboxConverge</h1>
<button onClick={() => setSidebarOpen(false)} className="text-gray-500 hover:text-gray-700">
<div className="fixed inset-0 bg-slate-900/50" onClick={() => setSidebarOpen(false)} />
<div className="fixed inset-y-0 left-0 flex w-72 flex-col bg-white">
<div className="flex items-center justify-between h-18 px-5 border-b border-[#d9e3f2]">
<BrandMark />
<button onClick={() => setSidebarOpen(false)} className="text-slate-500 hover:text-slate-700">
<X className="h-6 w-6" />
</button>
</div>
<nav className="flex-1 px-2 py-4 space-y-1">
<nav className="flex-1 px-3 py-5 space-y-1">
{navigation.map((item) => {
const isActive = pathname === item.href;
return (
@@ -144,21 +145,21 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
key={item.name}
href={item.href}
onClick={() => setSidebarOpen(false)}
className={`flex items-center px-4 py-2 text-sm font-medium rounded-md transition-colors ${
className={`flex items-center px-3.5 py-2.5 text-sm font-semibold rounded-lg transition-colors ${
isActive
? 'bg-blue-50 text-blue-600'
: 'text-gray-700 hover:bg-gray-50 hover:text-gray-900'
? 'bg-[#e7f0ff] text-[#0649bf]'
: 'text-slate-600 hover:bg-[#f7faff] hover:text-slate-950'
}`}
>
<item.icon className={`mr-3 h-5 w-5 ${isActive ? 'text-blue-600' : 'text-gray-400'}`} />
<item.icon className={`mr-3 h-5 w-5 ${isActive ? 'text-[#0b63f6]' : 'text-slate-400'}`} />
{item.name}
</Link>
);
})}
{adminNavigation.length > 0 && (
<>
<div className="pt-4 pb-1 px-4">
<p className="text-xs font-semibold text-gray-400 uppercase tracking-wider">Admin</p>
<div className="pt-5 pb-1 px-3.5">
<p className="text-xs font-bold uppercase text-slate-400">Admin</p>
</div>
{adminNavigation.map((item) => {
const isActive = pathname === item.href || pathname.startsWith(item.href + '/');
@@ -167,13 +168,13 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
key={item.name}
href={item.href}
onClick={() => setSidebarOpen(false)}
className={`flex items-center px-4 py-2 text-sm font-medium rounded-md transition-colors ${
className={`flex items-center px-3.5 py-2.5 text-sm font-semibold rounded-lg transition-colors ${
isActive
? 'bg-purple-50 text-purple-600'
: 'text-gray-700 hover:bg-gray-50 hover:text-gray-900'
? 'bg-[#e8f7ef] text-[#11834d]'
: 'text-slate-600 hover:bg-[#f7faff] hover:text-slate-950'
}`}
>
<item.icon className={`mr-3 h-5 w-5 ${isActive ? 'text-purple-600' : 'text-gray-400'}`} />
<item.icon className={`mr-3 h-5 w-5 ${isActive ? 'text-[#11834d]' : 'text-slate-400'}`} />
{item.name}
</Link>
);
@@ -181,12 +182,12 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
</>
)}
</nav>
<div className="flex-shrink-0 border-t border-gray-200 p-4">
<div className="flex-shrink-0 border-t border-[#d9e3f2] p-4">
<button
onClick={handleLogout}
className="flex items-center w-full px-4 py-2 text-sm font-medium text-gray-700 rounded-md hover:bg-gray-50 hover:text-gray-900 transition-colors"
className="flex items-center w-full px-3.5 py-2.5 text-sm font-semibold text-slate-600 rounded-lg hover:bg-[#f7faff] hover:text-slate-950 transition-colors"
>
<LogOut className="mr-3 h-5 w-5 text-gray-400" />
<LogOut className="mr-3 h-5 w-5 text-slate-400" />
Logout
</button>
</div>
@@ -197,28 +198,28 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
{/* Main content */}
<div className="lg:pl-64 flex flex-col flex-1">
{/* Top bar */}
<div className="sticky top-0 z-10 flex h-16 flex-shrink-0 bg-white border-b border-gray-200">
<div className="sticky top-0 z-10 flex h-16 flex-shrink-0 border-b border-[#d9e3f2] bg-white/90 backdrop-blur-xl">
<button
type="button"
className="border-r border-gray-200 px-4 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500 lg:hidden"
className="border-r border-[#d9e3f2] px-4 text-slate-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500 lg:hidden"
onClick={() => setSidebarOpen(true)}
>
<Menu className="h-6 w-6" />
</button>
<div className="flex flex-1 justify-between px-4 sm:px-6 lg:px-8">
<div className="flex flex-1 items-center">
<h2 className="text-lg font-semibold text-gray-900">
<h2 className="text-lg font-bold text-slate-950">
{allNavItems.find((item) => item.href === pathname)?.name || 'Dashboard'}
</h2>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className={`flex h-8 w-8 items-center justify-center rounded-full text-white ${user?.is_superuser ? 'bg-purple-600' : 'bg-blue-600'}`}>
<div className={`flex h-8 w-8 items-center justify-center rounded-md text-white ${user?.is_superuser ? 'bg-[#11834d]' : 'bg-[#0b63f6]'}`}>
{user?.is_superuser ? <Shield className="h-4 w-4" /> : <User className="h-4 w-4" />}
</div>
<div className="hidden sm:block">
<p className="text-sm font-medium text-gray-900">{user?.full_name}</p>
<p className="text-xs text-gray-500">
<p className="text-sm font-semibold text-slate-950">{user?.full_name}</p>
<p className="text-xs text-slate-500">
{user?.email}
{user?.is_superuser && (
<span className="ml-1 inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-700">
@@ -234,7 +235,7 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
{/* Page content */}
<main className="flex-1">
<div className="py-6">
<div className="py-7">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{children}
</div>
@@ -242,13 +243,13 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
</main>
{/* Footer */}
<footer className="border-t border-gray-200 bg-white py-4 px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-7xl flex flex-wrap items-center justify-between gap-2 text-xs text-gray-400">
<footer className="border-t border-[#d9e3f2] bg-white/80 py-4 px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-7xl flex flex-wrap items-center justify-between gap-2 text-xs text-slate-400">
<div className="flex flex-wrap gap-4">
<Link href="/impressum" className="hover:text-gray-600 transition-colors">
<Link href="/impressum" className="hover:text-slate-600 transition-colors">
Impressum
</Link>
<Link href="/datenschutz" className="hover:text-gray-600 transition-colors">
<Link href="/datenschutz" className="hover:text-slate-600 transition-colors">
Datenschutz
</Link>
</div>