Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

28 add public purchase page #32

Merged
merged 15 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ SURREALDB_ROOT_PASS=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=
APP_BASE_URL=
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ simple_logger = "4.2.0"
surrealdb = { version = "1" }
thiserror = "1"
chrono = { version = "0.4", features = [ "serde" ] }
timeago = { version = "0.4", default-features = false, features = [ "chrono" ] }
tokio = { version = "1", features = ["full"] }
tower = { version = "0.4", features = ["full"] }
tower-http = { version = "0.5", features = ["full"] }
Expand Down
20 changes: 20 additions & 0 deletions crates/bl/src/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ pub async fn fetch_user_owned_photo_groups(
Ok(groups)
}

#[instrument]
pub async fn fetch_photo_group(
photo_group_id: core_types::PhotoGroupRecordId,
) -> Result<Option<core_types::PhotoGroup>> {
let client = SurrealRootClient::new()
.await
.wrap_err("Failed to create surreal client")?;
client
.use_ns("main")
.use_db("main")
.await
.wrap_err("Failed to use surreal namespace/database")?;

let group = core_types::PhotoGroup::fetch(photo_group_id, &client)
.await
.wrap_err("Failed to fetch photo group")?;

Ok(group)
}

#[instrument]
pub async fn fetch_user(
user_id: core_types::UserRecordId,
Expand Down
1 change: 0 additions & 1 deletion crates/core_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ mod price;
#[cfg(feature = "ssr")]
pub(crate) mod ssr;

#[cfg(feature = "ssr")]
pub use ulid::Ulid;

#[cfg(feature = "ssr")]
Expand Down
2 changes: 2 additions & 0 deletions crates/site-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ cfg-if.workspace = true
thiserror.workspace = true
serde.workspace = true
bytes = { workspace = true, optional = true }
chrono = { workspace = true }
timeago = { workspace = true }
tracing = { workspace = true, optional = true }

validator = "0.16.1"
Expand Down
58 changes: 2 additions & 56 deletions crates/site-app/src/components/gallery.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use leptos::*;

use crate::components::user::UserName;
use crate::components::photo_group::PhotoGroup;

#[component]
pub fn Gallery() -> impl IntoView {
Expand Down Expand Up @@ -47,7 +47,7 @@ fn PhotoGroupList(groups: Vec<core_types::PhotoGroup>) -> impl IntoView {
}

view! {
<div class="flex-1 flex flex-col gap-4 justify-stretch max-w-lg">
<div class="flex-1 flex flex-col gap-4 items-stretch lg:max-w-lg">
{ groups.into_iter().map(|group| {
view! {
<PhotoGroup group=group />
Expand All @@ -59,60 +59,6 @@ fn PhotoGroupList(groups: Vec<core_types::PhotoGroup>) -> impl IntoView {
.into_view()
}

#[component]
fn PhotoGroup(group: core_types::PhotoGroup) -> impl IntoView {
let status_element = match group.status {
core_types::PhotoGroupStatus::OwnershipForSale { digital_price } => view! {
<p class="text-2xl tracking-tight text-base-content">
"For Sale: "
<span class="font-semibold">
{ format!("${:.2}", digital_price.0) }
</span>
</p>
}
.into_view(),
_ => view! {
<p class="text-xl font-semibold tracking-tight text-base-content/80">
"Not For Sale"
</p>
}
.into_view(),
};

view! {
<div class="flex p-4 gap-4 bg-base-100 rounded-box shadow">
{ group.photos.into_iter().map(|photo_id| {
view! {
<crate::components::photo::Photo photo_id=photo_id />
}
.into_view()
}).collect::<Vec<_>>() }
<div class="flex-1 flex flex-col gap-2">
{ status_element }
<div class="flex-1" />
<div class="flex flex-col gap-1">
<p class="text-xs text-base-content/80">
"Owned by "<UserName id={group.owner} />
</p>
<p class="text-xs text-base-content/80">
"Photographed by "<UserName id={group.photographer} />
</p>
</div>
</div>
<div class="flex flex-col items-end justify-between">
// context menu ellipsis
<button class="
d-btn d-btn-ghost d-btn-circle d-btn-sm text-xl font-bold
justify-center items-center text-center
">"⋮"</button>
<button class="d-btn d-btn-primary d-btn-sm text-lg font-semibold tracking-tight">
"Share"
</button>
</div>
</div>
}
}

#[cfg_attr(feature = "ssr", tracing::instrument)]
#[server]
pub async fn fetch_user_photo_groups(
Expand Down
11 changes: 11 additions & 0 deletions crates/site-app/src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod form;
pub mod gallery;
pub mod navigation;
pub mod photo;
pub mod photo_group;
pub mod photo_upload;
pub mod user;

Expand Down Expand Up @@ -33,4 +34,14 @@ pub mod basic {
</a>
}
}

#[component]
pub fn TimeAgo(time: chrono::DateTime<chrono::Utc>) -> impl IntoView {
let formatter = timeago::Formatter::new();
let formatted = formatter.convert_chrono(time, chrono::Utc::now());

view! {
<span>{ formatted }</span>
}
}
}
112 changes: 112 additions & 0 deletions crates/site-app/src/components/photo_group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use leptos::*;

use crate::components::user::UserName;

#[component]
pub fn EllipsisButton() -> impl IntoView {
view! {
<button class="
d-btn d-btn-ghost d-btn-circle d-btn-sm text-xl font-bold
justify-center items-center text-center
">"⋮"</button>
}
}

#[component]
pub fn PhotoGroup(
group: core_types::PhotoGroup,
#[prop(default = false)] read_only: bool,
#[prop(default = "")] extra_class: &'static str,
) -> impl IntoView {
let status_element = match group.status {
core_types::PhotoGroupStatus::OwnershipForSale { digital_price } => view! {
<p class="text-2xl tracking-tight text-base-content">
"For Sale: "
<span class="font-semibold">
{ format!("${:.2}", digital_price.0) }
</span>
</p>
}
.into_view(),
_ => view! {
<p class="text-xl font-semibold tracking-tight text-base-content/80">
"Not For Sale"
</p>
}
.into_view(),
};

let share_url = format!(
"{}/photo/{}",
std::env::var("APP_BASE_URL").expect("APP_BASE_URL not set"),
group.id.0
);

let photos_element = view! {
{ group.photos.clone().into_iter().map(|photo_id| {
view! {
<crate::components::photo::Photo photo_id=photo_id />
}
.into_view()
}).collect::<Vec<_>>() }
}
.into_view();

let owned_by_element = view! {
"Owned by "<UserName id={group.owner} />
}
.into_view();

let uploaded_by_element = view! {
"Uploaded by "<UserName id={group.photographer} />
}
.into_view();

let created_at_element = view! {
<crate::components::basic::TimeAgo time={group.meta.created_at} />
}
.into_view();

view! {
<div class={format!(
"grid grid-cols-[auto_1fr] p-6 gap-4 bg-base-100 rounded-box shadow {extra_class}"
)}>
<div class={format!(
"col-start-1 col-span-1 row-start-1 flex flex-col justify-center xs:px-4 {adjusted_for_action}",
adjusted_for_action = if !read_only { "row-span-1 sm:row-span-2" } else { "row-span-2" },
)}>
{ photos_element }
</div>
<div class="col-start-2 col-span-1 row-start-1 row-span-1 flex flex-row justify-between gap-4">
{ status_element }
<crate::components::photo_group::EllipsisButton />
</div>
<div class={format!(
"row-start-2 row-span-1 flex flex-row items-end justify-between gap-4 {adjusted_for_action}",
adjusted_for_action = if !read_only { "col-start-1 col-span-2 sm:col-start-2 sm:col-span-1" } else { "col-start-2 col-span-1" },
)}>
<div class="flex flex-col gap-1">
<p class="text-xs text-base-content/80">
{ owned_by_element }
</p>
<p class="text-xs text-base-content/80">
{ uploaded_by_element }
", "
{ created_at_element }
</p>
</div>
{ match read_only {
false => view! {
<a
class="d-btn d-btn-primary d-btn-sm text-lg font-semibold tracking-tight"
href={ share_url }
>
"Share"
</a>
}.into_view(),
true => ().into_view()
} }
</div>
</div>
}
}
8 changes: 6 additions & 2 deletions crates/site-app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use leptos_meta::*;
use leptos_router::*;

use crate::{
components::navigation::navigate_to,
components::navigation::reload,
error_template::{AppError, ErrorTemplate},
pages::Footer,
utils::authenticated_user,
};

Expand Down Expand Up @@ -39,7 +40,10 @@ pub fn App() -> impl IntoView {
<Route path="/dashboard" view=pages::dashboard::DashboardPage/>
<Route path="/login" view=pages::auth::login_page::LoginPage/>
<Route path="/signup" view=pages::auth::signup_page::SignupPage/>
<Route path="/photo/:id" view=pages::purchase::PurchasePage/>
<Route path="/photo" view=pages::purchase::error::PurchasePageNoId/>
</Routes>
<Footer/>
</div>
</Router>
}
Expand Down Expand Up @@ -88,7 +92,7 @@ pub fn LogoutButton(class: Option<String>) -> impl IntoView {

create_effect(move |_| {
if matches!(logout_value(), Some(Ok(_))) {
navigate_to("/");
reload();
}
});

Expand Down
6 changes: 3 additions & 3 deletions crates/site-app/src/pages/dashboard/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ pub fn DashboardPage() -> impl IntoView {
}

view! {
<PageWrapper bg_color="" shadow="">
<PageWrapper backed=false>
<p class="text-4xl font-semibold tracking-tight">"Marketplace Photos"</p>
<div class="flex flex-row justify-between gap-4 items-start">
<crate::components::gallery::Gallery />
<div class="flex flex-col lg:flex-row-reverse items-stretch lg:justify-between gap-4 items-start">
<crate::components::photo_upload::PhotoUpload />
<crate::components::gallery::Gallery />
</div>
</PageWrapper>
}
Expand Down
Loading
Loading