@elliemae/pui-app-bridge 2.19.2 → 2.21.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.
@@ -59,6 +59,12 @@ class EventManager {
59
59
  #emitEvent = (param) => {
60
60
  const { eventName, scriptingObject, eventParams, listeners } = param;
61
61
  listeners.map(async (listener) => {
62
+ if (listener.criteria) {
63
+ const matches = eventParams ? this.#matchesCriteria(eventParams, listener.criteria) : false;
64
+ if (!matches) {
65
+ return;
66
+ }
67
+ }
62
68
  try {
63
69
  await listener.callback({
64
70
  obj: scriptingObject,
@@ -77,8 +83,14 @@ class EventManager {
77
83
  feedbackWaitTime
78
84
  }) => {
79
85
  const listeners = this.#listeners.get(eventId) || [];
80
- const promises = listeners.map(
81
- (listener) => this.#asyncCallwithTimeout(
86
+ const promises = listeners.map((listener) => {
87
+ if (listener.criteria) {
88
+ const matches = eventParams ? this.#matchesCriteria(eventParams, listener.criteria) : false;
89
+ if (!matches) {
90
+ return Promise.resolve();
91
+ }
92
+ }
93
+ return this.#asyncCallwithTimeout(
82
94
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
83
95
  listener.callback({
84
96
  obj: scriptingObject,
@@ -86,11 +98,53 @@ class EventManager {
86
98
  eventParams
87
99
  }),
88
100
  feedbackWaitTime
89
- )
90
- );
101
+ );
102
+ });
91
103
  const retValues = await Promise.all(promises);
92
104
  return flatten(retValues);
93
105
  };
106
+ /**
107
+ * Checks if a given payload matches all specified filter criteria.
108
+ *
109
+ * Iterates over each entry in the criteria object and evaluates the corresponding condition
110
+ * against the payload's value for that key. Supported condition types include:
111
+ * - `eq`: Checks for strict equality.
112
+ * - `in`: Checks if the value is included in a provided array.
113
+ * - `includes`: Checks if the payload's value (an array) includes a specified element.
114
+ * - `gt`: Checks if the value is greater than a specified number.
115
+ * - `lt`: Checks if the value is less than a specified number.
116
+ * - `regex`: Checks if the value (a string) matches a provided regular expression.
117
+ * @param {Record<string, any>} payload - The object containing values to be checked against the criteria.
118
+ * @param {FilterCriteria} criteria - An object specifying filter conditions for each key.
119
+ * @returns {boolean} `true` if all criteria are satisfied by the payload, otherwise `false`.
120
+ */
121
+ #matchesCriteria(payload, criteria) {
122
+ return Object.entries(criteria).every(([key, condition]) => {
123
+ const value = payload[key];
124
+ if ("eq" in condition) return value === condition.eq;
125
+ if ("in" in condition)
126
+ return Array.isArray(condition.in) && condition.in.includes(value);
127
+ if ("includes" in condition)
128
+ return Array.isArray(value) && value.includes(condition.includes);
129
+ if ("gt" in condition) {
130
+ const numValue = typeof value === "number" ? value : Number(value);
131
+ return !Number.isNaN(numValue) && numValue > condition.gt;
132
+ }
133
+ if ("lt" in condition) {
134
+ const numValue = typeof value === "number" ? value : Number(value);
135
+ return !Number.isNaN(numValue) && numValue < condition.lt;
136
+ }
137
+ if ("regex" in condition) {
138
+ try {
139
+ const regex = new RegExp(condition.regex);
140
+ return typeof value === "string" && regex.test(value);
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+ return false;
146
+ });
147
+ }
94
148
  /**
95
149
  * dispatch an event
96
150
  * @param scriptingObject
@@ -127,13 +181,14 @@ class EventManager {
127
181
  * @param {SubscribeParam<EventId, AppEvents[EventId]>} param - parameters for subscribing to an event
128
182
  */
129
183
  subscribe = (param) => {
130
- const { eventId, callback } = param;
184
+ const { eventId, callback, criteria } = param;
131
185
  if (!eventId) throw new Error("eventId is required");
132
186
  if (!callback) throw new Error("Callback is required");
133
187
  const listeners = this.#listeners.get(eventId) || [];
134
188
  const token = (0, import_uuid.v4)();
135
189
  listeners.push({
136
190
  token,
191
+ criteria,
137
192
  callback
138
193
  });
139
194
  this.#listeners.set(eventId, listeners);
@@ -1,4 +1,7 @@
1
1
  {
2
2
  "type": "commonjs",
3
- "sideEffects": false
3
+ "sideEffects": false,
4
+ "publishConfig": {
5
+ "access": "public"
6
+ }
4
7
  }
@@ -36,6 +36,12 @@ class EventManager {
36
36
  #emitEvent = (param) => {
37
37
  const { eventName, scriptingObject, eventParams, listeners } = param;
38
38
  listeners.map(async (listener) => {
39
+ if (listener.criteria) {
40
+ const matches = eventParams ? this.#matchesCriteria(eventParams, listener.criteria) : false;
41
+ if (!matches) {
42
+ return;
43
+ }
44
+ }
39
45
  try {
40
46
  await listener.callback({
41
47
  obj: scriptingObject,
@@ -54,8 +60,14 @@ class EventManager {
54
60
  feedbackWaitTime
55
61
  }) => {
56
62
  const listeners = this.#listeners.get(eventId) || [];
57
- const promises = listeners.map(
58
- (listener) => this.#asyncCallwithTimeout(
63
+ const promises = listeners.map((listener) => {
64
+ if (listener.criteria) {
65
+ const matches = eventParams ? this.#matchesCriteria(eventParams, listener.criteria) : false;
66
+ if (!matches) {
67
+ return Promise.resolve();
68
+ }
69
+ }
70
+ return this.#asyncCallwithTimeout(
59
71
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
60
72
  listener.callback({
61
73
  obj: scriptingObject,
@@ -63,11 +75,53 @@ class EventManager {
63
75
  eventParams
64
76
  }),
65
77
  feedbackWaitTime
66
- )
67
- );
78
+ );
79
+ });
68
80
  const retValues = await Promise.all(promises);
69
81
  return flatten(retValues);
70
82
  };
83
+ /**
84
+ * Checks if a given payload matches all specified filter criteria.
85
+ *
86
+ * Iterates over each entry in the criteria object and evaluates the corresponding condition
87
+ * against the payload's value for that key. Supported condition types include:
88
+ * - `eq`: Checks for strict equality.
89
+ * - `in`: Checks if the value is included in a provided array.
90
+ * - `includes`: Checks if the payload's value (an array) includes a specified element.
91
+ * - `gt`: Checks if the value is greater than a specified number.
92
+ * - `lt`: Checks if the value is less than a specified number.
93
+ * - `regex`: Checks if the value (a string) matches a provided regular expression.
94
+ * @param {Record<string, any>} payload - The object containing values to be checked against the criteria.
95
+ * @param {FilterCriteria} criteria - An object specifying filter conditions for each key.
96
+ * @returns {boolean} `true` if all criteria are satisfied by the payload, otherwise `false`.
97
+ */
98
+ #matchesCriteria(payload, criteria) {
99
+ return Object.entries(criteria).every(([key, condition]) => {
100
+ const value = payload[key];
101
+ if ("eq" in condition) return value === condition.eq;
102
+ if ("in" in condition)
103
+ return Array.isArray(condition.in) && condition.in.includes(value);
104
+ if ("includes" in condition)
105
+ return Array.isArray(value) && value.includes(condition.includes);
106
+ if ("gt" in condition) {
107
+ const numValue = typeof value === "number" ? value : Number(value);
108
+ return !Number.isNaN(numValue) && numValue > condition.gt;
109
+ }
110
+ if ("lt" in condition) {
111
+ const numValue = typeof value === "number" ? value : Number(value);
112
+ return !Number.isNaN(numValue) && numValue < condition.lt;
113
+ }
114
+ if ("regex" in condition) {
115
+ try {
116
+ const regex = new RegExp(condition.regex);
117
+ return typeof value === "string" && regex.test(value);
118
+ } catch {
119
+ return false;
120
+ }
121
+ }
122
+ return false;
123
+ });
124
+ }
71
125
  /**
72
126
  * dispatch an event
73
127
  * @param scriptingObject
@@ -104,13 +158,14 @@ class EventManager {
104
158
  * @param {SubscribeParam<EventId, AppEvents[EventId]>} param - parameters for subscribing to an event
105
159
  */
106
160
  subscribe = (param) => {
107
- const { eventId, callback } = param;
161
+ const { eventId, callback, criteria } = param;
108
162
  if (!eventId) throw new Error("eventId is required");
109
163
  if (!callback) throw new Error("Callback is required");
110
164
  const listeners = this.#listeners.get(eventId) || [];
111
165
  const token = uuidv4();
112
166
  listeners.push({
113
167
  token,
168
+ criteria,
114
169
  callback
115
170
  });
116
171
  this.#listeners.set(eventId, listeners);
@@ -1,4 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
- "sideEffects": false
3
+ "sideEffects": false,
4
+ "publishConfig": {
5
+ "access": "public"
6
+ }
4
7
  }
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="mobile-web-app-capable" content="yes"/><link rel="icon" href="/favicon.ico"/><title>Application</title><script nonce="__CSP_NONCE__">!function(e,t,n,a,c,o,s){e.GoogleAnalyticsObject=c,e[c]=e[c]||function(){(e[c].q=e[c].q||[]).push(arguments)},e[c].l=1*new Date,o=t.createElement(n),s=t.getElementsByTagName(n)[0],o.async=1,o.src="https://www.google-analytics.com/analytics.js",s.parentNode.insertBefore(o,s)}(window,document,"script",0,"ga")</script><style nonce="__CSP_NONCE__">.full-width{width:100%}.full-height{height:100%}</style><script defer="defer" src="js/emuiAppBridge.023287a6115d74cc7707.js"></script></head><body class="full-width full-height"><noscript>If you're seeing this message, that means <strong>JavaScript has been disabled on your browser</strong>, please <strong>enable JS</strong> to make this app work.</noscript><div id="pui-app-container-" class="full-width full-height"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="mobile-web-app-capable" content="yes"/><link rel="icon" href="/favicon.ico"/><title>Application</title><script nonce="__CSP_NONCE__">!function(e,t,n,a,c,o,s){e.GoogleAnalyticsObject=c,e[c]=e[c]||function(){(e[c].q=e[c].q||[]).push(arguments)},e[c].l=1*new Date,o=t.createElement(n),s=t.getElementsByTagName(n)[0],o.async=1,o.src="https://www.google-analytics.com/analytics.js",s.parentNode.insertBefore(o,s)}(window,document,"script",0,"ga")</script><style nonce="__CSP_NONCE__">.full-width{width:100%}.full-height{height:100%}</style><script defer="defer" src="js/emuiAppBridge.62147eb5b94e639c6d83.js"></script></head><body class="full-width full-height"><noscript>If you're seeing this message, that means <strong>JavaScript has been disabled on your browser</strong>, please <strong>enable JS</strong> to make this app work.</noscript><div id="pui-app-container-" class="full-width full-height"></div></body></html>
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Host</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script src="https://cdn.qa1.ice.com/pui-diagnostics@3"></script><script defer="defer" src="js/emuiAppBridge.023287a6115d74cc7707.js"></script></head><body><header class="bg-indigo-300 h-10 flex place-items-center"><div class="px-2">ICE Mortgage Product</div></header><main class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8"><div class="min-w-0 flex-1 mt-4"><h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">Loan Application</h1></div><div id="successFeedback" class="hidden rounded-md bg-green-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd"/></svg></div><div class="ml-3"><p class="text-sm font-medium text-green-800">Loan Saved Successfully</p></div></div></div><div id="errorFeedback" class="hidden rounded-md bg-red-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd"/></svg></div><div class="ml-3"><h3 class="text-sm font-medium text-red-800">Credit Score is not meeting the requirement</h3></div></div></div><div class="mt-2 sm:grid sm:grid-cols-2 sm:gap-2"><form class="px-2 py-2 space-y-8 divide-y divide-gray-200 bg-gray-50"><div class="space-y-8 divide-y divide-gray-200 sm:space-y-5"><div class="space-y-6 sm:space-y-5"><div><h3 class="text-lg font-medium leading-6 text-gray-900">Personal Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="firstName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">First name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="firstName" id="firstName" autocomplete="given-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="John" placeholder="John"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="lastName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Last name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="lastName" id="lastName" autocomplete="family-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="Doe" placeholder="Doe"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="ssn" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">SSN</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="ssn" id="ssn" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="123456789" placeholder="123456789"/></div></div></div><div><h3 class="text-lg font-medium leading-6 text-gray-900">Loan Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="amount" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Amount</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="amount" id="amount" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="500000" placeholder="500000"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="Term" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Term (years)</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="term" id="term" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="30" placeholder="30"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="downPayment" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Down Payment</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="downPayment" id="downPayment" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="50000" placeholder="50000"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="creditScore" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Credit Score</label><div class="mt-1 sm:mt-0"><output id="creditScore" class="block w-full max-w-lg pl-2 pt-2 sm:max-w-xs sm:text-sm">NA</output></div><div class="mt-1 sm:mt-0"><button id="getCreditScore" type="button" class="inline-flex items-center rounded-full border border-transparent bg-indigo-600 p-1 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/></svg></button></div></div></div></div></div><button id="saveLoan" type="button" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Save</button></form><div id="aside-container" class="flex flex-col gap-4 items-start mt-4 border-2 p-2 rounded-lg border-dashed border-cyan-300 sm:mt-0"></div></div><div id="bottom-container" class="flex flex-col gap-4 items-start mt-4 p-2 sm:mt-0"></div></main><script src="./init.js" type="module"></script></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Host</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script src="https://cdn.qa1.ice.com/pui-diagnostics@3"></script><script defer="defer" src="js/emuiAppBridge.62147eb5b94e639c6d83.js"></script></head><body><header class="bg-indigo-300 h-10 flex place-items-center"><div class="px-2">ICE Mortgage Product</div></header><main class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8"><div class="min-w-0 flex-1 mt-4"><h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">Loan Application</h1></div><div id="successFeedback" class="hidden rounded-md bg-green-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd"/></svg></div><div class="ml-3"><p class="text-sm font-medium text-green-800">Loan Saved Successfully</p></div></div></div><div id="errorFeedback" class="hidden rounded-md bg-red-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd"/></svg></div><div class="ml-3"><h3 class="text-sm font-medium text-red-800">Credit Score is not meeting the requirement</h3></div></div></div><div class="mt-2 sm:grid sm:grid-cols-2 sm:gap-2"><form class="px-2 py-2 space-y-8 divide-y divide-gray-200 bg-gray-50"><div class="space-y-8 divide-y divide-gray-200 sm:space-y-5"><div class="space-y-6 sm:space-y-5"><div><h3 class="text-lg font-medium leading-6 text-gray-900">Personal Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="firstName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">First name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="firstName" id="firstName" autocomplete="given-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="John" placeholder="John"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="lastName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Last name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="lastName" id="lastName" autocomplete="family-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="Doe" placeholder="Doe"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="ssn" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">SSN</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="ssn" id="ssn" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="123456789" placeholder="123456789"/></div></div></div><div><h3 class="text-lg font-medium leading-6 text-gray-900">Loan Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="amount" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Amount</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="amount" id="amount" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="500000" placeholder="500000"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="Term" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Term (years)</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="term" id="term" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="30" placeholder="30"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="downPayment" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Down Payment</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="downPayment" id="downPayment" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="50000" placeholder="50000"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="creditScore" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Credit Score</label><div class="mt-1 sm:mt-0"><output id="creditScore" class="block w-full max-w-lg pl-2 pt-2 sm:max-w-xs sm:text-sm">NA</output></div><div class="mt-1 sm:mt-0"><button id="getCreditScore" type="button" class="inline-flex items-center rounded-full border border-transparent bg-indigo-600 p-1 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/></svg></button></div></div></div></div></div><button id="saveLoan" type="button" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Save</button></form><div id="aside-container" class="flex flex-col gap-4 items-start mt-4 border-2 p-2 rounded-lg border-dashed border-cyan-300 sm:mt-0"></div></div><div id="bottom-container" class="flex flex-col gap-4 items-start mt-4 p-2 sm:mt-0"></div></main><script src="./init.js" type="module"></script></body></html>