@@ -321,9 +320,7 @@ export default function HomePageClient() {
Start a room
-
- Free · Works on desktop and mobile
-
+
Free · Works on desktop and mobile
@@ -455,11 +452,16 @@ export default function HomePageClient() {
)}
{/* Get started */}
-
+
Get started
-
Create a new room or join one you were invited to
+
+ Create a new room or join one you were invited to
+
@@ -533,7 +535,9 @@ export default function HomePageClient() {
Join a room
Enter the code from your invite link — e.g.{" "}
- /r/abc12345
+
+ /r/abc12345
+
diff --git a/apps/web/src/app/import/soundcloud/page.tsx b/apps/web/src/app/import/soundcloud/page.tsx
index 89c7543..e517f98 100644
--- a/apps/web/src/app/import/soundcloud/page.tsx
+++ b/apps/web/src/app/import/soundcloud/page.tsx
@@ -3,7 +3,9 @@ import SoundCloudImportClient from "./soundcloud-import-client";
export default function SoundCloudImportPage() {
return (
- Loading...
}>
+ Loading...
}
+ >
);
diff --git a/apps/web/src/app/import/soundcloud/soundcloud-import-client.tsx b/apps/web/src/app/import/soundcloud/soundcloud-import-client.tsx
index 4624286..904e349 100644
--- a/apps/web/src/app/import/soundcloud/soundcloud-import-client.tsx
+++ b/apps/web/src/app/import/soundcloud/soundcloud-import-client.tsx
@@ -2,9 +2,9 @@
// TODO(v0.3): SoundCloud import page — not linked from room UI (API requires Artist Pro).
-import { useState } from "react";
-import { useRouter, useSearchParams } from "next/navigation";
import { Button, Input, Label } from "@together/ui";
+import { useRouter, useSearchParams } from "next/navigation";
+import { useState } from "react";
export default function SoundCloudImportClient() {
const router = useRouter();
diff --git a/apps/web/src/app/import/spotify/page.tsx b/apps/web/src/app/import/spotify/page.tsx
index 2afc05c..c95248e 100644
--- a/apps/web/src/app/import/spotify/page.tsx
+++ b/apps/web/src/app/import/spotify/page.tsx
@@ -3,7 +3,9 @@ import SpotifyImportClient from "./spotify-import-client";
export default function SpotifyImportPage() {
return (
- Loading...
}>
+ Loading...
}
+ >
);
diff --git a/apps/web/src/app/import/spotify/spotify-import-client.tsx b/apps/web/src/app/import/spotify/spotify-import-client.tsx
index eb3fee9..60ba496 100644
--- a/apps/web/src/app/import/spotify/spotify-import-client.tsx
+++ b/apps/web/src/app/import/spotify/spotify-import-client.tsx
@@ -2,9 +2,9 @@
// TODO(v0.3): Spotify import page — not linked from room UI until OAuth flow is production-ready.
-import { useEffect, useState } from "react";
-import { useRouter, useSearchParams } from "next/navigation";
import { Button } from "@together/ui";
+import { useRouter, useSearchParams } from "next/navigation";
+import { useEffect, useState } from "react";
interface Playlist {
id: string;
@@ -53,9 +53,7 @@ export default function SpotifyImportClient() {
});
const items = await res.json();
if (!res.ok) {
- throw new Error(
- typeof items.error === "string" ? items.error : "Spotify import failed",
- );
+ throw new Error(typeof items.error === "string" ? items.error : "Spotify import failed");
}
sessionStorage.setItem("together_import_items", JSON.stringify(items));
@@ -100,11 +98,7 @@ export default function SpotifyImportClient() {
{p.name}
{p.trackCount} tracks
-
@@ -68,30 +68,30 @@ export default function PrivacyPage() {
Information you provide
- Display name — chosen when you create or join a room. Stored in
- your browser and sent to room participants while you are connected.
+ Display name — chosen when you create or join a room. Stored in your
+ browser and sent to room participants while you are connected.
- Room details — room title, privacy setting, optional password
- (stored as a hash, not plain text), queue contents, chat messages, reactions, and
- moderation actions you take as a host or co-host.
+ Room details — room title, privacy setting, optional password (stored
+ as a hash, not plain text), queue contents, chat messages, reactions, and moderation
+ actions you take as a host or co-host.
- Chat and activity — messages, emoji reactions, skip votes, and
- similar in-room activity visible to other participants.
+ Chat and activity — messages, emoji reactions, skip votes, and similar
+ in-room activity visible to other participants.
- Account email (optional) — if you sign in with a magic link, we
- receive and store your email address through Supabase to identify your account.
+ Account email (optional) — if you sign in with a magic link, we receive
+ and store your email address through Supabase to identify your account.
- Saved playlists (optional) — if you are signed in, playlist names
- and track metadata you save are stored in our database.
+ Saved playlists (optional) — if you are signed in, playlist names and
+ track metadata you save are stored in our database.
- Third-party import tokens (optional) — if you connect Spotify to
- import a playlist, a short-lived access token may be stored in an HTTP-only cookie
- for that import session.
+ Third-party import tokens (optional) — if you connect Spotify to import
+ a playlist, a short-lived access token may be stored in an HTTP-only cookie for that
+ import session.
@@ -102,20 +102,20 @@ export default function PrivacyPage() {
(`localStorage`) to distinguish guests for moderation, bans, and queue limits.
- Local preferences — theme, audio-only mode, stream quality,
- volume, recent rooms, and similar settings stored in your browser unless synced to
- your account when signed in.
+ Local preferences — theme, audio-only mode, stream quality, volume,
+ recent rooms, and similar settings stored in your browser unless synced to your account
+ when signed in.
- Technical data — IP address, request timestamps, and basic logs
- used for security, abuse prevention, and rate limiting (for example, on room
- password attempts and API routes).
+ Technical data — IP address, request timestamps, and basic logs used
+ for security, abuse prevention, and rate limiting (for example, on room password
+ attempts and API routes).
- Service analytics — aggregated, privacy-oriented usage analytics
- via Cloudflare Web Analytics (page views on the web app) and Cloudflare Workers
- Analytics Engine (aggregated realtime events such as joins and skips). We do not
- use third-party advertising trackers.
+ Service analytics — aggregated, privacy-oriented usage analytics via
+ Cloudflare Web Analytics (page views on the web app) and Cloudflare Workers Analytics
+ Engine (aggregated realtime events such as joins and skips). We do not use third-party
+ advertising trackers.
Realtime session data — while a room is active, playback position,
@@ -147,29 +147,29 @@ export default function PrivacyPage() {
Together uses cookies and browser storage for essential functionality, including:
- Authentication cookies — when you sign in via Supabase, session
- cookies maintain your logged-in state.
+ Authentication cookies — when you sign in via Supabase, session cookies
+ maintain your logged-in state.
Room access cookies — for private rooms, cookies remember that you
entered the correct password or used a valid invite link for that room.
- OAuth cookies — short-lived cookies during optional Spotify import
- to complete authorization securely.
+ OAuth cookies — short-lived cookies during optional Spotify import to
+ complete authorization securely.
- Local storage — display name, anonymous ID, UI preferences, and
- recent rooms are stored locally in your browser.
+ Local storage — display name, anonymous ID, UI preferences, and recent
+ rooms are stored locally in your browser.
- Service worker cache — if you install or use the PWA, cached app
- shell assets may be stored on your device for offline fallback.
+ Service worker cache — if you install or use the PWA, cached app shell
+ assets may be stored on your device for offline fallback.
- You can clear cookies and local storage in your browser settings. Doing so may sign
- you out, reset local preferences, or require you to re-enter room passwords.
+ You can clear cookies and local storage in your browser settings. Doing so may sign you
+ out, reset local preferences, or require you to re-enter room passwords.
@@ -181,8 +181,7 @@ export default function PrivacyPage() {
- YouTube / Google — embedded players, search, and track resolution.
- See{" "}
+ YouTube / Google — embedded players, search, and track resolution. See{" "}
Google's Privacy Policy
@@ -203,16 +202,15 @@ export default function PrivacyPage() {
.
- Cloudflare — realtime WebSocket infrastructure, Durable Object
- storage, web analytics, and aggregated usage metrics. See{" "}
+ Cloudflare — realtime WebSocket infrastructure, Durable Object storage,
+ web analytics, and aggregated usage metrics. See{" "}
Cloudflare's Privacy Policy
@@ -236,18 +234,18 @@ export default function PrivacyPage() {
Data retention
- Room and account records — kept while the room or account exists
- and as needed to operate the service. Room owners may delete rooms; signed-in users
- may delete saved playlists.
+ Room and account records — kept while the room or account exists and as
+ needed to operate the service. Room owners may delete rooms; signed-in users may delete
+ saved playlists.
- Realtime session data — chat buffers, live queue state, and
- participant lists in Durable Object storage are temporary and tied to active or
- recently active sessions.
+ Realtime session data — chat buffers, live queue state, and participant
+ lists in Durable Object storage are temporary and tied to active or recently active
+ sessions.
- Security logs and rate-limit counters — retained for a limited
- period appropriate for abuse prevention, then discarded or aggregated.
+ Security logs and rate-limit counters — retained for a limited period
+ appropriate for abuse prevention, then discarded or aggregated.
Local browser data — remains on your device until you clear it.
@@ -281,19 +279,19 @@ export default function PrivacyPage() {
International users
- Together is operated from the United Arab Emirates and uses infrastructure providers
- that may process data in other countries. By using the service, you understand that
- your information may be transferred to and processed in jurisdictions with different
- data protection laws than your own.
+ Together is operated from the United Arab Emirates and uses infrastructure providers that
+ may process data in other countries. By using the service, you understand that your
+ information may be transferred to and processed in jurisdictions with different data
+ protection laws than your own.
Changes to this policy
- We may update this Privacy Policy from time to time. We will revise the effective date
- at the top of this page when we do. Continued use of Together after changes become
- effective constitutes acceptance of the updated policy.
+ We may update this Privacy Policy from time to time. We will revise the effective date at
+ the top of this page when we do. Continued use of Together after changes become effective
+ constitutes acceptance of the updated policy.
diff --git a/apps/web/src/app/r/[slug]/invite/route.ts b/apps/web/src/app/r/[slug]/invite/route.ts
index b40e515..e0b3eb2 100644
--- a/apps/web/src/app/r/[slug]/invite/route.ts
+++ b/apps/web/src/app/r/[slug]/invite/route.ts
@@ -2,10 +2,7 @@ import { NextResponse } from "next/server";
import { getRoomBySlug } from "@/lib/rooms";
/** Legacy invite URLs — unlisted rooms just need the slug */
-export async function GET(
- request: Request,
- { params }: { params: Promise<{ slug: string }> },
-) {
+export async function GET(request: Request, { params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const url = new URL(request.url);
diff --git a/apps/web/src/app/r/[slug]/join/page.tsx b/apps/web/src/app/r/[slug]/join/page.tsx
index 165fa60..81739ca 100644
--- a/apps/web/src/app/r/[slug]/join/page.tsx
+++ b/apps/web/src/app/r/[slug]/join/page.tsx
@@ -1,5 +1,5 @@
-import { notFound, redirect } from "next/navigation";
import type { Metadata } from "next";
+import { notFound, redirect } from "next/navigation";
import { Suspense } from "react";
import { JoinGateClient } from "@/components/join-gate";
import { getRoomBySlug } from "@/lib/rooms";
@@ -29,7 +29,9 @@ export default async function JoinPage({ params }: JoinPageProps) {
}
return (
- Loading...}>
+ Loading...}
+ >
);
diff --git a/apps/web/src/app/r/[slug]/opengraph-image.tsx b/apps/web/src/app/r/[slug]/opengraph-image.tsx
index 0bd2637..6c6ef8b 100644
--- a/apps/web/src/app/r/[slug]/opengraph-image.tsx
+++ b/apps/web/src/app/r/[slug]/opengraph-image.tsx
@@ -8,7 +8,8 @@ export const contentType = OG_CONTENT_TYPE;
export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const title = await getRoomOgTitle(slug);
- const appHost = process.env.NEXT_PUBLIC_APP_URL?.replace(/^https?:\/\//, "") ?? "together.chtnnhfoundation.org";
+ const appHost =
+ process.env.NEXT_PUBLIC_APP_URL?.replace(/^https?:\/\//, "") ?? "together.chtnnhfoundation.org";
return renderOgImage({
title,
diff --git a/apps/web/src/app/r/[slug]/page.tsx b/apps/web/src/app/r/[slug]/page.tsx
index c81ea67..195fbfc 100644
--- a/apps/web/src/app/r/[slug]/page.tsx
+++ b/apps/web/src/app/r/[slug]/page.tsx
@@ -1,12 +1,12 @@
-import { notFound, redirect } from "next/navigation";
import type { Metadata } from "next";
-import { getRoomBySlug } from "@/lib/rooms";
-import { hasPasswordCookie, verifyRoomAccess } from "@/lib/room-access";
+import { notFound, redirect } from "next/navigation";
import { RoomClient } from "@/components/room-client";
-import { getSupabaseServerUser } from "@/lib/supabase-server";
import { isUserGloballyBanned } from "@/lib/admin-data";
import { postRealtimeJson } from "@/lib/realtime-server";
+import { hasPasswordCookie, verifyRoomAccess } from "@/lib/room-access";
+import { getRoomBySlug } from "@/lib/rooms";
import { absoluteUrl } from "@/lib/seo";
+import { getSupabaseServerUser } from "@/lib/supabase-server";
interface RoomPageProps {
params: Promise<{ slug: string }>;
diff --git a/apps/web/src/app/settings/page.tsx b/apps/web/src/app/settings/page.tsx
index a865517..51b9855 100644
--- a/apps/web/src/app/settings/page.tsx
+++ b/apps/web/src/app/settings/page.tsx
@@ -1,13 +1,13 @@
"use client";
-import { useEffect, useState } from "react";
import { Button, Input, Label } from "@together/ui";
import Link from "next/link";
+import { useEffect, useState } from "react";
import { useAuthConfig } from "@/components/auth-config-provider";
-import { createSupabaseBrowserClient } from "@/lib/supabase-client";
import { ThemeSelector } from "@/components/theme-selector";
-import { useUserPreferences } from "@/hooks/use-user-preferences";
import { useSupabaseUser } from "@/hooks/use-supabase-user";
+import { useUserPreferences } from "@/hooks/use-user-preferences";
+import { createSupabaseBrowserClient } from "@/lib/supabase-client";
export default function SettingsPage() {
const { configured, url, anonKey } = useAuthConfig();
@@ -22,10 +22,12 @@ export default function SettingsPage() {
if (!configured) return;
try {
const supabase = createSupabaseBrowserClient(url, anonKey);
- supabase.auth.getUser().then(({ data }: { data: { user: { id: string; email?: string } | null } }) => {
- setUser(data.user);
- if (data.user?.email) setEmail(data.user.email);
- });
+ supabase.auth
+ .getUser()
+ .then(({ data }: { data: { user: { id: string; email?: string } | null } }) => {
+ setUser(data.user);
+ if (data.user?.email) setEmail(data.user.email);
+ });
} catch {
// Handled by configured gate in UI.
}
@@ -94,7 +96,9 @@ export default function SettingsPage() {
Account
- Home
+
+ Home
+
@@ -106,13 +110,19 @@ export default function SettingsPage() {
- Sign-in isn't available on this server. You can still listen in rooms without an account.
+ Sign-in isn't available on this server. You can still listen in rooms without an
+ account.
Together lets users create and join rooms to synchronize YouTube playback, manage
- collaborative queues, chat, vote to skip, and use related moderation and
- personalization features. Some features require an optional account; core room
- functionality does not.
+ collaborative queues, chat, vote to skip, and use related moderation and personalization
+ features. Some features require an optional account; core room functionality does not.
We may modify, suspend, or discontinue any part of the service at any time, with or
@@ -58,13 +57,13 @@ export default function TermsPage() {
2. Eligibility
- You must be at least 13 years old to use Together. If you are under the age of majority
- in your jurisdiction, you may use the service only with permission from a parent or
- legal guardian who accepts these Terms on your behalf.
+ You must be at least 13 years old to use Together. If you are under the age of majority in
+ your jurisdiction, you may use the service only with permission from a parent or legal
+ guardian who accepts these Terms on your behalf.
- You are responsible for ensuring that your use of Together complies with applicable
- laws, including copyright and platform terms for content you play or share.
+ You are responsible for ensuring that your use of Together complies with applicable laws,
+ including copyright and platform terms for content you play or share.
@@ -76,8 +75,8 @@ export default function TermsPage() {
account.
- You must provide accurate information and notify us if you suspect unauthorized access
- to your account. We may suspend or terminate accounts that violate these Terms.
+ You must provide accurate information and notify us if you suspect unauthorized access to
+ your account. We may suspend or terminate accounts that violate these Terms.
@@ -86,16 +85,15 @@ export default function TermsPage() {
Room creators and hosts control room settings, including privacy, passwords, playback
- controls, queue rules, and moderation actions such as kick, ban, and co-host
- promotion.
+ controls, queue rules, and moderation actions such as kick, ban, and co-host promotion.
- Hosts are responsible for how their rooms are used and for complying with applicable
- law and third-party terms, including YouTube's Terms of Service.
+ Hosts are responsible for how their rooms are used and for complying with applicable law
+ and third-party terms, including YouTube's Terms of Service.
- We do not guarantee continuous availability of any room. Rooms may become unavailable
- if inactive, deleted, or affected by infrastructure limits.
+ We do not guarantee continuous availability of any room. Rooms may become unavailable if
+ inactive, deleted, or affected by infrastructure limits.
@@ -107,9 +105,13 @@ export default function TermsPage() {
Use Together for unlawful, harmful, harassing, hateful, or abusive activity
Infringe intellectual property or privacy rights of others
Attempt to bypass room access controls, bans, or security measures
-
Disrupt the service, overload systems, scrape excessively, or probe for vulnerabilities
+
+ Disrupt the service, overload systems, scrape excessively, or probe for vulnerabilities
+
Impersonate others or misrepresent your affiliation
-
Upload malware, spam automated requests, or interfere with other users' experience
+
+ Upload malware, spam automated requests, or interfere with other users' experience
+
Use the service to stream or distribute content you do not have rights to share
@@ -122,13 +124,13 @@ export default function TermsPage() {
6. User content
You retain ownership of content you submit, such as chat messages, display names, room
- titles, and saved playlists. You grant us a non-exclusive, worldwide, royalty-free
- license to host, store, reproduce, and display that content solely as needed to operate
- and provide Together.
+ titles, and saved playlists. You grant us a non-exclusive, worldwide, royalty-free license
+ to host, store, reproduce, and display that content solely as needed to operate and
+ provide Together.
- You represent that you have the rights necessary to submit your content and that doing
- so does not violate these Terms or any third-party rights.
+ You represent that you have the rights necessary to submit your content and that doing so
+ does not violate these Terms or any third-party rights.
@@ -160,18 +162,17 @@ export default function TermsPage() {
We do not own third-party videos, music, or other media played in rooms. Rights holders
and platform operators control availability, takedowns, and geographic restrictions. We
- are not responsible for third-party content, outages, policy changes, or removal of
- media.
+ are not responsible for third-party content, outages, policy changes, or removal of media.
8. Intellectual property
- The Together name, branding, website, and original software are owned by the operator
- or licensors and protected by applicable intellectual property laws. The project source
- code is available under the Apache License 2.0 where published, which governs use of
- the code separately from these Terms for use of the hosted service.
+ The Together name, branding, website, and original software are owned by the operator or
+ licensors and protected by applicable intellectual property laws. The project source code
+ is available under the Apache License 2.0 where published, which governs use of the code
+ separately from these Terms for use of the hosted service.
You may not copy, modify, distribute, or reverse engineer the hosted service except as
@@ -183,13 +184,13 @@ export default function TermsPage() {
9. Disclaimers
TO THE FULLEST EXTENT PERMITTED BY LAW, TOGETHER IS PROVIDED WITHOUT WARRANTIES OF ANY
- KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
+ KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
+ FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
- We do not warrant that the service will be uninterrupted, error-free, secure, or free
- of harmful components, or that playback will remain synchronized in all network
- conditions or on all devices.
+ We do not warrant that the service will be uninterrupted, error-free, secure, or free of
+ harmful components, or that playback will remain synchronized in all network conditions or
+ on all devices.
@@ -197,29 +198,28 @@ export default function TermsPage() {
10. Limitation of liability
TO THE FULLEST EXTENT PERMITTED BY LAW, CHTNNH AND TOGETHER WILL NOT BE LIABLE FOR ANY
- INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR ANY LOSS OF
- PROFITS, DATA, GOODWILL, OR OTHER INTANGIBLE LOSSES, ARISING FROM YOUR USE OF OR
- INABILITY TO USE THE SERVICE.
+ INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR ANY LOSS OF PROFITS,
+ DATA, GOODWILL, OR OTHER INTANGIBLE LOSSES, ARISING FROM YOUR USE OF OR INABILITY TO USE
+ THE SERVICE.
- TO THE FULLEST EXTENT PERMITTED BY LAW, OUR TOTAL LIABILITY FOR ANY CLAIM RELATING TO
- THE SERVICE WILL NOT EXCEED THE GREATER OF (A) THE AMOUNT YOU PAID US FOR THE SERVICE
- IN THE TWELVE MONTHS BEFORE THE CLAIM OR (B) USD $100. TOGETHER IS CURRENTLY OFFERED
- FREE OF CHARGE, SO THIS LIMIT WILL TYPICALLY BE USD $100.
+ TO THE FULLEST EXTENT PERMITTED BY LAW, OUR TOTAL LIABILITY FOR ANY CLAIM RELATING TO THE
+ SERVICE WILL NOT EXCEED THE GREATER OF (A) THE AMOUNT YOU PAID US FOR THE SERVICE IN THE
+ TWELVE MONTHS BEFORE THE CLAIM OR (B) USD $100. TOGETHER IS CURRENTLY OFFERED FREE OF
+ CHARGE, SO THIS LIMIT WILL TYPICALLY BE USD $100.
- Some jurisdictions do not allow certain limitations of liability, so some of the above
- may not apply to you.
+ Some jurisdictions do not allow certain limitations of liability, so some of the above may
+ not apply to you.
11. Indemnification
- You agree to indemnify and hold harmless chtnnh and Together from claims, damages,
- losses, and expenses (including reasonable legal fees) arising from your use of the
- service, your content, your rooms, or your violation of these Terms or third-party
- rights.
+ You agree to indemnify and hold harmless chtnnh and Together from claims, damages, losses,
+ and expenses (including reasonable legal fees) arising from your use of the service, your
+ content, your rooms, or your violation of these Terms or third-party rights.
@@ -227,8 +227,7 @@ export default function TermsPage() {
12. Termination
You may stop using Together at any time. We may suspend or terminate your access if you
- violate these Terms, create risk for other users or the service, or where required by
- law.
+ violate these Terms, create risk for other users or the service, or where required by law.
Sections that by their nature should survive termination — including disclaimers,
@@ -249,9 +248,9 @@ export default function TermsPage() {
14. Changes to these Terms
- We may update these Terms from time to time. When we do, we will revise the effective
- date at the top of this page. Material changes may also be highlighted on the site.
- Continued use after changes take effect constitutes acceptance of the updated Terms.
+ We may update these Terms from time to time. When we do, we will revise the effective date
+ at the top of this page. Material changes may also be highlighted on the site. Continued
+ use after changes take effect constitutes acceptance of the updated Terms.
diff --git a/apps/web/src/components/account-nav.tsx b/apps/web/src/components/account-nav.tsx
index b754870..454f5c8 100644
--- a/apps/web/src/components/account-nav.tsx
+++ b/apps/web/src/components/account-nav.tsx
@@ -1,8 +1,8 @@
"use client";
-import Link from "next/link";
-import { ListMusic, LogIn, User } from "lucide-react";
import { Button } from "@together/ui";
+import { ListMusic, LogIn, User } from "lucide-react";
+import Link from "next/link";
interface AccountNavProps {
signedIn: boolean;
@@ -66,11 +66,7 @@ export function AccountNav({
) : (
-
+
{compact ? : "Playlists"}
@@ -86,11 +82,7 @@ export function AccountNav({
) : (
-
+
{compact ? : "Account"}
diff --git a/apps/web/src/components/account-settings-modal.tsx b/apps/web/src/components/account-settings-modal.tsx
index 01b9355..0cf3041 100644
--- a/apps/web/src/components/account-settings-modal.tsx
+++ b/apps/web/src/components/account-settings-modal.tsx
@@ -1,12 +1,12 @@
"use client";
-import { useEffect, useState } from "react";
import { Button, Input, Label } from "@together/ui";
import Link from "next/link";
+import { useEffect, useState } from "react";
import { useAuthConfig } from "@/components/auth-config-provider";
-import { createSupabaseBrowserClient } from "@/lib/supabase-client";
import { ThemeSelector } from "@/components/theme-selector";
import type { UserPreferences } from "@/hooks/use-user-preferences";
+import { createSupabaseBrowserClient } from "@/lib/supabase-client";
interface AccountSettingsModalProps {
open: boolean;
@@ -138,7 +138,8 @@ export function AccountSettingsModal({
>
) : !configured ? (
- Sign-in isn't available on this server. You can still listen in rooms without an account.
+ Sign-in isn't available on this server. You can still listen in rooms without an
+ account.
) : (
<>
diff --git a/apps/web/src/components/connection-status.tsx b/apps/web/src/components/connection-status.tsx
index 5349444..ea05c7b 100644
--- a/apps/web/src/components/connection-status.tsx
+++ b/apps/web/src/components/connection-status.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Wifi, WifiOff, Loader2 } from "lucide-react";
+import { Loader2, Wifi, WifiOff } from "lucide-react";
interface ConnectionStatusProps {
offline: boolean;
diff --git a/apps/web/src/components/discord-status-button.tsx b/apps/web/src/components/discord-status-button.tsx
index 3b0fa5f..a66e24e 100644
--- a/apps/web/src/components/discord-status-button.tsx
+++ b/apps/web/src/components/discord-status-button.tsx
@@ -1,8 +1,8 @@
"use client";
-import { useCallback, useState } from "react";
import { Button, Tooltip, TooltipContent, TooltipTrigger } from "@together/ui";
import { Check, MessageSquareQuote } from "lucide-react";
+import { useCallback, useState } from "react";
interface DiscordStatusButtonProps {
title?: string;
@@ -14,7 +14,7 @@ export function useDiscordStatus({ title, artist, slug }: DiscordStatusButtonPro
const [copied, setCopied] = useState(false);
const copyStatus = useCallback(async () => {
- const track = artist && title ? `${artist} - ${title}` : title ?? "Together room";
+ const track = artist && title ? `${artist} - ${title}` : (title ?? "Together room");
const origin = window.location.origin;
const text = `${track} | ${origin}/r/${slug}`;
await navigator.clipboard.writeText(text);
diff --git a/apps/web/src/components/import-playlist-dialog.tsx b/apps/web/src/components/import-playlist-dialog.tsx
index f256e8e..43c12fb 100644
--- a/apps/web/src/components/import-playlist-dialog.tsx
+++ b/apps/web/src/components/import-playlist-dialog.tsx
@@ -1,9 +1,9 @@
"use client";
-import { useState } from "react";
import { Button, Input, Label } from "@together/ui";
-import { importRequestForQuery } from "@/lib/import-url";
+import { useState } from "react";
import { isImportPlaylist, normalizeImportResponse } from "@/lib/import-results";
+import { importRequestForQuery } from "@/lib/import-url";
export type ImportService = "youtube" | "spotify" | "soundcloud" | "apple";
diff --git a/apps/web/src/components/keyboard-shortcuts-help.tsx b/apps/web/src/components/keyboard-shortcuts-help.tsx
index 132ead6..b131866 100644
--- a/apps/web/src/components/keyboard-shortcuts-help.tsx
+++ b/apps/web/src/components/keyboard-shortcuts-help.tsx
@@ -1,8 +1,8 @@
"use client";
-import { useEffect } from "react";
import { Button } from "@together/ui";
import { X } from "lucide-react";
+import { useEffect } from "react";
const SHORTCUTS = [
{ keys: "Space", action: "Play / pause" },
diff --git a/apps/web/src/components/legal-page.tsx b/apps/web/src/components/legal-page.tsx
index 9e50caf..87d878d 100644
--- a/apps/web/src/components/legal-page.tsx
+++ b/apps/web/src/components/legal-page.tsx
@@ -1,25 +1,16 @@
-import Link from "next/link";
import { Button } from "@together/ui";
import { Music2 } from "lucide-react";
+import Link from "next/link";
import type { ReactNode } from "react";
const EFFECTIVE_DATE = "July 6, 2026";
-export function LegalPage({
- title,
- children,
-}: {
- title: string;
- children: ReactNode;
-}) {
+export function LegalPage({ title, children }: { title: string; children: ReactNode }) {
return (
-
+
Together
@@ -33,9 +24,7 @@ export function LegalPage({
{title}
-
- Effective {EFFECTIVE_DATE}
-
+
Effective {EFFECTIVE_DATE}
{children}
diff --git a/apps/web/src/components/now-playing-bar.tsx b/apps/web/src/components/now-playing-bar.tsx
index 9ac2dcf..30cc913 100644
--- a/apps/web/src/components/now-playing-bar.tsx
+++ b/apps/web/src/components/now-playing-bar.tsx
@@ -1,12 +1,12 @@
"use client";
-import type { ReactNode } from "react";
import type { PlaybackState, ReactionEmoji, RoomReaction } from "@together/shared";
import { SkipVoteBar, Tooltip, TooltipContent, TooltipTrigger } from "@together/ui";
import { Pause, Play, SkipForward } from "lucide-react";
+import type { ReactNode } from "react";
+import { NowPlayingReactions } from "@/components/now-playing-reactions";
import { PlaybackSeekBar } from "@/components/playback-seek-bar";
import { PlaybackVolumeControl } from "@/components/playback-volume-control";
-import { NowPlayingReactions } from "@/components/now-playing-reactions";
import { useMediaQuery } from "@/hooks/use-media-query";
interface NowPlayingBarProps {
@@ -122,11 +122,7 @@ export function NowPlayingBar({
>
{thumbnailUrl ? (
-
+
) : (
No art
@@ -136,9 +132,7 @@ export function NowPlayingBar({