Skip to content

Commit

Permalink
feat: create basic login page (#38)
Browse files Browse the repository at this point in the history
* docs: created technical docs for authenticate setup

* docs: added docs

* docs: wrote machine, context, actions, .. docs

* docs: added state chart diagram

* docs: typo authorize -> authenticate

* docs: final changes

* docs: minor changes

* docs: minor changes

* docs: final changes

* docs: included feedback

* docs: added solidservice docs

* docs: sessioninfo edits

* feat: created initial files for authenticate

* chore: trying to add authenticate as parallel state node

* feat: integrated authenticate machine into app machine

* feat: basic implementation of authenticate feature

* chore: applied old feedback

* test: fixed tests

* chore: minor changes

* chore: cleaned up file structure

* test: fixed broken import in tests

* chore: cleaned up authenticate feature

* chore: renamed login and logout events

* fix: made sure logout is called

* chore: replaced nl-BE -> nl-NL

* feat: initial styling for login page

* feat: added NDE logo (inverse)

* feat: added border property to form element

* fix: fixed broken default borders

* feat: integrated form machine into authenticate machine

* fix: added enable/disable button + submit events

* fix: fixed styling for login page

* chore: removed unused imports

* chore: fixed build errors

* Docs: Create authenticate login page (#35)

* docs: added docs for authenticate login page

* docs: added images

* docs: added docs

* docs: final changes

* docs: final edits

* docs: renamed directory to 'authenticate'

* docs: finished docs

* docs: changed named slots references

* docs: fixed images

* docs: removed solidservice docs

* docs: applied feedback

* docs: removed inputfieldcomponent

Co-authored-by: Wouter Janssens <wouter@digita.ai>

* refactor: simplify export theme assets

* fix: remove default fill from logo

* fix: reduced contract of disabled button

* refactor: simplified authenticate machine and other minor improvements

* refactor: small improvements to form machines

Co-authored-by: Wouter Janssens <wouter@digita.ai>
  • Loading branch information
lem-onade and wouteraj authored Apr 29, 2021
1 parent c5a380a commit c050c98
Show file tree
Hide file tree
Showing 27 changed files with 811 additions and 206 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ export class DemoFormComponent extends RxLitElement {
public translator: Translator = new MemoryTranslator([
{
key: 'demo-form.name.required',
locale: 'nl-BE',
locale: 'nl-NL',
value: 'Name is required.',
},
{
key: 'demo-form.uri.required',
locale: 'nl-BE',
locale: 'nl-NL',
value: 'URI is required.',
},
], 'nl-BE');
], 'nl-NL');

/**
* The actor controlling this component.
Expand Down
125 changes: 73 additions & 52 deletions packages/nde-erfgoed-components/lib/forms/form-element.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import { FormEvents, FormUpdatedEvent } from './form.events';
*/
export class FormElementComponent<T> extends RxLitElement {

/**
* Decides whether a border should be shown around the content
*/
@property()
public inverse = false;

/**
* The component's translator.
*/
Expand Down Expand Up @@ -45,56 +51,6 @@ export class FormElementComponent<T> extends RxLitElement {
@property({type: Object})
public actor: SpawnedActorRef<Event<FormEvents>, State<FormContext<T>>>;

/**
* The styles associated with the component.
*/
static get styles() {
return [
unsafeCSS(Theme),
css`
:root {
display: block;
}
.form-element {
display: flex;
flex-direction: column;
align-items: stretch;
}
.form-element .content {
display: flex;
flex-direction: row;
align-items: stretch;
}
.form-element .content .field {
display: flex;
flex-direction: row;
border: var(--border-normal) solid var(--colors-foreground-normal);
padding: var(--gap-small) var(--gap-normal);
height: 20px;
align-items: center;
flex: 1 0;
}
.form-element .label {
font-weight: var(--font-weight-bold);
margin-bottom: var(--gap-small);
}
.form-element .content .field .input {
flex: 1 0;
}
.form-element .content .field .icon {
max-height: var(--gap-normal);
max-width: var(--gap-normal);
margin-left: var(--gap-normal);
}
.form-element .results .result {
background-color: var(--colors-status-warning);
padding: var(--gap-tiny) var(--gap-normal);
font-size: var(--font-size-small);
}
`,
];
}

/**
* Hook called on first update after connection to the DOM.
* It subscribes to the actor, logs state changes, and pipes state to the properties.
Expand Down Expand Up @@ -169,7 +125,7 @@ export class FormElementComponent<T> extends RxLitElement {
<slot name="label"></slot>
</div>
<div class="content">
<div class="field">
<div class="field ${this.inverse ? 'no-border' : ''}">
<div class="input">
<slot name="input" @slotchange=${this.handleInputSlotchange}></slot>
</div>
Expand All @@ -178,7 +134,7 @@ export class FormElementComponent<T> extends RxLitElement {
</div>
</div>
<div class="action">
<slot name="action"></slot>
<slot name="action" class="${this.inverse ? 'no-border' : ''}"></slot>
</div>
</div>
<div class="help" ?hidden="${this.validationResults && this.validationResults?.length > 0}">
Expand All @@ -190,6 +146,71 @@ export class FormElementComponent<T> extends RxLitElement {
</div>
`;
}

/**
* The styles associated with the component.
*/
static get styles() {
return [
unsafeCSS(Theme),
css`
:root {
display: block;
}
.no-border, .no-border ::slotted(*) {
border: none !important;
}
.form-element {
display: flex;
flex-direction: column;
align-items: stretch;
}
.form-element .label {
font-weight: var(--font-weight-bold);
margin-bottom: var(--gap-small);
}
.form-element .content {
display: flex;
flex-direction: row;
align-items: stretch;
height: 44px;
background-color: var(--colors-background-light)
}
.form-element .content .field {
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: space-between;
flex: 1 0;
border: var(--border-normal) solid var(--colors-foreground-normal);
}
.form-element .content .field .input {
padding: 0 var(--gap-normal);
width: 100%;
height: 100%;
}
.form-element .content .field .input ::slotted(input) {
width: 100%;
height: 100%;
}
.form-element .content .field .icon {
padding: 0 var(--gap-normal);
height: 100%;
display: flex;
align-items: center;
}
.form-element .content .field .icon ::slotted(*) {
max-height: var(--gap-normal);
max-width: var(--gap-normal);
}
.form-element .results .result {
background-color: var(--colors-status-warning);
padding: var(--gap-tiny) var(--gap-normal);
font-size: var(--font-size-small);
}
`,
];
}
}

export default FormElementComponent;
2 changes: 1 addition & 1 deletion packages/nde-erfgoed-components/lib/forms/form.events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { FormContext } from './form.machine';
*/
export enum FormEvents {
FORM_UPDATED = '[FormEvent: Updated element]',
FORM_SUBMITTED = '[FormEvent: Subitted]',
FORM_SUBMITTED = '[FormEvent: Submitted]',
}

/**
Expand Down
43 changes: 16 additions & 27 deletions packages/nde-erfgoed-components/lib/forms/form.machine.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,24 @@ describe('FormMachine', () => {
expect(machine.state.context.data).toEqual(data);

// States should be updated
const stateValueMap = machine.state.value as StateValueMap;
expect(stateValueMap[FormRootStates.CLEANLINESS]).toBe(cleanliness);
expect(stateValueMap[FormRootStates.SUBMISSION]).toBe(submission);
expect(stateValueMap[FormRootStates.VALIDATION]).toBe(validation);
expect(machine.state.matches({
[FormRootStates.CLEANLINESS]: cleanliness,
[FormRootStates.VALIDATION]: validation,
[FormRootStates.SUBMISSION]: submission,
})).toBeTruthy();
});

it('should should submit when form is valid', () => {
it('should submit when form is valid', () => {
machine.start();

machine.send(FormEvents.FORM_UPDATED, {field: 'uri', value: 'foo'});
machine.send(FormEvents.FORM_SUBMITTED);

const stateValueMap = machine.state.value as StateValueMap;
expect(stateValueMap[FormRootStates.CLEANLINESS]).toBe(FormCleanlinessStates.DIRTY);
expect(stateValueMap[FormRootStates.SUBMISSION]).toBe(FormSubmissionStates.SUBMITTED);
expect(stateValueMap[FormRootStates.VALIDATION]).toBe(FormValidationStates.VALID);
expect(machine.state.matches({
[FormRootStates.CLEANLINESS]: FormCleanlinessStates.DIRTY,
[FormRootStates.VALIDATION]: FormValidationStates.VALID,
[FormRootStates.SUBMISSION]: FormSubmissionStates.SUBMITTED,
})).toBeTruthy();
});

it('should not change original data when form is updated', () => {
Expand All @@ -79,23 +81,10 @@ describe('FormMachine', () => {

machine.send(FormEvents.FORM_SUBMITTED);

const stateValueMap = machine.state.value as StateValueMap;
expect(stateValueMap[FormRootStates.CLEANLINESS]).toBe(FormCleanlinessStates.PRISTINE);
expect(stateValueMap[FormRootStates.SUBMISSION]).toBe(FormSubmissionStates.NOT_SUBMITTED);
expect(stateValueMap[FormRootStates.VALIDATION]).toBe(FormValidationStates.INVALID);
});

it('should allow re-submitting forms', () => {
machine.start();

machine.send(FormEvents.FORM_UPDATED, {field: 'uri', value: 'foo'});
machine.send(FormEvents.FORM_SUBMITTED);
machine.send(FormEvents.FORM_UPDATED, {field: 'name', value: ''});
machine.send(FormEvents.FORM_SUBMITTED);

const stateValueMap = machine.state.value as StateValueMap;
expect(stateValueMap[FormRootStates.CLEANLINESS]).toBe(FormCleanlinessStates.DIRTY);
expect(stateValueMap[FormRootStates.SUBMISSION]).toBe(FormSubmissionStates.NOT_SUBMITTED);
expect(stateValueMap[FormRootStates.VALIDATION]).toBe(FormValidationStates.INVALID);
expect(machine.state.matches({
[FormRootStates.CLEANLINESS]: FormCleanlinessStates.PRISTINE,
[FormRootStates.VALIDATION]: FormValidationStates.INVALID,
[FormRootStates.SUBMISSION]: FormSubmissionStates.NOT_SUBMITTED,
})).toBeTruthy();
});
});
24 changes: 16 additions & 8 deletions packages/nde-erfgoed-components/lib/forms/form.machine.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMachine } from 'xstate';
import { createMachine, sendParent } from 'xstate';
import { State } from '../state/state';
import { Event } from '../state/event';
import { FormValidatorResult } from './form-validator-result';
Expand All @@ -14,6 +14,13 @@ export interface FormContext<TData> {
validation?: FormValidatorResult[];
}

/**
* Actor references for this machine config.
*/
export enum FormActors {
FORM_MACHINE = 'FormMachine',
}

/**
* State references of the root parallel states of the form machine.
*/
Expand Down Expand Up @@ -60,7 +67,7 @@ export type FormStates = FormRootStates | FormSubmissionStates | FormCleanliness
* The form component machine.
*/
export const formMachine = <T>(validator: FormValidator<T>) => createMachine<FormContext<T>, Event<FormEvents>, State<FormStates, FormContext<T>>>({
id: 'form',
id: FormActors.FORM_MACHINE,
type: 'parallel',
states: {
/**
Expand All @@ -79,6 +86,7 @@ export const formMachine = <T>(validator: FormValidator<T>) => createMachine<For
},
},
exit: [ update, validate(validator) ],
type: 'final',
},
/**
* Transient state while checking if form was changed.
Expand All @@ -104,6 +112,7 @@ export const formMachine = <T>(validator: FormValidator<T>) => createMachine<For
},
},
exit: [ update, validate(validator) ],
type: 'final',
},
},
},
Expand Down Expand Up @@ -143,12 +152,8 @@ export const formMachine = <T>(validator: FormValidator<T>) => createMachine<For
* The form has been submitted.
*/
[FormSubmissionStates.SUBMITTED]: {
on: {
[FormEvents.FORM_SUBMITTED]: {
target: FormSubmissionStates.SUBMITTING,
},
},
exit: [ validate(validator) ],
// entry: sendParent(FormEvents.FORM_SUBMITTED),
type: 'final',
},
},
},
Expand All @@ -172,6 +177,7 @@ export const formMachine = <T>(validator: FormValidator<T>) => createMachine<For
},
},
exit: [ update, validate(validator) ],
type: 'final',
},
/**
* Transient state while checking validation.
Expand All @@ -197,6 +203,7 @@ export const formMachine = <T>(validator: FormValidator<T>) => createMachine<For
},
},
exit: [ update, validate(validator) ],
type: 'final',
},
/**
* The form is invalid, based on the provided validator function.
Expand All @@ -208,6 +215,7 @@ export const formMachine = <T>(validator: FormValidator<T>) => createMachine<For
},
},
exit: [ update, validate(validator) ],
type: 'final',
},
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/nde-erfgoed-components/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './forms/form-element.component';
export * from './forms/form-validator-result';
export * from './forms/form-validator';
export * from './forms/form.machine';
export * from './forms/form.events';
export * from './state/event';
export * from './state/schema';
export * from './state/state';
6 changes: 3 additions & 3 deletions packages/nde-erfgoed-core/lib/i8n/memory-translator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('MemoryTranslator', () => {
value: 'Foo in English',
},
{
locale: 'nl-BE',
locale: 'nl-NL',
key: 'foo',
value: 'Foo in Dutch',
},
Expand Down Expand Up @@ -43,13 +43,13 @@ describe('MemoryTranslator', () => {
});

it('Should translate return null with an existing key in an non-existing locale.', () => {
const value = service.translate('foo.bar', 'nl-BE');
const value = service.translate('foo.bar', 'nl-NL');

expect(value).toBeFalsy();
});

it('Should translate return null with an non-existing key in an existing locale.', () => {
const value = service.translate('lorem', 'nl-BE');
const value = service.translate('lorem', 'nl-NL');

expect(value).toBeFalsy();
});
Expand Down
Loading

0 comments on commit c050c98

Please sign in to comment.