Refactors and improves profile settings tab.
This commit is contained in:
parent
dff0e89115
commit
54a7060dba
4 changed files with 204 additions and 130 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
184
src/frontend/profilesettings.nim
Normal file
184
src/frontend/profilesettings.nim
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue