diff --git a/package.json b/package.json index f814c019..b1dfe709 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@stripe/stripe-js": "^4.6.0", "@uidotdev/usehooks": "^2.4.1", "blurhash-base64": "^0.0.3", + "class-variance-authority": "^0.7.0", "clsx": "2.1.0", "next": "15.0.0-canary.6", "qrcode": "^1.5.3", @@ -36,7 +37,6 @@ "react-hook-form": "^7.52.0", "react-intersection-observer": "^9.13.0", "react-photo-view": "^1.2.4", - "sonner": "^1.5.0", "zod": "^3.23.8" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 591b2742..f2ad0461 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: blurhash-base64: specifier: ^0.0.3 version: 0.0.3 + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 clsx: specifier: 2.1.0 version: 2.1.0 @@ -77,9 +80,6 @@ importers: react-photo-view: specifier: ^1.2.4 version: 1.2.4(react-dom@19.0.0-rc-6d3110b4d9-20240531(react@19.0.0-rc-6d3110b4d9-20240531))(react@19.0.0-rc-6d3110b4d9-20240531) - sonner: - specifier: ^1.5.0 - version: 1.5.0(react-dom@19.0.0-rc-6d3110b4d9-20240531(react@19.0.0-rc-6d3110b4d9-20240531))(react@19.0.0-rc-6d3110b4d9-20240531) zod: specifier: ^3.23.8 version: 3.23.8 @@ -2941,6 +2941,9 @@ packages: cjs-module-lexer@1.3.1: resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} + class-variance-authority@0.7.0: + resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + clean-css@5.3.3: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} @@ -2971,6 +2974,10 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + clsx@2.0.0: + resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} + engines: {node: '>=6'} + clsx@2.1.0: resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} engines: {node: '>=6'} @@ -5729,12 +5736,6 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} - sonner@1.5.0: - resolution: {integrity: sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} @@ -10205,6 +10206,10 @@ snapshots: cjs-module-lexer@1.3.1: {} + class-variance-authority@0.7.0: + dependencies: + clsx: 2.0.0 + clean-css@5.3.3: dependencies: source-map: 0.6.1 @@ -10237,6 +10242,8 @@ snapshots: clone@1.0.4: {} + clsx@2.0.0: {} + clsx@2.1.0: {} collapse-white-space@2.1.0: {} @@ -10844,7 +10851,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.1(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) @@ -10868,7 +10875,7 @@ snapshots: enhanced-resolve: 5.16.0 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.3 is-core-module: 2.13.1 @@ -10890,7 +10897,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -13581,11 +13588,6 @@ snapshots: slash@5.1.0: {} - sonner@1.5.0(react-dom@19.0.0-rc-6d3110b4d9-20240531(react@19.0.0-rc-6d3110b4d9-20240531))(react@19.0.0-rc-6d3110b4d9-20240531): - dependencies: - react: 19.0.0-rc-6d3110b4d9-20240531 - react-dom: 19.0.0-rc-6d3110b4d9-20240531(react@19.0.0-rc-6d3110b4d9-20240531) - source-map-js@1.2.0: {} source-map-support@0.5.21: diff --git a/src/app/[mdxPageSlug]/page.tsx b/src/app/[mdxPageSlug]/page.tsx index 8bd17055..41d6a341 100644 --- a/src/app/[mdxPageSlug]/page.tsx +++ b/src/app/[mdxPageSlug]/page.tsx @@ -1,5 +1,5 @@ import { notFound } from "next/navigation"; -import CoreLayout from "@/components/layouts/CoreLayout"; +import NavigationLayout from "@/components/layouts/NavigationLayout"; import AboutMdx from "./about.mdx"; import ContactsMdx from "./contacts.mdx"; import ShippingMdx from "./shipping.mdx"; @@ -32,10 +32,10 @@ export default function Page({ params }: Props) { } return ( - +
-
+ ); } diff --git a/src/app/archive/page.tsx b/src/app/archive/page.tsx index 9f664851..20047313 100644 --- a/src/app/archive/page.tsx +++ b/src/app/archive/page.tsx @@ -1,4 +1,4 @@ -import CoreLayout from "@/components/layouts/CoreLayout"; +import NavigationLayout from "@/components/layouts/NavigationLayout"; import { serviceClient } from "@/lib/api"; import { FullscreenImagesCarousel } from "@/components/sections/FullscreenImagesCarousel"; @@ -15,7 +15,7 @@ export default async function Page() { return (
- +
{nonEmptyArchives?.map((a, i) => (
@@ -40,7 +40,7 @@ export default async function Page() {
))}
-
+
); } diff --git a/src/app/cart/checkout/page.tsx b/src/app/cart/checkout/page.tsx index f4cd8adb..f0664b18 100644 --- a/src/app/cart/checkout/page.tsx +++ b/src/app/cart/checkout/page.tsx @@ -5,7 +5,7 @@ import { common_OrderNew, } from "@/api/proto-http/frontend"; import NewOrderForm from "@/components/forms/NewOrderForm"; -import CoreLayout from "@/components/layouts/CoreLayout"; +import NavigationLayout from "@/components/layouts/NavigationLayout"; import { serviceClient } from "@/lib/api"; import { getValidateOrderItemsInsertItems } from "@/lib/utils/cart"; import { redirect } from "next/navigation"; @@ -21,42 +21,7 @@ export default async function CheckoutPage() { promoCode: undefined, }); - async function submitNewOrder(newOrderData: common_OrderNew) { - "use server"; - - try { - const submitOrderResponse = await serviceClient.SubmitOrder({ - order: newOrderData, - }); - - if (!submitOrderResponse?.orderUuid) { - console.log("no data to create order invoice"); - - return { - ok: false, - }; - } - - console.log({ - ok: true, - order: submitOrderResponse, - }); - - clearCartProducts(); - - return { - ok: true, - order: submitOrderResponse, - }; - } catch (error) { - console.error("Error submitting new order:", error); - return { - ok: false, - }; - } - } - - const updateCookieCart = async (validItems: common_OrderItem[]) => { + async function updateCookieCart(validItems: common_OrderItem[]) { "use server"; clearCartProducts(); @@ -74,15 +39,11 @@ export default async function CheckoutPage() { }); } } - }; + } return ( - - - + + + ); } diff --git a/src/app/cart/page.tsx b/src/app/cart/page.tsx index 949d827f..4c1ae263 100644 --- a/src/app/cart/page.tsx +++ b/src/app/cart/page.tsx @@ -1,8 +1,7 @@ -import CoreLayout from "@/components/layouts/CoreLayout"; +import NavigationLayout from "@/components/layouts/NavigationLayout"; import CartProductsList from "@/components/sections/Cart/CartProductsList"; import TotalPrice from "@/components/sections/Cart/TotalPrice"; -import Button from "@/components/ui/Button"; -import { ButtonStyle } from "@/components/ui/Button/styles"; +import { Button } from "@/components/ui/Button"; import { CartProductsSkeleton } from "@/components/ui/Skeleton"; import { getCookieCart } from "@/lib/utils/cart"; import Link from "next/link"; @@ -14,7 +13,7 @@ export default async function CartPage() { const cartItems = getCookieCart(); return ( - +
@@ -33,13 +32,13 @@ export default async function CartPage() { {/* */} {Object.keys(cartItems?.products || {}).length && ( - )}
-
+ ); } diff --git a/src/app/catalog/page.tsx b/src/app/catalog/page.tsx index 79e6fafc..ea55d5b1 100644 --- a/src/app/catalog/page.tsx +++ b/src/app/catalog/page.tsx @@ -1,6 +1,6 @@ import CatalogSection from "@/components/sections/CatalogSection"; import Filters from "@/components/sections/Filters"; -import CoreLayout from "@/components/layouts/CoreLayout"; +import NavigationLayout from "@/components/layouts/NavigationLayout"; import { CATALOG_LIMIT } from "@/constants"; import { serviceClient } from "@/lib/api"; import { getValidatedGetProductsPagedParams } from "@/lib/utils/queryFilters"; @@ -23,7 +23,7 @@ export default async function CatalogPage({ searchParams }: CatalogPageProps) { }); return ( - +
-
+ ); } diff --git a/src/app/globals.css b/src/app/globals.css index 0c93fb82..aea4869b 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -3,20 +3,41 @@ @tailwind utilities; @layer base { - .lightTheme { - --background: #fff; + :root { + /* COLORS */ --text: #000; - --button-text: #fff; - --highlight-text: #311eee; + --inactive: #cccccc; + --background: #fff; + --highlight: #311eee; --error: #ff0000; + --text-select: #75fb4c; + /* components-specific colors */ + --button-text: #fff; + --visited-link: #501089; + + /* TEXT */ + --text-giant: 200px; + --text-giant-small: 110px; + --text-base: @apply text-base; + --text-small: @apply text-xs; } .blueTheme { - --background: #311eee; --text: #fff; - --button-text: #000; - --highlight-text: #fff; + --inactive: #cccccc; + --background: #311eee; + --highlight: #fff; --error: #ff0000; + --text-select: #75fb4c; + /* components-specific colors */ + --button-text: #000; + --visited-link: #501089; + + /* TEXT */ + --text-giant: 200px; + --text-giant-small: 110px; + --text-base: @apply text-base; + --text-small: @apply text-xs; } /* * { diff --git a/src/app/invoices/V0GenForm.tsx b/src/app/invoices/V0GenForm.tsx deleted file mode 100644 index 5f85dbe1..00000000 --- a/src/app/invoices/V0GenForm.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/** - * v0 by Vercel. - * @see https://v0.dev/t/nz2xUz4Ag64 - * Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app - */ -export default function Component() { - return ( -
-
-

Check Invoice Status

-

- Enter your invoice number and email to check the status of your - invoice. -

-
-
- - -
-
- - -
-
- -
-
-
-
- ); -} diff --git a/src/app/invoices/crypto/[uuid]/page.tsx b/src/app/invoices/crypto/[uuid]/page.tsx deleted file mode 100644 index 0e42e895..00000000 --- a/src/app/invoices/crypto/[uuid]/page.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import CoreLayout from "@/components/layouts/CoreLayout"; -import { serviceClient } from "@/lib/api"; -import QRCode from "qrcode"; - -import { redirect } from "next/navigation"; -import V0GenUi from "./V0GenUi"; - -interface Props { - params: { - uuid: string; - }; -} - -export default async function Page({ params }: Props) { - const { uuid } = params; - - const cryptoPaymentInvoice = await serviceClient.CheckPayment({ - orderUuid: uuid, - }); - - if (!cryptoPaymentInvoice?.payment) redirect("/invoices"); - - const qrBase64Code = cryptoPaymentInvoice?.payment?.paymentInsert?.payee - ? await QRCode.toDataURL(cryptoPaymentInvoice.payment.paymentInsert.payee) - : undefined; - - return ( - - - - ); -} diff --git a/src/app/invoices/page.tsx b/src/app/invoices/page.tsx deleted file mode 100644 index bfabc821..00000000 --- a/src/app/invoices/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import CoreLayout from "@/components/layouts/CoreLayout"; -import V0GenForm from "./V0GenForm"; - -export default function Page() { - return ( - - - - ); -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5758a426..3d7a6fd2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,6 @@ import type { Metadata } from "next"; -import RootLayoutComponent from "@/components/layouts/RootLayout"; import { FeatureMono } from "../fonts"; -import HeroContextLayout from "@/components/layouts/HeroContextLayout"; +import { CommonLayout } from "@/components/layouts/CommonLayout"; import "./globals.css"; @@ -18,9 +17,7 @@ export default function RootLayout({ return ( - - {children} - + {children} ); diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 23dae35c..4cf6c855 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,6 +1,5 @@ import Header from "@/components/sections/Header"; -import Button from "@/components/ui/Button"; -import { ButtonStyle } from "@/components/ui/Button/styles"; +import { Button } from "@/components/ui/Button"; import Link from "next/link"; import "./globals.css"; @@ -17,10 +16,10 @@ export default function NotFound() { please go back to the homepage or contact us if the problem persists.

- -
diff --git a/src/app/invoices/crypto/[uuid]/V0GenUi.tsx b/src/app/order/[uuid]/V0GenUiQRCode.tsx similarity index 100% rename from src/app/invoices/crypto/[uuid]/V0GenUi.tsx rename to src/app/order/[uuid]/V0GenUiQRCode.tsx diff --git a/src/app/order/[uuid]/page.tsx b/src/app/order/[uuid]/page.tsx index fa5fc2f7..b1f74249 100644 --- a/src/app/order/[uuid]/page.tsx +++ b/src/app/order/[uuid]/page.tsx @@ -1,6 +1,8 @@ import { serviceClient } from "@/lib/api"; +import QRCode from "qrcode"; -import V0GenUi from "./V0GenUi"; +import V0GenUiOrder from "./V0GenUi"; +import V0GenUiQRCode from "./V0GenUiQRCode"; interface Props { params: { @@ -13,9 +15,39 @@ export default async function OrderPage({ params }: Props) { const response = await serviceClient.GetOrderByUUID({ orderUuid: uuid }); + const isCryptoPayment = + response.order?.payment?.paymentInsert?.paymentMethod === + "PAYMENT_METHOD_NAME_ENUM_USDT_SHASTA"; + const isTransactionDone = + response.order?.payment?.paymentInsert?.isTransactionDone; + + if (isCryptoPayment && !isTransactionDone) { + const qrBase64Code = await QRCode.toDataURL( + response.order?.payment?.paymentInsert?.payee || "", + ); + + return ( +
+ +
+ ); + } + return (
- +
); } diff --git a/src/app/page.tsx b/src/app/page.tsx index ecaf52bb..398c6393 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,9 +1,8 @@ -import CoreLayout from "@/components/layouts/CoreLayout"; +import NavigationLayout from "@/components/layouts/NavigationLayout"; import HeroAds from "@/components/sections/HeroSection/Ads"; import HeroMain from "@/components/sections/HeroSection/Main"; import ProductsSection from "@/components/sections/ProductsGridSection"; -import Button from "@/components/ui/Button"; -import { ButtonStyle } from "@/components/ui/Button/styles"; +import { Button } from "@/components/ui/Button"; import { serviceClient } from "@/lib/api"; import Link from "next/link"; @@ -15,7 +14,7 @@ export default async function Page() { return ( <> - + {hero?.entities?.map((e) => { switch (e.type) { case "HERO_TYPE_SINGLE_ADD": @@ -30,10 +29,10 @@ export default async function Page() { return null; } })} - - + ); } diff --git a/src/app/payment/stripe/page.tsx b/src/app/payment/stripe/page.tsx deleted file mode 100644 index 4ec6ee86..00000000 --- a/src/app/payment/stripe/page.tsx +++ /dev/null @@ -1,107 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { loadStripe } from "@stripe/stripe-js"; -import { - Elements, - CardElement, - useStripe, - useElements, -} from "@stripe/react-stripe-js"; - -const stripePromise = loadStripe( - process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!, -); - -function CheckoutForm() { - const stripe = useStripe(); - const elements = useElements(); - const [error, setError] = useState(null); - const [processing, setProcessing] = useState(false); - const [succeeded, setSucceeded] = useState(false); - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - - if (!stripe || !elements) { - return; - } - - setProcessing(true); - - const { error: backendError, clientSecret } = await fetch( - "/api/create-payment-intent", - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ amount: 1000 }), // Amount in cents - }, - ).then((res) => res.json()); - - if (backendError) { - setError(backendError.message); - setProcessing(false); - return; - } - - const { error, paymentIntent } = await stripe.confirmCardPayment( - clientSecret, - { - payment_method: { - card: elements.getElement(CardElement)!, - billing_details: { - name: "Customer Name", - }, - }, - }, - ); - - if (error) { - setError(`Payment failed: ${error.message}`); - } else if (paymentIntent.status === "succeeded") { - setSucceeded(true); - } - - setProcessing(false); - }; - - return ( -
-
- -
- -
-
- - {error &&
{error}
} - {succeeded && ( -
Payment succeeded!
- )} -
- ); -} - -export default function StripePaymentPage() { - return ( -
-

Stripe Payment

- - - -
- ); -} diff --git a/src/app/product/[...productParams]/page.tsx b/src/app/product/[...productParams]/page.tsx index 35fdc789..64d3825d 100644 --- a/src/app/product/[...productParams]/page.tsx +++ b/src/app/product/[...productParams]/page.tsx @@ -1,6 +1,6 @@ import { addCartProduct } from "@/actions/cart"; import AddToCartForm from "@/components/forms/AddToCartForm"; -import CoreLayout from "@/components/layouts/CoreLayout"; +import NavigationLayout from "@/components/layouts/NavigationLayout"; import { FullscreenImagesCarousel } from "@/components/sections/FullscreenImagesCarousel"; import { ProductMediaItem } from "@/components/sections/FullscreenImagesCarousel/ProductMediaItem"; import MeasurementsModal from "@/components/sections/MeasurementsModal"; @@ -55,7 +55,7 @@ export default async function ProductPage({ params }: ProductPageProps) { product?.product?.productDisplay?.productBody?.price?.value; return ( - +
{product?.media && (
@@ -93,6 +93,6 @@ export default async function ProductPage({ params }: ProductPageProps) { )}
-
+ ); } diff --git a/src/components/forms/AddToCartForm/index.tsx b/src/components/forms/AddToCartForm/index.tsx index d62d1d7a..31eca808 100644 --- a/src/components/forms/AddToCartForm/index.tsx +++ b/src/components/forms/AddToCartForm/index.tsx @@ -7,6 +7,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { AddToCartData, addToCartSchema } from "./schema"; +import { SubmitButton } from "../SubmitButton"; // todo: rework this to share logic with measurement modal export default function AddToCartForm({ @@ -28,7 +29,6 @@ export default function AddToCartForm({ setLoadingStatus(true); try { - console.log({ id, size: data.size }); await handleSubmit({ id, size: data.size }); } catch (error) { console.error(error); @@ -40,11 +40,9 @@ export default function AddToCartForm({ return ( } footerSide="right" > + + ); +} diff --git a/src/components/forms/StripeSecureCardForm/index.tsx b/src/components/forms/StripeSecureCardForm/index.tsx new file mode 100644 index 00000000..1a2378c2 --- /dev/null +++ b/src/components/forms/StripeSecureCardForm/index.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { Elements } from "@stripe/react-stripe-js"; +import { loadStripe } from "@stripe/stripe-js"; + +import CheckoutForm from "./CheckoutForm"; +// Make sure to call `loadStripe` outside of a component’s render to avoid +// recreating the `Stripe` object on every render. +const stripePromise = loadStripe( + "pk_test_51PQqJsP97IsmiR4DHmNimxWDeAAMhzOww6NNyrvYKyqW0nsRPKbfivB8M2gzeSJSgJktys1EWbHNfaqvvqDv5Mlq00rjie9vDF", +); + +export default function StripeSecureCardForm({ + clientSecret, +}: { + clientSecret: string; +}) { + return ( + + + + ); +} diff --git a/src/components/forms/SubmitButton.tsx b/src/components/forms/SubmitButton.tsx index abdae93f..b420f765 100644 --- a/src/components/forms/SubmitButton.tsx +++ b/src/components/forms/SubmitButton.tsx @@ -2,22 +2,23 @@ // @ts-ignore import { useFormStatus } from "react-dom"; -import Button from "@/components/ui/Button"; -import { ButtonStyle } from "@/components/ui/Button/styles"; +import { Button } from "@/components/ui/Button"; type Props = { text: string; disabled?: boolean; }; -export function SubmitButton({ text, disabled }: Props) { +export function SubmitButton({ text, disabled = false }: Props) { const { pending } = useFormStatus(); return ( diff --git a/src/components/layouts/CheckoutLayout.tsx b/src/components/layouts/CheckoutLayout.tsx new file mode 100644 index 00000000..de5297fb --- /dev/null +++ b/src/components/layouts/CheckoutLayout.tsx @@ -0,0 +1,27 @@ +import { Elements } from "@stripe/react-stripe-js"; +import CheckoutForm from "../forms/StripeSecureCardForm/CheckoutForm"; +import { loadStripe } from "@stripe/stripe-js"; + +// recreating the `Stripe` object on every render. +const stripePromise = loadStripe( + "pk_test_51PQqJsP97IsmiR4DHmNimxWDeAAMhzOww6NNyrvYKyqW0nsRPKbfivB8M2gzeSJSgJktys1EWbHNfaqvvqDv5Mlq00rjie9vDF", +); + +export default function CheckoutLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/components/layouts/HeroContextLayout.tsx b/src/components/layouts/CommonLayout.tsx similarity index 50% rename from src/components/layouts/HeroContextLayout.tsx rename to src/components/layouts/CommonLayout.tsx index 707c7732..2e397862 100644 --- a/src/components/layouts/HeroContextLayout.tsx +++ b/src/components/layouts/CommonLayout.tsx @@ -1,12 +1,16 @@ -import { serviceClient } from "@/lib/api"; -import { HeroContextProvider } from "@/components/contexts/HeroContext"; - type Props = { children: React.ReactNode; }; -export default async function Layout({ children }: Props) { +import { serviceClient } from "@/lib/api"; +import { HeroContextProvider } from "@/components/contexts/HeroContext"; + +export async function CommonLayout({ children }: Props) { const heroData = await serviceClient.GetHero({}); - return {children}; + return ( + +
{children}
+
+ ); } diff --git a/src/components/layouts/CoreLayout.tsx b/src/components/layouts/NavigationLayout.tsx similarity index 55% rename from src/components/layouts/CoreLayout.tsx rename to src/components/layouts/NavigationLayout.tsx index 0f41386a..1f956aa3 100644 --- a/src/components/layouts/CoreLayout.tsx +++ b/src/components/layouts/NavigationLayout.tsx @@ -3,25 +3,22 @@ import CartProductsList from "@/components/sections/Cart/CartProductsList"; import TotalPrice from "@/components/sections/Cart/TotalPrice"; import Footer from "@/components/sections/Footer"; import Header from "@/components/sections/Header"; -import Button from "@/components/ui/Button"; -import { ButtonStyle } from "@/components/ui/Button/styles"; +import { Button } from "@/components/ui/Button"; import { getCookieCart } from "@/lib/utils/cart"; import Link from "next/link"; import { Suspense } from "react"; -export default function CoreLayout({ +export default function NavigationLayout({ children, hideForm, - hidePopupCart, }: Readonly<{ children: React.ReactNode; hideForm?: boolean; - hidePopupCart?: boolean; }>) { const cartData = getCookieCart(); const hasCartProducts = cartData?.products && Object.keys(cartData?.products).length > 0; - const productsNumber = Object.keys(cartData?.products || {}).length; + const itemsQuantity = Object.keys(cartData?.products || {}).length; return (
@@ -30,13 +27,13 @@ export default function CoreLayout({
@@ -49,30 +46,19 @@ export default function CoreLayout({
diff --git a/src/components/layouts/RootLayout.tsx b/src/components/layouts/RootLayout.tsx deleted file mode 100644 index 14d0fe40..00000000 --- a/src/components/layouts/RootLayout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Toaster } from "sonner"; - -type Props = { - children: React.ReactNode; -}; - -export default function RootLayout({ children }: Props) { - // add header to this layout (invoices page) - return ( -
- - {children} -
- ); -} diff --git a/src/components/sections/Cart/CartItemRow.tsx b/src/components/sections/Cart/CartItemRow.tsx index b12c1e5d..4bb33d8a 100644 --- a/src/components/sections/Cart/CartItemRow.tsx +++ b/src/components/sections/Cart/CartItemRow.tsx @@ -1,52 +1,55 @@ import { changeCartProductQuantity, removeCartProduct } from "@/actions/cart"; import type { common_OrderItem } from "@/api/proto-http/frontend"; import Image from "@/components/ui/Image"; -import Size from "../Common/Size"; import ProductAmountButtons from "./ProductAmountButtons"; +import { cn } from "@/lib/utils"; +import ProductRemoveButton from "./ProductRemoveButton"; +import CartItemSize from "./CartItemSize"; export default function CartItemRow({ product, + className, }: { product?: common_OrderItem; + className?: string; }) { if (!product) return null; return ( -
-
-
- product -
-
-

{product.productName}

-

{product.color}

-

- {product.orderItem?.sizeId && ( - - )} -

-
+
+
+ product
-
+
+
+

{product.productName}

+
+

{product.color}

+ +
+
{product.orderItem?.productId !== undefined && ( + > + {product.orderItem?.quantity || 0} + )} -
quantity: {product.orderItem?.quantity}
-
-

BTC {product.productPrice}

-

BTC {product.productPrice}

+
+ +

BTC {product.productPrice}

); diff --git a/src/components/sections/Cart/CartItemSize.tsx b/src/components/sections/Cart/CartItemSize.tsx new file mode 100644 index 00000000..2bf19c04 --- /dev/null +++ b/src/components/sections/Cart/CartItemSize.tsx @@ -0,0 +1,14 @@ +"use client"; + +import { useHeroContext } from "@/components/contexts/HeroContext"; + +export default function CartItemSize({ sizeId }: { sizeId: string }) { + const { dictionary } = useHeroContext(); + + const sizeName = + dictionary?.sizes?.find((size) => size.id === Number(sizeId))?.name || ""; + + if (!sizeName) return null; + + return

{sizeName}

; +} diff --git a/src/components/sections/Cart/CartPopup.tsx b/src/components/sections/Cart/CartPopup.tsx index 68c21810..b8e51c09 100644 --- a/src/components/sections/Cart/CartPopup.tsx +++ b/src/components/sections/Cart/CartPopup.tsx @@ -1,22 +1,19 @@ "use client"; -import Button from "@/components/ui/Button"; +import { Button } from "@/components/ui/Button"; import { cn } from "@/lib/utils"; import { useClickAway } from "@uidotdev/usehooks"; import Link from "next/link"; import { useState } from "react"; -import { ButtonStyle } from "@/components/ui/Button/styles"; export default function CartPopup({ children, itemsQuantity, hasCartProducts, - hidePopupCart, }: { children: React.ReactNode; itemsQuantity?: number; hasCartProducts?: boolean; - hidePopupCart?: boolean; }) { const [open, setOpenStatus] = useState(false); @@ -27,32 +24,37 @@ export default function CartPopup({ return (
diff --git a/src/components/sections/Cart/CartProductsList/HACK__UpdateCookieCart.tsx b/src/components/sections/Cart/CartProductsList/HACK__UpdateCookieCart.tsx index afa4b2c7..cbfd4403 100644 --- a/src/components/sections/Cart/CartProductsList/HACK__UpdateCookieCart.tsx +++ b/src/components/sections/Cart/CartProductsList/HACK__UpdateCookieCart.tsx @@ -1,7 +1,6 @@ "use client"; import { useEffect } from "react"; -import { toast } from "sonner"; export default function HACK__UpdateCookieCart({ updateCookieCart, @@ -10,8 +9,6 @@ export default function HACK__UpdateCookieCart({ }) { useEffect(() => { updateCookieCart(); - - toast.info("Cart items have been updated!"); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/components/sections/Cart/CartProductsList/index.tsx b/src/components/sections/Cart/CartProductsList/index.tsx index 6935f52b..ea599e02 100644 --- a/src/components/sections/Cart/CartProductsList/index.tsx +++ b/src/components/sections/Cart/CartProductsList/index.tsx @@ -1,13 +1,18 @@ import { addCartProduct, clearCartProducts } from "@/actions/cart"; -import Button from "@/components/ui/Button"; +import { Button } from "@/components/ui/Button"; import { serviceClient } from "@/lib/api"; import { getValidateOrderItemsInsertItems } from "@/lib/utils/cart"; import Link from "next/link"; import CartItemRow from "../CartItemRow"; import SelectedCurrency from "../TotalPrice/SelectedCurrency"; import HACK__UpdateCookieCart from "./HACK__UpdateCookieCart"; +import { cn } from "@/lib/utils"; -export default async function CartProductsList() { +export default async function CartProductsList({ + className, +}: { + className?: string; +}) { const items = getValidateOrderItemsInsertItems(); if (items.length === 0) return null; @@ -41,19 +46,18 @@ export default async function CartProductsList() { }; return ( -
- {response.hasChanged && ( - - )} +
{response?.validItems?.map((p, i) => ( - + ))} -

total:

+ {response.hasChanged && ( + + )}
); } diff --git a/src/components/sections/Cart/ProductAmountButtons.tsx b/src/components/sections/Cart/ProductAmountButtons.tsx index de1d6991..2e29c1f7 100644 --- a/src/components/sections/Cart/ProductAmountButtons.tsx +++ b/src/components/sections/Cart/ProductAmountButtons.tsx @@ -1,7 +1,6 @@ "use client"; -import Button from "@/components/ui/Button"; -import { ButtonStyle } from "@/components/ui/Button/styles"; +import { Button } from "@/components/ui/Button"; type Props = { changeProductAmount: ({ @@ -13,53 +12,34 @@ type Props = { size: string; operation: "increase" | "decrease"; }) => void; - removeProduct: ({ id, size }: { id: number; size: string }) => void; id: number; size: string; + children: React.ReactNode; }; export default function ProductAmountButtons({ - changeProductAmount, - removeProduct, id, size, + children, + changeProductAmount, }: Props) { - // todo: check if product is in stock - return ( -
-
- - - -
+
+ + {children} +
); } diff --git a/src/components/sections/Cart/ProductRemoveButton.tsx b/src/components/sections/Cart/ProductRemoveButton.tsx new file mode 100644 index 00000000..49dff94a --- /dev/null +++ b/src/components/sections/Cart/ProductRemoveButton.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { Button } from "@/components/ui/Button"; + +type Props = { + removeProduct: ({ id, size }: { id: number; size: string }) => void; + id: number; + size: string; +}; + +export default function ProductRemoveButton({ + id, + size, + removeProduct, +}: Props) { + return ( + + ); +} diff --git a/src/components/sections/Cart/TotalPrice/SelectedCurrency.tsx b/src/components/sections/Cart/TotalPrice/SelectedCurrency.tsx index 94684953..3b5c1f5c 100644 --- a/src/components/sections/Cart/TotalPrice/SelectedCurrency.tsx +++ b/src/components/sections/Cart/TotalPrice/SelectedCurrency.tsx @@ -10,12 +10,11 @@ export default function SelectedCurrency({ const { selectedCurrency } = useHeroContext(); return ( -
-
total
-
base currency: {baseCurrencyTotal}
-
- todo: convert to {selectedCurrency}: {baseCurrencyTotal} -
+
+ SUBTOTAL: + + {selectedCurrency} {baseCurrencyTotal} +
); } diff --git a/src/components/sections/Cart/TotalPrice/index.tsx b/src/components/sections/Cart/TotalPrice/index.tsx index 1be25f4d..a1c7b230 100644 --- a/src/components/sections/Cart/TotalPrice/index.tsx +++ b/src/components/sections/Cart/TotalPrice/index.tsx @@ -12,9 +12,5 @@ export default function TotalPrice() { 0, ); - return ( -
- -
- ); + return ; } diff --git a/src/components/sections/Footer/index.tsx b/src/components/sections/Footer/index.tsx index d73fb23d..9dbdaf01 100644 --- a/src/components/sections/Footer/index.tsx +++ b/src/components/sections/Footer/index.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { FOOTER_LINKS as links, FOOTER_YEAR as year } from "@/constants"; import { serviceClient } from "@/lib/api"; import { cn } from "@/lib/utils"; -import Button from "@/components/ui/Button"; +import { Button } from "@/components/ui/Button"; import FooterForm from "../../forms/NewslatterForm"; import CurrencyPopover from "./CurrencyPopover"; diff --git a/src/components/sections/Header/useMobileMenu.tsx b/src/components/sections/Header/useMobileMenu.tsx index 1e9c8052..b79d642c 100644 --- a/src/components/sections/Header/useMobileMenu.tsx +++ b/src/components/sections/Header/useMobileMenu.tsx @@ -3,8 +3,7 @@ import { useClickAway } from "@uidotdev/usehooks"; import { Dispatch, SetStateAction, useState } from "react"; -import Button from "@/components/ui/Button"; -import { ButtonStyle } from "@/components/ui/Button/styles"; +import { Button } from "@/components/ui/Button"; import Link from "next/link"; export default function useMovileMenu() { @@ -29,9 +28,13 @@ function MobileMenuTrigger({ setOpenStatus: Dispatch>; }) { return ( - + ); } @@ -49,19 +52,19 @@ function MobileMenuDropdown({ {"[x]"} diff --git a/src/components/sections/MeasurementsModal/index.tsx b/src/components/sections/MeasurementsModal/index.tsx index b8b54f72..12767ae1 100644 --- a/src/components/sections/MeasurementsModal/index.tsx +++ b/src/components/sections/MeasurementsModal/index.tsx @@ -1,5 +1,4 @@ -import Button from "@/components/ui/Button"; -import { ButtonStyle } from "@/components/ui/Button/styles"; +import { Button } from "@/components/ui/Button"; import Modal from "@/components/ui/Modal"; import MeasurementsModalContent from "./MeasurementsModalContent"; import { common_ProductSize } from "@/api/proto-http/frontend"; @@ -29,7 +28,7 @@ export default function MeasurementsModal({ return ( + } diff --git a/src/components/sections/ProductsGridSection/ProductItem.tsx b/src/components/sections/ProductsGridSection/ProductItem.tsx index 97931c82..0f933ad7 100644 --- a/src/components/sections/ProductsGridSection/ProductItem.tsx +++ b/src/components/sections/ProductsGridSection/ProductItem.tsx @@ -1,5 +1,5 @@ import type { common_Product } from "@/api/proto-http/frontend"; -import Button from "@/components/ui/Button"; +import { Button } from "@/components/ui/Button"; import Image from "@/components/ui/Image"; import { CURRENCY_MAP } from "@/constants"; import { cn } from "@/lib/utils"; @@ -41,7 +41,7 @@ export default function ProductItem({
{/* todo: change to css variable */} -
+
{product.productDisplay?.productBody?.brand} {product.productDisplay?.productBody?.name}
diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx new file mode 100644 index 00000000..148c6ac4 --- /dev/null +++ b/src/components/ui/Button.tsx @@ -0,0 +1,93 @@ +import { Slot } from "@radix-ui/react-slot"; +import { cva, VariantProps } from "class-variance-authority"; + +const buttonVariants = cva("disabled:cursor-not-allowed", { + variants: { + variant: { + default: ["text-textBaseSize"], + simple: ["text-bgColor", "bg-textColor"], + main: [ + "border", + "border-textColor", + "text-textBaseSize", + "text-bgColor", + "disabled:bg-textInactiveColor", + "bg-textColor", + "hover:bg-bgColor", + "hover:text-textColor", + "focus:outline-none", + "focus:ring-2", + "focus:ring-offset-2", + "focus:ring-textColor", + "disabled:bg-textInactiveColor", + "disabled:text-bgColor", + "disabled:border-textInactiveColor", + "leading-4", + "text-center", + ], + underline: [ + "text-textBaseSize", + "border-textColor", + "underline", + "disabled:text-textInactiveColor", + ], + underlineWithColors: [ + "underline", + "text-textBaseSize", + "text-highlightColor", + "disabled:text-textInactiveColor", + "visited:text-visitedLinkColor", + ], + }, + size: { + sm: ["text-textSmallSize"], + default: ["text-textBaseSize"], + lg: ["py-2.5", "px-16", "text-textBaseSize"], + giant: [ + "py-10", + "px-16", + "text-textGiantSmallSize", + "lg:text-textGiantSize", + ], + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, +}); + +interface Props extends VariantProps { + asChild?: boolean; + children: React.ReactNode; + loading?: boolean; + className?: string; + [k: string]: unknown; +} + +export function Button({ + asChild, + children, + loading, + size, + variant, + className, + ...props +}: Props) { + const Component = asChild ? Slot : "button"; + + return ( +
+ + {children} + +
+ ); +} diff --git a/src/components/ui/Button/button.stories.ts b/src/components/ui/Button/button.stories.ts deleted file mode 100644 index 5152c0f0..00000000 --- a/src/components/ui/Button/button.stories.ts +++ /dev/null @@ -1,52 +0,0 @@ -import Button from "@/components/ui/Button"; -import { ButtonStyle } from "@/components/ui/Button/styles"; -import type { Meta, StoryObj } from "@storybook/react"; - -const meta = { - title: "UI/Button", - component: Button, - parameters: { - layout: "centered", - }, - args: { - style: ButtonStyle.default, - }, - tags: ["autodocs"], -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - children: "default button", - }, -}; - -export const BigButton: Story = { - args: { - style: ButtonStyle.bigButton, - children: "big button", - }, -}; - -export const UnderlinedButton: Story = { - args: { - style: ButtonStyle.underlinedButton, - children: "underlined button", - }, -}; - -export const UnderlinedHightlightButton: Story = { - args: { - style: ButtonStyle.underlinedHightlightButton, - children: "underlined hightlight button", - }, -}; - -export const SimpleButton: Story = { - args: { - style: ButtonStyle.simpleButton, - children: "simple button", - }, -}; diff --git a/src/components/ui/Button/index.tsx b/src/components/ui/Button/index.tsx deleted file mode 100644 index 5adb01a5..00000000 --- a/src/components/ui/Button/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Slot } from "@radix-ui/react-slot"; -import { getComponentByStyle, ButtonStyle } from "./styles"; - -export default function Button({ - style = ButtonStyle.default, - asChild, - children, - ...props -}: { - style?: ButtonStyle; - asChild?: boolean; - children: React.ReactNode; - [k: string]: unknown; -}) { - const Component = asChild ? Slot : "button"; - const ComponentStyle = getComponentByStyle(style); - - return ( -
- - {children} - -
- ); -} diff --git a/src/components/ui/Button/styles.tsx b/src/components/ui/Button/styles.tsx deleted file mode 100644 index 91caddab..00000000 --- a/src/components/ui/Button/styles.tsx +++ /dev/null @@ -1,62 +0,0 @@ -export enum ButtonStyle { - default = "default", - bigButton = "bigButton", - underlinedButton = "underlinedButton", - underlinedHightlightButton = "underlinedHightlightButton", - simpleButton = "simpleButton", -} - -type StyleComponentType = { - children: React.ReactNode; -}; - -function DefaultStyleComponent({ children }: StyleComponentType) { - return children; -} - -function UnderlinedButtonStyleComponent({ children }: StyleComponentType) { - return ( - - {children} - - ); -} - -function UnderlinedHightlightButtonStyleComponent({ - children, -}: StyleComponentType) { - return ( - - {children} - - ); -} - -function BigButtonStyleComponent({ children }: StyleComponentType) { - return ( - - {children} - - ); -} - -function SimpleButtonStyleComponent({ children }: StyleComponentType) { - return ( - - {children} - - ); -} - -const componentsStyleMap: Record = { - [ButtonStyle.underlinedButton]: UnderlinedButtonStyleComponent, - [ButtonStyle.underlinedHightlightButton]: - UnderlinedHightlightButtonStyleComponent, - [ButtonStyle.bigButton]: BigButtonStyleComponent, - [ButtonStyle.default]: DefaultStyleComponent, - [ButtonStyle.simpleButton]: SimpleButtonStyleComponent, -}; - -export function getComponentByStyle(style: ButtonStyle) { - return componentsStyleMap[style] || DefaultStyleComponent; -} diff --git a/src/components/ui/Form/FormContainer.tsx b/src/components/ui/Form/FormContainer.tsx index 767e2e72..3f893dda 100644 --- a/src/components/ui/Form/FormContainer.tsx +++ b/src/components/ui/Form/FormContainer.tsx @@ -5,36 +5,26 @@ import { SubmitButton } from "@/components/forms/SubmitButton"; type Props = { form: UseFormReturn; - initialData: any; + submitButton?: React.ReactNode; onSubmit: (data: any) => void; - loading?: boolean; children: React.ReactNode; className?: string; - ctaLabel: string; footerSide?: "left" | "right"; }; export const FormContainer = ({ form, - initialData, onSubmit, - loading, children, className, - ctaLabel, footerSide, + submitButton, }: Props) => { return (
{children} - - - + {submitButton}
); diff --git a/tailwind.config.cjs b/tailwind.config.cjs index df444cef..fb7d1d44 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -4,11 +4,19 @@ const config = { theme: { extend: { colors: { - bgColor: "var(--background)", textColor: "var(--text)", - buttonTextColor: "var(--button-text)", - highlightTextColor: "var(--highlight-text)", + textInactiveColor: "var(--inactive)", + bgColor: "var(--background)", + highlightColor: "var(--highlight)", errorColor: "var(--error)", + buttonTextColor: "var(--button-text)", + visitedLinkColor: "var(--visited-link)", + }, + fontSize: { + textGiantSize: "var(--text-giant)", + textGiantSmallSize: "var(--text-giant-small)", + textBaseSize: "var(--text-base)", + textSmallSize: "var(--text-small)", }, }, },