diff --git a/redesign/error.nim b/redesign/error.nim index 108adbe..3d540d5 100644 --- a/redesign/error.nim +++ b/redesign/error.nim @@ -1,3 +1,4 @@ +import options type PostError* = object errorFields*: seq[string] ## IDs of the fields with an error. @@ -7,6 +8,8 @@ when defined(js): include karax/prelude import karax / [vstyles, kajax, kdom] + import karaxutils + proc renderError*(message: string): VNode = result = buildHtml(): tdiv(class="empty error"): @@ -19,4 +22,22 @@ when defined(js): tdiv(class="empty-action"): a(href="https://github.com/nim-lang/nimforum/issues", target="_blank"): button(class="btn btn-primary"): - text "Report issue" \ No newline at end of file + text "Report issue" + + proc genFormField*(error: Option[PostError], name, label, typ: string, + isLast: bool): VNode = + let hasError = + not error.isNone and ( + name in error.get().errorFields or + error.get().errorFields.len == 0) + result = buildHtml(): + tdiv(class=class({"has-error": hasError}, "form-group")): + label(class="form-label", `for`=name): + text label + input(class="form-input", `type`="text", name=name) + + if not error.isNone: + let e = error.get() + if (e.errorFields.len == 1 and e.errorFields[0] == name) or isLast: + p(class="form-input-hint"): + text e.message \ No newline at end of file diff --git a/redesign/header.nim b/redesign/header.nim index 1d7b3bb..cf07135 100644 --- a/redesign/header.nim +++ b/redesign/header.nim @@ -9,7 +9,7 @@ when defined(js): include karax/prelude import karax / [kajax] - import login + import login, signup import karaxutils from dom import setTimeout, window, document, getElementById, focus @@ -21,6 +21,7 @@ when defined(js): status: HttpCode lastUpdate: Time loginModal: LoginModal + signupModal: SignupModal proc newState(): State var @@ -33,7 +34,12 @@ when defined(js): loading: false, status: Http200, loginModal: newLoginModal( - () => (state.lastUpdate = fromUnix(0); getStatus()) + () => (state.lastUpdate = fromUnix(0); getStatus()), + () => state.signupModal.show() + ), + signupModal: newSignupModal( + () => (state.lastUpdate = fromUnix(0); getStatus()), + () => state.loginModal.show() ) ) @@ -57,37 +63,6 @@ when defined(js): let uri = makeUri("status.json") ajaxGet(uri, @[], onStatus) - proc genSignUpModal(): VNode = - result = buildHtml(): - tdiv(class="modal", id="signup-modal"): - a(href="#", class="modal-overlay", "aria-label"="close") - tdiv(class="modal-container"): - tdiv(class="modal-header"): - a(href="#", class="btn btn-clear float-right", "aria-label"="close") - tdiv(class="modal-title h5"): - text "Create a new account" - tdiv(class="modal-body"): - tdiv(class="content"): - form(): - tdiv(class="form-group"): - label(class="form-label", `for`="email"): - text "Email" - input(class="form-input", `type`="text", name="email") - tdiv(class="form-group"): - label(class="form-label", `for`="regusername"): - text "Username" - input(class="form-input", `type`="text", name="username") - tdiv(class="form-group"): - label(class="form-label", `for`="regpassword"): - text "Password" - input(class="form-input", `type`="password", name="password") - tdiv(class="modal-footer"): - button(class="btn btn-primary"): - text "Create account" - a(href="#login-modal"): - button(class="btn"): - text "Log in" - proc renderHeader*(): VNode = if state.data.isNone: getStatus() @@ -107,10 +82,10 @@ when defined(js): if state.loading: tdiv(class="loading") elif user.isNone: - a(href="#signup-modal", id="signup-btn"): - button(class="btn btn-primary btn-sm"): - italic(class="fas fa-user-plus") - text " Sign up" + button(class="btn btn-primary btn-sm", + onClick=(e: Event, n: VNode) => state.signupModal.show()): + italic(class="fas fa-user-plus") + text " Sign up" button(class="btn btn-primary btn-sm", onClick=(e: Event, n: VNode) => state.loginModal.show()): italic(class="fas fa-sign-in-alt") @@ -121,4 +96,4 @@ when defined(js): # Modals render(state.loginModal) - genSignUpModal() \ No newline at end of file + render(state.signupModal) \ No newline at end of file diff --git a/redesign/karaxutils.nim b/redesign/karaxutils.nim index 2c7f7ad..7ee5eaf 100644 --- a/redesign/karaxutils.nim +++ b/redesign/karaxutils.nim @@ -1,4 +1,4 @@ -import strutils +import strutils, options import dom except window include karax/prelude diff --git a/redesign/login.nim b/redesign/login.nim index aa92fac..006a610 100644 --- a/redesign/login.nim +++ b/redesign/login.nim @@ -12,6 +12,7 @@ when defined(js): LoginModal* = ref object shown: bool onLogIn: proc () + onSignUp: proc () error: Option[PostError] proc onLogInPost(httpStatus: int, response: kstring, state: LoginModal) = @@ -47,33 +48,16 @@ when defined(js): state.shown = false ev.preventDefault() - proc newLoginModal*(onLogIn: proc ()): LoginModal = + proc newLoginModal*(onLogIn, onSignUp: proc ()): LoginModal = LoginModal( shown: false, - onLogIn: onLogIn + onLogIn: onLogIn, + onSignUp: onSignUp ) proc show*(state: LoginModal) = state.shown = true - proc genFormField(error: Option[PostError], name, label, typ: string, - isLast: bool): VNode = - let hasError = - not error.isNone and ( - name in error.get().errorFields or - error.get().errorFields.len == 0) - result = buildHtml(): - tdiv(class=class({"has-error": hasError}, "form-group")): - label(class="form-label", `for`=name): - text "Username" - input(class="form-input", `type`="text", name=name) - - if not error.isNone: - let e = error.get() - if (e.errorFields.len == 1 and e.errorFields[0] == name) or isLast: - p(class="form-input-hint"): - text e.message - proc render*(state: LoginModal): VNode = result = buildHtml(): tdiv(class=class({"active": state.shown}, "modal modal-sm"), @@ -104,6 +88,7 @@ when defined(js): button(class="btn btn-primary", onClick=(ev: Event, n: VNode) => onLogInClick(ev, n, state)): text "Log in" - a(href="#signup-modal"): - button(class="btn"): - text "Create account" \ No newline at end of file + button(class="btn", + onClick=(ev: Event, n: VNode) => + (state.onSignUp(); state.shown = false)): + text "Create account" \ No newline at end of file diff --git a/redesign/signup.nim b/redesign/signup.nim new file mode 100644 index 0000000..1b0db46 --- /dev/null +++ b/redesign/signup.nim @@ -0,0 +1,92 @@ +when defined(js): + import sugar, httpcore, options, json + import dom except Event + + include karax/prelude + import karax / [kajax, kdom] + + import error + import karaxutils + + type + SignupModal* = ref object + shown: bool + onSignUp, onLogIn: proc () + error: Option[PostError] + + proc onSignUpPost(httpStatus: int, response: kstring, state: SignupModal) = + let status = httpStatus.HttpCode + if status == Http200: + state.shown = false + state.onSignUp() + else: + # TODO: Karax should pass the content-type... + try: + let parsed = parseJson($response) + let error = to(parsed, PostError) + + state.error = some(error) + except: + kout(getCurrentExceptionMsg().cstring) + state.error = some(PostError( + errorFields: @[], + message: "Unknown error occurred." + )) + + proc onSignUpClick(ev: Event, n: VNode, state: SignupModal) = + state.error = none[PostError]() + + let uri = makeUri("signup") + let form = dom.document.getElementById("signup-form") + # TODO: This is a hack, karax should support this. + let formData = newFormData(form) + ajaxPost(uri, @[], cast[cstring](formData), + (s: int, r: kstring) => onSignUpPost(s, r, state)) + + proc onClose(ev: Event, n: VNode, state: SignupModal) = + state.shown = false + ev.preventDefault() + + proc newSignupModal*(onSignUp, onLogIn: proc ()): SignupModal = + SignupModal( + shown: false, + onLogIn: onLogIn, + onSignUp: onSignUp + ) + + proc show*(state: SignupModal) = + state.shown = true + + proc render*(state: SignupModal): VNode = + result = buildHtml(): + tdiv(class=class({"active": state.shown}, "modal modal-sm"), + id="signup-modal"): + a(href="", class="modal-overlay", "aria-label"="close", + onClick=(ev: Event, n: VNode) => onClose(ev, n, state)) + tdiv(class="modal-container"): + tdiv(class="modal-header"): + a(href="", class="btn btn-clear float-right", + "aria-label"="close", + onClick=(ev: Event, n: VNode) => onClose(ev, n, state)) + tdiv(class="modal-title h5"): + text "Create a new account" + tdiv(class="modal-body"): + tdiv(class="content"): + form(id="signup-form"): + genFormField(state.error, "email", "Email", "email", false) + genFormField(state.error, "username", "Username", "text", false) + genFormField( + state.error, + "password", + "Password", + "password", + true + ) + tdiv(class="modal-footer"): + button(class="btn btn-primary", + onClick=(ev: Event, n: VNode) => onSignUpClick(ev, n, state)): + text "Create account" + button(class="btn", + onClick=(ev: Event, n: VNode) => + (state.onLogIn(); state.shown = false)): + text "Log in" \ No newline at end of file