simplify list ownership & invitations
This commit is contained in:
+42
-49
@@ -1,7 +1,7 @@
|
||||
use maud::{DOCTYPE, Markup, html};
|
||||
|
||||
use crate::{
|
||||
db::{Category, GroceryList, InvitationInfo, Item, ListAccess, ListSummary, User},
|
||||
db::{Category, GroceryList, Item, User},
|
||||
hub::PresenceUser,
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ pub fn login_page(error: Option<&str>, invite: Option<&str>) -> Markup {
|
||||
div class="auth-card" {
|
||||
p class="eyebrow" { "SUSTENANCE" }
|
||||
h1 { "Welcome back" }
|
||||
p class="lede" { "Keep the household running, one item at a time." }
|
||||
p class="lede" { "Keep the shopping list in sync." }
|
||||
@if let Some(error) = error {
|
||||
div class="alert alert-error" role="alert" { (error) }
|
||||
}
|
||||
@@ -69,23 +69,23 @@ pub fn registration_closed_page() -> Markup {
|
||||
None,
|
||||
html! {
|
||||
div class="auth-card" {
|
||||
p class="eyebrow" { "PRIVATE HOUSEHOLD" }
|
||||
p class="eyebrow" { "PRIVATE LISTS" }
|
||||
h1 { "Registration is invite-only" }
|
||||
p class="lede" { "Ask someone who owns a list to send you an invitation link." }
|
||||
p class="lede" { "Ask someone who uses the app to send you an invitation link." }
|
||||
a class="button button-primary" href="/login" { "Back to sign in" }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn lists_page(user: &User, lists: &[ListSummary], csrf_token: &str) -> Markup {
|
||||
pub fn lists_page(user: &User, lists: &[GroceryList], csrf_token: &str) -> Markup {
|
||||
page(
|
||||
"Your lists",
|
||||
Some(user),
|
||||
html! {
|
||||
div class="page-heading" {
|
||||
div {
|
||||
p class="eyebrow" { "YOUR HOUSEHOLD" }
|
||||
p class="eyebrow" { "SHARED LISTS" }
|
||||
h1 { "Grocery lists" }
|
||||
p class="lede" { "Everything you need, in one place." }
|
||||
}
|
||||
@@ -104,12 +104,11 @@ pub fn lists_page(user: &User, lists: &[ListSummary], csrf_token: &str) -> Marku
|
||||
}
|
||||
} @else {
|
||||
div class="list-cards" {
|
||||
@for summary in lists {
|
||||
a class="list-card" href=(format!("/lists/{}", summary.list.id)) {
|
||||
@for list in lists {
|
||||
a class="list-card" href=(format!("/lists/{}", list.id)) {
|
||||
span class="list-card-icon" { "✓" }
|
||||
span class="list-card-copy" {
|
||||
strong { (summary.list.name) }
|
||||
small { @if summary.role == "owner" { "Owner" } @else { "Member" } }
|
||||
strong { (list.name) }
|
||||
}
|
||||
span class="list-card-arrow" { "→" }
|
||||
}
|
||||
@@ -126,6 +125,20 @@ pub fn lists_page(user: &User, lists: &[ListSummary], csrf_token: &str) -> Marku
|
||||
button class="button button-primary" type="submit" { "Create list" }
|
||||
}
|
||||
}
|
||||
section id="sharing" class="panel sharing-panel" {
|
||||
div class="panel-heading" { h2 { "Invite someone" } }
|
||||
p { "Create a one-time invite link so a new person can join." }
|
||||
form
|
||||
hx-post="/invitations"
|
||||
hx-target="#invite-result"
|
||||
hx-swap="innerHTML"
|
||||
class="stack"
|
||||
{
|
||||
input type="hidden" name="csrf" value=(csrf_token);
|
||||
button class="button button-secondary" type="submit" { "Create invite link" }
|
||||
}
|
||||
div id="invite-result" class="invite-result" {}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -133,24 +146,20 @@ pub fn lists_page(user: &User, lists: &[ListSummary], csrf_token: &str) -> Marku
|
||||
|
||||
pub fn list_page(
|
||||
user: &User,
|
||||
access: &ListAccess,
|
||||
list: &GroceryList,
|
||||
items: &[Item],
|
||||
categories: &[Category],
|
||||
presence: &[PresenceUser],
|
||||
csrf_token: &str,
|
||||
) -> Markup {
|
||||
let is_owner = access.role == "owner";
|
||||
page(
|
||||
&access.list.name,
|
||||
&list.name,
|
||||
Some(user),
|
||||
html! {
|
||||
div class="list-topbar" {
|
||||
a class="back-link" href="/lists" { "← All lists" }
|
||||
div class="list-topbar-actions" {
|
||||
span class="live-pill" { span class="live-dot" {} "Live" }
|
||||
@if is_owner {
|
||||
a class="button button-small button-quiet" href="#sharing" { "Share list" }
|
||||
}
|
||||
}
|
||||
}
|
||||
div class="list-layout" {
|
||||
@@ -158,38 +167,22 @@ pub fn list_page(
|
||||
div class="list-heading" {
|
||||
div {
|
||||
p class="eyebrow" { "SHARED LIST" }
|
||||
h1 { (access.list.name) }
|
||||
h1 { (list.name) }
|
||||
p class="list-meta" { (items.iter().filter(|item| !item.checked).count()) " items to get" }
|
||||
}
|
||||
}
|
||||
(list_content_fragment(&access.list, items, categories, csrf_token, false))
|
||||
(list_content_fragment(list, items, categories, csrf_token, false))
|
||||
}
|
||||
aside class="side-column" {
|
||||
(presence_panel(presence, false))
|
||||
(categories_panel(&access.list, categories, csrf_token, false))
|
||||
@if is_owner {
|
||||
section id="sharing" class="panel sharing-panel" {
|
||||
div class="panel-heading" { h2 { "Share this list" } }
|
||||
p { "Create a one-time invite link for someone you shop with." }
|
||||
form
|
||||
hx-post=(format!("/lists/{}/invitations", access.list.id))
|
||||
hx-target="#invite-result"
|
||||
hx-swap="innerHTML"
|
||||
class="stack"
|
||||
{
|
||||
input type="hidden" name="csrf" value=(csrf_token);
|
||||
button class="button button-secondary" type="submit" { "Create invite link" }
|
||||
}
|
||||
div id="invite-result" class="invite-result" {}
|
||||
}
|
||||
}
|
||||
(categories_panel(list, categories, csrf_token, false))
|
||||
section class="panel tip-panel" {
|
||||
span class="tip-label" { "TIP" }
|
||||
p { "Check items off as you go. Everyone viewing this list will see it instantly." }
|
||||
}
|
||||
}
|
||||
}
|
||||
div id="live-stream" class="live-stream" hx-ext="ws" ws-connect=(format!("/lists/{}/stream", access.list.id)) {}
|
||||
div id="live-stream" class="live-stream" hx-ext="ws" ws-connect=(format!("/lists/{}/stream", list.id)) {}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -448,26 +441,26 @@ pub fn categories_panel(
|
||||
}
|
||||
|
||||
pub fn live_list_fragments(
|
||||
access: &ListAccess,
|
||||
list: &GroceryList,
|
||||
items: &[Item],
|
||||
categories: &[Category],
|
||||
csrf_token: &str,
|
||||
) -> Markup {
|
||||
html! {
|
||||
(list_content_fragment(&access.list, items, categories, csrf_token, true))
|
||||
(categories_panel(&access.list, categories, csrf_token, true))
|
||||
(list_content_fragment(list, items, categories, csrf_token, true))
|
||||
(categories_panel(list, categories, csrf_token, true))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn category_created(
|
||||
access: &ListAccess,
|
||||
list: &GroceryList,
|
||||
items: &[Item],
|
||||
categories: &[Category],
|
||||
csrf_token: &str,
|
||||
) -> Markup {
|
||||
html! {
|
||||
p class="category-success" { "Category added." }
|
||||
(live_list_fragments(access, items, categories, csrf_token))
|
||||
(live_list_fragments(list, items, categories, csrf_token))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,7 +502,6 @@ fn presence_content(presence: &[PresenceUser]) -> Markup {
|
||||
}
|
||||
|
||||
pub fn invite_page(
|
||||
info: &InvitationInfo,
|
||||
user: Option<&User>,
|
||||
token: &str,
|
||||
error: Option<&str>,
|
||||
@@ -521,8 +513,8 @@ pub fn invite_page(
|
||||
html! {
|
||||
div class="auth-card invite-card" {
|
||||
p class="eyebrow" { "YOU'RE INVITED" }
|
||||
h1 { "Join " (info.list_name) }
|
||||
p class="lede" { "Shop together and keep the list in sync." }
|
||||
h1 { "Join the shared lists" }
|
||||
p class="lede" { "Shop together and keep all the lists in sync." }
|
||||
@if let Some(error) = error {
|
||||
div class="alert alert-error" role="alert" { (error) }
|
||||
}
|
||||
@@ -532,14 +524,15 @@ pub fn invite_page(
|
||||
@if let Some(csrf_token) = csrf_token {
|
||||
input type="hidden" name="csrf" value=(csrf_token);
|
||||
}
|
||||
button class="button button-primary" type="submit" { "Join list" }
|
||||
button class="button button-primary" type="submit" { "Join lists" }
|
||||
}
|
||||
p class="muted" { "Want to join as a different person? Create a new account below." }
|
||||
} @else {
|
||||
p { "Sign in or create an account to accept this invite." }
|
||||
div class="invite-actions" {
|
||||
a class="button button-primary" href=(format!("/login?invite={}", token)) { "Sign in" }
|
||||
a class="button button-secondary" href=(format!("/register?invite={}", token)) { "Create account" }
|
||||
}
|
||||
}
|
||||
div class="invite-actions" {
|
||||
a class="button button-primary" href=(format!("/register?invite={}", token)) { "Create account" }
|
||||
a class="button button-secondary" href=(format!("/login?invite={}", token)) { "Sign in" }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user