diff --git a/.env.example b/.env.example index f3935649..b86c4261 100644 --- a/.env.example +++ b/.env.example @@ -7,20 +7,21 @@ PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID=shp_182e9ed0-270f-473a-be74-e3073f852c39 PUBLIC_CUSTOMER_ACCOUNT_API_URL=https://shopify.com/72804106547 PUBLIC_CHECKOUT_DOMAIN=www.weaverse.dev -## optional +## Optional PUBLIC_STOREFRONT_ID=1000010106 -#PRIVATE_STOREFRONT_API_TOKEN="your-private-storefront-api-token" +# PRIVATE_STOREFRONT_API_TOKEN="your-private-storefront-api-token" -# Weaverse setup +# Weaverse WEAVERSE_PROJECT_ID=clptu3l4p001sxfsn1u9jzqnm -#WEAVERSE_API_KEY="your-weaverse-api-key" +# WEAVERSE_API_KEY="your-weaverse-api-key" # Additional services -#PUBLIC_GOOGLE_GTM_ID=G-R1KFYYKE48 -#JUDGEME_PRIVATE_API_TOKEN="your-judgeme-private-api-token" -#ALI_REVIEWS_API_KEY="your-ali-reviews-api-key" +# PUBLIC_GOOGLE_GTM_ID=G-R1KFYYKE48 +# JUDGEME_PRIVATE_API_TOKEN="your-judgeme-private-api-token" +# ALI_REVIEWS_API_KEY="your-ali-reviews-api-key" -CUSTOM_COLLECTION_BANNER_METAFIELD="custom.collection_banner" +# Custom metafields & metaobjects METAOBJECT_COLORS_TYPE="colors" METAOBJECT_COLOR_NAME_KEY="label" METAOBJECT_COLOR_VALUE_KEY="color" +CUSTOM_COLLECTION_BANNER_METAFIELD="custom.collection_banner" diff --git a/.gitignore b/.gitignore index a4fba8fb..3399816a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ node_modules /playwright-report/ /playwright/.cache/ pnpm-lock.yaml +.DS_Store diff --git a/app/components/account/account-details.tsx b/app/components/account/account-details.tsx index 73226a74..32eb0a98 100644 --- a/app/components/account/account-details.tsx +++ b/app/components/account/account-details.tsx @@ -6,29 +6,37 @@ export function AccountDetails({ }: { customer: CustomerDetailsFragment; }) { - const { firstName, lastName, emailAddress, phoneNumber } = customer; - const fullName = `${firstName || ""} ${lastName || ""}`.trim(); + let { firstName, lastName, emailAddress, phoneNumber } = customer; + let fullName = `${firstName || ""} ${lastName || ""}`.trim(); return (
Account
-
-
Name
-

{fullName || "N/A"}

+
+
+
Name
+
{fullName || "N/A"}
+
-
Phone number
-

{phoneNumber?.phoneNumber ?? "N/A"}

+
+
Phone number
+
{phoneNumber?.phoneNumber ?? "N/A"}
+
-
Email address
-

{emailAddress?.emailAddress ?? "N/A"}

-

+

+
Email address
+
{emailAddress?.emailAddress ?? "N/A"}
+
+ +
- Edit + Edit account details -

+
); diff --git a/app/components/account/address-book.tsx b/app/components/account/address-book.tsx index 933225fc..19e20210 100644 --- a/app/components/account/address-book.tsx +++ b/app/components/account/address-book.tsx @@ -2,7 +2,7 @@ import { Form } from "@remix-run/react"; import type { CustomerAddress } from "@shopify/hydrogen/customer-account-api-types"; import type { CustomerDetailsFragment } from "customer-accountapi.generated"; import { Link } from "~/components/link"; -import { Text } from "../../modules/text"; +import { Button } from "~/components/button"; export function AccountAddressBook({ customer, @@ -14,18 +14,16 @@ export function AccountAddressBook({ return (
Address Book
-
+
{!addresses?.length && ( - - You haven't saved any addresses yet. - +
You haven't saved any addresses yet.
)}
- + Add an Address
- {Boolean(addresses?.length) && ( + {addresses?.length > 0 ? (
{customer.defaultAddress && (
@@ -36,7 +34,7 @@ export function AccountAddressBook({
))}
- )} + ) : null}
); @@ -50,10 +48,10 @@ function Address({ defaultAddress?: boolean; }) { return ( -
+
{defaultAddress && (
- + Default
@@ -74,16 +72,21 @@ function Address({
Edit
- +
diff --git a/app/components/account/edit-address-form.tsx b/app/components/account/edit-address-form.tsx new file mode 100644 index 00000000..2bfaf3a4 --- /dev/null +++ b/app/components/account/edit-address-form.tsx @@ -0,0 +1,198 @@ +import { Check } from "@phosphor-icons/react"; +import * as Checkbox from "@radix-ui/react-checkbox"; +import * as Dialog from "@radix-ui/react-dialog"; +import { + Form, + useActionData, + useNavigation, + useOutletContext, + useParams, +} from "@remix-run/react"; +import { flattenConnection } from "@shopify/hydrogen"; +import clsx from "clsx"; +import { Button } from "~/components/button"; +import Link from "~/components/link"; +import type { AccountOutletContext } from "~/routes/($locale).account.edit"; + +export function AccountEditAddressForm() { + let { id: addressId } = useParams(); + let isNewAddress = addressId === "add"; + let actionData = useActionData<{ formError?: string }>(); + let { state } = useNavigation(); + let { customer } = useOutletContext(); + let addresses = flattenConnection(customer.addresses); + let defaultAddress = customer.defaultAddress; + /** + * When a refresh happens (or a user visits this link directly), the URL + * is actually stale because it contains a special token. This means the data + * loaded by the parent and passed to the outlet contains a newer, fresher token, + * and we don't find a match. We update the `find` logic to just perform a match + * on the first (permanent) part of the ID. + */ + let normalizedAddress = decodeURIComponent(addressId ?? "").split("?")[0]; + let address = addresses.find((address) => + address.id?.startsWith(normalizedAddress), + ); + + return ( +
+
+ {isNewAddress ? "Add new address" : "Edit address"} +
+
+ + {actionData?.formError && ( +
+ {actionData.formError} +
+ )} + + + + + + + + + + +
+ + + + + + +
+
+ + + Cancel + + + +
+
+
+ ); +} diff --git a/app/components/account/order-details.tsx b/app/components/account/order-details.tsx new file mode 100644 index 00000000..4384ef7d --- /dev/null +++ b/app/components/account/order-details.tsx @@ -0,0 +1,182 @@ +import { ArrowLeft, Tag } from "@phosphor-icons/react"; +import { useLoaderData } from "@remix-run/react"; +import { Image, Money } from "@shopify/hydrogen"; +import clsx from "clsx"; +import { Link } from "~/components/link"; +import { Section } from "~/components/section"; +import { statusMessage } from "~/lib/utils"; +import type { loader as orderDetailsLoader } from "~/routes/($locale).account.orders.$id"; + +export function OrderDetails() { + let { order, lineItems, fulfillmentStatus } = + useLoaderData(); + + let totalDiscount = 0; + for (let lineItem of lineItems) { + totalDiscount += lineItem.discountAllocations.reduce( + (acc, curr) => acc + Number.parseFloat(curr.allocatedAmount.amount), + 0, + ); + } + + let totalDiscountMoney = { + amount: totalDiscount.toString(), + currencyCode: order.totalPrice?.currencyCode, + }; + + return ( +
+
+
+

Order Detail

+ + + Return to My Account + +
+
+

Order No. {order.name}

+

+ Placed on {new Date(order.processedAt).toDateString()} +

+
+
+ {lineItems.map((lineItem) => { + let hasDiscount = false; + if ( + lineItem.currentTotalPrice?.amount !== + lineItem.totalPrice?.amount + ) { + hasDiscount = true; + } + return ( +
+ {lineItem?.image && ( +
+ +
+ )} +
+
Product
+
+
{lineItem.title}
+
+ {lineItem.variantTitle} +
+
+
Quantity
+
+ x{lineItem.quantity} +
+
Discount
+
+ {lineItem.discountAllocations.map((discount, index) => { + let discountApp = discount.discountApplication as any; + let discountTitle = + discountApp?.title || discountApp?.code; + return ( +
+ + {discountTitle} +
+ (- + ) +
+
+ ); + })} +
+
Current Price
+
+ {hasDiscount && ( + + + + )} + +
+
+
+ ); + })} +
+
+
+ Subtotal + + + +
+
+ Tax + + + +
+
+ Shipping + + + +
+
+
+ Total + + + +
+
+
+ + + Total savings + +
+ + + +
+
+
+
+
Shipping Address
+ {order?.shippingAddress ? ( +
    +
  • {order.shippingAddress.name}
  • + {order?.shippingAddress?.formatted + ? order.shippingAddress.formatted.map((line: string) => ( +
  • + {line} +
  • + )) + : null} +
+ ) : ( +

No shipping address defined

+ )} +
Status
+ {fulfillmentStatus && ( +
+ {statusMessage(fulfillmentStatus)} +
+ )} +
+
+
+
+
+ ); +} diff --git a/app/components/account/orders.tsx b/app/components/account/orders.tsx index 260cd462..a023f8f8 100644 --- a/app/components/account/orders.tsx +++ b/app/components/account/orders.tsx @@ -1,8 +1,7 @@ import { Image, flattenConnection } from "@shopify/hydrogen"; import type { OrderCardFragment } from "customer-accountapi.generated"; import Link from "~/components/link"; -import { statusMessage, usePrefixPathWithLocale } from "~/lib/utils"; -import { Text } from "~/modules/text"; +import { statusMessage } from "~/lib/utils"; type OrderCardsProps = { orders: OrderCardFragment[]; @@ -18,18 +17,7 @@ export function AccountOrderHistory({ orders }: OrderCardsProps) { } function EmptyOrders() { - return ( -
- - You haven't placed any orders yet. - -
- - Start Shopping - -
-
- ); + return
You haven't placed any orders yet.
; } function Orders({ orders }: OrderCardsProps) { @@ -45,7 +33,7 @@ function Orders({ orders }: OrderCardsProps) { function OrderCard({ order }: { order: OrderCardFragment }) { if (!order?.id) return null; - let [legacyOrderId, key] = order!.id!.split("/").pop()!.split("?"); + let [legacyOrderId, key] = order.id.split("/").pop().split("?"); let lineItems = flattenConnection(order?.lineItems); let fulfillmentStatus = flattenConnection(order?.fulfillments)[0]?.status; let orderLink = key @@ -53,18 +41,16 @@ function OrderCard({ order }: { order: OrderCardFragment }) { : `/account/orders/${legacyOrderId}`; return ( -
  • +
  • {lineItems[0].image && ( -
    - {lineItems[0].image?.altText -
    + {lineItems[0].image?.altText )}
    - {/* - {lineItems.length > 1 - ? `${lineItems[0].title} +${lineItems.length - 1} more` - : lineItems[0].title} - */} -

    +

    {lineItems.length > 1 ? `${lineItems[0].title} +${lineItems.length - 1} more` : lineItems[0].title} -

    +
    Order ID
    @@ -104,9 +85,10 @@ function OrderCard({ order }: { order: OrderCardFragment }) { )} View details diff --git a/app/components/account/outlet-modal.tsx b/app/components/account/outlet-modal.tsx new file mode 100644 index 00000000..ad2eb479 --- /dev/null +++ b/app/components/account/outlet-modal.tsx @@ -0,0 +1,51 @@ +import { X } from "@phosphor-icons/react"; +import * as Dialog from "@radix-ui/react-dialog"; +import { clsx } from "clsx"; +import Link from "~/components/link"; +import * as VisuallyHidden from "@radix-ui/react-visually-hidden"; + +export function OutletModal({ + children, + cancelLink, +}: { children: React.ReactNode; cancelLink: string }) { + return ( + + + + +
    + + Account modal + + {children} + + + + + +
    +
    +
    +
    + ); +} diff --git a/app/components/button.tsx b/app/components/button.tsx index 9bab6d38..c07dd761 100644 --- a/app/components/button.tsx +++ b/app/components/button.tsx @@ -22,7 +22,7 @@ export let variants = cva( "border-[--btn-primary-bg]", "hover:text-[--btn-primary-bg]", "hover:bg-[--btn-primary-text]", - "hover:border-[--btn-primary-text]", + "hover:border-[--btn-primary-bg]", ], secondary: [ "border px-4 py-3", @@ -55,7 +55,7 @@ export let variants = cva( "bg-transparent pb-1 text-body", "after:bg-body after:absolute after:left-0 after:bottom-0.5 after:w-full after:h-px", "after:scale-x-100 after:transition-transform after:origin-right", - "hover:after:origin-left hover:after:animate-underline", + "hover:after:origin-left hover:after:animate-underline-toggle", ], }, }, @@ -83,6 +83,7 @@ export interface ButtonProps disabled?: boolean; loading?: boolean; children?: React.ReactNode; + animate?: boolean; } export let Button = forwardRef((props, ref) => { @@ -98,6 +99,7 @@ export let Button = forwardRef((props, ref) => { backgroundColorHover, borderColorHover, style = {}, + animate = true, children, ...rest } = props; @@ -124,12 +126,15 @@ export let Button = forwardRef((props, ref) => { content = children; } + if (animate) { + rest["data-motion"] = "fade-up"; + } + return ( +
    + +
    + {error && ( +
    +

    ERROR:

    +

    {error}

    +
    + )} + {message && ( +
    +

    {message}

    +
    + )} +
    +
  • +
    @@ -147,56 +213,6 @@ export function Footer() { ); } -function NewsLetter() { - let { - newsletterTitle, - newsletterDescription, - newsletterPlaceholder, - newsletterButtonText, - } = useThemeSettings(); - - let fetcher = useFetcher(); - let { state, Form } = fetcher; - let data = fetcher.data as CustomerApiPlayLoad; - let { ok, errorMessage } = data || {}; - - return ( -
    -
    {newsletterTitle}
    -
    -

    {newsletterDescription}

    -
    - - -
    -
    - {ok - ? "🎉 Thank you for subscribing!" - : errorMessage || "Something went wrong!"} -
    -
    -
    - ); -} - function FooterMenu() { let { footerMenu } = useShopMenu(); let items = footerMenu.items as unknown as SingleMenuItem[]; @@ -222,16 +238,16 @@ function FooterMenu() {
    diff --git a/app/components/link.tsx b/app/components/link.tsx index 697027e0..3a054f6b 100644 --- a/app/components/link.tsx +++ b/app/components/link.tsx @@ -57,7 +57,7 @@ let variants = cva(["transition-colors inline-flex"], { "relative bg-transparent pb-1 text-body", "after:bg-body after:absolute after:left-0 after:bottom-0.5 after:w-full after:h-px", "after:scale-x-100 after:transition-transform after:origin-right", - "hover:after:origin-left hover:after:animate-underline", + "hover:after:origin-left hover:after:animate-underline-toggle", ], }, }, diff --git a/app/components/modal.tsx b/app/components/modal.tsx index 39ad8689..f4ab8861 100644 --- a/app/components/modal.tsx +++ b/app/components/modal.tsx @@ -20,7 +20,7 @@ export let Modal: React.FC = Root; export let ModalTrigger = forwardRef( ({ asChild = true, ...rest }, ref) => { return ; - } + }, ); interface ModalContentProps extends DialogContentProps {} @@ -34,15 +34,15 @@ export let ModalContent = forwardRef( {...rest} ref={ref} className={cn( - "data-[state='open']:animate-slide-down-and-fade", - "fixed inset-0 z-10 flex items-center overflow-x-hidden bg-gray-900/50 px-4" + "data-[state='open']:animate-slide-up", + "fixed inset-0 z-10 flex items-center overflow-x-hidden bg-gray-900/50 px-4", )} >
    @@ -53,7 +53,7 @@ export let ModalContent = forwardRef( ); - } + }, ); export let ModalClose = forwardRef( @@ -67,5 +67,5 @@ export let ModalClose = forwardRef( )} ); - } + }, ); diff --git a/app/components/tooltip.tsx b/app/components/tooltip.tsx index 6886a94e..4ec27994 100644 --- a/app/components/tooltip.tsx +++ b/app/components/tooltip.tsx @@ -29,7 +29,7 @@ export let TooltipContent = forwardRef( ( collisionPadding={8} style={ { - "--slide-down-and-fade-duration": "0.3s", + "--slide-up-from": "6px", + "--slide-up-duration": "0.3s", ...style, } as React.CSSProperties } diff --git a/app/data/fragments.ts b/app/data/fragments.ts index 76438169..6012066c 100644 --- a/app/data/fragments.ts +++ b/app/data/fragments.ts @@ -276,32 +276,3 @@ export const CART_QUERY_FRAGMENT = `#graphql } } ` as const; - -export const ORDER_CARD_FRAGMENT = `#graphql - fragment OrderCard on Order { - id - orderNumber - processedAt - financialStatus - fulfillmentStatus - currentTotalPrice { - amount - currencyCode - } - lineItems(first: 2) { - edges { - node { - variant { - image { - url - altText - height - width - } - } - title - } - } - } - } -`; \ No newline at end of file diff --git a/app/graphql/customer-account/customer-address-mutations.ts b/app/graphql/customer-account/customer-address-mutations.ts deleted file mode 100644 index b6c6c53d..00000000 --- a/app/graphql/customer-account/customer-address-mutations.ts +++ /dev/null @@ -1,58 +0,0 @@ -// NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerAddressUpdate -export const UPDATE_ADDRESS_MUTATION = `#graphql - mutation customerAddressUpdate( - $address: CustomerAddressInput! - $addressId: ID! - $defaultAddress: Boolean - ) { - customerAddressUpdate( - address: $address - addressId: $addressId - defaultAddress: $defaultAddress - ) { - userErrors { - code - field - message - } - } - } -` as const; - -// NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerAddressDelete -export const DELETE_ADDRESS_MUTATION = `#graphql - mutation customerAddressDelete( - $addressId: ID!, - ) { - customerAddressDelete(addressId: $addressId) { - deletedAddressId - userErrors { - code - field - message - } - } - } -` as const; - -// NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerAddressCreate -export const CREATE_ADDRESS_MUTATION = `#graphql - mutation customerAddressCreate( - $address: CustomerAddressInput! - $defaultAddress: Boolean - ) { - customerAddressCreate( - address: $address - defaultAddress: $defaultAddress - ) { - customerAddress { - id - } - userErrors { - code - field - message - } - } - } -` as const; diff --git a/app/graphql/customer-account/customer-details-query.tsx b/app/graphql/customer-account/customer-details-query.tsx deleted file mode 100644 index 05ed3a41..00000000 --- a/app/graphql/customer-account/customer-details-query.tsx +++ /dev/null @@ -1,83 +0,0 @@ -const CUSTOMER_FRAGMENT = `#graphql - fragment OrderCard on Order { - id - number - processedAt - financialStatus - fulfillments(first: 1) { - nodes { - status - } - } - totalPrice { - amount - currencyCode - } - lineItems(first: 2) { - edges { - node { - title - image { - altText - height - url - width - } - } - } - } - } - - fragment AddressPartial on CustomerAddress { - id - formatted - firstName - lastName - company - address1 - address2 - territoryCode - zoneCode - city - zip - phoneNumber - } - - fragment CustomerDetails on Customer { - firstName - lastName - phoneNumber { - phoneNumber - } - emailAddress { - emailAddress - } - defaultAddress { - ...AddressPartial - } - addresses(first: 6) { - edges { - node { - ...AddressPartial - } - } - } - orders(first: 250, sortKey: PROCESSED_AT, reverse: true) { - edges { - node { - ...OrderCard - } - } - } - } -` as const; - -// NOTE: https://shopify.dev/docs/api/customer/latest/queries/customer -export const CUSTOMER_DETAILS_QUERY = `#graphql - query CustomerDetails { - customer { - ...CustomerDetails - } - } - ${CUSTOMER_FRAGMENT} -` as const; diff --git a/app/graphql/customer-account/customer-order-query.ts b/app/graphql/customer-account/customer-order-query.ts deleted file mode 100644 index cb755684..00000000 --- a/app/graphql/customer-account/customer-order-query.ts +++ /dev/null @@ -1,102 +0,0 @@ -// NOTE: https://shopify.dev/docs/api/customer/latest/queries/order -export const CUSTOMER_ORDER_QUERY = `#graphql - fragment OrderMoney on MoneyV2 { - amount - currencyCode - } - fragment DiscountApplication on DiscountApplication { - ... on AutomaticDiscountApplication { - title - } - ... on DiscountCodeApplication { - code - } - value { - __typename - ... on MoneyV2 { - ...OrderMoney - } - ... on PricingPercentageValue { - percentage - } - } - } - fragment OrderLineItemFull on LineItem { - id - title - quantity - price { - ...OrderMoney - } - currentTotalPrice { - ...OrderMoney - } - totalPrice { - ...OrderMoney - } - discountAllocations { - allocatedAmount { - ...OrderMoney - } - discountApplication { - ...DiscountApplication - } - } - totalDiscount { - ...OrderMoney - } - image { - altText - height - url - id - width - } - variantTitle - } - fragment Order on Order { - id - name - statusPageUrl - processedAt - fulfillments(first: 1) { - nodes { - status - } - } - totalTax { - ...OrderMoney - } - totalPrice { - ...OrderMoney - } - subtotal { - ...OrderMoney - } - totalShipping { - ...OrderMoney - } - shippingAddress { - name - formatted(withName: true) - formattedArea - } - discountApplications(first: 100) { - nodes { - ...DiscountApplication - } - } - lineItems(first: 100) { - nodes { - ...OrderLineItemFull - } - } - } - query Order($orderId: ID!) { - order(id: $orderId) { - ... on Order { - ...Order - } - } - } -` as const; diff --git a/app/graphql/customer-account/customer-update-mutation.tsx b/app/graphql/customer-account/customer-update-mutation.tsx deleted file mode 100644 index 02e2f7f4..00000000 --- a/app/graphql/customer-account/customer-update-mutation.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export const CUSTOMER_UPDATE_MUTATION = `#graphql -mutation customerUpdate($customer: CustomerUpdateInput!) { - customerUpdate(input: $customer) { - userErrors { - code - field - message - } - } -} -`; diff --git a/app/hooks/use-animation.ts b/app/hooks/use-animation.ts index 5e594444..d28f4cf3 100644 --- a/app/hooks/use-animation.ts +++ b/app/hooks/use-animation.ts @@ -27,21 +27,23 @@ export function useAnimation(ref?: ForwardedRef) { } if (scope.current) { scope.current.classList.add("animated-scope"); - scope.current - .querySelectorAll("[data-motion]") - .forEach((elem: HTMLElement, idx: number) => { - inView( - elem, - ({ target }) => { - let { motion, delay } = elem.dataset; - animate(target, ANIMATIONS[motion || "fade-up"], { - delay: Number(delay) || idx * 0.15, - duration: 0.5, - }); - }, - { amount: 0.3 }, - ); - }); + let elems = scope.current.querySelectorAll("[data-motion]"); + elems.forEach((elem: HTMLElement, idx: number) => { + inView( + elem, + ({ target }) => { + let { motion, delay } = elem.dataset; + animate(target, ANIMATIONS[motion || "fade-up"], { + delay: Number(delay) || idx * 0.15, + duration: 0.5, + }); + if (idx === elems.length - 1) { + scope.current.classList.remove("animated-scope"); + } + }, + { amount: 0.3 }, + ); + }); } }, []); diff --git a/app/hooks/use-closest-weaverse-item.ts b/app/hooks/use-closest-weaverse-item.ts index 943b9435..6b2b8df3 100644 --- a/app/hooks/use-closest-weaverse-item.ts +++ b/app/hooks/use-closest-weaverse-item.ts @@ -1,22 +1,19 @@ import { useItemInstance } from "@weaverse/hydrogen"; -import { useEffect, useState } from "react"; +import { type RefObject, useEffect, useState } from "react"; -export function useClosestWeaverseItem(selector: string) { +export function useClosestWeaverseItem(ref: RefObject) { let [weaverseId, setWeaverseId] = useState(""); let weaverseItem = useItemInstance(weaverseId); // biome-ignore lint/correctness/useExhaustiveDependencies: assuming `selector` does not change useEffect(() => { - if (!weaverseItem) { - let target = document.querySelector(selector); - if (target) { - let closest = target.closest("[data-wv-id]"); - if (closest) { - setWeaverseId(closest.getAttribute("data-wv-id")); - } + if (!weaverseItem && ref.current) { + let closest = (ref.current as HTMLElement).closest("[data-wv-id]"); + if (closest) { + setWeaverseId(closest.getAttribute("data-wv-id")); } } - }, []); + }, [ref]); return weaverseItem; } diff --git a/app/hooks/use-shop-menu.ts b/app/hooks/use-shop-menu.ts index a3e13039..8836e348 100644 --- a/app/hooks/use-shop-menu.ts +++ b/app/hooks/use-shop-menu.ts @@ -1,7 +1,28 @@ import { useRouteLoaderData } from "@remix-run/react"; -import type { EnhancedMenu } from "~/lib/utils"; +import type { + ChildMenuItemFragment, + MenuFragment, + ParentMenuItemFragment, +} from "storefrontapi.generated"; import type { RootLoader } from "~/root"; +type EnhancedMenuItemProps = { + to: string; + target: string; + isExternal?: boolean; +}; + +type ChildEnhancedMenuItem = ChildMenuItemFragment & EnhancedMenuItemProps; + +type ParentEnhancedMenuItem = (ParentMenuItemFragment & + EnhancedMenuItemProps) & { + items: ChildEnhancedMenuItem[]; +}; + +export type EnhancedMenu = Pick & { + items: ParentEnhancedMenuItem[]; +}; + export function useShopMenu() { let { layout } = useRouteLoaderData("root"); let shopName = layout?.shop?.name; diff --git a/app/lib/root.ts b/app/lib/root.ts index da0387d0..0c4a4873 100644 --- a/app/lib/root.ts +++ b/app/lib/root.ts @@ -8,8 +8,8 @@ import type { } from "storefrontapi.generated"; import invariant from "tiny-invariant"; import { COLORS_CONFIGS_QUERY, LAYOUT_QUERY } from "~/data/queries"; +import type { EnhancedMenu } from "~/hooks/use-shop-menu"; import { seoPayload } from "~/lib/seo.server"; -import type { EnhancedMenu } from "./utils"; /** * Load data necessary for rendering content above the fold. This is the critical data diff --git a/app/lib/utils.ts b/app/lib/utils.ts index 7a725e3a..9f7bcbfc 100644 --- a/app/lib/utils.ts +++ b/app/lib/utils.ts @@ -2,58 +2,11 @@ import { useLocation, useRouteLoaderData } from "@remix-run/react"; import type { FulfillmentStatus } from "@shopify/hydrogen/customer-account-api-types"; import type { MoneyV2 } from "@shopify/hydrogen/storefront-api-types"; import type { LinkHTMLAttributes } from "react"; -import type { - ChildMenuItemFragment, - MenuFragment, - ParentMenuItemFragment, -} from "storefrontapi.generated"; -import typographicBase from "typographic-base/index"; + import { countries } from "~/data/countries"; import type { RootLoader } from "~/root"; import type { I18nLocale } from "./type"; -type EnhancedMenuItemProps = { - to: string; - target: string; - isExternal?: boolean; -}; - -export type ChildEnhancedMenuItem = ChildMenuItemFragment & - EnhancedMenuItemProps; - -export type ParentEnhancedMenuItem = (ParentMenuItemFragment & - EnhancedMenuItemProps) & { - items: ChildEnhancedMenuItem[]; -}; - -export type EnhancedMenu = Pick & { - items: ParentEnhancedMenuItem[]; -}; - -export function missingClass(string?: string, prefix?: string) { - if (!string) { - return true; - } - - const regex = new RegExp(` ?${prefix}`, "g"); - return string.match(regex) === null; -} - -export function formatText(input?: string | React.ReactNode) { - if (!input) { - return; - } - - if (typeof input !== "string") { - return input; - } - - return typographicBase(input, { locale: "en-us" }).replace( - /\s([^\s<]+)\s*$/g, - "\u00A0$1", - ); -} - export function getExcerpt(text: string) { const regex = /(.*?)<\/p>/; const match = regex.exec(text); @@ -74,15 +27,6 @@ export function isDiscounted(price: MoneyV2, compareAtPrice: MoneyV2) { return false; } -export const INPUT_STYLE_CLASSES = - "appearance-none rounded dark:bg-transparent border focus:border-line focus:ring-0 w-full py-2 px-3 text-body placeholder:text-body leading-tight focus:shadow-outline"; - -export const getInputStyleClasses = (isError?: string | null) => { - return `${INPUT_STYLE_CLASSES} ${ - isError ? "border-red-500" : "border-gray-200" - }`; -}; - export function statusMessage(status: FulfillmentStatus) { const translations: Record = { SUCCESS: "Success", @@ -105,8 +49,8 @@ export const DEFAULT_LOCALE: I18nLocale = Object.freeze({ }); export function getLocaleFromRequest(request: Request): I18nLocale { - const url = new URL(request.url); - const firstPathPart = `/${url.pathname.substring(1).split("/")[0].toLowerCase()}`; + let url = new URL(request.url); + let firstPathPart = `/${url.pathname.substring(1).split("/")[0].toLowerCase()}`; return countries[firstPathPart] ? { @@ -120,8 +64,8 @@ export function getLocaleFromRequest(request: Request): I18nLocale { } export function usePrefixPathWithLocale(path: string) { - const rootData = useRouteLoaderData("root"); - const selectedLocale = rootData?.selectedLocale ?? DEFAULT_LOCALE; + let rootData = useRouteLoaderData("root"); + let selectedLocale = rootData?.selectedLocale ?? DEFAULT_LOCALE; return `${selectedLocale.pathPrefix}${ path.startsWith("/") ? path : `/${path}` diff --git a/app/modules/button.tsx b/app/modules/button.tsx deleted file mode 100644 index a3fe07d9..00000000 --- a/app/modules/button.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Link } from "@remix-run/react"; -import { forwardRef } from "react"; -import { cn } from "~/lib/cn"; -import { missingClass } from "~/lib/utils"; - -/** - * @deprecated - * Use `Button` from components directory instead - */ -export const Button = forwardRef( - ( - { - as = "button", - className = "", - variant = "primary", - width = "auto", - ...props - }: { - as?: React.ElementType; - className?: string; - variant?: "primary" | "secondary" | "inline" | "secondary-white"; - width?: "auto" | "full"; - [key: string]: any; - }, - ref, - ) => { - const Component = props?.to ? Link : as; - - const baseButtonClasses = - "inline-block rounded font-medium text-center py-3 px-4 text-sm font-medium"; - - const disabledClasses = - "disabled:opacity-50 disabled:cursor-not-allowed disabled:select-none disabled:hover:bg-btn disabled:hover:text-btn-content"; - - const variants = { - primary: `${baseButtonClasses} border-2 border-btn hover:bg-inv-btn hover:text-inv-btn-content bg-btn text-btn-content`, - secondary: `${baseButtonClasses} border-2 border-btn text-btnTextInverse hover:bg-btn hover:text-btn-content`, - "secondary-white": `${baseButtonClasses} border-2 border-inv-btn text-btn hover:bg-inv-btn hover:text-inv-btn-content`, - inline: "border-b border-gray-100 leading-none pb-1", - }; - - const widths = { - auto: "w-auto", - full: "w-full", - }; - - const styles = cn( - missingClass(className, "bg-") && variants[variant], - missingClass(className, "w-") && widths[width], - disabledClasses, - className, - ); - - return ( - - ); - }, -); diff --git a/app/modules/cart.tsx b/app/modules/cart.tsx index 73dd3131..e29d327c 100644 --- a/app/modules/cart.tsx +++ b/app/modules/cart.tsx @@ -19,7 +19,6 @@ import type { CartApiQueryFragment } from "storefrontapi.generated"; import { Button } from "~/components/button"; import { Link } from "~/components/link"; import { getImageAspectRatio } from "~/lib/utils"; -import { Text } from "~/modules/text"; import { CartBestSellers } from "./cart-best-sellers"; type CartLine = OptimisticCart["lines"]["nodes"][0]; @@ -92,14 +91,14 @@ function CartDiscounts({ {/* Have existing discount, display it with a remove option */}
    - Discount(s) +
    Discount(s)
    - {codes?.join(", ")} +
    {codes?.join(", ")}
    diff --git a/app/modules/modal.tsx b/app/modules/modal.tsx deleted file mode 100644 index d09d97a2..00000000 --- a/app/modules/modal.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { X } from "@phosphor-icons/react"; -import { useEffect } from "react"; -import { Button } from "~/components/button"; -import { Link } from "~/components/link"; - -/** - * @deprecated use `~/components/modal` instead - */ -export function Modal({ - children, - cancelLink, - onClose, -}: { - children: React.ReactNode; - cancelLink: string; - onClose?: () => void; -}) { - useEffect(() => { - if (!document.body.classList.contains("overflow-hidden")) { - document.body.classList.add("overflow-hidden"); - } - return () => { - document.body.classList.remove("overflow-hidden"); - }; - }, []); - - return ( -