Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Get started: [Quick Demo](#get-started-quick-demo) or [Self-Hosting](#get-starte

The built-in control plane centralizes gateway configuration, access management, traffic controls, and operations in one self-hosted interface.

![Doorman API catalog](docs/images/api-catalog.png)
![Doorman control plane](docs/images/control-plane.png)

- **API lifecycle:** catalog and visual builder, protocol and endpoint configuration, schema validation, and API discovery.
- **Access and consumption:** authentication, users, roles, groups, subscriptions, credits, tiers, and quotas.
Expand Down
Binary file removed docs/images/api-catalog.png
Binary file not shown.
Binary file added docs/images/control-plane.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified web-client/public/android-chrome-192x192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified web-client/public/android-chrome-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified web-client/public/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified web-client/public/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified web-client/public/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 34 additions & 1 deletion web-client/src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,39 @@ import { postJson, getJson } from '@/utils/api'

const DEFAULT_AUTH_REDIRECT = '/dashboard'

function SiteHeader() {
const [open, setOpen] = useState(false)
const links: [string, string][] = [['Gateways', 'https://doorman.dev/#platform'], ['Capabilities', 'https://doorman.dev/#features'], ['Deploy', 'https://doorman.dev/#deploy']]
return (
<header className="sh">
<div className="sh-container">
<div className="sh-inner">
<a href="https://doorman.dev/" className="sh-brand" aria-label="Doorman home">
<img src="/doorman-mark.svg" alt="" aria-hidden="true" />
<span><span className="sh-name">Doorman</span><span className="sh-sub">API + AI Gateway</span></span>
</a>
<nav className="sh-nav" aria-label="Primary navigation">
{links.map(([label, href]) => <a key={href} href={href}>{label}</a>)}
<a href="https://github.com/doorman-dev/doorman#readme" target="_blank" rel="noopener noreferrer">Docs</a>
</nav>
<div className="sh-actions">
<a href="https://github.com/doorman-dev/doorman" target="_blank" rel="noopener noreferrer" className="sh-gh" aria-label="GitHub repository" title="View on GitHub"><svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 .5A12 12 0 0 0 0 12.7c0 5.37 3.44 9.92 8.21 11.53.6.11.82-.27.82-.6 0-.3-.01-1.1-.02-2.17-3.34.75-4.04-1.65-4.04-1.65-.55-1.42-1.34-1.8-1.34-1.8-1.1-.77.08-.76.08-.76 1.22.09 1.86 1.28 1.86 1.28 1.08 1.9 2.84 1.35 3.53 1.03.11-.81.42-1.35.76-1.66-2.67-.31-5.47-1.39-5.47-6.21 0-1.37.46-2.49 1.22-3.37-.12-.3-.53-1.56.12-3.25 0 0 1.01-.33 3.3 1.29a11.3 11.3 0 0 1 6 0c2.3-1.62 3.3-1.29 3.3-1.29.66 1.69.25 2.95.12 3.25.76.88 1.22 2 1.22 3.37 0 4.83-2.8 5.89-5.47 6.2.43.37.81 1.1.81 2.22 0 1.6-.01 2.9-.01 3.3 0 .33.22.72.83.6A12.2 12.2 0 0 0 24 12.7 12.1 12.1 0 0 0 12 .5z" /></svg></a>
<a href="/login" className="sh-btn">Log in</a>
<a href="https://app.doorman.dev/signup" className="sh-btn sh-btn--primary">Get Doorman</a>
<button type="button" className="sh-burger" aria-label="Toggle menu" aria-expanded={open} onClick={() => setOpen(!open)}><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M4 7h16M4 12h16M4 17h16" /></svg></button>
</div>
</div>
<nav className={`sh-mobile${open ? ' open' : ''}`} aria-label="Mobile navigation">
{links.map(([label, href]) => <a key={href} href={href} onClick={() => setOpen(false)}>{label}</a>)}
<a href="https://github.com/doorman-dev/doorman#readme" target="_blank" rel="noopener noreferrer">Documentation</a>
<a href="/login">Log in</a>
<a href="https://app.doorman.dev/signup" className="sh-btn sh-btn--primary">Get Doorman</a>
</nav>
</div>
</header>
)
}

function getSafeNextPath(nextValue: string | null): string {
const candidate = nextValue
? (() => {
Expand Down Expand Up @@ -72,7 +105,7 @@ function LoginPageContent() {
} finally { setIsLoading(false) }
}

return <main className="login-signal min-h-screen"><section className="login-signal__panel"><header className="login-signal__header"><a href="https://doorman.dev" className="login-signal__brand" aria-label="Doorman"><span className="login-signal__mark" aria-hidden="true"><img src="/doorman-mark.svg" alt="" /></span><span><strong>Doorman</strong><small>API + AI Gateway</small></span></a><p className="signal-kicker">Gateway access</p><h1>Sign in.</h1><p>Use your Doorman account to manage gateway configuration, traffic, and access control.</p></header><form onSubmit={handleLogin} className="login-signal__form"><div><label htmlFor="email">Email</label><input id="email" type="email" value={email} onChange={event => setEmail(event.target.value)} required autoComplete="email" placeholder="you@company.com" className="input" disabled={isLoading} /></div><div><label htmlFor="password">Password</label><input id="password" type="password" value={password} onChange={event => setPassword(event.target.value)} required autoComplete="current-password" placeholder="Enter your password" className="input" disabled={isLoading} /></div>{errorMessage && <div className="login-signal__error">{errorMessage}</div>}<button type="submit" disabled={isLoading} className="signal-button signal-button--primary w-full">{isLoading ? 'Signing in…' : 'Sign in'}</button></form><footer>By signing in, you agree to our <a href="/terms">Terms</a> and <a href="/privacy">Privacy Policy</a>.</footer></section></main>
return <><SiteHeader /><main className="login-signal"><section className="login-signal__panel"><header className="login-signal__header"><p className="signal-kicker">Gateway access</p><h1>Sign in to Doorman</h1><p>Use your Doorman account to manage gateway configuration, traffic, and access control.</p></header><form onSubmit={handleLogin} className="login-signal__form"><div><label htmlFor="email">Email</label><input id="email" type="email" value={email} onChange={event => setEmail(event.target.value)} required autoComplete="email" placeholder="you@company.com" className="input" disabled={isLoading} /></div><div><label htmlFor="password">Password</label><input id="password" type="password" value={password} onChange={event => setPassword(event.target.value)} required autoComplete="current-password" placeholder="Enter your password" className="input" disabled={isLoading} /></div>{errorMessage && <div className="login-signal__error">{errorMessage}</div>}<button type="submit" disabled={isLoading} className="signal-button signal-button--primary w-full">{isLoading ? 'Signing in…' : 'Sign in'}</button></form><footer>By signing in, you agree to our <a href="/terms">Terms</a> and <a href="/privacy">Privacy Policy</a>.</footer></section></main></>
}

export default function LoginPage() {
Expand Down
48 changes: 48 additions & 0 deletions web-client/src/app/signal.css
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,51 @@ input[type='file']:hover::file-selector-button, .signal-file-picker:hover::file-

/* Cards already pad themselves; drop the extra inner padding so tables/forms span the full card width */
.card > .p-6, .card > .p-4, .card > .p-5 { padding-left: 0 !important; padding-right: 0 !important; padding-bottom: 0 !important; padding-top: 4px !important; }

/* ---- Shared site header (1:1 with doorman-site Navbar) ---- */
.sh { font-size: 16px; letter-spacing: normal; line-height: 1.5; position: sticky; top: 0; z-index: 50; width: 100%; background: rgba(255,255,255,.95) !important; backdrop-filter: blur(8px); border-bottom: 1px solid #dfe3e8 !important; font-family: 'Segoe UI', 'Helvetica Neue', Arial, system-ui, sans-serif; }
.sh-container { width: 100%; padding: 0 1.25rem; margin: 0 auto; }
.sh-inner { display: flex; height: 60px; align-items: center; justify-content: space-between; }
.sh-brand { display: flex; align-items: center; gap: .75rem; text-decoration: none; }
.sh-brand img { width: 40px; height: 40px; display: block; flex: none; }
.sh-name { display: block; font-size: 18px; font-weight: 700; line-height: 1; letter-spacing: -.025em; color: #111827; }
.sh-sub { display: block; margin-top: 4px; font-size: 10px; font-weight: 600; line-height: 1; letter-spacing: .13em; text-transform: uppercase; color: #66727f; }
.sh-nav { display: none; align-items: center; gap: 1.75rem; }
.sh-nav a { font-size: 14px; line-height: 20px; font-weight: 500; color: #1f2933; text-decoration: none; }
.sh-actions { display: flex; align-items: center; gap: .625rem; }
.sh-gh { display: none; width: 40px; height: 40px; align-items: center; justify-content: center; border-radius: 8px !important; color: #55616d; transition: background .15s, color .15s; }
.sh-gh:hover { background: #f2f4f7; color: #111827; }
.sh-btn { display: none; min-height: 44px; align-items: center; justify-content: center; padding: 10px 20px; border: 1px solid #1d4ed8; border-radius: 3px; background: #fff; color: #1d4ed8; font-size: 14px; line-height: 20px; font-weight: 600; text-decoration: none; box-shadow: 0 1px 1px rgba(16,24,40,.03); transition: background .15s, color .15s; cursor: pointer; }
.sh-btn:hover { background: #eff6ff; color: #1e40af; }
.sh-btn--primary { background: #1d4ed8; color: #fff; }
.sh-btn--primary:hover { background: #1d4ed8; color: #fff; }
.sh-burger { display: inline-flex; width: 40px; height: 40px; align-items: center; justify-content: center; border: 1px solid #dfe3e8; border-radius: 8px !important; background: transparent; color: #3b4753; cursor: pointer; padding: 0; }
.sh-mobile { display: none; border-top: 1px solid #dfe3e8; padding: 1rem 0; }
.sh-mobile.open { display: block; }
.sh-mobile a { display: block; padding: .75rem; border-radius: 8px; font-size: 14px; font-weight: 600; color: #1f2933; text-decoration: none; }
.sh-mobile a:hover { background: #f2f4f7; }
.sh-mobile .sh-btn { display: flex; margin-top: .75rem; color: #fff; }
.login-signal { min-height: calc(100vh - 61px); }
@media (min-width: 640px) { .sh-gh { display: inline-flex; } .sh-btn { display: inline-flex; } .sh-mobile .sh-btn { display: flex; } }
@media (min-width: 768px) { .sh-container { width: 80%; padding: 0 2rem; } .sh-nav { display: flex; } .sh-burger, .sh-mobile { display: none !important; } }
@media (min-width: 1024px) { .sh-btn--primary { display: inline-flex; } }
@media (max-width: 1023px) { .sh-actions > .sh-btn--primary { display: none; } }
@media (max-width: 767px) { .sh-mobile .sh-btn--primary { display: flex; } }

.login-signal { place-items: start center; align-content: start; padding-top: 39px; padding-bottom: 56px; }

/* ---- Login card: same format as the wrapper signup card ---- */
.login-signal { padding-top: 39px; }
.login-signal__panel { width: min(100%, 520px); border: 1px solid #dfe3e8; border-radius: 6px; box-shadow: 0 2px 6px rgba(16, 24, 40, .08); }
.login-signal__header { display: flex; flex-direction: column; gap: 5px; padding: 28px 32px 20px; border-bottom: 1px solid #eceff3; }
.login-signal__header .signal-kicker { margin: 0; color: #1d4ed8; font-size: 12px; font-weight: 600; letter-spacing: .02em !important; text-transform: none !important; }
.login-signal__header h1 { margin: 0; font-size: 22px; font-weight: 600; line-height: 1.2; letter-spacing: 0; color: #111827; }
.login-signal__header > p:last-child { max-width: none; margin: 2px 0 0; color: #66727f; font-size: 14px; line-height: 1.45; }
.login-signal__form { gap: 20px; padding: 24px 32px 32px; }
.login-signal__form > div { gap: 6px; }
.login-signal__form .input { padding: 10px 12px; font-size: 14px; border-radius: 4px !important; }
.login-signal__form label { font-size: 13px; font-weight: 600; color: #1f2933 !important; }
.login-signal__form .signal-button { width: 100%; min-height: 44px; font-size: 14px !important; font-weight: 600 !important; }
.login-signal footer { padding: 14px 32px; border-top: 1px solid #eceff3; font-size: 12px; color: #66727f; }
.login-signal footer a { color: #1d4ed8; font-weight: 500; text-decoration: none; }
.login-signal footer a:hover { text-decoration: underline; }
Loading