Add darkmode for oAuth screen (#21005)

## after

<img width="1512" height="828" alt="image"
src="https://github.com/user-attachments/assets/71664eab-c921-45da-ac67-7a660c976d5c"
/>
This commit is contained in:
martmull
2026-05-28 15:48:57 +02:00
committed by GitHub
parent bb4e28904f
commit 6fb6ef4e7a
2 changed files with 54 additions and 17 deletions
@@ -134,7 +134,7 @@ const OAUTH_SCOPE_ICONS: { [scope: string]: IconComponent | undefined } = {
export const Authorize = () => {
const { t } = useLingui();
const { theme } = useContext(ThemeContext);
const { theme, colorScheme } = useContext(ThemeContext);
const navigate = useNavigateApp();
const [searchParam] = useSearchParams();
const { redirect } = useRedirect();
@@ -175,6 +175,18 @@ export const Authorize = () => {
}
}, [shouldRedirectToNotFound, navigate]);
const appendThemeToUrl = (urlString: string) => {
try {
const url = new URL(urlString);
url.searchParams.set('theme', colorScheme);
return url.toString();
} catch {
return urlString;
}
};
const handleAuthorize = async () => {
if (isDefined(clientId) && isDefined(redirectUrl)) {
setIsAuthorizing(true);
@@ -188,7 +200,7 @@ export const Authorize = () => {
state: state ?? undefined,
},
onCompleted: (responseData) => {
redirect(responseData.authorizeApp.redirectUrl);
redirect(appendThemeToUrl(responseData.authorizeApp.redirectUrl));
},
onError: (error) => {
setIsAuthorizing(false);
@@ -17,12 +17,14 @@ const pageHtml = ({
title,
message,
isSuccess,
isDarkMode,
}: {
title: string;
message: string;
isSuccess: boolean;
isDarkMode: boolean;
}) => `<!DOCTYPE html>
<html lang="en">
<html lang="en"${isDarkMode ? ' class="dark"' : ''}>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -31,20 +33,40 @@ const pageHtml = ({
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg-body: #fafafa;
--bg-card: #fff;
--bg-logo-tile: #fff;
--text-primary: #333;
--text-secondary: #666;
--shadow: 2px 4px 16px 0 rgba(0,0,0,0.16), 0 2px 4px 0 rgba(0,0,0,0.08);
--success-bg: #f0faf0;
--error-bg: #fef0f0;
}
html.dark {
--bg-body: #171717;
--bg-card: #1b1b1b;
--bg-logo-tile: #2b2b2b;
--text-primary: #e6e6e6;
--text-secondary: #999;
--shadow: 2px 4px 16px 0 rgba(0,0,0,0.5), 0 2px 4px 0 rgba(0,0,0,0.3);
--success-bg: #0a2e0a;
--error-bg: #2e0a0a;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: #fafafa;
background: var(--bg-body);
display: flex;
align-items: center;
justify-content: center;
min-height: 100dvh;
color: #333;
color: var(--text-primary);
}
.card {
background: #fff;
background: var(--bg-card);
border-radius: 8px;
box-shadow: 2px 4px 16px 0 rgba(0,0,0,0.16), 0 2px 4px 0 rgba(0,0,0,0.08);
box-shadow: var(--shadow);
max-width: min(100%, 360px);
width: fit-content;
display: flex;
@@ -66,9 +88,9 @@ const pageHtml = ({
width: 36px;
height: 36px;
padding: 4px;
background: #fff;
background: var(--bg-logo-tile);
border-radius: 100%;
box-shadow: 2px 4px 16px 0 rgba(0,0,0,0.16), 0 2px 4px 0 rgba(0,0,0,0.08);
box-shadow: var(--shadow);
display: flex;
align-items: center;
justify-content: center;
@@ -86,8 +108,8 @@ const pageHtml = ({
width: 20px;
height: 20px;
}
.status-icon-success { background: #f0faf0; }
.status-icon-error { background: #fef0f0; }
.status-icon-success { background: var(--success-bg); }
.status-icon-error { background: var(--error-bg); }
.content {
display: flex;
flex-direction: column;
@@ -98,14 +120,14 @@ const pageHtml = ({
font-size: 1rem;
font-weight: 500;
line-height: 1.1;
color: #333;
color: var(--text-primary);
text-align: center;
text-wrap: balance;
margin-bottom: 16px;
}
p {
font-size: 0.82rem;
color: #666;
color: var(--text-secondary);
text-align: center;
line-height: 1.5;
}
@@ -132,11 +154,12 @@ const pageHtml = ({
</body>
</html>`;
const successHtml = () =>
const successHtml = (isDarkMode: boolean) =>
pageHtml({
title: 'Authentication successful',
message: 'You can close this window and return to the terminal.',
isSuccess: true,
isDarkMode,
});
const escapeHtml = (text: string): string =>
@@ -147,11 +170,12 @@ const escapeHtml = (text: string): string =>
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
const errorHtml = (error: string) =>
const errorHtml = (error: string, isDarkMode: boolean) =>
pageHtml({
title: 'Authentication failed',
message: `${escapeHtml(error)}<br>Please return to the terminal and try again.`,
isSuccess: false,
isDarkMode,
});
export const startCallbackServer = (options?: {
@@ -179,6 +203,7 @@ export const startCallbackServer = (options?: {
const code = url.searchParams.get('code');
const error = url.searchParams.get('error');
const isDarkMode = url.searchParams.get('theme') === 'dark';
const headers = {
'Content-Type': 'text/html',
@@ -187,14 +212,14 @@ export const startCallbackServer = (options?: {
if (code) {
res.writeHead(200, headers);
res.end(successHtml());
res.end(successHtml(isDarkMode));
callbackResolve({ success: true, code });
} else {
const errorMessage =
error ?? url.searchParams.get('error_description') ?? 'Unknown error';
res.writeHead(200, headers);
res.end(errorHtml(errorMessage));
res.end(errorHtml(errorMessage, isDarkMode));
callbackResolve({ success: false, error: errorMessage });
}
});