feat: signal server tag
This commit is contained in:
@@ -1,408 +1,418 @@
|
||||
<ng-template #pageContent>
|
||||
<div class="h-full min-h-0 overflow-y-auto bg-background text-foreground">
|
||||
<div class="mx-auto w-full max-w-5xl space-y-8 p-4 sm:p-6 lg:py-8">
|
||||
<header class="space-y-1">
|
||||
<h1 class="text-2xl font-semibold text-foreground">
|
||||
@if (currentUser()) {
|
||||
Welcome back, {{ currentUser()!.displayName || 'there' }}
|
||||
} @else {
|
||||
Welcome to MetoYou
|
||||
}
|
||||
</h1>
|
||||
<p class="text-sm text-muted-foreground">Find people, discover servers, or start your own community.</p>
|
||||
</header>
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<ng-icon
|
||||
name="lucideSearch"
|
||||
class="absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground"
|
||||
/>
|
||||
<input
|
||||
#searchInput
|
||||
type="text"
|
||||
aria-label="Search people, servers, and invites"
|
||||
class="h-12 w-full rounded-xl border border-border bg-secondary py-2 pl-11 pr-20 text-base text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
placeholder="Search for people, servers, or paste an invite..."
|
||||
[ngModel]="searchQuery()"
|
||||
(ngModelChange)="onSearchChange($event)"
|
||||
(keydown.enter)="submitSearch()"
|
||||
/>
|
||||
<kbd class="pointer-events-none absolute right-3 top-1/2 hidden -translate-y-1/2 items-center gap-1 rounded border border-border bg-card px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground sm:flex">
|
||||
Ctrl K
|
||||
</kbd>
|
||||
</div>
|
||||
|
||||
@if (!isSearchMode() && recentSearches().length > 0) {
|
||||
<div class="mt-3 flex flex-wrap items-center gap-2">
|
||||
<span class="text-xs font-medium text-muted-foreground">Recent:</span>
|
||||
@for (term of recentSearches(); track term) {
|
||||
<span class="group inline-flex items-center gap-1 rounded-full border border-border bg-secondary py-1 pl-3 pr-1 text-xs text-foreground">
|
||||
<button
|
||||
type="button"
|
||||
class="max-w-[10rem] truncate hover:text-primary"
|
||||
(click)="applyRecentSearch(term)"
|
||||
>
|
||||
{{ term }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="grid h-4 w-4 place-items-center rounded-full text-muted-foreground hover:bg-card hover:text-foreground"
|
||||
[attr.aria-label]="'Remove ' + term"
|
||||
(click)="removeRecentSearch(term)"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideX"
|
||||
class="h-3 w-3"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
<div class="h-full min-h-0 overflow-y-auto bg-background text-foreground">
|
||||
<div class="mx-auto w-full max-w-5xl space-y-8 p-4 sm:p-6 lg:py-8">
|
||||
<header class="space-y-1">
|
||||
<h1 class="text-2xl font-semibold text-foreground">
|
||||
@if (currentUser()) {
|
||||
Welcome back, {{ currentUser()!.displayName || 'there' }}
|
||||
} @else {
|
||||
Welcome to MetoYou
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="text-xs font-medium text-muted-foreground hover:text-foreground hover:underline"
|
||||
(click)="clearRecentSearches()"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</h1>
|
||||
<p class="text-sm text-muted-foreground">Find people, discover servers, or start your own community.</p>
|
||||
</header>
|
||||
|
||||
@if (isSearchMode()) {
|
||||
<section class="space-y-5">
|
||||
@if (inviteResult(); as invite) {
|
||||
<div>
|
||||
<h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">Invite</h2>
|
||||
<div>
|
||||
<div class="relative">
|
||||
<ng-icon
|
||||
name="lucideSearch"
|
||||
class="absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground"
|
||||
/>
|
||||
<input
|
||||
#searchInput
|
||||
type="text"
|
||||
aria-label="Search people, servers, and invites"
|
||||
class="h-12 w-full rounded-xl border border-border bg-secondary py-2 pl-11 pr-20 text-base text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
placeholder="Search for people, servers, or paste an invite..."
|
||||
[ngModel]="searchQuery()"
|
||||
(ngModelChange)="onSearchChange($event)"
|
||||
(keydown.enter)="submitSearch()"
|
||||
/>
|
||||
<kbd
|
||||
class="pointer-events-none absolute right-3 top-1/2 hidden -translate-y-1/2 items-center gap-1 rounded border border-border bg-card px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground sm:flex"
|
||||
>
|
||||
Ctrl K
|
||||
</kbd>
|
||||
</div>
|
||||
|
||||
@if (!isSearchMode() && recentSearches().length > 0) {
|
||||
<div class="mt-3 flex flex-wrap items-center gap-2">
|
||||
<span class="text-xs font-medium text-muted-foreground">Recent:</span>
|
||||
@for (term of recentSearches(); track term) {
|
||||
<span
|
||||
class="group inline-flex items-center gap-1 rounded-full border border-border bg-secondary py-1 pl-3 pr-1 text-xs text-foreground"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="max-w-[10rem] truncate hover:text-primary"
|
||||
(click)="applyRecentSearch(term)"
|
||||
>
|
||||
{{ term }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="grid h-4 w-4 place-items-center rounded-full text-muted-foreground hover:bg-card hover:text-foreground"
|
||||
[attr.aria-label]="'Remove ' + term"
|
||||
(click)="removeRecentSearch(term)"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideX"
|
||||
class="h-3 w-3"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center gap-3 rounded-lg border border-border bg-card p-3 text-left transition-colors hover:border-primary/50 hover:bg-card/80"
|
||||
(click)="openInvite()"
|
||||
class="text-xs font-medium text-muted-foreground hover:text-foreground hover:underline"
|
||||
(click)="clearRecentSearches()"
|
||||
>
|
||||
<div class="grid h-10 w-10 shrink-0 place-items-center rounded-lg bg-primary/10 text-primary">
|
||||
<ng-icon
|
||||
name="lucideTicket"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-semibold text-foreground">Open invite</p>
|
||||
<p class="truncate text-xs text-muted-foreground">{{ invite }}</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
class="h-4 w-4 text-muted-foreground"
|
||||
/>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (topServerResults().length > 0) {
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Servers</h2>
|
||||
<a
|
||||
routerLink="/servers"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>View all</a
|
||||
@if (isSearchMode()) {
|
||||
<section class="space-y-5">
|
||||
@if (inviteResult(); as invite) {
|
||||
<div>
|
||||
<h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">Invite</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center gap-3 rounded-lg border border-border bg-card p-3 text-left transition-colors hover:border-primary/50 hover:bg-card/80"
|
||||
(click)="openInvite()"
|
||||
>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
@for (server of topServerResults(); track server.id) {
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center gap-3 rounded-lg border border-border bg-card p-3 text-left transition-colors hover:border-primary/50 hover:bg-card/80"
|
||||
(click)="openServer(server)"
|
||||
>
|
||||
<div class="grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-lg bg-secondary text-sm font-semibold text-foreground">
|
||||
@if (server.icon) {
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="h-full w-full bg-cover bg-center bg-no-repeat"
|
||||
[style.backgroundImage]="'url(' + server.icon + ')'"
|
||||
></div>
|
||||
} @else {
|
||||
{{ serverInitial(server) }}
|
||||
}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-semibold text-foreground">{{ server.name }}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">{{ serverMetaLabel(server) }}</p>
|
||||
</div>
|
||||
<div class="grid h-10 w-10 shrink-0 place-items-center rounded-lg bg-primary/10 text-primary">
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
class="h-4 w-4 text-muted-foreground"
|
||||
name="lucideTicket"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-semibold text-foreground">Open invite</p>
|
||||
<p class="truncate text-xs text-muted-foreground">{{ invite }}</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
class="h-4 w-4 text-muted-foreground"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (topServerResults().length > 0) {
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Servers</h2>
|
||||
<a
|
||||
routerLink="/servers"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>View all</a
|
||||
>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
@for (server of topServerResults(); track server.id) {
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center gap-3 rounded-lg border border-border bg-card p-3 text-left transition-colors hover:border-primary/50 hover:bg-card/80"
|
||||
(click)="openServer(server)"
|
||||
>
|
||||
<div
|
||||
class="grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-lg bg-secondary text-sm font-semibold text-foreground"
|
||||
>
|
||||
@if (server.icon) {
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="h-full w-full bg-cover bg-center bg-no-repeat"
|
||||
[style.backgroundImage]="'url(' + server.icon + ')'"
|
||||
></div>
|
||||
} @else {
|
||||
{{ serverInitial(server) }}
|
||||
}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-semibold text-foreground">{{ server.name }}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">{{ serverMetaLabel(server) }}</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
class="h-4 w-4 text-muted-foreground"
|
||||
/>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (topPeopleResults().length > 0) {
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">People</h2>
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>View all</a
|
||||
>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
@for (person of topPeopleResults(); track person.id) {
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="flex w-full items-center gap-3 rounded-lg border border-border bg-card p-3 text-left transition-colors hover:border-primary/50 hover:bg-card/80"
|
||||
>
|
||||
<app-user-avatar
|
||||
[name]="personLabel(person)"
|
||||
[avatarUrl]="person.avatarUrl"
|
||||
size="md"
|
||||
[status]="person.status"
|
||||
[showStatusBadge]="true"
|
||||
/>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-semibold text-foreground">{{ personLabel(person) }}</p>
|
||||
<p class="text-xs text-muted-foreground">{{ isOnline(person) ? 'Online' : 'Offline' }}</p>
|
||||
</div>
|
||||
<app-friend-button [user]="person" />
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (hasNoQuickResults() && !isSearching()) {
|
||||
<div class="rounded-lg border border-border bg-card px-4 py-8 text-center text-sm text-muted-foreground">
|
||||
No people, servers, or invites match
|
||||
<span class="font-medium text-foreground">{{ searchQuery() }}</span
|
||||
>.
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
} @else {
|
||||
<!-- Primary actions -->
|
||||
<section class="grid gap-3 sm:grid-cols-3">
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="group flex items-center gap-3 rounded-xl border border-border bg-card p-4 transition-colors hover:border-primary/40 hover:bg-card/80"
|
||||
>
|
||||
<div class="grid h-11 w-11 shrink-0 place-items-center rounded-lg bg-purple-500/15 text-purple-400">
|
||||
<ng-icon
|
||||
name="lucideUsers"
|
||||
class="h-6 w-6"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-semibold text-foreground">Find People</p>
|
||||
<p class="mt-0.5 text-xs text-muted-foreground">Connect with friends.</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
class="h-4 w-4 text-muted-foreground transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a
|
||||
routerLink="/servers"
|
||||
class="group flex items-center gap-3 rounded-xl border border-border bg-card p-4 transition-colors hover:border-primary/40 hover:bg-card/80"
|
||||
>
|
||||
<div class="grid h-11 w-11 shrink-0 place-items-center rounded-lg bg-blue-500/15 text-blue-400">
|
||||
<ng-icon
|
||||
name="lucideCompass"
|
||||
class="h-6 w-6"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-semibold text-foreground">Find Servers</p>
|
||||
<p class="mt-0.5 text-xs text-muted-foreground">Browse communities.</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
class="h-4 w-4 text-muted-foreground transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a
|
||||
routerLink="/create-server"
|
||||
class="group flex items-center gap-3 rounded-xl border border-emerald-500/40 bg-emerald-500/10 p-4 transition-colors hover:bg-emerald-500/15"
|
||||
>
|
||||
<div class="grid h-11 w-11 shrink-0 place-items-center rounded-lg bg-emerald-500/20 text-emerald-400">
|
||||
<ng-icon
|
||||
name="lucidePlus"
|
||||
class="h-6 w-6"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-semibold text-foreground">Create Server</p>
|
||||
<p class="mt-0.5 text-xs text-muted-foreground">Start your own.</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
class="h-4 w-4 text-emerald-400 transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
@if (isNewUser()) {
|
||||
<section class="rounded-xl border border-border bg-card p-6 text-center">
|
||||
<div class="mx-auto mb-3 grid h-12 w-12 place-items-center rounded-full bg-secondary">
|
||||
<ng-icon
|
||||
name="lucideServer"
|
||||
class="h-6 w-6 text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
<h2 class="text-base font-semibold text-foreground">Get started</h2>
|
||||
<p class="mx-auto mt-1 max-w-sm text-sm text-muted-foreground">
|
||||
You have not joined any servers yet. Find a community to join, or create your own to invite friends.
|
||||
</p>
|
||||
</section>
|
||||
}
|
||||
|
||||
@if (topPeopleResults().length > 0) {
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">People</h2>
|
||||
<!-- People + Popular servers -->
|
||||
<section class="grid gap-4 lg:grid-cols-2">
|
||||
<div class="rounded-xl border border-border bg-card/40 p-4">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-foreground">People you might know</h2>
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>View all</a
|
||||
>See all</a
|
||||
>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
@for (person of topPeopleResults(); track person.id) {
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="flex w-full items-center gap-3 rounded-lg border border-border bg-card p-3 text-left transition-colors hover:border-primary/50 hover:bg-card/80"
|
||||
>
|
||||
<app-user-avatar
|
||||
[name]="personLabel(person)"
|
||||
[avatarUrl]="person.avatarUrl"
|
||||
size="md"
|
||||
[status]="person.status"
|
||||
[showStatusBadge]="true"
|
||||
/>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-semibold text-foreground">{{ personLabel(person) }}</p>
|
||||
<p class="text-xs text-muted-foreground">{{ isOnline(person) ? 'Online' : 'Offline' }}</p>
|
||||
@if (peopleYouMightKnow().length > 0) {
|
||||
<div class="space-y-1">
|
||||
@for (person of peopleYouMightKnow(); track person.id) {
|
||||
<div class="flex items-center gap-3 rounded-lg px-2 py-2 transition-colors hover:bg-secondary/60">
|
||||
<app-user-avatar
|
||||
[name]="personLabel(person)"
|
||||
[avatarUrl]="person.avatarUrl"
|
||||
size="md"
|
||||
[status]="person.status"
|
||||
[showStatusBadge]="true"
|
||||
/>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium text-foreground">{{ personLabel(person) }}</p>
|
||||
<p class="text-xs text-muted-foreground">{{ isOnline(person) ? 'Online' : 'Offline' }}</p>
|
||||
</div>
|
||||
<app-friend-button [user]="person" />
|
||||
</div>
|
||||
<app-friend-button [user]="person" />
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<p class="py-6 text-center text-sm text-muted-foreground">No people to suggest yet.</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-border bg-card/40 p-4">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-foreground">Popular Servers</h2>
|
||||
<a
|
||||
routerLink="/servers"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>See all</a
|
||||
>
|
||||
</div>
|
||||
@if (popularServers().length > 0) {
|
||||
<div class="space-y-1">
|
||||
@for (server of popularServers(); track server.id) {
|
||||
<div class="flex items-center gap-3 rounded-lg px-2 py-2 transition-colors hover:bg-secondary/60">
|
||||
<div
|
||||
class="grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-lg bg-secondary text-sm font-semibold text-foreground"
|
||||
>
|
||||
@if (server.icon) {
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="h-full w-full bg-cover bg-center bg-no-repeat"
|
||||
[style.backgroundImage]="'url(' + server.icon + ')'"
|
||||
></div>
|
||||
} @else {
|
||||
{{ serverInitial(server) }}
|
||||
}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium text-foreground">{{ server.name }}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">{{ serverMetaLabel(server) }}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="shrink-0 rounded-md bg-primary px-3 py-1.5 text-xs font-semibold text-primary-foreground transition-colors hover:bg-primary/90"
|
||||
(click)="openServer(server)"
|
||||
>
|
||||
Join
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<p class="py-6 text-center text-sm text-muted-foreground">No popular servers right now.</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (hasNoQuickResults() && !isSearching()) {
|
||||
<div class="rounded-lg border border-border bg-card px-4 py-8 text-center text-sm text-muted-foreground">
|
||||
No people, servers, or invites match
|
||||
<span class="font-medium text-foreground">{{ searchQuery() }}</span
|
||||
>.
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
} @else {
|
||||
<!-- Primary actions -->
|
||||
<section class="grid gap-3 sm:grid-cols-3">
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="group flex items-center gap-3 rounded-xl border border-border bg-card p-4 transition-colors hover:border-primary/40 hover:bg-card/80"
|
||||
>
|
||||
<div class="grid h-11 w-11 shrink-0 place-items-center rounded-lg bg-purple-500/15 text-purple-400">
|
||||
<ng-icon
|
||||
name="lucideUsers"
|
||||
class="h-6 w-6"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-semibold text-foreground">Find People</p>
|
||||
<p class="mt-0.5 text-xs text-muted-foreground">Connect with friends.</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
class="h-4 w-4 text-muted-foreground transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a
|
||||
routerLink="/servers"
|
||||
class="group flex items-center gap-3 rounded-xl border border-border bg-card p-4 transition-colors hover:border-primary/40 hover:bg-card/80"
|
||||
>
|
||||
<div class="grid h-11 w-11 shrink-0 place-items-center rounded-lg bg-blue-500/15 text-blue-400">
|
||||
<ng-icon
|
||||
name="lucideCompass"
|
||||
class="h-6 w-6"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-semibold text-foreground">Find Servers</p>
|
||||
<p class="mt-0.5 text-xs text-muted-foreground">Browse communities.</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
class="h-4 w-4 text-muted-foreground transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a
|
||||
routerLink="/create-server"
|
||||
class="group flex items-center gap-3 rounded-xl border border-emerald-500/40 bg-emerald-500/10 p-4 transition-colors hover:bg-emerald-500/15"
|
||||
>
|
||||
<div class="grid h-11 w-11 shrink-0 place-items-center rounded-lg bg-emerald-500/20 text-emerald-400">
|
||||
<ng-icon
|
||||
name="lucidePlus"
|
||||
class="h-6 w-6"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-semibold text-foreground">Create Server</p>
|
||||
<p class="mt-0.5 text-xs text-muted-foreground">Start your own.</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
class="h-4 w-4 text-emerald-400 transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
@if (isNewUser()) {
|
||||
<section class="rounded-xl border border-border bg-card p-6 text-center">
|
||||
<div class="mx-auto mb-3 grid h-12 w-12 place-items-center rounded-full bg-secondary">
|
||||
<ng-icon
|
||||
name="lucideServer"
|
||||
class="h-6 w-6 text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
<h2 class="text-base font-semibold text-foreground">Get started</h2>
|
||||
<p class="mx-auto mt-1 max-w-sm text-sm text-muted-foreground">
|
||||
You have not joined any servers yet. Find a community to join, or create your own to invite friends.
|
||||
</p>
|
||||
</section>
|
||||
}
|
||||
|
||||
<!-- People + Popular servers -->
|
||||
<section class="grid gap-4 lg:grid-cols-2">
|
||||
<div class="rounded-xl border border-border bg-card/40 p-4">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-foreground">People you might know</h2>
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>See all</a
|
||||
>
|
||||
</div>
|
||||
@if (peopleYouMightKnow().length > 0) {
|
||||
<div class="space-y-1">
|
||||
@for (person of peopleYouMightKnow(); track person.id) {
|
||||
<div class="flex items-center gap-3 rounded-lg px-2 py-2 transition-colors hover:bg-secondary/60">
|
||||
<!-- Your friends -->
|
||||
@if (friends().length > 0) {
|
||||
<section>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-foreground">Your Friends</h2>
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>Manage</a
|
||||
>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@for (friend of friends(); track friend.id) {
|
||||
<div class="flex items-center gap-3 rounded-xl border border-border bg-card p-3">
|
||||
<app-user-avatar
|
||||
[name]="personLabel(person)"
|
||||
[avatarUrl]="person.avatarUrl"
|
||||
[name]="personLabel(friend)"
|
||||
[avatarUrl]="friend.avatarUrl"
|
||||
size="md"
|
||||
[status]="person.status"
|
||||
[status]="friend.status"
|
||||
[showStatusBadge]="true"
|
||||
/>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium text-foreground">{{ personLabel(person) }}</p>
|
||||
<p class="text-xs text-muted-foreground">{{ isOnline(person) ? 'Online' : 'Offline' }}</p>
|
||||
<p class="truncate text-sm font-medium text-foreground">{{ personLabel(friend) }}</p>
|
||||
<p class="text-xs text-muted-foreground">{{ isOnline(friend) ? 'Online' : 'Offline' }}</p>
|
||||
</div>
|
||||
<app-friend-button [user]="person" />
|
||||
<app-friend-button [user]="friend" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<p class="py-6 text-center text-sm text-muted-foreground">No people to suggest yet.</p>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
<div class="rounded-xl border border-border bg-card/40 p-4">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-foreground">Popular Servers</h2>
|
||||
<a
|
||||
routerLink="/servers"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>See all</a
|
||||
>
|
||||
</div>
|
||||
@if (popularServers().length > 0) {
|
||||
<div class="space-y-1">
|
||||
@for (server of popularServers(); track server.id) {
|
||||
<div class="flex items-center gap-3 rounded-lg px-2 py-2 transition-colors hover:bg-secondary/60">
|
||||
<div class="grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-lg bg-secondary text-sm font-semibold text-foreground">
|
||||
@if (server.icon) {
|
||||
<!-- Recently active servers -->
|
||||
@if (recentlyActiveServers().length > 0) {
|
||||
<section>
|
||||
<h2 class="mb-3 text-sm font-semibold text-foreground">Recently Active Servers</h2>
|
||||
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
|
||||
@for (room of recentlyActiveServers(); track room.id) {
|
||||
<button
|
||||
type="button"
|
||||
class="flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 text-center transition-colors hover:border-primary/50 hover:bg-card/80"
|
||||
(click)="openSavedRoom(room)"
|
||||
>
|
||||
<div
|
||||
class="grid h-12 w-12 shrink-0 place-items-center overflow-hidden rounded-xl bg-secondary text-base font-semibold text-foreground"
|
||||
>
|
||||
@if (room.icon) {
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="h-full w-full bg-cover bg-center bg-no-repeat"
|
||||
[style.backgroundImage]="'url(' + server.icon + ')'"
|
||||
[style.backgroundImage]="'url(' + room.icon + ')'"
|
||||
></div>
|
||||
} @else {
|
||||
{{ serverInitial(server) }}
|
||||
{{ room.name[0]?.toUpperCase() || '?' }}
|
||||
}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium text-foreground">{{ server.name }}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">{{ serverMetaLabel(server) }}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="shrink-0 rounded-md bg-primary px-3 py-1.5 text-xs font-semibold text-primary-foreground transition-colors hover:bg-primary/90"
|
||||
(click)="openServer(server)"
|
||||
>
|
||||
Join
|
||||
</button>
|
||||
</div>
|
||||
<p class="w-full truncate text-sm font-medium text-foreground">{{ room.name }}</p>
|
||||
<p class="text-xs text-muted-foreground">{{ room.userCount }} members</p>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<p class="py-6 text-center text-sm text-muted-foreground">No popular servers right now.</p>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Your friends -->
|
||||
@if (friends().length > 0) {
|
||||
<section>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-foreground">Your Friends</h2>
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>Manage</a
|
||||
>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@for (friend of friends(); track friend.id) {
|
||||
<div class="flex items-center gap-3 rounded-xl border border-border bg-card p-3">
|
||||
<app-user-avatar
|
||||
[name]="personLabel(friend)"
|
||||
[avatarUrl]="friend.avatarUrl"
|
||||
size="md"
|
||||
[status]="friend.status"
|
||||
[showStatusBadge]="true"
|
||||
/>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium text-foreground">{{ personLabel(friend) }}</p>
|
||||
<p class="text-xs text-muted-foreground">{{ isOnline(friend) ? 'Online' : 'Offline' }}</p>
|
||||
</div>
|
||||
<app-friend-button [user]="friend" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
<!-- Recently active servers -->
|
||||
@if (recentlyActiveServers().length > 0) {
|
||||
<section>
|
||||
<h2 class="mb-3 text-sm font-semibold text-foreground">Recently Active Servers</h2>
|
||||
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
|
||||
@for (room of recentlyActiveServers(); track room.id) {
|
||||
<button
|
||||
type="button"
|
||||
class="flex flex-col items-center gap-2 rounded-xl border border-border bg-card p-4 text-center transition-colors hover:border-primary/50 hover:bg-card/80"
|
||||
(click)="openSavedRoom(room)"
|
||||
>
|
||||
<div class="grid h-12 w-12 shrink-0 place-items-center overflow-hidden rounded-xl bg-secondary text-base font-semibold text-foreground">
|
||||
@if (room.icon) {
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="h-full w-full bg-cover bg-center bg-no-repeat"
|
||||
[style.backgroundImage]="'url(' + room.icon + ')'"
|
||||
></div>
|
||||
} @else {
|
||||
{{ room.name[0]?.toUpperCase() || '?' }}
|
||||
}
|
||||
</div>
|
||||
<p class="w-full truncate text-sm font-medium text-foreground">{{ room.name }}</p>
|
||||
<p class="text-xs text-muted-foreground">{{ room.userCount }} members</p>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@if (isMobile()) {
|
||||
|
||||
Reference in New Issue
Block a user