mirror of
https://github.com/wahyd4/heygo.git
synced 2026-08-27 22:36:02 +10:00
- LandingHero: hero section with Heygo service intro + search input - App.tsx: logged-in users auto-redirect to My Links - LinkDetailPage: click-based stats/history toggle (hidden by default) - 404 pages: styled with login prompt for private links - Private redirect: pass alias to 404/login pages for redirect flow - Mobile responsive: prevent text overflow on small screens
107 lines
6.4 KiB
TypeScript
107 lines
6.4 KiB
TypeScript
export function htmlResponse(body: string, init: ResponseInit = {}): Response {
|
|
const headers = new Headers(init.headers);
|
|
if (!headers.has('content-type')) {
|
|
headers.set('content-type', 'text/html; charset=utf-8');
|
|
}
|
|
|
|
return new Response(body, {
|
|
...init,
|
|
headers,
|
|
});
|
|
}
|
|
|
|
function withCookieVary(headers: Headers): Headers {
|
|
const vary = headers.get('vary');
|
|
const varies = vary?.split(',').map((value) => value.trim().toLowerCase()) ?? [];
|
|
if (!varies.includes('cookie')) {
|
|
headers.set('vary', vary ? `${vary}, Cookie` : 'Cookie');
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
function privateNoStoreHeaders(headersInit?: HeadersInit): Headers {
|
|
const headers = new Headers(headersInit);
|
|
headers.set('cache-control', 'no-store');
|
|
return withCookieVary(headers);
|
|
}
|
|
|
|
export function withPrivateNoStoreHeaders(response: Response): Response {
|
|
return new Response(response.body, {
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
headers: privateNoStoreHeaders(response.headers),
|
|
});
|
|
}
|
|
|
|
export function privateHtmlResponse(body: string, init: ResponseInit = {}): Response {
|
|
return htmlResponse(body, {
|
|
...init,
|
|
headers: privateNoStoreHeaders(init.headers),
|
|
});
|
|
}
|
|
|
|
export function privateRedirectResponse(location: string, status = 302): Response {
|
|
return new Response(null, {
|
|
status,
|
|
headers: privateNoStoreHeaders({ location }),
|
|
});
|
|
}
|
|
|
|
export function publicNotFoundResponse(): Response {
|
|
return htmlResponse(
|
|
'<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Not found</title><style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;display:flex;justify-content:center;align-items:center;min-height:80vh;margin:0;background:#f1f5f9;color:#0f172a}.card{background:#fff;border:1px solid #e2e8f0;border-radius:16px;padding:2rem;max-width:480px;text-align:center;box-shadow:0 1px 3px rgba(15,23,42,.08)}h1{font-size:1.5rem;font-weight:700;margin:0 0 .5rem;letter-spacing:-.02em}p{color:#64748b;font-size:.9375rem;line-height:1.5;margin:0 0 .75rem}.btn{display:inline-block;background:#6366f1;color:#fff;border-radius:8px;padding:.5rem 1rem;font-size:.9rem;font-weight:500;text-decoration:none;margin-top:.75rem}.btn:hover{background:#4f46e5}.note{font-size:.8125rem;color:#94a3b8;margin-top:.5rem}</style></head><body><div class="card"><h1>Not found</h1><p>This short link does not exist.</p><p class="note">If this is a private link, you may need to <a href="/app/login">sign in</a> to access it.</p></div></body></html>',
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
|
|
export function publicBadRequestResponse(message = 'Invalid short link'): Response {
|
|
return htmlResponse(
|
|
`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Invalid short link</title></head><body><h1>Invalid short link</h1><p>${escapeHtml(message)}</p></body></html>`,
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Build the app login URL for a given environment's APP_BASE_URL.
|
|
* Kept as a function (not a constant) so each deployment's base URL is honored.
|
|
*/
|
|
export function privateLoginUrl(appBaseUrl: string): string {
|
|
return `${appBaseUrl}/app/login`;
|
|
}
|
|
|
|
/**
|
|
* Build the private links app URL for a given environment's APP_BASE_URL.
|
|
*/
|
|
export function privateAppUrl(appBaseUrl: string): string {
|
|
return `${appBaseUrl}/app/private`;
|
|
}
|
|
|
|
export function privateLoginRequiredResponse(appBaseUrl: string, alias?: string): Response {
|
|
const loginUrl = alias
|
|
? `${appBaseUrl}/app/login?redirect=${encodeURIComponent('/' + alias)}`
|
|
: privateLoginUrl(appBaseUrl);
|
|
return privateHtmlResponse(
|
|
`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Login required</title><style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;display:flex;justify-content:center;align-items:center;min-height:80vh;margin:0;background:#f1f5f9;color:#0f172a}.card{background:#fff;border:1px solid #e2e8f0;border-radius:16px;padding:2rem;max-width:480px;text-align:center;box-shadow:0 1px 3px rgba(15,23,42,.08)}h1{font-size:1.5rem;font-weight:700;margin:0 0 .5rem;letter-spacing:-.02em}p{color:#64748b;font-size:.9375rem;line-height:1.5;margin:0 0 .75rem}.btn{display:inline-block;background:#6366f1;color:#fff;border-radius:8px;padding:.6rem 1.25rem;font-size:.9rem;font-weight:500;text-decoration:none;transition:background .15s}.btn:hover{background:#4f46e5}.note{font-size:.8125rem;color:#94a3b8;margin-top:.5rem}</style></head><body><div class="card"><h1>Not found</h1><p>This link may be a private link that only you can see.</p><p>If you created this link, <strong>sign in</strong> below and you will be redirected automatically.</p><a class="btn" href="${loginUrl}">Sign in to access</a><p class="note">Not your link? It may not exist — double-check the alias.</p></div></body></html>`,
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
|
|
export function privateAliasNotFoundResponse(appBaseUrl: string, alias?: string): Response {
|
|
const createUrl = alias
|
|
? `${appBaseUrl}/app/private?alias=${encodeURIComponent(alias)}`
|
|
: privateAppUrl(appBaseUrl);
|
|
return privateHtmlResponse(
|
|
`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Not found</title><style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;display:flex;justify-content:center;align-items:center;min-height:80vh;margin:0;background:#f1f5f9;color:#0f172a}.card{background:#fff;border:1px solid #e2e8f0;border-radius:16px;padding:2rem;max-width:480px;text-align:center;box-shadow:0 1px 3px rgba(15,23,42,.08)}h1{font-size:1.5rem;font-weight:700;margin:0 0 .5rem;letter-spacing:-.02em}p{color:#64748b;font-size:.9375rem;line-height:1.5;margin:0 0 .75rem}.btn{display:inline-block;background:#6366f1;color:#fff;border-radius:8px;padding:.6rem 1.25rem;font-size:.9rem;font-weight:500;text-decoration:none;transition:background .15s}.btn:hover{background:#4f46e5}</style></head><body><div class="card"><h1>Not found</h1><p>This private link does not exist or has been deleted.</p><a class="btn" href="${createUrl}">Create this link</a></div></body></html>`,
|
|
{ status: 404 },
|
|
);
|
|
}
|
|
|
|
export function escapeHtml(value: string): string {
|
|
return value
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|