From 209f387d40f82810ac64ee4762ba2a4ac9f13c26 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Tue, 19 Mar 2024 10:39:41 -0400 Subject: [PATCH 01/18] start cart fix providerposts to sellerposts --- src/assets/shopping-cart.svg | 7 ++ src/components/common/AddToCartButton.tsx | 59 ++++++++++ src/components/common/Cart.tsx | 101 ++++++++++++++++++ src/components/common/Header.astro | 5 + .../posts/ClientViewProviderPosts.tsx | 6 +- src/components/posts/FullPostView.tsx | 6 +- src/components/posts/fetchPosts.ts | 6 +- src/components/services/ServicesMain.tsx | 14 +-- src/components/services/ViewCard.tsx | 8 ++ 9 files changed, 196 insertions(+), 16 deletions(-) create mode 100644 src/assets/shopping-cart.svg create mode 100644 src/components/common/AddToCartButton.tsx create mode 100644 src/components/common/Cart.tsx diff --git a/src/assets/shopping-cart.svg b/src/assets/shopping-cart.svg new file mode 100644 index 00000000..8eeb53b3 --- /dev/null +++ b/src/assets/shopping-cart.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/common/AddToCartButton.tsx b/src/components/common/AddToCartButton.tsx new file mode 100644 index 00000000..f9460c2f --- /dev/null +++ b/src/components/common/AddToCartButton.tsx @@ -0,0 +1,59 @@ +import { Show, createResource, createSignal, onMount } from "solid-js"; +import type { Component } from "solid-js"; +import { getLangFromUrl, useTranslations } from "../../i18n/utils"; +import cart from "../../assets/shopping-cart.svg"; + +const lang = getLangFromUrl(new URL(window.location.href)); +const t = useTranslations(lang); + +interface Item { + description: string; + price: number; + price_id: string; + quantity: number; +} + +interface Props { + description: string; + price: number; + price_id: string; + quantity: number; +} + +//TODO Remove this test code +localStorage.items = JSON.stringify([ + { description: "t-shirt", price: 1000, price_id: "", quantity: 2 }, + { description: "watch", price: 2000, price_id: "", quantity: 2 }, +]); + +export const Cart: Component = (props: Props) => { + const [items, setItems] = createSignal([]); + + onMount(() => { + try { + setItems(JSON.parse(localStorage.items)); + } catch (_) { + setItems([]); + } + }); + + function clickHandler() { + + } + + + return ( +
+ + +
+ ); +}; diff --git a/src/components/common/Cart.tsx b/src/components/common/Cart.tsx new file mode 100644 index 00000000..449617e5 --- /dev/null +++ b/src/components/common/Cart.tsx @@ -0,0 +1,101 @@ +import { Show, createResource, createSignal, onMount } from "solid-js"; +import { getLangFromUrl, useTranslations } from "../../i18n/utils"; +import cart from "../../assets/shopping-cart.svg"; + +const lang = getLangFromUrl(new URL(window.location.href)); +const t = useTranslations(lang); + +interface Item { + description: string; + price: number; + price_id: string; + quantity: number; +} + +//TODO Remove this test code +localStorage.items = JSON.stringify([ + { description: "t-shirt", price: 1000, price_id: "", quantity: 2 }, + { description: "watch", price: 2000, price_id: "", quantity: 2 }, +]); + +export const Cart = () => { + const [items, setItems] = createSignal([]); + + onMount(() => { + try { + setItems(JSON.parse(localStorage.items)); + console.log("Items: " + items().map((item: Item) => item.description)); + } catch (_) { + setItems([]); + } + }); + + function clickHandler() { + const listShow = document.getElementById("cartItems"); + if (listShow?.classList.contains("hidden")) { + listShow?.classList.remove("hidden"); + } else { + listShow?.classList.add("hidden"); + } + } + + function goToCart() { + window.location.href = `/${lang}/cart`; + } + + function shoppingCart() { + if (items().length > 0) { + { + console.log("items in cart: " + items().length); + } + return ( +
    + {items().map((item: Item) => ( +
    +
    {item.description}
    +
    {item.price}
    +
    {item.quantity}
    +
    + ))} +
+ ); + } else { + return ( +
+
Cart is empty
+ +
+ {/* TODO: Internationalize */} +
Total:
+
$0.00
+
+
+ ); + } + } + + // ADD EMAIL TO SEND FOR CONTACT US + + return ( +
+ + +
+ ); +}; diff --git a/src/components/common/Header.astro b/src/components/common/Header.astro index bc99cd9f..8595bb3a 100644 --- a/src/components/common/Header.astro +++ b/src/components/common/Header.astro @@ -7,6 +7,7 @@ import { getLangFromUrl, useTranslations } from "../../i18n/utils"; import ThemeIcon from "./ThemeIcon.astro"; import { ProfileBtn } from "./ProfileBtn"; +import { Cart } from "./Cart"; import { CreatePostsRouting } from "../posts/CreatePostsRouting"; import { ClientRouting } from "../users/ClientRouting"; @@ -77,6 +78,10 @@ const { links = [] } = Astro.props; +
+ +
+
diff --git a/src/components/posts/ClientViewProviderPosts.tsx b/src/components/posts/ClientViewProviderPosts.tsx index a0e49e07..9cd1f6db 100644 --- a/src/components/posts/ClientViewProviderPosts.tsx +++ b/src/components/posts/ClientViewProviderPosts.tsx @@ -34,7 +34,7 @@ export const ClientViewProviderPosts: Component = (props) => { createEffect(async () => { const { data, error } = await supabase - .from("providerposts") + .from("sellerposts") .select("*") .eq("seller_id", props.id); if (!data) { @@ -45,11 +45,11 @@ export const ClientViewProviderPosts: Component = (props) => { } else { data?.map((item) => { productCategories.forEach((productCategories) => { - if (item.service_category.toString() === productCategories.id) { + if (item.product_category.toString() === productCategories.id) { item.category = productCategories.name; } }); - delete item.service_category; + delete item.product_category; }); setPosts(data); } diff --git a/src/components/posts/FullPostView.tsx b/src/components/posts/FullPostView.tsx index 2969467a..0004ee3b 100644 --- a/src/components/posts/FullPostView.tsx +++ b/src/components/posts/FullPostView.tsx @@ -56,7 +56,7 @@ export const ViewFullPost: Component = (props) => { if (session()) { try { const { data, error } = await supabase - .from("providerposts") + .from("sellerposts") .select("*") .eq("id", id); @@ -68,11 +68,11 @@ export const ViewFullPost: Component = (props) => { } else { data?.map(async (item) => { productCategories.forEach(productCategories => { - if (item.service_category.toString() === productCategories.id) { + if (item.product_category.toString() === productCategories.id) { item.category = productCategories.name } }) - delete item.service_category + delete item.product_category item.seller_url = `/${lang}/provider/${item.seller_id}` }) setPost(data[0]); diff --git a/src/components/posts/fetchPosts.ts b/src/components/posts/fetchPosts.ts index 5ef189b7..0d1e1f75 100644 --- a/src/components/posts/fetchPosts.ts +++ b/src/components/posts/fetchPosts.ts @@ -9,9 +9,9 @@ const t = useTranslations(lang); // one giant filter function that includes the logic for all combinations export async function fetchFilteredPosts(categoryFilters: Array, locationFilters: Array, minorLocationFilters: Array, governingLocationFilters: Array, searchString: string) { try { - let query = supabase.from("providerposts").select("*"); + let query = supabase.from("sellerposts").select("*"); if(categoryFilters.length !== 0) { - query = query.in('service_category', categoryFilters); + query = query.in('product_category', categoryFilters); } if(locationFilters.length !== 0) { query = query.in('major_municipality', locationFilters); @@ -44,7 +44,7 @@ export async function fetchFilteredPosts(categoryFilters: Array, locatio export async function fetchAllPosts() { try { - const { data: allPosts, error } = await supabase.from("providerposts").select("*") + const { data: allPosts, error } = await supabase.from("sellerposts").select("*") if(error) { console.log("supabase error: " + error.message); diff --git a/src/components/services/ServicesMain.tsx b/src/components/services/ServicesMain.tsx index 1c3777ad..056fd133 100644 --- a/src/components/services/ServicesMain.tsx +++ b/src/components/services/ServicesMain.tsx @@ -26,15 +26,15 @@ if (user.session === null || user.session === undefined) { location.href = `/${lang}/login`; } -const { data, error } = await supabase.from("providerposts").select("*"); +const { data, error } = await supabase.from("sellerposts").select("*"); data?.map((item) => { productCategories.forEach((productCategories) => { - if (item.service_category.toString() === productCategories.id) { + if (item.product_category.toString() === productCategories.id) { item.category = productCategories.name; } }); - delete item.service_category; + delete item.product_category; }); interface ProviderPost { @@ -132,11 +132,11 @@ export const ServicesView: Component = () => { //Add the categories to the posts in the current language allPosts?.map((item) => { productCategories.forEach((productCategories) => { - if (item.service_category.toString() === productCategories.id) { + if (item.product_category.toString() === productCategories.id) { item.category = productCategories.name; } }); - delete item.service_category; + delete item.product_category; }); setPosts(allPosts!); @@ -150,11 +150,11 @@ export const ServicesView: Component = () => { res.map((post) => { productCategories.forEach((productCategory) => { - if (post.service_category.toString() === productCategory.id) { + if (post.product_category.toString() === productCategory.id) { post.category = productCategory.name; } }); - delete post.service_category; + delete post.product_category; }); setPosts(res); diff --git a/src/components/services/ViewCard.tsx b/src/components/services/ViewCard.tsx index 5df1b6f8..f8994431 100644 --- a/src/components/services/ViewCard.tsx +++ b/src/components/services/ViewCard.tsx @@ -5,6 +5,7 @@ import supabase from "../../lib/supabaseClient"; import { getLangFromUrl, useTranslations } from "../../i18n/utils"; import { SocialMediaShares } from "../posts/SocialMediaShares"; import SocialModal from "../posts/SocialModal"; +import { Cart } from "../common/AddToCartButton"; const lang = getLangFromUrl(new URL(window.location.href)); const t = useTranslations(lang); @@ -135,6 +136,13 @@ export const ViewCard: Component = (props) => { image_urls={post.image_urls} /> +
+ +

From ad26c8b73ff9b7ee5c8db54086f2bc78c94ece15 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Tue, 19 Mar 2024 17:10:47 -0400 Subject: [PATCH 02/18] cart with quantity counter working --- src/components/common/AddToCartButton.tsx | 52 ++++++++++++++++--- src/components/common/Cart.tsx | 50 +++++++++++++----- .../posts/ClientViewProviderPosts.tsx | 4 ++ src/components/services/ServicesMain.tsx | 22 +++++--- src/components/services/ViewCard.tsx | 14 +++-- .../migrations/20240319173341_addCart.sql | 21 ++++++++ supabase/seed.sql | 12 ++--- 7 files changed, 139 insertions(+), 36 deletions(-) create mode 100644 supabase/migrations/20240319173341_addCart.sql diff --git a/src/components/common/AddToCartButton.tsx b/src/components/common/AddToCartButton.tsx index f9460c2f..365a18c2 100644 --- a/src/components/common/AddToCartButton.tsx +++ b/src/components/common/AddToCartButton.tsx @@ -11,6 +11,7 @@ interface Item { price: number; price_id: string; quantity: number; + product_id: string; } interface Props { @@ -18,29 +19,65 @@ interface Props { price: number; price_id: string; quantity: number; + product_id: string; } //TODO Remove this test code -localStorage.items = JSON.stringify([ - { description: "t-shirt", price: 1000, price_id: "", quantity: 2 }, - { description: "watch", price: 2000, price_id: "", quantity: 2 }, -]); +if (localStorage.order === undefined || localStorage.order === null) { + localStorage.order = JSON.stringify([ + { + description: "t-shirt", + price: 10, + price_id: "", + product_id: "", + quantity: 3, + }, + { + description: "watch", + price: 20.99, + price_id: "", + product_id: "", + quantity: 1, + }, + ]); +} export const Cart: Component = (props: Props) => { const [items, setItems] = createSignal([]); onMount(() => { try { - setItems(JSON.parse(localStorage.items)); + setItems(JSON.parse(localStorage.order)); } catch (_) { setItems([]); } }); function clickHandler() { - - } + let itemInCart = false; + + items().forEach((item: Item) => { + if (item.product_id === props.product_id) { + item.quantity += props.quantity; + itemInCart = true; + } + }); + if (!itemInCart) { + const newItem = { + description: props.description, + price: props.price, + price_id: props.price_id, + quantity: props.quantity, + product_id: props.product_id, + }; + setItems([...items(), newItem]); + } + + localStorage.setItem("order", JSON.stringify(items())); + console.log("Items: " + items().map((item: Item) => item.description)); + console.log("Order: " + localStorage.order); + } return (

@@ -53,7 +90,6 @@ export const Cart: Component = (props: Props) => { {/* TODO Internationalize */} Add to Cart -
); }; diff --git a/src/components/common/Cart.tsx b/src/components/common/Cart.tsx index 449617e5..fe4e42b8 100644 --- a/src/components/common/Cart.tsx +++ b/src/components/common/Cart.tsx @@ -1,4 +1,4 @@ -import { Show, createResource, createSignal, onMount } from "solid-js"; +import { Show, createEffect, createResource, createSignal, onMount } from "solid-js"; import { getLangFromUrl, useTranslations } from "../../i18n/utils"; import cart from "../../assets/shopping-cart.svg"; @@ -12,25 +12,31 @@ interface Item { quantity: number; } -//TODO Remove this test code -localStorage.items = JSON.stringify([ - { description: "t-shirt", price: 1000, price_id: "", quantity: 2 }, - { description: "watch", price: 2000, price_id: "", quantity: 2 }, -]); - export const Cart = () => { const [items, setItems] = createSignal([]); + const [totalItems, setTotalItems] = createSignal(0); onMount(() => { try { - setItems(JSON.parse(localStorage.items)); + setItems(JSON.parse(localStorage.order)); console.log("Items: " + items().map((item: Item) => item.description)); + items().forEach(item => { + setTotalItems(totalItems() + item.quantity); + }); } catch (_) { setItems([]); } }); + createEffect(() => { + setTotalItems(0); + items().forEach(item => { + setTotalItems(totalItems() + item.quantity); + }) +}); + function clickHandler() { + setItems(JSON.parse(localStorage.order)); const listShow = document.getElementById("cartItems"); if (listShow?.classList.contains("hidden")) { listShow?.classList.remove("hidden"); @@ -45,19 +51,32 @@ export const Cart = () => { function shoppingCart() { if (items().length > 0) { + let total = 0; { console.log("items in cart: " + items().length); } + items().forEach((item: Item) => { + total += item.price * item.quantity; + }) return ( +
+
Cart
    {items().map((item: Item) => ( -
    -
    {item.description}
    -
    {item.price}
    +
    +
    {item.description}
    +
    ${item.price.toFixed(2)}
    {item.quantity}
    ))}
+
+ {/* TODO: Internationalize */} +
Subtotal:
+
${total}
+
Taxes calculated at checkout
+
+
); } else { return ( @@ -80,11 +99,14 @@ export const Cart = () => {
{ >
{shoppingCart()}
{/* TODO: Style and Internationalize */} +
+
); diff --git a/src/components/posts/ClientViewProviderPosts.tsx b/src/components/posts/ClientViewProviderPosts.tsx index 9cd1f6db..cf486148 100644 --- a/src/components/posts/ClientViewProviderPosts.tsx +++ b/src/components/posts/ClientViewProviderPosts.tsx @@ -23,6 +23,10 @@ interface ProviderPost { // minor_municipality: string; // governing_district: string; image_urls: string; + price: number; + price_id: string; + quantity: number; + product_id: string; } interface Props { diff --git a/src/components/services/ServicesMain.tsx b/src/components/services/ServicesMain.tsx index 056fd133..f608f28f 100644 --- a/src/components/services/ServicesMain.tsx +++ b/src/components/services/ServicesMain.tsx @@ -9,6 +9,7 @@ import { ui } from "../../i18n/ui"; import type { uiObject } from "../../i18n/uiType"; import { getLangFromUrl, useTranslations } from "../../i18n/utils"; import * as allFilters from "../posts/fetchPosts"; +import stripe from "../../lib/stripe"; const lang = getLangFromUrl(new URL(window.location.href)); const t = useTranslations(lang); @@ -28,13 +29,18 @@ if (user.session === null || user.session === undefined) { const { data, error } = await supabase.from("sellerposts").select("*"); -data?.map((item) => { +data?.map(async (item) => { productCategories.forEach((productCategories) => { if (item.product_category.toString() === productCategories.id) { item.category = productCategories.name; } }); delete item.product_category; + + if(item.price_id !== null) { + const priceData = await stripe.prices.retrieve(item.price_id); + item.price = priceData.unit_amount! / 100; + } }); interface ProviderPost { @@ -48,6 +54,10 @@ interface ProviderPost { // governing_district: string; user_id: string; image_urls: string; + price: number; + price_id: string; + quantity: number; + product_id: string; } export const ServicesView: Component = () => { @@ -104,7 +114,7 @@ export const ServicesView: Component = () => { locationFilters(), minorLocationFilters(), governingLocationFilters(), - searchString(), + searchString() ); if (res === null || res === undefined) { @@ -124,7 +134,7 @@ export const ServicesView: Component = () => { setTimeout(() => { //Clear all filters after the timeout otherwise the message immediately disappears (probably not a perfect solution) clearAllFilters(); - }, 3000), + }, 3000) ); let allPosts = await allFilters.fetchAllPosts(); @@ -165,7 +175,7 @@ export const ServicesView: Component = () => { const filterPostsByMajorMunicipality = (location: string) => { if (locationFilters().includes(location)) { let currentLocationFilters = locationFilters().filter( - (el) => el !== location, + (el) => el !== location ); setLocationFilters(currentLocationFilters); } else { @@ -205,7 +215,7 @@ export const ServicesView: Component = () => { let searchInput = document.getElementById("search") as HTMLInputElement; let selectedCategories = document.querySelectorAll(".selected"); const majorMuniCheckboxes = document.querySelectorAll( - "input[type='checkbox'].major-muni", + "input[type='checkbox'].major-muni" ) as NodeListOf; // const minorMuniCheckboxes = document.querySelectorAll( // "input[type='checkbox'].minor-muni" @@ -260,7 +270,7 @@ export const ServicesView: Component = () => { const clearMajorMunicipality = () => { const majorMuniCheckboxes = document.querySelectorAll( - "input[type='checkbox'].major-muni", + "input[type='checkbox'].major-muni" ) as NodeListOf; majorMuniCheckboxes.forEach((checkbox) => { diff --git a/src/components/services/ViewCard.tsx b/src/components/services/ViewCard.tsx index f8994431..fce126a9 100644 --- a/src/components/services/ViewCard.tsx +++ b/src/components/services/ViewCard.tsx @@ -21,6 +21,10 @@ interface Post { // governing_district: string; user_id: string; image_urls: string | null; + price: number; + price_id: string; + quantity: number; + product_id: string; } interface Props { @@ -40,6 +44,8 @@ export const ViewCard: Component = (props) => { post.image_urls.split(",")[0] )) : (post.image_url = null); + // Set the default quantity to 1 This should be replaced with the quantity from the quantity counter in the future + post.quantity = 1; return post; }) ); @@ -138,9 +144,11 @@ export const ViewCard: Component = (props) => {
diff --git a/supabase/migrations/20240319173341_addCart.sql b/supabase/migrations/20240319173341_addCart.sql new file mode 100644 index 00000000..c8da8d26 --- /dev/null +++ b/supabase/migrations/20240319173341_addCart.sql @@ -0,0 +1,21 @@ +create or replace view "public"."sellerposts" as SELECT seller_post.id, + seller_post.title, + seller_post.content, + seller_post.user_id, + seller_post.image_urls, + seller_post.product_category, + locationview.major_municipality, + locationview.minor_municipality, + locationview.governing_district, + sellers.seller_name, + sellers.seller_id, + profiles.email, + seller_post.stripe_price_id AS price_id, + seller_post.stripe_product_id AS product_id + FROM (((seller_post + LEFT JOIN profiles ON ((seller_post.user_id = profiles.user_id))) + LEFT JOIN sellers ON ((seller_post.user_id = sellers.user_id))) + LEFT JOIN locationview ON ((seller_post.location = locationview.id))); + + + diff --git a/supabase/seed.sql b/supabase/seed.sql index 1ec87c2b..f1b5c660 100644 --- a/supabase/seed.sql +++ b/supabase/seed.sql @@ -72,12 +72,12 @@ INSERT INTO "public"."post_category" ("id", "category", "language") VALUES -- Data for Name: seller_post; Type: TABLE DATA; Schema: public; Owner: postgres -- -INSERT INTO "public"."seller_post" ("id", "created_at", "title", "product_category", "content", "location", "user_id", "image_urls") VALUES - (1, '2024-03-05 15:38:02.509065+00', 'Test Post 1', 1, 'Content of the first post', 1, '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL), - (2, '2024-03-05 15:40:05.243892+00', 'Test Post 2', 1, 'Content for the second post', 1, '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL), - (3, '2024-03-05 21:52:06.919336+00', 'Math course', 3, 'math course for k12', 1, '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL), - (5, '2024-03-05 21:53:47.560102+00', 'Geography course', 2, 'k10 geography course', 1, '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL), - (6, '2024-03-05 21:54:44.695358+00', 'programming course ', 3, 'learn programming', 1, '1caa66a7-d9a2-462f-93c4-e65946d61c02', NULL); +INSERT INTO "public"."seller_post" ("id", "created_at", "title", "product_category", "content", "location", "user_id", "image_urls", "stripe_product_id", "stripe_price_id") VALUES + (1, '2024-03-05 15:38:02.509065+00', 'Test Post 1', 1, 'Content of the first post', 1, '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, 'prod_PihVI0liGFkala', 'price_1OtG6cBRZLMDvS4Ri22IpzGq'), + (2, '2024-03-05 15:40:05.243892+00', 'Test Post 2', 1, 'Content for the second post', 1, '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, NULL, NULL), + (3, '2024-03-05 21:52:06.919336+00', 'Math course', 3, 'math course for k12', 1, '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, NULL, NULL), + (5, '2024-03-05 21:53:47.560102+00', 'Geography course', 2, 'k10 geography course', 1, '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, 'prod_PihF0aDvvT4PeU', 'price_1OtFqrBRZLMDvS4RK5Ajf7na'), + (6, '2024-03-05 21:54:44.695358+00', 'programming course ', 3, 'learn programming', 1, '1caa66a7-d9a2-462f-93c4-e65946d61c02', NULL, NULL, NULL); -- From 58087d6f5aea34498563d5d30a7dc64d4636f87e Mon Sep 17 00:00:00 2001 From: r-southworth Date: Tue, 19 Mar 2024 17:50:08 -0400 Subject: [PATCH 03/18] move files and start cart page --- src/components/common/Header.astro | 6 +- .../common/{ => cart}/AddToCartButton.tsx | 4 +- src/components/common/{ => cart}/Cart.tsx | 10 +- src/components/common/cart/ViewCart.tsx | 134 ++++++++++++++++++ src/components/services/ViewCard.tsx | 2 +- src/i18n/UI/English.ts | 2 + src/i18n/UI/French.ts | 2 + src/i18n/UI/Spanish.ts | 2 + src/i18n/uiType.ts | 2 + src/pages/cart.astro | 21 +++ 10 files changed, 175 insertions(+), 10 deletions(-) rename src/components/common/{ => cart}/AddToCartButton.tsx (94%) rename src/components/common/{ => cart}/Cart.tsx (89%) create mode 100644 src/components/common/cart/ViewCart.tsx create mode 100644 src/pages/cart.astro diff --git a/src/components/common/Header.astro b/src/components/common/Header.astro index 8595bb3a..624eaba3 100644 --- a/src/components/common/Header.astro +++ b/src/components/common/Header.astro @@ -7,7 +7,7 @@ import { getLangFromUrl, useTranslations } from "../../i18n/utils"; import ThemeIcon from "./ThemeIcon.astro"; import { ProfileBtn } from "./ProfileBtn"; -import { Cart } from "./Cart"; +import { Cart } from "@components/common/cart/Cart"; import { CreatePostsRouting } from "../posts/CreatePostsRouting"; import { ClientRouting } from "../users/ClientRouting"; @@ -41,13 +41,13 @@ const { links = [] } = Astro.props; - + diff --git a/src/components/common/AddToCartButton.tsx b/src/components/common/cart/AddToCartButton.tsx similarity index 94% rename from src/components/common/AddToCartButton.tsx rename to src/components/common/cart/AddToCartButton.tsx index 365a18c2..ef8e3466 100644 --- a/src/components/common/AddToCartButton.tsx +++ b/src/components/common/cart/AddToCartButton.tsx @@ -1,7 +1,7 @@ import { Show, createResource, createSignal, onMount } from "solid-js"; import type { Component } from "solid-js"; -import { getLangFromUrl, useTranslations } from "../../i18n/utils"; -import cart from "../../assets/shopping-cart.svg"; +import { getLangFromUrl, useTranslations } from "@i18n/utils"; +import cart from "@assets/shopping-cart.svg"; const lang = getLangFromUrl(new URL(window.location.href)); const t = useTranslations(lang); diff --git a/src/components/common/Cart.tsx b/src/components/common/cart/Cart.tsx similarity index 89% rename from src/components/common/Cart.tsx rename to src/components/common/cart/Cart.tsx index fe4e42b8..c3391e78 100644 --- a/src/components/common/Cart.tsx +++ b/src/components/common/cart/Cart.tsx @@ -1,6 +1,6 @@ import { Show, createEffect, createResource, createSignal, onMount } from "solid-js"; -import { getLangFromUrl, useTranslations } from "../../i18n/utils"; -import cart from "../../assets/shopping-cart.svg"; +import { getLangFromUrl, useTranslations } from "@i18n/utils"; +import cart from "@assets/shopping-cart.svg"; const lang = getLangFromUrl(new URL(window.location.href)); const t = useTranslations(lang); @@ -36,7 +36,9 @@ export const Cart = () => { }); function clickHandler() { - setItems(JSON.parse(localStorage.order)); + if (localStorage.order) { + setItems(JSON.parse(localStorage.order)); + } const listShow = document.getElementById("cartItems"); if (listShow?.classList.contains("hidden")) { listShow?.classList.remove("hidden"); @@ -105,7 +107,7 @@ export const Cart = () => { > 0}> -
{totalItems()}
+
{totalItems()}
{ + const [items, setItems] = createSignal([]); + const [totalItems, setTotalItems] = createSignal(0); + + onMount(() => { + try { + setItems(JSON.parse(localStorage.order)); + console.log("Items: " + items().map((item: Item) => item.description)); + items().forEach((item) => { + setTotalItems(totalItems() + item.quantity); + }); + } catch (_) { + setItems([]); + } + }); + + createEffect(() => { + setTotalItems(0); + items().forEach((item) => { + setTotalItems(totalItems() + item.quantity); + }); + }); + + function clickHandler() { + setItems(JSON.parse(localStorage.order)); + const listShow = document.getElementById("cartItems"); + if (listShow?.classList.contains("hidden")) { + listShow?.classList.remove("hidden"); + } else { + listShow?.classList.add("hidden"); + } + } + + function goToCart() { + window.location.href = `/${lang}/cart`; + } + + function shoppingCart() { + if (items().length > 0) { + let total = 0; + { + console.log("items in cart: " + items().length); + } + items().forEach((item: Item) => { + total += item.price * item.quantity; + }); + return ( +
+
Cart
+
    + {items().map((item: Item) => ( +
    +
    + {item.description} +
    +
    + ${item.price.toFixed(2)} +
    +
    {item.quantity}
    +
    + ))} +
+
+ {/* TODO: Internationalize */} +
Subtotal:
+
${total}
+
+ Taxes calculated at checkout +
+
+
+ ); + } else { + return ( +
+
Cart is empty
+ +
+ {/* TODO: Internationalize */} +
Total:
+
$0.00
+
+
+ ); + } + } + + // ADD EMAIL TO SEND FOR CONTACT US + + return ( +
+ {/* */} +
+
{shoppingCart()}
+ {/* TODO: Style and Internationalize */} +
+ +
+
+
+ ); +}; diff --git a/src/components/services/ViewCard.tsx b/src/components/services/ViewCard.tsx index fce126a9..366ca6e6 100644 --- a/src/components/services/ViewCard.tsx +++ b/src/components/services/ViewCard.tsx @@ -5,7 +5,7 @@ import supabase from "../../lib/supabaseClient"; import { getLangFromUrl, useTranslations } from "../../i18n/utils"; import { SocialMediaShares } from "../posts/SocialMediaShares"; import SocialModal from "../posts/SocialModal"; -import { Cart } from "../common/AddToCartButton"; +import { Cart } from "../common/cart/AddToCartButton"; const lang = getLangFromUrl(new URL(window.location.href)); const t = useTranslations(lang); diff --git a/src/i18n/UI/English.ts b/src/i18n/UI/English.ts index f23b03f6..49bf28ca 100644 --- a/src/i18n/UI/English.ts +++ b/src/i18n/UI/English.ts @@ -27,6 +27,7 @@ export const English = { fullPost: "View Full Post", offline: 'Offline', faq: "Frequently Asked Questions", + viewCart: "My Cart", }, pageDescriptions: { @@ -51,6 +52,7 @@ export const English = { impact: "Explore the meaningful impact of TodoServis. Discover how we're making a difference and learn more about our projects, and contributions to positive change.", fullPost: "Read more about this service and contact the provider if interested. Explore additional images and the full description of the service.", faq: "Explore our comprehensive FAQ page for answers to common questions about our services, policies, and more.", + viewCart: "View your cart and checkout with ease.", }, buttons: { diff --git a/src/i18n/UI/French.ts b/src/i18n/UI/French.ts index 1445647f..ab5735c5 100644 --- a/src/i18n/UI/French.ts +++ b/src/i18n/UI/French.ts @@ -27,6 +27,7 @@ export const French = { fullPost: "Voir le message complet", offline: 'Hors ligne', faq: "Questions fréquemment posées", + viewCart: "Mon panier", }, pageDescriptions: { @@ -51,6 +52,7 @@ export const French = { impact: "Découvrez l’impact significatif de TodoServis. Découvrez comment nous faisons la différence et apprenez-en davantage sur nos projets et nos contributions à un changement positif.", fullPost: "En savoir plus sur ce service et contacter le fournisseur si vous êtes intéressé. Découvrez des images supplémentaires et la description complète du service.", faq: "Explorez notre page FAQ complète pour obtenir des réponses aux questions courantes sur nos services, nos politiques et bien plus encore.", + viewCart: "Consultez votre panier et passez à la caisse en toute simplicité.", }, buttons: { diff --git a/src/i18n/UI/Spanish.ts b/src/i18n/UI/Spanish.ts index f23668f9..ed479f41 100644 --- a/src/i18n/UI/Spanish.ts +++ b/src/i18n/UI/Spanish.ts @@ -27,6 +27,7 @@ export const Spanish = { fullPost: "Ver publicación completa", offline: 'Desconectado', faq: "Preguntas frecuentes", + viewCart: "Mi carrito", }, pageDescriptions: { @@ -51,6 +52,7 @@ export const Spanish = { impact: "Explore el impacto significativo de TodoServis. Descubra cómo estamos marcando la diferencia y obtenga más información sobre nuestros proyectos y nuestras contribuciones al cambio positivo.", fullPost: "Lea más sobre este servicio y comuníquese con el proveedor si está interesado. Explora imágenes adicionales y la descripción completa del servicio.", faq: "Explore nuestra completa página de preguntas frecuentes para obtener respuestas a preguntas comunes sobre nuestros servicios, políticas y más.", + viewCart: "Vea su carrito y realice el pago con facilidad.", }, buttons: { diff --git a/src/i18n/uiType.ts b/src/i18n/uiType.ts index 2c92be08..118481e3 100644 --- a/src/i18n/uiType.ts +++ b/src/i18n/uiType.ts @@ -27,6 +27,7 @@ export interface uiObject { fullPost: string, offline: string, faq: string, + viewCart: string, }, pageDescriptions: { @@ -51,6 +52,7 @@ export interface uiObject { impact: string, fullPost: string, faq: string, + viewCart: string, }, buttons: { diff --git a/src/pages/cart.astro b/src/pages/cart.astro new file mode 100644 index 00000000..339d8942 --- /dev/null +++ b/src/pages/cart.astro @@ -0,0 +1,21 @@ +--- +import PageLayout from "@layouts/PageLayout.astro"; +import { CartView } from "@components/common/cart/ViewCart"; +import { getLangFromUrl, useTranslations } from "@i18n/utils"; + +const lang = getLangFromUrl(Astro.url); +const t = useTranslations(lang); +--- + + +
+
+ +
+
+
From 79323b4cafb424bf0520c1db4a5b36d22f9b2d77 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Wed, 20 Mar 2024 15:37:04 -0400 Subject: [PATCH 04/18] cart not working saving progress --- .../common/cart/AddToCartButton.tsx | 52 +++---- src/components/common/cart/Cart.tsx | 116 ++++++++------ src/components/common/cart/CartCard.tsx | 146 ++++++++++++++++++ src/components/common/cart/ViewCart.tsx | 112 +++++++------- 4 files changed, 291 insertions(+), 135 deletions(-) create mode 100644 src/components/common/cart/CartCard.tsx diff --git a/src/components/common/cart/AddToCartButton.tsx b/src/components/common/cart/AddToCartButton.tsx index ef8e3466..ea77dd11 100644 --- a/src/components/common/cart/AddToCartButton.tsx +++ b/src/components/common/cart/AddToCartButton.tsx @@ -1,4 +1,5 @@ import { Show, createResource, createSignal, onMount } from "solid-js"; +import { createStore } from "solid-js/store"; import type { Component } from "solid-js"; import { getLangFromUrl, useTranslations } from "@i18n/utils"; import cart from "@assets/shopping-cart.svg"; @@ -22,45 +23,30 @@ interface Props { product_id: string; } -//TODO Remove this test code -if (localStorage.order === undefined || localStorage.order === null) { - localStorage.order = JSON.stringify([ - { - description: "t-shirt", - price: 10, - price_id: "", - product_id: "", - quantity: 3, - }, - { - description: "watch", - price: 20.99, - price_id: "", - product_id: "", - quantity: 1, - }, - ]); -} +export const [items, setItems] = createStore([]); export const Cart: Component = (props: Props) => { - const [items, setItems] = createSignal([]); + + const storedItems = localStorage.getItem("cartItems"); onMount(() => { - try { - setItems(JSON.parse(localStorage.order)); - } catch (_) { - setItems([]); + if (storedItems) { + setItems(JSON.parse(storedItems)); } }); - function clickHandler() { + function clickHandler(e: Event) { + e.preventDefault(); + e.stopPropagation(); + let itemInCart = false; - items().forEach((item: Item) => { + const updatedItems = items.map((item: Item) => { if (item.product_id === props.product_id) { - item.quantity += props.quantity; itemInCart = true; + return { ...item, quantity: item.quantity + props.quantity }; } + return item; }); if (!itemInCart) { @@ -71,18 +57,18 @@ export const Cart: Component = (props: Props) => { quantity: props.quantity, product_id: props.product_id, }; - setItems([...items(), newItem]); + setItems([...updatedItems, newItem]); + } else { + setItems (updatedItems); } - localStorage.setItem("order", JSON.stringify(items())); - console.log("Items: " + items().map((item: Item) => item.description)); - console.log("Order: " + localStorage.order); + console.log(items) } return ( -
+
{
{shoppingCart()}
{/* TODO: Style and Internationalize */}
- +
diff --git a/src/components/common/cart/CartCard.tsx b/src/components/common/cart/CartCard.tsx new file mode 100644 index 00000000..019fe5b8 --- /dev/null +++ b/src/components/common/cart/CartCard.tsx @@ -0,0 +1,146 @@ +import type { Component } from "solid-js"; +import { createSignal, createEffect } from "solid-js"; +import supabase from "@lib/supabaseClient"; +import { getLangFromUrl, useTranslations } from "@i18n/utils"; +import SocialModal from "@components/posts/SocialModal"; + +const lang = getLangFromUrl(new URL(window.location.href)); +const t = useTranslations(lang); + +interface ItemDetails { + content: string; + title: string; + seller_name: string; + image_urls: string | null; + price?: number; + price_id: string; + quantity?: number; + product_id: string; +} + +interface Props { + // Define the type for the filterPosts prop + items: Array; +} + +export const CartCard: Component = (props) => { + const [newItems, setNewItems] = createSignal>([]); + + createEffect(async () => { + if (props.items) { + const updatedItems = await Promise.all( + props.items.map(async (item: any) => { + item.image_urls + ? (item.image_url = await downloadImage( + item.image_urls.split(",")[0] + )) + : (item.image_url = null); + // Set the default quantity to 1 This should be replaced with the quantity from the quantity counter in the future + item.quantity = 1; + return item; + }) + ); + + setNewItems(updatedItems); + } + }); + + const downloadImage = async (path: string) => { + try { + const { data, error } = await supabase.storage + .from("post.image") + .download(path); + if (error) { + throw error; + } + const url = URL.createObjectURL(data); + return url; + } catch (error) { + if (error instanceof Error) { + console.log("Error downloading image: ", error.message); + } + } + }; + + return ( + + ); +}; diff --git a/src/components/common/cart/ViewCart.tsx b/src/components/common/cart/ViewCart.tsx index fed8e028..4f2b0351 100644 --- a/src/components/common/cart/ViewCart.tsx +++ b/src/components/common/cart/ViewCart.tsx @@ -7,6 +7,9 @@ import { } from "solid-js"; import { getLangFromUrl, useTranslations } from "@i18n/utils"; import cart from "@assets/shopping-cart.svg"; +import supabase from "@lib/supabaseClient"; +import { CartCard } from "@components/common/cart/CartCard"; +import { items } from "@components/common/cart/AddToCartButton"; const lang = getLangFromUrl(new URL(window.location.href)); const t = useTranslations(lang); @@ -15,71 +18,75 @@ interface Item { description: string; price: number; price_id: string; + product_id: string; quantity: number; } +interface ItemDetails { + content: string; + title: string; + seller_name: string; + image_urls: string | null; + price?: number; + price_id: string; + quantity?: number; + product_id: string; +} + export const CartView = () => { - const [items, setItems] = createSignal([]); const [totalItems, setTotalItems] = createSignal(0); + const [itemsDetails, setItemsDetails] = createSignal([]); - onMount(() => { + onMount(async () => { try { - setItems(JSON.parse(localStorage.order)); - console.log("Items: " + items().map((item: Item) => item.description)); - items().forEach((item) => { - setTotalItems(totalItems() + item.quantity); - }); + console.log("Cart Items: " + items.map((item: Item) => item.description)); + items.forEach(async (item) => { + if (item.product_id !== "") { + const { data , error } = await supabase.from("sellerposts").select("title, content, image_urls, price_id, product_id, seller_name, seller_id").eq("product_id", item.product_id); + if (data){ + const currentItem: ItemDetails = data[0]; + currentItem.quantity = item.quantity; + currentItem.price = item.price; + console.log(currentItem); + setItemsDetails([...itemsDetails(), currentItem]); + } + if (error) { + console.log("Supabase error: " + error.code + " " + error.message); + } + } + }); } catch (_) { - setItems([]); + // setItems([]); + console.log("Cart Error"); } }); createEffect(() => { - setTotalItems(0); - items().forEach((item) => { - setTotalItems(totalItems() + item.quantity); + let count = 0; + items.forEach((item) => { + count += item.quantity; }); - }); + setTotalItems(count); + }) - function clickHandler() { - setItems(JSON.parse(localStorage.order)); - const listShow = document.getElementById("cartItems"); - if (listShow?.classList.contains("hidden")) { - listShow?.classList.remove("hidden"); - } else { - listShow?.classList.add("hidden"); - } - } - - function goToCart() { + function goToCheckout() { window.location.href = `/${lang}/cart`; } function shoppingCart() { - if (items().length > 0) { + if (items.length > 0) { let total = 0; { - console.log("items in cart: " + items().length); + console.log("items in cart: " + items.length); } - items().forEach((item: Item) => { + items.forEach((item: Item) => { total += item.price * item.quantity; }); return (
-
Cart
-
    - {items().map((item: Item) => ( -
    -
    - {item.description} -
    -
    - ${item.price.toFixed(2)} -
    -
    {item.quantity}
    -
    - ))} -
+ {/* TODO: Internationalize */} +
My Cart
+
{/* TODO: Internationalize */}
Subtotal:
@@ -108,27 +115,16 @@ export const CartView = () => { // ADD EMAIL TO SEND FOR CONTACT US return ( -
- {/* */} -
+
+
{shoppingCart()}
- {/* TODO: Style and Internationalize */} -
-
+
+
-
); }; From c67fa2b8b66b32163833607c4f82a70822ae63f5 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Wed, 20 Mar 2024 17:40:02 -0400 Subject: [PATCH 05/18] styling for cart page --- src/assets/shopping-cart.svg | 11 ++-- src/components/common/cart/Cart.tsx | 37 +++++++++++-- src/components/common/cart/CartCard.tsx | 26 +++------- src/components/common/cart/ViewCart.tsx | 69 ++++++++++++++----------- src/pages/cart.astro | 2 +- 5 files changed, 85 insertions(+), 60 deletions(-) diff --git a/src/assets/shopping-cart.svg b/src/assets/shopping-cart.svg index 8eeb53b3..4522c836 100644 --- a/src/assets/shopping-cart.svg +++ b/src/assets/shopping-cart.svg @@ -1,7 +1,6 @@ - - - - - - + + + + + \ No newline at end of file diff --git a/src/components/common/cart/Cart.tsx b/src/components/common/cart/Cart.tsx index 5809b991..7c3684d8 100644 --- a/src/components/common/cart/Cart.tsx +++ b/src/components/common/cart/Cart.tsx @@ -7,7 +7,6 @@ import { onCleanup, } from "solid-js"; import { getLangFromUrl, useTranslations } from "@i18n/utils"; -import cart from "@assets/shopping-cart.svg"; import { items, setItems } from "@components/common/cart/AddToCartButton"; const lang = getLangFromUrl(new URL(window.location.href)); @@ -106,7 +105,7 @@ export const Cart = () => {
); - } else { + } else { return (
Cart is empty
@@ -131,9 +130,39 @@ export const Cart = () => { //TODO: Internationalize Aria Label aria-label="Cart" > - + + + + + + 0}> -
+
{totalItems()}
diff --git a/src/components/common/cart/CartCard.tsx b/src/components/common/cart/CartCard.tsx index 019fe5b8..03af80e0 100644 --- a/src/components/common/cart/CartCard.tsx +++ b/src/components/common/cart/CartCard.tsx @@ -66,10 +66,10 @@ export const CartCard: Component = (props) => {
diff --git a/src/components/common/cart/ViewCart.tsx b/src/components/common/cart/ViewCart.tsx index 4f2b0351..7a6e98eb 100644 --- a/src/components/common/cart/ViewCart.tsx +++ b/src/components/common/cart/ViewCart.tsx @@ -26,6 +26,7 @@ interface ItemDetails { content: string; title: string; seller_name: string; + seller_id: string; image_urls: string | null; price?: number; price_id: string; @@ -36,25 +37,31 @@ interface ItemDetails { export const CartView = () => { const [totalItems, setTotalItems] = createSignal(0); const [itemsDetails, setItemsDetails] = createSignal([]); + const [cartTotal, setCartTotal] = createSignal(0); onMount(async () => { try { console.log("Cart Items: " + items.map((item: Item) => item.description)); items.forEach(async (item) => { if (item.product_id !== "") { - const { data , error } = await supabase.from("sellerposts").select("title, content, image_urls, price_id, product_id, seller_name, seller_id").eq("product_id", item.product_id); - if (data){ - const currentItem: ItemDetails = data[0]; - currentItem.quantity = item.quantity; - currentItem.price = item.price; - console.log(currentItem); - setItemsDetails([...itemsDetails(), currentItem]); - } - if (error) { - console.log("Supabase error: " + error.code + " " + error.message); + const { data, error } = await supabase + .from("sellerposts") + .select( + "title, content, image_urls, price_id, product_id, seller_name, seller_id" + ) + .eq("product_id", item.product_id); + if (data) { + const currentItem: ItemDetails = data[0]; + currentItem.quantity = item.quantity; + currentItem.price = item.price; + console.log(currentItem); + setItemsDetails([...itemsDetails(), currentItem]); + } + if (error) { + console.log("Supabase error: " + error.code + " " + error.message); + } } - } - }); + }); } catch (_) { // setItems([]); console.log("Cart Error"); @@ -67,7 +74,7 @@ export const CartView = () => { count += item.quantity; }); setTotalItems(count); - }) + }); function goToCheckout() { window.location.href = `/${lang}/cart`; @@ -82,31 +89,23 @@ export const CartView = () => { items.forEach((item: Item) => { total += item.price * item.quantity; }); + setCartTotal(total); return ( -
+
{/* TODO: Internationalize */} -
My Cart
- -
- {/* TODO: Internationalize */} -
Subtotal:
-
${total}
-
- Taxes calculated at checkout -
+
My Cart
+
+
+
); } else { + setCartTotal(0); return ( + //TODO: Revisit Styling
Cart is empty
- -
- {/* TODO: Internationalize */} -
Total:
-
$0.00
-
); } @@ -120,11 +119,23 @@ export const CartView = () => {
{shoppingCart()}
+
Order Summary
+
+
+
+
+ Subtotal ({totalItems()} items){" "} +
+
${cartTotal()}
+
+
+
+
); }; diff --git a/src/pages/cart.astro b/src/pages/cart.astro index 339d8942..cabebf21 100644 --- a/src/pages/cart.astro +++ b/src/pages/cart.astro @@ -14,7 +14,7 @@ const t = useTranslations(lang);
-
+
From 3ac6a14271ed5670b01e9ab99eee1781857f656d Mon Sep 17 00:00:00 2001 From: r-southworth Date: Wed, 20 Mar 2024 18:04:37 -0400 Subject: [PATCH 06/18] working on card layout --- src/components/common/cart/CartCard.tsx | 98 +++++++++++++------------ 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/src/components/common/cart/CartCard.tsx b/src/components/common/cart/CartCard.tsx index 03af80e0..2f224399 100644 --- a/src/components/common/cart/CartCard.tsx +++ b/src/components/common/cart/CartCard.tsx @@ -67,63 +67,69 @@ export const CartCard: Component = (props) => {
    {newItems().map((item: any) => (
  • - -
    -
    - {item.image_url ? ( - { - ) : ( -
    +
    + {item.image_url ? ( + { + ) : ( + - )} -
    + + + )} +
    -
    -
    -
    -

    +

    +
    +
    +

    {item.title} -

    -
    - -

    - {item.seller_name}

    - +
    + +

    - +
    +
    + {/* Quantity */} +
    +
    + {/* Price */} +
    +
    + {/* Remove All from Cart */}
    +
  • ))}
From bcf8e076bc54a19425c364a011b037ef49d64764 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Mon, 25 Mar 2024 17:24:58 -0400 Subject: [PATCH 07/18] cart, add to cart, quantity, cart Card working --- .../common/cart/AddToCartButton.tsx | 9 +- src/components/common/cart/CartCard.tsx | 53 +++++++++++- src/components/common/cart/Quantity.tsx | 82 +++++++++++++++++++ src/components/common/cart/ViewCart.tsx | 1 + src/components/services/ViewCard.tsx | 21 ++++- 5 files changed, 154 insertions(+), 12 deletions(-) create mode 100644 src/components/common/cart/Quantity.tsx diff --git a/src/components/common/cart/AddToCartButton.tsx b/src/components/common/cart/AddToCartButton.tsx index ea77dd11..4fef33b6 100644 --- a/src/components/common/cart/AddToCartButton.tsx +++ b/src/components/common/cart/AddToCartButton.tsx @@ -21,12 +21,12 @@ interface Props { price_id: string; quantity: number; product_id: string; + buttonClick: (event: Event) => void; } export const [items, setItems] = createStore([]); -export const Cart: Component = (props: Props) => { - +export const AddToCart: Component = (props: Props) => { const storedItems = localStorage.getItem("cartItems"); onMount(() => { @@ -59,10 +59,11 @@ export const Cart: Component = (props: Props) => { }; setItems([...updatedItems, newItem]); } else { - setItems (updatedItems); + setItems(updatedItems); } - console.log(items) + props.buttonClick(e); + console.log(items); } return ( diff --git a/src/components/common/cart/CartCard.tsx b/src/components/common/cart/CartCard.tsx index 2f224399..e6737d81 100644 --- a/src/components/common/cart/CartCard.tsx +++ b/src/components/common/cart/CartCard.tsx @@ -2,11 +2,20 @@ import type { Component } from "solid-js"; import { createSignal, createEffect } from "solid-js"; import supabase from "@lib/supabaseClient"; import { getLangFromUrl, useTranslations } from "@i18n/utils"; -import SocialModal from "@components/posts/SocialModal"; +import { Quantity } from "@components/common/cart/Quantity"; +import { items, setItems } from "@components/common/cart/AddToCartButton"; const lang = getLangFromUrl(new URL(window.location.href)); const t = useTranslations(lang); +interface Item { + description: string; + price: number; + price_id: string; + quantity: number; + product_id: string; +} + interface ItemDetails { content: string; title: string; @@ -24,7 +33,8 @@ interface Props { } export const CartCard: Component = (props) => { - const [newItems, setNewItems] = createSignal>([]); + const [newItems, setNewItems] = createSignal>([]); + const [quantity, setQuantity] = createSignal(0); createEffect(async () => { if (props.items) { @@ -36,7 +46,7 @@ export const CartCard: Component = (props) => { )) : (item.image_url = null); // Set the default quantity to 1 This should be replaced with the quantity from the quantity counter in the future - item.quantity = 1; + // item.quantity = 1; return item; }) ); @@ -45,6 +55,40 @@ export const CartCard: Component = (props) => { } }); + const updateQuantity = async (quantity: number, product_id?: string) => { + console.log("Card Card Update Quantity") + setQuantity(quantity); + if(product_id){ + const updatedItems: Array = await Promise.all( + props.items.map(async (item: ItemDetails) => { + if (item.product_id === product_id) { + item.quantity = quantity; + } + return item; + }) + ) + setNewItems(updatedItems); + console.log("Updated Items: " + updatedItems); + + const cartItems: Array = await Promise.all( + props.items.map(async (oldItem: ItemDetails) => { + let item: Item = {description: oldItem.title, + price: oldItem.price? oldItem.price : 0, + price_id: oldItem.price_id, + quantity: oldItem.quantity? oldItem.quantity : 0, + product_id: oldItem.product_id}; + if (oldItem.product_id === product_id) { + item.quantity = quantity; + } + return item; + }) + ) + + setItems(cartItems); + console.log("Cart Store: " + cartItems); + } + }; + const downloadImage = async (path: string) => { try { const { data, error } = await supabase.storage @@ -118,8 +162,9 @@ export const CartCard: Component = (props) => { innerHTML={item.content} >

-
+
{/* Quantity */} +
{/* Price */} diff --git a/src/components/common/cart/Quantity.tsx b/src/components/common/cart/Quantity.tsx new file mode 100644 index 00000000..b4bd5717 --- /dev/null +++ b/src/components/common/cart/Quantity.tsx @@ -0,0 +1,82 @@ +import { createEffect, createSignal, onMount } from "solid-js"; +import { createStore } from "solid-js/store"; +import type { Component } from "solid-js"; +import { getLangFromUrl, useTranslations } from "@i18n/utils"; +import { items, setItems } from "@components/common/cart/AddToCartButton"; +import { e } from "dist/_astro/web.DzrY7x_K"; + +const lang = getLangFromUrl(new URL(window.location.href)); +const t = useTranslations(lang); + +interface Props { + product_id?: string; + quantity: number; + updateQuantity: (quantity: number, product_id?: string) => void; +} + +export const Quantity: Component = (props: Props) => { + const [updateQuantity, setUpdateQuantity] = createSignal(1); + + onMount(() => { + if (props.quantity) { + setUpdateQuantity(props.quantity); + } else setUpdateQuantity(1); + }); + + function inputHandler(e: Event) { + e.preventDefault(); + e.stopPropagation(); + + setUpdateQuantity(Number((e.target as HTMLInputElement).value)); + props.updateQuantity(Number((e.target as HTMLInputElement).value), props.product_id?.toString()); + } + + function increase(e: Event) { + e.preventDefault(); + e.stopPropagation(); + + setUpdateQuantity(updateQuantity() + 1); + props.updateQuantity(updateQuantity(), props.product_id?.toString()); + } + + function decrease(e: Event) { + e.preventDefault(); + e.stopPropagation(); + + setUpdateQuantity(updateQuantity() - 1); + props.updateQuantity(updateQuantity(), props.product_id?.toString()); + } + + return ( +
+ + inputHandler(e)} + value={updateQuantity()} + onclick={(e) => { + e.preventDefault(); + e.stopPropagation(); + }} + > + +
+ ); +}; diff --git a/src/components/common/cart/ViewCart.tsx b/src/components/common/cart/ViewCart.tsx index 7a6e98eb..7e619952 100644 --- a/src/components/common/cart/ViewCart.tsx +++ b/src/components/common/cart/ViewCart.tsx @@ -51,6 +51,7 @@ export const CartView = () => { ) .eq("product_id", item.product_id); if (data) { + console.log(data[0]); const currentItem: ItemDetails = data[0]; currentItem.quantity = item.quantity; currentItem.price = item.price; diff --git a/src/components/services/ViewCard.tsx b/src/components/services/ViewCard.tsx index 366ca6e6..00d06c81 100644 --- a/src/components/services/ViewCard.tsx +++ b/src/components/services/ViewCard.tsx @@ -5,7 +5,9 @@ import supabase from "../../lib/supabaseClient"; import { getLangFromUrl, useTranslations } from "../../i18n/utils"; import { SocialMediaShares } from "../posts/SocialMediaShares"; import SocialModal from "../posts/SocialModal"; -import { Cart } from "../common/cart/AddToCartButton"; +import { AddToCart } from "../common/cart/AddToCartButton"; +import { Quantity } from "@components/common/cart/Quantity"; + const lang = getLangFromUrl(new URL(window.location.href)); const t = useTranslations(lang); @@ -34,6 +36,7 @@ interface Props { export const ViewCard: Component = (props) => { const [newPosts, setNewPosts] = createSignal>([]); + const [quantity, setQuantity] = createSignal(1); createEffect(async () => { if (props.posts) { @@ -44,7 +47,7 @@ export const ViewCard: Component = (props) => { post.image_urls.split(",")[0] )) : (post.image_url = null); - // Set the default quantity to 1 This should be replaced with the quantity from the quantity counter in the future + // Set the default quantity to 1 post.quantity = 1; return post; }) @@ -54,6 +57,14 @@ export const ViewCard: Component = (props) => { } }); + const updateQuantity = (quantity: number) => { + setQuantity(quantity); + }; + + const resetQuantity = () => { + setQuantity(1); + }; + const downloadImage = async (path: string) => { try { const { data, error } = await supabase.storage @@ -143,13 +154,15 @@ export const ViewCard: Component = (props) => { />
- +
From 55862750c4fc8d304edfd1ed5603cb26a8862a6c Mon Sep 17 00:00:00 2001 From: r-southworth Date: Tue, 26 Mar 2024 09:42:07 -0400 Subject: [PATCH 08/18] start work on delete post, does not refresh full cart view currently --- src/components/common/cart/CartCard.tsx | 59 ++++++++++++++++++++----- src/components/common/cart/ViewCart.tsx | 23 ++++++++-- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/components/common/cart/CartCard.tsx b/src/components/common/cart/CartCard.tsx index e6737d81..ffc55651 100644 --- a/src/components/common/cart/CartCard.tsx +++ b/src/components/common/cart/CartCard.tsx @@ -56,9 +56,9 @@ export const CartCard: Component = (props) => { }); const updateQuantity = async (quantity: number, product_id?: string) => { - console.log("Card Card Update Quantity") + console.log("Card Card Update Quantity"); setQuantity(quantity); - if(product_id){ + if (product_id) { const updatedItems: Array = await Promise.all( props.items.map(async (item: ItemDetails) => { if (item.product_id === product_id) { @@ -66,29 +66,38 @@ export const CartCard: Component = (props) => { } return item; }) - ) + ); setNewItems(updatedItems); console.log("Updated Items: " + updatedItems); - + const cartItems: Array = await Promise.all( props.items.map(async (oldItem: ItemDetails) => { - let item: Item = {description: oldItem.title, - price: oldItem.price? oldItem.price : 0, + let item: Item = { + description: oldItem.title, + price: oldItem.price ? oldItem.price : 0, price_id: oldItem.price_id, - quantity: oldItem.quantity? oldItem.quantity : 0, - product_id: oldItem.product_id}; + quantity: oldItem.quantity ? oldItem.quantity : 0, + product_id: oldItem.product_id, + }; if (oldItem.product_id === product_id) { item.quantity = quantity; - } + } return item; }) - ) + ); setItems(cartItems); console.log("Cart Store: " + cartItems); } }; + const removeItem = async (product_id: string) => { + let currentItems = items; + currentItems = currentItems.filter((item) => item.product_id !== product_id); + setItems(currentItems) + console.log("Items" + items.map((item) => item.description)) + }; + const downloadImage = async (path: string) => { try { const { data, error } = await supabase.storage @@ -164,13 +173,39 @@ export const CartCard: Component = (props) => {
{/* Quantity */} - +
{/* Price */} + {"$" + (item.price * item.quantity).toFixed(2)}
-
+
{/* Remove All from Cart */} +
diff --git a/src/components/common/cart/ViewCart.tsx b/src/components/common/cart/ViewCart.tsx index 7e619952..8e58176e 100644 --- a/src/components/common/cart/ViewCart.tsx +++ b/src/components/common/cart/ViewCart.tsx @@ -40,10 +40,16 @@ export const CartView = () => { const [cartTotal, setCartTotal] = createSignal(0); onMount(async () => { + await fetchItemDetails(); + }) + + const fetchItemDetails = async () => { try { console.log("Cart Items: " + items.map((item: Item) => item.description)); + const details: ItemDetails[] = []; items.forEach(async (item) => { - if (item.product_id !== "") { + if (item.product_id !== "" && + !itemsDetails().some((items) => items.product_id === item.product_id)) { const { data, error } = await supabase .from("sellerposts") .select( @@ -56,18 +62,28 @@ export const CartView = () => { currentItem.quantity = item.quantity; currentItem.price = item.price; console.log(currentItem); - setItemsDetails([...itemsDetails(), currentItem]); + details.push(currentItem); } if (error) { console.log("Supabase error: " + error.code + " " + error.message); } + } else if (item.product_id !== "" && + itemsDetails().some((items) => items.product_id === item.product_id)) { + const index = itemsDetails().findIndex((itemDetail) => itemDetail.product_id === item.product_id); + if (index !== -1) { + const updatedItem: ItemDetails = { ...itemsDetails()[index] }; + details.push(updatedItem) } + } }); + console.log("Details" + details) + setItemsDetails([]) + setItemsDetails(details) } catch (_) { // setItems([]); console.log("Cart Error"); } - }); + }; createEffect(() => { let count = 0; @@ -98,7 +114,6 @@ export const CartView = () => {
-
); } else { From c28c2bd3f9b570aeebfd1b864ac8db125e554b84 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Tue, 26 Mar 2024 16:41:05 -0400 Subject: [PATCH 09/18] add header logo --- src/assets/LearnGroveLogoBWNoText.svg | 63 +++++++++++++++++++++++++++ src/components/common/Header.astro | 40 ++++++++++++----- 2 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 src/assets/LearnGroveLogoBWNoText.svg diff --git a/src/assets/LearnGroveLogoBWNoText.svg b/src/assets/LearnGroveLogoBWNoText.svg new file mode 100644 index 00000000..5b12519c --- /dev/null +++ b/src/assets/LearnGroveLogoBWNoText.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + diff --git a/src/components/common/Header.astro b/src/components/common/Header.astro index 624eaba3..f3ec3f14 100644 --- a/src/components/common/Header.astro +++ b/src/components/common/Header.astro @@ -38,16 +38,36 @@ const { links = [] } = Astro.props; ); From 4d2695036b780114b1f466cde0d498196e98d42b Mon Sep 17 00:00:00 2001 From: r-southworth Date: Tue, 26 Mar 2024 18:28:39 -0400 Subject: [PATCH 11/18] internationalization --- src/components/common/Dropdown.tsx | 8 +++-- src/components/common/Header.astro | 2 +- .../common/cart/AddToCartButton.tsx | 6 ++-- src/components/common/cart/Cart.tsx | 29 ++++++++----------- src/components/common/cart/CartCard.tsx | 3 +- src/components/common/cart/Quantity.tsx | 6 ++-- src/components/common/cart/ViewCart.tsx | 11 ++++--- .../posts/CreateStripeProductPrice.tsx | 2 +- src/components/users/ClientRouting.tsx | 2 +- .../users/provider/StripeButton.tsx | 13 ++++----- src/i18n/UI/English.ts | 23 ++++++++++++++- src/i18n/UI/French.ts | 23 ++++++++++++++- src/i18n/UI/Spanish.ts | 24 ++++++++++++++- src/i18n/uiType.ts | 23 ++++++++++++++- 14 files changed, 125 insertions(+), 50 deletions(-) diff --git a/src/components/common/Dropdown.tsx b/src/components/common/Dropdown.tsx index 9fd69799..90ea516d 100644 --- a/src/components/common/Dropdown.tsx +++ b/src/components/common/Dropdown.tsx @@ -1,5 +1,10 @@ import { createSignal, Show } from "solid-js"; import type { Component } from "solid-js"; +import { getLangFromUrl, useTranslations } from "../../i18n/utils"; + +// Internationalization +const lang = getLangFromUrl(new URL(window.location.href)); +const t = useTranslations(lang); //TODO: Upgrade to have an option for required and not let a user select nothing interface Props { @@ -29,8 +34,7 @@ const Dropdown: Component = (Props: Props) => { }} >
- {/* TODO:Internationalize */} - {Props.selectedOption || "Select an option"} + {Props.selectedOption || t("formLabels.dropdownDefault")}
{/* Dropdown icon */}
diff --git a/src/components/common/Header.astro b/src/components/common/Header.astro index f3ec3f14..50b992b7 100644 --- a/src/components/common/Header.astro +++ b/src/components/common/Header.astro @@ -84,7 +84,7 @@ const { links = [] } = Astro.props; diff --git a/src/components/common/cart/AddToCartButton.tsx b/src/components/common/cart/AddToCartButton.tsx index 4fef33b6..e4a919de 100644 --- a/src/components/common/cart/AddToCartButton.tsx +++ b/src/components/common/cart/AddToCartButton.tsx @@ -71,11 +71,9 @@ export const AddToCart: Component = (props: Props) => {
); diff --git a/src/components/common/cart/Cart.tsx b/src/components/common/cart/Cart.tsx index 7c3684d8..16a57f4a 100644 --- a/src/components/common/cart/Cart.tsx +++ b/src/components/common/cart/Cart.tsx @@ -71,13 +71,11 @@ export const Cart = () => { }); return (
- {/* TODO: Internationalize */} -
My Cart
+
{t("cartLabels.myCart")}
- {/* TODO: Internationalize */} -
Product
-
Quantity
-
Price
+
{t("cartLabels.product")}
+
{t("cartLabels.quantity")}
+
{t("cartLabels.price")}
    {items.map((item: Item) => ( @@ -94,13 +92,12 @@ export const Cart = () => { ))}
- {/* TODO: Internationalize */} -
Subtotal:
+
{t("cartLabels.subTotal")}:
${total.toFixed(2)}
- Taxes calculated at checkout + {t("cartLabels.taxes")}
@@ -108,11 +105,10 @@ export const Cart = () => { } else { return (
-
Cart is empty
+
{t("cartLabels.emptyCart")}
- {/* TODO: Internationalize */} -
Total:
+
{t("cartLabels.total")}:
$0.00
@@ -127,8 +123,7 @@ export const Cart = () => {
diff --git a/src/components/common/cart/CartCard.tsx b/src/components/common/cart/CartCard.tsx index 098a8ffb..771758e6 100644 --- a/src/components/common/cart/CartCard.tsx +++ b/src/components/common/cart/CartCard.tsx @@ -190,8 +190,7 @@ export const CartCard: Component = (props) => {
-
diff --git a/src/components/posts/CreateStripeProductPrice.tsx b/src/components/posts/CreateStripeProductPrice.tsx index 8cf2660e..979ccc12 100644 --- a/src/components/posts/CreateStripeProductPrice.tsx +++ b/src/components/posts/CreateStripeProductPrice.tsx @@ -16,7 +16,7 @@ async function postStripeData(stripeData: FormData) { }); const data = await response.json(); if (data.redirect) { - alert(data.message); //TODO: Not sure how to internationalize these + alert(data.message); window.location.href = `/${lang}` + data.redirect; } return data; diff --git a/src/components/users/ClientRouting.tsx b/src/components/users/ClientRouting.tsx index 317cc450..d018588b 100644 --- a/src/components/users/ClientRouting.tsx +++ b/src/components/users/ClientRouting.tsx @@ -54,7 +54,7 @@ export const ClientRouting = () => { diff --git a/src/components/users/provider/StripeButton.tsx b/src/components/users/provider/StripeButton.tsx index b808c78a..b3e0b76f 100644 --- a/src/components/users/provider/StripeButton.tsx +++ b/src/components/users/provider/StripeButton.tsx @@ -75,11 +75,10 @@ export const StripeButton = () => { ); @@ -93,11 +92,9 @@ export const StripeButton = () => { ); diff --git a/src/i18n/UI/English.ts b/src/i18n/UI/English.ts index 49bf28ca..e35acb7b 100644 --- a/src/i18n/UI/English.ts +++ b/src/i18n/UI/English.ts @@ -77,6 +77,11 @@ export const English = { saveProfile: 'Save Profile', filters: "Filters", faq: "Help Center", + addToCart: "Add to Cart", + stripeSetup: "Stripe Setup", + stripeLogin: "Stripe Login", + proceedToCheckout: "Proceed to Checkout", + viewCart: "View Cart" }, messages: { @@ -147,6 +152,7 @@ export const English = { chooseLanguage: "Choose one or more", languagesSpoken: "Languages Spoken", taxCode: "Tax Code", + dropdownDefault: "Select an option", }, postLabels: { @@ -159,6 +165,17 @@ export const English = { clientProfileImage: 'Client Profile Image', }, + cartLabels: { + product: "Product", + quantity: "Quantity", + price: "Price", + myCart: "My Cart", + subTotal: "SubTotal", + taxes: "Taxes calculated at checkout", + total: "Total", + emptyCart: "Cart is empty", + }, + homePageText: { headline: "Everything you need to get the job done.", subHeadline: "Providers post services. Customers search posts. Work gets done.", @@ -166,7 +183,7 @@ export const English = { }, menus: { - services: 'Find Services', + resources: 'Find Resources', contactUs: 'Contact Us', }, @@ -235,6 +252,10 @@ export const English = { checkboxGoverningDistrict: "Checkbox for selecting governing district", darkMessage: "Toggle between Dark and Light mode", closeDialog: "Close Dialog", + cart: "Cart", + removeFromCart: "Remove From Cart", + increaseQuantity: "Increase Quantity", + decreaseQuantity: "Decrease Quantity", }, headerData: { diff --git a/src/i18n/UI/French.ts b/src/i18n/UI/French.ts index ab5735c5..e992eb6b 100644 --- a/src/i18n/UI/French.ts +++ b/src/i18n/UI/French.ts @@ -77,6 +77,11 @@ export const French = { saveProfile: "Enregistrer le profil", filters: "Filtres", faq: "Centre d'Aide", + addToCart: "Ajouter au panier", + stripeSetup: "Configuration de Stripe", + stripeLogin: "Connexion à Stripe", + proceedToCheckout: "Passer à la caisse", + viewCart: "Voir le panier", }, messages: { @@ -146,6 +151,7 @@ export const French = { chooseLanguage: "Choisissez-en un ou plusieurs", languagesSpoken: "Langues parlées", taxCode: "Code fiscal", + dropdownDefault: "Choisir une option", }, postLabels: { @@ -158,6 +164,17 @@ export const French = { clientProfileImage: "Image du profil du client", }, + cartLabels: { + product: "Produit", + quantity: "Quantité", + price: "Prix", + myCart: "Mon panier", + subTotal: "Total", + taxes: "Taxes calculées à la caisse", + total: "Total", + emptyCart: "Le panier est vide", + }, + homePageText: { headline: "Tout ce dont vous avez besoin pour faire le travail.", subHeadline: "Les fournisseurs publient des services. Les clients recherchent des messages. Le travail est fait.", @@ -165,7 +182,7 @@ export const French = { }, menus: { - services: 'Trouver des Services', + resources: 'Trouver des ressources', contactUs: 'Contactez-nous', }, @@ -235,6 +252,10 @@ export const French = { checkboxGoverningDistrict: "Case à cocher pour sélectionner le district administratif", darkMessage: "Basculer entre le mode clair et le mode sombre", closeDialog: "Fermer la boîte de dialogue", + cart: "Chariot", + removeFromCart: "Supprimer du panier", + increaseQuantity: "Augmenter la quantité", + decreaseQuantity: "Diminuer la quantité", }, headerData: { diff --git a/src/i18n/UI/Spanish.ts b/src/i18n/UI/Spanish.ts index ed479f41..cb2a27ff 100644 --- a/src/i18n/UI/Spanish.ts +++ b/src/i18n/UI/Spanish.ts @@ -77,6 +77,11 @@ export const Spanish = { saveProfile: 'Guardar Perfil', filters: "Filtros", faq: "Centro de Ayuda", + addToCart: "añadir a la cesta", + stripeSetup: "Configuración de Stripe", + stripeLogin: "Iniciar sesión Stripe", + proceedToCheckout: "Pasar por la caja", + viewCart: "Ver carrito", }, messages: { @@ -148,6 +153,7 @@ export const Spanish = { chooseLanguage: "Elige uno o más", languagesSpoken: "Idiomas hablados", taxCode: "Código de impuestos", + dropdownDefault: "Seleccione una opción", }, postLabels: { @@ -160,13 +166,25 @@ export const Spanish = { clientProfileImage: "Imagen de Perfil del Cliente", }, + cartLabels: { + product: "Producto", + quantity: "Cantidad", + price: "Precio", + myCart: "Mi carrito", + subTotal: "Total parcial", + taxes: "Impuestos calculados al finalizar la compra", + total: "Total", + emptyCart: "El carrito esta vacío", + + }, + homePageText: { headline: "Todo lo que necesitas para hacer el trabajo.", subHeadline: "Los proveedores publican servicios. Los clientes pueden selecionar los servicios. El trabajo se hace.", ariaLabel: "Imagen para el marcador de posición de información", }, menus: { - services: 'Buscar Servicios', + resources: 'Buscar Recursos', contactUs: 'Contáctenos', }, @@ -235,6 +253,10 @@ export const Spanish = { checkboxGoverningDistrict: "Casilla de verificación para seleccionar Distrito", darkMessage: "Cambia entre modo claro y modo oscuro", closeDialog: "Cerrar Diálogo", + cart: "Carro", + removeFromCart: "Quitar del carrito", + increaseQuantity: "Aumentar cantidad", + decreaseQuantity: "Disminuir cantidad", }, headerData: { diff --git a/src/i18n/uiType.ts b/src/i18n/uiType.ts index 118481e3..7c827c35 100644 --- a/src/i18n/uiType.ts +++ b/src/i18n/uiType.ts @@ -77,6 +77,11 @@ export interface uiObject { saveProfile: string, filters: string, faq: string, + addToCart: string, + stripeSetup: string, + stripeLogin: string, + proceedToCheckout: string, + viewCart: string, }, messages: { @@ -148,6 +153,7 @@ export interface uiObject { chooseLanguage: string, languagesSpoken: string, taxCode: string, + dropdownDefault: string, }, postLabels: { @@ -160,6 +166,17 @@ export interface uiObject { clientProfileImage: string, }, + cartLabels: { + product: string, + quantity: string, + price: string, + myCart: string, + subTotal: string, + taxes: string, + total: string, + emptyCart: string, + }, + homePageText: { headline: string, subHeadline: string, @@ -167,7 +184,7 @@ export interface uiObject { }, menus: { - services: string, + resources: string, contactUs: string, }, @@ -236,6 +253,10 @@ export interface uiObject { checkboxGoverningDistrict: string, darkMessage: string, closeDialog: string, + cart: string, + removeFromCart: string, + increaseQuantity: string, + decreaseQuantity: string, }, headerData: { From 4b78f01ee72dd66e7ecb57e1b24d765c72e2c680 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Tue, 26 Mar 2024 18:34:11 -0400 Subject: [PATCH 12/18] more internationalization --- src/components/common/cart/ViewCart.tsx | 5 +++-- src/i18n/UI/English.ts | 4 +++- src/i18n/UI/French.ts | 2 ++ src/i18n/UI/Spanish.ts | 3 ++- src/i18n/uiType.ts | 2 ++ 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/common/cart/ViewCart.tsx b/src/components/common/cart/ViewCart.tsx index dd6b9253..b96926b5 100644 --- a/src/components/common/cart/ViewCart.tsx +++ b/src/components/common/cart/ViewCart.tsx @@ -140,12 +140,13 @@ export const CartView = () => {
{shoppingCart()}
-
Order Summary
+ {/* TODO: Internationalization */} +
{t("cartLabels.orderSummary")}
- Subtotal ({totalItems()} items){" "} + {t("cartLabels.subTotal")} ({totalItems()} {t("cartLabels.items")}){" "}
${cartTotal()}
diff --git a/src/i18n/UI/English.ts b/src/i18n/UI/English.ts index e35acb7b..7fdb1004 100644 --- a/src/i18n/UI/English.ts +++ b/src/i18n/UI/English.ts @@ -170,10 +170,12 @@ export const English = { quantity: "Quantity", price: "Price", myCart: "My Cart", - subTotal: "SubTotal", + subTotal: "Subtotal", taxes: "Taxes calculated at checkout", total: "Total", emptyCart: "Cart is empty", + orderSummary: "Order Summary", + items: "items", }, homePageText: { diff --git a/src/i18n/UI/French.ts b/src/i18n/UI/French.ts index e992eb6b..20193b69 100644 --- a/src/i18n/UI/French.ts +++ b/src/i18n/UI/French.ts @@ -173,6 +173,8 @@ export const French = { taxes: "Taxes calculées à la caisse", total: "Total", emptyCart: "Le panier est vide", + orderSummary: "Récapitulatif de la commande", + items: "articles", }, homePageText: { diff --git a/src/i18n/UI/Spanish.ts b/src/i18n/UI/Spanish.ts index cb2a27ff..ad5fe060 100644 --- a/src/i18n/UI/Spanish.ts +++ b/src/i18n/UI/Spanish.ts @@ -175,7 +175,8 @@ export const Spanish = { taxes: "Impuestos calculados al finalizar la compra", total: "Total", emptyCart: "El carrito esta vacío", - + orderSummary: "Resumen del pedido", + items: "elementos", }, homePageText: { diff --git a/src/i18n/uiType.ts b/src/i18n/uiType.ts index 7c827c35..1ba4a3ff 100644 --- a/src/i18n/uiType.ts +++ b/src/i18n/uiType.ts @@ -175,6 +175,8 @@ export interface uiObject { taxes: string, total: string, emptyCart: string, + orderSummary: string, + items: string, }, homePageText: { From 5238decbb18d439d670d62bd134503e4fa6b17b1 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Tue, 26 Mar 2024 18:36:38 -0400 Subject: [PATCH 13/18] remove imports from quantity --- src/components/common/cart/Quantity.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/common/cart/Quantity.tsx b/src/components/common/cart/Quantity.tsx index 5dcbc615..3a5a6cb3 100644 --- a/src/components/common/cart/Quantity.tsx +++ b/src/components/common/cart/Quantity.tsx @@ -1,9 +1,7 @@ -import { createEffect, createSignal, onMount } from "solid-js"; -import { createStore } from "solid-js/store"; +import { createSignal, onMount } from "solid-js"; import type { Component } from "solid-js"; import { getLangFromUrl, useTranslations } from "@i18n/utils"; -import { items, setItems } from "@components/common/cart/AddToCartButton"; -import { e } from "dist/_astro/web.DzrY7x_K"; + const lang = getLangFromUrl(new URL(window.location.href)); const t = useTranslations(lang); From 4bde56e5e4b6353f68c02cbdc31d555c361ce9d5 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Wed, 27 Mar 2024 11:50:02 -0400 Subject: [PATCH 14/18] add seller image to cart card --- .../common/cart/AddToCartButton.tsx | 2 +- src/components/common/cart/CartCard.tsx | 75 +++++++++++++++---- src/components/common/cart/ViewCart.tsx | 63 ++++++++++++---- 3 files changed, 108 insertions(+), 32 deletions(-) diff --git a/src/components/common/cart/AddToCartButton.tsx b/src/components/common/cart/AddToCartButton.tsx index e4a919de..563bcf18 100644 --- a/src/components/common/cart/AddToCartButton.tsx +++ b/src/components/common/cart/AddToCartButton.tsx @@ -1,4 +1,4 @@ -import { Show, createResource, createSignal, onMount } from "solid-js"; +import { onMount } from "solid-js"; import { createStore } from "solid-js/store"; import type { Component } from "solid-js"; import { getLangFromUrl, useTranslations } from "@i18n/utils"; diff --git a/src/components/common/cart/CartCard.tsx b/src/components/common/cart/CartCard.tsx index 771758e6..4f035820 100644 --- a/src/components/common/cart/CartCard.tsx +++ b/src/components/common/cart/CartCard.tsx @@ -20,6 +20,7 @@ interface ItemDetails { content: string; title: string; seller_name: string; + seller_image?: string; image_urls: string | null; price?: number; price_id: string; @@ -46,6 +47,10 @@ export const CartCard: Component = (props) => { item.image_urls.split(",")[0] )) : (item.image_url = null); + + item.seller_image + ? (item.seller_image = await downloadSellerImage(item.seller_image)) + : (item.seller_image = null); // Set the default quantity to 1 This should be replaced with the quantity from the quantity counter in the future // item.quantity = 1; return item; @@ -94,9 +99,11 @@ export const CartCard: Component = (props) => { const removeItem = async (product_id: string) => { let currentItems = items; - currentItems = currentItems.filter((item) => item.product_id !== product_id); - setItems(currentItems) - console.log("Items" + items.map((item) => item.description)) + currentItems = currentItems.filter( + (item) => item.product_id !== product_id + ); + setItems(currentItems); + console.log("Items" + items.map((item) => item.description)); props.deleteItem(); }; @@ -117,11 +124,28 @@ export const CartCard: Component = (props) => { } }; + const downloadSellerImage = async (path: string) => { + try { + const { data, error } = await supabase.storage + .from("user.image") + .download(path); + if (error) { + throw error; + } + const url = URL.createObjectURL(data); + return url; + } catch (error) { + if (error instanceof Error) { + console.log("Error downloading image: ", error.message); + } + } + }; + return (
-
    +
      {newItems().map((item: any) => ( -
    • +
    • {item.image_url ? ( @@ -154,26 +178,47 @@ export const CartCard: Component = (props) => { id="cardContent" class="flex justify-between px-1 pt-1 text-left w-full md:w-5/6 md:h-full" > -
      -
      -

      +

      + -
      -

      +

      -
      +

      -
      +
      {/* Quantity */} = (props) => { product_id={item.product_id} />
      -
      +
      {/* Price */} {"$" + (item.price * item.quantity).toFixed(2)}
      -
      +
      {/* Remove All from Cart */} From 7e6dbc3a215d0ebeb1d7304bf0dcad927a232db1 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Wed, 27 Mar 2024 14:56:07 -0400 Subject: [PATCH 15/18] update components so that add to cart works on all pages with view card --- src/components/common/cart/Cart.tsx | 1 - .../posts/ClientViewProviderPosts.tsx | 17 +++-- src/components/posts/ViewProviderPosts.tsx | 43 +++++++----- src/components/services/ServicesMain.tsx | 67 +++++++++++++++---- src/components/services/ViewCard.tsx | 3 + src/components/users/ClientProviderView.tsx | 2 +- 6 files changed, 98 insertions(+), 35 deletions(-) diff --git a/src/components/common/cart/Cart.tsx b/src/components/common/cart/Cart.tsx index 16a57f4a..573e0c4d 100644 --- a/src/components/common/cart/Cart.tsx +++ b/src/components/common/cart/Cart.tsx @@ -1,7 +1,6 @@ import { Show, createEffect, - createResource, createSignal, onMount, onCleanup, diff --git a/src/components/posts/ClientViewProviderPosts.tsx b/src/components/posts/ClientViewProviderPosts.tsx index cf486148..f8dc3557 100644 --- a/src/components/posts/ClientViewProviderPosts.tsx +++ b/src/components/posts/ClientViewProviderPosts.tsx @@ -5,6 +5,7 @@ import supabase from "../../lib/supabaseClient"; import { ui } from "../../i18n/ui"; import type { uiObject } from "../../i18n/uiType"; import { getLangFromUrl, useTranslations } from "../../i18n/utils"; +import stripe from "@lib/stripe"; const lang = getLangFromUrl(new URL(window.location.href)); @@ -20,8 +21,6 @@ interface ProviderPost { title: string; seller_name: string; major_municipality: string; - // minor_municipality: string; - // governing_district: string; image_urls: string; price: number; price_id: string; @@ -47,15 +46,25 @@ export const ClientViewProviderPosts: Component = (props) => { if (error) { console.log("supabase error: " + error.message); } else { - data?.map((item) => { + const newItems = await Promise.all( + data?.map(async (item) => { productCategories.forEach((productCategories) => { if (item.product_category.toString() === productCategories.id) { item.category = productCategories.name; } }); delete item.product_category; - }); + + if (item.price_id !== null) { + const priceData = await stripe.prices.retrieve(item.price_id); + item.price = priceData.unit_amount! / 100; + } + return item; + })) + ; setPosts(data); + console.log("Posts") + console.log(posts()) } }); return ( diff --git a/src/components/posts/ViewProviderPosts.tsx b/src/components/posts/ViewProviderPosts.tsx index 4e71f4ca..08926a39 100644 --- a/src/components/posts/ViewProviderPosts.tsx +++ b/src/components/posts/ViewProviderPosts.tsx @@ -3,33 +3,37 @@ import { createEffect, createSignal } from "solid-js"; import { ViewCard } from "../services/ViewCard"; import supabase from "../../lib/supabaseClient"; import type { AuthSession } from "@supabase/supabase-js"; -import { ui } from '../../i18n/ui' -import type { uiObject } from '../../i18n/uiType'; +import { ui } from "../../i18n/ui"; +import type { uiObject } from "../../i18n/uiType"; import { getLangFromUrl, useTranslations } from "../../i18n/utils"; +import stripe from "@lib/stripe"; const lang = getLangFromUrl(new URL(window.location.href)); //get the categories from the language files so they translate with changes in the language picker -const values = ui[lang] as uiObject -const productCategories = values.productCategoryInfo.categories +const values = ui[lang] as uiObject; +const productCategories = values.productCategoryInfo.categories; // Define the type for the ProviderPost interface interface ProviderPost { - user_id: string; content: string; id: number; category: string; title: string; seller_name: string; major_municipality: string; - minor_municipality: string; - governing_district: string; - image_urls: string; + user_id: string; + image_urls: string | null; + price: number; + price_id: string; + quantity: number; + product_id: string; } // Get the user session const { data: User, error: UserError } = await supabase.auth.getSession(); + export const ViewProviderPosts: Component = () => { // initialize posts and session const [posts, setPosts] = createSignal>([]); @@ -54,15 +58,24 @@ export const ViewProviderPosts: Component = () => { if (error) { console.log("supabase error: " + error.message); } else { - data?.map(item => { - productCategories.forEach(productCategories => { - if (item.product_category.toString() === productCategories.id) { - item.category = productCategories.name + const newItems = await Promise.all( + data?.map(async (item) => { + productCategories.forEach((productCategories) => { + if (item.product_category.toString() === productCategories.id) { + item.category = productCategories.name; + } + }); + delete item.product_category; + + if (item.price_id !== null) { + const priceData = await stripe.prices.retrieve(item.price_id); + item.price = priceData.unit_amount! / 100; } + return item; }) - delete item.product_category - }) - setPosts(data); + ); + console.log(newItems.map(item => item.price)) + setPosts(newItems); } }); return ( diff --git a/src/components/services/ServicesMain.tsx b/src/components/services/ServicesMain.tsx index f608f28f..ef13229e 100644 --- a/src/components/services/ServicesMain.tsx +++ b/src/components/services/ServicesMain.tsx @@ -1,5 +1,5 @@ import type { Component } from "solid-js"; -import { createSignal, createEffect } from "solid-js"; +import { createSignal, createEffect, onMount } from "solid-js"; import supabase from "../../lib/supabaseClient"; import { CategoryCarousel } from "./CategoryCarousel"; import { ViewCard } from "./ViewCard"; @@ -27,21 +27,21 @@ if (user.session === null || user.session === undefined) { location.href = `/${lang}/login`; } -const { data, error } = await supabase.from("sellerposts").select("*"); +// const { data, error } = await supabase.from("sellerposts").select("*"); -data?.map(async (item) => { - productCategories.forEach((productCategories) => { - if (item.product_category.toString() === productCategories.id) { - item.category = productCategories.name; - } - }); - delete item.product_category; +// data?.map(async (item) => { +// productCategories.forEach((productCategories) => { +// if (item.product_category.toString() === productCategories.id) { +// item.category = productCategories.name; +// } +// }); +// delete item.product_category; - if(item.price_id !== null) { - const priceData = await stripe.prices.retrieve(item.price_id); - item.price = priceData.unit_amount! / 100; - } -}); +// if (item.price_id !== null) { +// const priceData = await stripe.prices.retrieve(item.price_id); +// item.price = priceData.unit_amount! / 100; +// } +// }); interface ProviderPost { content: string; @@ -75,6 +75,45 @@ export const ServicesView: Component = () => { const [searchString, setSearchString] = createSignal(""); const [noPostsVisible, setNoPostsVisible] = createSignal(false); + onMount(async () => { + await fetchPosts(); + }); + + let data; + + async function fetchPosts() { + const { data, error } = await supabase + .from("sellerposts") + .select("*"); + + if (!data) { + alert("No posts available."); + } + if (error) { + console.log("supabase error: " + error.message); + } else { + const newItems = await Promise.all( + data?.map(async (item) => { + productCategories.forEach((productCategories) => { + if (item.product_category.toString() === productCategories.id) { + item.category = productCategories.name; + } + }); + delete item.product_category; + + if (item.price_id !== null) { + const priceData = await stripe.prices.retrieve(item.price_id); + item.price = priceData.unit_amount! / 100; + } + return item; + }) + ); + console.log(newItems.map(item => item.price)) + setPosts(newItems); + setCurrentPosts(newItems); + } + } + // start the page as displaying all posts if (!data) { let noPostsMessage = document.getElementById("no-posts-message"); diff --git a/src/components/services/ViewCard.tsx b/src/components/services/ViewCard.tsx index 00d06c81..02dcf3d4 100644 --- a/src/components/services/ViewCard.tsx +++ b/src/components/services/ViewCard.tsx @@ -146,6 +146,9 @@ export const ViewCard: Component = (props) => { postImage={post.image_urls} />
      +
      +

      {post.price}

      +
      = (props) => { if (session()) { try { const { data, error } = await supabase - .from("providerview") + .from("sellerview") .select("*") .eq("seller_id", id); From 14c233cb4920fafa33e30aea097432c057e40930 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Wed, 27 Mar 2024 14:57:35 -0400 Subject: [PATCH 16/18] remove border box from viewcard --- src/components/services/ViewCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/services/ViewCard.tsx b/src/components/services/ViewCard.tsx index 02dcf3d4..b27c3df9 100644 --- a/src/components/services/ViewCard.tsx +++ b/src/components/services/ViewCard.tsx @@ -146,8 +146,8 @@ export const ViewCard: Component = (props) => { postImage={post.image_urls} />
      -
      -

      {post.price}

      +
      +

      ${post.price.toFixed(2)}

      Date: Wed, 27 Mar 2024 15:19:18 -0400 Subject: [PATCH 17/18] click outside menus modals closes them --- src/components/common/ProfileBtn.tsx | 9 +++++++++ src/components/common/cart/Cart.tsx | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/components/common/ProfileBtn.tsx b/src/components/common/ProfileBtn.tsx index ac463b53..aef845a9 100644 --- a/src/components/common/ProfileBtn.tsx +++ b/src/components/common/ProfileBtn.tsx @@ -27,6 +27,7 @@ export const ProfileBtn = () => { const listShow = document.getElementById("profileItems"); if (listShow?.classList.contains("hidden")) { listShow?.classList.remove("hidden"); + document.getElementById("backdrop")?.classList.remove("hidden") } else { listShow?.classList.add("hidden"); } @@ -36,6 +37,11 @@ export const ProfileBtn = () => { console.log("User Error: " + UserError.message); } + function hideMenu () { + document.getElementById("profileItems")?.classList.add("hidden") + document.getElementById("backdrop")?.classList.add("hidden") + } + function renderWhenUser() { if (isUser()) { return ( @@ -64,6 +70,9 @@ export const ProfileBtn = () => { +