diff options
| author | A Farzat <a@farzat.xyz> | 2025-10-08 15:26:43 +0300 |
|---|---|---|
| committer | A Farzat <a@farzat.xyz> | 2025-10-08 15:26:43 +0300 |
| commit | 05607dc845a1dd111fd393478acf084ca3532081 (patch) | |
| tree | 4c8cb78e591d9f76983cb68eee265fef85addd1f /front-end | |
| parent | d93a91867baff153e0d57655996726914f042888 (diff) | |
| download | csca5028-05607dc845a1dd111fd393478acf084ca3532081.tar.gz csca5028-05607dc845a1dd111fd393478acf084ca3532081.zip | |
Add the ability to add subscriptions
Diffstat (limited to 'front-end')
| -rw-r--r-- | front-end/src/App.css | 136 | ||||
| -rw-r--r-- | front-end/src/App.jsx | 128 |
2 files changed, 264 insertions, 0 deletions
diff --git a/front-end/src/App.css b/front-end/src/App.css index 0220950..19cddd2 100644 --- a/front-end/src/App.css +++ b/front-end/src/App.css @@ -92,6 +92,142 @@ body { color: #74b9ff; } +.subscription-section { + max-width: 600px; + margin: 1.5rem auto 0; +} + +.add-subscription-toggle { + padding: 0.6rem 1.2rem; + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 20px; + background: rgba(255, 255, 255, 0.15); + color: white; + cursor: pointer; + font-size: 0.9rem; + transition: all 0.3s ease; +} + +.add-subscription-toggle:hover { + background: rgba(255, 255, 255, 0.25); + transform: translateY(-1px); +} + +.subscription-form-container { + margin-top: 1.5rem; + padding: 1.5rem; + background: rgba(255, 255, 255, 0.1); + border-radius: 15px; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.subscription-form h3 { + color: white; + margin-bottom: 1.5rem; + text-align: center; + font-size: 1.3rem; +} + +.form-group { + margin-bottom: 1.5rem; + text-align: left; +} + +.form-group label { + display: block; + color: white; + margin-bottom: 0.5rem; + font-weight: bold; +} + +.subscription-input { + width: 100%; + padding: 0.8rem 1rem; + border: none; + border-radius: 10px; + background: rgba(255, 255, 255, 0.9); + font-size: 1rem; + box-sizing: border-box; +} + +.subscription-input:focus { + outline: none; + box-shadow: 0 0 0 2px rgba(116, 185, 255, 0.5); +} + +.interval-help { + display: block; + color: rgba(255, 255, 255, 0.7); + font-size: 0.8rem; + margin-top: 0.3rem; +} + +.subscription-error { + color: #ff6b6b; + background: rgba(255, 107, 107, 0.1); + padding: 0.8rem; + border-radius: 8px; + border-left: 3px solid #ff6b6b; + margin-bottom: 1rem; + font-size: 0.9rem; +} + +.subscription-success { + color: #51cf66; + background: rgba(81, 207, 102, 0.1); + padding: 0.8rem; + border-radius: 8px; + border-left: 3px solid #51cf66; + margin-bottom: 1rem; + font-size: 0.9rem; +} + +.form-actions { + text-align: center; +} + +.submit-subscription { + padding: 0.8rem 2rem; + border: none; + border-radius: 20px; + background: #51cf66; + color: white; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + font-size: 1rem; +} + +.submit-subscription:hover:not(:disabled) { + background: #69db7c; + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(81, 207, 102, 0.3); +} + +.submit-subscription:disabled { + background: rgba(255, 255, 255, 0.3); + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +/* Responsive design updates */ +@media (max-width: 768px) { + .subscription-form-container { + padding: 1rem; + margin-top: 1rem; + } + + .subscription-form h3 { + font-size: 1.1rem; + } + + .submit-subscription { + width: 100%; + } +} + .main-content { padding: 2rem; max-width: 1200px; diff --git a/front-end/src/App.jsx b/front-end/src/App.jsx index 92d0adf..4215650 100644 --- a/front-end/src/App.jsx +++ b/front-end/src/App.jsx @@ -13,6 +13,12 @@ function App() { const [error, setError] = useState(null); const [selectedChannelId, setSelectedChannelId] = useState(''); const [expandedDescriptions, setExpandedDescriptions] = useState({}); + const [showAddSubscription, setShowAddSubscription] = useState(false); + const [subscriptionUrl, setSubscriptionUrl] = useState(''); + const [timeBetweenFetches, setTimeBetweenFetches] = useState(300); // Default 5 minutes + const [addingSubscription, setAddingSubscription] = useState(false); + const [subscriptionError, setSubscriptionError] = useState(null); + const [subscriptionSuccess, setSubscriptionSuccess] = useState(null); const fetchChannels = async () => { try { @@ -138,6 +144,59 @@ function App() { } }; + const handleAddSubscription = async (e) => { + e.preventDefault(); + + if (!subscriptionUrl.trim()) { + setSubscriptionError('Please enter a YouTube channel/playlist URL'); + return; + } + + try { + setAddingSubscription(true); + setSubscriptionError(null); + setSubscriptionSuccess(null); + + const formData = new FormData(); + formData.append('url', subscriptionUrl.trim()); + formData.append('time_between_fetches', timeBetweenFetches.toString()); + + const response = await axios.post(`${API_BASE_URL}/add-sub/`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + if (response.data) { + setSubscriptionSuccess('Subscription added successfully!'); + setSubscriptionUrl(''); + setTimeBetweenFetches(300); + setShowAddSubscription(false); + + // Refresh the subs list to include the new subscription + fetchChannels(); + } else { + throw new Error(response.data.error || 'Failed to add subscription'); + } + } catch (err) { + console.error('Error adding subscription:', err); + setSubscriptionError( + err.response?.data?.error || + err.message || + 'Failed to add subscription. Please check the URL and try again.' + ); + } finally { + setAddingSubscription(false); + } + }; + + const resetSubscriptionForm = () => { + setSubscriptionUrl(''); + setTimeBetweenFetches(300); + setSubscriptionError(null); + setSubscriptionSuccess(null); + }; + return ( <div className="App"> <header className="App-header"> @@ -189,6 +248,75 @@ function App() { </div> )} </div> + <div className="subscription-section"> + <button + onClick={() => { + setShowAddSubscription(!showAddSubscription); + resetSubscriptionForm(); + }} + className="add-subscription-toggle" + > + {showAddSubscription ? '✕ Cancel' : '+ Add Subscription'} + </button> + + {showAddSubscription && ( + <div className="subscription-form-container"> + <form onSubmit={handleAddSubscription} className="subscription-form"> + <h3>Add New Subscription</h3> + + <div className="form-group"> + <label htmlFor="subscription-url">YouTube Channel URL:</label> + <input + id="subscription-url" + type="url" + value={subscriptionUrl} + onChange={(e) => setSubscriptionUrl(e.target.value)} + placeholder="https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxx" + className="subscription-input" + disabled={addingSubscription} + /> + </div> + + <div className="form-group"> + <label htmlFor="time-between-fetches"> + Fetch Interval (seconds): + </label> + <input + id="time-between-fetches" + type="number" + value={timeBetweenFetches} + onChange={(e) => setTimeBetweenFetches(parseInt(e.target.value) || 300)} + min="60" + max="86400" + className="subscription-input" + disabled={addingSubscription} + /> + <small className="interval-help"> + How often to check for new videos (default: 300s = 5 minutes) + </small> + </div> + + {subscriptionError && ( + <div className="subscription-error">{subscriptionError}</div> + )} + + {subscriptionSuccess && ( + <div className="subscription-success">{subscriptionSuccess}</div> + )} + + <div className="form-actions"> + <button + type="submit" + className="submit-subscription" + disabled={addingSubscription || !subscriptionUrl.trim()} + > + {addingSubscription ? 'Adding...' : 'Add Subscription'} + </button> + </div> + </form> + </div> + )} + </div> </header> <main className="main-content"> |
