pict-section-login 0.0.1

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.
Files changed (25) hide show
  1. package/example_applications/custom_login/Custom-Login-Application.js +75 -0
  2. package/example_applications/custom_login/html/index.html +110 -0
  3. package/example_applications/custom_login/package.json +27 -0
  4. package/example_applications/harness_app/Harness-App-Application.js +167 -0
  5. package/example_applications/harness_app/Harness-App-Configuration.json +4 -0
  6. package/example_applications/harness_app/html/index.html +90 -0
  7. package/example_applications/harness_app/package.json +28 -0
  8. package/example_applications/harness_app/providers/PictRouter-HarnessApp.json +24 -0
  9. package/example_applications/harness_app/views/PictView-HarnessApp-Books.js +172 -0
  10. package/example_applications/harness_app/views/PictView-HarnessApp-Dashboard.js +158 -0
  11. package/example_applications/harness_app/views/PictView-HarnessApp-Layout.js +86 -0
  12. package/example_applications/harness_app/views/PictView-HarnessApp-Login.js +58 -0
  13. package/example_applications/harness_app/views/PictView-HarnessApp-TopBar.js +157 -0
  14. package/example_applications/harness_app/views/PictView-HarnessApp-Users.js +188 -0
  15. package/example_applications/oauth_login/OAuth-Login-Application.js +78 -0
  16. package/example_applications/oauth_login/html/index.html +57 -0
  17. package/example_applications/oauth_login/package.json +27 -0
  18. package/example_applications/orator_login/Orator-Login-Application.js +61 -0
  19. package/example_applications/orator_login/html/index.html +51 -0
  20. package/example_applications/orator_login/package.json +27 -0
  21. package/package.json +53 -0
  22. package/source/Pict-Section-Login-DefaultConfiguration.js +265 -0
  23. package/source/Pict-Section-Login.js +533 -0
  24. package/test/Browser_Integration_tests.js +588 -0
  25. package/test/Pict-Section-Login_tests.js +593 -0
@@ -0,0 +1,75 @@
1
+ const libPictApplication = require('pict-application');
2
+ const libPictSectionLogin = require('../../source/Pict-Section-Login.js');
3
+
4
+ /**
5
+ * A custom login view that demonstrates pointing the section at an
6
+ * arbitrary endpoint. The HTML page mocks window.fetch so this works
7
+ * entirely client-side with no server.
8
+ */
9
+ class CustomLoginView extends libPictSectionLogin
10
+ {
11
+ constructor(pFable, pOptions, pServiceHash)
12
+ {
13
+ super(pFable, pOptions, pServiceHash);
14
+ }
15
+
16
+ onLoginSuccess(pSessionData)
17
+ {
18
+ this.log.info('Custom login succeeded!', pSessionData);
19
+ }
20
+
21
+ onLoginFailed(pError)
22
+ {
23
+ this.log.warn('Custom login failed: ' + pError);
24
+ }
25
+
26
+ onLogout()
27
+ {
28
+ this.log.info('Custom logout complete.');
29
+ }
30
+ }
31
+
32
+ const _CustomLoginViewConfiguration = (
33
+ {
34
+ "ViewIdentifier": "CustomLogin",
35
+ "TargetElementAddress": "#Pict-Login-Container",
36
+ "LoginEndpoint": "/api/custom-auth",
37
+ "LoginMethod": "POST",
38
+ "LogoutEndpoint": "/api/custom-logout",
39
+ "CheckSessionEndpoint": "/api/custom-session",
40
+ "CheckSessionOnLoad": false,
41
+ "ShowOAuthProviders": false
42
+ });
43
+
44
+ class CustomLoginApplication extends libPictApplication
45
+ {
46
+ constructor(pFable, pOptions, pServiceHash)
47
+ {
48
+ super(pFable, pOptions, pServiceHash);
49
+
50
+ this.pict.addView('CustomLogin', _CustomLoginViewConfiguration, CustomLoginView);
51
+ }
52
+
53
+ onAfterInitialize()
54
+ {
55
+ super.onAfterInitialize();
56
+ let tmpView = this.pict.views.CustomLogin;
57
+ if (tmpView)
58
+ {
59
+ tmpView.render();
60
+ }
61
+ }
62
+ }
63
+
64
+ module.exports = CustomLoginApplication;
65
+
66
+ module.exports.default_configuration = (
67
+ {
68
+ "Name": "Custom Login Example",
69
+ "Hash": "CustomLoginExample",
70
+ "MainViewportViewIdentifier": "CustomLogin",
71
+ "pict_configuration":
72
+ {
73
+ "Product": "CustomLogin-Example"
74
+ }
75
+ });
@@ -0,0 +1,110 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Custom Login - Pict Example</title>
7
+ <!-- PICT Dynamic View CSS Container -->
8
+ <style id="PICT-CSS"></style>
9
+ <!-- Red Rock Mesa Theme — Pict Example App -->
10
+ <style>
11
+ *, *::before, *::after { box-sizing: border-box; }
12
+ body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #FAEDCD; color: #264653; }
13
+
14
+ /* --- Header Bar --- */
15
+ .pict-example-header { display: flex; align-items: stretch; background: #264653; border-bottom: 3px solid #E76F51; }
16
+ .pict-example-badge { background: #E76F51; color: #fff; padding: 0.6rem 1rem; font-size: 0.7rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.1em; display: flex; align-items: center; gap: 0.5rem; }
17
+ .pict-example-badge svg { width: 14px; height: 14px; fill: #fff; flex-shrink: 0; }
18
+ .pict-example-app-name { padding: 0.6rem 1rem; color: #FAEDCD; font-size: 1.1rem; font-weight: 600; display: flex; align-items: center; }
19
+ .pict-example-module { margin-left: auto; padding: 0.6rem 1rem; color: #D4A373; font-size: 0.75rem; display: flex; align-items: center; letter-spacing: 0.03em; }
20
+
21
+ /* --- Content Area --- */
22
+ .pict-example-content { padding: 1.5rem; }
23
+
24
+ /* --- Info Banner --- */
25
+ .pict-example-info { background: #264653; color: #FAEDCD; border-radius: 6px; padding: 1rem 1.25rem; margin-bottom: 1.5rem; font-size: 0.85rem; line-height: 1.5; }
26
+ .pict-example-info code { background: rgba(250,237,205,0.15); padding: 0.15rem 0.4rem; border-radius: 3px; font-size: 0.8rem; }
27
+ </style>
28
+ <script>
29
+ // =============================================================
30
+ // MOCK FETCH — Simulates a custom authentication backend.
31
+ // In a real application you would NOT do this; the section
32
+ // would simply point at your real API endpoint.
33
+ // =============================================================
34
+ (function ()
35
+ {
36
+ var _realFetch = window.fetch;
37
+ var _mockSession = null;
38
+
39
+ window.fetch = function (pURL, pOptions)
40
+ {
41
+ var tmpURL = typeof pURL === 'string' ? pURL : pURL.toString();
42
+
43
+ // --- POST /api/custom-auth ---
44
+ if (tmpURL === '/api/custom-auth' && pOptions && pOptions.method === 'POST')
45
+ {
46
+ var tmpBody = JSON.parse(pOptions.body || '{}');
47
+ var tmpUser = tmpBody.UserName || '';
48
+ var tmpPass = tmpBody.Password || '';
49
+
50
+ if (tmpUser === 'demo' && tmpPass === 'demo')
51
+ {
52
+ _mockSession = {
53
+ LoggedIn: true,
54
+ SessionID: 'mock-session-' + Date.now(),
55
+ UserID: 1,
56
+ UserRecord: { LoginID: 'demo', IDUser: 1, FullName: 'Demo User', Email: 'demo@example.com' }
57
+ };
58
+ return Promise.resolve(new Response(JSON.stringify(_mockSession), { status: 200, headers: { 'Content-Type': 'application/json' } }));
59
+ }
60
+ else
61
+ {
62
+ return Promise.resolve(new Response(JSON.stringify({ LoggedIn: false, Error: 'Invalid credentials. Try demo / demo.' }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
63
+ }
64
+ }
65
+
66
+ // --- GET /api/custom-logout ---
67
+ if (tmpURL === '/api/custom-logout')
68
+ {
69
+ _mockSession = null;
70
+ return Promise.resolve(new Response(JSON.stringify({ LoggedIn: false }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
71
+ }
72
+
73
+ // --- GET /api/custom-session ---
74
+ if (tmpURL === '/api/custom-session')
75
+ {
76
+ if (_mockSession)
77
+ {
78
+ return Promise.resolve(new Response(JSON.stringify(_mockSession), { status: 200, headers: { 'Content-Type': 'application/json' } }));
79
+ }
80
+ return Promise.resolve(new Response(JSON.stringify({ LoggedIn: false }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
81
+ }
82
+
83
+ // All other requests pass through to the real fetch
84
+ return _realFetch.apply(window, arguments);
85
+ };
86
+ })();
87
+ </script>
88
+ <script src="./pict.js" type="text/javascript"></script>
89
+ <script type="text/javascript">Pict.safeOnDocumentReady(() => { Pict.safeLoadPictApplication(CustomLoginExample, 1); });</script>
90
+ </head>
91
+ <body>
92
+ <div class="pict-example-header">
93
+ <div class="pict-example-badge">
94
+ <svg viewBox="0 0 16 16"><polygon points="8,1 10,6 16,6 11,9.5 13,15 8,11.5 3,15 5,9.5 0,6 6,6"/></svg>
95
+ Pict Example
96
+ </div>
97
+ <div class="pict-example-app-name">Custom Login</div>
98
+ <div class="pict-example-module">pict-section-login</div>
99
+ </div>
100
+ <div class="pict-example-content">
101
+ <div class="pict-example-info">
102
+ This example demonstrates <code>pict-section-login</code> pointed at a <strong>custom endpoint</strong>
103
+ (<code>/api/custom-auth</code>). No server is needed &mdash; <code>window.fetch</code> is mocked
104
+ to simulate a backend. Use <code>demo</code> / <code>demo</code> to log in.
105
+ </div>
106
+ <div id="Pict-Login-Container"></div>
107
+ </div>
108
+ <script src="./custom_login_example.js" type="text/javascript"></script>
109
+ </body>
110
+ </html>
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "custom_login_example",
3
+ "version": "1.0.0",
4
+ "description": "Custom login example — pict-section-login with a mocked custom endpoint",
5
+ "main": "Custom-Login-Application.js",
6
+ "scripts": {
7
+ "start": "node Custom-Login-Application.js",
8
+ "build": "npx quack build && npx quack copy"
9
+ },
10
+ "author": "steven velozo <steven@velozo.com>",
11
+ "license": "MIT",
12
+ "devDependencies": {
13
+ },
14
+ "copyFilesSettings": {
15
+ "whenFileExists": "overwrite"
16
+ },
17
+ "copyFiles": [
18
+ {
19
+ "from": "./html/*",
20
+ "to": "./dist/"
21
+ },
22
+ {
23
+ "from": "../../node_modules/pict/dist/*",
24
+ "to": "./dist/"
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,167 @@
1
+ const libPictApplication = require('pict-application');
2
+ const libPictRouter = require('pict-router');
3
+
4
+ // Views
5
+ const libViewLogin = require('./views/PictView-HarnessApp-Login.js');
6
+ const libViewLayout = require('./views/PictView-HarnessApp-Layout.js');
7
+ const libViewTopBar = require('./views/PictView-HarnessApp-TopBar.js');
8
+ const libViewDashboard = require('./views/PictView-HarnessApp-Dashboard.js');
9
+ const libViewBooks = require('./views/PictView-HarnessApp-Books.js');
10
+ const libViewUsers = require('./views/PictView-HarnessApp-Users.js');
11
+
12
+ class HarnessAppApplication extends libPictApplication
13
+ {
14
+ constructor(pFable, pOptions, pServiceHash)
15
+ {
16
+ super(pFable, pOptions, pServiceHash);
17
+
18
+ // Router provider — SkipRouteResolveOnAdd is set in the config JSON
19
+ // so routes only resolve after the layout renders (not during construction).
20
+ this.pict.addProvider('PictRouter',
21
+ require('./providers/PictRouter-HarnessApp.json'),
22
+ libPictRouter);
23
+
24
+ // Login view (extends pict-section-login)
25
+ this.pict.addView('HarnessApp-Login', libViewLogin.default_configuration, libViewLogin);
26
+
27
+ // Layout shell (top bar + content area)
28
+ this.pict.addView('HarnessApp-Layout', libViewLayout.default_configuration, libViewLayout);
29
+
30
+ // Top bar navigation
31
+ this.pict.addView('HarnessApp-TopBar', libViewTopBar.default_configuration, libViewTopBar);
32
+
33
+ // Content views (rendered into the content area by the router)
34
+ this.pict.addView('HarnessApp-Dashboard', libViewDashboard.default_configuration, libViewDashboard);
35
+ this.pict.addView('HarnessApp-Books', libViewBooks.default_configuration, libViewBooks);
36
+ this.pict.addView('HarnessApp-Users', libViewUsers.default_configuration, libViewUsers);
37
+ }
38
+
39
+ onAfterInitializeAsync(fCallback)
40
+ {
41
+ // Render the login form first
42
+ this.pict.views['HarnessApp-Login'].render();
43
+
44
+ // Inject CSS so the login section styling is applied immediately
45
+ this.pict.CSSMap.injectCSS();
46
+
47
+ // Check if a session already exists (e.g. cookie from a previous visit).
48
+ // The login view's onSessionChecked hook will call showProtectedApp()
49
+ // if a valid session is found.
50
+ this.pict.views['HarnessApp-Login'].checkSession();
51
+
52
+ return super.onAfterInitializeAsync(fCallback);
53
+ }
54
+
55
+ // ===== Application-Level Navigation =====
56
+
57
+ /**
58
+ * Switch from the login screen to the protected application.
59
+ * Called by the login view after a successful login or session check.
60
+ */
61
+ showProtectedApp()
62
+ {
63
+ // Hide the login container
64
+ let tmpLoginElements = this.pict.ContentAssignment.getElement('#HarnessApp-Login-Container');
65
+ if (tmpLoginElements && tmpLoginElements.length > 0)
66
+ {
67
+ tmpLoginElements[0].style.display = 'none';
68
+ }
69
+
70
+ // Show the protected app container
71
+ let tmpAppElements = this.pict.ContentAssignment.getElement('#HarnessApp-Container');
72
+ if (tmpAppElements && tmpAppElements.length > 0)
73
+ {
74
+ tmpAppElements[0].style.display = 'block';
75
+ }
76
+
77
+ // Render the layout shell (triggers TopBar, Dashboard, CSS injection, and router resolve)
78
+ this.pict.views['HarnessApp-Layout'].render();
79
+ }
80
+
81
+ /**
82
+ * Switch from the protected application back to the login screen.
83
+ * Called by the doLogout() method after the session is destroyed.
84
+ */
85
+ showLogin()
86
+ {
87
+ // Clear session data
88
+ this.pict.AppData.Session = null;
89
+
90
+ // Hide the protected app container
91
+ let tmpAppElements = this.pict.ContentAssignment.getElement('#HarnessApp-Container');
92
+ if (tmpAppElements && tmpAppElements.length > 0)
93
+ {
94
+ tmpAppElements[0].style.display = 'none';
95
+ }
96
+
97
+ // Show the login container and re-render
98
+ let tmpLoginElements = this.pict.ContentAssignment.getElement('#HarnessApp-Login-Container');
99
+ if (tmpLoginElements && tmpLoginElements.length > 0)
100
+ {
101
+ tmpLoginElements[0].style.display = 'block';
102
+ }
103
+
104
+ // Reset the login view state and re-render
105
+ let tmpLoginView = this.pict.views['HarnessApp-Login'];
106
+ if (tmpLoginView)
107
+ {
108
+ tmpLoginView.authenticated = false;
109
+ tmpLoginView.sessionData = null;
110
+ tmpLoginView.initialRenderComplete = false;
111
+ tmpLoginView.render();
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Render a specific content view into the content container.
117
+ * Called by the router when a route template matches.
118
+ *
119
+ * @param {string} pViewIdentifier - The view identifier to render
120
+ */
121
+ showView(pViewIdentifier)
122
+ {
123
+ if (pViewIdentifier in this.pict.views)
124
+ {
125
+ this.pict.views[pViewIdentifier].render();
126
+ }
127
+ else
128
+ {
129
+ this.pict.log.warn('View [' + pViewIdentifier + '] not found; falling back to Dashboard.');
130
+ this.pict.views['HarnessApp-Dashboard'].render();
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Navigate to a route using pict-router.
136
+ *
137
+ * @param {string} pRoute - The route path (e.g. '/Dashboard', '/Books')
138
+ */
139
+ navigateTo(pRoute)
140
+ {
141
+ this.pict.providers.PictRouter.navigate(pRoute);
142
+ }
143
+
144
+ /**
145
+ * Log out and return to the login screen.
146
+ * Called from the TopBar logout button.
147
+ */
148
+ doLogout()
149
+ {
150
+ let tmpLoginView = this.pict.views['HarnessApp-Login'];
151
+ if (tmpLoginView)
152
+ {
153
+ tmpLoginView.logout(() =>
154
+ {
155
+ this.showLogin();
156
+ });
157
+ }
158
+ else
159
+ {
160
+ this.showLogin();
161
+ }
162
+ }
163
+ }
164
+
165
+ module.exports = HarnessAppApplication;
166
+
167
+ module.exports.default_configuration = require('./Harness-App-Configuration.json');
@@ -0,0 +1,4 @@
1
+ {
2
+ "Name": "Harness App",
3
+ "Hash": "HarnessApp"
4
+ }
@@ -0,0 +1,90 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Bookstore Harness - Pict Login + Router</title>
7
+ <!-- PICT Dynamic View CSS Container -->
8
+ <style id="PICT-CSS"></style>
9
+ <!-- Red Rock Mesa Base Theme -->
10
+ <style>
11
+ *, *::before, *::after { box-sizing: border-box; }
12
+ body
13
+ {
14
+ margin: 0;
15
+ padding: 0;
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
17
+ background: #FAEDCD;
18
+ color: #264653;
19
+ }
20
+ a { color: inherit; }
21
+
22
+ /* --- Login wrapper (centered on page) --- */
23
+ #HarnessApp-Login-Container
24
+ {
25
+ display: flex;
26
+ flex-direction: column;
27
+ align-items: center;
28
+ justify-content: center;
29
+ min-height: 100vh;
30
+ padding: 2rem;
31
+ }
32
+ .harnessapp-login-header
33
+ {
34
+ text-align: center;
35
+ margin-bottom: 1.5rem;
36
+ }
37
+ .harnessapp-login-header h1
38
+ {
39
+ font-size: 1.6rem;
40
+ color: #264653;
41
+ margin: 0 0 0.25rem 0;
42
+ }
43
+ .harnessapp-login-header p
44
+ {
45
+ color: #888;
46
+ font-size: 0.85rem;
47
+ margin: 0;
48
+ }
49
+ .harnessapp-login-hint
50
+ {
51
+ margin-top: 1rem;
52
+ text-align: center;
53
+ font-size: 0.8rem;
54
+ color: #999;
55
+ }
56
+ .harnessapp-login-hint code
57
+ {
58
+ background: #264653;
59
+ color: #FAEDCD;
60
+ padding: 0.1rem 0.35rem;
61
+ border-radius: 3px;
62
+ font-size: 0.75rem;
63
+ }
64
+ </style>
65
+ <script src="./pict.js" type="text/javascript"></script>
66
+ <script type="text/javascript">
67
+ Pict.safeOnDocumentReady(() => { Pict.safeLoadPictApplication(HarnessApp, 1); });
68
+ </script>
69
+ </head>
70
+ <body>
71
+
72
+ <!-- ===== Login Screen (visible when unauthenticated) ===== -->
73
+ <div id="HarnessApp-Login-Container">
74
+ <div class="harnessapp-login-header">
75
+ <h1>Bookstore Harness</h1>
76
+ <p>pict-section-login + pict-router</p>
77
+ </div>
78
+ <!-- pict-section-login renders its login card into this container -->
79
+ <div id="Pict-Login-Container"></div>
80
+ <div class="harnessapp-login-hint">
81
+ Use any valid user (e.g. <code>admin</code>, <code>jdoe</code>, <code>bsmith</code>) with any password.
82
+ </div>
83
+ </div>
84
+
85
+ <!-- ===== Protected App (hidden until authenticated) ===== -->
86
+ <div id="HarnessApp-Container" style="display:none"></div>
87
+
88
+ <script src="./harness_app.js" type="text/javascript"></script>
89
+ </body>
90
+ </html>
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "harness_app",
3
+ "version": "1.0.0",
4
+ "description": "Full application example — pict-section-login + pict-router against retold-harness bookstore",
5
+ "main": "Harness-App-Application.js",
6
+ "scripts": {
7
+ "start": "node Harness-App-Application.js",
8
+ "build": "npx quack build && npx quack copy"
9
+ },
10
+ "author": "steven velozo <steven@velozo.com>",
11
+ "license": "MIT",
12
+ "devDependencies": {
13
+ "pict-router": "^1.0.8"
14
+ },
15
+ "copyFilesSettings": {
16
+ "whenFileExists": "overwrite"
17
+ },
18
+ "copyFiles": [
19
+ {
20
+ "from": "./html/*",
21
+ "to": "./dist/"
22
+ },
23
+ {
24
+ "from": "../../node_modules/pict/dist/*",
25
+ "to": "./dist/"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "ProviderIdentifier": "Pict-Router",
3
+
4
+ "AutoInitialize": true,
5
+ "AutoInitializeOrdinal": 0,
6
+
7
+ "SkipRouteResolveOnAdd": true,
8
+
9
+ "Routes":
10
+ [
11
+ {
12
+ "path": "/Dashboard",
13
+ "template": "{~LV:Pict.PictApplication.showView(`HarnessApp-Dashboard`)~}"
14
+ },
15
+ {
16
+ "path": "/Books",
17
+ "template": "{~LV:Pict.PictApplication.showView(`HarnessApp-Books`)~}"
18
+ },
19
+ {
20
+ "path": "/Users",
21
+ "template": "{~LV:Pict.PictApplication.showView(`HarnessApp-Users`)~}"
22
+ }
23
+ ]
24
+ }