BT  ·  2026-06-08

Login.res.mjs

  1// Generated by ReScript, PLEASE EDIT WITH CARE
  2
  3import * as Fetch from "../ffi/Fetch.res.mjs";
  4import * as State from "../State.res.mjs";
  5import * as Preact from "../ffi/Preact.res.mjs";
  6import * as Content from "./Content.res.mjs";
  7import * as Core__JSON from "@rescript/core/lib/es6/src/Core__JSON.res.mjs";
  8import * as Core__Option from "@rescript/core/lib/es6/src/Core__Option.res.mjs";
  9import * as Signals from "@preact/signals";
 10import * as JsxRuntime from "preact/jsx-runtime";
 11
 12let errors = Signals.signal(undefined);
 13
 14let signup = Signals.signal(false);
 15
 16let submitting = Signals.signal(false);
 17
 18function get(selector) {
 19  let elem = document.querySelector(selector);
 20  if (elem == null) {
 21    return;
 22  }
 23  if (elem.value === undefined) {
 24    return;
 25  }
 26  let v = elem.value;
 27  if (!(v == null) && v !== "") {
 28    return v;
 29  }
 30}
 31
 32function validateLoginForm(user, password) {
 33  if (user !== undefined) {
 34    if (password !== undefined) {
 35      return "Valid";
 36    } else {
 37      return {
 38        TAG: "Invalid",
 39        _0: "Password is required"
 40      };
 41    }
 42  } else if (password !== undefined) {
 43    return {
 44      TAG: "Invalid",
 45      _0: "Username or handle is required"
 46    };
 47  } else {
 48    return {
 49      TAG: "Invalid",
 50      _0: "Username and password are required"
 51    };
 52  }
 53}
 54
 55function validateSignupForm(user, handle, password) {
 56  if (user === undefined && handle === undefined) {
 57    return {
 58      TAG: "Invalid",
 59      _0: "A handle or an email and a password are required"
 60    };
 61  }
 62  if (password !== undefined) {
 63    return "Valid";
 64  } else {
 65    return {
 66      TAG: "Invalid",
 67      _0: "A handle or an email and a password are required"
 68    };
 69  }
 70}
 71
 72function parseUserFromJSON(json) {
 73  let dict = Core__JSON.Decode.object(json);
 74  if (dict === undefined) {
 75    return;
 76  }
 77  let id = Core__Option.flatMap(dict["id"], Core__JSON.Decode.string);
 78  let email = Core__Option.getOr(Core__Option.flatMap(dict["email"], Core__JSON.Decode.string), "");
 79  let handle = Core__Option.getOr(Core__Option.flatMap(dict["handle"], Core__JSON.Decode.string), "");
 80  let registered = Core__Option.getOr(Core__Option.flatMap(dict["registered"], Core__JSON.Decode.bool), false);
 81  if (id !== undefined) {
 82    return {
 83      id: id,
 84      email: email,
 85      handle: handle,
 86      registered: registered
 87    };
 88  }
 89}
 90
 91async function submit() {
 92  errors.value = undefined;
 93  submitting.value = true;
 94  let user = get("#user");
 95  let pwd = get("#password");
 96  let validation;
 97  if (signup.value) {
 98    let handle = get("#handle");
 99    validation = validateSignupForm(user, handle, pwd);
100  } else {
101    validation = validateLoginForm(user, pwd);
102  }
103  if (typeof validation !== "object") {
104    let url = signup.value ? "/signup" : "/login";
105    let bodyDict = {};
106    bodyDict["user"] = Core__Option.getOr(user, "");
107    bodyDict["password"] = Core__Option.getOr(pwd, "");
108    if (signup.value) {
109      let handle$1 = get("#handle");
110      bodyDict["handle"] = Core__Option.getOr(handle$1, "");
111    }
112    await Fetch.send(Fetch.onResponse(Fetch.credentials(Fetch.setBody(Fetch.post(url), bodyDict), "include"), json => {
113      submitting.value = false;
114      let user = parseUserFromJSON(json);
115      if (user !== undefined) {
116        State.user.value = user;
117        State.content.value = undefined;
118      } else {
119        errors.value = "Invalid response from server";
120      }
121    }, (status, msg) => {
122      submitting.value = false;
123      errors.value = msg;
124      console.error("Login/signup failed", [
125        status,
126        msg
127      ]);
128    }));
129    return;
130  }
131  errors.value = validation._0;
132  submitting.value = false;
133}
134
135let fields = "bg-white border border-gray-300 text-gray-900 rounded-lg focus:ring-[var(--color-orange)] focus:border-[var(--color-orange)] block w-full p-2.5";
136
137function Login(props) {
138  let match = signup.value;
139  let match$1 = submitting.value;
140  let err = errors.value;
141  return JsxRuntime.jsxs("div", {
142    children: [
143      JsxRuntime.jsx(Content.CloseButton.make, {}),
144      JsxRuntime.jsxs("div", {
145        children: [
146          JsxRuntime.jsxs("div", {
147            children: [
148              JsxRuntime.jsx("label", {
149                children: signup.value ? "Email" : "Email or Handle",
150                className: Preact.cls("block text-sm font-medium", {
151                  "mb-2": !signup.value
152                }),
153                htmlFor: "user"
154              }),
155              signup.value ? JsxRuntime.jsx("div", {
156                  children: "Email is optional. Without it you can't reset your password.",
157                  className: "text-xs text-gray-700 italic leading-none mb-2"
158                }) : null,
159              JsxRuntime.jsx("input", {
160                className: fields,
161                id: "user",
162                autoComplete: "off",
163                autoCapitalize: "off",
164                name: "user",
165                type: "text"
166              })
167            ],
168            className: ""
169          }),
170          signup.value ? JsxRuntime.jsxs("div", {
171              children: [
172                JsxRuntime.jsx("label", {
173                  children: "Handle",
174                  className: "block text-sm font-medium",
175                  htmlFor: "handle"
176                }),
177                JsxRuntime.jsxs("div", {
178                  children: [
179                    JsxRuntime.jsx("span", {
180                      children: "Your handle will appear on your public routes. You can change it later but it must be unique. Allowed characters:"
181                    }),
182                    JsxRuntime.jsx("span", {
183                      children: " A-Z a-z 0-9 . _  @ - [] ! ",
184                      className: "not-italic font-mono"
185                    }),
186                    JsxRuntime.jsx("span", {
187                      children: "and at least 3 letters long."
188                    })
189                  ],
190                  className: "text-xs text-gray-700 italic leading-none mb-2"
191                }),
192                JsxRuntime.jsx("input", {
193                  className: fields,
194                  id: "handle",
195                  autoComplete: "off",
196                  autoCapitalize: "off",
197                  name: "handle"
198                })
199              ],
200              className: ""
201            }) : null,
202          JsxRuntime.jsxs("div", {
203            children: [
204              JsxRuntime.jsx("label", {
205                children: "Password",
206                className: "block mb-2 text-sm font-medium",
207                htmlFor: "password"
208              }),
209              JsxRuntime.jsx("input", {
210                className: fields,
211                id: "password",
212                autoComplete: "off",
213                autoCapitalize: "off",
214                name: "password",
215                type: "password"
216              })
217            ],
218            className: ""
219          }),
220          JsxRuntime.jsx("div", {
221            children: JsxRuntime.jsx("button", {
222              children: match ? (
223                  match$1 ? "Submitting..." : "Sign up"
224                ) : (
225                  match$1 ? "Submitting..." : "Login"
226                ),
227              className: "w-full text-white bg-[var(--color-orange)] focus:ring-4 focus:outline-none focus:ring-[var(--color-blue)] font-medium rounded-lg text-sm px-5 py-2.5 disabled:opacity-50 disabled:cursor-not-allowed",
228              disabled: submitting.value,
229              onClick: _evt => {
230                submit();
231              }
232            }),
233            className: ""
234          }),
235          err !== undefined ? JsxRuntime.jsx("div", {
236              children: err,
237              className: "bg-red-300 text-[var(--color-red)] px-4 py-2 rounded-sm"
238            }) : null,
239          signup.value ? JsxRuntime.jsxs("div", {
240              children: [
241                "Already have an account? ",
242                JsxRuntime.jsx("a", {
243                  children: "Login",
244                  className: "text-[var(--color-blue)] cursor-pointer underline decoration-dotted",
245                  role: "button",
246                  onClick: _evt => {
247                    signup.value = false;
248                  }
249                })
250              ],
251              className: ""
252            }) : JsxRuntime.jsxs("div", {
253              children: [
254                "Don't have an account? ",
255                JsxRuntime.jsx("a", {
256                  children: "Sign up",
257                  className: "text-[var(--color-blue)] cursor-pointer underline decoration-dotted",
258                  role: "button",
259                  onClick: _evt => {
260                    signup.value = true;
261                  }
262                })
263              ],
264              className: ""
265            })
266        ],
267        className: "space-y-4 md:space-y-6"
268      })
269    ],
270    className: "flex flex-col p-10 md:p-15"
271  });
272}
273
274let make = Login;
275
276export {
277  errors,
278  signup,
279  submitting,
280  get,
281  validateLoginForm,
282  validateSignupForm,
283  parseUserFromJSON,
284  submit,
285  fields,
286  make,
287}
288/* errors Not a pure module */