From eb4292133c5bd1e34af0290e601cbfb20c218189 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Thu, 19 Sep 2024 10:21:31 -0400 Subject: [PATCH 01/11] add price to database --- .vscode/settings.json | 6 +- .../posts/CreateStripeProductPrice.tsx | 6 +- src/components/posts/EditPost.tsx | 29 ++- src/components/posts/PostImage.tsx | 24 +-- src/pages/api/creatorUpdatePost.ts | 180 +----------------- src/pages/api/pricesWebhook.ts | 86 +++++++++ src/pages/api/updatePostStripe.ts | 4 +- .../migrations/20240918173838_addPrice.sql | 154 +++++++++++++++ 8 files changed, 293 insertions(+), 196 deletions(-) create mode 100644 src/pages/api/pricesWebhook.ts create mode 100644 supabase/migrations/20240918173838_addPrice.sql diff --git a/.vscode/settings.json b/.vscode/settings.json index dc1f9207..458a1223 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,5 @@ { - "prettier.printWidth": 250 -} \ No newline at end of file + "prettier.printWidth": 250, + "deno.enable": true, + "deno.lint": true, +} diff --git a/src/components/posts/CreateStripeProductPrice.tsx b/src/components/posts/CreateStripeProductPrice.tsx index eaa2d3ad..8134e25c 100644 --- a/src/components/posts/CreateStripeProductPrice.tsx +++ b/src/components/posts/CreateStripeProductPrice.tsx @@ -61,11 +61,15 @@ export const CreateStripeProductPrice: Component = (props: Props) => { const product = await createProduct( props.name, props.description, - props.tax_code == "" ? 'txcd_10000000' : props.tax_code + props.tax_code == "" ? "txcd_10000000" : props.tax_code ); const price = await createPrice(product, props.price); const stripeData = new FormData(); stripeData.append("price_id", price.id); + stripeData.append( + "price_value", + price.unit_amount ? price.unit_amount.toString() : "0" + ); stripeData.append("product_id", product.id); stripeData.append("id", String(props.id)); stripeData.append("access_token", props.access_token); diff --git a/src/components/posts/EditPost.tsx b/src/components/posts/EditPost.tsx index 9836bc11..627cd897 100644 --- a/src/components/posts/EditPost.tsx +++ b/src/components/posts/EditPost.tsx @@ -96,6 +96,7 @@ export const EditPost: Component = (props: Props) => { setSession(data.session); setIsFree(props.post?.price === 0); + console.log("Is Free on Mount", isFree()); window.addEventListener("storage", (event) => { if (event.key === "theme") { @@ -104,6 +105,11 @@ export const EditPost: Component = (props: Props) => { } }); + if (isFree()) { + setPrice("0"); + } else { + setPrice((props.post?.price * 100).toString()); + } setGradePick(props.post?.grades); setSubjectPick(props.post.subjects); setSubtopicPick(props.post?.subtopics); @@ -213,7 +219,7 @@ export const EditPost: Component = (props: Props) => { }); createEffect(async () => { - console.log("allRequirementsMet: ", allRequirementsMet()); + // console.log("allRequirementsMet: ", allRequirementsMet()); let title = document.getElementById("Title"); @@ -252,6 +258,7 @@ export const EditPost: Component = (props: Props) => { if (form) { setDraftStatus(false); + // console.log(form); form.requestSubmit(); } } @@ -264,6 +271,7 @@ export const EditPost: Component = (props: Props) => { formData.append("refresh_token", session()?.refresh_token!); formData.append("lang", lang); if (isFree()) { + console.log("item is free"); setPrice("0"); formData.set("Price", price()); } else { @@ -310,8 +318,12 @@ export const EditPost: Component = (props: Props) => { if (props.post?.id! !== undefined) { formData.append("idSupabase", props.post!.id.toString()); } + + for (let pair of formData.entries()) { + console.log(pair[0] + ", " + pair[1]); + } setFormData(formData); - console.log(formData); + // console.log(formData); } function subjectCheckboxes() { @@ -421,14 +433,17 @@ export const EditPost: Component = (props: Props) => { ); } } - console.log(subtopicPick()); + // console.log(subtopicPick()); } function formatPrice(resourcePrice: string) { + console.log(resourcePrice); if (resourcePrice.indexOf(".") === -1) { setPrice(resourcePrice + "00"); + console.log(price()); } else if (resourcePrice.indexOf(".") >= 0) { setPrice(resourcePrice.replace(".", "")); + console.log(price()); } else { console.log("Price error"); } @@ -496,13 +511,13 @@ export const EditPost: Component = (props: Props) => { } function removeImage(imageId: string) { - console.log(imageUrl()); + // console.log(imageUrl()); const index = imageUrl().indexOf(imageId); const imageArray = [...imageUrl()]; if (index > -1) { imageArray.splice(index, 1); setImageUrl(imageArray); - console.log(imageUrl()); + // console.log(imageUrl()); } } @@ -1156,7 +1171,7 @@ export const EditPost: Component = (props: Props) => { {/* Price Implementation */} - {/*
+

{t("formLabels.isResourceFree")}?

@@ -1233,7 +1248,7 @@ export const EditPost: Component = (props: Props) => {
-
*/} +

diff --git a/src/components/posts/PostImage.tsx b/src/components/posts/PostImage.tsx index 05ae2e55..e3b31a99 100644 --- a/src/components/posts/PostImage.tsx +++ b/src/components/posts/PostImage.tsx @@ -27,8 +27,8 @@ const PostImage: Component = (props) => { const [imageIds, setImageIds] = createSignal>([]); createEffect(() => { - console.log("Mounting", props.url); - console.log("Has Run", hasRun()); + // console.log("Mounting", props.url); + // console.log("Has Run", hasRun()); if (props.url && !hasRun()) { if (Array.isArray(props.url) && props.url.length > 0) { updateImages(); @@ -41,14 +41,14 @@ const PostImage: Component = (props) => { }); const updateImages = async () => { - console.log("Update Images", props.url); - console.log("Image Ids", imageIds()); + // console.log("Update Images", props.url); + // console.log("Image Ids", imageIds()); if (props.url) { if (Array.isArray(props.url)) { const urlsToDownload = props.url.filter( (url) => !imageIds().includes(url) ); - console.log("Urls to download", urlsToDownload); + // console.log("Urls to download", urlsToDownload); await Promise.all( urlsToDownload.map(async (url) => { await downloadImage(url); @@ -100,7 +100,7 @@ const PostImage: Component = (props) => { } const file = target.files[0]; - console.log("File", file); + // console.log("File", file); let jpegFile = file; let webpFile = file; @@ -114,9 +114,9 @@ const PostImage: Component = (props) => { img.onload = async () => { const formatFiles = await resizeImage(img); webpFile = formatFiles.webpFile; - console.log("New File", webpFile); + // console.log("New File", webpFile); jpegFile = formatFiles.jpegFile; - console.log("New File", jpegFile); + // console.log("New File", jpegFile); if (webpFile && jpegFile) { const filePath = `${webpFile.name.split(".")[0]}`; @@ -225,9 +225,9 @@ const PostImage: Component = (props) => { index: number, image: { webpUrl: string; jpegUrl: string } ) => { - console.log(imageIds()); + // console.log(imageIds()); const imageId = imageIds()[index]; - console.log(imageId); + // console.log(imageId); if (props.removeImage) { props.removeImage(imageId); } @@ -238,7 +238,7 @@ const PostImage: Component = (props) => { if (blobIndex > -1) { imageArray.splice(blobIndex, 1); setImageUrl(imageArray); - console.log(imageUrl()); + // console.log(imageUrl()); } const imageIdArray = [...imageIds()]; @@ -246,7 +246,7 @@ const PostImage: Component = (props) => { if (index > -1) { imageIdArray.splice(index, 1); setImageIds(imageIdArray); - console.log(imageIds()); + // console.log(imageIds()); } }; diff --git a/src/pages/api/creatorUpdatePost.ts b/src/pages/api/creatorUpdatePost.ts index 65e60042..7d55cb5c 100644 --- a/src/pages/api/creatorUpdatePost.ts +++ b/src/pages/api/creatorUpdatePost.ts @@ -101,13 +101,13 @@ export const POST: APIRoute = async ({ request, redirect }) => { product: String(product_id), }); - const default_price = price_info.id; + // const default_price = price_info.id; - const stripe_update = await stripe.products.update(String(product_id), { - name: String(title), - description: String(description), - default_price: default_price, - }); + // const stripe_update = await stripe.products.update(String(product_id), { + // name: String(title), + // description: String(description), + // default_price: default_price, + // }); if (price) { const pricesToArchive = prices.data.map((item) => { @@ -123,44 +123,7 @@ export const POST: APIRoute = async ({ request, redirect }) => { const resourceLinksList = resourceLinks?.toString().split(","); - if (stripe_update.active) { - // let postSubmission = { - // title: title, - // content: content, - // // product_subject: JSON.parse(subject as string), - // // post_grade: JSON.parse(gradeLevel as string), - // image_urls: imageUrl, - // user_id: user.id, - // stripe_price_id: default_price, - // // resource_types: JSON.parse(resourceType as string), - // secular: secular?.valueOf(), - // resource_links: resourceLinksList, - // draft_status: draft_status?.valueOf(), - // }; - // const { data, error } = await supabase - // .from("seller_post") - // .update([postSubmission]) - // .eq("id", idSupabase) - // .select(); - // console.log(postSubmission); - - // if (error) { - // console.log(error); - // return new Response( - // JSON.stringify({ - // message: t("apiErrors.postError"), - // }), - // { status: 500 } - // ); - // } else if (!data) { - // return new Response( - // JSON.stringify({ - // message: t("apiErrors.noPost"), - // }), - // { status: 500 } - // ); - // } - + if (price_info.active) { const postId = idSupabase; let postSubmission = { @@ -168,7 +131,6 @@ export const POST: APIRoute = async ({ request, redirect }) => { new_title: title, new_content: content, new_image_urls: (imageUrl as string).split(",") || [], - new_stripe_price_id: default_price, new_secular: secular === "true" ? true : false, new_draft_status: draft_status?.valueOf() === "true" ? true : false, new_subjects: JSON.parse(subject as string).map(Number), // Array of subject IDs @@ -199,134 +161,6 @@ export const POST: APIRoute = async ({ request, redirect }) => { { status: 200 } ); } - // await supabase - // .from("seller_post_subject") - // .delete() - // .eq("post_id", postId); - - // await supabase - // .from("seller_post_grade") - // .delete() - // .eq("post_id", postId); - - // await supabase - // .from("seller_post_resource_types") - // .delete() - // .eq("post_id", postId); - - // await supabase - // .from("seller_post_subtopic") - // .delete() - // .eq("post_id", postId); - // //Insert values into each join table - // if (postId && subject) { - // const { error: subjectError } = await supabase - // .from("seller_post_subject") - // .insert( - // JSON.parse(subject as string).map( - // (subjectId: string) => ({ - // post_id: postId, - // subject_id: parseInt(subjectId), - // }) - // ) - // ); - // if (subjectError) { - // console.log(subjectError); - // throw subjectError; - // //Thrown errors are not very specific so may want to return a response during testing - // // return new Response( - // // JSON.stringify({ - // // //TODO Internationalize - // // message: "Error inserting subject", - // // }), - // // { status: 500 } - // // ); - // } - // } - - // if (postId && gradeLevel) { - // const { error: gradeError } = await supabase - // .from("seller_post_grade") - // .insert( - // JSON.parse(gradeLevel as string).map( - // (gradeId: string) => ({ - // post_id: postId, - // grade_id: parseInt(gradeId), - // }) - // ) - // ); - // if (gradeError) { - // console.log(gradeError); - // throw gradeError; - // //Thrown errors are not very specific so may want to return a response during testing - // // return new Response( - // // JSON.stringify({ - // // // TODO Internationalize - // // message: "Error inserting grade", - // // }), - // // { status: 500 } - // // ); - // } - // } - - // if (postId && resourceType) { - // const { error: resourceTypeError } = await supabase - // .from("seller_post_resource_types") - // .insert( - // JSON.parse(resourceType as string).map( - // (resourceTypeId: string) => ({ - // post_id: postId, - // resource_type_id: parseInt(resourceTypeId), - // }) - // ) - // ); - // if (resourceTypeError) { - // console.log(resourceTypeError); - // throw resourceTypeError; - // //Thrown errors are not very specific so may want to return a response during testing - // // return new Response( - // // JSON.stringify({ - // // // TODO Internationalize - // // message: "resource type insert error", - // // }), - // // { status: 500 } - // // ); - // } - // } - - // if (postId && subtopics) { - // const { error: subtopicError } = await supabase - // .from("seller_post_subtopic") - // .insert( - // JSON.parse(subtopics as string).map( - // (subtopicId: string) => ({ - // post_id: postId, - // subtopic_id: parseInt(subtopicId), - // }) - // ) - // ); - // if (subtopicError) { - // console.log("subtopicError: ", subtopicError); - // throw subtopicError; - // //Thrown errors are not very specific so may want to return a response during testing - // // return new Response( - // // JSON.stringify({ - // // // TODO Internationalize - // // message: "subtopic insert error", - // // }), - // // { status: 500 } - // // ); - // } - // } - - // //If all inserts are successful, return successful response - // return new Response( - // JSON.stringify({ - // message: t("apiErrors.success"), - // id: data[0].id, - // }), - // { status: 200 } - // ); } catch (error) { console.log("Error updating Post: ", error); diff --git a/src/pages/api/pricesWebhook.ts b/src/pages/api/pricesWebhook.ts new file mode 100644 index 00000000..1943215d --- /dev/null +++ b/src/pages/api/pricesWebhook.ts @@ -0,0 +1,86 @@ +import Stripe from "stripe"; +import type { APIRoute } from "astro"; +import supabase from "@lib/supabaseClientServiceRole"; +// import {DatabaseSubmit} from '../lib/OrderSubmit' + +const stripe = new Stripe(import.meta.env.PUBLIC_VITE_STRIPE_PRIVATE_KEY, { + apiVersion: "2023-10-16", +}); + +const endpointSecret = import.meta.env.PUBLIC_VITE_STRIPE_ENDPOINT_SECRET; + +export const POST: APIRoute = async function ({ request }: any) { + // console.log("Post triggered"); + // console.log("Request Header" + request.headers.get("stripe-signature")); + // const requestbody = await request.body.getReader().read(); + // console.log("Body: " + JSON.stringify(requestbody)) + + const buffers = []; + for await (const chunk of request.body) { + buffers.push(chunk); + } + + let body: string = ""; + + buffers.forEach((buffer) => { + body += new TextDecoder().decode(buffer); + }); + + // const body = new TextDecoder().decode(buffers[0]); + + const sig = request.headers.get("stripe-signature"); + + let event; + + try { + event = await stripe.webhooks.constructEventAsync( + body, + sig, + endpointSecret + ); + console.log(`Event Type: ${event.type}`); + } catch (err: any) { + console.log("Error Type: " + err.type); + } + + if (event === undefined) { + console.log("Event is undefined"); + } else { + const data = event.data.object as Stripe.Price; + + switch (event.type) { + case "price.created": { + // console.log("Session Completed", data); + if (data.active === true && data.unit_amount !== null) { + try { + await supabase + .from("seller_post") + .update({ + price_value: data.unit_amount / 100, + stripe_price_id: data.id, + }) + .eq("stripe_product_id", data.product); + } catch (err) { + console.log(err); + } + break; + } + } + default: + console.log(`Unhandled event type ${event.type}`); + } + + return new Response( + JSON.stringify({ + message: `Success`, + }), + { status: 200 } + ); + } + return new Response( + JSON.stringify({ + message: `Failure`, + }), + { status: 400 } + ); +}; \ No newline at end of file diff --git a/src/pages/api/updatePostStripe.ts b/src/pages/api/updatePostStripe.ts index c8b8d58d..72166f43 100644 --- a/src/pages/api/updatePostStripe.ts +++ b/src/pages/api/updatePostStripe.ts @@ -17,12 +17,13 @@ export const POST: APIRoute = async ({ request, redirect }) => { const stripeProductId = formData.get("product_id"); const stripePriceId = formData.get("price_id"); + const stripePriceValue = formData.get("price_value"); const postId = formData.get("id"); const access_token = formData.get("access_token"); const refresh_token = formData.get("refresh_token"); // Validate the formData - you'll probably want to do more than this - if (!stripeProductId || !stripePriceId || !postId) { + if (!stripeProductId || !stripePriceId || !postId || !stripePriceValue) { return new Response( JSON.stringify({ message: t("apiErrors.missingFields"), @@ -70,6 +71,7 @@ export const POST: APIRoute = async ({ request, redirect }) => { let stripeUpdate = { stripe_product_id: stripeProductId, stripe_price_id: stripePriceId, + price_value: parseInt(stripePriceValue as string) / 100, }; const { error, data } = await supabase diff --git a/supabase/migrations/20240918173838_addPrice.sql b/supabase/migrations/20240918173838_addPrice.sql new file mode 100644 index 00000000..f03b623e --- /dev/null +++ b/supabase/migrations/20240918173838_addPrice.sql @@ -0,0 +1,154 @@ +alter table "public"."seller_post" add column "price_value" double precision; + +CREATE OR REPLACE FUNCTION public.update_post_with_join_data( + edit_post_id BIGINT, + new_title TEXT, + new_content TEXT, + new_image_urls TEXT[], + new_secular BOOLEAN, + new_draft_status BOOLEAN, + new_subjects BIGINT[], + new_grades BIGINT[], + new_resource_types BIGINT[], + new_subtopics BIGINT[], + new_resource_links TEXT[] +) +RETURNS VOID AS $$ +BEGIN + + -- Step 1: Delete existing records in join tables only if they exist + IF EXISTS (SELECT 1 FROM seller_post_image WHERE seller_post_image.post_id = edit_post_id) THEN + DELETE FROM seller_post_image WHERE seller_post_image.post_id = edit_post_id; + END IF; + + IF EXISTS (SELECT 1 FROM seller_post_subject WHERE seller_post_subject.post_id = edit_post_id) THEN + DELETE FROM seller_post_subject WHERE seller_post_subject.post_id = edit_post_id; + END IF; + + IF EXISTS (SELECT 1 FROM seller_post_grade WHERE seller_post_grade.post_id = edit_post_id) THEN + DELETE FROM seller_post_grade WHERE seller_post_grade.post_id = edit_post_id; + END IF; + + IF EXISTS (SELECT 1 FROM seller_post_resource_types WHERE seller_post_resource_types.post_id = edit_post_id) THEN + DELETE FROM seller_post_resource_types WHERE seller_post_resource_types.post_id = edit_post_id; + END IF; + + IF EXISTS (SELECT 1 FROM seller_post_subtopic WHERE seller_post_subtopic.post_id = edit_post_id) THEN + DELETE FROM seller_post_subtopic WHERE seller_post_subtopic.post_id = edit_post_id; + END IF; + + -- Step 2: Insert new records in join tables + IF array_length(new_image_urls, 1) IS NOT NULL THEN + INSERT INTO seller_post_image (post_id, image_uuid) + SELECT edit_post_id, unnest(new_image_urls); + END IF; + + IF array_length(new_subjects, 1) IS NOT NULL THEN + INSERT INTO seller_post_subject (post_id, subject_id) + SELECT edit_post_id, unnest(new_subjects); + END IF; + + IF array_length(new_grades, 1) IS NOT NULL THEN + INSERT INTO seller_post_grade (post_id, grade_id) + SELECT edit_post_id, unnest(new_grades); + END IF; + + IF array_length(new_resource_types, 1) IS NOT NULL THEN + INSERT INTO seller_post_resource_types (post_id, resource_type_id) + SELECT edit_post_id, unnest(new_resource_types); + END IF; + + IF array_length(new_subtopics, 1) IS NOT NULL THEN + INSERT INTO seller_post_subtopic (post_id, subtopic_id) + SELECT edit_post_id, unnest(new_subtopics); + END IF; + + -- Step 3: Update the seller_post table + UPDATE seller_post + SET + title = new_title, + content = new_content, + secular = new_secular, + draft_status = new_draft_status, + resource_links = new_resource_links + WHERE seller_post.id = edit_post_id; + +EXCEPTION + -- If anything fails, rollback all changes + WHEN OTHERS THEN + RAISE EXCEPTION 'Transaction failed: %', SQLERRM; +END; +$$ LANGUAGE plpgsql; + +drop view if exists"public"."sellerposts" cascade; + +CREATE OR REPLACE VIEW "public"."sellerposts" WITH ("security_invoker"='on') AS +SELECT + seller_post.id, + seller_post.title, + seller_post.content, + seller_post.user_id, + sellers.seller_name, + sellers.seller_id, + profiles.email, + seller_post.stripe_price_id AS price_id, + seller_post.price_value, + seller_post.stripe_product_id AS product_id, + seller_post.listing_status, + seller_post.secular, + seller_post.draft_status, + seller_post.resource_urls, + COALESCE( + ARRAY_AGG(DISTINCT seller_post_image.image_uuid) FILTER (WHERE seller_post_image.image_uuid IS NOT NULL), + '{}' + ) as image_urls, + COALESCE( + ARRAY_AGG(DISTINCT post_subtopic.id) FILTER (WHERE post_subtopic.id IS NOT NULL), + '{}' + ) AS subtopics, -- Aggregate subcategories + COALESCE( + ARRAY_AGG(DISTINCT post_subject.id) FILTER (WHERE post_subject.id IS NOT NULL), + '{}' + ) AS subjects, -- Aggregate subjects + COALESCE( + ARRAY_AGG(DISTINCT grade_level.id) FILTER (WHERE grade_level.id IS NOT NULL), + '{}' + ) AS grades, + COALESCE( + ARRAY_AGG(DISTINCT resource_types.id) FILTER (WHERE resource_types.id IS NOT NULL), + '{}' + ) AS resource_types -- Aggregate resource types +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 seller_post_image ON seller_post.id = seller_post_image.post_id +LEFT JOIN seller_post_subject ON seller_post.id = seller_post_subject.post_id -- Join post and subjects +LEFT JOIN post_subject ON seller_post_subject.subject_id = post_subject.id -- Join subjects +LEFT JOIN seller_post_subtopic ON seller_post.id = seller_post_subtopic.post_id -- Join subcategories +LEFT JOIN post_subtopic ON seller_post_subtopic.subtopic_id = post_subtopic.id -- Join subcategories +LEFT JOIN seller_post_grade ON seller_post.id = seller_post_grade.post_id -- Join post and grades +LEFT JOIN grade_level ON seller_post_grade.grade_id = grade_level.id -- Join grades +LEFT JOIN seller_post_resource_types ON seller_post.id = seller_post_resource_types.post_id +LEFT JOIN resource_types ON seller_post_resource_types.resource_type_id = resource_types.id -- Join resource types +GROUP BY + seller_post.id, + seller_post.title, + seller_post.content, + seller_post.user_id, + sellers.seller_name, + sellers.seller_id, + profiles.email, + seller_post.stripe_price_id, + seller_post.price_value, + seller_post.stripe_product_id, + seller_post.listing_status, + seller_post.secular, + seller_post.draft_status, + seller_post.resource_urls; + +CREATE OR REPLACE FUNCTION "public"."title_content"("public"."sellerposts") RETURNS "text" + LANGUAGE "sql" IMMUTABLE + AS $_$ + select $1.title || ' ' || $1.content; +$_$; \ No newline at end of file From 42b282e713dafadb87f1c812b84542cedbc507d2 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Thu, 19 Sep 2024 10:30:43 -0400 Subject: [PATCH 02/11] add price_value in seed.sql --- supabase/seed.sql | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/supabase/seed.sql b/supabase/seed.sql index b0098e63..d93dec2e 100644 --- a/supabase/seed.sql +++ b/supabase/seed.sql @@ -102,20 +102,20 @@ INSERT INTO "public"."favorites" ("list_number", "created_date", "list_name", "c -- Data for Name: seller_post; Type: TABLE DATA; Schema: public; Owner: postgres -- -INSERT INTO "public"."seller_post" ("id", "created_at", "title", "content", "user_id", "image_urls", "stripe_price_id", "stripe_product_id", "resource_urls", "listing_status", "secular", "resource_links", "draft_status") VALUES - (1, '2024-03-05 15:38:02.509065+00', 'Test Post 1', '

This post is for testing provider images in the cart and also longer post content so here is some more content to see if the cards properly cut off after three lines like we expect? Is this working? Test Test Test TESt testing Test

', '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, 'price_1OtG6cBRZLMDvS4Ri22IpzGq', 'prod_PihVI0liGFkala', NULL, true, true, NULL, false), - (5, '2024-03-05 21:53:47.560102+00', 'Geography course', '

test

', '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, 'price_1OtFqrBRZLMDvS4RK5Ajf7na', 'prod_PihF0aDvvT4PeU', NULL, true, false, NULL, false), - (3, '2024-03-05 21:52:06.919336+00', 'Math course', '

test

', '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, 'price_1OtFmkBRZLMDvS4RJ8TNXGrH', 'prod_PihB8lq2HWY15J', NULL, true, true, NULL, false), - (2, '2024-03-05 15:40:05.243892+00', 'Test Post 2', '

test

', '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, 'price_1OtFjpBRZLMDvS4RPoOg78AS', 'prod_Pih8Qrjfpr0Zmo', NULL, true, false, NULL, false), - (6, '2024-03-05 21:54:44.695358+00', 'programming course ', '

test learn programming

', 'a23376db-215d-49c4-9d9d-791c26579543', NULL, 'price_1OtFmFBRZLMDvS4RsYXD3Q3C', 'prod_PihAqPK24WxqZp', NULL, true, false, NULL, false), - (7, '2024-03-27 14:25:37.137856+00', 'Test', '

This post is for testing provider images in the cart and also longer post content so here is some more content to see if the cards properly cut off after three lines like we expect? Is this working? Test Test Test TESt testing Test

', 'b78eab21-c34e-41ef-9a72-64ee49f4cbc0', NULL, 'price_1OyxQ5BRZLMDvS4RJ1oWeAIj', 'prod_PoabbRuN1tqxj0', NULL, true, false, NULL, false), - (10, '2024-03-27 15:49:08.243995+00', 'Testing testing', '

test

', 'b00f3d62-4eb1-40ba-b73e-e3dc78eff08a', NULL, 'price_1OyyiuBRZLMDvS4Rpb0hhUK3', 'prod_PobwysGlI4L0OK', NULL, true, true, NULL, false), - (11, '2024-03-27 15:50:36.102267+00', 'New Test', '

Test

', 'b00f3d62-4eb1-40ba-b73e-e3dc78eff08a', NULL, 'price_1OyykKBRZLMDvS4RC4SRSDLb', 'prod_PobyNSYyDLZArR', NULL, true, true, NULL, false), - (12, '2024-03-27 15:53:32.292584+00', 'Another test', '

test

', 'b00f3d62-4eb1-40ba-b73e-e3dc78eff08a', NULL, 'price_1OyynABRZLMDvS4RMi5Z83UK', 'prod_Poc1Kl6ObNXQ89', NULL, true, true, NULL, false), - (13, '2024-05-17 16:10:12.692569+00', 'Free Post Test', '

Test free post

', 'a23376db-215d-49c4-9d9d-791c26579543', NULL, 'price_1PMZvBBRZLMDvS4RcPyxuTRL', 'prod_QCzvzVmGr9GP84', NULL, true, true, NULL, false), - (20, '2024-09-13 19:04:07.751644+00', 'test', '

etest

', 'a23376db-215d-49c4-9d9d-791c26579543', NULL, 'price_1PzmJ0BRZLMDvS4RZXrUWXBE', 'prod_QqLT7NqggoKdOi', 'b37341b5-ecd9-4349-9bba-dc4542ce6530', true, false, '{}', false), - (22, '2024-09-16 22:13:52.087191+00', 'More subtopics', '

test

', 'a23376db-215d-49c4-9d9d-791c26579543', NULL, 'price_1PznGHBRZLMDvS4RNAKvrtAv', 'prod_QrWDzqiNcIe5dO', '4c7b13a1-338f-4583-86e7-842e627cbc2e', true, false, '{}', false), - (21, '2024-09-16 21:19:13.958564+00', 'test subtopics', '

test

', 'a23376db-215d-49c4-9d9d-791c26579543', NULL, 'price_1PznKqBRZLMDvS4RyP4tve7n', 'prod_QrVKviVznbhXaE', '95757a2c-e4f8-4c32-a402-a5286388edab', true, false, '{}', false); +INSERT INTO "public"."seller_post" ("id", "created_at", "title", "content", "user_id", "image_urls", "stripe_price_id", "stripe_product_id", "resource_urls", "listing_status", "secular", "resource_links", "draft_status", "price_value") VALUES + (1, '2024-03-05 15:38:02.509065+00', 'Test Post 1', '

This post is for testing provider images in the cart and also longer post content so here is some more content to see if the cards properly cut off after three lines like we expect? Is this working? Test Test Test TESt testing Test

', '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, 'price_1OtG6cBRZLMDvS4Ri22IpzGq', 'prod_PihVI0liGFkala', NULL, true, true, NULL, false, 20.00), + (5, '2024-03-05 21:53:47.560102+00', 'Geography course', '

test

', '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, 'price_1OtFqrBRZLMDvS4RK5Ajf7na', 'prod_PihF0aDvvT4PeU', NULL, true, false, NULL, false, 20), + (3, '2024-03-05 21:52:06.919336+00', 'Math course', '

test

', '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, 'price_1OtFmkBRZLMDvS4RJ8TNXGrH', 'prod_PihB8lq2HWY15J', NULL, true, true, NULL, false, 20), + (2, '2024-03-05 15:40:05.243892+00', 'Test Post 2', '

test

', '84a298b6-9caf-4305-9bfe-3ea325df9188', NULL, 'price_1OtFjpBRZLMDvS4RPoOg78AS', 'prod_Pih8Qrjfpr0Zmo', NULL, true, false, NULL, false, 20), + (6, '2024-03-05 21:54:44.695358+00', 'programming course ', '

test learn programming

', 'a23376db-215d-49c4-9d9d-791c26579543', NULL, 'price_1OtFmFBRZLMDvS4RsYXD3Q3C', 'prod_PihAqPK24WxqZp', NULL, true, false, NULL, false, 20), + (7, '2024-03-27 14:25:37.137856+00', 'Test', '

This post is for testing provider images in the cart and also longer post content so here is some more content to see if the cards properly cut off after three lines like we expect? Is this working? Test Test Test TESt testing Test

', 'b78eab21-c34e-41ef-9a72-64ee49f4cbc0', NULL, 'price_1OyxQ5BRZLMDvS4RJ1oWeAIj', 'prod_PoabbRuN1tqxj0', NULL, true, false, NULL, false, 20), + (10, '2024-03-27 15:49:08.243995+00', 'Testing testing', '

test

', 'b00f3d62-4eb1-40ba-b73e-e3dc78eff08a', NULL, 'price_1OyyiuBRZLMDvS4Rpb0hhUK3', 'prod_PobwysGlI4L0OK', NULL, true, true, NULL, false, 20), + (11, '2024-03-27 15:50:36.102267+00', 'New Test', '

Test

', 'b00f3d62-4eb1-40ba-b73e-e3dc78eff08a', NULL, 'price_1OyykKBRZLMDvS4RC4SRSDLb', 'prod_PobyNSYyDLZArR', NULL, true, true, NULL, false, 20), + (12, '2024-03-27 15:53:32.292584+00', 'Another test', '

test

', 'b00f3d62-4eb1-40ba-b73e-e3dc78eff08a', NULL, 'price_1OyynABRZLMDvS4RMi5Z83UK', 'prod_Poc1Kl6ObNXQ89', NULL, true, true, NULL, false, 20), + (13, '2024-05-17 16:10:12.692569+00', 'Free Post Test', '

Test free post

', 'a23376db-215d-49c4-9d9d-791c26579543', NULL, 'price_1PMZvBBRZLMDvS4RcPyxuTRL', 'prod_QCzvzVmGr9GP84', NULL, true, true, NULL, false, 0), + (20, '2024-09-13 19:04:07.751644+00', 'test', '

etest

', 'a23376db-215d-49c4-9d9d-791c26579543', NULL, 'price_1PzmJ0BRZLMDvS4RZXrUWXBE', 'prod_QqLT7NqggoKdOi', 'b37341b5-ecd9-4349-9bba-dc4542ce6530', true, false, '{}', false, 0), + (22, '2024-09-16 22:13:52.087191+00', 'More subtopics', '

test

', 'a23376db-215d-49c4-9d9d-791c26579543', NULL, 'price_1PznGHBRZLMDvS4RNAKvrtAv', 'prod_QrWDzqiNcIe5dO', '4c7b13a1-338f-4583-86e7-842e627cbc2e', true, false, '{}', false, 0), + (21, '2024-09-16 21:19:13.958564+00', 'test subtopics', '

test

', 'a23376db-215d-49c4-9d9d-791c26579543', NULL, 'price_1PznKqBRZLMDvS4RyP4tve7n', 'prod_QrVKviVznbhXaE', '95757a2c-e4f8-4c32-a402-a5286388edab', true, false, '{}', false, 0); From 73c968049998740227935597d44e0d5471b7472f Mon Sep 17 00:00:00 2001 From: r-southworth Date: Fri, 20 Sep 2024 11:00:08 -0400 Subject: [PATCH 03/11] add downloadable filter --- src/components/services/FiltersMobile.tsx | 296 ++++++- src/components/services/ResourcesMain.tsx | 113 +-- src/i18n/UI/English.ts | 21 +- src/i18n/UI/French.ts | 31 +- src/i18n/UI/Spanish.ts | 19 +- src/i18n/uiType.ts | 920 +++++++++++----------- src/lib/types.ts | 1 + src/pages/api/fetchFilterPosts.ts | 43 +- 8 files changed, 789 insertions(+), 655 deletions(-) diff --git a/src/components/services/FiltersMobile.tsx b/src/components/services/FiltersMobile.tsx index b1a065ae..51706259 100644 --- a/src/components/services/FiltersMobile.tsx +++ b/src/components/services/FiltersMobile.tsx @@ -20,6 +20,19 @@ let grades: Array<{ grade: string; id: number; checked: boolean }> = []; let subjects: Array = []; let resourceTypes: Array<{ type: string; id: number; checked: boolean }> = []; +function getFilterButtonIndexById(id: string) { + console.log( + values.clearFilters.filterButtons.findIndex( + (button) => button.id === id + ) + ); + return ( + values.clearFilters.filterButtons.findIndex( + (button) => button.id === id + ) || -1 + ); +} + const { data: gradeData, error: gradeError } = await supabase .from("grade_level") .select("*"); @@ -101,38 +114,76 @@ interface Props { clearFilters: boolean; secularFilter: (secular: boolean) => void; clearSecular: () => void; + filterPostsByDownloadable: (downloadable: boolean) => void; + clearDownloadFilter: () => void; } export const FiltersMobile: Component = (props) => { + //Grades + //Whether to show the grades window or not const [showGrades, setShowGrades] = createSignal(false); - const [showResourceTypes, setShowResourceTypes] = createSignal(false); - const [showSubjects, setShowSubjects] = createSignal(false); - const [showFilters, setShowFilters] = createSignal(false); + //The list of all grades const [grade, setGrade] = createSignal>( grades ); + //The list of selected grades + const [gradeFilters, setGradeFilters] = createSignal>([]); + //The number of selected grades + const [gradeFilterCount, setGradeFilterCount] = createSignal(0); + + //Resource Types + //Whether to show the resource types window or not + const [showResourceTypes, setShowResourceTypes] = createSignal(false); + //The list of all resource types const [resourceType, setResourceType] = createSignal>( resourceTypes ); - const [gradeFilters, setGradeFilters] = createSignal>([]); + //The list of selected resource types const [resourceTypesFilters, setResourceTypesFilters] = createSignal< Array >([]); + //The number of selected resource types + const [resourceTypesFilterCount, setResourceTypesFilterCount] = + createSignal(0); + + //Whether to show the filters window or not (for mobile) + const [showFilters, setShowFilters] = createSignal(false); + //Whether to show the filter number or not + const [showFilterNumber, setShowFilterNumber] = createSignal(false); + + //Subjects + //Whether to show the subjects window or not + const [showSubjects, setShowSubjects] = createSignal(false); + //The list of all subjects const [subject, setSubject] = createSignal>(allSubjectInfo); + //The list of selected subjects const [selectedSubjects, setSelectedSubjects] = createSignal>( [] ); - const [gradeFilterCount, setGradeFilterCount] = createSignal(0); - const [resourceTypesFilterCount, setResourceTypesFilterCount] = - createSignal(0); + //The number of selected subjects const [subjectFilterCount, setSubjectFilterCount] = createSignal(0); - const [showFilterNumber, setShowFilterNumber] = createSignal(false); + + //Secular + //Whether to show the secular window or not const [showSecular, setShowSecular] = createSignal(false); + //Whether to filter for secular or not const [selectedSecular, setSelectedSecular] = createSignal(false); + //The number of selected secular filers (1 or 0) const [secularInNumber, setSecularInNumber] = createSignal(0); + //Downloadable vs External Resources + //Whether to show the downloadable window or not + const [showDownloadable, setShowDownloadable] = + createSignal(false); + //Whether to filter for downloadable or not + const [selectDownloadable, setSelectDownloadable] = + createSignal(false); + //The number of selected downloadable filers (1 or 0) + const [downloadableFilterNumber, setDownloadableFilterNumber] = + createSignal(0); + const screenSize = useStore(windowSize); onMount(() => { @@ -170,12 +221,22 @@ export const FiltersMobile: Component = (props) => { } }); + createEffect(() => { + if (screenSize() !== "sm") { + setShowFilters(true); + } else { + setShowFilters(false); + } + }); + + //Check if any filters are selected createEffect(() => { if ( gradeFilterCount() === 0 && subjectFilterCount() === 0 && resourceTypesFilterCount() === 0 && - selectedSecular() === false + selectedSecular() === false && + selectDownloadable() === false ) { setShowFilterNumber(false); } else { @@ -183,22 +244,8 @@ export const FiltersMobile: Component = (props) => { } }); - createEffect(() => { - if (props.clearFilters) { - clearAllFiltersMobile(); - } - }); - function checkSubjectBoxes() { selectedSubjects().map((item) => { - // console.log(item); - // console.log(subject()); - // subject().map((subject) => { - // if (subject.id === item) { - // console.log(subject, item, "matched"); - // } - // console.log("no match"); - // }); setSubject((prevSubject) => prevSubject.map((subject) => { if (subject.id === item) { @@ -291,6 +338,7 @@ export const FiltersMobile: Component = (props) => { }; const clearAllFiltersMobile = () => { + console.log("clear all filters mobile"); props.clearAllFilters(); setGrade((prevGrades) => prevGrades.map((grade) => ({ ...grade, checked: false })) @@ -309,6 +357,7 @@ export const FiltersMobile: Component = (props) => { setResourceTypesFilterCount(0); setShowFilterNumber(false); setSelectedSecular(false); + setSelectDownloadable(false); localStorage.removeItem("selectedGrades"); localStorage.removeItem("selectedSubjects"); localStorage.removeItem("selectedResourceTypes"); @@ -349,6 +398,12 @@ export const FiltersMobile: Component = (props) => { setResourceTypesFilters([]); }; + const clearDownloadableFilter = () => { + props.clearDownloadFilter(); + setSelectDownloadable(false); + setDownloadableFilterNumber(0); + }; + const gradeCheckboxClick = (e: Event) => { let currCheckbox = e.currentTarget as HTMLInputElement; let currCheckboxID = Number(currCheckbox.id); @@ -380,6 +435,19 @@ export const FiltersMobile: Component = (props) => { } }; + const downloadableCheckboxClick = (e: Event) => { + const target = e.target as HTMLInputElement; + if (target.checked !== null) { + setSelectDownloadable(target.checked); + props.filterPostsByDownloadable(selectDownloadable()); + } + if (selectDownloadable() === true) { + setDownloadableFilterNumber(1); + } else { + setDownloadableFilterNumber(0); + } + }; + function setSubjectFilter(id: number) { if (selectedSubjects().includes(id)) { let currentSubjectFilters = selectedSubjects().filter( @@ -419,12 +487,14 @@ export const FiltersMobile: Component = (props) => { showGrades() === true || showSubjects() === true || showSecular() === true || - showResourceTypes() === true + showResourceTypes() === true || + showDownloadable() === true ) { setShowGrades(false); setShowSubjects(false); setShowFilters(false); setShowResourceTypes(false); + setShowDownloadable(false); } else if (showFilters() === true) { setShowFilters(false); } else { @@ -449,7 +519,8 @@ export const FiltersMobile: Component = (props) => { {gradeFilterCount() + subjectFilterCount() + resourceTypesFilterCount() + - secularInNumber()} + secularInNumber() + + downloadableFilterNumber()}

@@ -474,9 +545,9 @@ export const FiltersMobile: Component = (props) => { onClick={() => { setShowFilters(false); - if (showSubjects() === true) { - setShowSubjects(false); - } + // if (showSubjects() === true) { + // setShowSubjects(false); + // } setShowGrades(!showGrades()); }} > @@ -674,12 +745,65 @@ export const FiltersMobile: Component = (props) => { + + {/* Add Additional filter menu buttons here */} +
+ {/* Individual filter menus */} + + {/* Resource Types */}
+ {/* Grades */}
@@ -893,23 +1027,23 @@ export const FiltersMobile: Component = (props) => { -
-
-
-
- {t("formLabels.secular")} -
-
-
+
+
+
{ secularCheckboxClick(e); }} />
+
+
+ {t("formLabels.secular")} +
+
@@ -918,12 +1052,15 @@ export const FiltersMobile: Component = (props) => { class="w-32 rounded border border-border1 py-1 font-light dark:border-border1-DM" onClick={clearSecularFilterMobile} > - {t("clearFilters.filterButtons.6.text")} + {t( + `clearFilters.filterButtons.${getFilterButtonIndexById("Clear-Secular")}.text` + )}
+ {/* Subjects */}
+
+
+
+ + {/* Downloadable */} + +
+ + +
+
+
+ { + downloadableCheckboxClick(e); + }} + /> +
+
+
+ {t("formLabels.downloadable")} +
+
+
+
+ +
+
+ + {/* Add new filter rendering here */} ); diff --git a/src/components/services/ResourcesMain.tsx b/src/components/services/ResourcesMain.tsx index e247d08a..30610bf6 100644 --- a/src/components/services/ResourcesMain.tsx +++ b/src/components/services/ResourcesMain.tsx @@ -19,6 +19,7 @@ async function fetchPosts({ searchString, resourceFilters, secularFilter, + downloadable, listing_status, draft_status, lang, @@ -33,6 +34,7 @@ async function fetchPosts({ searchString: searchString, resourceFilters: resourceFilters, secularFilter: secularFilter, + downloadable: downloadable, lang: lang, listing_status: listing_status, draft_status: draft_status, @@ -55,6 +57,7 @@ export const ResourcesView: Component = () => { >([]); const [searchString, setSearchString] = createSignal(""); const [secularFilters, setSecularFilters] = createSignal(false); + const [downloadFilter, setDownloadFilter] = createSignal(false); const [clearFilters, setClearFilters] = createSignal(false); const [page, setPage] = createSignal(1); const [loading, setLoading] = createSignal(false); @@ -147,6 +150,7 @@ export const ResourcesView: Component = () => { searchString: searchString(), resourceFilters: resourceTypesFilters(), secularFilter: secularFilters(), + downloadable: downloadFilter(), listing_status: true, draft_status: false, lang: lang, @@ -202,69 +206,6 @@ export const ResourcesView: Component = () => { triggerNewSearch(); }; - let timeouts: (string | number | NodeJS.Timeout | undefined)[] = []; - - // const filterPosts = async () => { - // console.log("Filtering posts..."); - // const noPostsMessage = document.getElementById("no-posts-message"); - - // const res = await fetchPosts({ - // subjectFilters: subjectFilters(), - // gradeFilters: gradeFilters(), - // searchString: searchString(), - // resourceFilters: resourceTypesFilters(), - // secularFilter: secularFilters(), - // lang: lang, - // listing_status: true, - // draft_status: false, - // }); - - // console.log(res); - - // if ( - // res.body === null || - // res.body === undefined || - // res.body.length < 1 - // ) { - // noPostsMessage?.classList.remove("hidden"); - // setTimeout(() => { - // noPostsMessage?.classList.add("hidden"); - // }, 3000); - - // setPosts([]); - // console.error(); - - // timeouts.push( - // setTimeout(() => { - // //Clear all filters after the timeout otherwise the message immediately disappears (probably not a perfect solution) - // clearAllFilters(); - // }, 3000) - // ); - - // let allPosts = await fetchPosts({ - // subjectFilters: [], - // gradeFilters: [], - // searchString: "", - // resourceFilters: [], - // secularFilter: false, - // lang: lang, - // listing_status: true, - // draft_status: false, - // }); - - // setPosts(allPosts); - // console.log(allPosts); - // } else { - // for (let i = 0; i < timeouts.length; i++) { - // clearTimeout(timeouts[i]); - // } - - // timeouts = []; - - // setPosts(res.body); - // } - // }; - const filterPostsByGrade = (grade: number) => { if (gradeFilters().includes(grade)) { let currentGradeFilters = gradeFilters().filter( @@ -296,6 +237,11 @@ export const ResourcesView: Component = () => { triggerNewSearch(); }; + const filterPostsByDownloadable = (downloadable: boolean) => { + setDownloadFilter(downloadable); + triggerNewSearch(); + }; + const clearAllFilters = () => { let searchInput = document.getElementById( "headerSearch" @@ -313,6 +259,7 @@ export const ResourcesView: Component = () => { setGradeFilters([]); setResourceTypeFilters([]); setSecularFilters(false); + setDownloadFilter(false); triggerNewSearch(); setClearFilters(false); @@ -338,6 +285,11 @@ export const ResourcesView: Component = () => { triggerNewSearch(); }; + const clearDownloadFilter = () => { + setDownloadFilter(false); + triggerNewSearch(); + }; + return (
@@ -345,7 +297,7 @@ export const ResourcesView: Component = () => { {/* */}
- +
{ filterPostsBySubject={setCategoryFilter} secularFilter={filterPostsBySecular} clearSecular={clearSecular} - filterPostsByResourceTypes={filterPostsByResourceTypes} clearResourceTypes={clearResourceTypes} + filterPostsByResourceTypes={filterPostsByResourceTypes} + clearDownloadFilter={clearDownloadFilter} + filterPostsByDownloadable={filterPostsByDownloadable} /> - - - -
-

- {t("pageTitles.services")} -

-
-
-
- - + +
+

+ {t("pageTitles.services")} +

+
diff --git a/src/i18n/UI/English.ts b/src/i18n/UI/English.ts index 6759651c..7cac7c21 100644 --- a/src/i18n/UI/English.ts +++ b/src/i18n/UI/English.ts @@ -261,6 +261,7 @@ export const English = { secular: "Secular", resourceLinks: "Resource Links", draft: "Draft", + downloadable: "Downloadable", }, postLabels: { @@ -672,23 +673,17 @@ export const English = { clearFilters: { filterButtons: [ - { text: "Clear All Filters", ariaLabel: "Clear All Filters" }, - { text: "Clear Subjects", ariaLabel: "Clear Subjects" }, + { id: "Clear-All", text: "Clear All Filters", ariaLabel: "Clear All Filters" }, + { id: "Clear-Subjects", text: "Clear Subjects", ariaLabel: "Clear Subjects" }, { + id: "Clear-Grade", text: "Clear Grade", ariaLabel: "Clear Grade", }, - { - text: "Clear Minor Municipality", - ariaLabel: "Clear Minor Municipality", - }, - { - text: "Clear Governing District", - ariaLabel: "Clear Governing District", - }, - { text: "View Results", ariaLabel: "View Results" }, - { text: "Clear Secular", ariaLabel: "Clear Secular" }, - { text: "Clear Resource Type", ariaLabel: "Clear Resource Type" }, + { id: "View-Results", text: "View Results", ariaLabel: "View Results" }, + { id: "Clear-Secular", text: "Clear Secular", ariaLabel: "Clear Secular" }, + { id: "Clear-Resource-Type", text: "Clear Resource Type", ariaLabel: "Clear Resource Type" }, + { id: "Clear-Downloadable", text: "Clear Downloadable", ariaLabel: "Clear Downloadable Filter" }, ], }, diff --git a/src/i18n/UI/French.ts b/src/i18n/UI/French.ts index f52acb4e..6b2bbed1 100644 --- a/src/i18n/UI/French.ts +++ b/src/i18n/UI/French.ts @@ -263,6 +263,7 @@ export const French = { secular: "Laïque", resourceLinks: "Liens vers les ressources", draft: "Brouillon", + downloadable: "Téléchargeable" }, postLabels: { @@ -678,29 +679,17 @@ export const French = { clearFilters: { filterButtons: [ + { id: "Clear-All", text: "Effacer tous les filtres", ariaLabel: "Effacer tous les filtres" }, + { id: "Clear-Subjects", text: "Effacer les sujets", ariaLabel: "Effacer les filtres de sujet" }, { - text: "Effacer Tous Les Filtres", - ariaLabel: "Effacer Tous Les Filtres", + id: "Clear-Grade", + text: "Effacer la note", + ariaLabel: "Effacer le filtre de qualité", }, - { - text: "Effacer le Filtre de Catégorie", - ariaLabel: "Effacer le Filtre de Catégorie", - }, - { - text: "Effacer le Filtre des Grandes Municipalités", - ariaLabel: "Effacer le Filtre des Grandes Municipalités", - }, - { - text: "Effacer le Filtre des Municipalités Mineures", - ariaLabel: "Effacer le Filtre des Municipalités Mineures", - }, - { - text: "Effacer le Filtre de District de Gouvernement", - ariaLabel: "Effacer le Filtre de District de Gouvernement", - }, - { text: "Voir les Résultats", ariaLabel: "Voir les Résultats" }, - { text: "Propre Laïque", ariaLabel: "Propre Laïque" }, - { text: "Propre Type de Ressource", ariaLabel: "Propre Type de Ressource" }, + { id: "View-Results", text: "Afficher les résultats", ariaLabel: "Afficher les résultats" }, + { id: "Clear-Secular", text: "Clair Laïque", ariaLabel: "Effacer le filtre laïque" }, + { id: "Clear-Resource-Type", text: "Effacer le type de ressource", ariaLabel: "Effacer le filtre de type de ressource" }, + { id: "Clear-Downloadable", text: "Effacer téléchargeable", ariaLabel: "Effacer le filtre téléchargeable" }, ], }, diff --git a/src/i18n/UI/Spanish.ts b/src/i18n/UI/Spanish.ts index 797b85bc..7e8bc460 100644 --- a/src/i18n/UI/Spanish.ts +++ b/src/i18n/UI/Spanish.ts @@ -265,6 +265,7 @@ export const Spanish = { secular: "Laico", resourceLinks: "Enlaces de Recurso", draft: "Borrador", + downloadable: "Descargable", }, postLabels: { @@ -678,16 +679,20 @@ export const Spanish = { clearFilters: { filterButtons: [ { + id: "Clear-All", text: "Borrar Todos los Filtros", ariaLabel: "Borrar Todos los Filtros", }, - { text: "Borrar Categorías", ariaLabel: "Borrar Categorías" }, - { text: "Borrar Provincia", ariaLabel: "Borrar Provincia" }, - { text: "Borrar Cantón", ariaLabel: "Borrar Cantón" }, - { text: "Borrar Distrito", ariaLabel: "Borrar Distrito" }, - { text: "Ver Resultados", ariaLabel: "Ver Resultados" }, - { text: "Limpiar Laico", ariaLabel: "Limpiar Laico" }, - { text: "Limpiar Tipo de Recurso", ariaLabel: "Limpiar Tipo de Recurso" }, + { id: "Clear-Subjects", text: "Borrar Categorías", ariaLabel: "Borrar Categorías" }, + { + id: "Clear-Grade", + text: "Borrar calificación", + ariaLabel: "Borrar calificación", + }, + { id: "View-Results", text: "Ver Resultados", ariaLabel: "Ver Resultados" }, + { id: "Clear-Secular", text: "Claro secular", ariaLabel: "Limpiar Laico" }, + { id: "Clear-Resource-Type", text: "Limpiar Tipo de Recurso", ariaLabel: "Limpiar Tipo de Recurso" }, + { id: "Clear-Downloadable", text: "Borrar descargable", ariaLabel: "Borrar filtro descargable" }, ], }, diff --git a/src/i18n/uiType.ts b/src/i18n/uiType.ts index f10e50d3..510b87dc 100644 --- a/src/i18n/uiType.ts +++ b/src/i18n/uiType.ts @@ -1,488 +1,486 @@ type Subtopic = { - name: string; - description: string; - ariaLabel: string; - id: number; - subject_id: number; + name: string; + description: string; + ariaLabel: string; + id: number; + subject_id: number; +}; + +type FilterLabel = { + id: string; + text: string; + ariaLabel: string; }; export interface uiObject { - textDirection: string; - siteDescription: string; + textDirection: string; + siteDescription: string; - pageTitles: { - services: string; - signUp: string; - login: string; - home: string; - signIn: string; - createUserAccount: string; - editUserAccount: string; - viewUserAccount: string; - createPost: string; - createCreatorAccount: string; - editCreatorAccount: string; - viewCreatorAccount: string; - userViewCreatorAccount: string; - page404: string; - requestPasswordReset: string; - resetPassword: string; - terms: string; - privacy: string; - acceptableUse: string; - about: string; - impact: string; - fullPost: string; - offline: string; - faq: string; - viewCart: string; - popularResources: string; - shopBySubject: string; - newResources: string; - shopByGrade: string; - community: string; - sellerFeePayout: string; - copyright: string; - marketplacetax: string; - }; + pageTitles: { + services: string; + signUp: string; + login: string; + home: string; + signIn: string; + createUserAccount: string; + editUserAccount: string; + viewUserAccount: string; + createPost: string; + createCreatorAccount: string; + editCreatorAccount: string; + viewCreatorAccount: string; + userViewCreatorAccount: string; + page404: string; + requestPasswordReset: string; + resetPassword: string; + terms: string; + privacy: string; + acceptableUse: string; + about: string; + impact: string; + fullPost: string; + offline: string; + faq: string; + viewCart: string; + popularResources: string; + shopBySubject: string; + newResources: string; + shopByGrade: string; + community: string; + sellerFeePayout: string; + copyright: string; + marketplacetax: string; + }; - pageMetaTitle: { - home: string, - }, + pageMetaTitle: { + home: string; + }; - pageDescriptions: { - services: string; - signUp: string; - login: string; - home: string; - // signIn: string; - createUserAccount: string; - viewUserAccount: string; - createPost: string; - createCreatorAccount: string; - viewCreatorAccount: string; - userViewCreatorAccount: string; - page404: string; - requestPasswordReset: string; - resetPassword: string; - terms: string; - privacy: string; - acceptableUse: string; - about: string; - impact: string; - fullPost: string; - faq: string; - viewCart: string; - community: string; - sellerFeePayout: string; - copyright: string; - marketplacetax: string; - taxCodeLearnMore: string; - }; + pageDescriptions: { + services: string; + signUp: string; + login: string; + home: string; + // signIn: string; + createUserAccount: string; + viewUserAccount: string; + createPost: string; + createCreatorAccount: string; + viewCreatorAccount: string; + userViewCreatorAccount: string; + page404: string; + requestPasswordReset: string; + resetPassword: string; + terms: string; + privacy: string; + acceptableUse: string; + about: string; + impact: string; + fullPost: string; + faq: string; + viewCart: string; + community: string; + sellerFeePayout: string; + copyright: string; + marketplacetax: string; + taxCodeLearnMore: string; + }; - buttons: { - creatorProfile: string; - editProfile: string; - register: string; - uploadImage: string; - uploading: string; - loading: string; - login: string; - signUp: string; - signIn: string; - signOut: string; - returnHome: string; - reset: string; - post: string; - next: string; - previous: string; - delete: string; - contact: string; - phone: string; - saveProfile: string; - filters: string; - faq: string; - addToCart: string; - stripeSetup: string; - stripeLogin: string; - proceedToCheckout: string; - viewCart: string; - showMore: string; - showLess: string; - browseCatalog: string; - findResources: string; - download: string; - follow: string; - following: string; - top: string; - downloadResources: string; - addedToCart: string; - resetPassword: string; - finishStripeSetup: string; - requestStripePayout: string; - listResource: string; - continueShopping: string; - viewOrders: string; - reportResource: string; - updateResource: string; - editPost: string; - getLinks: string; - checkoutAsGuest: string; - }; + buttons: { + creatorProfile: string; + editProfile: string; + register: string; + uploadImage: string; + uploading: string; + loading: string; + login: string; + signUp: string; + signIn: string; + signOut: string; + returnHome: string; + reset: string; + post: string; + next: string; + previous: string; + delete: string; + contact: string; + phone: string; + saveProfile: string; + filters: string; + faq: string; + addToCart: string; + stripeSetup: string; + stripeLogin: string; + proceedToCheckout: string; + viewCart: string; + showMore: string; + showLess: string; + browseCatalog: string; + findResources: string; + download: string; + follow: string; + following: string; + top: string; + downloadResources: string; + addedToCart: string; + resetPassword: string; + finishStripeSetup: string; + requestStripePayout: string; + listResource: string; + continueShopping: string; + viewOrders: string; + reportResource: string; + updateResource: string; + editPost: string; + getLinks: string; + checkoutAsGuest: string; + }; - messages: { - noAccount: string; - emailValid: string; - emailLackRequirements: string; - passwordLength: string; - passwordValid: string; - passwordLackRequirements: string; - phoneLackRequirements: string; - phoneValid: string; - passwordMatch: string; - passwordReset: string; - forgotPassword: string; - alreadyAccount: string; - error404: string; - onlyCreator: string; - signInAsCreator: string; - checkEmail: string; - checkConfirmEmail: string; - signIn: string; - createCreatorAccount: string; - createUserAccount: string; - viewCreatorAccount: string; - noPosts: string; - noPost: string; - selectAnImage: string; - noCreator: string; - translation: string; - translations: string; - clickWrap1: string; - clickWrap2: string; - fetch: string; - todoFetch: string; - mustSignIn: string; - profileEdits: string; - noUser: string; - noPostsSearch: string; - noStripeAccount: string; - selectSubject: string; - comingSoon: string; - emailNotProvided: string; - report: string; - free: string; - freeResourceCreated: string; - noPurchasedItems: string; - insufficientStripeBalance: string; - payoutRequested: string; - payoutSetup: string; - requestPayout: string; - currentBalance: string; - descriptionRequired: string; - reportResource: string; - pleaseDescribe: string; - addedToFavorites: string; - noFavoriteItems: string; - signIntoAddToFavorites: string; - resourceLinks: string; - externalResourceDisclaimer: string; - }; + messages: { + noAccount: string; + emailValid: string; + emailLackRequirements: string; + passwordLength: string; + passwordValid: string; + passwordLackRequirements: string; + phoneLackRequirements: string; + phoneValid: string; + passwordMatch: string; + passwordReset: string; + forgotPassword: string; + alreadyAccount: string; + error404: string; + onlyCreator: string; + signInAsCreator: string; + checkEmail: string; + checkConfirmEmail: string; + signIn: string; + createCreatorAccount: string; + createUserAccount: string; + viewCreatorAccount: string; + noPosts: string; + noPost: string; + selectAnImage: string; + noCreator: string; + translation: string; + translations: string; + clickWrap1: string; + clickWrap2: string; + fetch: string; + todoFetch: string; + mustSignIn: string; + profileEdits: string; + noUser: string; + noPostsSearch: string; + noStripeAccount: string; + selectSubject: string; + comingSoon: string; + emailNotProvided: string; + report: string; + free: string; + freeResourceCreated: string; + noPurchasedItems: string; + insufficientStripeBalance: string; + payoutRequested: string; + payoutSetup: string; + requestPayout: string; + currentBalance: string; + descriptionRequired: string; + reportResource: string; + pleaseDescribe: string; + addedToFavorites: string; + noFavoriteItems: string; + signIntoAddToFavorites: string; + resourceLinks: string; + externalResourceDisclaimer: string; + }; - formLabels: { - title: string; - serviceCategory: string; - postContent: string; - country: string; - majorMunicipality: string; - minorMunicipality: string; - governingDistrict: string; - search: string; - firstName: string; - lastName: string; - creatorName: string; - phone: string; - email: string; - password: string; - confirmPassword: string; - displayName: string; - enterPostContent: string; - noValue: string; - creatorInfo: string; - posts: string; - profileInfo: string; - yourPosts: string; - optional: string; - required: string; - languages: string; - chooseLanguage: string; - languagesSpoken: string; - taxCode: string; - dropdownDefault: string; - subjects: string; - grades: string; - resourceTypes: string; - standards: string; - fileTypes: string; - chooseSubject: string; - chooseGrade: string; - chooseResourceTypes: string; - chooseTaxCode: string; - pages: string; - pricePost: string; - isResourceFree: string; - about: string; - platformSupport: string; - images: string; - yes: string; - no: string; - more: string; - secular: string; - resourceLinks: string; - draft: string; - }; + formLabels: { + title: string; + serviceCategory: string; + postContent: string; + country: string; + majorMunicipality: string; + minorMunicipality: string; + governingDistrict: string; + search: string; + firstName: string; + lastName: string; + creatorName: string; + phone: string; + email: string; + password: string; + confirmPassword: string; + displayName: string; + enterPostContent: string; + noValue: string; + creatorInfo: string; + posts: string; + profileInfo: string; + yourPosts: string; + optional: string; + required: string; + languages: string; + chooseLanguage: string; + languagesSpoken: string; + taxCode: string; + dropdownDefault: string; + subjects: string; + grades: string; + resourceTypes: string; + standards: string; + fileTypes: string; + chooseSubject: string; + chooseGrade: string; + chooseResourceTypes: string; + chooseTaxCode: string; + pages: string; + pricePost: string; + isResourceFree: string; + about: string; + platformSupport: string; + images: string; + yes: string; + no: string; + more: string; + secular: string; + resourceLinks: string; + draft: string; + downloadable: string; + }; - postLabels: { - creator: string; - location: string; - category: string; - image: string; - slide: string; - creatorProfileImage: string; - userProfileImage: string; - subtopics: string; - }; + postLabels: { + creator: string; + location: string; + category: string; + image: string; + slide: string; + creatorProfileImage: string; + userProfileImage: string; + subtopics: string; + }; - cartLabels: { - product: string; - quantity: string; - price: string; - myCart: string; - subTotal: string; - taxes: string; - total: string; - emptyCart: string; - orderSummary: string; - items: string; - }; + cartLabels: { + product: string; + quantity: string; + price: string; + myCart: string; + subTotal: string; + taxes: string; + total: string; + emptyCart: string; + orderSummary: string; + items: string; + }; - homePageText: { - headline: string; - subHeadline: string; - ariaLabel: string; - becomeCreator: string; - clickToBecomeCreator: string; - welcome: string; - clickToLearnMore: string; - contribute: string; - clickToContribute: string; - }; + homePageText: { + headline: string; + subHeadline: string; + ariaLabel: string; + becomeCreator: string; + clickToBecomeCreator: string; + welcome: string; + clickToLearnMore: string; + contribute: string; + clickToContribute: string; + }; - menus: { - resources: string; - contactUs: string; - profile: string; - ratingsReviews: string; - questions: string; - freeDownload: string; - creatorResources: string; - payouts: string; - reviews: string; - details: string; - description: string; - qA: string; - purchases: string; - favorites: string; - following: string; - purchased: string; - updated: string; - }; + menus: { + resources: string; + contactUs: string; + profile: string; + ratingsReviews: string; + questions: string; + freeDownload: string; + creatorResources: string; + payouts: string; + reviews: string; + details: string; + description: string; + qA: string; + purchases: string; + favorites: string; + following: string; + purchased: string; + updated: string; + }; - toolTips: { - firstName: string; - lastName: string; - displayName: string; - profileImage: string; - changeEmail: string; - locationUpdate: string; - postImages: string; - firstNameEdit: string; - lastNameEdit: string; - languages: string; - subjects: string; - grades: string; - contribution: string; - taxCode: string; - resourceTypes: string; - price: string; - secular: string; - }; + toolTips: { + firstName: string; + lastName: string; + displayName: string; + profileImage: string; + changeEmail: string; + locationUpdate: string; + postImages: string; + firstNameEdit: string; + lastNameEdit: string; + languages: string; + subjects: string; + grades: string; + contribution: string; + taxCode: string; + resourceTypes: string; + price: string; + secular: string; + }; - apiErrors: { - missingFields: string; - noSession: string; - noUser: string; - creatorExists: string; - profileCreateError: string; - profileEditError: string; - noDistrict: string; - noMinorMunicipality: string; - noMajorMunicipality: string; - noCountry: string; - locationError: string; - creatorCreateProfileError: string; - creatorEditProfileError: string; - noProfileData: string; - success: string; - emailError: string; - noCategory: string; - postError: string; - noPost: string; - userExists: string; - userCreateProfileError: string; - userEditProfileError: string; - createUserError: string; - emailNotConfirmed: string; - }; + apiErrors: { + missingFields: string; + noSession: string; + noUser: string; + creatorExists: string; + profileCreateError: string; + profileEditError: string; + noDistrict: string; + noMinorMunicipality: string; + noMajorMunicipality: string; + noCountry: string; + locationError: string; + creatorCreateProfileError: string; + creatorEditProfileError: string; + noProfileData: string; + success: string; + emailError: string; + noCategory: string; + postError: string; + noPost: string; + userExists: string; + userCreateProfileError: string; + userEditProfileError: string; + createUserError: string; + emailNotConfirmed: string; + }; - socialModal: { - shareService: string; - twitterX: string; - facebook: string; - WhatsApp: string; - email: string; - copyLink: string; - embedLink: string; - textLink: string; - disclaimer: string; - shareButton: string; - closeShareMenu: string; - }; + socialModal: { + shareService: string; + twitterX: string; + facebook: string; + WhatsApp: string; + email: string; + copyLink: string; + embedLink: string; + textLink: string; + disclaimer: string; + shareButton: string; + closeShareMenu: string; + }; - ariaLabels: { - todo: string; - logo: string; - navigation: string; - checkboxMajorMunicipality: string; - checkboxMinorMunicipality: string; - checkboxGoverningDistrict: string; - darkMessage: string; - closeDialog: string; - cart: string; - removeFromCart: string; - increaseQuantity: string; - decreaseQuantity: string; - checkboxGrade: string; - checkbox: string; - readMoreAbout: string; - }; + ariaLabels: { + todo: string; + logo: string; + navigation: string; + checkboxMajorMunicipality: string; + checkboxMinorMunicipality: string; + checkboxGoverningDistrict: string; + darkMessage: string; + closeDialog: string; + cart: string; + removeFromCart: string; + increaseQuantity: string; + decreaseQuantity: string; + checkboxGrade: string; + checkbox: string; + readMoreAbout: string; + }; - headerData: { - links: { - text: string; - href: string; - }[]; - }; + headerData: { + links: { + text: string; + href: string; + }[]; + }; - footerData: { - links: [ - // { - // title: string, - // links: [ - // { text: string, href: string }, - // { text: string, href: string }, - // { text: string, href: string }, - // { text: string, href: string }, - // { text: string, href: string }, - // { text: string, href: string }, - // { text: string, href: string }, - // ], - // }, - // { - // title: string, - // links: [ - // { text: string, href: string }, - // { text: string, href: string }, - // ], - // }, - { - // title: string, + footerData: { links: [ - { text: string; href: string }, - // { text: string, href: string }, - // { text: string, href: string }, - // { text: string, href: string }, - // { text: string, href: string }, + // { + // title: string, + // links: [ + // { text: string, href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + // ], + // }, + // { + // title: string, + // links: [ + // { text: string, href: string }, + // { text: string, href: string }, + // ], + // }, + { + // title: string, + links: [ + { text: string; href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + ]; + }, + { + // title: string, + links: [ + { text: string; href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + // { text: string, href: string }, + ]; + }, ]; - }, - { - // title: string, - links: [ - { text: string; href: string }, - // { text: string, href: string }, - // { text: string, href: string }, - // { text: string, href: string }, - // { text: string, href: string }, - // { text: string, href: string }, - // { text: string, href: string }, + secondaryLinks: [ + { text: string; href: string }, + { text: string; href: string }, ]; - }, - ]; - secondaryLinks: [ - { text: string; href: string }, - { text: string; href: string }, - ]; - socialLinks: [ - // { ariaLabel: string, icon: string, href: string }, - { ariaLabel: string; icon: string; href: string }, - { ariaLabel: string; icon: string; href: string }, - // { ariaLabel: string, icon: string, href: string }, - { ariaLabel: string; icon: string; href: string }, - { ariaLabel: string; icon: string; href: string }, - { ariaLabel: string; icon: string; href: string }, - ]; - footNote: string; - }; + socialLinks: [ + // { ariaLabel: string, icon: string, href: string }, + { ariaLabel: string; icon: string; href: string }, + { ariaLabel: string; icon: string; href: string }, + // { ariaLabel: string, icon: string, href: string }, + { ariaLabel: string; icon: string; href: string }, + { ariaLabel: string; icon: string; href: string }, + { ariaLabel: string; icon: string; href: string }, + ]; + footNote: string; + }; - subjectCategoryInfo: { - subjects: [ - { name: string; description: string; ariaLabel: string; id: "3" }, - { name: string; description: string; ariaLabel: string; id: "4" }, - { name: string; description: string; ariaLabel: string; id: "5" }, - { name: string; description: string; ariaLabel: string; id: "6" }, - { name: string; description: string; ariaLabel: string; id: "7" }, - { name: string; description: string; ariaLabel: string; id: "8" }, - { name: string, description: string, ariaLabel: string, id: "9" }, - // { name: string, description: string, ariaLabel: string, id: "10" }, - // { name: string, description: string, ariaLabel: string, id: "11" }, - // { name: string, description: string, ariaLabel: string, id: "12" }, - // { name: string, description: string, ariaLabel: string, id: "13" }, - // { name: string, description: string, ariaLabel: string, id: "14" }, - // { name: string, description: string, ariaLabel: string, id: "15" }, - // { name: string, description: string, ariaLabel: string, id: "16" }, - // Add more products as needed - ]; - subtopics: Subtopic[]; - }; + subjectCategoryInfo: { + subjects: [ + { name: string; description: string; ariaLabel: string; id: "3" }, + { name: string; description: string; ariaLabel: string; id: "4" }, + { name: string; description: string; ariaLabel: string; id: "5" }, + { name: string; description: string; ariaLabel: string; id: "6" }, + { name: string; description: string; ariaLabel: string; id: "7" }, + { name: string; description: string; ariaLabel: string; id: "8" }, + { name: string; description: string; ariaLabel: string; id: "9" }, + // { name: string, description: string, ariaLabel: string, id: "10" }, + // { name: string, description: string, ariaLabel: string, id: "11" }, + // { name: string, description: string, ariaLabel: string, id: "12" }, + // { name: string, description: string, ariaLabel: string, id: "13" }, + // { name: string, description: string, ariaLabel: string, id: "14" }, + // { name: string, description: string, ariaLabel: string, id: "15" }, + // { name: string, description: string, ariaLabel: string, id: "16" }, + // Add more products as needed + ]; + subtopics: Subtopic[]; + }; - clearFilters: { - filterButtons: [ - { text: string; ariaLabel: string }, - { text: string; ariaLabel: string }, - { text: string; ariaLabel: string }, - { text: string; ariaLabel: string }, - { text: string; ariaLabel: string }, - { text: string; ariaLabel: string }, - { text: string; ariaLabel: string }, - { text: string; ariaLabel: string }, - ]; - }; + clearFilters: { + filterButtons: FilterLabel[]; + }; - checkout: { - success: string; - thankYou: string; - orderID: string; - total: string; - purchases: string; - }; + checkout: { + success: string; + thankYou: string; + orderID: string; + total: string; + purchases: string; + }; } diff --git a/src/lib/types.ts b/src/lib/types.ts index d7d978e0..66fd9549 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -79,4 +79,5 @@ export interface FilterPostsParams { seller_id?: string; from?: number; to?: number; + downloadable?: boolean; } \ No newline at end of file diff --git a/src/pages/api/fetchFilterPosts.ts b/src/pages/api/fetchFilterPosts.ts index f1d6cb36..1ecf3b4d 100644 --- a/src/pages/api/fetchFilterPosts.ts +++ b/src/pages/api/fetchFilterPosts.ts @@ -23,6 +23,7 @@ export const POST: APIRoute = async ({ request, redirect }) => { seller_id, from, to, + downloadable, }: FilterPostsParams = await request.json(); const values = ui[lang] as uiObject; @@ -69,12 +70,15 @@ export const POST: APIRoute = async ({ request, redirect }) => { if (seller_id) { query = query.eq("seller_id", seller_id); } - if (from !== undefined && to !== undefined && from>=0 && to >=0) { + if (from !== undefined && to !== undefined && from >= 0 && to >= 0) { query = query.range(from, to); } if (limit) { query = query.limit(limit); } + if (downloadable === true) { + query = query.not("resource_urls", "is", null); + } const { data: posts, error } = await query; @@ -86,7 +90,9 @@ export const POST: APIRoute = async ({ request, redirect }) => { }), { status: 500 } ); - } else {console.log("Posts: ", posts)} + } else { + console.log("Posts: ", posts); + } const { data: gradeData, error: gradeError } = await supabase .from("grade_level") @@ -95,7 +101,7 @@ export const POST: APIRoute = async ({ request, redirect }) => { const { data: resourceTypesData, error: resourceTypesError } = await supabase.from("resource_types").select("*"); - if (gradeError ||resourceTypesError) { + if (gradeError || resourceTypesError) { return new Response( JSON.stringify({ message: gradeError?.message || resourceTypesError?.message, @@ -107,7 +113,7 @@ export const POST: APIRoute = async ({ request, redirect }) => { let formattedPosts: Post[] = []; if (posts && gradeData && resourceTypesData) { - console.log(posts) + console.log(posts); formattedPosts = await Promise.all( posts.map(async (post: Post) => { post.subject = []; @@ -143,8 +149,7 @@ export const POST: APIRoute = async ({ request, redirect }) => { resourceTypesData.forEach((databaseResourceTypes) => { post.resource_types.map((postResourceTypes: number) => { if ( - postResourceTypes === - databaseResourceTypes.id + postResourceTypes === databaseResourceTypes.id ) { post.resourceTypes?.push( databaseResourceTypes.type @@ -247,29 +252,23 @@ const downloadPostImage = async (path: string) => { } try { - const [webpData, jpegData] = await Promise.all([ - supabase.storage.from("post.image").createSignedUrl(`webp/${path}.webp`, 60 * 60 * 24 * 30), - supabase.storage.from("post.image").createSignedUrl(`jpeg/${path}.jpeg`, 60 * 60 * 24 * 30) + supabase.storage + .from("post.image") + .createSignedUrl(`webp/${path}.webp`, 60 * 60 * 24 * 30), + supabase.storage + .from("post.image") + .createSignedUrl(`jpeg/${path}.jpeg`, 60 * 60 * 24 * 30), ]); - // const { data: webpData, error: webpError } = await supabase.storage - // .from("post.image") - // .createSignedUrl(`webp/${path}.webp`, 60 * 60 * 24 * 30); if (webpData.error || jpegData.error) { throw new Error(webpData.error?.message || jpegData.error?.message); } - // const webpUrl = webpData.signedUrl; - - // // const { data: jpegData, error: jpegError } = await supabase.storage - // // .from("post.image") - // // .createSignedUrl(`jpeg/${path}.jpeg`, 60 * 60 * 24 * 30); - // // if (jpegError) { - // // throw jpegError; - // // } - // const jpegUrl = jpegData.signedUrl; - const url = { webpUrl: webpData.data.signedUrl, jpegUrl: jpegData.data.signedUrl }; + const url = { + webpUrl: webpData.data.signedUrl, + jpegUrl: jpegData.data.signedUrl, + }; urlCache.set(path, url); // Cache the URL return url; } catch (error) { From f71e19b3d8acfedfec0138a740f20073e4974994 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Tue, 24 Sep 2024 11:05:17 -0400 Subject: [PATCH 04/11] subtopic filters --- src/components/home/Home.tsx | 5 +- src/components/posts/DeletePostButton.tsx | 2 +- src/components/services/FiltersMobile.tsx | 393 +++++++++++++++--- src/components/services/ResourcesMain.tsx | 34 +- src/i18n/UI/English.ts | 14 +- src/i18n/UI/French.ts | 14 +- src/i18n/UI/Spanish.ts | 14 +- src/i18n/uiType.ts | 25 +- src/lib/types.ts | 1 + src/lib/utils/debounce.tsx | 26 ++ src/pages/api/fetchFilterPosts.ts | 11 +- src/styles/global.css | 3 + .../20240920165548_filtersUpdates.sql | 8 + 13 files changed, 454 insertions(+), 96 deletions(-) create mode 100644 src/lib/utils/debounce.tsx create mode 100644 supabase/migrations/20240920165548_filtersUpdates.sql diff --git a/src/components/home/Home.tsx b/src/components/home/Home.tsx index 8e2c03e3..6594fc8c 100644 --- a/src/components/home/Home.tsx +++ b/src/components/home/Home.tsx @@ -6,6 +6,7 @@ import { windowSize } from "@components/common/WindowSizeStore"; import { HomeCard } from "@components/home/HomeCard"; import { HomeGradeCarousel } from "./HomeGradeCarousel"; import { useTranslations } from "../../i18n/utils"; +import { debounce } from "@lib/utils/debounce"; // const lang = getLangFromUrl(new URL(window.location.href)); @@ -15,7 +16,7 @@ interface Props { subjectCarousel: JSXElement; } -async function fetchPosts({ +async function postRequest({ lang, draft_status, listing_status, @@ -40,6 +41,8 @@ async function fetchPosts({ return data; } +const fetchPosts = debounce(postRequest, 500); + export const Home: Component = (props) => { const [lang, setLang] = createSignal<"en" | "es" | "fr">(props.lang); const [popularPosts, setPopularPosts] = createSignal>([]); diff --git a/src/components/posts/DeletePostButton.tsx b/src/components/posts/DeletePostButton.tsx index 9977e22d..6a8717c3 100644 --- a/src/components/posts/DeletePostButton.tsx +++ b/src/components/posts/DeletePostButton.tsx @@ -24,7 +24,7 @@ export const DeletePostButton: Component = (props) => { console.log("User Error: " + UserError.message); } else { if (User.session === null) { - console.log("User Session: " + User.session); + // console.log("User Session: " + User.session); setSession(null); } else { setSession(User.session); diff --git a/src/components/services/FiltersMobile.tsx b/src/components/services/FiltersMobile.tsx index 51706259..e03ea947 100644 --- a/src/components/services/FiltersMobile.tsx +++ b/src/components/services/FiltersMobile.tsx @@ -17,7 +17,20 @@ const values = ui[lang] as uiObject; const productCategoryData = values.subjectCategoryInfo; let grades: Array<{ grade: string; id: number; checked: boolean }> = []; -let subjects: Array = []; +let subjects: Array<{ + name?: string; + subject: string; + description?: string; + ariaLabel?: string; + id: number; + checked?: boolean; +}> = []; +let subtopics: Array<{ + subtopic: string; + id: number; + checked: boolean; + subject_id: number; +}> = []; let resourceTypes: Array<{ type: string; id: number; checked: boolean }> = []; function getFilterButtonIndexById(id: string) { @@ -50,6 +63,23 @@ if (gradeError) { grades.sort((a, b) => (a.id > b.id ? 0 : -1)); } +const { data: subTopicData, error: subTopicError } = await supabase + .from("post_subtopic") + .select("*"); + +if (subTopicError) { + console.log("supabase error: " + subTopicError.message); +} else { + subTopicData.forEach((subtopic) => { + subtopics.push({ + subtopic: subtopic.subtopic, + id: subtopic.id, + checked: false, + subject_id: subtopic.subject_id, + }); + }); +} + const { data: resourceTypesData, error: resourceTypesError } = await supabase .from("resource_types") .select("*"); @@ -89,7 +119,7 @@ let allSubjectInfo: Array<{ name: string; description: string; ariaLabel: string; - id: string; + id: number; checked: boolean; }> = []; @@ -97,7 +127,7 @@ for (let i = 0; i < subjectData.length; i++) { allSubjectInfo.push({ ...subjectData[i], ...subjects.find( - (itmInner) => itmInner.id.toString() === subjectData[i].id + (itmInner) => itmInner.id === Number(subjectData[i].id) ), checked: false, }); @@ -116,6 +146,8 @@ interface Props { clearSecular: () => void; filterPostsByDownloadable: (downloadable: boolean) => void; clearDownloadFilter: () => void; + filterPostsBySubtopic: (subtopics: Array) => void; + clearSubtopics: () => void; } export const FiltersMobile: Component = (props) => { @@ -157,11 +189,36 @@ export const FiltersMobile: Component = (props) => { //Whether to show the subjects window or not const [showSubjects, setShowSubjects] = createSignal(false); //The list of all subjects - const [subject, setSubject] = createSignal>(allSubjectInfo); + const [subject, setSubject] = createSignal< + Array<{ + name: string; + description: string; + ariaLabel: string; + id: number; + checked: boolean; + }> + >(allSubjectInfo); + //The list of all subtopics + const [subtopic, setSubtopic] = createSignal< + Array<{ + subtopic: string; + id: number; + checked: boolean; + subject_id: number; + }> + >(subtopics); + //The expanded subject + const [expandedSubject, setExpandedSubject] = createSignal( + null + ); //The list of selected subjects const [selectedSubjects, setSelectedSubjects] = createSignal>( [] ); + //The list of selected subtopics + const [selectedSubtopics, setSelectedSubtopics] = createSignal< + Array + >([]); //The number of selected subjects const [subjectFilterCount, setSubjectFilterCount] = createSignal(0); @@ -187,6 +244,7 @@ export const FiltersMobile: Component = (props) => { const screenSize = useStore(windowSize); onMount(() => { + console.log("Subtopics", subtopic()); const localSubjects = localStorage.getItem("selectedSubjects"); const localGrades = localStorage.getItem("selectedGrades"); const localResourceTypes = localStorage.getItem( @@ -194,7 +252,9 @@ export const FiltersMobile: Component = (props) => { ); if (localSubjects !== null && localSubjects) { setSelectedSubjects([...JSON.parse(localSubjects).map(Number)]); - setSubjectFilterCount(selectedSubjects().length); + setSubjectFilterCount( + selectedSubjects().length + selectedSubtopics().length + ); checkSubjectBoxes(); } else { setSelectedSubjects([]); @@ -224,11 +284,19 @@ export const FiltersMobile: Component = (props) => { createEffect(() => { if (screenSize() !== "sm") { setShowFilters(true); + setShowGrades(false); + setShowSubjects(false); + setShowResourceTypes(false); + setShowDownloadable(false); } else { setShowFilters(false); } }); + createEffect(() => { + console.log("expanded subject", expandedSubject()); + }); + //Check if any filters are selected createEffect(() => { if ( @@ -351,6 +419,7 @@ export const FiltersMobile: Component = (props) => { ); setGradeFilters([]); setSelectedSubjects([]); + setSelectedSubtopics([]); setResourceTypesFilters([]); setGradeFilterCount(0); setSubjectFilterCount(0); @@ -363,12 +432,25 @@ export const FiltersMobile: Component = (props) => { localStorage.removeItem("selectedResourceTypes"); }; + const clearSubtopics = () => { + if (selectedSubtopics().length === 0) { + return; + } else { + setSelectedSubtopics([]); + props.filterPostsBySubtopic(selectedSubtopics()); + } + }; + const clearSubjectFiltersMobile = () => { props.clearSubjects(); setSubject((prevSubjects) => prevSubjects.map((subject) => ({ ...subject, checked: false })) ); + setSubtopic((prevSubtopics) => + prevSubtopics.map((subtopic) => ({ ...subtopic, checked: false })) + ); setSelectedSubjects([]); + setSelectedSubtopics([]); setSubjectFilterCount(0); localStorage.removeItem("selectedSubjects"); }; @@ -406,28 +488,29 @@ export const FiltersMobile: Component = (props) => { const gradeCheckboxClick = (e: Event) => { let currCheckbox = e.currentTarget as HTMLInputElement; - let currCheckboxID = Number(currCheckbox.id); + let currCheckboxID = Number(currCheckbox.getAttribute("data-id")); setGradesFilter(currCheckboxID); }; const resourceTypesCheckboxClick = (e: Event) => { let currCheckbox = e.currentTarget as HTMLInputElement; - let currCheckboxID = Number(currCheckbox.id); + let currCheckboxID = Number(currCheckbox.getAttribute("data-id")); setResourceTypesFilter(currCheckboxID); }; const subjectCheckboxClick = (e: Event) => { let currCheckbox = e.currentTarget as HTMLInputElement; - let currCheckboxID = Number(currCheckbox.id); + let currCheckboxID = Number(currCheckbox.getAttribute("data-id")); setSubjectFilter(currCheckboxID); }; const secularCheckboxClick = (e: Event) => { - if ((e.target as HTMLInputElement)?.checked !== null) { - setSelectedSecular((e.target as HTMLInputElement)?.checked); + const target = e.target as HTMLInputElement; + if (target?.checked !== null) { + setSelectedSecular(target?.checked); props.secularFilter(selectedSecular()); } if (selectedSecular() === true) { @@ -454,11 +537,12 @@ export const FiltersMobile: Component = (props) => { (el) => el !== id ); setSelectedSubjects(currentSubjectFilters); - setSubjectFilterCount(selectedSubjects().length); } else { setSelectedSubjects([...selectedSubjects(), id]); - setSubjectFilterCount(selectedSubjects().length); } + setSubjectFilterCount( + selectedSubjects().length + selectedSubtopics().length + ); //Refactor send the full list just let filters track the contents and send the whole thing to main props.filterPostsBySubject(id); @@ -474,8 +558,116 @@ export const FiltersMobile: Component = (props) => { return subject; }) ); + syncSubTopicsWithSubjects(); + } + + function updateSubtopicArray(e: Event) { + console.log("updating subtopic array"); + const target = e.target as HTMLInputElement; + const targetValue = Number(target.getAttribute("data-id")); + if (target.checked === true) { + setSelectedSubtopics([...selectedSubtopics(), targetValue]); + } else if (target.checked === false) { + if (selectedSubtopics().includes(targetValue)) { + setSelectedSubtopics( + selectedSubtopics().filter((value) => value !== targetValue) + ); + } + } + setSubjectFilterCount( + selectedSubjects().length + selectedSubtopics().length + ); + + props.filterPostsBySubtopic(selectedSubtopics()); + + setSubtopic((prevSubtopics) => + prevSubtopics.map((subtopic) => { + if (subtopic.id === Number(target.getAttribute("data-id"))) { + if (target.checked) { + return { ...subtopic, checked: true }; + } else { + return { ...subtopic, checked: false }; + } + } + return subtopic; + }) + ); + + syncSubjectsWithSubtopics(); + console.log(selectedSubtopics()); } + //Track changes in selected subjects and remove subtopics if the subject is removed from the list + + const syncSubTopicsWithSubjects = () => { + console.log("Sync subtopics with subjects"); + + if (selectedSubjects().length === 0) { + setSubtopic((prevSubtopics) => + prevSubtopics.map((subtopic) => ({ + ...subtopic, + checked: false, + })) + ); + setSelectedSubtopics([]); + props.filterPostsBySubtopic([]); + } else { + setSubtopic((prevSubtopics) => + prevSubtopics.map((subtopic) => { + if ( + selectedSubjects().indexOf(subtopic.subject_id) === + -1 && + subtopic.checked === true + ) { + setSelectedSubtopics((prev) => + prev.filter((item) => item !== subtopic.id) + ); + return { ...subtopic, checked: false }; + } else { + return { ...subtopic }; + } + }) + ); + } + }; + + createEffect(() => { + console.log("Subjects", selectedSubjects()); + }); + + createEffect(() => { + console.log("Subtopics", selectedSubtopics()); + }); + + const syncSubjectsWithSubtopics = () => { + console.log("Sync subjects with subtopics"); + + selectedSubtopics().forEach((subtopicId) => { + const subtopicInfo = subtopic().find( + (item) => item.id === subtopicId + ); + if ( + subtopicInfo !== undefined && + selectedSubjects().indexOf(subtopicInfo.subject_id) === -1 + ) { + setSelectedSubjects((prev) => [ + ...prev, + subtopicInfo.subject_id, + ]); + props.filterPostsBySubject(subtopicInfo.subject_id); + setSubject((prevSubject) => { + return prevSubject.map((subject) => { + if (subject.id === subtopicInfo.subject_id) { + return { ...subject, checked: true }; + } else { + return subject; + } + }); + }); + } + }); + }; + return (
@@ -825,7 +1017,7 @@ export const FiltersMobile: Component = (props) => { {/* Resource Types */} -
+
-
+
{(item, index) => (
@@ -871,9 +1063,10 @@ export const FiltersMobile: Component = (props) => { t("ariaLabels.checkbox") } type="checkbox" - id={item.id.toString()} + id={`resource-checkbox ${item.id.toString()}`} + data-id={item.id} checked={item.checked} - class="resourceType mr-4 scale-125 leading-tight" + class="resourceType mr-4" onClick={(e) => resourceTypesCheckboxClick( e @@ -881,17 +1074,20 @@ export const FiltersMobile: Component = (props) => { } />
-
+
+
)}
-
+
-
+
+
)} @@ -1027,23 +1227,27 @@ export const FiltersMobile: Component = (props) => {
-
+
{ secularCheckboxClick(e); }} />
-
+
+
@@ -1062,7 +1266,7 @@ export const FiltersMobile: Component = (props) => { {/* Subjects */} -
+
-
- {subject()?.map((item) => ( -
-
- - subjectCheckboxClick(e) - } - /> -
- -
- -

- {item.name} -

-
+
+ + {(subject) => ( +
+
+
+
+ + subjectCheckboxClick( + e + ) + } + /> +
+ + +
+ + +
+
+ + {(subtopic) => + subtopic.subject_id === + subject.id && ( + <> +
+
+ + updateSubtopicArray( + e + ) + } + /> +
+ +
+ + ) + } +
+
-
- ))} + )} +
@@ -1171,11 +1451,17 @@ export const FiltersMobile: Component = (props) => {
-
+
{ @@ -1183,11 +1469,14 @@ export const FiltersMobile: Component = (props) => { }} />
-
+
+
diff --git a/src/components/services/ResourcesMain.tsx b/src/components/services/ResourcesMain.tsx index 30610bf6..904e2334 100644 --- a/src/components/services/ResourcesMain.tsx +++ b/src/components/services/ResourcesMain.tsx @@ -9,17 +9,19 @@ import { getLangFromUrl, useTranslations } from "../../i18n/utils"; import { useStore } from "@nanostores/solid"; import { windowSize } from "@components/common/WindowSizeStore"; import type { FilterPostsParams } from "@lib/types"; +import { debounce } from "@lib/utils/debounce"; const lang = getLangFromUrl(new URL(window.location.href)); const t = useTranslations(lang); -async function fetchPosts({ +async function postRequest({ subjectFilters, gradeFilters, searchString, resourceFilters, secularFilter, downloadable, + subtopics, listing_status, draft_status, lang, @@ -35,6 +37,7 @@ async function fetchPosts({ resourceFilters: resourceFilters, secularFilter: secularFilter, downloadable: downloadable, + subtopics: subtopics, lang: lang, listing_status: listing_status, draft_status: draft_status, @@ -47,10 +50,15 @@ async function fetchPosts({ return data; } +const fetchPosts = debounce(postRequest, 500); + export const ResourcesView: Component = () => { const [posts, setPosts] = createSignal>([]); const [searchPost, setSearchPost] = createSignal>([]); const [subjectFilters, setSubjectFilters] = createSignal>([]); + const [subtopicFilters, setSubtopicFilters] = createSignal>( + [] + ); const [gradeFilters, setGradeFilters] = createSignal>([]); const [resourceTypesFilters, setResourceTypeFilters] = createSignal< Array @@ -137,9 +145,15 @@ export const ResourcesView: Component = () => { localStorage.removeItem("selectedResourceTypes"); }); + let noPostsMessageTimeout: NodeJS.Timeout; + const fetchPaginatedPosts = async (pageValue: number) => { const noPostsMessage = document.getElementById("no-posts-message"); + if (noPostsMessageTimeout) { + noPostsMessage?.classList.add("hidden"); + clearTimeout(noPostsMessageTimeout); + } setLoading(true); const from = (pageValue - 1) * postsPerPage; const to = from + postsPerPage - 1; @@ -151,6 +165,7 @@ export const ResourcesView: Component = () => { resourceFilters: resourceTypesFilters(), secularFilter: secularFilters(), downloadable: downloadFilter(), + subtopics: subtopicFilters(), listing_status: true, draft_status: false, lang: lang, @@ -166,7 +181,7 @@ export const ResourcesView: Component = () => { } else { if (pageValue === 1) { noPostsMessage?.classList.remove("hidden"); // Show no-posts message on the first page - setTimeout(() => { + noPostsMessageTimeout = setTimeout(() => { noPostsMessage?.classList.add("hidden"); clearAllFilters(); }, 3000); @@ -242,7 +257,13 @@ export const ResourcesView: Component = () => { triggerNewSearch(); }; + const filterPostsBySubtopic = (subtopics: Array) => { + setSubtopicFilters(subtopics); + triggerNewSearch(); + }; + const clearAllFilters = () => { + console.log("clear all filters RM triggered"); let searchInput = document.getElementById( "headerSearch" ) as HTMLInputElement; @@ -260,6 +281,7 @@ export const ResourcesView: Component = () => { setResourceTypeFilters([]); setSecularFilters(false); setDownloadFilter(false); + setSubtopicFilters([]); triggerNewSearch(); setClearFilters(false); @@ -267,6 +289,7 @@ export const ResourcesView: Component = () => { const clearSubjects = () => { setSubjectFilters([]); + setSubtopicFilters([]); triggerNewSearch(); }; @@ -290,6 +313,11 @@ export const ResourcesView: Component = () => { triggerNewSearch(); }; + const clearSubtopicsFilter = () => { + setSubtopicFilters([]); + triggerNewSearch(); + }; + return (
@@ -311,6 +339,8 @@ export const ResourcesView: Component = () => { filterPostsByResourceTypes={filterPostsByResourceTypes} clearDownloadFilter={clearDownloadFilter} filterPostsByDownloadable={filterPostsByDownloadable} + filterPostsBySubtopic={filterPostsBySubtopic} + clearSubtopics={clearSubtopicsFilter} /> diff --git a/src/i18n/UI/English.ts b/src/i18n/UI/English.ts index 7cac7c21..dc01fd08 100644 --- a/src/i18n/UI/English.ts +++ b/src/i18n/UI/English.ts @@ -522,43 +522,43 @@ export const English = { name: "Art & Music", description: "Art & Music description", ariaLabel: "Art & Music", - id: "3", + id: 3, }, { name: "Holiday", description: "Holiday description", ariaLabel: "Holiday", - id: "4", + id: 4, }, { name: "Math", description: "Math description", ariaLabel: "Math", - id: "5", + id: 5, }, { name: "Science", description: "Description Science", ariaLabel: "Science", - id: "6", + id: 6, }, { name: "Social Studies", description: "Social Studies Description", ariaLabel: "Social Studies", - id: "7", + id: 7, }, { name: "Specialty", description: "Specialty description", ariaLabel: "Specialty", - id: "8", + id: 8, }, { name: "English Language Arts", description: "English Language Arts description", ariaLabel: "English Language Arts", - id: "9", + id: 9, }, // Add more products as needed ], diff --git a/src/i18n/UI/French.ts b/src/i18n/UI/French.ts index 6b2bbed1..8a68f049 100644 --- a/src/i18n/UI/French.ts +++ b/src/i18n/UI/French.ts @@ -528,43 +528,43 @@ export const French = { name: "Art et Musique", description: "Description Musique", ariaLabel: "Musique", - id: "3", + id: 3, }, { name: "Vacances", description: "Description Vacances", ariaLabel: "Vacances", - id: "4", + id: 4, }, { name: "Matematique", description: "Description Matematique", ariaLabel: "Matematique", - id: "5", + id: 5, }, { name: "Science", description: "Description Science", ariaLabel: "Science", - id: "6", + id: 6, }, { name: "Etudes sociale", description: "Description Etude Sociale", ariaLabel: "Etudes Sociale", - id: "7", + id: 7, }, { name: "Spécialité", description: "Description Spécialité", ariaLabel: "Spécialité", - id: "8", + id: 8, }, { name: "Arts de la langue anglaise", description: "Description Arts de la langue anglaise", ariaLabel: "Arts de la langue anglaise", - id: "9", + id: 9, }, // Add more products as needed ], diff --git a/src/i18n/UI/Spanish.ts b/src/i18n/UI/Spanish.ts index 7e8bc460..8bdac63e 100644 --- a/src/i18n/UI/Spanish.ts +++ b/src/i18n/UI/Spanish.ts @@ -527,43 +527,43 @@ export const Spanish = { name: "Arte y Musica", description: "Descripción arte y musica.", ariaLabel: "Arte y Musica", - id: "3", + id: 3, }, { name: "Vacaciones", description: "Descripción Vacaciones", ariaLabel: "Vacaciones", - id: "4", + id: 4, }, { name: "Matematica", description: "Descripción Matematica", ariaLabel: "Matematica", - id: "5", + id: 5, }, { name: "Ciencia", description: "Descripción Ciencia", ariaLabel: "Ciencia", - id: "6", + id: 6, }, { name: "Sociales", description: "Descripción Sociales", ariaLabel: "Sociales", - id: "7", + id: 7, }, { name: "Especilidad", description: "Descripcion especialidad", ariaLabel: "Especilidad", - id: "8", + id: 8, }, { name: "Artes del lenguaje inglés", description: "Descripcion eArtes del lenguaje inglés", ariaLabel: "Artes del lenguaje inglés", - id: "9", + id: 9, }, // Add more products as needed ], diff --git a/src/i18n/uiType.ts b/src/i18n/uiType.ts index 510b87dc..49771544 100644 --- a/src/i18n/uiType.ts +++ b/src/i18n/uiType.ts @@ -12,6 +12,13 @@ type FilterLabel = { ariaLabel: string; }; +type Subject = { + name: string; + description: string; + ariaLabel: string; + id: number; +}; + export interface uiObject { textDirection: string; siteDescription: string; @@ -452,23 +459,7 @@ export interface uiObject { }; subjectCategoryInfo: { - subjects: [ - { name: string; description: string; ariaLabel: string; id: "3" }, - { name: string; description: string; ariaLabel: string; id: "4" }, - { name: string; description: string; ariaLabel: string; id: "5" }, - { name: string; description: string; ariaLabel: string; id: "6" }, - { name: string; description: string; ariaLabel: string; id: "7" }, - { name: string; description: string; ariaLabel: string; id: "8" }, - { name: string; description: string; ariaLabel: string; id: "9" }, - // { name: string, description: string, ariaLabel: string, id: "10" }, - // { name: string, description: string, ariaLabel: string, id: "11" }, - // { name: string, description: string, ariaLabel: string, id: "12" }, - // { name: string, description: string, ariaLabel: string, id: "13" }, - // { name: string, description: string, ariaLabel: string, id: "14" }, - // { name: string, description: string, ariaLabel: string, id: "15" }, - // { name: string, description: string, ariaLabel: string, id: "16" }, - // Add more products as needed - ]; + subjects: Subject[]; subtopics: Subtopic[]; }; diff --git a/src/lib/types.ts b/src/lib/types.ts index 66fd9549..f13b4f7e 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -80,4 +80,5 @@ export interface FilterPostsParams { from?: number; to?: number; downloadable?: boolean; + subtopics?: number[]; } \ No newline at end of file diff --git a/src/lib/utils/debounce.tsx b/src/lib/utils/debounce.tsx new file mode 100644 index 00000000..c49fada2 --- /dev/null +++ b/src/lib/utils/debounce.tsx @@ -0,0 +1,26 @@ +export function debounce(fn: (...args: any) => Promise, delay: number) { + let timeoutId: ReturnType | null = null; + let firstCall = true; + + return function (...args: any): Promise { + if (firstCall) { + firstCall = false; + return fn(...args); + } + + if (timeoutId !== null) { + clearTimeout(timeoutId); + } + + return new Promise((resolve, reject) => { + timeoutId = setTimeout(async () => { + try { + const result = await fn(...args); + resolve(result); + } catch (error) { + reject(error); + } + }, delay); + }); + }; +} diff --git a/src/pages/api/fetchFilterPosts.ts b/src/pages/api/fetchFilterPosts.ts index 1ecf3b4d..d5ea708f 100644 --- a/src/pages/api/fetchFilterPosts.ts +++ b/src/pages/api/fetchFilterPosts.ts @@ -24,6 +24,7 @@ export const POST: APIRoute = async ({ request, redirect }) => { from, to, downloadable, + subtopics, }: FilterPostsParams = await request.json(); const values = ui[lang] as uiObject; @@ -31,6 +32,7 @@ export const POST: APIRoute = async ({ request, redirect }) => { const postSubtopics = values.subjectCategoryInfo.subtopics; console.log("From: ", from); console.log(" To: ", to); + console.log(subtopics); try { let query = supabase @@ -43,6 +45,9 @@ export const POST: APIRoute = async ({ request, redirect }) => { if (Array.isArray(subjectFilters) && subjectFilters.length !== 0) { query = query.overlaps("subjects", subjectFilters); } + if (Array.isArray(subtopics) && subtopics.length !== 0) { + query = query.overlaps("subtopics", subtopics); + } if (Array.isArray(gradeFilters) && gradeFilters.length !== 0) { query = query.overlaps("grades", gradeFilters); } @@ -80,6 +85,8 @@ export const POST: APIRoute = async ({ request, redirect }) => { query = query.not("resource_urls", "is", null); } + console.log(query) + const { data: posts, error } = await query; if (error) { @@ -91,7 +98,7 @@ export const POST: APIRoute = async ({ request, redirect }) => { { status: 500 } ); } else { - console.log("Posts: ", posts); + console.log("Posts: ", posts.length); } const { data: gradeData, error: gradeError } = await supabase @@ -113,7 +120,7 @@ export const POST: APIRoute = async ({ request, redirect }) => { let formattedPosts: Post[] = []; if (posts && gradeData && resourceTypesData) { - console.log(posts); + // console.log(posts); formattedPosts = await Promise.all( posts.map(async (post: Post) => { post.subject = []; diff --git a/src/styles/global.css b/src/styles/global.css index 438c9448..37e72cd4 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -3,6 +3,9 @@ @tailwind utilities; @layer components { + input[type="checkbox"] { + @apply h-[24px] w-[24px]; + } .btn-primary { @apply max-h-14 cursor-pointer rounded-full bg-btn1 px-4 py-2 font-bold text-btn1Text shadow-md active:animate-click dark:bg-btn1-DM dark:text-btn1Text-DM md:text-[1vw]; } diff --git a/supabase/migrations/20240920165548_filtersUpdates.sql b/supabase/migrations/20240920165548_filtersUpdates.sql new file mode 100644 index 00000000..97ce3c67 --- /dev/null +++ b/supabase/migrations/20240920165548_filtersUpdates.sql @@ -0,0 +1,8 @@ +drop policy "Allow Read Access for Everyone" on "public"."post_subtopic" + +create policy "Allow Read Access for Everyone" +on "public"."post_subtopic" +as permissive +for select +to authenticated, anon +using (true); \ No newline at end of file From 3fb047615393cf4fdd952c684337f55b6922b6d1 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Tue, 24 Sep 2024 11:41:31 -0400 Subject: [PATCH 05/11] fix view card on 768 screen width, adjust some checkbox labels --- src/components/posts/CreateNewPost.tsx | 4 +++- src/components/posts/EditPost.tsx | 4 +++- src/components/services/FiltersMobile.tsx | 4 +--- .../services/{GradeFilter.tsx => GradeFilter.txt} | 4 ++-- .../{LocationFilter.tsx => LocationFilter.txt} | 0 src/components/services/ResourcesMain.tsx | 13 +++++++------ .../{SecularFilter.tsx => SecularFilter.txt} | 0 .../{SubjectFilter.tsx => SubjectFilter.txt} | 0 8 files changed, 16 insertions(+), 13 deletions(-) rename src/components/services/{GradeFilter.tsx => GradeFilter.txt} (96%) rename src/components/services/{LocationFilter.tsx => LocationFilter.txt} (100%) rename src/components/services/{SecularFilter.tsx => SecularFilter.txt} (100%) rename src/components/services/{SubjectFilter.tsx => SubjectFilter.txt} (100%) diff --git a/src/components/posts/CreateNewPost.tsx b/src/components/posts/CreateNewPost.tsx index 760efcd5..095b5683 100644 --- a/src/components/posts/CreateNewPost.tsx +++ b/src/components/posts/CreateNewPost.tsx @@ -1167,7 +1167,9 @@ export const CreateNewPost: Component = () => { {t("formLabels.secular")}?
- + = (props: Props) => { {t("formLabels.secular")}?
- + = (props) => { }; return ( -
+
)} diff --git a/src/components/services/LocationFilter.tsx b/src/components/services/LocationFilter.txt similarity index 100% rename from src/components/services/LocationFilter.tsx rename to src/components/services/LocationFilter.txt diff --git a/src/components/services/ResourcesMain.tsx b/src/components/services/ResourcesMain.tsx index 904e2334..14f570dc 100644 --- a/src/components/services/ResourcesMain.tsx +++ b/src/components/services/ResourcesMain.tsx @@ -1,11 +1,12 @@ import type { Component } from "solid-js"; -import type { Post } from "@lib/types"; import { createSignal, createEffect, onMount, Show, onCleanup } from "solid-js"; -import { ViewCard } from "./ViewCard"; -import { MobileViewCard } from "./MobileViewCard"; -import { FiltersMobile } from "./FiltersMobile"; -import { SearchBar } from "./SearchBar"; -import { getLangFromUrl, useTranslations } from "../../i18n/utils"; + +import type { Post } from "@lib/types"; +import { ViewCard } from "@components/services/ViewCard"; +import { MobileViewCard } from "@components/services/MobileViewCard"; +import { FiltersMobile } from "@components/services/FiltersMobile"; +import { SearchBar } from "@components/services/SearchBar"; +import { getLangFromUrl, useTranslations } from "@i18n/utils"; import { useStore } from "@nanostores/solid"; import { windowSize } from "@components/common/WindowSizeStore"; import type { FilterPostsParams } from "@lib/types"; diff --git a/src/components/services/SecularFilter.tsx b/src/components/services/SecularFilter.txt similarity index 100% rename from src/components/services/SecularFilter.tsx rename to src/components/services/SecularFilter.txt diff --git a/src/components/services/SubjectFilter.tsx b/src/components/services/SubjectFilter.txt similarity index 100% rename from src/components/services/SubjectFilter.tsx rename to src/components/services/SubjectFilter.txt From 583050c542a64efa99ed03207b76e336ab70d4f6 Mon Sep 17 00:00:00 2001 From: r-southworth Date: Tue, 24 Sep 2024 15:05:18 -0400 Subject: [PATCH 06/11] tap target and subtopic improvements for CreateResource --- src/components/common/Dropdown.tsx | 6 +- src/components/posts/CreateNewPost.tsx | 255 +++++++++++++++++-------- 2 files changed, 177 insertions(+), 84 deletions(-) diff --git a/src/components/common/Dropdown.tsx b/src/components/common/Dropdown.tsx index 0c404772..8039051d 100644 --- a/src/components/common/Dropdown.tsx +++ b/src/components/common/Dropdown.tsx @@ -27,13 +27,13 @@ const Dropdown: Component = (Props: Props) => {
{/* Dropdown button */} + +
+
{ {(subtopic) => subtopic.subject_id === subject.id && ( -
{/* Grade Picker */} -
+
{/* resourceTypes Picker */} -
+