diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index 8e4e1cb..0000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,52 +0,0 @@ -.app { - min-height: 100vh; - display: flex; - flex-direction: column; -} - -.main-content { - flex: 1; -} - -/* Error container styling */ -.error-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 100vh; - padding: 24px; - text-align: center; -} - -.error-container h1 { - font-size: 24px; - margin-bottom: 16px; - color: var(--primary); -} - -.error-container p { - font-size: 14px; - color: var(--muted-foreground); - margin-bottom: 24px; -} - -.error-container button { - padding: 10px 20px; - background: var(--primary); - color: var(--primary-foreground); - border: none; - border-radius: 999px; - font-size: 14px; - cursor: pointer; - box-shadow: 0 12px 24px rgba(0, 0, 0, 0.08); - transition: transform 0.2s, box-shadow 0.2s; -} - -.error-container button:hover { - transform: translateY(-1px); - box-shadow: 0 16px 28px rgba(0, 0, 0, 0.12); -} - - - diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 08398f7..8b668cc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,16 +11,15 @@ import { SpeechSoundsAdminPage } from './pages/SpeechSoundsAdminPage'; import { LoginPage } from './pages/LoginPage'; import { APPS } from './config/apps'; import './globals.css'; -import './App.css'; function App() { return ( -
+
-
+
} /> {/* Dynamically generate routes for enabled apps */} diff --git a/frontend/src/components/ChannelManager/ChannelManager.css b/frontend/src/components/ChannelManager/ChannelManager.css deleted file mode 100644 index 72e7dcb..0000000 --- a/frontend/src/components/ChannelManager/ChannelManager.css +++ /dev/null @@ -1,178 +0,0 @@ -.channel-manager { - width: 100%; - padding: 24px; - background: var(--color-surface); - border-radius: 12px; - border: 1px solid rgba(212, 222, 239, 0.8); -} - -.channel-manager h2 { - margin: 0 0 24px 0; - font-size: 24px; - font-weight: 500; -} - -.add-channel-form { - display: flex; - gap: 12px; - margin-bottom: 24px; -} - -.channel-input { - flex: 1; - padding: 12px 16px; - border: 1px solid #ccc; - border-radius: 4px; - font-size: 14px; -} - -.channel-input:focus { - outline: none; - border-color: #065fd4; -} - -.add-button { - padding: 12px 24px; - background-color: #065fd4; - color: white; - border: none; - border-radius: 4px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - white-space: nowrap; - transition: background-color 0.2s; -} - -.add-button:hover:not(:disabled) { - background-color: #0556c4; -} - -.add-button:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.alert { - padding: 12px 16px; - border-radius: 4px; - margin-bottom: 16px; - font-size: 14px; -} - -.alert-error { - background-color: #fef2f2; - color: #991b1b; - border: 1px solid #fecaca; -} - -.alert-success { - background-color: #f0fdf4; - color: #166534; - border: 1px solid #bbf7d0; -} - -.empty-message { - text-align: center; - padding: 48px 24px; - color: #606060; - font-size: 14px; -} - -.channels-list { - display: flex; - flex-direction: column; - gap: 16px; -} - -.channel-item { - display: flex; - align-items: center; - gap: 16px; - padding: 16px; - background-color: #f9f9f9; - border-radius: 8px; - border: 1px solid #e5e5e5; -} - -.channel-thumbnail { - width: 80px; - height: 80px; - border-radius: 50%; - object-fit: cover; -} - -.channel-info { - flex: 1; - min-width: 0; -} - -.channel-name { - margin: 0 0 4px 0; - font-size: 16px; - font-weight: 500; - color: #030303; -} - -.channel-stats { - margin: 0 0 4px 0; - font-size: 14px; - color: #606060; -} - -.channel-meta { - margin: 0; - font-size: 12px; - color: #909090; -} - -.channel-error { - margin: 0; - font-size: 12px; - color: #d00; -} - -.remove-button { - padding: 8px 16px; - background-color: #fff; - color: #d00; - border: 1px solid #d00; - border-radius: 4px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - white-space: nowrap; - transition: all 0.2s; -} - -.remove-button:hover { - background-color: #d00; - color: white; -} - -@media (max-width: 768px) { - .channel-manager { - padding: 16px; - } - - .add-channel-form { - flex-direction: column; - } - - .channel-item { - flex-direction: column; - align-items: flex-start; - } - - .channel-thumbnail { - width: 60px; - height: 60px; - } - - .remove-button { - align-self: flex-end; - } -} - - - diff --git a/frontend/src/components/ChannelManager/ChannelManager.tsx b/frontend/src/components/ChannelManager/ChannelManager.tsx index 595ace1..c9ebcdd 100644 --- a/frontend/src/components/ChannelManager/ChannelManager.tsx +++ b/frontend/src/components/ChannelManager/ChannelManager.tsx @@ -1,6 +1,5 @@ import { useState } from 'react'; import { useChannels } from '../../hooks/useChannels'; -import './ChannelManager.css'; export function ChannelManager() { const { channels, loading, error, addChannel, removeChannel } = useChannels(); @@ -48,59 +47,80 @@ export function ChannelManager() { }; return ( -
-

Channel Management

+
+

Channel Management

-
+ setChannelInput(e.target.value)} disabled={adding} - className="channel-input" + className="flex-1 px-4 py-3 border border-border rounded-md text-sm bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent disabled:opacity-50" /> -
- {addError &&
{addError}
} - {addSuccess &&
{addSuccess}
} + {addError && ( +
+ {addError} +
+ )} + {addSuccess && ( +
+ {addSuccess} +
+ )} - {loading &&

Loading channels...

} - {error &&
{error}
} + {loading &&

Loading channels...

} + {error && ( +
+ {error} +
+ )} {!loading && channels.length === 0 && ( -

No channels added yet. Add your first channel above!

+

+ No channels added yet. Add your first channel above! +

)} {channels.length > 0 && ( -
+
{channels.map(channel => ( -
+
{channel.name} -
-

{channel.name}

-

+

+

{channel.name}

+

{formatNumber(channel.subscriberCount)} subscribers • {channel.videoCount} videos

{channel.lastFetchedAt && ( -

+

Last updated: {new Date(channel.lastFetchedAt).toLocaleString()}

)} {channel.fetchError && ( -

Error: {channel.fetchError}

+

Error: {channel.fetchError}

)}
@@ -111,6 +131,3 @@ export function ChannelManager() {
); } - - - diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx index 862c4bd..bcc09a6 100644 --- a/frontend/src/components/ErrorBoundary.tsx +++ b/frontend/src/components/ErrorBoundary.tsx @@ -26,10 +26,13 @@ export class ErrorBoundary extends React.Component { render() { if (this.state.hasError) { return ( -
-

Something went wrong

-

{this.state.error?.message}

-
diff --git a/frontend/src/components/Navbar/Navbar.css b/frontend/src/components/Navbar/Navbar.css deleted file mode 100644 index b9c245f..0000000 --- a/frontend/src/components/Navbar/Navbar.css +++ /dev/null @@ -1,233 +0,0 @@ -.navbar { - background: var(--color-surface); - border-bottom: 1px solid var(--color-border); - position: sticky; - top: 0; - z-index: 100; - box-shadow: 0 10px 30px var(--color-shadow); -} - -.navbar-container { - max-width: 1600px; - margin: 0 auto; - padding: 12px 0px; - display: flex; - justify-content: space-between; - align-items: center; -} - -.navbar-logo { - display: flex; - align-items: center; - gap: 8px; - text-decoration: none; - color: var(--color-primary-dark); - font-size: 20px; - font-weight: 600; -} - -.logo-icon { - font-size: 24px; -} - -.logo-text { - font-family: 'YouTube Sans', 'Roboto', sans-serif; -} - -.navbar-menu { - display: flex; - align-items: center; - gap: 24px; -} - -.navbar-link { - text-decoration: none; - color: var(--color-text); - font-size: 14px; - font-weight: 500; - padding: 8px 12px; - border-radius: 999px; - transition: background-color 0.2s, color 0.2s; -} - -.navbar-link:hover { - background-color: rgba(47, 128, 237, 0.12); - color: var(--color-primary-dark); -} - -.navbar-user { - display: flex; - align-items: center; - gap: 12px; -} - -.navbar-username { - font-size: 14px; - color: var(--color-muted); -} - -.navbar-button { - background: var(--color-primary); - color: white; - border: none; - padding: 10px 20px; - border-radius: 999px; - font-size: 14px; - font-weight: 600; - cursor: pointer; - text-decoration: none; - display: inline-block; - transition: transform 0.2s, box-shadow 0.2s; - box-shadow: 0 10px 20px rgba(32, 76, 130, 0.25); -} - -.navbar-button:hover { - transform: translateY(-1px); - box-shadow: 0 12px 26px rgba(32, 76, 130, 0.32); -} - -/* Search and Filters Section */ -.navbar-filters { - background-color: var(--color-surface-muted); - border-top: 1px solid var(--color-border); - padding: 16px 24px; -} - -.navbar-filters-container { - max-width: 1600px; - margin: 0 auto; - display: flex; - gap: 16px; - align-items: center; -} - -.navbar-search-form { - flex: 1; - display: flex; - gap: 8px; - max-width: 500px; -} - -.navbar-search-input { - flex: 1; - padding: 10px 14px; - border: 1px solid var(--color-border); - font-size: 14px; - background-color: var(--color-surface); - border-radius: 999px; -} - -.navbar-search-input:focus { - outline: none; - border-color: var(--color-primary); - box-shadow: 0 8px 22px rgba(47, 128, 237, 0.1); -} - -.navbar-search-button { - padding: 10px 18px; - background: var(--color-primary); - border: none; - border-radius: 999px; - cursor: pointer; - font-size: 16px; - transition: transform 0.2s, box-shadow 0.2s; - color: white; - box-shadow: 0 10px 22px rgba(32, 76, 130, 0.25); -} - -.navbar-search-button:hover { - transform: translateY(-1px); - box-shadow: 0 14px 26px rgba(32, 76, 130, 0.32); -} - -.navbar-filter-controls { - display: flex; - gap: 12px; - align-items: center; -} - -.navbar-filter-select { - padding: 8px 12px; - border: 1px solid var(--color-border); - border-radius: 999px; - font-size: 14px; - background-color: var(--color-surface); - cursor: pointer; - color: var(--color-text); -} - -.navbar-filter-select:focus { - outline: none; - border-color: var(--color-primary); - box-shadow: 0 0 0 3px rgba(47, 128, 237, 0.18); -} - -.navbar-clear-button { - padding: 8px 12px; - background-color: transparent; - border: 1px solid var(--color-border); - border-radius: 999px; - cursor: pointer; - font-size: 14px; - white-space: nowrap; - transition: background-color 0.2s, color 0.2s; - color: var(--color-muted); -} - -.navbar-clear-button:hover { - background-color: rgba(47, 128, 237, 0.08); - color: var(--color-text); -} - -/* Mobile Responsive - Second Row Layout */ -@media (max-width: 1024px) { - .navbar-filters-container { - flex-direction: column; - align-items: stretch; - gap: 12px; - } - - .navbar-search-form { - max-width: 100%; - } - - .navbar-filter-controls { - justify-content: space-between; - flex-wrap: wrap; - } - - .navbar-filter-select { - flex: 1; - min-width: 120px; - } -} - -@media (max-width: 768px) { - .navbar-container { - padding: 8px 16px; - } - - .navbar-filters { - padding: 12px 16px; - } - - .navbar-menu { - gap: 12px; - } - - .logo-text { - display: none; - } - - .navbar-username { - display: none; - } - - .navbar-filter-controls { - gap: 8px; - } - - .navbar-clear-button { - width: 100%; - } -} diff --git a/frontend/src/components/Navbar/Navbar.tsx b/frontend/src/components/Navbar/Navbar.tsx index 508b7f2..ef812c8 100644 --- a/frontend/src/components/Navbar/Navbar.tsx +++ b/frontend/src/components/Navbar/Navbar.tsx @@ -89,15 +89,6 @@ export function Navbar() { Home - {isAuthenticated && ( - - Admin - - )} - {isAuthenticated ? ( -
+
onChannelChange(e.target.value || undefined)} - className="filter-select" + className="px-3 py-2 border border-border rounded-full text-sm bg-card cursor-pointer text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent md:flex-none flex-1" > {channels.map(channel => ( @@ -69,7 +71,7 @@ export function SearchFilter({ onSearch(''); onChannelChange(undefined); }} - className="clear-button" + className="px-3 py-2 bg-transparent border border-border rounded-full cursor-pointer text-sm whitespace-nowrap transition-colors text-muted-foreground hover:bg-primary/10 hover:text-foreground md:flex-none flex-1" > Clear Filters @@ -79,6 +81,3 @@ export function SearchFilter({
); } - - - diff --git a/frontend/src/components/TimeLimitManager/TimeLimitManager.css b/frontend/src/components/TimeLimitManager/TimeLimitManager.css deleted file mode 100644 index 35769f8..0000000 --- a/frontend/src/components/TimeLimitManager/TimeLimitManager.css +++ /dev/null @@ -1,268 +0,0 @@ -.time-limit-manager { - background: var(--color-surface); - border-radius: 12px; - padding: 24px; - border: 1px solid rgba(212, 222, 239, 0.8); - height: fit-content; -} - -.time-limit-header { - margin-bottom: 24px; -} - -.time-limit-header h2 { - margin: 0 0 8px 0; - font-size: 20px; - font-weight: 600; - color: var(--color-text); -} - -.time-limit-header p { - margin: 0; - font-size: 14px; - color: var(--color-muted); -} - -.time-limit-section { - display: flex; - flex-direction: column; - gap: 24px; -} - -.time-limit-setting { - display: flex; - flex-direction: column; - gap: 12px; -} - -.time-limit-setting label { - font-size: 14px; - font-weight: 500; - color: var(--color-text); -} - -.time-limit-input-group { - display: flex; - gap: 12px; - align-items: center; -} - -.time-limit-input { - flex: 1; - max-width: 200px; - padding: 10px 12px; - border: 1px solid rgba(212, 222, 239, 0.8); - border-radius: 6px; - font-size: 14px; - background: var(--color-surface-muted); - color: var(--color-text); - transition: border-color 0.2s; -} - -.time-limit-input:focus { - outline: none; - border-color: var(--color-primary); -} - -.time-limit-save-btn { - padding: 10px 20px; - background: linear-gradient(135deg, var(--color-primary), var(--color-secondary)); - color: white; - border: none; - border-radius: 6px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; -} - -.time-limit-save-btn:hover:not(:disabled) { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(47, 128, 237, 0.3); -} - -.time-limit-save-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.time-limit-hint { - margin: 0; - font-size: 13px; - color: var(--color-muted); -} - -.time-limit-status { - padding: 20px; - background: var(--color-surface-muted); - border-radius: 8px; - border: 1px solid rgba(212, 222, 239, 0.5); -} - -.time-limit-status h3 { - margin: 0 0 16px 0; - font-size: 16px; - font-weight: 600; - color: var(--color-text); -} - -.time-limit-progress { - display: flex; - flex-direction: column; - gap: 12px; -} - -.time-limit-progress-bar { - width: 100%; - height: 24px; - background: rgba(212, 222, 239, 0.3); - border-radius: 12px; - overflow: hidden; - position: relative; -} - -.time-limit-progress-fill { - height: 100%; - background: linear-gradient(90deg, #4a90e2, #357abd); - transition: width 0.3s ease; - border-radius: 12px; -} - -.time-limit-stats { - display: flex; - justify-content: space-between; - font-size: 14px; - color: var(--color-text); -} - -.time-used strong { - color: var(--color-primary); -} - -.time-remaining strong { - color: #4a90e2; -} - -.time-limit-actions { - margin-top: 16px; - padding-top: 16px; - border-top: 1px solid rgba(212, 222, 239, 0.5); -} - -.time-limit-reset-btn { - padding: 8px 16px; - background: transparent; - color: var(--color-primary); - border: 1px solid var(--color-primary); - border-radius: 6px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s, color 0.2s; -} - -.time-limit-reset-btn:hover { - background: var(--color-primary); - color: white; -} - -.time-limit-confirm-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; - backdrop-filter: blur(4px); -} - -.time-limit-confirm-modal { - background: var(--color-surface); - border-radius: 12px; - padding: 24px; - max-width: 400px; - width: 90%; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); -} - -.time-limit-confirm-modal h3 { - margin: 0 0 12px 0; - font-size: 18px; - font-weight: 600; - color: var(--color-text); -} - -.time-limit-confirm-modal p { - margin: 0 0 20px 0; - font-size: 14px; - color: var(--color-muted); - line-height: 1.5; -} - -.time-limit-confirm-actions { - display: flex; - gap: 12px; - justify-content: flex-end; -} - -.time-limit-confirm-btn { - padding: 10px 20px; - border: none; - border-radius: 6px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; -} - -.time-limit-confirm-btn.confirm { - background: linear-gradient(135deg, var(--color-primary), var(--color-secondary)); - color: white; -} - -.time-limit-confirm-btn.confirm:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(47, 128, 237, 0.3); -} - -.time-limit-confirm-btn.cancel { - background: transparent; - color: var(--color-text); - border: 1px solid rgba(212, 222, 239, 0.8); -} - -.time-limit-confirm-btn.cancel:hover { - background: var(--color-surface-muted); -} - -@media (max-width: 768px) { - .time-limit-manager { - padding: 16px; - } - - .time-limit-input-group { - flex-direction: column; - align-items: stretch; - } - - .time-limit-input { - max-width: 100%; - } - - .time-limit-stats { - flex-direction: column; - gap: 8px; - } - - .time-limit-confirm-modal { - padding: 20px; - } - - .time-limit-confirm-actions { - flex-direction: column; - } -} diff --git a/frontend/src/components/TimeLimitManager/TimeLimitManager.tsx b/frontend/src/components/TimeLimitManager/TimeLimitManager.tsx index b5cad16..acd2414 100644 --- a/frontend/src/components/TimeLimitManager/TimeLimitManager.tsx +++ b/frontend/src/components/TimeLimitManager/TimeLimitManager.tsx @@ -5,7 +5,6 @@ import { setDailyLimit, resetDailyCounter } from '../../services/timeLimitService'; -import './TimeLimitManager.css'; export function TimeLimitManager() { const [dailyLimit, setDailyLimitState] = useState(null); @@ -86,34 +85,36 @@ export function TimeLimitManager() { if (isLoading) { return ( -
-
-

Daily Time Limit Settings

-

Loading...

+
+
+

Daily Time Limit Settings

+

Loading...

); } return ( -
-
-

Daily Time Limit Settings

-

Configure how much time users can spend watching videos each day

+
+
+

Daily Time Limit Settings

+

+ Configure how much time users can spend watching videos each day +

{error && ( -
+
{error}
)} -
-
-