feat: Rename to Toju and add translation
Some checks failed
Deploy Web Apps / deploy (push) Successful in 5m52s
Build Android APK / build-android-apk (push) Failing after 23m15s
Queue Release Build / prepare (push) Successful in 1m42s
Queue Release Build / build-linux (push) Failing after 9m33s
Queue Release Build / build-windows (push) Successful in 26m5s
Queue Release Build / finalize (push) Has been skipped
Some checks failed
Deploy Web Apps / deploy (push) Successful in 5m52s
Build Android APK / build-android-apk (push) Failing after 23m15s
Queue Release Build / prepare (push) Successful in 1m42s
Queue Release Build / build-linux (push) Failing after 9m33s
Queue Release Build / build-windows (push) Successful in 26m5s
Queue Release Build / finalize (push) Has been skipped
This commit is contained in:
@@ -4,12 +4,12 @@
|
||||
<header class="min-w-0 space-y-1">
|
||||
<h1 class="text-2xl font-semibold text-foreground">
|
||||
@if (currentUser()) {
|
||||
Welcome back, {{ currentUser()!.displayName || 'there' }}
|
||||
{{ 'dashboard.welcomeBack' | translate: { name: currentUser()!.displayName || ('dashboard.welcomeGuestFallback' | translate) } }}
|
||||
} @else {
|
||||
Welcome to MetoYou
|
||||
{{ 'dashboard.welcomeTitle' | translate }}
|
||||
}
|
||||
</h1>
|
||||
<p class="text-sm text-muted-foreground">Find people, discover servers, or start your own community.</p>
|
||||
<p class="text-sm text-muted-foreground">{{ 'dashboard.subtitle' | translate }}</p>
|
||||
</header>
|
||||
|
||||
<div class="min-w-0">
|
||||
@@ -21,9 +21,9 @@
|
||||
<input
|
||||
#searchInput
|
||||
type="text"
|
||||
aria-label="Search people, servers, and invites"
|
||||
[attr.aria-label]="'dashboard.searchAriaLabel' | translate"
|
||||
class="h-12 w-full min-w-0 rounded-xl border border-border bg-secondary py-2 pl-11 pr-4 text-base text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary sm:pr-20"
|
||||
[placeholder]="isMobile() ? 'Search people, servers, invites...' : 'Search for people, servers, or paste an invite...'"
|
||||
[placeholder]="isMobile() ? ('dashboard.searchPlaceholderMobile' | translate) : ('dashboard.searchPlaceholderDesktop' | translate)"
|
||||
[ngModel]="searchQuery()"
|
||||
(ngModelChange)="onSearchChange($event)"
|
||||
(keydown.enter)="submitSearch()"
|
||||
@@ -31,13 +31,13 @@
|
||||
<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
|
||||
{{ 'dashboard.searchShortcut' | translate }}
|
||||
</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>
|
||||
<span class="text-xs font-medium text-muted-foreground">{{ 'dashboard.recent' | translate }}</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"
|
||||
@@ -52,7 +52,7 @@
|
||||
<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"
|
||||
[attr.aria-label]="'dashboard.removeRecent' | translate: { term }"
|
||||
(click)="removeRecentSearch(term)"
|
||||
>
|
||||
<ng-icon
|
||||
@@ -67,7 +67,7 @@
|
||||
class="text-xs font-medium text-muted-foreground hover:text-foreground hover:underline"
|
||||
(click)="clearRecentSearches()"
|
||||
>
|
||||
Clear
|
||||
{{ 'common.clear' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@@ -77,7 +77,7 @@
|
||||
<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>
|
||||
<h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">{{ 'dashboard.invite' | translate }}</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"
|
||||
@@ -90,7 +90,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-semibold text-foreground">Open invite</p>
|
||||
<p class="truncate text-sm font-semibold text-foreground">{{ 'dashboard.openInvite' | translate }}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">{{ invite }}</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
@@ -104,11 +104,11 @@
|
||||
@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>
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">{{ 'dashboard.servers' | translate }}</h2>
|
||||
<a
|
||||
routerLink="/servers"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>View all</a
|
||||
>{{ 'common.viewAll' | translate }}</a
|
||||
>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
@@ -148,11 +148,11 @@
|
||||
@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>
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">{{ 'dashboard.people' | translate }}</h2>
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>View all</a
|
||||
>{{ 'common.viewAll' | translate }}</a
|
||||
>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
@@ -170,7 +170,7 @@
|
||||
/>
|
||||
<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>
|
||||
<p class="text-xs text-muted-foreground">{{ (isOnline(person) ? 'common.online' : 'common.offline') | translate }}</p>
|
||||
</div>
|
||||
<app-friend-button [user]="person" />
|
||||
</a>
|
||||
@@ -181,7 +181,7 @@
|
||||
|
||||
@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
|
||||
{{ 'dashboard.noResults' | translate }}
|
||||
<span class="font-medium text-foreground">{{ searchQuery() }}</span
|
||||
>.
|
||||
</div>
|
||||
@@ -201,8 +201,8 @@
|
||||
/>
|
||||
</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>
|
||||
<p class="text-sm font-semibold text-foreground">{{ 'dashboard.findPeople.title' | translate }}</p>
|
||||
<p class="mt-0.5 text-xs text-muted-foreground">{{ 'dashboard.findPeople.subtitle' | translate }}</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
@@ -221,8 +221,8 @@
|
||||
/>
|
||||
</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>
|
||||
<p class="text-sm font-semibold text-foreground">{{ 'dashboard.findServers.title' | translate }}</p>
|
||||
<p class="mt-0.5 text-xs text-muted-foreground">{{ 'dashboard.findServers.subtitle' | translate }}</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
@@ -241,8 +241,8 @@
|
||||
/>
|
||||
</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>
|
||||
<p class="text-sm font-semibold text-foreground">{{ 'dashboard.createServer.title' | translate }}</p>
|
||||
<p class="mt-0.5 text-xs text-muted-foreground">{{ 'dashboard.createServer.subtitle' | translate }}</p>
|
||||
</div>
|
||||
<ng-icon
|
||||
name="lucideArrowRight"
|
||||
@@ -259,9 +259,9 @@
|
||||
class="h-6 w-6 text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
<h2 class="text-base font-semibold text-foreground">Get started</h2>
|
||||
<h2 class="text-base font-semibold text-foreground">{{ 'dashboard.getStarted.title' | translate }}</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.
|
||||
{{ 'dashboard.getStarted.description' | translate }}
|
||||
</p>
|
||||
</section>
|
||||
}
|
||||
@@ -270,11 +270,11 @@
|
||||
<section class="grid min-w-0 gap-4 lg:grid-cols-2">
|
||||
<div class="min-w-0 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>
|
||||
<h2 class="text-sm font-semibold text-foreground">{{ 'dashboard.peopleYouMightKnow' | translate }}</h2>
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>See all</a
|
||||
>{{ 'common.seeAll' | translate }}</a
|
||||
>
|
||||
</div>
|
||||
@if (peopleYouMightKnow().length > 0) {
|
||||
@@ -290,7 +290,7 @@
|
||||
/>
|
||||
<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="text-xs text-muted-foreground">{{ (isOnline(person) ? 'common.online' : 'common.offline') | translate }}</p>
|
||||
</div>
|
||||
<app-friend-button
|
||||
class="shrink-0"
|
||||
@@ -300,17 +300,17 @@
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<p class="py-6 text-center text-sm text-muted-foreground">No people to suggest yet.</p>
|
||||
<p class="py-6 text-center text-sm text-muted-foreground">{{ 'dashboard.noPeopleSuggestions' | translate }}</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 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>
|
||||
<h2 class="text-sm font-semibold text-foreground">{{ 'dashboard.popularServers' | translate }}</h2>
|
||||
<a
|
||||
routerLink="/servers"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>See all</a
|
||||
>{{ 'common.seeAll' | translate }}</a
|
||||
>
|
||||
</div>
|
||||
@if (popularServers().length > 0) {
|
||||
@@ -339,13 +339,13 @@
|
||||
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
|
||||
{{ 'common.join' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<p class="py-6 text-center text-sm text-muted-foreground">No popular servers right now.</p>
|
||||
<p class="py-6 text-center text-sm text-muted-foreground">{{ 'dashboard.noPopularServers' | translate }}</p>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
@@ -354,11 +354,11 @@
|
||||
@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>
|
||||
<h2 class="text-sm font-semibold text-foreground">{{ 'dashboard.yourFriends' | translate }}</h2>
|
||||
<a
|
||||
routerLink="/people"
|
||||
class="text-xs font-medium text-primary hover:underline"
|
||||
>Manage</a
|
||||
>{{ 'common.manage' | translate }}</a
|
||||
>
|
||||
</div>
|
||||
<div class="grid min-w-0 grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@@ -373,7 +373,7 @@
|
||||
/>
|
||||
<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>
|
||||
<p class="text-xs text-muted-foreground">{{ (isOnline(friend) ? 'common.online' : 'common.offline') | translate }}</p>
|
||||
</div>
|
||||
<app-friend-button [user]="friend" />
|
||||
</div>
|
||||
@@ -385,7 +385,7 @@
|
||||
<!-- Recently active servers -->
|
||||
@if (recentlyActiveServers().length > 0) {
|
||||
<section>
|
||||
<h2 class="mb-3 text-sm font-semibold text-foreground">Recently Active Servers</h2>
|
||||
<h2 class="mb-3 text-sm font-semibold text-foreground">{{ 'dashboard.recentlyActiveServers' | translate }}</h2>
|
||||
<div class="grid min-w-0 grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
|
||||
@for (room of recentlyActiveServers(); track room.id) {
|
||||
<button
|
||||
@@ -407,7 +407,7 @@
|
||||
}
|
||||
</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>
|
||||
<p class="text-xs text-muted-foreground">{{ 'dashboard.roomMembers' | translate: { count: room.userCount } }}</p>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
import { selectAllUsers, selectCurrentUser } from '../../store/users/users.selectors';
|
||||
import type { ServerInfo } from '../../domains/server-directory/domain/models/server-directory.model';
|
||||
import type { Room, User } from '../../shared-kernel';
|
||||
import { AppI18nService } from '../../core/i18n';
|
||||
|
||||
interface HarnessOptions {
|
||||
searchResults?: ServerInfo[];
|
||||
@@ -81,6 +82,19 @@ function createHarness(options: HarnessOptions = {}) {
|
||||
const serverDirectory = { getFeaturedServers, getTrendingServers } as unknown as ServerDirectoryFacade;
|
||||
const friendIds = new Set<string>(options.friendIds ?? []);
|
||||
const friendService = { friendIds: () => friendIds, friends: () => [] } as unknown as FriendService;
|
||||
const appI18n = {
|
||||
instant: (key: string, params?: Record<string, unknown>) => {
|
||||
if (key === 'dashboard.serverMeta.member') {
|
||||
return `${params?.count} member`;
|
||||
}
|
||||
|
||||
if (key === 'dashboard.serverMeta.members') {
|
||||
return `${params?.count} members`;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
} as unknown as AppI18nService;
|
||||
const injector = Injector.create({
|
||||
providers: [
|
||||
DashboardComponent,
|
||||
@@ -88,7 +102,8 @@ function createHarness(options: HarnessOptions = {}) {
|
||||
{ provide: Router, useValue: router },
|
||||
{ provide: ServerDirectoryFacade, useValue: serverDirectory },
|
||||
{ provide: FriendService, useValue: friendService },
|
||||
{ provide: ViewportService, useValue: { isMobile: signal(options.isMobile ?? false) } }
|
||||
{ provide: ViewportService, useValue: { isMobile: signal(options.isMobile ?? false) } },
|
||||
{ provide: AppI18nService, useValue: appI18n }
|
||||
]
|
||||
});
|
||||
const component = runInInjectionContext(injector, () => injector.get(DashboardComponent));
|
||||
|
||||
@@ -45,6 +45,7 @@ import { FriendService } from '../../domains/direct-message/application/services
|
||||
import { FriendButtonComponent } from '../../domains/direct-message/feature/friend-button/friend-button.component';
|
||||
import { UserAvatarComponent } from '../../shared/components/user-avatar/user-avatar.component';
|
||||
import { parseInviteQuery } from './invite-query.util';
|
||||
import { AppI18nService, APP_TRANSLATE_IMPORTS } from '../../core/i18n';
|
||||
|
||||
/** Maximum quick-search rows shown per group on the dashboard. */
|
||||
const QUICK_RESULT_LIMIT = 5;
|
||||
@@ -70,7 +71,8 @@ const RECENT_SEARCHES_STORAGE_KEY = 'metoyou_dashboard_recent_searches';
|
||||
RouterLink,
|
||||
NgIcon,
|
||||
FriendButtonComponent,
|
||||
UserAvatarComponent
|
||||
UserAvatarComponent,
|
||||
...APP_TRANSLATE_IMPORTS
|
||||
],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
@@ -90,6 +92,7 @@ const RECENT_SEARCHES_STORAGE_KEY = 'metoyou_dashboard_recent_searches';
|
||||
}
|
||||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
private readonly appI18n = inject(AppI18nService);
|
||||
private store = inject(Store);
|
||||
private router = inject(Router);
|
||||
private serverDirectory = inject(ServerDirectoryFacade);
|
||||
@@ -267,7 +270,11 @@ export class DashboardComponent implements OnInit {
|
||||
}
|
||||
|
||||
serverMetaLabel(server: ServerInfo): string {
|
||||
const members = `${server.userCount ?? 0} ${server.userCount === 1 ? 'member' : 'members'}`;
|
||||
const count = server.userCount ?? 0;
|
||||
const members = this.appI18n.instant(
|
||||
count === 1 ? 'dashboard.serverMeta.member' : 'dashboard.serverMeta.members',
|
||||
{ count }
|
||||
);
|
||||
const detail = server.description?.trim();
|
||||
|
||||
return detail ? `${members} • ${detail}` : members;
|
||||
|
||||
Reference in New Issue
Block a user