Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Global startup loading modal shown while Matrix session restore is in progress
- Global auth middleware for protected routes with restore-aware redirect checks
- Unit coverage for restore-state transitions and global auth middleware
- Root landing page on `/` with hero content, feature cards, and a download
call-to-action placeholder
- Dedicated landing logo background assets (`logoBg.svg`) for the root route
- Unit tests for root-page restore and redirect behavior in
`tests/unit/pages/index.spec.ts`

### Changed

Expand All @@ -74,6 +79,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
size and improve composable reuse
- Root route startup behavior now waits for restore completion before login/chat
navigation decisions
- Root route now keeps restore-aware `watchEffect` gating while rendering
landing content for unauthenticated users
- Landing copy and i18n keys were aligned with the finalized Issue-41 hero and
feature text
- Landing background logo loading now uses a bundler URL import to avoid
runtime path resolution issues

### Fixed

Expand Down
77 changes: 77 additions & 0 deletions app/assets/logoBg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions app/composables/useAppI18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@ const messages: Record<AppLocale, Record<string, string>> = {
'spaces.name': 'Space name',
'spaces.createStub': 'Create (stub)',
'spaces.stubInfo': 'Real server-side creation will follow in a later step.',
'landing.kicker': 'OPEN AND FEDERATED TEAM CHAT',
'landing.title': 'Own your collaboration with Decentra.',
'landing.subtitle': 'Keep communication in your control while staying connected through Matrix.',
'landing.loginCta': 'Sign in',
'landing.chatCta': 'Open chat',
'landing.featureOneTitle': 'Federated by default',
'landing.featureOneText': 'Use Matrix homeservers to connect teams without vendor lock-in.',
'landing.featureTwoTitle': 'Focus on productive channels',
'landing.featureTwoText': 'Organize spaces, channels and replies in a clean, fast interface.',
'landing.featureThreeTitle': 'Built for privacy-minded teams',
'landing.featureThreeText': 'Decentra keeps your communication choices transparent and portable.',
'landing.downloadTitle': 'Native app download',
'landing.downloadPlaceholder': 'Desktop and mobile installers will arrive in a future phase.',
'landing.downloadCta': 'Download coming soon',
'landing.downloadIos': 'iOS (soon)',
'landing.downloadAndroid': 'Android (soon)',
'common.loading': 'Loading...'
},
de: {
Expand Down Expand Up @@ -175,6 +191,22 @@ const messages: Record<AppLocale, Record<string, string>> = {
'spaces.name': 'Space-Name',
'spaces.createStub': 'Erstellen (Stub)',
'spaces.stubInfo': 'Echte Server-Erstellung folgt spaeter.',
'landing.kicker': 'OPEN AND FEDERATED TEAM CHAT',
'landing.title': 'Own your collaboration with Decentra.',
'landing.subtitle': 'Keep communication in your control while staying connected through Matrix.',
'landing.loginCta': 'Anmelden',
'landing.chatCta': 'Chat oeffnen',
'landing.featureOneTitle': 'Foederiert von Anfang an',
'landing.featureOneText': 'Nutze Matrix-Homeserver fuer Team-Chats ohne Vendor-Lock-in.',
'landing.featureTwoTitle': 'Fokus auf produktive Kanaele',
'landing.featureTwoText': 'Organize spaces, channels and replies in a clean, fast interface.',
'landing.featureThreeTitle': 'Fuer privacy-orientierte Teams gebaut',
'landing.featureThreeText': 'Decentra haelt Kommunikationsentscheidungen transparent und portabel.',
'landing.downloadTitle': 'Native app download',
'landing.downloadPlaceholder': 'Desktop and mobile installers will arrive in a future phase.',
'landing.downloadCta': 'Download folgt bald',
'landing.downloadIos': 'iOS (bald)',
'landing.downloadAndroid': 'Android (bald)',
'common.loading': 'Lade...'
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/pages/chat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ async function onLoadOlder() {

function handleLogout() {
logout();
navigateTo("/login");
navigateTo("/");
}

function selectSpace(spaceId: string) {
Expand Down
114 changes: 99 additions & 15 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { useAppI18n } from '~/composables/useAppI18n'
import logoBgUrl from '~/assets/logoBg.svg?url'

const { translateText } = useAppI18n()
const {
Expand All @@ -18,22 +19,105 @@ watchEffect(() => {
</script>

<template>
<div class="flex min-h-screen items-center justify-center">
<span
<div class="min-h-screen bg-gray-950 text-gray-100">
<div
v-if="!isSessionRestoreFinished"
class="text-gray-500"
class="flex min-h-screen items-center justify-center"
>
{{ translateText('common.loading') }}
</span>
<UCard v-else class="w-full max-w-sm">
<div class="space-y-4 text-center">
<p class="text-sm text-gray-600 dark:text-gray-300">
Decentra
</p>
<UButton to="/login" block>
{{ translateText('auth.signIn') }}
</UButton>
</div>
</UCard>
<span class="text-gray-500">
{{ translateText('common.loading') }}
</span>
</div>
<div v-else class="min-h-screen">
<header
class="border-b border-gray-200/70 bg-white/90 backdrop-blur dark:border-gray-800 dark:bg-gray-900/85"
>
<div class="mx-auto flex h-16 w-full max-w-6xl items-center justify-between px-6">
<p class="text-lg font-semibold text-white/70 dark:text-white/80">
Decentra
</p>
<UButton to="/login" color="primary">
{{ translateText('auth.signIn') }}
</UButton>
</div>
</header>

<main class="mx-auto w-full max-w-6xl px-6 py-12 md:py-14">
<section
class="relative overflow-hidden rounded-2xl border border-white/10 p-8 md:p-10"
>
<img
:src="logoBgUrl"
alt=""
aria-hidden="true"
class="pointer-events-none absolute left-1/2 top-1/2 w-[100%] max-w-[1100px] -translate-x-1/2 -translate-y-1/2 opacity-[0.16]"
>
<div class="relative z-10 space-y-4">
<p class="text-sm font-semibold uppercase tracking-wide text-primary-400">
{{ translateText('landing.kicker') }}
</p>
<h1 class="max-w-2xl text-4xl font-bold leading-tight text-white md:text-6xl">
{{ translateText('landing.title') }}
</h1>
<p class="max-w-2xl text-base text-gray-300 md:text-lg">
{{ translateText('landing.subtitle') }}
</p>

<div class="grid gap-4 pt-4 md:grid-cols-3">
<UCard class="bg-white/5 ring-1 ring-white/10">
<div class="space-y-2">
<h2 class="text-base font-semibold text-white">
{{ translateText('landing.featureOneTitle') }}
</h2>
<p class="text-sm text-gray-300">
{{ translateText('landing.featureOneText') }}
</p>
</div>
</UCard>
<UCard class="bg-white/5 ring-1 ring-white/10">
<div class="space-y-2">
<h2 class="text-base font-semibold text-white">
{{ translateText('landing.featureTwoTitle') }}
</h2>
<p class="text-sm text-gray-300">
{{ translateText('landing.featureTwoText') }}
</p>
</div>
</UCard>
<UCard class="bg-white/5 ring-1 ring-white/10">
<div class="space-y-2">
<h2 class="text-base font-semibold text-white">
{{ translateText('landing.featureThreeTitle') }}
</h2>
<p class="text-sm text-gray-300">
{{ translateText('landing.featureThreeText') }}
</p>
</div>
</UCard>
</div>

<UCard
class="mt-4 bg-white/5 ring-1 ring-white/10"
:ui="{ body: 'p-4 sm:p-6 my-2.5' }"
>
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h2 class="text-lg font-semibold text-white">
{{ translateText('landing.downloadTitle') }}
</h2>
<p class="text-sm text-gray-300">
{{ translateText('landing.downloadPlaceholder') }}
</p>
</div>
<UButton color="neutral" variant="outline" disabled>
{{ translateText('landing.downloadCta') }}
</UButton>
</div>
</UCard>

</div>
</section>
</main>
</div>
</div>
</template>
8 changes: 8 additions & 0 deletions app/pages/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ async function handleLogin() {
>
{{ translateText('auth.signIn') }}
</UButton>
<UButton
to="/"
block
variant="ghost"
color="neutral"
>
{{ translateText('layout.homeSpace') }}
</UButton>
</form>
</UCard>
</div>
Expand Down
89 changes: 89 additions & 0 deletions public/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading