({
email,
redirectUri,
state,
}: {
email: string;
redirectUri: URL;
state: Option.Option<string>;
})
| 116 | }; |
| 117 | |
| 118 | const renderConsentPage = ({ |
| 119 | email, |
| 120 | redirectUri, |
| 121 | state, |
| 122 | }: { |
| 123 | email: string; |
| 124 | redirectUri: URL; |
| 125 | state: Option.Option<string>; |
| 126 | }) => { |
| 127 | const stateField = Option.isSome(state) |
| 128 | ? `<input type="hidden" name="state" value="${escapeHtml(state.value)}" />` |
| 129 | : ""; |
| 130 | // The form action is resolved relative to this page's own URL |
| 131 | // (.../auth/start -> .../auth/approve) so it works under any API mount |
| 132 | // prefix without hardcoding it here. |
| 133 | return HttpServerResponse.html(`<!doctype html> |
| 134 | <html lang="en"> |
| 135 | <head> |
| 136 | <meta charset="utf-8" /> |
| 137 | <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| 138 | <meta name="robots" content="noindex" /> |
| 139 | <title>Connect Cap</title> |
| 140 | <style> |
| 141 | body { margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center; font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; background: #f4f4f5; color: #18181b; } |
| 142 | .card { background: #fff; border: 1px solid #e4e4e7; border-radius: 16px; padding: 32px; max-width: 400px; width: 100%; margin: 16px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); } |
| 143 | h1 { font-size: 18px; margin: 0 0 12px; } |
| 144 | p { font-size: 14px; line-height: 1.5; color: #52525b; margin: 0 0 12px; } |
| 145 | .email { font-weight: 600; color: #18181b; } |
| 146 | .actions { display: flex; gap: 12px; margin: 24px 0 0; } |
| 147 | .actions > * { flex: 1; display: flex; align-items: center; justify-content: center; height: 40px; border-radius: 10px; font-size: 14px; font-weight: 500; cursor: pointer; text-decoration: none; box-sizing: border-box; } |
| 148 | button { background: #18181b; color: #fff; border: none; } |
| 149 | a.cancel { background: #fff; color: #18181b; border: 1px solid #d4d4d8; } |
| 150 | </style> |
| 151 | </head> |
| 152 | <body> |
| 153 | <main class="card"> |
| 154 | <h1>Connect the Cap Chrome extension</h1> |
| 155 | <p>The Cap extension is asking for access to your Cap account <span class="email">${escapeHtml(email)}</span> to create and upload recordings on your behalf.</p> |
| 156 | <p>Only continue if you opened this page from the Cap extension.</p> |
| 157 | <form method="post" action="approve" class="actions"> |
| 158 | <input type="hidden" name="redirectUri" value="${escapeHtml(redirectUri.toString())}" /> |
| 159 | ${stateField} |
| 160 | <a class="cancel" href="${escapeHtml(buildCancelUrl(redirectUri, state).toString())}">Cancel</a> |
| 161 | <button type="submit">Allow access</button> |
| 162 | </form> |
| 163 | </main> |
| 164 | </body> |
| 165 | </html>`); |
| 166 | }; |
| 167 | |
| 168 | const redirectWithAuthKey = ({ |
| 169 | redirectUri, |
no test coverage detected