parent
e33d8dee8f
commit
e86cf420df
@ -0,0 +1,24 @@
|
|||||||
|
import {FormRequest, ValidationRules} from '../../forms'
|
||||||
|
import {Is, Str} from '../../forms/rules/rules'
|
||||||
|
import {Singleton} from '../../di'
|
||||||
|
|
||||||
|
export interface BasicLoginCredentials {
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton()
|
||||||
|
export class BasicLoginFormRequest extends FormRequest<BasicLoginCredentials> {
|
||||||
|
protected getRules(): ValidationRules {
|
||||||
|
return {
|
||||||
|
username: [
|
||||||
|
Is.required,
|
||||||
|
Str.lengthMin(1),
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
Is.required,
|
||||||
|
Str.lengthMin(1),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import {ResponseFactory} from './ResponseFactory'
|
||||||
|
import {HTTPStatus} from '../../util'
|
||||||
|
import {Request} from '../lifecycle/Request'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to create a new RedirectResponseFactory to the given destination.
|
||||||
|
* @param destination
|
||||||
|
*/
|
||||||
|
export function redirect(destination: string): RedirectResponseFactory {
|
||||||
|
return new RedirectResponseFactory(destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response factory that sends an HTTP redirect to the given destination.
|
||||||
|
*/
|
||||||
|
export class RedirectResponseFactory extends ResponseFactory {
|
||||||
|
protected targetStatus: HTTPStatus = HTTPStatus.MOVED_TEMPORARILY
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
/** THe URL where the client should redirect to. */
|
||||||
|
public readonly destination: string,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async write(request: Request): Promise<Request> {
|
||||||
|
request = await super.write(request)
|
||||||
|
request.response.setHeader('Location', this.destination)
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
import {ResponseFactory} from './ResponseFactory'
|
||||||
|
import {HTTPStatus} from '../../util'
|
||||||
|
import {Request} from '../lifecycle/Request'
|
||||||
|
import {Routing} from '../../service/Routing'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to create a new RouteResponseFactory to the given destination.
|
||||||
|
* @param nameOrPath
|
||||||
|
*/
|
||||||
|
export function route(nameOrPath: string): RouteResponseFactory {
|
||||||
|
return new RouteResponseFactory(nameOrPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response factory that sends an HTTP redirect to the given destination.
|
||||||
|
*/
|
||||||
|
export class RouteResponseFactory extends ResponseFactory {
|
||||||
|
protected targetStatus: HTTPStatus = HTTPStatus.MOVED_TEMPORARILY
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
/** The alias or path of the route to redirect to. */
|
||||||
|
public readonly nameOrPath: string,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async write(request: Request): Promise<Request> {
|
||||||
|
const routing = <Routing> request.make(Routing)
|
||||||
|
request = await super.write(request)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const routePath = routing.getNamedPath(this.nameOrPath)
|
||||||
|
request.response.setHeader('Location', routePath.toRemote)
|
||||||
|
} catch (e: unknown) {
|
||||||
|
request.response.setHeader('Location', routing.getAppUrl().concat(this.nameOrPath).toRemote)
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
:root {
|
||||||
|
--input-padding-x: 1.5rem;
|
||||||
|
--input-padding-y: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login,
|
||||||
|
.image {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-heading {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
letter-spacing: 0.05rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group>input,
|
||||||
|
.form-label-group>label {
|
||||||
|
padding: var(--input-padding-y) var(--input-padding-x);
|
||||||
|
height: auto;
|
||||||
|
border-radius: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group>label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0;
|
||||||
|
/* Override default `<label>` margin */
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #495057;
|
||||||
|
cursor: text;
|
||||||
|
/* Match the input under the label */
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: .25rem;
|
||||||
|
transition: all .1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input::-webkit-input-placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input:-ms-input-placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input::-ms-input-placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input::-moz-placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input::placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input:not(:placeholder-shown) {
|
||||||
|
padding-top: calc(var(--input-padding-y) + var(--input-padding-y) * (2 / 3));
|
||||||
|
padding-bottom: calc(var(--input-padding-y) / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input:not(:placeholder-shown)~label {
|
||||||
|
padding-top: calc(var(--input-padding-y) / 3);
|
||||||
|
padding-bottom: calc(var(--input-padding-y) / 3);
|
||||||
|
font-size: 12px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-error-message {
|
||||||
|
color: darkred;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-submit-button {
|
||||||
|
margin-top: 50px;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback for Edge
|
||||||
|
-------------------------------------------------- */
|
||||||
|
|
||||||
|
@supports (-ms-ime-align: auto) {
|
||||||
|
.form-label-group>label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.form-label-group input::-ms-input-placeholder {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback for IE
|
||||||
|
-------------------------------------------------- */
|
||||||
|
|
||||||
|
@media all and (-ms-high-contrast: none),
|
||||||
|
(-ms-high-contrast: active) {
|
||||||
|
.form-label-group>label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.form-label-group input:-ms-input-placeholder {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,12 @@
|
|||||||
|
extends ./theme
|
||||||
|
|
||||||
|
block content
|
||||||
|
if heading
|
||||||
|
h3.login-heading.mb-4 #{heading}
|
||||||
|
|
||||||
|
if errors
|
||||||
|
each error in errors
|
||||||
|
p.form-error-message #{error}
|
||||||
|
|
||||||
|
if message
|
||||||
|
p #{message}
|
Loading…
Reference in new issue