Refactors and improves profile settings tab.

This commit is contained in:
Dominik Picheta 2018-05-19 15:41:11 +01:00
commit 54a7060dba
4 changed files with 204 additions and 130 deletions

View file

@ -45,6 +45,17 @@ proc isLikedBy*(post: Post, user: Option[User]): bool =
return false
type
Profile* = object
user*: User
joinTime*: int64
threads*: seq[PostLink]
posts*: seq[PostLink]
postCount*: int
threadCount*: int
# Information that only admins should see.
email*: Option[string]
when defined(js):
import karaxutils

View file

@ -62,6 +62,7 @@ when defined(js):
},
"btn btn-secondary"
),
`type`="button",
onClick=(e: Event, n: VNode) => (onClick(e, n, state))):
if state.posted:
if state.error.isNone():

View file

@ -1,58 +1,30 @@
import options, httpcore, json, sugar, times, strformat
import options, httpcore, json, sugar, times, strformat, strutils
import threadlist, post, category, error, user
type
Profile* = object
user*: User
joinTime*: int64
threads*: seq[PostLink]
posts*: seq[PostLink]
postCount*: int
threadCount*: int
# Information that only admins should see.
email*: Option[string]
when defined(js):
include karax/prelude
import karax/[kajax]
import karaxutils, postbutton
import karax/[kajax, kdom]
import karaxutils, postbutton, delete, profilesettings
type
ProfileTab* = enum
Overview, Settings
ProfileSettings* = object
email: kstring
rank: Rank
ProfileState* = ref object
profile: Option[Profile]
settings: ProfileSettings
settings: Option[ProfileSettings]
currentTab: ProfileTab
loading: bool
status: HttpCode
resetPassword: Option[PostButton]
proc newProfileState*(): ProfileState =
ProfileState(
loading: false,
status: Http200,
currentTab: Overview,
settings: ProfileSettings(
email: "",
rank: Spammer
)
currentTab: Overview
)
proc resetSettings(state: ProfileState) =
let profile = state.profile.get()
if profile.email.isSome():
state.settings = ProfileSettings(
email: profile.email.get(),
rank: profile.user.rank
)
proc onProfile(httpStatus: int, response: kstring, state: ProfileState) =
# TODO: Try to abstract these.
state.loading = false
@ -63,9 +35,7 @@ when defined(js):
let profile = to(parsed, Profile)
state.profile = some(profile)
resetSettings(state)
if profile.email.isSome():
state.resetPassword = some(newResetPasswordButton(profile.email.get()))
state.settings = some(newProfileSettings(profile))
proc genPostLink(link: PostLink): VNode =
let url = renderPostUrl(link)
@ -81,14 +51,6 @@ when defined(js):
p(title=title):
text renderActivity(link.creation)
proc onEmailChange(event: Event, node: VNode, state: ProfileState) =
state.settings.email = node.value
if state.settings.email != state.profile.get().email.get():
state.settings.rank = EmailUnconfirmed
else:
state.settings.rank = state.profile.get().user.rank
proc render*(
state: ProfileState,
username: string,
@ -104,37 +66,6 @@ when defined(js):
return buildHtml(tdiv(class="loading loading-lg"))
let profile = state.profile.get()
let isAdmin = currentUser.isSome() and currentUser.get().rank == Admin
let rankSelect = buildHtml(tdiv()):
if isAdmin:
select(class="form-select", value = $state.settings.rank):
for r in Rank:
option(text $r)
p(class="form-input-hint text-warning"):
text "As an admin you can modify anyone's rank. Remember: with " &
"great power comes great responsibility."
else:
input(class="form-input",
`type`="text", value = $state.settings.rank, disabled="")
p(class="form-input-hint"):
text "Your rank determines the actions you can perform " &
"on the forum."
case state.settings.rank:
of Spammer, Troll:
p(class="form-input-hint text-warning"):
text "Your account was banned."
of EmailUnconfirmed:
p(class="form-input-hint text-warning"):
text "You cannot post until you confirm your email."
of Moderated:
p(class="form-input-hint text-warning"):
text "Your account is under moderation. This is a spam prevention "&
"measure. You can write posts but only moderators and admins "&
"will see them until your account is verified by them."
else:
discard
result = buildHtml():
section(class="container grid-xl"):
tdiv(class="profile"):
@ -205,58 +136,5 @@ when defined(js):
for thread in profile.threads:
genPostLink(thread)
of Settings:
tdiv(class="columns"):
tdiv(class="column col-6"):
form(class="form-horizontal"):
tdiv(class="form-group"):
tdiv(class="col-3 col-sm-12"):
label(class="form-label"):
text "Username"
tdiv(class="col-9 col-sm-12"):
input(class="form-input",
`type`="text",
value=profile.user.name,
disabled="")
p(class="form-input-hint"):
text fmt("Users can refer to you by writing" &
" @{profile.user.name} in their posts.")
tdiv(class="form-group"):
tdiv(class="col-3 col-sm-12"):
label(class="form-label"):
text "Email"
tdiv(class="col-9 col-sm-12"):
input(class="form-input",
`type`="text", value=state.settings.email,
oninput=(e: Event, n: VNode) =>
onEmailChange(e, n, state)
)
p(class="form-input-hint"):
text "Your avatar is linked to this email and can be " &
"changed at "
a(href="https://gravatar.com/emails"):
text "gravatar.com"
text ". Note that any changes to your email will " &
"require email verification."
tdiv(class="form-group"):
tdiv(class="col-3 col-sm-12"):
label(class="form-label"):
text "Rank"
tdiv(class="col-9 col-sm-12"):
rankSelect
if state.resetPassword.isSome():
tdiv(class="form-group"):
tdiv(class="col-3 col-sm-12"):
label(class="form-label"):
text "Password"
tdiv(class="col-9 col-sm-12"):
render(state.resetPassword.get(),
disabled=state.settings.rank==EmailUnconfirmed)
tdiv(class="float-right"):
button(class="btn btn-link",
onClick=(e: Event, n: VNode) => (resetSettings(state))):
text "Cancel"
button(class="btn btn-primary"):
italic(class="fas fa-check")
text " Save"
if state.settings.isSome():
render(state.settings.get(), currentUser)

View file

@ -0,0 +1,184 @@
when defined(js):
import httpcore, options, sugar, json, strutils, strformat
include karax/prelude
import karax/[kajax, kdom]
import replybox, post, karaxutils, postbutton, error, delete, user
type
ProfileSettings* = ref object
loading: bool
status: HttpCode
error: Option[PostError]
email: kstring
rank: Rank
deleteModal: DeleteModal
resetPassword: PostButton
profile: Profile
proc onUserDelete(user: User) =
window.location.href = makeUri("/")
proc resetSettings(state: ProfileSettings) =
let profile = state.profile
if profile.email.isSome():
state.email = profile.email.get()
state.rank = profile.user.rank
proc newProfileSettings*(profile: Profile): ProfileSettings =
result = ProfileSettings(
status: Http200,
deleteModal: newDeleteModal(nil, nil, onUserDelete),
resetPassword: newResetPasswordButton(profile.email.get()),
profile: profile
)
resetSettings(result)
proc onProfilePost(httpStatus: int, response: kstring,
state: ProfileSettings) =
postFinished:
discard
proc onEmailChange(event: Event, node: VNode, state: ProfileSettings) =
state.email = node.value
if state.profile.user.rank != Admin:
if state.email != state.profile.email.get():
state.rank = EmailUnconfirmed
else:
state.rank = state.profile.user.rank
proc onRankChange(event: Event, node: VNode, state: ProfileSettings) =
state.rank = parseEnum[Rank]($node.value)
proc save(state: ProfileSettings) =
if state.loading:
return
state.loading = true
state.error = none[PostError]()
let formData = newFormData()
formData.append("email", state.email)
formData.append("rank", $state.rank)
formData.append("username", $state.profile.user.name)
let uri = makeUri("/saveProfile")
ajaxPost(uri, @[], cast[cstring](formData),
(s: int, r: kstring) => onProfilePost(s, r, state))
proc render*(state: ProfileSettings,
currentUser: Option[User]): VNode =
if state.status != Http200:
return renderError("Couldn't save profile")
let isAdmin = currentUser.isSome() and currentUser.get().rank == Admin
let canResetPassword = state.profile.user.rank > EmailUnconfirmed
let rankSelect = buildHtml(tdiv()):
if isAdmin:
select(id="rank-field",
class="form-select", value = $state.rank,
onchange=(e: Event, n: VNode) => onRankChange(e, n, state)):
for r in Rank:
option(text $r)
p(class="form-input-hint text-warning"):
text "As an admin you can modify anyone's rank. Remember: with " &
"great power comes great responsibility."
else:
input(id="rank-field", class="form-input",
`type`="text", disabled="", value = $state.rank)
p(class="form-input-hint"):
text "Your rank determines the actions you can perform " &
"on the forum."
case state.rank:
of Spammer, Troll:
p(class="form-input-hint text-warning"):
text "Your account was banned."
of EmailUnconfirmed:
p(class="form-input-hint text-warning"):
text "You cannot post until you confirm your email."
of Moderated:
p(class="form-input-hint text-warning"):
text "Your account is under moderation. This is a spam prevention "&
"measure. You can write posts but only moderators and admins "&
"will see them until your account is verified by them."
else:
discard
result = buildHtml():
tdiv(class="columns"):
tdiv(class="column col-6"):
form(class="form-horizontal"):
tdiv(class="form-group"):
tdiv(class="col-3 col-sm-12"):
label(class="form-label"):
text "Username"
tdiv(class="col-9 col-sm-12"):
input(class="form-input",
`type`="text",
value=state.profile.user.name,
disabled="")
p(class="form-input-hint"):
text fmt("Users can refer to you by writing" &
" @{state.profile.user.name} in their posts.")
tdiv(class="form-group"):
tdiv(class="col-3 col-sm-12"):
label(class="form-label"):
text "Email"
tdiv(class="col-9 col-sm-12"):
input(id="email-input", class="form-input",
`type`="text", value=state.email,
oninput=(e: Event, n: VNode) =>
onEmailChange(e, n, state)
)
p(class="form-input-hint"):
text "Your avatar is linked to this email and can be " &
"changed at "
a(href="https://gravatar.com/emails"):
text "gravatar.com"
text ". Note that any changes to your email will " &
"require email verification."
tdiv(class="form-group"):
tdiv(class="col-3 col-sm-12"):
label(class="form-label"):
text "Rank"
tdiv(class="col-9 col-sm-12"):
rankSelect
tdiv(class="form-group"):
tdiv(class="col-3 col-sm-12"):
label(class="form-label"):
text "Password"
tdiv(class="col-9 col-sm-12"):
render(state.resetPassword,
disabled=not canResetPassword)
tdiv(class="form-group"):
tdiv(class="col-3 col-sm-12"):
label(class="form-label"):
text "Account"
tdiv(class="col-9 col-sm-12"):
button(class="btn btn-secondary", `type`="button",
onClick=(e: Event, n: VNode) =>
(state.deleteModal.show(state.profile.user))):
italic(class="fas fa-times")
text " Delete account"
tdiv(class="float-right"):
button(class="btn btn-link",
onClick=(e: Event, n: VNode) => (resetSettings(state))):
text "Cancel"
button(class="btn btn-primary",
onClick=(e: Event, n: VNode) => save(state)):
italic(class="fas fa-check")
text " Save"
render(state.deleteModal)
# TODO: I really should just be able to set the `value` attr.
# TODO: This doesn't work when settings are reset for some reason.
let rankField = getVNodeById("rank-field")
if not rankField.isNil:
rankField.setInputText($state.rank)
let emailField = getVNodeById("email-field")
if not emailField.isNil:
emailField.setInputText($state.email)