@wral/studio.mods.auth 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,158 +0,0 @@
1
- import { LitElement, html, css } from 'lit';
2
- import { getToken } from '../helper.mjs';
3
- import { createClient } from '@wral/sdk-auth';
4
-
5
- export function render(state, auth) {
6
- return html`
7
- <auth-change-password-form .auth=${auth}></auth-change-password-form>
8
- `;
9
- }
10
-
11
- class ChangePasswordForm extends LitElement {
12
-
13
- static get styles() {
14
- return css`
15
- :host {
16
- color: var(--color-text);
17
- display: block;
18
- font-family: var(--font-body);
19
- }
20
- h1,h2,h3,h4,h5,h6,label { font-family: var(--font-heading) }
21
- label {
22
- display: block;
23
- font-size: var(--font-size-base);
24
- margin-top: var(--spacing-md, 0.5rem);
25
- }
26
- input[type="text"],
27
- input[type="password"] {
28
- width: 100%;
29
- margin: 5px 0 15px 0;
30
- padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
31
- box-sizing: border-box;
32
- border: 1px solid var(--color-gray-300, #ccc);
33
- border-radius: var(--radius-sm, 5px);
34
- color: var(--color-gray-9, #333);
35
- background-color: var(--color-gray-0, transparent);
36
- }
37
- input[type="submit"] {
38
- background-color: var(--color-primary, inherit);
39
- color: var(--color-gray-0, #fff);
40
- border: none;
41
- padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
42
- border-radius: var(--radius-dynamic, 5px);
43
- width: 200px;
44
- cursor: pointer;
45
- }
46
- .error {
47
- color: var(--color-error);
48
- }
49
- `;
50
- }
51
-
52
- static get properties() {
53
- return {
54
- auth: { type: Object },
55
- };
56
- }
57
-
58
- constructor() {
59
- super();
60
- this.errors = {};
61
- this.success = null;
62
- }
63
-
64
- connectedCallback() {
65
- super.connectedCallback();
66
- // discard result, we're only interested in the side-effect of being logged in
67
- getToken(this);
68
- }
69
-
70
- async onSubmit(e) {
71
- e.preventDefault();
72
-
73
- // Ensure passwords match
74
- if (e.target.newPassword.value !== e.target.confirmPassword.value) {
75
- this.errors['confirmPassword'] = 'Passwords do not match';
76
- this.requestUpdate();
77
- return;
78
- }
79
-
80
- // Check that new password is actually different
81
- if (e.target.newPassword.value === e.target.currentPassword.value) {
82
- this.errors['newPassword'] = 'The new password is the same as your '
83
- + 'current password.';
84
- this.requestUpdate();
85
- return;
86
- }
87
-
88
- const apiKey = await getToken(this);
89
-
90
- // TODO: send request to API
91
- const authClient = createClient({
92
- baseUrl: this.auth.config.api,
93
- apiKey,
94
- });
95
- await authClient.updatePassword({
96
- currentPassword: e.target.currentPassword.value,
97
- newPassword: e.target.newPassword.value,
98
- }).then(() => {
99
- this.errors = {};
100
- this.success = 'Password changed.';
101
- this.requestUpdate();
102
- }).catch((error) => {
103
- this.errors['submit'] = error.message;
104
- this.requestUpdate();
105
- });
106
- }
107
-
108
- render() {
109
- return html`
110
- <h1>Change your password</h1>
111
- <p>Enter your current password and choose a new password.</p>
112
- <form @submit=${this.onSubmit}>
113
- <div class="field">
114
- <label for="currentPassword">Current Password</label>
115
- <input
116
- type="password"
117
- name="currentPassword"
118
- placeholder="Current Password"
119
- required
120
- />
121
- ${this.errors['currentPassword']
122
- ? html`<p class="error">${this.errors['currentPassword']}</p>` : ''}
123
- </div>
124
- <div class="field">
125
- <label for="newPassword">New Password</label>
126
- <input
127
- type="password"
128
- name="newPassword"
129
- placeholder="New Password"
130
- required
131
- />
132
- ${this.errors['newPassword']
133
- ? html`<p class="error">${this.errors['newPassword']}</p>` : ''}
134
- </div>
135
- <div class="field">
136
- <label for="confirmPassword">Confirm Password</label>
137
- <input
138
- type="password"
139
- name="confirmPassword"
140
- placeholder="Confirm Password"
141
- required
142
- />
143
- ${this.errors['confirmPassword']
144
- ? html`<p class="error">${this.errors['confirmPassword']}</p>` : ''}
145
- </div>
146
- <input type="submit" value="Change Password" />
147
- ${this.errors['submit']
148
- ? html`<p class="error">${this.errors['submit']}</p>` : ''}
149
- ${this.success ? html`<p class="success">${this.success}</p>` : ''}
150
- </form>
151
- `;
152
- }
153
-
154
- }
155
-
156
- if (!customElements.get('auth-change-password-form')) {
157
- customElements.define('auth-change-password-form', ChangePasswordForm);
158
- }
@@ -1,17 +0,0 @@
1
- import { html } from 'lit';
2
- import { pushState } from '../state.mjs';
3
-
4
-
5
- export function render(state, auth) {
6
- const { history } = auth.config.toolkit.element.ownerDocument.defaultView;
7
- const navigateTo = (path, title) => () => {
8
- pushState(auth, history, { path, title });
9
- };
10
- return html`
11
- <h1>Auth Dashboard</h1>
12
- <ul>
13
- <li><a href="#" @click=${navigateTo('/change-password')}>Change Password</a></li>
14
- <li><a href="#" @click=${auth.handleDestroyAuth}>Logout</a></li>
15
- </ul>
16
- `;
17
- }
@@ -1,15 +0,0 @@
1
- import * as dashboard from './dashboard.mjs';
2
- import * as changePassword from './change-password.mjs';
3
-
4
- export const routes = [
5
- { path: '/', render: dashboard.render },
6
- { path: '/change-password', render: changePassword.render },
7
- ];
8
-
9
- export function renderRoute(state, auth) {
10
- const { path } = state;
11
- const route = routes.find((r) => r.path === path);
12
- if (route) {
13
- return route.render(state, auth);
14
- }
15
- }
package/src/state.mjs DELETED
@@ -1,61 +0,0 @@
1
- /**
2
- * Ensure pathname has a consistent format, with a leading slash, and no
3
- * trailing slash. This lets the application be more tolerant of input.
4
- * @param {string} pathname
5
- * @returns {string}
6
- */
7
- export function coercePathname(pathname) {
8
- return (pathname.startsWith('/') ? pathname : `/${pathname}`) // ensure leading slash
9
- .replace(/\/{2,}/g, '/') // remove consecutive slashes
10
- .replace(/(?<=.)\/$/, ''); // remove trailing slash (not at beginning)
11
- }
12
-
13
- /**
14
- * Push a new state to the history (presumably window.history).
15
- * After the application changes state, call pushState to enable this history
16
- * to be tracked.
17
- *
18
- * To take advantage of the forward and back button behavior, be sure to handle
19
- * window.onpopstate events.
20
- * @param {Object} auth
21
- * @param {History} history
22
- * @param {Object} state
23
- * @returns {void}
24
- */
25
- export function pushState(auth, history, state) {
26
- const pathname = stateToPathname(auth, state);
27
- history.pushState(state, state.title, pathname);
28
- auth.requestRender(state);
29
- }
30
-
31
- /**
32
- * Produce a state object from a pathname
33
- * @param {Object} auth
34
- * @param {string} pathname
35
- * @returns {Object}
36
- */
37
- export function pathnameToState(auth, pathname) {
38
- const { mount } = auth.config;
39
- const { history } = auth.config.toolkit.element.ownerDocument.defaultView;
40
-
41
- // no state if pathname is not prefixed with mount
42
- if (!pathname.startsWith(mount)) {
43
- return null;
44
- }
45
- // remove mount from pathname
46
- return {
47
- path: coercePathname(pathname.slice(mount.length)),
48
- push: (state) => pushState(auth, history, state),
49
- };
50
- }
51
-
52
- /**
53
- * Produce a pathname from a state object
54
- * @param {Object} auth
55
- * @param {Object} state
56
- * @returns {string}
57
- */
58
- export function stateToPathname(auth, state) {
59
- const { mount } = auth.config;
60
- return coercePathname(`${mount}/${state.path}`);
61
- }
@@ -1,58 +0,0 @@
1
- import {
2
- coercePathname,
3
- pushState,
4
- pathnameToState,
5
- } from './state.mjs';
6
- import { jest } from '@jest/globals';
7
-
8
- describe('state', () => {
9
-
10
- describe('coercePathname', () => {
11
- // input, expected
12
- const cases = [
13
- ['', '/'],
14
- ['//', '/'],
15
- ['auth', '/auth'],
16
- ['foo/bar', '/foo/bar'],
17
- ['foo//bar', '/foo/bar'],
18
- ['foo//bar/', '/foo/bar'],
19
- ];
20
- cases.forEach(([input, expected]) => {
21
- it(`should coerce ${input} to ${expected}`, () => {
22
- expect(coercePathname(input)).toBe(expected);
23
- });
24
- });
25
- });
26
-
27
- describe('pushState', () => {
28
- it('pushes a state object to history', () => {
29
- const history = {
30
- pushState: jest.fn(),
31
- };
32
- const auth = {
33
- requestRender: jest.fn(),
34
- config: { mount: 'foo' },
35
- };
36
- const newState = {
37
- title: 'Some Title',
38
- path: 'bar',
39
- };
40
- pushState(auth, history, newState);
41
- expect(history.pushState)
42
- .toHaveBeenCalledWith(newState, newState.title, '/foo/bar');
43
- });
44
- });
45
-
46
- describe.skip('pathnameToState', () => {
47
- // TODO: refactor to not expect auth.config.toolkit.element.ownerDocument.defaultView
48
- it('produces a state object from a pathname', () => {
49
- const auth = {
50
- config: { mount: 'foo' },
51
- };
52
- const pathname = '/foo/bar';
53
- expect(pathnameToState(auth, pathname)).toEqual({
54
- path: 'bar',
55
- });
56
- });
57
- });
58
- });
package/src/styles.mjs DELETED
@@ -1,9 +0,0 @@
1
- import { css } from 'lit';
2
-
3
- export const styles = css`
4
- :host {
5
-
6
- }
7
- `;
8
-
9
- export default styles;
package/src/utils.mjs DELETED
@@ -1,3 +0,0 @@
1
- export function imply(fn, ...implied) {
2
- return (...args) => fn(...implied, ...args);
3
- }
@@ -1,86 +0,0 @@
1
- /**
2
- * This is a Vellum fixture application that resembles an environment that would
3
- * use this Auth mod.
4
- */
5
- import {
6
- VellumHarness,
7
- VellumModLoader,
8
- initLayoutManager,
9
- initThemeManager,
10
- } from '@thefarce/vellum';
11
- import { html, css } from 'lit';
12
-
13
- export function init(...args) {
14
- initLayoutManager(...args);
15
- initThemeManager(...args);
16
- const [ toolkit ] = args;
17
- toolkit.dispatchAction({
18
- type: 'layout:register',
19
- detail: {
20
- name: 'menu-main-task-layout',
21
- slots: ['menu','main','task'],
22
- templateFn: layoutTemplate,
23
- },
24
- });
25
- toolkit.dispatchAction({
26
- type: 'layout:push',
27
- detail: { layout: 'menu-main-task-layout' },
28
- });
29
- }
30
-
31
-
32
- /* Define some core web components */
33
- Object.entries({
34
- 'studio-app': VellumHarness,
35
- 'studio-mod': VellumModLoader,
36
- }).forEach(([name, webComponent]) => {
37
- if (!customElements.get(name)) {
38
- customElements.define(name, webComponent);
39
- }
40
- });
41
-
42
- function layoutTemplate(slots) {
43
- const styles = css`
44
- .menu-main-task-layout {
45
- display: flex;
46
- height: 100vh;
47
- }
48
- .menu-main-task-layout > .toolbar-slot {
49
- position: fixed;
50
- width: var(--menu-width, 200px);
51
- background-color: var(--color-gray-100, #f7f5f4);
52
- height: 100vh;
53
- box-sizing: border-box;
54
- padding: var(--spacing-md, 1rem);
55
- }
56
- .menu-main-task-layout > .main-slot {
57
- background-color: var(--color-gray-0, #fff);
58
- flex-grow: 1;
59
- margin-left: var(--menu-width, 250px);
60
- padding: var(--spacing-lg, 2rem);
61
- }
62
- .menu-main-task-layout > .task-slot {
63
- padding: var(--spacing-lg, 2rem);
64
- box-sizing: border-box;
65
- background-color: var(--color-gray-100, #f7f5f4);
66
- }
67
- `;
68
- return html`
69
- <style>${styles}</style>
70
- <div class="menu-main-task-layout">
71
- <aside class="toolbar-slot">
72
- <p>Application Menu</p>
73
- ${slots.menu && slots.menu.length > 0
74
- ? slots.menu.map(item => item)
75
- : html``}
76
- </aside>
77
- <main class="main-slot">
78
- ${slots.main && slots.main.length > 0
79
- ? slots.main.map(item => item)
80
- : html``}
81
- </main>
82
- ${slots.task && slots.task.length > 0
83
- ? html`<div class="task-slot">${slots.task.map(item => item)}</div>`
84
- : html``}
85
- </div>`;
86
- }