From bc6f65dc9afa29fbb94038b1cfd5cbee2d87719c Mon Sep 17 00:00:00 2001 From: ertopogo Date: Sun, 1 Feb 2026 02:53:13 +0100 Subject: feat: mise à jour storefront + checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- storefront/components/Layout.js | 154 ++++++------- storefront/lib/format.js | 22 +- storefront/lib/storefront.js | 132 ++++++------ storefront/pages/cart.js | 202 ++++++++--------- storefront/pages/checkout.js | 384 ++++++++++++++++----------------- storefront/pages/login.js | 164 +++++++------- storefront/pages/order-confirmation.js | 34 +-- storefront/pages/register.js | 200 ++++++++--------- 8 files changed, 646 insertions(+), 646 deletions(-) diff --git a/storefront/components/Layout.js b/storefront/components/Layout.js index dce09eb..bb3070f 100644 --- a/storefront/components/Layout.js +++ b/storefront/components/Layout.js @@ -1,77 +1,77 @@ -import { useEffect, useState } from "react" -import Link from "next/link" -import { medusaClient } from "../lib/medusa-client" -import { clearStoredToken, getStoredToken } from "../lib/storefront" - -export default function Layout({ children }) { - const [isLoggedIn, setIsLoggedIn] = useState(false) - - useEffect(() => { - const token = getStoredToken() - if (token) { - medusaClient.setToken(token) - setIsLoggedIn(true) - } - }, []) - - const handleLogout = () => { - clearStoredToken() - medusaClient.setToken(null) - setIsLoggedIn(false) - } - - return ( -
-
- - Lucien-sens-bon - - -
-
{children}
-
- ) -} +import { useEffect, useState } from "react" +import Link from "next/link" +import { medusaClient } from "../lib/medusa-client" +import { clearStoredToken, getStoredToken } from "../lib/storefront" + +export default function Layout({ children }) { + const [isLoggedIn, setIsLoggedIn] = useState(false) + + useEffect(() => { + const token = getStoredToken() + if (token) { + medusaClient.setToken(token) + setIsLoggedIn(true) + } + }, []) + + const handleLogout = () => { + clearStoredToken() + medusaClient.setToken(null) + setIsLoggedIn(false) + } + + return ( +
+
+ + Lucien-sens-bon + + +
+
{children}
+
+ ) +} diff --git a/storefront/lib/format.js b/storefront/lib/format.js index 2f44c2e..9e92a8b 100644 --- a/storefront/lib/format.js +++ b/storefront/lib/format.js @@ -1,11 +1,11 @@ -export const formatAmount = (amount, currencyCode = "EUR") => { - if (typeof amount !== "number") { - return "" - } - - const normalizedCurrency = currencyCode.toUpperCase() - return new Intl.NumberFormat("fr-FR", { - style: "currency", - currency: normalizedCurrency, - }).format(amount / 100) -} +export const formatAmount = (amount, currencyCode = "EUR") => { + if (typeof amount !== "number") { + return "" + } + + const normalizedCurrency = currencyCode.toUpperCase() + return new Intl.NumberFormat("fr-FR", { + style: "currency", + currency: normalizedCurrency, + }).format(amount / 100) +} diff --git a/storefront/lib/storefront.js b/storefront/lib/storefront.js index b6433a9..ccfe1cd 100644 --- a/storefront/lib/storefront.js +++ b/storefront/lib/storefront.js @@ -1,66 +1,66 @@ -export const cartStorageKey = "lsb_cart_id" -export const tokenStorageKey = "lsb_customer_token" - -export const getStoredCartId = () => { - if (typeof window === "undefined") { - return null - } - return window.localStorage.getItem(cartStorageKey) -} - -export const setStoredCartId = (cartId) => { - if (typeof window === "undefined") { - return - } - window.localStorage.setItem(cartStorageKey, cartId) -} - -export const clearStoredCartId = () => { - if (typeof window === "undefined") { - return - } - window.localStorage.removeItem(cartStorageKey) -} - -export const getStoredToken = () => { - if (typeof window === "undefined") { - return null - } - return window.localStorage.getItem(tokenStorageKey) -} - -export const setStoredToken = (token) => { - if (typeof window === "undefined") { - return - } - window.localStorage.setItem(tokenStorageKey, token) -} - -export const clearStoredToken = () => { - if (typeof window === "undefined") { - return - } - window.localStorage.removeItem(tokenStorageKey) -} - -export const ensureCart = async (client) => { - const storedCartId = getStoredCartId() - - if (storedCartId) { - try { - const { cart } = await client.carts.retrieve(storedCartId) - return cart - } catch (error) { - clearStoredCartId() - } - } - - const { regions } = await client.regions.list() - if (!regions?.length) { - throw new Error("Aucune région disponible pour créer un panier.") - } - - const { cart } = await client.carts.create({ region_id: regions[0].id }) - setStoredCartId(cart.id) - return cart -} +export const cartStorageKey = "lsb_cart_id" +export const tokenStorageKey = "lsb_customer_token" + +export const getStoredCartId = () => { + if (typeof window === "undefined") { + return null + } + return window.localStorage.getItem(cartStorageKey) +} + +export const setStoredCartId = (cartId) => { + if (typeof window === "undefined") { + return + } + window.localStorage.setItem(cartStorageKey, cartId) +} + +export const clearStoredCartId = () => { + if (typeof window === "undefined") { + return + } + window.localStorage.removeItem(cartStorageKey) +} + +export const getStoredToken = () => { + if (typeof window === "undefined") { + return null + } + return window.localStorage.getItem(tokenStorageKey) +} + +export const setStoredToken = (token) => { + if (typeof window === "undefined") { + return + } + window.localStorage.setItem(tokenStorageKey, token) +} + +export const clearStoredToken = () => { + if (typeof window === "undefined") { + return + } + window.localStorage.removeItem(tokenStorageKey) +} + +export const ensureCart = async (client) => { + const storedCartId = getStoredCartId() + + if (storedCartId) { + try { + const { cart } = await client.carts.retrieve(storedCartId) + return cart + } catch (error) { + clearStoredCartId() + } + } + + const { regions } = await client.regions.list() + if (!regions?.length) { + throw new Error("Aucune région disponible pour créer un panier.") + } + + const { cart } = await client.carts.create({ region_id: regions[0].id }) + setStoredCartId(cart.id) + return cart +} diff --git a/storefront/pages/cart.js b/storefront/pages/cart.js index fa625a9..3b02b50 100644 --- a/storefront/pages/cart.js +++ b/storefront/pages/cart.js @@ -1,101 +1,101 @@ -import { useCallback, useEffect, useState } from "react" -import { medusaClient } from "../lib/medusa-client" -import { formatAmount } from "../lib/format" -import { getStoredCartId, clearStoredCartId } from "../lib/storefront" - -export default function CartPage() { - const [cart, setCart] = useState(null) - const [status, setStatus] = useState("") - const [isLoading, setIsLoading] = useState(true) - - const loadCart = useCallback(async () => { - const storedCartId = getStoredCartId() - if (!storedCartId) { - setCart(null) - setIsLoading(false) - return - } - - try { - const { cart: fetchedCart } = await medusaClient.carts.retrieve(storedCartId) - setCart(fetchedCart) - } catch (error) { - clearStoredCartId() - setCart(null) - } finally { - setIsLoading(false) - } - }, []) - - useEffect(() => { - loadCart() - }, [loadCart]) - - const handleRemove = async (lineItemId) => { - if (!cart) { - return - } - setStatus("") - try { - await medusaClient.carts.lineItems.delete(cart.id, lineItemId) - await loadCart() - } catch (error) { - setStatus("Impossible de retirer l'article.") - } - } - - if (isLoading) { - return

Chargement du panier...

- } - - if (!cart || !cart.items?.length) { - return

Votre panier est vide.

- } - - return ( -
-

Panier

- {status &&

{status}

} -
- {cart.items.map((item) => ( -
-
- {item.title} -

Quantité : {item.quantity}

-

- {formatAmount(item.unit_price, cart.region?.currency_code || "eur")} -

-
- -
- ))} -
-

- Total : {formatAmount(cart.total, cart.region?.currency_code || "eur")} -

-
- ) -} +import { useCallback, useEffect, useState } from "react" +import { medusaClient } from "../lib/medusa-client" +import { formatAmount } from "../lib/format" +import { getStoredCartId, clearStoredCartId } from "../lib/storefront" + +export default function CartPage() { + const [cart, setCart] = useState(null) + const [status, setStatus] = useState("") + const [isLoading, setIsLoading] = useState(true) + + const loadCart = useCallback(async () => { + const storedCartId = getStoredCartId() + if (!storedCartId) { + setCart(null) + setIsLoading(false) + return + } + + try { + const { cart: fetchedCart } = await medusaClient.carts.retrieve(storedCartId) + setCart(fetchedCart) + } catch (error) { + clearStoredCartId() + setCart(null) + } finally { + setIsLoading(false) + } + }, []) + + useEffect(() => { + loadCart() + }, [loadCart]) + + const handleRemove = async (lineItemId) => { + if (!cart) { + return + } + setStatus("") + try { + await medusaClient.carts.lineItems.delete(cart.id, lineItemId) + await loadCart() + } catch (error) { + setStatus("Impossible de retirer l'article.") + } + } + + if (isLoading) { + return

Chargement du panier...

+ } + + if (!cart || !cart.items?.length) { + return

Votre panier est vide.

+ } + + return ( +
+

Panier

+ {status &&

{status}

} +
+ {cart.items.map((item) => ( +
+
+ {item.title} +

Quantité : {item.quantity}

+

+ {formatAmount(item.unit_price, cart.region?.currency_code || "eur")} +

+
+ +
+ ))} +
+

+ Total : {formatAmount(cart.total, cart.region?.currency_code || "eur")} +

+
+ ) +} diff --git a/storefront/pages/checkout.js b/storefront/pages/checkout.js index 1970df4..9457daa 100644 --- a/storefront/pages/checkout.js +++ b/storefront/pages/checkout.js @@ -1,192 +1,192 @@ -import { useEffect, useState } from "react" -import { useRouter } from "next/router" -import { medusaClient } from "../lib/medusa-client" -import { getStoredCartId, clearStoredCartId } from "../lib/storefront" - -const initialForm = { - email: "", - first_name: "", - last_name: "", - address_1: "", - postal_code: "", - city: "", - country_code: "fr", -} - -export default function CheckoutPage() { - const router = useRouter() - const [form, setForm] = useState(initialForm) - const [status, setStatus] = useState("") - const [isLoading, setIsLoading] = useState(false) - const [cartId, setCartId] = useState(null) - - useEffect(() => { - const storedCartId = getStoredCartId() - setCartId(storedCartId) - }, []) - - const handleChange = (event) => { - const { name, value } = event.target - setForm((prev) => ({ ...prev, [name]: value })) - } - - const handleSubmit = async (event) => { - event.preventDefault() - setStatus("") - setIsLoading(true) - - if (!cartId) { - setStatus("Votre panier est vide.") - setIsLoading(false) - return - } - - try { - await medusaClient.carts.update(cartId, { - email: form.email, - shipping_address: { - first_name: form.first_name, - last_name: form.last_name, - address_1: form.address_1, - postal_code: form.postal_code, - city: form.city, - country_code: form.country_code, - }, - }) - - const { shipping_options: shippingOptions } = - await medusaClient.shippingOptions.listCartOptions(cartId) - - if (!shippingOptions?.length) { - throw new Error("Aucune option de livraison disponible.") - } - - await medusaClient.carts.addShippingMethod(cartId, { - option_id: shippingOptions[0].id, - }) - - const { cart: cartWithPayments } = await medusaClient.carts.createPaymentSessions( - cartId - ) - - const manualSession = cartWithPayments?.payment_sessions?.find( - (session) => session.provider_id === "manual" - ) - const providerId = - manualSession?.provider_id || - cartWithPayments?.payment_sessions?.[0]?.provider_id - - if (!providerId) { - throw new Error("Aucun moyen de paiement disponible.") - } - - await medusaClient.carts.setPaymentSession(cartId, { provider_id: providerId }) - - const { type, data } = await medusaClient.carts.complete(cartId) - if (type === "order" && data?.id) { - clearStoredCartId() - router.push(`/order-confirmation?order_id=${data.id}`) - return - } - - setStatus("Commande validée, mais sans numéro de commande.") - } catch (error) { - setStatus("Impossible de finaliser la commande.") - } finally { - setIsLoading(false) - } - } - - return ( -
-

Finaliser la commande

-
- - - - - - - - - {status &&

{status}

} -
-
- ) -} +import { useEffect, useState } from "react" +import { useRouter } from "next/router" +import { medusaClient } from "../lib/medusa-client" +import { getStoredCartId, clearStoredCartId } from "../lib/storefront" + +const initialForm = { + email: "", + first_name: "", + last_name: "", + address_1: "", + postal_code: "", + city: "", + country_code: "fr", +} + +export default function CheckoutPage() { + const router = useRouter() + const [form, setForm] = useState(initialForm) + const [status, setStatus] = useState("") + const [isLoading, setIsLoading] = useState(false) + const [cartId, setCartId] = useState(null) + + useEffect(() => { + const storedCartId = getStoredCartId() + setCartId(storedCartId) + }, []) + + const handleChange = (event) => { + const { name, value } = event.target + setForm((prev) => ({ ...prev, [name]: value })) + } + + const handleSubmit = async (event) => { + event.preventDefault() + setStatus("") + setIsLoading(true) + + if (!cartId) { + setStatus("Votre panier est vide.") + setIsLoading(false) + return + } + + try { + await medusaClient.carts.update(cartId, { + email: form.email, + shipping_address: { + first_name: form.first_name, + last_name: form.last_name, + address_1: form.address_1, + postal_code: form.postal_code, + city: form.city, + country_code: form.country_code, + }, + }) + + const { shipping_options: shippingOptions } = + await medusaClient.shippingOptions.listCartOptions(cartId) + + if (!shippingOptions?.length) { + throw new Error("Aucune option de livraison disponible.") + } + + await medusaClient.carts.addShippingMethod(cartId, { + option_id: shippingOptions[0].id, + }) + + const { cart: cartWithPayments } = await medusaClient.carts.createPaymentSessions( + cartId + ) + + const manualSession = cartWithPayments?.payment_sessions?.find( + (session) => session.provider_id === "manual" + ) + const providerId = + manualSession?.provider_id || + cartWithPayments?.payment_sessions?.[0]?.provider_id + + if (!providerId) { + throw new Error("Aucun moyen de paiement disponible.") + } + + await medusaClient.carts.setPaymentSession(cartId, { provider_id: providerId }) + + const { type, data } = await medusaClient.carts.complete(cartId) + if (type === "order" && data?.id) { + clearStoredCartId() + router.push(`/order-confirmation?order_id=${data.id}`) + return + } + + setStatus("Commande validée, mais sans numéro de commande.") + } catch (error) { + setStatus("Impossible de finaliser la commande.") + } finally { + setIsLoading(false) + } + } + + return ( +
+

Finaliser la commande

+
+ + + + + + + + + {status &&

{status}

} +
+
+ ) +} diff --git a/storefront/pages/login.js b/storefront/pages/login.js index 033f9b5..9539a51 100644 --- a/storefront/pages/login.js +++ b/storefront/pages/login.js @@ -1,82 +1,82 @@ -import { useState } from "react" -import { useRouter } from "next/router" -import { medusaClient } from "../lib/medusa-client" -import { setStoredToken } from "../lib/storefront" - -export default function LoginPage() { - const router = useRouter() - const [form, setForm] = useState({ email: "", password: "" }) - const [status, setStatus] = useState("") - const [isLoading, setIsLoading] = useState(false) - - const handleChange = (event) => { - const { name, value } = event.target - setForm((prev) => ({ ...prev, [name]: value })) - } - - const handleSubmit = async (event) => { - event.preventDefault() - setStatus("") - setIsLoading(true) - - try { - const { access_token: accessToken } = await medusaClient.auth.getToken({ - email: form.email, - password: form.password, - }) - - setStoredToken(accessToken) - medusaClient.setToken(accessToken) - setStatus("Connexion réussie.") - router.push("/") - } catch (error) { - setStatus("Identifiants invalides ou indisponibles.") - } finally { - setIsLoading(false) - } - } - - return ( -
-

Se connecter

-
- - - - {status &&

{status}

} -
-
- ) -} +import { useState } from "react" +import { useRouter } from "next/router" +import { medusaClient } from "../lib/medusa-client" +import { setStoredToken } from "../lib/storefront" + +export default function LoginPage() { + const router = useRouter() + const [form, setForm] = useState({ email: "", password: "" }) + const [status, setStatus] = useState("") + const [isLoading, setIsLoading] = useState(false) + + const handleChange = (event) => { + const { name, value } = event.target + setForm((prev) => ({ ...prev, [name]: value })) + } + + const handleSubmit = async (event) => { + event.preventDefault() + setStatus("") + setIsLoading(true) + + try { + const { access_token: accessToken } = await medusaClient.auth.getToken({ + email: form.email, + password: form.password, + }) + + setStoredToken(accessToken) + medusaClient.setToken(accessToken) + setStatus("Connexion réussie.") + router.push("/") + } catch (error) { + setStatus("Identifiants invalides ou indisponibles.") + } finally { + setIsLoading(false) + } + } + + return ( +
+

Se connecter

+
+ + + + {status &&

{status}

} +
+
+ ) +} diff --git a/storefront/pages/order-confirmation.js b/storefront/pages/order-confirmation.js index 4c7930f..c1ce8df 100644 --- a/storefront/pages/order-confirmation.js +++ b/storefront/pages/order-confirmation.js @@ -1,17 +1,17 @@ -import { useRouter } from "next/router" - -export default function OrderConfirmationPage() { - const router = useRouter() - const { order_id: orderId } = router.query - - return ( -
-

Merci pour votre commande

- {orderId ? ( -

Votre commande a bien été enregistrée : {orderId}

- ) : ( -

Votre commande a bien été enregistrée.

- )} -
- ) -} +import { useRouter } from "next/router" + +export default function OrderConfirmationPage() { + const router = useRouter() + const { order_id: orderId } = router.query + + return ( +
+

Merci pour votre commande

+ {orderId ? ( +

Votre commande a bien été enregistrée : {orderId}

+ ) : ( +

Votre commande a bien été enregistrée.

+ )} +
+ ) +} diff --git a/storefront/pages/register.js b/storefront/pages/register.js index 48831ea..688a09e 100644 --- a/storefront/pages/register.js +++ b/storefront/pages/register.js @@ -1,100 +1,100 @@ -import { useState } from "react" -import { useRouter } from "next/router" -import { medusaClient } from "../lib/medusa-client" - -export default function RegisterPage() { - const router = useRouter() - const [form, setForm] = useState({ - first_name: "", - last_name: "", - email: "", - password: "", - }) - const [status, setStatus] = useState("") - const [isLoading, setIsLoading] = useState(false) - - const handleChange = (event) => { - const { name, value } = event.target - setForm((prev) => ({ ...prev, [name]: value })) - } - - const handleSubmit = async (event) => { - event.preventDefault() - setStatus("") - setIsLoading(true) - - try { - await medusaClient.customers.create(form) - setStatus("Compte créé. Vous pouvez vous connecter.") - router.push("/login") - } catch (error) { - setStatus("Impossible de créer le compte pour le moment.") - } finally { - setIsLoading(false) - } - } - - return ( -
-

Créer un compte

-
- - - - - - {status &&

{status}

} -
-
- ) -} +import { useState } from "react" +import { useRouter } from "next/router" +import { medusaClient } from "../lib/medusa-client" + +export default function RegisterPage() { + const router = useRouter() + const [form, setForm] = useState({ + first_name: "", + last_name: "", + email: "", + password: "", + }) + const [status, setStatus] = useState("") + const [isLoading, setIsLoading] = useState(false) + + const handleChange = (event) => { + const { name, value } = event.target + setForm((prev) => ({ ...prev, [name]: value })) + } + + const handleSubmit = async (event) => { + event.preventDefault() + setStatus("") + setIsLoading(true) + + try { + await medusaClient.customers.create(form) + setStatus("Compte créé. Vous pouvez vous connecter.") + router.push("/login") + } catch (error) { + setStatus("Impossible de créer le compte pour le moment.") + } finally { + setIsLoading(false) + } + } + + return ( +
+

Créer un compte

+
+ + + + + + {status &&

{status}

} +
+
+ ) +} -- cgit v1.2.3