Compare commits

...

2 Commits

20 changed files with 328 additions and 139 deletions

View File

@ -15,3 +15,6 @@ this is all off the top of my head so if you try to run it got help you
- luaossl
i think thats it
# icons
the icons in the `icons/` folder are by [Gabriele Malaspina](https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license)

View File

@ -11,14 +11,21 @@ local util = require("util")
app:enable("etlua")
app.layout = require "views.base"
local function inject_constants(req)
req.constants = constants
end
local function inject_methods(req)
req.avatar_url = util.get_user_avatar_url
req.ntob = function(_, v)
return util.ntob(v)
end
req.PermissionLevelString = constants.PermissionLevelString
util.pop_infobox(req)
end
app:before_filter(inject_constants)
app:before_filter(inject_methods)
app:include("apps.users", {path = "/user"})

View File

@ -14,7 +14,7 @@ local POSTS_PER_PAGE = 10
app:get("thread_create", "/create", function(self)
local user = util.get_logged_in_user(self)
if not user then
self.session.flash = {error = "You must be logged in to perform this action."}
util.inject_err_infobox(self, "You must be logged in to perform this action.")
return {redirect_to = self:url_for("user_login")}
end
local all_topics = db.query("select * from topics limit 25;")
@ -30,7 +30,7 @@ end)
app:post("thread_create", "/create", function(self)
local user = util.get_logged_in_user(self)
if not user then
self.session.flash = {error = "You must be logged in to perform this action."}
util.inject_err_infobox(self, "You must be logged in to perform this action.")
return {redirect_to = self:url_for("user_login")}
end
local topic = Topics:find(self.params.topic_id)

View File

@ -66,11 +66,6 @@ app:get("user", "/:username", function(self)
return {status = 404}
end
if self.session.flash ~= nil and self.session.flash.just_logged_in then
self.just_logged_in = true
self.session.flash = {}
end
local me = util.get_logged_in_user_or_transient(self)
self.user = user
self.me = me
@ -106,47 +101,39 @@ app:get("user", "/:username", function(self)
end)
app:post("user_delete", "/:username/delete", function(self)
-- this route explicitly does not handle admins deleting other users
-- i might make a separate route for it later, but guesting users is possible
local me = util.get_logged_in_user(self)
if me == nil then
self.session.flash = {error = "You must be logged in to perform this action."}
util.inject_err_infobox(self, "You must be logged in to perform this action.")
return {redirect_to = self:url_for("user_login")}
end
local target_user = Users:find({username = self.params.username})
if not me:is_mod() then
if me.id ~= target_user.id then
return {redirect_to = self:url_for("user", {username = self.params.username})}
end
if not authenticate_user(target_user, self.params.password) then
self.session.flash = {error = "The password you entered is incorrect."}
return {redirect_to = self:url_for("user_delete_confirm", {username = me.username})}
end
util.transfer_and_delete_user(target_user)
self.session.flash = {error = "Your account has been added to the deletion queue."}
return {redirect_to = self:url_for("user_signup")}
else
if target_user.permission >= me.permission then
self.session.flash = {error = "You can not delete another moderator."}
return {redirect_to = self:url_for("user", {username = me.username})}
end
if me.id ~= target_user.id then
return {redirect_to = self:url_for("user", {username = self.params.username})}
end
if not authenticate_user(target_user, self.params.password) then
util.inject_err_infobox(self, "The password you entered is incorrect.")
return {redirect_to = self:url_for("user_delete_confirm", {username = me.username})}
end
util.transfer_and_delete_user(target_user)
util.inject_infobox(self, "Your account has been added to the deletion queue.")
return {redirect_to = self:url_for("user_signup")}
end)
app:get("user_delete_confirm", "/:username/delete_confirm", function(self)
local me = util.get_logged_in_user(self)
if me == nil then
self.session.flash = {error = "You must be logged in to perform this action."}
-- util.inject_err_infobox(self, "You must be logged in to perform this action.")
return {redirect_to = self:url_for("user_login")}
end
local target_user = Users:find({username = self.params.username})
if me.id ~= target_user.id then
return {redirect_to = self:url_for("user", {username = self.params.username})}
end
if self.session.flash then
self.err = self.session.flash.error
self.session.flash = {}
end
self.me = target_user
self.page_title = "confirm deletion"
@ -156,7 +143,7 @@ end)
app:post("user_clear_avatar", "/:username/clear_avatar", function(self)
local me = util.get_logged_in_user(self)
if me == nil then
self.session.flash = {error = "You must be logged in to perform this action."}
util.inject_err_infobox(self, "You must be logged in to perform this action.")
return {redirect_to = self:url_for("user_login")}
end
local target_user = Users:find({username = self.params.username})
@ -166,14 +153,14 @@ app:post("user_clear_avatar", "/:username/clear_avatar", function(self)
target_user:update({
avatar_id = db.NULL,
})
self.session.flash = {success = true, msg = "Avatar cleared."}
util.inject_infobox(self, "Avatar cleared.")
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
end)
app:post("user_set_avatar", "/:username/set_avatar", function(self)
local me = util.get_logged_in_user(self)
if me == nil then
self.session.flash = {error = "You must be logged in to perform this action."}
util.inject_err_infobox(self, "You must be logged in to perform this action.")
return {redirect_to = self:url_for("user_login")}
end
local target_user = Users:find({username = self.params.username})
@ -182,7 +169,7 @@ app:post("user_set_avatar", "/:username/set_avatar", function(self)
end
local file = self.params.avatar
if not file then
self.session.flash = {error = "Something went wrong. Try again later."}
util.inject_warn_infobox(self, "Something went wrong. Try again later.")
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
end
local time = os.time()
@ -191,11 +178,11 @@ app:post("user_set_avatar", "/:username/set_avatar", function(self)
local save_path = "static" .. proxied_filename
local res = util.validate_and_create_image(file.content, save_path)
if not res then
self.session.flash = {error = "Something went wrong. Try again later."}
util.inject_warn_infobox(self, "Something went wrong. Try again later.")
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
end
self.session.flash = {success = true, msg = "Avatar updated."}
util.inject_infobox(self, "Avatar updated.")
local avatar = Avatars:create({
file_path = proxied_filename,
uploaded_at = time,
@ -211,22 +198,13 @@ end)
app:get("user_settings", "/:username/settings", function(self)
local me = util.get_logged_in_user(self)
if me == nil then
self.session.flash = {error = "You must be logged in to perform this action."}
util.inject_err_infobox(self, "You must be logged in to perform this action.")
return {redirect_to = self:url_for("user_login")}
end
local target_user = Users:find({username = self.params.username})
if me.id ~= target_user.id then
return {redirect_to = self:url_for("user", {username = self.params.username})}
end
if self.session.flash then
local flash = self.session.flash
self.session.flash = nil
if flash.success then
self.flash_msg = flash.msg
elseif flash.error then
self.flash_msg = flash.error
end
end
self.me = target_user
self.page_title = "settings"
@ -236,7 +214,7 @@ end)
app:post("user_settings", "/:username/settings", function(self)
local me = util.get_logged_in_user(self)
if me == nil then
self.session.flash = {error = "You must be logged in to perform this action."}
util.inject_err_infobox(self, "You must be logged in to perform this action.")
return {redirect_to = self:url_for("user_login")}
end
local target_user = Users:find({username = self.params.username})
@ -249,10 +227,7 @@ app:post("user_settings", "/:username/settings", function(self)
target_user:update({
status = status,
})
self.session.flash = {
success = true,
msg = "Settings updated."
}
util.inject_infobox(self, "Status updated.")
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
end)
@ -264,11 +239,6 @@ app:get("user_login", "/login", function(self)
end
end
if self.session.flash then
self.err = self.session.flash.error
self.session.flash = {}
end
self.page_title = "log in"
return {render = "user.login"}
@ -285,19 +255,19 @@ app:post("user_login", "/login", function(self)
local password = self.params.password
local user = Users:find({username = username})
if not user then
self.session.flash = {error = "Invalid username or password"}
util.inject_err_infobox(self, "Invalid username or password")
return {redirect_to = self:url_for("user_login")}
end
if user.permission == constants.PermissionLevel.SYSTEM then
self.session.flash = {error = "Invalid username or password"}
util.inject_err_infobox(self, "Invalid username or password")
return {redirect_to = self:url_for("user_login")}
end
if not authenticate_user(user, password) then
self.session.flash = {error = "Invalid username or password"}
util.inject_err_infobox(self, "Invalid username or password")
return {redirect_to = self:url_for("user_login")}
end
local session = create_session(user.id)
self.session.flash = {just_logged_in = true}
util.inject_infobox(self, "Logged in successfully.")
self.session.session_key = session.key
return {redirect_to = self:url_for("user", {username = username})}
end)
@ -309,10 +279,6 @@ app:get("user_signup", "/signup", function(self)
return {redirect_to = self:url_for("user", {username = user.username})}
end
end
if self.session.flash then
self.err = self.session.flash.error
self.session.flash = {}
end
self.page_title = "sign up"
@ -332,22 +298,22 @@ app:post("user_signup", "/signup", function(self)
local password2 = self.params.password2
local user = Users:find({username = username})
if user then
self.session.flash = {error = "Username '" .. username .. "' is already taken."}
util.inject_err_infobox(self, "Username '" .. username .. "' is already taken.")
return {redirect_to = self:url_for("user_signup")}
end
if not validate_username(username) then
self.session.flash = {error = "Username must be 3-20 characters with only upper and lowercase letters, hyphens, and underscores."}
util.inject_err_infobox(self, "Username must be 3-20 characters with only upper and lowercase letters, hyphens, and underscores.")
return {redirect_to = self:url_for("user_signup")}
end
if not validate_password(password) then
self.session.flash = {error = "Password must be 10+ chars with: 1 uppercase, 1 lowercase, 1 number, 1 special char, and no spaces."}
util.inject_err_infobox(self, "Password must be 10+ chars with: 1 uppercase, 1 lowercase, 1 number, 1 special char, and no spaces.")
return {redirect_to = self:url_for("user_signup")}
end
if password ~= password2 then
self.session.flash = {error = "Passwords do not match."}
util.inject_err_infobox(self, "Passwords do not match.")
return {redirect_to = self:url_for("user_signup")}
end
@ -358,7 +324,7 @@ app:post("user_signup", "/signup", function(self)
})
local session = create_session(new_user.id)
self.session.flash = {just_logged_in = true}
util.inject_infobox(self, "Siged up successfully.")
self.session.session_key = session.key
return {redirect_to = self:url_for("user", {username = username})}
end)

View File

@ -16,6 +16,26 @@ Constants.PermissionLevelString = {
[Constants.PermissionLevel.ADMIN] = "Administrator",
}
Constants.InfoboxKind = {
INFO = 0,
LOCK = 1,
WARN = 2,
ERROR = 3,
}
Constants.InfoboxIcons = {
[Constants.InfoboxKind.INFO] = "svg-icons.info",
[Constants.InfoboxKind.LOCK] = "svg-icons.lock",
[Constants.InfoboxKind.WARN] = "svg-icons.warn",
[Constants.InfoboxKind.ERROR] = "svg-icons.error",
}
Constants.InfoboxHTMLClass = {
[Constants.InfoboxKind.INFO] = "",
[Constants.InfoboxKind.LOCK] = "warn",
[Constants.InfoboxKind.WARN] = "warn",
[Constants.InfoboxKind.ERROR] = "critical",
}
Constants.BCRYPT_ROUNDS = 10
return Constants

View File

@ -8,6 +8,7 @@ $dark_bg: color.scale($accent_color, $lightness: -25%, $saturation: -97%);
$dark2: color.scale($accent_color, $lightness: -30%, $saturation: -60%);
$light: color.scale($accent_color, $lightness: 40%, $saturation: -60%);
$lighter: color.scale($accent_color, $lightness: 60%, $saturation: -60%);
$main_bg: color.scale($accent_color, $lightness: -10%, $saturation: -40%);
$button_color: color.adjust($accent_color, $hue: 90);
@ -66,6 +67,7 @@ body {
.darkbg {
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
background-color: $dark_bg;
}
@ -75,8 +77,11 @@ body {
}
.site-title {
display: inline;
padding-right: 30px;
font-size: 1.5rem;
font-weight: bold;
text-decoration: none;
color: black;
}
.thread-title {
@ -197,6 +202,7 @@ button, input[type="submit"], .linkbutton {
// not sure why this one has to be separate, but if it's included in the rule above everything breaks
input[type="file"]::file-selector-button {
@include button($button_color);
margin: 10px 10px;
}
.pagebutton {
@ -221,3 +227,53 @@ input[type="file"]::file-selector-button {
.modform {
display: inline;
}
.login-container > * {
width: 25%;
margin: auto;
}
.settings-container > * {
width: 40%;
margin: auto;
}
.avatar-form {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
}
input[type="text"], input[type="password"] {
border: 1px solid black;
border-radius: 3px;
padding: 7px 10px;
width: 100%;
box-sizing: border-box;
background-color: $lighter;
}
.infobox {
border: 2px solid black;
background-color: $accent_color;
padding: 20px 15px;
&.critical {
background-color: rgb(237, 129, 129);
}
&.warn {
background-color: #fbfb8d;
}
}
.infobox > span {
display: flex;
align-items: center;
}
.infobox-icon-container {
min-width: 60px;
padding-right: 15px;
}

View File

@ -39,6 +39,7 @@ body {
.darkbg {
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
background-color: rgb(143.7039271654, 144.3879625984, 142.8620374016);
}
@ -48,8 +49,11 @@ body {
}
.site-title {
display: inline;
padding-right: 30px;
font-size: 1.5rem;
font-weight: bold;
text-decoration: none;
color: black;
}
.thread-title {
@ -179,6 +183,7 @@ button.warn:active, input[type=submit].warn:active, .linkbutton.warn:active {
input[type=file]::file-selector-button {
background-color: rgb(177, 206, 204.5);
margin: 10px 10px;
}
input[type=file]::file-selector-button:hover {
background-color: rgb(192.6, 215.8, 214.6);
@ -214,3 +219,51 @@ input[type=file]::file-selector-button:active {
.modform {
display: inline;
}
.login-container > * {
width: 25%;
margin: auto;
}
.settings-container > * {
width: 40%;
margin: auto;
}
.avatar-form {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
}
input[type=text], input[type=password] {
border: 1px solid black;
border-radius: 3px;
padding: 7px 10px;
width: 100%;
box-sizing: border-box;
background-color: rgb(229.84, 231.92, 227.28);
}
.infobox {
border: 2px solid black;
background-color: #c1ceb1;
padding: 20px 15px;
}
.infobox.critical {
background-color: rgb(237, 129, 129);
}
.infobox.warn {
background-color: #fbfb8d;
}
.infobox > span {
display: flex;
align-items: center;
}
.infobox-icon-container {
min-width: 60px;
padding-right: 15px;
}

5
svg-icons/error.etlua Normal file
View File

@ -0,0 +1,5 @@
<!-- https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license -->
<?xml version="1.0" encoding="utf-8"?>
<svg width="60px" height="60px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.364 5.63604C19.9926 7.26472 21 9.51472 21 12C21 16.9706 16.9706 21 12 21C9.51472 21 7.26472 19.9926 5.63604 18.364M18.364 5.63604C16.7353 4.00736 14.4853 3 12 3C7.02944 3 3 7.02944 3 12C3 14.4853 4.00736 16.7353 5.63604 18.364M18.364 5.63604L5.63604 18.364" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

5
svg-icons/info.etlua Normal file
View File

@ -0,0 +1,5 @@
<!-- https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license -->
<?xml version="1.0" encoding="utf-8"?>
<svg width="60px" height="60px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 8V8.5M12 12V16M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

5
svg-icons/lock.etlua Normal file
View File

@ -0,0 +1,5 @@
<!-- https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license -->
<?xml version="1.0" encoding="utf-8"?>
<svg width="60px" height="60px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 14V16M8 9V6C8 3.79086 9.79086 2 12 2C14.2091 2 16 3.79086 16 6V9M7 21H17C18.1046 21 19 20.1046 19 19V11C19 9.89543 18.1046 9 17 9H7C5.89543 9 5 9.89543 5 11V19C5 20.1046 5.89543 21 7 21Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

5
svg-icons/warn.etlua Normal file
View File

@ -0,0 +1,5 @@
<!-- https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license -->
<?xml version="1.0" encoding="utf-8"?>
<svg width="60px" height="60px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 15H12.01M12 12V9M4.98207 19H19.0179C20.5615 19 21.5233 17.3256 20.7455 15.9923L13.7276 3.96153C12.9558 2.63852 11.0442 2.63852 10.2724 3.96153L3.25452 15.9923C2.47675 17.3256 3.43849 19 4.98207 19Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

View File

@ -2,6 +2,7 @@ local util = {}
local magick = require("magick")
local db = require("lapis.db")
local html_escape = require("lapis.html").escape
local constants = require("constants")
local Avatars = require("models").Avatars
local Users = require("models").Users
@ -147,4 +148,37 @@ function util.transfer_and_delete_user(user)
db.query("COMMIT")
end
function util.pop_infobox(req)
print("1")
if not req.session.infobox then return end
print("2")
req.infobox = req.session.infobox
req.session.infobox = nil
end
function util.inject_infobox(req, message, kind)
kind = kind or constants.InfoboxKind.INFO
local ib = {
msg = message,
kind = kind,
}
req.session.infobox = ib
end
function util.inject_err_infobox(req, message)
local ib = {
msg = message,
kind = constants.InfoboxKind.ERROR,
}
req.session.infobox = ib
end
function util.inject_warn_infobox(req, message)
local ib = {
msg = message,
kind = constants.InfoboxKind.WARN,
}
req.session.infobox = ib
end
return util

View File

@ -0,0 +1,13 @@
<%
local class = "infobox " .. constants.InfoboxHTMLClass[kind]
local icon = constants.InfoboxIcons[kind]
%>
<div class="<%= class %>">
<span>
<div class="infobox-icon-container">
<% render(icon) %>
</div>
<%= msg %>
</span>
</div>

View File

@ -1,10 +1,11 @@
<nav id="topnav">
<span>
<h1 class="site-title">Porom</h1>
<a href="<%= url_for("all_topics") %>">All topics</a>
<% local topics_url = url_for("all_topics") %>
<a class="site-title" href="<%= topics_url %>">Porom</a>
<a href="<%= topics_url %>">All topics</a>
</span>
<span>
<% if me:is_logged_in() then -%>
<% if me and me:is_logged_in() then -%>
Welcome, <a href="<%= url_for("user", {username = me.username}) %>"><%= me.username %></a>
<% else -%>
Welcome, guest. Please <a href="<%= url_for("user_signup") %>">sign up</a> or <a href="<%= url_for("user_login") %>">log in</a>

View File

@ -1,4 +1,5 @@
<% render("views.common.topnav") -%>
<% local is_locked = ntob(thread.is_locked) %>
<main>
<nav class="darkbg">
<h1 class="thread-title"><%= thread.title %></h1>
@ -13,10 +14,13 @@
<% render("views.common.pagination", {page_count = pages, current_page = page}) %>
</nav>
<% if not me:is_guest() then %>
<% if is_locked then -%>
<% render("views.common.infobox", {kind = constants.InfoboxKind.LOCK, msg = "This thread is locked."}) %>
<% end -%>
<% if not me:is_guest() and not is_locked then %>
<h1>Respond to "<%= thread.title %>"</h1>
<form method="post">
<textarea id="post_content" name="post_content" placeholder="Response body" required></textarea><br>
<input type="submit" value="Reply">
<input type="submit" value="Post reply">
</form>
<% end %>

View File

@ -1,12 +1,15 @@
<h1>Are you sure you want to delete your account, <%= me.username %>?</h1>
<p>This cannot be undone. This will not delete your posts, only anonymize them.</p>
<p>If you are sure, please type your password below.</p>
<% render("views.common.topnav") -%>
<div class="darkbg settings-container">
<h1>Are you sure you want to delete your account, <%= me.username %>?</h1>
<p>This cannot be undone. This will not delete your posts, only anonymize them.</p>
<p>If you are sure, please type your password below.</p>
<% if err then %>
<h2><%= err %></h2>
<% end %>
<% if infobox then %>
<% render("views.common.infobox", infobox) %>
<% end %>
<form method="post" action="<%= url_for("user_delete", {username = me.username}) %>">
<input type="password" name="password" id="password" autocomplete="current-password" placeholder="Password" required><br>
<input class="critical" type="submit" value="Delete my account (NO UNDO)">
</form>
<form method="post" action="<%= url_for("user_delete", {username = me.username}) %>">
<input type="password" name="password" id="password" autocomplete="current-password" placeholder="Password" required><br>
<input class="critical" type="submit" value="Delete my account (NO UNDO)">
</form>
</div>

View File

@ -1,12 +1,14 @@
<h1>Log In</h1>
<% if err then %>
<h2><%= err %></h2>
<% end %>
<form method="post" action="<%= url_for('user_login') %>" enctype="multipart/form-data">
<label for="username">Username</label><br>
<input type="text" id="username" name="username" required autocomplete="username"><br>
<label for="password">Password</label><br>
<input type="password" id="password" name="password" required autocomplete="current-password"><br>
<input type="submit" value="Log in">
</form>
<% render("views.common.topnav") -%>
<div class="darkbg login-container">
<h1>Log In</h1>
<% if infobox then %>
<% render("views.common.infobox", infobox) %>
<% end %>
<form method="post" action="<%= url_for('user_login') %>" enctype="multipart/form-data">
<label for="username">Username</label><br>
<input type="text" id="username" name="username" required autocomplete="username"><br>
<label for="password">Password</label><br>
<input type="password" id="password" name="password" required autocomplete="current-password"><br>
<input type="submit" value="Log in">
</form>
</div>

View File

@ -1,20 +1,25 @@
<h1>User settings</h1>
<% if flash_msg then %>
<h2><%= flash_msg %></h2>
<% end %>
<form method="post" action="<%= url_for("user_set_avatar", {username = me.username}) %>" enctype="multipart/form-data">
<img src="<%= avatar_url(me) %>"><br>
<input id="file" type="file" name="avatar" accept="image/*">
<input type="submit" value="Update avatar">
<% if not me:is_default_avatar() then %>
<input type="submit" value="Clear avatar" formaction="<%= url_for("user_clear_avatar", {username = me.username}) %>">
<% end %>
<br>
</form>
<form method="post" action="">
<label for="status">Status</label>
<input type="text" id="status" name="status" value="<%= me.status %>" maxlength="30"><br>
<input type="submit" value="Save status">
</form>
<br>
<a class="linkbutton critical" href="<%= url_for("user_delete_confirm", {username = me.username}) %>">Delete account</a>
<% render("views.common.topnav") -%>
<div class="darkbg settings-container">
<h1>User settings</h1>
<% if infobox then %>
<% render("views.common.infobox", infobox) %>
<% end %>
<form class="avatar-form" method="post" action="<%= url_for("user_set_avatar", {username = me.username}) %>" enctype="multipart/form-data">
<img src="<%= avatar_url(me) %>">
<input id="file" type="file" name="avatar" accept="image/*" required>
<div>
<input type="submit" value="Update avatar">
<% if not me:is_default_avatar() then %>
<input type="submit" value="Clear avatar" formaction="<%= url_for("user_clear_avatar", {username = me.username}) %>" formnovalidate>
<% end %>
</div>
</form>
<form method="post" action="">
<label for="status">Status</label>
<input type="text" id="status" name="status" value="<%= me.status %>" maxlength="30">
<input type="submit" value="Save status">
</form>
<div>
<a class="linkbutton critical" href="<%= url_for("user_delete_confirm", {username = me.username}) %>">Delete account</a>
</div>
</div>

View File

@ -1,15 +1,17 @@
<h1>Sign up</h1>
<% if err then %>
<h2><%= err %></h2>
<% end %>
<form method="post" action="<%= url_for('user_signup') %>" enctype="multipart/form-data">
<label for="username">Username</label><br>
<input type="text" id="username" name="username" pattern="[\w\-]{3,20}" title="3-20 characters. Only upper and lowercase letters, hyphens, and underscores" required autocomplete="username"><br>
<label for="password">Password</label><br>
<input type="password" id="password" name="password" pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])(?!.*\s).{10,}" title="10+ chars with: 1 uppercase, 1 lowercase, 1 number, 1 special char, and no spaces" required autocomplete="new-password"><br>
<label for="password2">Confirm Password</label><br>
<input type="password" id="password2" name="password2" pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])(?!.*\s).{10,}" title="10+ chars with: 1 uppercase, 1 lowercase, 1 number, 1 special char, and no spaces" required autocomplete="new-password"><br>
<input type="submit" value="Sign up">
</form>
<p>After you sign up, a moderator will need to confirm your account before you will be allowed to post.</p>
<% render("views.common.topnav") -%>
<div class="darkbg login-container">
<h1>Sign up</h1>
<% if infobox then %>
<% render("views.common.infobox", infobox) %>
<% end %>
<form method="post" action="<%= url_for('user_signup') %>" enctype="multipart/form-data">
<label for="username">Username</label><br>
<input type="text" id="username" name="username" pattern="[\w\-]{3,20}" title="3-20 characters. Only upper and lowercase letters, hyphens, and underscores" required autocomplete="username"><br>
<label for="password">Password</label><br>
<input type="password" id="password" name="password" pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])(?!.*\s).{10,}" title="10+ chars with: 1 uppercase, 1 lowercase, 1 number, 1 special char, and no spaces" required autocomplete="new-password"><br>
<label for="password2">Confirm Password</label><br>
<input type="password" id="password2" name="password2" pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])(?!.*\s).{10,}" title="10+ chars with: 1 uppercase, 1 lowercase, 1 number, 1 special char, and no spaces" required autocomplete="new-password"><br>
<input type="submit" value="Sign up">
</form>
<p>After you sign up, a moderator will need to confirm your account before you will be allowed to post.</p>
</div>

View File

@ -1,7 +1,7 @@
<% if just_logged_in then %>
<h1>Logged in successfully.</h1>
<% end %>
<% render("views.common.topnav") -%>
<% if infobox then %>
<% render("views.common.infobox", pop_infobox) %>
<% end %>
<div class="darkbg">
<h1 class="thread-title">Latest posts by <i><%= user.username %></i></h1>
<div>