feat: Phase 4 Aether Drift — auth, modals & UI component token migration
Migrate all auth pages, modals, and shared UI components from Discord visual language to Aether Drift design tokens. Zero discord-* classes or raw Tailwind palette colors remain in components/auth/, modals/, ui/. - Avatar: status dots use status-online/idle/dnd/offline tokens - Tooltip, ContextMenu, UserProfilePopout: z-index raised to z-[200] - LoginPage, RegisterPage: warm bg-surface-base with lavender glow - All modals: inputs use surface-input, buttons use accent-primary - Toggles: bg-status-online (on), bg-surface-input (off) - ServerSettings: slider tracks, pills, danger zone fully tokenized - Purged raw Tailwind green-500 leak in StreamingLimitsPanel success msg
This commit is contained in:
@@ -32,44 +32,44 @@ export function LoginPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-[#080a0b] relative">
|
<div className="min-h-screen flex items-center justify-center bg-surface-base relative">
|
||||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(88,101,242,0.06)_0%,transparent_50%)]" />
|
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(124,108,246,0.06)_0%,transparent_50%)]" />
|
||||||
<div className="w-full max-w-[480px] bg-discord-bg-surface rounded-md p-8 shadow-elevation-high relative z-10">
|
<div className="w-full max-w-[480px] bg-surface-elevated rounded-md p-8 shadow-elevation-high relative z-10">
|
||||||
<div className="text-center mb-6">
|
<div className="text-center mb-6">
|
||||||
<h1 className="text-2xl font-bold text-discord-text-primary">Welcome back!</h1>
|
<h1 className="text-2xl font-bold text-txt-primary">Welcome back!</h1>
|
||||||
<p className="text-discord-text-muted mt-1">We're so excited to see you again!</p>
|
<p className="text-txt-tertiary mt-1">We're so excited to see you again!</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-4 p-3 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
|
<div className="mb-4 p-3 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Username <span className="text-discord-red">*</span>
|
Username <span className="text-txt-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
className="w-full px-3 py-2.5 bg-discord-bg-tertiary border-none rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple transition-all"
|
className="w-full px-3 py-2.5 bg-surface-input border-none rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary transition-all"
|
||||||
autoFocus
|
autoFocus
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Password <span className="text-discord-red">*</span>
|
Password <span className="text-txt-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
className="w-full px-3 py-2.5 bg-discord-bg-tertiary border-none rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple transition-all"
|
className="w-full px-3 py-2.5 bg-surface-input border-none rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary transition-all"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -77,14 +77,14 @@ export function LoginPage() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="w-full py-2.5 bg-discord-blurple hover:bg-discord-blurple-hover text-white font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
className="w-full py-2.5 bg-accent-primary hover:bg-accent-primary/80 text-white font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Logging in...' : 'Log In'}
|
{isLoading ? 'Logging in...' : 'Log In'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p className="mt-3 text-sm text-discord-text-muted">
|
<p className="mt-3 text-sm text-txt-tertiary">
|
||||||
Need an account?{' '}
|
Need an account?{' '}
|
||||||
<Link to="/register" className="text-discord-text-link hover:underline">
|
<Link to="/register" className="text-accent-primary hover:underline">
|
||||||
Register
|
Register
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -45,56 +45,56 @@ export function RegisterPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-[#080a0b] relative">
|
<div className="min-h-screen flex items-center justify-center bg-surface-base relative">
|
||||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(88,101,242,0.06)_0%,transparent_50%)]" />
|
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(124,108,246,0.06)_0%,transparent_50%)]" />
|
||||||
<div className="w-full max-w-[480px] bg-discord-bg-surface rounded-md p-8 shadow-elevation-high relative z-10">
|
<div className="w-full max-w-[480px] bg-surface-elevated rounded-md p-8 shadow-elevation-high relative z-10">
|
||||||
<div className="text-center mb-6">
|
<div className="text-center mb-6">
|
||||||
<h1 className="text-2xl font-bold text-discord-text-primary">Create an account</h1>
|
<h1 className="text-2xl font-bold text-txt-primary">Create an account</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-4 p-3 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
|
<div className="mb-4 p-3 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Username <span className="text-discord-red">*</span>
|
Username <span className="text-txt-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
className="w-full px-3 py-2.5 bg-discord-bg-tertiary border-none rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple transition-all"
|
className="w-full px-3 py-2.5 bg-surface-input border-none rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary transition-all"
|
||||||
autoFocus
|
autoFocus
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Display Name
|
Display Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={displayName}
|
value={displayName}
|
||||||
onChange={(e) => setDisplayName(e.target.value)}
|
onChange={(e) => setDisplayName(e.target.value)}
|
||||||
className="w-full px-3 py-2.5 bg-discord-bg-tertiary border-none rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple transition-all"
|
className="w-full px-3 py-2.5 bg-surface-input border-none rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary transition-all"
|
||||||
autoComplete="name"
|
autoComplete="name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Password <span className="text-discord-red">*</span>
|
Password <span className="text-txt-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
className="w-full px-3 py-2.5 bg-discord-bg-tertiary border-none rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple transition-all"
|
className="w-full px-3 py-2.5 bg-surface-input border-none rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary transition-all"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,14 +102,14 @@ export function RegisterPage() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="w-full py-2.5 bg-discord-blurple hover:bg-discord-blurple-hover text-white font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
className="w-full py-2.5 bg-accent-primary hover:bg-accent-primary/80 text-white font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Creating account...' : 'Continue'}
|
{isLoading ? 'Creating account...' : 'Continue'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p className="mt-3 text-sm text-discord-text-muted">
|
<p className="mt-3 text-sm text-txt-tertiary">
|
||||||
Already have an account?{' '}
|
Already have an account?{' '}
|
||||||
<Link to="/login" className="text-discord-text-link hover:underline">
|
<Link to="/login" className="text-accent-primary hover:underline">
|
||||||
Log In
|
Log In
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -80,10 +80,10 @@ export function AddDmMemberModal() {
|
|||||||
<Modal isOpen={isOpen} onClose={closeModal} title="Add Friends to DM">
|
<Modal isOpen={isOpen} onClose={closeModal} title="Add Friends to DM">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-[13px] text-discord-text-muted">
|
<p className="text-[13px] text-txt-tertiary">
|
||||||
Search for a user to add to this conversation.
|
Search for a user to add to this conversation.
|
||||||
</p>
|
</p>
|
||||||
<span className="text-[12px] text-discord-text-muted flex-shrink-0 ml-2">
|
<span className="text-[12px] text-txt-tertiary flex-shrink-0 ml-2">
|
||||||
{memberCount}/10
|
{memberCount}/10
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,25 +94,25 @@ export function AddDmMemberModal() {
|
|||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
placeholder="Search for a user..."
|
placeholder="Search for a user..."
|
||||||
className="w-full px-3 py-2 bg-discord-bg-tertiary text-discord-text-primary placeholder-discord-text-muted/60 rounded-[4px] text-[14px] outline-none focus:ring-1 focus:ring-discord-blurple"
|
className="w-full px-3 py-2 bg-surface-input text-txt-primary placeholder-txt-tertiary/60 rounded-[4px] text-[14px] outline-none focus:ring-1 focus:ring-accent-primary"
|
||||||
disabled={memberCount >= 10}
|
disabled={memberCount >= 10}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{memberCount >= 10 && (
|
{memberCount >= 10 && (
|
||||||
<p className="text-discord-red text-[13px]">This group DM has reached the 10-member limit.</p>
|
<p className="text-txt-danger text-[13px]">This group DM has reached the 10-member limit.</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<p className="text-discord-red text-[13px]">{error}</p>
|
<p className="text-txt-danger text-[13px]">{error}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="max-h-[300px] overflow-y-auto space-y-[2px]">
|
<div className="max-h-[300px] overflow-y-auto space-y-[2px]">
|
||||||
{isSearching && (
|
{isSearching && (
|
||||||
<div className="py-4 text-center text-discord-text-muted text-[14px]">Searching...</div>
|
<div className="py-4 text-center text-txt-tertiary text-[14px]">Searching...</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isSearching && query.trim().length >= 2 && results.length === 0 && (
|
{!isSearching && query.trim().length >= 2 && results.length === 0 && (
|
||||||
<div className="py-4 text-center text-discord-text-muted text-[14px]">No users found</div>
|
<div className="py-4 text-center text-txt-tertiary text-[14px]">No users found</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{results.map((user) => (
|
{results.map((user) => (
|
||||||
@@ -120,14 +120,14 @@ export function AddDmMemberModal() {
|
|||||||
key={user.id}
|
key={user.id}
|
||||||
onClick={() => handleSelectUser(user)}
|
onClick={() => handleSelectUser(user)}
|
||||||
disabled={isAdding}
|
disabled={isAdding}
|
||||||
className="w-full flex items-center gap-3 px-3 py-2 rounded-[4px] hover:bg-discord-modifier-hover transition-colors text-left disabled:opacity-50"
|
className="w-full flex items-center gap-3 px-3 py-2 rounded-[4px] hover:bg-interactive-hover transition-colors text-left disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={36} status={user.status as any} />
|
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={36} status={user.status as any} />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[14px] font-medium text-discord-text-primary truncate">
|
<div className="text-[14px] font-medium text-txt-primary truncate">
|
||||||
{user.displayName ?? user.username}
|
{user.displayName ?? user.username}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[12px] text-discord-text-muted truncate">@{user.username}</div>
|
<div className="text-[12px] text-txt-tertiary truncate">@{user.username}</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -92,10 +92,10 @@ export function ChannelSettingsModal() {
|
|||||||
<Modal isOpen={isOpen} onClose={closeModal} title="Channel Settings" maxWidth="max-w-md">
|
<Modal isOpen={isOpen} onClose={closeModal} title="Channel Settings" maxWidth="max-w-md">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Channel
|
Channel
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center gap-2 text-discord-text-primary">
|
<div className="flex items-center gap-2 text-txt-primary">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="opacity-60 flex-shrink-0">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="opacity-60 flex-shrink-0">
|
||||||
<path d="M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41001 9L8.35001 15H14.35L15.41 9H9.41001Z" />
|
<path d="M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41001 9L8.35001 15H14.35L15.41 9H9.41001Z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -104,16 +104,16 @@ export function ChannelSettingsModal() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
|
<div className="p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="pt-2 border-t border-discord-bg-tertiary">
|
<div className="pt-2 border-t border-border-soft">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium text-discord-text-primary">Private Channel</div>
|
<div className="text-sm font-medium text-txt-primary">Private Channel</div>
|
||||||
<div className="text-xs text-discord-text-muted mt-0.5">
|
<div className="text-xs text-txt-tertiary mt-0.5">
|
||||||
Only selected members and roles will be able to view this channel.
|
Only selected members and roles will be able to view this channel.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,10 +122,10 @@ export function ChannelSettingsModal() {
|
|||||||
disabled={isLoading || isFetching}
|
disabled={isLoading || isFetching}
|
||||||
className={`relative w-10 h-6 rounded-full transition-colors flex-shrink-0 ml-4 ${
|
className={`relative w-10 h-6 rounded-full transition-colors flex-shrink-0 ml-4 ${
|
||||||
isFetching
|
isFetching
|
||||||
? 'bg-discord-bg-tertiary opacity-50 cursor-wait'
|
? 'bg-surface-input opacity-50 cursor-wait'
|
||||||
: isPrivate
|
: isPrivate
|
||||||
? 'bg-discord-green'
|
? 'bg-status-online'
|
||||||
: 'bg-discord-bg-tertiary'
|
: 'bg-surface-input'
|
||||||
} ${isLoading ? 'opacity-70 cursor-wait' : 'cursor-pointer'}`}
|
} ${isLoading ? 'opacity-70 cursor-wait' : 'cursor-pointer'}`}
|
||||||
aria-label={isPrivate ? 'Make channel public' : 'Make channel private'}
|
aria-label={isPrivate ? 'Make channel public' : 'Make channel private'}
|
||||||
>
|
>
|
||||||
@@ -139,8 +139,8 @@ export function ChannelSettingsModal() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isPrivate && !isFetching && (
|
{isPrivate && !isFetching && (
|
||||||
<div className="flex items-start gap-2 p-2 bg-discord-bg-tertiary/50 rounded text-xs text-discord-text-muted">
|
<div className="flex items-start gap-2 p-2 bg-surface-input/50 rounded text-xs text-txt-tertiary">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0 mt-0.5 text-discord-text-secondary">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0 mt-0.5 text-txt-secondary">
|
||||||
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" />
|
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -48,13 +48,13 @@ export function CreateChannelModal() {
|
|||||||
<Modal isOpen={isOpen} onClose={closeModal} title="Create Channel">
|
<Modal isOpen={isOpen} onClose={closeModal} title="Create Channel">
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-3 p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
|
<div className="mb-3 p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Channel Type
|
Channel Type
|
||||||
</label>
|
</label>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -63,8 +63,8 @@ export function CreateChannelModal() {
|
|||||||
key={t}
|
key={t}
|
||||||
className={`flex items-center gap-3 p-3 rounded cursor-pointer border ${
|
className={`flex items-center gap-3 p-3 rounded cursor-pointer border ${
|
||||||
type === t
|
type === t
|
||||||
? 'border-discord-blurple bg-discord-bg-hover'
|
? 'border-accent-primary bg-interactive-hover'
|
||||||
: 'border-discord-bg-tertiary bg-discord-bg-secondary hover:bg-discord-bg-hover'
|
: 'border-border-soft bg-surface-channel hover:bg-interactive-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -75,7 +75,7 @@ export function CreateChannelModal() {
|
|||||||
onChange={() => setType(t)}
|
onChange={() => setType(t)}
|
||||||
className="hidden"
|
className="hidden"
|
||||||
/>
|
/>
|
||||||
<div className="w-5 h-5 text-discord-text-muted">
|
<div className="w-5 h-5 text-txt-tertiary">
|
||||||
{t === 'text' && (
|
{t === 'text' && (
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41001 9L8.35001 15H14.35L15.41 9H9.41001Z" /></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41001 9L8.35001 15H14.35L15.41 9H9.41001Z" /></svg>
|
||||||
)}
|
)}
|
||||||
@@ -87,8 +87,8 @@ export function CreateChannelModal() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium text-discord-text-primary capitalize">{t}</div>
|
<div className="text-sm font-medium text-txt-primary capitalize">{t}</div>
|
||||||
<div className="text-xs text-discord-text-muted">
|
<div className="text-xs text-txt-tertiary">
|
||||||
{t === 'text' && 'Send messages, images, and files'}
|
{t === 'text' && 'Send messages, images, and files'}
|
||||||
{t === 'voice' && 'Hang out with voice and video'}
|
{t === 'voice' && 'Hang out with voice and video'}
|
||||||
{t === 'video' && 'Share your screen and camera'}
|
{t === 'video' && 'Share your screen and camera'}
|
||||||
@@ -100,14 +100,14 @@ export function CreateChannelModal() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Channel Name
|
Channel Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
|
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
|
||||||
placeholder="new-channel"
|
placeholder="new-channel"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
@@ -115,14 +115,14 @@ export function CreateChannelModal() {
|
|||||||
|
|
||||||
{type === 'text' && (
|
{type === 'text' && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Topic (optional)
|
Topic (optional)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={topic}
|
value={topic}
|
||||||
onChange={(e) => setTopic(e.target.value)}
|
onChange={(e) => setTopic(e.target.value)}
|
||||||
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
|
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
|
||||||
placeholder="What's this channel about?"
|
placeholder="What's this channel about?"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,14 +132,14 @@ export function CreateChannelModal() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={closeModal}
|
onClick={closeModal}
|
||||||
className="px-4 py-2 text-sm text-discord-text-secondary hover:text-discord-text-primary transition-colors"
|
className="px-4 py-2 text-sm text-txt-secondary hover:text-txt-primary transition-colors"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="px-4 py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
className="px-4 py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Creating...' : 'Create Channel'}
|
{isLoading ? 'Creating...' : 'Create Channel'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -41,19 +41,19 @@ export function CreateServerModal() {
|
|||||||
<Modal isOpen={isOpen} onClose={closeModal} title="Create a Server">
|
<Modal isOpen={isOpen} onClose={closeModal} title="Create a Server">
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-3 p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
|
<div className="mb-3 p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Server Name
|
Server Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
|
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
|
||||||
placeholder="My Awesome Server"
|
placeholder="My Awesome Server"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
@@ -62,14 +62,14 @@ export function CreateServerModal() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={closeModal}
|
onClick={closeModal}
|
||||||
className="px-4 py-2 text-sm text-discord-text-secondary hover:text-discord-text-primary transition-colors"
|
className="px-4 py-2 text-sm text-txt-secondary hover:text-txt-primary transition-colors"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="px-4 py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
className="px-4 py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Creating...' : 'Create'}
|
{isLoading ? 'Creating...' : 'Create'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ export function InviteModal() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onClose={closeModal} title="Invite Friends">
|
<Modal isOpen={isOpen} onClose={closeModal} title="Invite Friends">
|
||||||
<p className="text-discord-text-secondary text-sm mb-4">
|
<p className="text-txt-secondary text-sm mb-4">
|
||||||
Share this invite link with friends to let them join your server.
|
Share this invite link with friends to let them join your server.
|
||||||
</p>
|
</p>
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-3 p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
|
<div className="mb-3 p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -64,15 +64,15 @@ export function InviteModal() {
|
|||||||
type="text"
|
type="text"
|
||||||
value={isLoading ? 'Generating...' : inviteUrl}
|
value={isLoading ? 'Generating...' : inviteUrl}
|
||||||
readOnly
|
readOnly
|
||||||
className="invite-code-input flex-1 px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none font-mono text-xs"
|
className="invite-code-input flex-1 px-3 py-2 bg-surface-input rounded text-txt-primary outline-none font-mono text-xs"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
disabled={isLoading || !inviteUrl}
|
disabled={isLoading || !inviteUrl}
|
||||||
className={`px-4 py-2 text-sm font-medium rounded transition-colors ${
|
className={`px-4 py-2 text-sm font-medium rounded transition-colors ${
|
||||||
copied
|
copied
|
||||||
? 'bg-discord-green text-white'
|
? 'bg-status-online text-white'
|
||||||
: 'bg-discord-blurple hover:bg-discord-blurple-hover text-white'
|
: 'bg-accent-primary hover:bg-accent-primary/80 text-white'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{copied ? 'Copied!' : 'Copy'}
|
{copied ? 'Copied!' : 'Copy'}
|
||||||
|
|||||||
@@ -48,23 +48,23 @@ export function JoinServerModal() {
|
|||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onClose={closeModal} title="Join a Server">
|
<Modal isOpen={isOpen} onClose={closeModal} title="Join a Server">
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<p className="text-discord-text-secondary text-sm mb-4">
|
<p className="text-txt-secondary text-sm mb-4">
|
||||||
Enter an invite code to join an existing server.
|
Enter an invite code to join an existing server.
|
||||||
</p>
|
</p>
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-3 p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">
|
<div className="mb-3 p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Invite Code
|
Invite Code
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={inviteCode}
|
value={inviteCode}
|
||||||
onChange={(e) => setInviteCode(e.target.value)}
|
onChange={(e) => setInviteCode(e.target.value)}
|
||||||
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
|
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
|
||||||
placeholder="e.g. abc123"
|
placeholder="e.g. abc123"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
@@ -73,14 +73,14 @@ export function JoinServerModal() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={closeModal}
|
onClick={closeModal}
|
||||||
className="px-4 py-2 text-sm text-discord-text-secondary hover:text-discord-text-primary transition-colors"
|
className="px-4 py-2 text-sm text-txt-secondary hover:text-txt-primary transition-colors"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="px-4 py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
className="px-4 py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Joining...' : 'Join Server'}
|
{isLoading ? 'Joining...' : 'Join Server'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -78,34 +78,34 @@ export function NewDmModal() {
|
|||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
placeholder="Search for a user..."
|
placeholder="Search for a user..."
|
||||||
className="w-full px-3 py-2 bg-discord-bg-tertiary text-discord-text-primary placeholder-discord-text-muted/60 rounded-[4px] text-[14px] outline-none focus:ring-1 focus:ring-discord-blurple"
|
className="w-full px-3 py-2 bg-surface-input text-txt-primary placeholder-txt-tertiary/60 rounded-[4px] text-[14px] outline-none focus:ring-1 focus:ring-accent-primary"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<p className="text-discord-red text-[13px]">{error}</p>
|
<p className="text-txt-danger text-[13px]">{error}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="max-h-[300px] overflow-y-auto space-y-[2px]">
|
<div className="max-h-[300px] overflow-y-auto space-y-[2px]">
|
||||||
{isSearching && (
|
{isSearching && (
|
||||||
<div className="py-4 text-center text-discord-text-muted text-[14px]">Searching...</div>
|
<div className="py-4 text-center text-txt-tertiary text-[14px]">Searching...</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isSearching && query.trim().length >= 2 && results.length === 0 && (
|
{!isSearching && query.trim().length >= 2 && results.length === 0 && (
|
||||||
<div className="py-4 text-center text-discord-text-muted text-[14px]">No users found</div>
|
<div className="py-4 text-center text-txt-tertiary text-[14px]">No users found</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{results.map((user) => (
|
{results.map((user) => (
|
||||||
<button
|
<button
|
||||||
key={user.id}
|
key={user.id}
|
||||||
onClick={() => handleSelectUser(user)}
|
onClick={() => handleSelectUser(user)}
|
||||||
className="w-full flex items-center gap-3 px-3 py-2 rounded-[4px] hover:bg-discord-modifier-hover transition-colors text-left"
|
className="w-full flex items-center gap-3 px-3 py-2 rounded-[4px] hover:bg-interactive-hover transition-colors text-left"
|
||||||
>
|
>
|
||||||
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={36} status={user.status as any} />
|
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={36} status={user.status as any} />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[14px] font-medium text-discord-text-primary truncate">
|
<div className="text-[14px] font-medium text-txt-primary truncate">
|
||||||
{user.displayName ?? user.username}
|
{user.displayName ?? user.username}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[12px] text-discord-text-muted truncate">@{user.username}</div>
|
<div className="text-[12px] text-txt-tertiary truncate">@{user.username}</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function StreamingLimitsPanel() {
|
|||||||
if (limits) setDraft({ ...limits });
|
if (limits) setDraft({ ...limits });
|
||||||
}, [limits]);
|
}, [limits]);
|
||||||
|
|
||||||
if (!draft) return <div className="text-sm text-discord-text-muted">Loading settings...</div>;
|
if (!draft) return <div className="text-sm text-txt-tertiary">Loading settings...</div>;
|
||||||
|
|
||||||
const hasChanges = JSON.stringify(draft) !== JSON.stringify(limits);
|
const hasChanges = JSON.stringify(draft) !== JSON.stringify(limits);
|
||||||
|
|
||||||
@@ -79,23 +79,23 @@ function StreamingLimitsPanel() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const pillBase = 'px-3 py-1.5 rounded text-[13px] font-medium transition-colors cursor-pointer select-none';
|
const pillBase = 'px-3 py-1.5 rounded text-[13px] font-medium transition-colors cursor-pointer select-none';
|
||||||
const pillOn = 'bg-discord-blurple text-white';
|
const pillOn = 'bg-accent-primary text-white';
|
||||||
const pillOff = 'bg-[#2b2d31] text-discord-text-secondary hover:bg-[#35373c]';
|
const pillOff = 'bg-surface-elevated text-txt-secondary hover:bg-interactive-hover';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="text-xs text-discord-text-muted">
|
<div className="text-xs text-txt-tertiary">
|
||||||
These limits apply to all users on this instance. Users can pick values within these bounds.
|
These limits apply to all users on this instance. Users can pick values within these bounds.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bitrate Range */}
|
{/* Bitrate Range */}
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] text-discord-text-muted font-semibold uppercase tracking-wider mb-2">
|
<div className="text-[11px] text-txt-tertiary font-semibold uppercase tracking-wider mb-2">
|
||||||
Bitrate Range
|
Bitrate Range
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<label className="text-[11px] text-discord-text-muted mb-1 block">Min</label>
|
<label className="text-[11px] text-txt-tertiary mb-1 block">Min</label>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min={100}
|
min={100}
|
||||||
@@ -103,15 +103,15 @@ function StreamingLimitsPanel() {
|
|||||||
step={100}
|
step={100}
|
||||||
value={draft.minBitrateKbps}
|
value={draft.minBitrateKbps}
|
||||||
onChange={(e) => setDraft({ ...draft, minBitrateKbps: Number(e.target.value) })}
|
onChange={(e) => setDraft({ ...draft, minBitrateKbps: Number(e.target.value) })}
|
||||||
className="w-full h-1.5 accent-discord-blurple cursor-pointer appearance-none bg-[#4e5058] rounded-full
|
className="w-full h-1.5 accent-accent-primary cursor-pointer appearance-none bg-interactive-muted rounded-full
|
||||||
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
|
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
|
||||||
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md
|
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md
|
||||||
[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:border-0"
|
[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:border-0"
|
||||||
/>
|
/>
|
||||||
<div className="text-[11px] text-discord-text-secondary mt-0.5">{formatKbps(draft.minBitrateKbps)}</div>
|
<div className="text-[11px] text-txt-secondary mt-0.5">{formatKbps(draft.minBitrateKbps)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<label className="text-[11px] text-discord-text-muted mb-1 block">Max</label>
|
<label className="text-[11px] text-txt-tertiary mb-1 block">Max</label>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min={draft.minBitrateKbps + 500}
|
min={draft.minBitrateKbps + 500}
|
||||||
@@ -119,19 +119,19 @@ function StreamingLimitsPanel() {
|
|||||||
step={500}
|
step={500}
|
||||||
value={draft.maxBitrateKbps}
|
value={draft.maxBitrateKbps}
|
||||||
onChange={(e) => setDraft({ ...draft, maxBitrateKbps: Number(e.target.value) })}
|
onChange={(e) => setDraft({ ...draft, maxBitrateKbps: Number(e.target.value) })}
|
||||||
className="w-full h-1.5 accent-discord-blurple cursor-pointer appearance-none bg-[#4e5058] rounded-full
|
className="w-full h-1.5 accent-accent-primary cursor-pointer appearance-none bg-interactive-muted rounded-full
|
||||||
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
|
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
|
||||||
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md
|
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md
|
||||||
[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:border-0"
|
[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:border-0"
|
||||||
/>
|
/>
|
||||||
<div className="text-[11px] text-discord-text-secondary mt-0.5">{formatKbps(draft.maxBitrateKbps)}</div>
|
<div className="text-[11px] text-txt-secondary mt-0.5">{formatKbps(draft.maxBitrateKbps)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bitrate Step */}
|
{/* Bitrate Step */}
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] text-discord-text-muted font-semibold uppercase tracking-wider mb-1.5">
|
<div className="text-[11px] text-txt-tertiary font-semibold uppercase tracking-wider mb-1.5">
|
||||||
Slider Step
|
Slider Step
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -145,15 +145,15 @@ function StreamingLimitsPanel() {
|
|||||||
const v = Number(e.target.value);
|
const v = Number(e.target.value);
|
||||||
if (v >= 50 && v <= 5000) setDraft({ ...draft, bitrateStepKbps: v });
|
if (v >= 50 && v <= 5000) setDraft({ ...draft, bitrateStepKbps: v });
|
||||||
}}
|
}}
|
||||||
className="w-24 px-2 py-1 bg-discord-bg-tertiary rounded text-sm text-discord-text-primary outline-none focus:ring-1 focus:ring-discord-blurple"
|
className="w-24 px-2 py-1 bg-surface-input rounded text-sm text-txt-primary outline-none focus:ring-1 focus:ring-accent-primary"
|
||||||
/>
|
/>
|
||||||
<span className="text-[12px] text-discord-text-muted">kbps</span>
|
<span className="text-[12px] text-txt-tertiary">kbps</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Allowed Resolutions */}
|
{/* Allowed Resolutions */}
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] text-discord-text-muted font-semibold uppercase tracking-wider mb-1.5">
|
<div className="text-[11px] text-txt-tertiary font-semibold uppercase tracking-wider mb-1.5">
|
||||||
Allowed Resolutions
|
Allowed Resolutions
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1.5">
|
<div className="flex gap-1.5">
|
||||||
@@ -171,7 +171,7 @@ function StreamingLimitsPanel() {
|
|||||||
|
|
||||||
{/* Allowed Frame Rates */}
|
{/* Allowed Frame Rates */}
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] text-discord-text-muted font-semibold uppercase tracking-wider mb-1.5">
|
<div className="text-[11px] text-txt-tertiary font-semibold uppercase tracking-wider mb-1.5">
|
||||||
Allowed Frame Rates
|
Allowed Frame Rates
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1.5">
|
<div className="flex gap-1.5">
|
||||||
@@ -189,23 +189,23 @@ function StreamingLimitsPanel() {
|
|||||||
|
|
||||||
{/* Save / Reset */}
|
{/* Save / Reset */}
|
||||||
{saveError && (
|
{saveError && (
|
||||||
<div className="p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">{saveError}</div>
|
<div className="p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">{saveError}</div>
|
||||||
)}
|
)}
|
||||||
{saveSuccess && (
|
{saveSuccess && (
|
||||||
<div className="p-2 bg-green-500/10 border border-green-500/30 rounded text-green-400 text-sm">Settings saved</div>
|
<div className="p-2 bg-status-online/10 border border-status-online/30 rounded text-status-online text-sm">Settings saved</div>
|
||||||
)}
|
)}
|
||||||
{hasChanges && (
|
{hasChanges && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
className="px-4 py-1.5 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
className="px-4 py-1.5 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saving ? 'Saving...' : 'Save'}
|
{saving ? 'Saving...' : 'Save'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
className="px-4 py-1.5 text-sm text-discord-text-muted hover:text-discord-text-secondary transition-colors"
|
className="px-4 py-1.5 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
@@ -341,7 +341,7 @@ export function ServerSettingsModal() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setTab('overview')}
|
onClick={() => setTab('overview')}
|
||||||
className={`w-full text-left px-3 py-1.5 rounded text-sm transition-colors ${
|
className={`w-full text-left px-3 py-1.5 rounded text-sm transition-colors ${
|
||||||
tab === 'overview' ? 'bg-discord-bg-active text-discord-text-primary' : 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-bg-hover'
|
tab === 'overview' ? 'bg-interactive-selected text-txt-primary' : 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Overview
|
Overview
|
||||||
@@ -349,7 +349,7 @@ export function ServerSettingsModal() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setTab('members')}
|
onClick={() => setTab('members')}
|
||||||
className={`w-full text-left px-3 py-1.5 rounded text-sm transition-colors ${
|
className={`w-full text-left px-3 py-1.5 rounded text-sm transition-colors ${
|
||||||
tab === 'members' ? 'bg-discord-bg-active text-discord-text-primary' : 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-bg-hover'
|
tab === 'members' ? 'bg-interactive-selected text-txt-primary' : 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Members
|
Members
|
||||||
@@ -358,7 +358,7 @@ export function ServerSettingsModal() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setTab('streaming')}
|
onClick={() => setTab('streaming')}
|
||||||
className={`w-full text-left px-3 py-1.5 rounded text-sm transition-colors ${
|
className={`w-full text-left px-3 py-1.5 rounded text-sm transition-colors ${
|
||||||
tab === 'streaming' ? 'bg-discord-bg-active text-discord-text-primary' : 'text-discord-text-muted hover:text-discord-text-secondary hover:bg-discord-bg-hover'
|
tab === 'streaming' ? 'bg-interactive-selected text-txt-primary' : 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Streaming
|
Streaming
|
||||||
@@ -369,20 +369,20 @@ export function ServerSettingsModal() {
|
|||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-3 p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">{error}</div>
|
<div className="mb-3 p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">{error}</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{tab === 'overview' && (
|
{tab === 'overview' && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Server Name
|
Server Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={serverName}
|
value={serverName}
|
||||||
onChange={(e) => setServerName(e.target.value)}
|
onChange={(e) => setServerName(e.target.value)}
|
||||||
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
|
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
|
||||||
disabled={!canManageServer}
|
disabled={!canManageServer}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -392,16 +392,16 @@ export function ServerSettingsModal() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="px-4 py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
className="px-4 py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Saving...' : 'Save Changes'}
|
{isLoading ? 'Saving...' : 'Save Changes'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="pt-4 border-t border-discord-bg-tertiary">
|
<div className="pt-4 border-t border-border-soft">
|
||||||
<h3 className="text-sm font-bold text-discord-red mb-2">Danger Zone</h3>
|
<h3 className="text-sm font-bold text-txt-danger mb-2">Danger Zone</h3>
|
||||||
<button
|
<button
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
className="px-4 py-2 bg-discord-red hover:bg-discord-red-hover text-white text-sm font-medium rounded transition-colors"
|
className="px-4 py-2 bg-accent-rose hover:bg-accent-rose/80 text-white text-sm font-medium rounded transition-colors"
|
||||||
>
|
>
|
||||||
{confirmDelete ? 'Click again to confirm deletion' : 'Delete Server'}
|
{confirmDelete ? 'Click again to confirm deletion' : 'Delete Server'}
|
||||||
</button>
|
</button>
|
||||||
@@ -420,7 +420,7 @@ export function ServerSettingsModal() {
|
|||||||
const hasPendingChanges = pendingRoleChanges.has(member.userId);
|
const hasPendingChanges = pendingRoleChanges.has(member.userId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={member.userId} className="p-2 rounded hover:bg-discord-bg-hover">
|
<div key={member.userId} className="p-2 rounded hover:bg-interactive-hover">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Avatar
|
<Avatar
|
||||||
@@ -433,7 +433,7 @@ export function ServerSettingsModal() {
|
|||||||
<div className="text-sm font-medium">{displayName}</div>
|
<div className="text-sm font-medium">{displayName}</div>
|
||||||
<div className="flex items-center gap-1 flex-wrap">
|
<div className="flex items-center gap-1 flex-wrap">
|
||||||
{isOwner && (
|
{isOwner && (
|
||||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-discord-red/20 text-discord-red font-medium">
|
<span className="text-[10px] px-1.5 py-0.5 rounded bg-accent-rose/20 text-txt-danger font-medium">
|
||||||
Owner
|
Owner
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -447,7 +447,7 @@ export function ServerSettingsModal() {
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{!isOwner && (!member.roles || member.roles.filter(r => r.id !== currentServerId).length === 0) && (
|
{!isOwner && (!member.roles || member.roles.filter(r => r.id !== currentServerId).length === 0) && (
|
||||||
<span className="text-[10px] text-discord-text-muted">No roles</span>
|
<span className="text-[10px] text-txt-tertiary">No roles</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -457,7 +457,7 @@ export function ServerSettingsModal() {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleKick(member.userId)}
|
onClick={() => handleKick(member.userId)}
|
||||||
className="px-2 py-1 text-xs text-discord-red hover:bg-discord-red/10 rounded transition-colors"
|
className="px-2 py-1 text-xs text-txt-danger hover:bg-accent-rose/10 rounded transition-colors"
|
||||||
>
|
>
|
||||||
Kick
|
Kick
|
||||||
</button>
|
</button>
|
||||||
@@ -474,11 +474,11 @@ export function ServerSettingsModal() {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={memberRoleIds.has(role.id)}
|
checked={memberRoleIds.has(role.id)}
|
||||||
onChange={() => handleRoleToggle(member.userId, role.id, memberRoleIds)}
|
onChange={() => handleRoleToggle(member.userId, role.id, memberRoleIds)}
|
||||||
className="w-3.5 h-3.5 rounded border-discord-text-muted accent-discord-blurple"
|
className="w-3.5 h-3.5 rounded border-txt-tertiary accent-accent-primary"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className="text-xs font-medium"
|
className="text-xs font-medium"
|
||||||
style={{ color: role.color !== '#b9bbbe' ? role.color : undefined }}
|
style={{ color: role.color !== '#9ca3af' ? role.color : undefined }}
|
||||||
>
|
>
|
||||||
{role.name}
|
{role.name}
|
||||||
</span>
|
</span>
|
||||||
@@ -488,13 +488,13 @@ export function ServerSettingsModal() {
|
|||||||
<div className="flex items-center gap-2 mt-1.5">
|
<div className="flex items-center gap-2 mt-1.5">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleSaveRoles(member.userId)}
|
onClick={() => handleSaveRoles(member.userId)}
|
||||||
className="px-2 py-0.5 text-xs bg-discord-blurple hover:bg-discord-blurple-hover text-white rounded transition-colors"
|
className="px-2 py-0.5 text-xs bg-accent-primary hover:bg-accent-primary/80 text-white rounded transition-colors"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleCancelRoleChange(member.userId)}
|
onClick={() => handleCancelRoleChange(member.userId)}
|
||||||
className="px-2 py-0.5 text-xs text-discord-text-muted hover:text-discord-text-secondary transition-colors"
|
className="px-2 py-0.5 text-xs text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export function UserSettingsModal() {
|
|||||||
<Modal isOpen={isOpen} onClose={closeModal} title="User Settings" maxWidth="max-w-lg">
|
<Modal isOpen={isOpen} onClose={closeModal} title="User Settings" maxWidth="max-w-lg">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Profile preview */}
|
{/* Profile preview */}
|
||||||
<div className="flex items-center gap-4 p-4 bg-discord-bg-secondary rounded-lg">
|
<div className="flex items-center gap-4 p-4 bg-surface-channel rounded-lg">
|
||||||
<Avatar
|
<Avatar
|
||||||
src={user.avatar}
|
src={user.avatar}
|
||||||
name={user.displayName ?? user.username}
|
name={user.displayName ?? user.username}
|
||||||
@@ -67,28 +67,28 @@ export function UserSettingsModal() {
|
|||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-bold text-lg">{user.displayName ?? user.username}</div>
|
<div className="font-bold text-lg">{user.displayName ?? user.username}</div>
|
||||||
<div className="text-discord-text-muted text-sm">@{user.username}</div>
|
<div className="text-txt-tertiary text-sm">@{user.username}</div>
|
||||||
{user.customStatus && (
|
{user.customStatus && (
|
||||||
<div className="text-discord-text-secondary text-sm mt-1">{user.customStatus}</div>
|
<div className="text-txt-secondary text-sm mt-1">{user.customStatus}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="p-2 bg-discord-red/10 border border-discord-red/30 rounded text-discord-text-danger text-sm">{error}</div>
|
<div className="p-2 bg-accent-rose/10 border border-accent-rose/30 rounded text-txt-danger text-sm">{error}</div>
|
||||||
)}
|
)}
|
||||||
{success && (
|
{success && (
|
||||||
<div className="p-2 bg-discord-green/10 border border-discord-green/30 rounded text-discord-text-positive text-sm">{success}</div>
|
<div className="p-2 bg-status-online/10 border border-status-online/30 rounded text-status-online text-sm">{success}</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Status
|
Status
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={status}
|
value={status}
|
||||||
onChange={(e) => setStatus(e.target.value as any)}
|
onChange={(e) => setStatus(e.target.value as any)}
|
||||||
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple appearance-none"
|
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary appearance-none"
|
||||||
>
|
>
|
||||||
<option value="online">Online</option>
|
<option value="online">Online</option>
|
||||||
<option value="idle">Idle</option>
|
<option value="idle">Idle</option>
|
||||||
@@ -97,44 +97,44 @@ export function UserSettingsModal() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Display Name
|
Display Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={displayName}
|
value={displayName}
|
||||||
onChange={(e) => setDisplayName(e.target.value)}
|
onChange={(e) => setDisplayName(e.target.value)}
|
||||||
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
|
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-discord-text-secondary uppercase mb-2">
|
<label className="block text-xs font-bold text-txt-secondary uppercase mb-2">
|
||||||
Custom Status
|
Custom Status
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={customStatus}
|
value={customStatus}
|
||||||
onChange={(e) => setCustomStatus(e.target.value)}
|
onChange={(e) => setCustomStatus(e.target.value)}
|
||||||
className="w-full px-3 py-2 bg-discord-bg-tertiary rounded text-discord-text-primary outline-none focus:ring-2 focus:ring-discord-blurple"
|
className="w-full px-3 py-2 bg-surface-input rounded text-txt-primary outline-none focus:ring-2 focus:ring-accent-primary"
|
||||||
placeholder="What are you up to?"
|
placeholder="What are you up to?"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Voice Processing */}
|
{/* Voice Processing */}
|
||||||
<div className="border-t border-white/[0.06] pt-4">
|
<div className="border-t border-white/[0.06] pt-4">
|
||||||
<h3 className="text-xs font-bold text-discord-text-secondary uppercase mb-3">
|
<h3 className="text-xs font-bold text-txt-secondary uppercase mb-3">
|
||||||
Voice Processing
|
Voice Processing
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="flex items-center justify-between py-2">
|
<div className="flex items-center justify-between py-2">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-discord-text-primary">AI Noise Suppression</div>
|
<div className="text-sm text-txt-primary">AI Noise Suppression</div>
|
||||||
<div className="text-xs text-discord-text-muted">ML-based noise removal (RNNoise) — filters keyboard, fans, and background noise</div>
|
<div className="text-xs text-txt-tertiary">ML-based noise removal (RNNoise) — filters keyboard, fans, and background noise</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setRnnoiseEnabled(!rnnoiseEnabled)}
|
onClick={() => setRnnoiseEnabled(!rnnoiseEnabled)}
|
||||||
className={`relative w-10 h-5 rounded-full transition-colors ${rnnoiseEnabled ? 'bg-discord-green' : 'bg-discord-bg-tertiary'}`}
|
className={`relative w-10 h-5 rounded-full transition-colors ${rnnoiseEnabled ? 'bg-status-online' : 'bg-surface-input'}`}
|
||||||
>
|
>
|
||||||
<div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${rnnoiseEnabled ? 'translate-x-5' : 'translate-x-0'}`} />
|
<div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${rnnoiseEnabled ? 'translate-x-5' : 'translate-x-0'}`} />
|
||||||
</button>
|
</button>
|
||||||
@@ -142,12 +142,12 @@ export function UserSettingsModal() {
|
|||||||
|
|
||||||
<div className="flex items-center justify-between py-2">
|
<div className="flex items-center justify-between py-2">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-discord-text-primary">Echo Cancellation</div>
|
<div className="text-sm text-txt-primary">Echo Cancellation</div>
|
||||||
<div className="text-xs text-discord-text-muted">Removes echo when using speakers (auto-disabled during screen share)</div>
|
<div className="text-xs text-txt-tertiary">Removes echo when using speakers (auto-disabled during screen share)</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setEchoCancellation(!echoCancellation)}
|
onClick={() => setEchoCancellation(!echoCancellation)}
|
||||||
className={`relative w-10 h-5 rounded-full transition-colors ${echoCancellation ? 'bg-discord-green' : 'bg-discord-bg-tertiary'}`}
|
className={`relative w-10 h-5 rounded-full transition-colors ${echoCancellation ? 'bg-status-online' : 'bg-surface-input'}`}
|
||||||
>
|
>
|
||||||
<div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${echoCancellation ? 'translate-x-5' : 'translate-x-0'}`} />
|
<div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${echoCancellation ? 'translate-x-5' : 'translate-x-0'}`} />
|
||||||
</button>
|
</button>
|
||||||
@@ -155,12 +155,12 @@ export function UserSettingsModal() {
|
|||||||
|
|
||||||
<div className="flex items-center justify-between py-2">
|
<div className="flex items-center justify-between py-2">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-discord-text-primary">Auto Gain Control</div>
|
<div className="text-sm text-txt-primary">Auto Gain Control</div>
|
||||||
<div className="text-xs text-discord-text-muted">Auto-adjusts mic volume — can cause voice ducking during streams</div>
|
<div className="text-xs text-txt-tertiary">Auto-adjusts mic volume — can cause voice ducking during streams</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setAutoGainControl(!autoGainControl)}
|
onClick={() => setAutoGainControl(!autoGainControl)}
|
||||||
className={`relative w-10 h-5 rounded-full transition-colors ${autoGainControl ? 'bg-discord-green' : 'bg-discord-bg-tertiary'}`}
|
className={`relative w-10 h-5 rounded-full transition-colors ${autoGainControl ? 'bg-status-online' : 'bg-surface-input'}`}
|
||||||
>
|
>
|
||||||
<div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${autoGainControl ? 'translate-x-5' : 'translate-x-0'}`} />
|
<div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${autoGainControl ? 'translate-x-5' : 'translate-x-0'}`} />
|
||||||
</button>
|
</button>
|
||||||
@@ -170,14 +170,14 @@ export function UserSettingsModal() {
|
|||||||
<div className="flex items-center justify-between pt-2">
|
<div className="flex items-center justify-between pt-2">
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className="px-4 py-2 text-sm text-discord-red hover:bg-discord-red/10 rounded transition-colors"
|
className="px-4 py-2 text-sm text-txt-danger hover:bg-accent-rose/10 rounded transition-colors"
|
||||||
>
|
>
|
||||||
Log Out
|
Log Out
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="px-4 py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
className="px-4 py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Saving...' : 'Save Changes'}
|
{isLoading ? 'Saving...' : 'Save Changes'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ interface AvatarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const statusColors: Record<string, string> = {
|
const statusColors: Record<string, string> = {
|
||||||
online: 'bg-discord-green',
|
online: 'bg-status-online',
|
||||||
idle: 'bg-discord-yellow',
|
idle: 'bg-status-idle',
|
||||||
dnd: 'bg-discord-red',
|
dnd: 'bg-status-dnd',
|
||||||
offline: 'bg-discord-text-muted',
|
offline: 'bg-status-offline',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Avatar({ src, name, size = 40, status, className = '', onClick, user }: AvatarProps) {
|
export function Avatar({ src, name, size = 40, status, className = '', onClick, user }: AvatarProps) {
|
||||||
@@ -59,14 +59,14 @@ export function Avatar({ src, name, size = 40, status, className = '', onClick,
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div
|
<div
|
||||||
className={`avatar-fallback w-full h-full rounded-full bg-discord-blurple flex items-center justify-center ${fontSize} font-semibold text-white ${src ? 'hidden' : 'flex'}`}
|
className={`avatar-fallback w-full h-full rounded-full bg-accent-primary flex items-center justify-center ${fontSize} font-semibold text-white ${src ? 'hidden' : 'flex'}`}
|
||||||
style={src ? { display: 'none' } : undefined}
|
style={src ? { display: 'none' } : undefined}
|
||||||
>
|
>
|
||||||
{initials}
|
{initials}
|
||||||
</div>
|
</div>
|
||||||
{status && (
|
{status && (
|
||||||
<div
|
<div
|
||||||
className={`absolute -bottom-0.5 -right-0.5 rounded-full border-[3px] border-discord-bg-secondary ${statusColors[status] ?? 'bg-discord-text-muted'}`}
|
className={`absolute -bottom-0.5 -right-0.5 rounded-full border-[3px] border-surface-channel ${statusColors[status] ?? 'bg-status-offline'}`}
|
||||||
style={{
|
style={{
|
||||||
width: size * 0.35,
|
width: size * 0.35,
|
||||||
height: size * 0.35,
|
height: size * 0.35,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function ContextMenu({ items, children }: ContextMenuProps) {
|
|||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div
|
<div
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
className="fixed z-50 min-w-[180px] py-1.5 bg-discord-bg-floating rounded-md shadow-elevation-high animate-fade-in"
|
className="fixed z-[200] min-w-[180px] py-1.5 bg-surface-elevated rounded-md shadow-elevation-high animate-fade-in"
|
||||||
style={{ left: position.x, top: position.y }}
|
style={{ left: position.x, top: position.y }}
|
||||||
>
|
>
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
@@ -70,8 +70,8 @@ export function ContextMenu({ items, children }: ContextMenuProps) {
|
|||||||
key={i}
|
key={i}
|
||||||
className={`w-full text-left px-2 py-1.5 mx-1.5 text-sm rounded-sm flex items-center gap-2 ${
|
className={`w-full text-left px-2 py-1.5 mx-1.5 text-sm rounded-sm flex items-center gap-2 ${
|
||||||
item.danger
|
item.danger
|
||||||
? 'text-discord-red hover:bg-discord-red hover:text-white'
|
? 'text-txt-danger hover:bg-accent-rose hover:text-white'
|
||||||
: 'text-discord-text-secondary hover:bg-discord-blurple hover:text-white'
|
: 'text-txt-secondary hover:bg-accent-primary hover:text-white'
|
||||||
}`}
|
}`}
|
||||||
style={{ width: 'calc(100% - 12px)' }}
|
style={{ width: 'calc(100% - 12px)' }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function Tooltip({ content, children, position = 'right', delay = 200 }:
|
|||||||
{children}
|
{children}
|
||||||
{isVisible && (
|
{isVisible && (
|
||||||
<div
|
<div
|
||||||
className={`absolute z-50 px-3 py-1.5 text-sm font-medium text-discord-text-primary bg-discord-bg-floating rounded-md shadow-elevation-high whitespace-nowrap pointer-events-none ${positionClasses[position]}`}
|
className={`absolute z-[200] px-3 py-1.5 text-sm font-medium text-txt-primary bg-surface-elevated rounded-md shadow-elevation-high whitespace-nowrap pointer-events-none ${positionClasses[position]}`}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,15 +31,15 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed z-50 w-[300px] bg-discord-bg-floating rounded-[8px] shadow-elevation-high overflow-hidden animate-fade-in select-none"
|
className="fixed z-[200] w-[300px] bg-surface-elevated rounded-[8px] shadow-elevation-high overflow-hidden animate-fade-in select-none"
|
||||||
style={position ? { top: position.top, left: position.left } : { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }}
|
style={position ? { top: position.top, left: position.left } : { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }}
|
||||||
>
|
>
|
||||||
{/* Banner */}
|
{/* Banner */}
|
||||||
<div className="h-[60px] bg-discord-blurple" />
|
<div className="h-[60px] bg-accent-primary" />
|
||||||
|
|
||||||
{/* Avatar Container */}
|
{/* Avatar Container */}
|
||||||
<div className="px-4 pb-4 relative">
|
<div className="px-4 pb-4 relative">
|
||||||
<div className="absolute -top-8 left-4 rounded-full border-[6px] border-discord-bg-floating bg-discord-bg-floating">
|
<div className="absolute -top-8 left-4 rounded-full border-[6px] border-surface-elevated bg-surface-elevated">
|
||||||
<Avatar
|
<Avatar
|
||||||
src={user.avatar}
|
src={user.avatar}
|
||||||
name={displayName}
|
name={displayName}
|
||||||
@@ -49,27 +49,27 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="mt-12 bg-discord-bg-tertiary rounded-[8px] p-3">
|
<div className="mt-12 bg-surface-input rounded-[8px] p-3">
|
||||||
<div className="text-[20px] font-bold text-discord-text-header leading-tight mb-1">
|
<div className="text-[20px] font-bold text-txt-primary leading-tight mb-1">
|
||||||
{displayName}
|
{displayName}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[14px] text-discord-text-normal font-medium mb-3">
|
<div className="text-[14px] text-txt-secondary font-medium mb-3">
|
||||||
@{user.username}
|
@{user.username}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full h-[1px] bg-discord-modifier-accent mb-3" />
|
<div className="w-full h-[1px] bg-border-soft mb-3" />
|
||||||
|
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<div className="text-[12px] font-bold text-discord-text-header uppercase mb-1">Backspace Member Since</div>
|
<div className="text-[12px] font-bold text-txt-primary uppercase mb-1">Backspace Member Since</div>
|
||||||
<div className="text-[12px] text-discord-text-normal font-medium">
|
<div className="text-[12px] text-txt-secondary font-medium">
|
||||||
{new Date(user.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
|
{new Date(user.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{user.customStatus && (
|
{user.customStatus && (
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<div className="text-[12px] font-bold text-discord-text-header uppercase mb-1">Status</div>
|
<div className="text-[12px] font-bold text-txt-primary uppercase mb-1">Status</div>
|
||||||
<div className="text-[14px] text-discord-text-normal">{user.customStatus}</div>
|
<div className="text-[14px] text-txt-secondary">{user.customStatus}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -79,7 +79,7 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
|
|||||||
<div className="px-4 pb-4">
|
<div className="px-4 pb-4">
|
||||||
<button
|
<button
|
||||||
onClick={handleSendMessage}
|
onClick={handleSendMessage}
|
||||||
className="w-full py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-[14px] font-medium rounded-[4px] transition-colors"
|
className="w-full py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-[14px] font-medium rounded-[4px] transition-colors"
|
||||||
>
|
>
|
||||||
Send Message
|
Send Message
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user