Skip to content

Commit 44b7866

Browse files
committed
Surface UI auth and preview errors
1 parent ed109c5 commit 44b7866

2 files changed

Lines changed: 89 additions & 11 deletions

File tree

frontend/src/components/App.tsx

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ export function App() {
2222

2323
const [showAuthForm, setShowAuthForm] = useState(false);
2424
const [authFormData, setAuthFormData] = useState({ username: '', token: '' });
25+
const [authFieldErrors, setAuthFieldErrors] = useState({ username: '', token: '', form: '' });
2526
const [feedFormData, setFeedFormData] = useState({ url: '', strategy: 'ssrf_filter' });
27+
const [feedFieldErrors, setFeedFieldErrors] = useState({ url: '', form: '' });
28+
const [demoError, setDemoError] = useState('');
2629

2730
useEffect(() => {
2831
if (isAuthenticated) {
@@ -39,21 +42,47 @@ export function App() {
3942
const handleAuthSubmit = async (event?: Event) => {
4043
event?.preventDefault();
4144

42-
if (!authFormData.username || !authFormData.token) return;
45+
setAuthFieldErrors({ username: '', token: '', form: '' });
46+
47+
if (!authFormData.username.trim()) {
48+
setAuthFieldErrors({ username: 'Username is required.', token: '', form: '' });
49+
return;
50+
}
51+
if (!authFormData.token.trim()) {
52+
setAuthFieldErrors({ username: '', token: 'Token is required.', form: '' });
53+
return;
54+
}
4355

4456
try {
4557
await login(authFormData.username, authFormData.token);
46-
} catch (error) {}
58+
} catch (error) {
59+
setAuthFieldErrors({
60+
username: '',
61+
token: '',
62+
form: error instanceof Error ? error.message : 'Unable to authenticate. Please try again.',
63+
});
64+
}
4765
};
4866

4967
const handleFeedSubmit = async (event: Event) => {
5068
event.preventDefault();
5169

52-
if (!feedFormData.url) return;
70+
setFeedFieldErrors({ url: '', form: '' });
71+
if (!feedFormData.url.trim()) {
72+
setFeedFieldErrors({ url: 'Website URL is required.', form: '' });
73+
return;
74+
}
5375

5476
try {
5577
await convertFeed(feedFormData.url, feedFormData.strategy, token || '');
56-
} catch (error) {}
78+
} catch (error) {
79+
const message = error instanceof Error ? error.message : 'Unable to start conversion.';
80+
if (message.toLowerCase().includes('url')) {
81+
setFeedFieldErrors({ url: message, form: '' });
82+
} else {
83+
setFeedFieldErrors({ url: '', form: message });
84+
}
85+
}
5786
};
5887

5988
const handleShowAuth = () => {
@@ -67,10 +96,13 @@ export function App() {
6796
};
6897

6998
const handleDemoConversion = async (url: string) => {
99+
setDemoError('');
70100
try {
71101
const demoStrategy = strategies.length > 0 ? strategies[0].id : 'ssrf_filter';
72102
await convertFeed(url, demoStrategy, 'self-host-for-full-access');
73-
} catch (error) {}
103+
} catch (error) {
104+
setDemoError(error instanceof Error ? error.message : 'Demo conversion failed. Please try again.');
105+
}
74106
};
75107

76108
const showResultExperience = Boolean(result);
@@ -126,6 +158,11 @@ export function App() {
126158
<p class="surface-header__hint">
127159
Launch a demo conversion to see the results instantly. No sign-in required.
128160
</p>
161+
{demoError && (
162+
<div class="notice notice--error" role="alert">
163+
<p>{demoError}</p>
164+
</div>
165+
)}
129166
</header>
130167
<DemoButtons onConvert={handleDemoConversion} />
131168
<QuickLogin onShowLogin={handleShowAuth} />
@@ -136,10 +173,24 @@ export function App() {
136173
<section class="surface">
137174
<header class="surface-header">
138175
<h3 class="surface-header__title">🔐 Sign in</h3>
139-
<p class="surface-header__hint">Use your html2rss credentials to convert any website.</p>
176+
<p class="surface-header__hint">
177+
Use your html2rss credentials to convert any website. Tokens are stored for this browser session.
178+
</p>
179+
<p class="surface-header__hint">
180+
Need a token? Ask your html2rss-web admin or see the{' '}
181+
<a href="https://html2rss.github.io/" target="_blank" rel="noopener noreferrer">
182+
official docs
183+
</a>
184+
.
185+
</p>
140186
</header>
141187

142188
<form id="auth-section" class="form" onSubmit={handleAuthSubmit}>
189+
{authFieldErrors.form && (
190+
<div class="notice notice--error" role="alert">
191+
<p>{authFieldErrors.form}</p>
192+
</div>
193+
)}
143194
<div class="field">
144195
<label for="username" class="label" data-required>
145196
Username
@@ -157,7 +208,9 @@ export function App() {
157208
setAuthFormData({ ...authFormData, username: (e.target as HTMLInputElement).value })
158209
}
159210
/>
160-
<div class="field-error" id="username-error"></div>
211+
<div class="field-error" id="username-error">
212+
{authFieldErrors.username}
213+
</div>
161214
</div>
162215

163216
<div class="field">
@@ -177,7 +230,9 @@ export function App() {
177230
setAuthFormData({ ...authFormData, token: (e.target as HTMLInputElement).value })
178231
}
179232
/>
180-
<div class="field-error" id="token-error"></div>
233+
<div class="field-error" id="token-error">
234+
{authFieldErrors.token}
235+
</div>
181236
</div>
182237

183238
<div class="form-actions">
@@ -231,7 +286,9 @@ export function App() {
231286
{isConverting ? 'Converting...' : 'Convert'}
232287
</button>
233288
</div>
234-
<div class="field-error" id="url-error"></div>
289+
<div class="field-error" id="url-error">
290+
{feedFieldErrors.url}
291+
</div>
235292
</div>
236293

237294
<fieldset class="fieldset">
@@ -280,13 +337,19 @@ export function App() {
280337
)}
281338
</fieldset>
282339
</form>
340+
{feedFieldErrors.form && (
341+
<div class="notice notice--error" role="alert">
342+
<p>{feedFieldErrors.form}</p>
343+
</div>
344+
)}
283345
</section>
284346
)}
285347

286348
{!showResultExperience && error && (
287349
<section class="notice notice--error">
288350
<h3>Conversion error</h3>
289351
<p>{error}</p>
352+
<p>Double-check your token if this keeps happening.</p>
290353
<button type="button" class="btn btn--outline" onClick={clearResult}>
291354
Close
292355
</button>

frontend/src/components/ResultDisplay.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export function ResultDisplay({ result, onClose, isAuthenticated, username, onLo
2424
const [previewMode, setPreviewMode] = useState<PreviewMode>('preview');
2525
const [xmlContent, setXmlContent] = useState<string>('');
2626
const [isLoadingXml, setIsLoadingXml] = useState(false);
27+
const [xmlError, setXmlError] = useState('');
28+
const [copyNotice, setCopyNotice] = useState('');
2729
const previewTabId = 'result-preview-tab-preview';
2830
const xmlTabId = 'result-preview-tab-xml';
2931
const panelId = 'result-preview-panel';
@@ -48,12 +50,14 @@ export function ResultDisplay({ result, onClose, isAuthenticated, username, onLo
4850

4951
const loadRawXml = async () => {
5052
setIsLoadingXml(true);
53+
setXmlError('');
5154
try {
5255
const response = await fetch(fullUrl);
5356
const content = await response.text();
5457
setXmlContent(content);
5558
} catch (error) {
56-
setXmlContent(`Error loading XML: ${error instanceof Error ? error.message : 'Unknown error'}`);
59+
setXmlError(error instanceof Error ? error.message : 'Unable to load XML preview.');
60+
setXmlContent('');
5761
} finally {
5862
setIsLoadingXml(false);
5963
}
@@ -62,8 +66,10 @@ export function ResultDisplay({ result, onClose, isAuthenticated, username, onLo
6266
const copyToClipboard = async (text: string) => {
6367
try {
6468
await navigator.clipboard.writeText(text);
69+
setCopyNotice('Copied to clipboard.');
70+
window.setTimeout(() => setCopyNotice(''), 3000);
6571
} catch (error) {
66-
console.error('Failed to copy:', error);
72+
setCopyNotice('Clipboard copy failed. Please copy the URL manually.');
6773
}
6874
};
6975

@@ -96,6 +102,11 @@ export function ResultDisplay({ result, onClose, isAuthenticated, username, onLo
96102
<span>Open feed in new tab</span>
97103
</a>
98104
</div>
105+
{copyNotice && (
106+
<div class="notice" role="status">
107+
<p>{copyNotice}</p>
108+
</div>
109+
)}
99110
</header>
100111

101112
{isAuthenticated && (
@@ -159,6 +170,10 @@ export function ResultDisplay({ result, onClose, isAuthenticated, username, onLo
159170
<div class={styles.previewXml}>
160171
{isLoadingXml ? (
161172
<div class={styles.previewLoading}>Loading raw XML...</div>
173+
) : xmlError ? (
174+
<div class="notice notice--error" role="alert">
175+
<p>Failed to load XML preview: {xmlError}</p>
176+
</div>
162177
) : (
163178
<pre>
164179
<code>{xmlContent}</code>

0 commit comments

Comments
 (0)