diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 5bdbcd0..14ca068 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -1,6 +1,8 @@ from flask import Flask +from config import Config app = Flask(__name__) +app.config.from_object(Config) from app import routes diff --git a/backend/app/forms.py b/backend/app/forms.py new file mode 100644 index 0000000..7462ea6 --- /dev/null +++ b/backend/app/forms.py @@ -0,0 +1,10 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, BooleanField, SubmitField +from wtforms.validators import DataRequired + +class LoginForm(FlaskForm): + username = StringField('Username', validators=[DataRequired()]) + password = PasswordField('Pasword', validators=[DataRequired()]) + remember_me = BooleanField('Remember Me') + submit = SubmitField('Sign In') + diff --git a/backend/app/routes.py b/backend/app/routes.py index 9a10401..5e2a53f 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -1,5 +1,6 @@ -from flask import render_template +from flask import render_template, flash, redirect, url_for from app import app +from app.forms import LoginForm @app.route('/') @app.route('/index') @@ -19,4 +20,12 @@ def index(): #return posts; return render_template('index.html', title='Home', user=user, posts=posts) +@app.route('/login', methods=['GET', 'POST']) +def login(): + form = LoginForm() + if form.validate_on_submit(): + flash('Login requested for user {}, remember_me={}'.format(form.username.data, form.remember_me.data)) + return redirect(url_for('index')) + return render_template('login.html', title='Sign In', form=form) + diff --git a/backend/app/static/simple.css b/backend/app/static/simple.css new file mode 100644 index 0000000..efe643b --- /dev/null +++ b/backend/app/static/simple.css @@ -0,0 +1,713 @@ +/* Global variables. */ +:root, +::backdrop { + /* Set sans-serif & mono fonts */ + --sans-font: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir, + "Nimbus Sans L", Roboto, "Noto Sans", "Segoe UI", Arial, Helvetica, + "Helvetica Neue", sans-serif; + --mono-font: Consolas, Menlo, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + --standard-border-radius: 5px; + + /* Default (light) theme */ + --bg: #fff; + --accent-bg: #f5f7ff; + --text: #212121; + --text-light: #585858; + --border: #898EA4; + --accent: #0d47a1; + --accent-hover: #1266e2; + --accent-text: var(--bg); + --code: #d81b60; + --preformatted: #444; + --marked: #ffdd33; + --disabled: #efefef; +} + +/* Dark theme */ +@media (prefers-color-scheme: dark) { + :root, + ::backdrop { + color-scheme: dark; + --bg: #212121; + --accent-bg: #2b2b2b; + --text: #dcdcdc; + --text-light: #ababab; + /* --accent: #ffb300; */ + --accent: #ace1af; + /* --accent-hover: #ffe099; */ + --accent-hover: #99dabe; + --accent-text: var(--bg); + --code: #f06292; + --preformatted: #ccc; + --disabled: #111; + } + /* Add a bit of transparency so light media isn't so glaring in dark mode */ + img, + video { + opacity: 0.8; + } +} + +/* Reset box-sizing */ +*, *::before, *::after { + box-sizing: border-box; +} + +/* Reset default appearance */ +textarea, +select, +input, +progress { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; +} + +html { + /* Set the font globally */ + font-family: var(--sans-font); + scroll-behavior: smooth; +} + +/* Make the body a nice central block */ +body { + color: var(--text); + background-color: var(--bg); + font-size: 1.15rem; + line-height: 1.5; + display: grid; + grid-template-columns: 1fr min(45rem, 90%) 1fr; + margin: 0; +} +body > * { + grid-column: 2; +} + +/* Make the header bg full width, but the content inline with body */ +body > header { + background-color: var(--accent-bg); + border-bottom: 1px solid var(--border); + text-align: center; + padding: 0 0.5rem 2rem 0.5rem; + grid-column: 1 / -1; +} + +body > header > *:only-child { + margin-block-start: 2rem; +} + +body > header h1 { + max-width: 1200px; + margin: 1rem auto; +} + +body > header p { + max-width: 40rem; + margin: 1rem auto; +} + +/* Add a little padding to ensure spacing is correct between content and header > nav */ +main { + padding-top: 1.5rem; +} + +body > footer { + margin-top: 4rem; + padding: 2rem 1rem 1.5rem 1rem; + color: var(--text-light); + font-size: 0.9rem; + text-align: center; + border-top: 1px solid var(--border); +} + +/* Format headers */ +h1 { + font-size: 3rem; +} + +h2 { + font-size: 2.6rem; + margin-top: 3rem; +} + +h3 { + font-size: 2rem; + margin-top: 3rem; +} + +h4 { + font-size: 1.44rem; +} + +h5 { + font-size: 1.15rem; +} + +h6 { + font-size: 0.96rem; +} + +p { + margin: 1.5rem 0; +} + +/* Prevent long strings from overflowing container */ +p, h1, h2, h3, h4, h5, h6 { + overflow-wrap: break-word; +} + +/* Fix line height when title wraps */ +h1, +h2, +h3 { + line-height: 1.1; +} + +/* Reduce header size on mobile */ +@media only screen and (max-width: 720px) { + h1 { + font-size: 2.5rem; + } + + h2 { + font-size: 2.1rem; + } + + h3 { + font-size: 1.75rem; + } + + h4 { + font-size: 1.25rem; + } +} + +/* Format links & buttons */ +a, +a:visited { + color: var(--accent); +} + +a:hover { + text-decoration: none; +} + +button, +.button, +a.button, /* extra specificity to override a */ +input[type="submit"], +input[type="reset"], +input[type="button"], +label[type="button"] { + border: 1px solid var(--accent); + background-color: var(--accent); + color: var(--accent-text); + padding: 0.5rem 0.9rem; + text-decoration: none; + line-height: normal; +} + +.button[aria-disabled="true"], +input:disabled, +textarea:disabled, +select:disabled, +button[disabled] { + cursor: not-allowed; + background-color: var(--disabled); + border-color: var(--disabled); + color: var(--text-light); +} + +input[type="range"] { + padding: 0; +} + +/* Set the cursor to '?' on an abbreviation and style the abbreviation to show that there is more information underneath */ +abbr[title] { + cursor: help; + text-decoration-line: underline; + text-decoration-style: dotted; +} + +button:enabled:hover, +.button:not([aria-disabled="true"]):hover, +input[type="submit"]:enabled:hover, +input[type="reset"]:enabled:hover, +input[type="button"]:enabled:hover, +label[type="button"]:hover { + background-color: var(--accent-hover); + border-color: var(--accent-hover); + cursor: pointer; +} + +.button:focus-visible, +button:focus-visible:where(:enabled), +input:enabled:focus-visible:where( + [type="submit"], + [type="reset"], + [type="button"] +) { + outline: 2px solid var(--accent); + outline-offset: 1px; +} + +/* Format navigation */ +header > nav { + font-size: 1rem; + line-height: 2; + padding: 1rem 0 0 0; +} + +/* Use flexbox to allow items to wrap, as needed */ +header > nav ul, +header > nav ol { + align-content: space-around; + align-items: center; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + list-style-type: none; + margin: 0; + padding: 0; +} + +/* List items are inline elements, make them behave more like blocks */ +header > nav ul li, +header > nav ol li { + display: inline-block; +} + +header > nav a, +header > nav a:visited { + margin: 0 0.5rem 1rem 0.5rem; + border: 1px solid var(--border); + border-radius: var(--standard-border-radius); + color: var(--text); + display: inline-block; + padding: 0.1rem 1rem; + text-decoration: none; +} + +header > nav a:hover, +header > nav a.current, +header > nav a[aria-current="page"] { + border-color: var(--accent); + color: var(--accent); + cursor: pointer; +} + +/* Reduce nav side on mobile */ +@media only screen and (max-width: 720px) { + header > nav a { + border: none; + padding: 0; + text-decoration: underline; + line-height: 1; + } +} + +/* Consolidate box styling */ +aside, details, pre, progress { + background-color: var(--accent-bg); + border: 1px solid var(--border); + border-radius: var(--standard-border-radius); + margin-bottom: 1rem; +} + +aside { + font-size: 1rem; + width: 30%; + padding: 0 15px; + margin-inline-start: 15px; + float: right; +} +*[dir="rtl"] aside { + float: left; +} + +/* Make aside full-width on mobile */ +@media only screen and (max-width: 720px) { + aside { + width: 100%; + float: none; + margin-inline-start: 0; + } +} + +article, fieldset, dialog { + border: 1px solid var(--border); + padding: 1rem; + border-radius: var(--standard-border-radius); + margin-bottom: 1rem; +} + +article h2:first-child, +section h2:first-child, +article h3:first-child, +section h3:first-child { + margin-top: 1rem; +} + +section { + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); + padding: 2rem 1rem; + margin: 3rem 0; +} + +/* Don't double separators when chaining sections */ +section + section, +section:first-child { + border-top: 0; + padding-top: 0; +} + +section + section { + margin-top: 0; +} + +section:last-child { + border-bottom: 0; + padding-bottom: 0; +} + +details { + padding: 0.7rem 1rem; +} + +summary { + cursor: pointer; + font-weight: bold; + padding: 0.7rem 1rem; + margin: -0.7rem -1rem; + word-break: break-all; +} + +details[open] > summary + * { + margin-top: 0; +} + +details[open] > summary { + margin-bottom: 0.5rem; +} + +details[open] > :last-child { + margin-bottom: 0; +} + +/* Format tables */ +table { + border-collapse: collapse; + margin: 1.5rem 0; +} + +figure > table { + width: max-content; + margin: 0; +} + +td, +th { + border: 1px solid var(--border); + text-align: start; + padding: 0.5rem; +} + +th { + background-color: var(--accent-bg); + font-weight: bold; +} + +tr:nth-child(even) { + /* Set every other cell slightly darker. Improves readability. */ + background-color: var(--accent-bg); +} + +table caption { + font-weight: bold; + margin-bottom: 0.5rem; +} + +/* Format forms */ +textarea, +select, +input, +button, +.button { + font-size: inherit; + font-family: inherit; + padding: 0.5rem; + margin-bottom: 0.5rem; + border-radius: var(--standard-border-radius); + box-shadow: none; + max-width: 100%; + display: inline-block; +} +textarea, +select, +input { + color: var(--text); + background-color: var(--bg); + border: 1px solid var(--border); +} +label { + display: block; +} +textarea:not([cols]) { + width: 100%; +} + +/* Add arrow to drop-down */ +select:not([multiple]) { + background-image: linear-gradient(45deg, transparent 49%, var(--text) 51%), + linear-gradient(135deg, var(--text) 51%, transparent 49%); + background-position: calc(100% - 15px), calc(100% - 10px); + background-size: 5px 5px, 5px 5px; + background-repeat: no-repeat; + padding-inline-end: 25px; +} +*[dir="rtl"] select:not([multiple]) { + background-position: 10px, 15px; +} + +/* checkbox and radio button style */ +input[type="checkbox"], +input[type="radio"] { + vertical-align: middle; + position: relative; + width: min-content; +} + +input[type="checkbox"] + label, +input[type="radio"] + label { + display: inline-block; +} + +input[type="radio"] { + border-radius: 100%; +} + +input[type="checkbox"]:checked, +input[type="radio"]:checked { + background-color: var(--accent); +} + +input[type="checkbox"]:checked::after { + /* Creates a rectangle with colored right and bottom borders which is rotated to look like a check mark */ + content: " "; + width: 0.18em; + height: 0.32em; + border-radius: 0; + position: absolute; + top: 0.05em; + left: 0.17em; + background-color: transparent; + border-right: solid var(--bg) 0.08em; + border-bottom: solid var(--bg) 0.08em; + font-size: 1.8em; + transform: rotate(45deg); +} +input[type="radio"]:checked::after { + /* creates a colored circle for the checked radio button */ + content: " "; + width: 0.25em; + height: 0.25em; + border-radius: 100%; + position: absolute; + top: 0.125em; + background-color: var(--bg); + left: 0.125em; + font-size: 32px; +} + +/* Makes input fields wider on smaller screens */ +@media only screen and (max-width: 720px) { + textarea, + select, + input { + width: 100%; + } +} + +/* Set a height for color input */ +input[type="color"] { + height: 2.5rem; + padding: 0.2rem; +} + +/* do not show border around file selector button */ +input[type="file"] { + border: 0; +} + +/* Misc body elements */ +hr { + border: none; + height: 1px; + background: var(--border); + margin: 1rem auto; +} + +mark { + padding: 2px 5px; + border-radius: var(--standard-border-radius); + background-color: var(--marked); + color: black; +} + +mark a { + color: #0d47a1; +} + +img, +video { + max-width: 100%; + height: auto; + border-radius: var(--standard-border-radius); +} + +figure { + margin: 0; + display: block; + overflow-x: auto; +} + +figure > img, +figure > picture > img { + display: block; + margin-inline: auto; +} + +figcaption { + text-align: center; + font-size: 0.9rem; + color: var(--text-light); + margin-block: 1rem; +} + +blockquote { + margin-inline-start: 2rem; + margin-inline-end: 0; + margin-block: 2rem; + padding: 0.4rem 0.8rem; + border-inline-start: 0.35rem solid var(--accent); + color: var(--text-light); + font-style: italic; +} + +cite { + font-size: 0.9rem; + color: var(--text-light); + font-style: normal; +} + +dt { + color: var(--text-light); +} + +/* Use mono font for code elements */ +code, +pre, +pre span, +kbd, +samp { + font-family: var(--mono-font); + color: var(--code); +} + +kbd { + color: var(--preformatted); + border: 1px solid var(--preformatted); + border-bottom: 3px solid var(--preformatted); + border-radius: var(--standard-border-radius); + padding: 0.1rem 0.4rem; +} + +pre { + padding: 1rem 1.4rem; + max-width: 100%; + overflow: auto; + color: var(--preformatted); +} + +/* Fix embedded code within pre */ +pre code { + color: var(--preformatted); + background: none; + margin: 0; + padding: 0; +} + +/* Progress bars */ +/* Declarations are repeated because you */ +/* cannot combine vendor-specific selectors */ +progress { + width: 100%; +} + +progress:indeterminate { + background-color: var(--accent-bg); +} + +progress::-webkit-progress-bar { + border-radius: var(--standard-border-radius); + background-color: var(--accent-bg); +} + +progress::-webkit-progress-value { + border-radius: var(--standard-border-radius); + background-color: var(--accent); +} + +progress::-moz-progress-bar { + border-radius: var(--standard-border-radius); + background-color: var(--accent); + transition-property: width; + transition-duration: 0.3s; +} + +progress:indeterminate::-moz-progress-bar { + background-color: var(--accent-bg); +} + +dialog { + max-width: 40rem; + margin: auto; +} + +dialog::backdrop { + background-color: var(--bg); + opacity: 0.8; +} + +@media only screen and (max-width: 720px) { + dialog { + max-width: 100%; + margin: auto 1em; + } +} + +/* Superscript & Subscript */ +/* Prevent scripts from affecting line-height. */ +sup, sub { + vertical-align: baseline; + position: relative; +} + +sup { + top: -0.4em; +} + +sub { + top: 0.3em; +} + +/* Classes for notices */ +.notice { + background: var(--accent-bg); + border: 2px solid var(--border); + border-radius: var(--standard-border-radius); + padding: 1.5rem; + margin: 2rem 0; +} diff --git a/backend/app/static/simple.min.css b/backend/app/static/simple.min.css new file mode 100644 index 0000000..9db8fe6 --- /dev/null +++ b/backend/app/static/simple.min.css @@ -0,0 +1 @@ +:root,::backdrop{--sans-font:-apple-system,BlinkMacSystemFont,"Avenir Next",Avenir,"Nimbus Sans L",Roboto,"Noto Sans","Segoe UI",Arial,Helvetica,"Helvetica Neue",sans-serif;--mono-font:Consolas,Menlo,Monaco,"Andale Mono","Ubuntu Mono",monospace;--standard-border-radius:5px;--bg:#fff;--accent-bg:#f5f7ff;--text:#212121;--text-light:#585858;--border:#898ea4;--accent:#0d47a1;--accent-hover:#1266e2;--accent-text:var(--bg);--code:#d81b60;--preformatted:#444;--marked:#fd3;--disabled:#efefef}@media (prefers-color-scheme:dark){:root,::backdrop{color-scheme:dark;--bg:#212121;--accent-bg:#2b2b2b;--text:#dcdcdc;--text-light:#ababab;--accent:#ffb300;--accent-hover:#ffe099;--accent-text:var(--bg);--code:#f06292;--preformatted:#ccc;--disabled:#111}img,video{opacity:.8}}*,:before,:after{box-sizing:border-box}textarea,select,input,progress{-webkit-appearance:none;-moz-appearance:none;appearance:none}html{font-family:var(--sans-font);scroll-behavior:smooth}body{color:var(--text);background-color:var(--bg);grid-template-columns:1fr min(45rem,90%) 1fr;margin:0;font-size:1.15rem;line-height:1.5;display:grid}body>*{grid-column:2}body>header{background-color:var(--accent-bg);border-bottom:1px solid var(--border);text-align:center;grid-column:1/-1;padding:0 .5rem 2rem}body>header>:only-child{margin-block-start:2rem}body>header h1{max-width:1200px;margin:1rem auto}body>header p{max-width:40rem;margin:1rem auto}main{padding-top:1.5rem}body>footer{color:var(--text-light);text-align:center;border-top:1px solid var(--border);margin-top:4rem;padding:2rem 1rem 1.5rem;font-size:.9rem}h1{font-size:3rem}h2{margin-top:3rem;font-size:2.6rem}h3{margin-top:3rem;font-size:2rem}h4{font-size:1.44rem}h5{font-size:1.15rem}h6{font-size:.96rem}p{margin:1.5rem 0}p,h1,h2,h3,h4,h5,h6{overflow-wrap:break-word}h1,h2,h3{line-height:1.1}@media only screen and (width<=720px){h1{font-size:2.5rem}h2{font-size:2.1rem}h3{font-size:1.75rem}h4{font-size:1.25rem}}a,a:visited{color:var(--accent)}a:hover{text-decoration:none}button,.button,a.button,input[type=submit],input[type=reset],input[type=button],label[type=button]{border:1px solid var(--accent);background-color:var(--accent);color:var(--accent-text);padding:.5rem .9rem;line-height:normal;text-decoration:none}.button[aria-disabled=true],input:disabled,textarea:disabled,select:disabled,button[disabled]{cursor:not-allowed;background-color:var(--disabled);border-color:var(--disabled);color:var(--text-light)}input[type=range]{padding:0}abbr[title]{cursor:help;text-decoration-line:underline;text-decoration-style:dotted}button:enabled:hover,.button:not([aria-disabled=true]):hover,input[type=submit]:enabled:hover,input[type=reset]:enabled:hover,input[type=button]:enabled:hover,label[type=button]:hover{background-color:var(--accent-hover);border-color:var(--accent-hover);cursor:pointer}.button:focus-visible,button:focus-visible:where(:enabled),input:enabled:focus-visible:where([type=submit],[type=reset],[type=button]){outline:2px solid var(--accent);outline-offset:1px}header>nav{padding:1rem 0 0;font-size:1rem;line-height:2}header>nav ul,header>nav ol{flex-flow:wrap;place-content:space-around center;align-items:center;margin:0;padding:0;list-style-type:none;display:flex}header>nav ul li,header>nav ol li{display:inline-block}header>nav a,header>nav a:visited{border:1px solid var(--border);border-radius:var(--standard-border-radius);color:var(--text);margin:0 .5rem 1rem;padding:.1rem 1rem;text-decoration:none;display:inline-block}header>nav a:hover,header>nav a.current,header>nav a[aria-current=page]{border-color:var(--accent);color:var(--accent);cursor:pointer}@media only screen and (width<=720px){header>nav a{border:none;padding:0;line-height:1;text-decoration:underline}}aside,details,pre,progress{background-color:var(--accent-bg);border:1px solid var(--border);border-radius:var(--standard-border-radius);margin-bottom:1rem}aside{float:right;width:30%;margin-inline-start:15px;padding:0 15px;font-size:1rem}[dir=rtl] aside{float:left}@media only screen and (width<=720px){aside{float:none;width:100%;margin-inline-start:0}}article,fieldset,dialog{border:1px solid var(--border);border-radius:var(--standard-border-radius);margin-bottom:1rem;padding:1rem}article h2:first-child,section h2:first-child,article h3:first-child,section h3:first-child{margin-top:1rem}section{border-top:1px solid var(--border);border-bottom:1px solid var(--border);margin:3rem 0;padding:2rem 1rem}section+section,section:first-child{border-top:0;padding-top:0}section+section{margin-top:0}section:last-child{border-bottom:0;padding-bottom:0}details{padding:.7rem 1rem}summary{cursor:pointer;word-break:break-all;margin:-.7rem -1rem;padding:.7rem 1rem;font-weight:700}details[open]>summary+*{margin-top:0}details[open]>summary{margin-bottom:.5rem}details[open]>:last-child{margin-bottom:0}table{border-collapse:collapse;margin:1.5rem 0}figure>table{width:max-content;margin:0}td,th{border:1px solid var(--border);text-align:start;padding:.5rem}th{background-color:var(--accent-bg);font-weight:700}tr:nth-child(2n){background-color:var(--accent-bg)}table caption{margin-bottom:.5rem;font-weight:700}textarea,select,input,button,.button{font-size:inherit;border-radius:var(--standard-border-radius);box-shadow:none;max-width:100%;margin-bottom:.5rem;padding:.5rem;font-family:inherit;display:inline-block}textarea,select,input{color:var(--text);background-color:var(--bg);border:1px solid var(--border)}label{display:block}textarea:not([cols]){width:100%}select:not([multiple]){background-image:linear-gradient(45deg,transparent 49%,var(--text)51%),linear-gradient(135deg,var(--text)51%,transparent 49%);background-position:calc(100% - 15px),calc(100% - 10px);background-repeat:no-repeat;background-size:5px 5px,5px 5px;padding-inline-end:25px}[dir=rtl] select:not([multiple]){background-position:10px,15px}input[type=checkbox],input[type=radio]{vertical-align:middle;width:min-content;position:relative}input[type=checkbox]+label,input[type=radio]+label{display:inline-block}input[type=radio]{border-radius:100%}input[type=checkbox]:checked,input[type=radio]:checked{background-color:var(--accent)}input[type=checkbox]:checked:after{content:" ";border-right:solid var(--bg).08em;border-bottom:solid var(--bg).08em;background-color:#0000;border-radius:0;width:.18em;height:.32em;font-size:1.8em;position:absolute;top:.05em;left:.17em;transform:rotate(45deg)}input[type=radio]:checked:after{content:" ";background-color:var(--bg);border-radius:100%;width:.25em;height:.25em;font-size:32px;position:absolute;top:.125em;left:.125em}@media only screen and (width<=720px){textarea,select,input{width:100%}}input[type=color]{height:2.5rem;padding:.2rem}input[type=file]{border:0}hr{background:var(--border);border:none;height:1px;margin:1rem auto}mark{border-radius:var(--standard-border-radius);background-color:var(--marked);color:#000;padding:2px 5px}mark a{color:#0d47a1}img,video{border-radius:var(--standard-border-radius);max-width:100%;height:auto}figure{margin:0;display:block;overflow-x:auto}figure>img,figure>picture>img{margin-inline:auto;display:block}figcaption{text-align:center;color:var(--text-light);margin-block:1rem;font-size:.9rem}blockquote{border-inline-start:.35rem solid var(--accent);color:var(--text-light);margin-block:2rem;margin-inline:2rem 0;padding:.4rem .8rem;font-style:italic}cite{color:var(--text-light);font-size:.9rem;font-style:normal}dt{color:var(--text-light)}code,pre,pre span,kbd,samp{font-family:var(--mono-font);color:var(--code)}kbd{color:var(--preformatted);border:1px solid var(--preformatted);border-bottom:3px solid var(--preformatted);border-radius:var(--standard-border-radius);padding:.1rem .4rem}pre{color:var(--preformatted);max-width:100%;padding:1rem 1.4rem;overflow:auto}pre code{color:var(--preformatted);background:0 0;margin:0;padding:0}progress{width:100%}progress:indeterminate{background-color:var(--accent-bg)}progress::-webkit-progress-bar{border-radius:var(--standard-border-radius);background-color:var(--accent-bg)}progress::-webkit-progress-value{border-radius:var(--standard-border-radius);background-color:var(--accent)}progress::-moz-progress-bar{border-radius:var(--standard-border-radius);background-color:var(--accent);transition-property:width;transition-duration:.3s}progress:indeterminate::-moz-progress-bar{background-color:var(--accent-bg)}dialog{max-width:40rem;margin:auto}dialog::backdrop{background-color:var(--bg);opacity:.8}@media only screen and (width<=720px){dialog{max-width:100%;margin:auto 1em}}sup,sub{vertical-align:baseline;position:relative}sup{top:-.4em}sub{top:.3em}.notice{background:var(--accent-bg);border:2px solid var(--border);border-radius:var(--standard-border-radius);margin:2rem 0;padding:1.5rem} \ No newline at end of file diff --git a/backend/app/templates/base.html b/backend/app/templates/base.html index 920f44b..8ecf3bb 100644 --- a/backend/app/templates/base.html +++ b/backend/app/templates/base.html @@ -1,6 +1,7 @@ + {% if title %} {{ title }} - blog {% else %} @@ -8,8 +9,24 @@ {% endif %} -
blgo: home
+
+ blgo: + home + login + +

+ {% with messages = get_flashed_messages() %} + {% if messages %} + + {% endif %} + {% endwith %} + + {% block content %}{% endblock %} diff --git a/backend/app/templates/login.html b/backend/app/templates/login.html new file mode 100644 index 0000000..e159759 --- /dev/null +++ b/backend/app/templates/login.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block content %} +

Sign In

+
+ {{ form.hidden_tag() }} +

+ {{ form.username.label }}
+ {{ form.username(size=32) }}
+ {% for error in form.username.errors %} + [{{ error }}] + {% endfor %} +

+

+ {{ form.password.label }}
+ {{ form.password(size=32) }}
+ {% for error in form.password.errors %} + [{{ error }}] + {% endfor %} +

+

{{ form.remember_me() }} {{ form.remember_me.label }}

+

{{ form.submit }}

+
+ +{% endblock %} diff --git a/backend/config.py b/backend/config.py new file mode 100644 index 0000000..6e8209e --- /dev/null +++ b/backend/config.py @@ -0,0 +1,7 @@ +import os + +# Remove or fallbacks for prod + +class Config: + SECRET_KEY = os.environ.get('FLASK_SECRET_KEY') or 'flasksk' + diff --git a/backend/requirements.txt b/backend/requirements.txt index c1e99d5..2517dd8 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,8 +1,10 @@ blinker==1.8.2 click==8.1.7 Flask==3.0.3 +Flask-WTF==1.2.1 itsdangerous==2.2.0 Jinja2==3.1.4 MarkupSafe==2.1.5 python-dotenv==1.0.1 Werkzeug==3.0.3 +WTForms==3.1.2 diff --git a/backend/templates/about.html b/backend/templates/about.html deleted file mode 100644 index 7318c07..0000000 --- a/backend/templates/about.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - -

About

- - - diff --git a/backend/templates/basictemp.html b/backend/templates/basictemp.html deleted file mode 100644 index cbbcf44..0000000 --- a/backend/templates/basictemp.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/backend/templates/home.html b/backend/templates/home.html deleted file mode 100644 index fa70d65..0000000 --- a/backend/templates/home.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - -

Home

- - -