Create project and documents #1

Merged
nicole merged 9 commits from projects-and-documents into main 2024-05-21 12:59:05 +00:00
11 changed files with 182 additions and 41 deletions
Showing only changes of commit a1420f590e - Show all commits

View File

@ -1,5 +1,6 @@
pub mod home; pub mod home;
pub mod login; pub mod login;
pub mod projects;
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::response::Response; use axum::response::Response;

View File

@ -10,12 +10,14 @@ pub async fn home_page(State(ctx): State<Context>, auth_session: AuthSession<Con
id: 1, id: 1,
owner_id: 1, owner_id: 1,
name: "Blog posts".to_owned(), name: "Blog posts".to_owned(),
description: "Planning and publication schedule for my blog".to_owned(),
key: "BLOG".to_owned(), key: "BLOG".to_owned(),
}, },
Project { Project {
id: 2, id: 2,
owner_id: 1, owner_id: 1,
name: "Bugs (Pique)".to_owned(), name: "Bugs (Pique)".to_owned(),
description: "The bugs we've found so far in Pique".to_owned(),
key: "BUG".to_owned(), key: "BUG".to_owned(),
}, },
]; ];

50
src/handler/projects.rs Normal file
View File

@ -0,0 +1,50 @@
use axum::response::Redirect;
use axum_login::AuthSession;
use crate::{models::Project, prelude::*};
pub async fn projects_page(
State(ctx): State<Context>,
auth_session: AuthSession<Context>,
) -> Response {
if let Some(user) = auth_session.user {
let projects: Vec<Project> = vec![
Project {
id: 1,
owner_id: 1,
name: "Blog posts".to_owned(),
description: "Planning and publication schedule for my blog".to_owned(),
key: "BLOG".to_owned(),
},
Project {
id: 2,
owner_id: 1,
name: "Bugs (Pique)".to_owned(),
description: "The bugs we've found so far in Pique".to_owned(),
key: "BUG".to_owned(),
},
];
let values = context! {
user => user,
projects => projects,
};
ctx.render_resp("projects/list_projects.html", values)
} else {
Redirect::to("/login").into_response()
}
}
pub async fn create_project(
State(ctx): State<Context>,
auth_session: AuthSession<Context>,
) -> Response {
if let Some(_user) = auth_session.user {
let values = context! {};
ctx.render_resp("projects/create_project.html", values)
} else {
Redirect::to("/login").into_response()
}
}

View File

@ -6,6 +6,7 @@ pub struct Project {
pub owner_id: i32, pub owner_id: i32,
pub name: String, pub name: String,
pub description: String,
// The key is the short code, like BUG, which is used to refer to a project // The key is the short code, like BUG, which is used to refer to a project
// quickly and to display it more compactly. This must be unique across the // quickly and to display it more compactly. This must be unique across the

View File

@ -10,7 +10,7 @@ use tower_sessions::SessionManagerLayer;
use tower_sessions_sqlx_store::{sqlx::SqlitePool, SqliteStore}; use tower_sessions_sqlx_store::{sqlx::SqlitePool, SqliteStore};
use tracing::Level; use tracing::Level;
use crate::{config::CommandLineOptions, context::Context, handler::{home::home_page, login::logout, login_page, login_submit}, logging::setup_logging, templates::make_template_loader}; use crate::{config::CommandLineOptions, context::Context, handler::{home::home_page, login::logout, login_page, login_submit, projects::{create_project, projects_page}}, logging::setup_logging, templates::make_template_loader};
pub async fn run() -> Result<()> { pub async fn run() -> Result<()> {
dotenvy::dotenv()?; dotenvy::dotenv()?;
@ -40,6 +40,8 @@ pub async fn run() -> Result<()> {
.route("/login", get(login_page)) .route("/login", get(login_page))
.route("/login", post(login_submit)) .route("/login", post(login_submit))
.route("/logout", get(logout)) .route("/logout", get(logout))
.route("/projects", get(projects_page))
.route("/projects/new", get(create_project))
.layer(trace_layer) .layer(trace_layer)
.layer(session_layer) .layer(session_layer)
.layer(auth_layer) .layer(auth_layer)

View File

@ -0,0 +1,17 @@
<li>
{% if selected %}
<a href="{{ path }}" class="bg-gray-100 text-gray-900 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-gray-900" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="{{ svg }}" />
</svg>
{{ text }}
</a>
{% else %}
<a href="{{ path }}" class="text-gray-700 hover:text-gray-900 hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-gray-900" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="{{ svg }}" />
</svg>
{{ text }}
</a>
{% endif %}
</li>

View File

@ -1,3 +1,4 @@
<div class="fixed inset-y-0 z-50 flex w-72 flex-col"> <div class="fixed inset-y-0 z-50 flex w-72 flex-col">
<div class="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-20 bg-white px-6"> <div class="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-20 bg-white px-6">
@ -10,41 +11,22 @@
<li> <li>
<ul role="list" class="-mx-2 space-y-1"> <ul role="list" class="-mx-2 space-y-1">
<li>
<!-- Current: "bg-gray-50 text-indigo-600", Default: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50" -->
<a href="#" class="bg-gray-50 text-indigo-600 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
</svg>
Dashboard
</a>
</li>
<li> {% with path = "/", text = "Dashboard", selected = (current_page == "home"), svg = "M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" %}
<a href="#" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"> {% include "components/nav/main_item.html" %}
<svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true"> {% endwith %}
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" />
</svg>
Projects
</a>
</li>
<li>
<a href="#" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" />
</svg>
Documents
</a>
</li>
<li>
<a href="#" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 0 1-.825-.242m9.345-8.334a2.126 2.126 0 0 0-.476-.095 48.64 48.64 0 0 0-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0 0 11.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" />
</svg>
Chats
</a>
</li>
{% with path = "/projects", text = "Projects", selected = (current_page == "projects"), svg = "M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" %}
{% include "components/nav/main_item.html" %}
{% endwith %}
{% with path = "/documents", text = "Documents", selected = (current_page == "documents"), svg = "M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" %}
{% include "components/nav/main_item.html" %}
{% endwith %}
{% with path = "/chats", text = "Chats", selected = (current_page == "chats"), svg = "M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 0 1-.825-.242m9.345-8.334a2.126 2.126 0 0 0-.476-.095 48.64 48.64 0 0 0-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0 0 11.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" %}
{% include "components/nav/main_item.html" %}
{% endwith %}
</ul> </ul>
</li> </li>
@ -60,13 +42,18 @@
<span class="truncate">{{ project.name }}</span> <span class="truncate">{{ project.name }}</span>
</a> </a>
</li> </li>
{% else %}
<div class="text-gray-500 p-2 text-xs leading-6 italic">
No projects.
</div>
{% endfor %} {% endfor %}
</ul> </ul>
</li> </li>
<li class="-mx-6 mt-auto"> <li class="-mx-6 mt-auto">
<a href="/profile" class="flex items-center gap-x-4 px-6 py-3 text-sm font-semibold leading-6 text-gray-900 hover:bg-gray-50"> <div class="w-auto flex justify-around items-center text-sm font-semibold leading-6 text-gray-900">
<a href="/profile" class="flex items-center gap-x-4 px-6 py-3 hover:bg-gray-50 rounded-md">
<div class="h-8 w-8 rounded-full bg-gray-50"> <div class="h-8 w-8 rounded-full bg-gray-50">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
@ -74,7 +61,11 @@
</div> </div>
<span class="sr-only">Your profile</span> <span class="sr-only">Your profile</span>
<span aria-hidden="true">{{ user.full_name }}</span> <span aria-hidden="true" class="">Profile</span>
</a>
<a href="/logout" class="flex px-6 py-3 hover:bg-gray-50 rounded-md">
<span>Log out</span>
</a> </a>
</li> </li>

View File

@ -4,6 +4,7 @@
<body class="h-full"> <body class="h-full">
<div> <div>
{% set current_page = "home" %}
{% include "components/sidebar.html" %} {% include "components/sidebar.html" %}
<main class="py-10 pl-72"> <main class="py-10 pl-72">

View File

@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="h-full bg-purple-100"> <html lang="en" class="h-full bg-secondary">
{% include "head.html" %} {% include "head.html" %}
<body class="h-full"> <body class="h-full">
<div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8"> <div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-md"> <div class="sm:mx-auto sm:w-full sm:max-w-md">
<h2 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">Sign in to Pique</h2> <h2 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-secondary-content">Sign in to Pique</h2>
</div> </div>
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px]"> <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px]">
@ -14,19 +14,19 @@
<div> <div>
<label for="username" class="block text-sm font-medium leading-6 text-gray-900">Username</label> <label for="username" class="block text-sm font-medium leading-6 text-gray-900">Username</label>
<div class=""> <div class="">
<input id="username" name="username" type="username" autocomplete="username" required class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-emerald-600 sm:text-sm sm:leading-6"> <input id="username" name="username" type="username" autocomplete="username" required class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-accent sm:text-sm sm:leading-6">
</div> </div>
</div> </div>
<div> <div>
<label for="password" class="block text-sm font-medium leading-6 text-gray-900">Password</label> <label for="password" class="block text-sm font-medium leading-6 text-gray-900">Password</label>
<div class=""> <div class="">
<input id="password" name="password" type="password" autocomplete="current-password" required class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-emerald-600 sm:text-sm sm:leading-6"> <input id="password" name="password" type="password" autocomplete="current-password" required class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-accent sm:text-sm sm:leading-6">
</div> </div>
</div> </div>
<div> <div>
<button type="submit" class="flex w-full justify-center rounded-md bg-emerald-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-emerald-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-600">Sign in</button> <button type="submit" class="btn btn-primary w-full">Sign in</button>
</div> </div>
</form> </form>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en" class="h-full bg-white">
{% include "head.html" %}
<body class="h-full">
<div class="h-full">
{% set current_page = "projects" %}
{% include "components/sidebar.html" %}
<main class="pl-72 bg-gray-50 h-full">
<div class="navbar bg-accent text-accent-content">
<div class="navbar-start">
<a class="btn" href="/projects/new">New Project</a>
</div>
<div class="navbar-end">
<div class="btn">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" />
</svg>
</div>
</div>
</div>
<div class="px-8 py-8 flex flex-col gap-y-4">
</div>
</main>
<script type="text/javascript" src="/static/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en" class="h-full bg-white">
{% include "head.html" %}
<body class="h-full">
<div class="h-full">
{% set current_page = "projects" %}
{% include "components/sidebar.html" %}
<main class="pl-72 bg-gray-50 h-full">
<div class="navbar bg-accent text-accent-content">
<div class="navbar-start">
<a class="btn" href="/projects/new">New Project</a>
</div>
<div class="navbar-end">
<div class="btn">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" />
</svg>
</div>
</div>
</div>
<div class="px-8 py-8 flex flex-col gap-y-4">
{% for project in projects %}
<div class="card w-96 bg-base-100 shadow-md border-2 border-solid">
<div class="card-body">
<h2 class="card-title">{{ project.key }} - {{ project.name }}</h2>
<p>{{ project.description }}</p>
<div class="card-actions justify-end">
<button class="btn btn-primary">View</button>
<button class="btn">Edit</button>
</div>
</div>
</div>
{% endfor %}
</div>
</main>
<script type="text/javascript" src="/static/main.js"></script>
</body>
</html>