Skip to content

Commit 2f46ccd

Browse files
authored
Merge branch 'Weaverse:main' into main
2 parents 4889d4d + 1736ff7 commit 2f46ccd

12 files changed

+165
-191
lines changed

.env.example

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ SESSION_SECRET="foobar"
44
PUBLIC_STORE_DOMAIN=weaverse-hydrogen.myshopify.com
55
PUBLIC_STOREFRONT_API_TOKEN=939ac830f7c7c6d229a24dd78f4258f0
66
PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID=shp_182e9ed0-270f-473a-be74-e3073f852c39
7-
PUBLIC_CUSTOMER_ACCOUNT_API_URL=https://shopify.com/72804106547
7+
SHOP_ID=72804106547
88
PUBLIC_CHECKOUT_DOMAIN=www.weaverse.dev
99

1010
## Optional
@@ -21,7 +21,7 @@ WEAVERSE_PROJECT_ID=clptu3l4p001sxfsn1u9jzqnm
2121
# ALI_REVIEWS_API_KEY="your-ali-reviews-api-key"
2222

2323
# Custom metafields & metaobjects
24-
METAOBJECT_COLORS_TYPE="colors"
24+
METAOBJECT_COLORS_TYPE="shopify--color-pattern"
2525
METAOBJECT_COLOR_NAME_KEY="label"
2626
METAOBJECT_COLOR_VALUE_KEY="color"
2727
CUSTOM_COLLECTION_BANNER_METAFIELD="custom.collection_banner"

app/components/product/variant-option.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function VariantOption(props: VariantOptionProps) {
8484
</legend>
8585

8686
{type === "button" && (
87-
<div className="flex gap-3">
87+
<div className="flex flex-wrap gap-3">
8888
{values.map(({ value, isAvailable }) => (
8989
<button
9090
key={value}
@@ -107,7 +107,7 @@ export function VariantOption(props: VariantOptionProps) {
107107
</div>
108108
)}
109109
{type === "color" && (
110-
<div className="flex gap-3">
110+
<div className="flex flex-wrap gap-3">
111111
{values.map(({ value, isAvailable }) => {
112112
let swatchColor = colorsSwatches.find(({ name }) => name === value);
113113
return (
@@ -142,7 +142,7 @@ export function VariantOption(props: VariantOptionProps) {
142142
</div>
143143
)}
144144
{type === "custom-image" && (
145-
<div className="flex gap-3">
145+
<div className="flex flex-wrap gap-3">
146146
{values.map(({ value, image, isAvailable }) => {
147147
let swatchImage = swatches.images.find(
148148
(i) => i.name.toLowerCase() === value.toLowerCase()
@@ -197,7 +197,7 @@ export function VariantOption(props: VariantOptionProps) {
197197
</div>
198198
)}
199199
{type === "variant-image" && (
200-
<div className="flex gap-3">
200+
<div className="flex flex-wrap gap-3">
201201
{values.map(({ value, image, isAvailable }) => {
202202
let aspectRatio = "1/1";
203203
if (image && shape !== "circle") {
@@ -253,7 +253,7 @@ export function VariantOption(props: VariantOptionProps) {
253253
</select>
254254
)}
255255
{type === "default" && (
256-
<div className="flex gap-3">
256+
<div className="flex flex-wrap gap-3">
257257
{values.map((value) => (
258258
<span
259259
key={value.value}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { RouteLoaderArgs } from "@weaverse/hydrogen";
2+
import invariant from "tiny-invariant";
3+
import { getJudgemeReviews } from "~/utils/judgeme";
4+
5+
export async function loader({
6+
params: { productHandle },
7+
context: { env, weaverse },
8+
}: RouteLoaderArgs) {
9+
invariant(productHandle, "Missing product handle");
10+
11+
return await getJudgemeReviews(
12+
env.JUDGEME_PRIVATE_API_TOKEN,
13+
env.PUBLIC_STORE_DOMAIN,
14+
productHandle,
15+
weaverse
16+
);
17+
}

app/routes/($locale).api.review.$productHandle.tsx

-18
This file was deleted.

app/routes/($locale).collections.$collectionHandle.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export async function loader({ params, request, context }: LoaderFunctionArgs) {
3838
invariant(collectionHandle, "Missing collectionHandle param");
3939

4040
let searchParams = new URL(request.url).searchParams;
41-
4241
let { sortKey, reverse } = getSortValuesFromParam(
4342
searchParams.get("sort") as SortParam
4443
);
@@ -53,8 +52,9 @@ export async function loader({ params, request, context }: LoaderFunctionArgs) {
5352
}, [] as ProductFilter[]);
5453

5554
let { CUSTOM_COLLECTION_BANNER_METAFIELD = "" } = env;
56-
let [bannerNamespace, bannerKey] =
55+
let [bannerNamespace = "", bannerKey = ""] =
5756
CUSTOM_COLLECTION_BANNER_METAFIELD.split(".");
57+
5858
let { collection, collections } = await storefront
5959
.query<CollectionDetailsQuery>(COLLECTION_QUERY, {
6060
variables: {

app/routes/($locale).products.$productHandle.tsx

+27-59
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useLoaderData, useSearchParams } from "@remix-run/react";
2-
import { Analytics, type Storefront, getSeoMeta } from "@shopify/hydrogen";
2+
import { Analytics, getSeoMeta } from "@shopify/hydrogen";
33
import type {
44
ActionFunctionArgs,
55
LoaderFunctionArgs,
@@ -8,20 +8,13 @@ import type {
88
import { defer, json } from "@shopify/remix-oxygen";
99
import { getSelectedProductOptions } from "@weaverse/hydrogen";
1010
import { useEffect } from "react";
11-
import type {
12-
ProductQuery,
13-
ProductRecommendationsQuery,
14-
} from "storefrontapi.generated";
11+
import type { ProductQuery } from "storefrontapi.generated";
1512
import invariant from "tiny-invariant";
13+
import { PRODUCT_QUERY, VARIANTS_QUERY } from "~/graphql/queries";
1614
import { routeHeaders } from "~/utils/cache";
17-
import {
18-
PRODUCT_QUERY,
19-
RECOMMENDED_PRODUCTS_QUERY,
20-
VARIANTS_QUERY,
21-
} from "~/graphql/queries";
22-
import { createJudgemeReview, getJudgemeReviews } from "~/utils/judgeme";
15+
import { createJudgeMeReview, getJudgemeReviews } from "~/utils/judgeme";
16+
import { getRecommendedProducts } from "~/utils/product";
2317
import { seoPayload } from "~/utils/seo.server";
24-
import type { I18nLocale } from "~/types/locale";
2518
import { WeaverseContent } from "~/weaverse";
2619

2720
export let headers = routeHeaders;
@@ -104,23 +97,26 @@ export async function loader({ params, request, context }: LoaderFunctionArgs) {
10497
});
10598
}
10699

107-
export type ProductLoaderType = typeof loader;
108-
109-
export async function action({ request, context }: ActionFunctionArgs) {
110-
const formData = await request.formData();
111-
let judgeme_API_TOKEN = context.env.JUDGEME_PRIVATE_API_TOKEN;
112-
invariant(judgeme_API_TOKEN, "Missing JUDGEME_PRIVATE_API_TOKEN");
113-
let response: any = {
114-
status: 201,
115-
};
116-
let shop_domain = context.env.PUBLIC_STORE_DOMAIN;
117-
response = await createJudgemeReview(
118-
judgeme_API_TOKEN,
119-
shop_domain,
120-
formData
121-
);
122-
const { status, ...rest } = response;
123-
return json(rest, { status });
100+
export async function action({
101+
request,
102+
context: { env },
103+
}: ActionFunctionArgs) {
104+
try {
105+
invariant(
106+
env.JUDGEME_PRIVATE_API_TOKEN,
107+
"Missing `JUDGEME_PRIVATE_API_TOKEN`"
108+
);
109+
110+
let response = await createJudgeMeReview({
111+
formData: await request.formData(),
112+
apiToken: env.JUDGEME_PRIVATE_API_TOKEN,
113+
shopDomain: env.PUBLIC_STORE_DOMAIN,
114+
});
115+
return json(response);
116+
} catch (error) {
117+
console.error(error);
118+
return json({ error: "Failed to create review!" }, { status: 500 });
119+
}
124120
}
125121

126122
export let meta = ({ matches }: MetaArgs<typeof loader>) => {
@@ -130,7 +126,7 @@ export let meta = ({ matches }: MetaArgs<typeof loader>) => {
130126
/**
131127
* We need to handle the route change from client to keep the view transition persistent
132128
*/
133-
let useApplyFirstVariant = () => {
129+
function useApplyFirstVariant() {
134130
let { product } = useLoaderData<typeof loader>();
135131
let [searchParams, setSearchParams] = useSearchParams();
136132

@@ -146,7 +142,7 @@ let useApplyFirstVariant = () => {
146142
});
147143
}
148144
}, [product?.id]);
149-
};
145+
}
150146

151147
export default function Product() {
152148
useApplyFirstVariant();
@@ -174,31 +170,3 @@ export default function Product() {
174170
</>
175171
);
176172
}
177-
178-
async function getRecommendedProducts(
179-
storefront: Storefront<I18nLocale>,
180-
productId: string
181-
) {
182-
let products = await storefront.query<ProductRecommendationsQuery>(
183-
RECOMMENDED_PRODUCTS_QUERY,
184-
{
185-
variables: { productId, count: 12 },
186-
}
187-
);
188-
189-
invariant(products, "No data returned from Shopify API");
190-
191-
let mergedProducts = (products.recommended ?? [])
192-
.concat(products.additional.nodes)
193-
.filter((prod, idx, arr) => {
194-
return arr.findIndex(({ id }) => id === prod.id) === idx;
195-
});
196-
197-
let originalProduct = mergedProducts.findIndex(
198-
(item) => item.id === productId
199-
);
200-
201-
mergedProducts.splice(originalProduct, 1);
202-
203-
return { nodes: mergedProducts };
204-
}

app/sections/judgeme-reviews/review-form.tsx

+20-22
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,29 @@ import clsx from "clsx";
44
import { type FormEvent, useEffect, useRef, useState } from "react";
55
import { Button } from "~/components/button";
66
import { StarRating } from "~/components/star-rating";
7+
import type { loader as productRouteLoader } from "~/routes/($locale).products.$productHandle";
78
import type { JudgemeReviewsData } from "~/utils/judgeme";
8-
import type { ProductLoaderType } from "~/routes/($locale).products.$productHandle";
99

1010
export function ReviewForm({
1111
judgemeReviews,
1212
}: {
1313
judgemeReviews: JudgemeReviewsData;
1414
}) {
15-
const { product } = useLoaderData<ProductLoaderType>();
16-
const [rating, setRating] = useState(0);
17-
const [hover, setHover] = useState(0);
18-
const [isFormVisible, setIsFormVisible] = useState(false);
19-
const [isPopupVisible, setIsPopupVisible] = useState(false);
20-
const fetcher = useFetcher<any>();
21-
const formRef = useRef<HTMLFormElement>(null);
15+
let { product } = useLoaderData<typeof productRouteLoader>();
16+
let [rating, setRating] = useState(0);
17+
let [hover, setHover] = useState(0);
18+
let [isFormVisible, setIsFormVisible] = useState(false);
19+
let [isPopupVisible, setIsPopupVisible] = useState(false);
20+
let fetcher = useFetcher<any>();
21+
let formRef = useRef<HTMLFormElement>(null);
2222
let [message, setMessage] = useState("");
23-
const internalId = product.id.split("gid://shopify/Product/")[1];
24-
const submittable = rating > 0;
23+
let internalId = product.id.split("gid://shopify/Product/")[1];
24+
let submittable = rating > 0;
2525

2626
useEffect(() => {
2727
if (fetcher.data) {
28-
setMessage((fetcher.data as any)?.message || "");
29-
if (fetcher.data.success) {
28+
// setMessage((fetcher.data as Response)?.message || "");
29+
if ((fetcher.data as Response).ok) {
3030
setIsFormVisible(false);
3131
setIsPopupVisible(true);
3232
setRating(0);
@@ -36,14 +36,6 @@ export function ReviewForm({
3636
}
3737
}, [fetcher.data]);
3838

39-
const handleRatingClick = (value: number) => {
40-
setRating(value);
41-
};
42-
43-
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
44-
fetcher.submit(event.currentTarget);
45-
};
46-
4739
return (
4840
<div
4941
className={clsx(
@@ -116,7 +108,11 @@ export function ReviewForm({
116108
return (
117109
<div
118110
key={index}
119-
onClick={() => handleRatingClick(ratingValue)}
111+
onClick={() =>
112+
((value: number) => {
113+
setRating(value);
114+
})(ratingValue)
115+
}
120116
onMouseEnter={() => setHover(ratingValue)}
121117
onMouseLeave={() => setHover(0)}
122118
aria-label={`Rate ${ratingValue} out of 5 stars`}
@@ -134,7 +130,9 @@ export function ReviewForm({
134130
</div>
135131
{/* Review Form */}
136132
<fetcher.Form
137-
onSubmit={handleSubmit}
133+
onSubmit={(event: FormEvent<HTMLFormElement>) => {
134+
fetcher.submit(event.currentTarget);
135+
}}
138136
ref={formRef}
139137
method="POST"
140138
encType="multipart/form-data"

app/sections/judgeme-reviews/review-index.tsx

+14-12
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
import { HydrogenComponentSchema } from "@weaverse/hydrogen";
1+
import { useLoaderData } from "@remix-run/react";
2+
import type { HydrogenComponentSchema } from "@weaverse/hydrogen";
23
import { forwardRef } from "react";
4+
import type { loader as productRouteLoader } from "~/routes/($locale).products.$productHandle";
35
import ReviewForm from "./review-form";
46
import { ReviewList } from "./review-list";
5-
import { useLoaderData } from "@remix-run/react";
6-
import { ProductLoaderType } from "~/routes/($locale).products.$productHandle";
77

8-
type ReviewIndexProps = {};
9-
const ReviewIndex = forwardRef<HTMLDivElement, ReviewIndexProps>((props, ref) => {
10-
let { ...rest } = props;
11-
const { judgemeReviews } = useLoaderData<ProductLoaderType>();
8+
let ReviewIndex = forwardRef<HTMLDivElement>((props, ref) => {
9+
let { judgemeReviews } = useLoaderData<typeof productRouteLoader>();
1210
return (
13-
<div ref={ref} {...rest} className="flex flex-col md:flex-row md:gap-10 gap-5">
14-
<ReviewForm judgemeReviews={judgemeReviews}/>
15-
{judgemeReviews.reviews.length > 0 && (
16-
<ReviewList judgemeReviews={judgemeReviews}/>
17-
)}
11+
<div
12+
ref={ref}
13+
{...props}
14+
className="flex flex-col md:flex-row md:gap-10 gap-5"
15+
>
16+
<ReviewForm judgemeReviews={judgemeReviews} />
17+
{judgemeReviews.reviews.length > 0 ? (
18+
<ReviewList judgemeReviews={judgemeReviews} />
19+
) : null}
1820
</div>
1921
);
2022
});

0 commit comments

Comments
 (0)