Plugins (Interne)
- Introduction
- Nos plugins
Architecture des projets
Architecture storefront
Arborescence de l’application NextJS
Dossier contenant l’ensemble du système de routing de NextJS, chaque page doit être géré de la manière la plus simple de manière à géré la récupération des informations attendu par le template, les chargements, et gérer les métadata de la page
Dossier regroupant les ressources static de l’application
- fonts
- icons
Dossier regroupant les composants génériques (shadcn / custom)
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import * as React from 'react'
import { cn } from 'lib/util'
const buttonVariants = cva(
'font-medium text-sm md:text-base text-center transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2 justify-center rounded',
{
variants: {
variant: {
default: '!bg-primary border border-transparent hover:!bg-primary-700 disabled:hover:!bg-primary text-white',
outline:
'bg-white hover:!bg-primary border !border-primary text-black hover:text-white disabled:hover:bg-white disabled:hover:text-black',
secondary:
'bg-slate-100 text-slate-900 hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80',
ghost: 'hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-slate-50',
},
size: {
default: 'px-5 py-2.5',
sm: 'px-3',
lg: 'px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button'
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
},
)
Button.displayName = 'Button'
export { Button, buttonVariants }
Dossier contenant tous les utilitaire de l’application (par exemple les fonction du client Medusa, des fonctions globales, les configurations et constantes)
Dossier contenant les animations utilisées sur l’application
export const articleCardAnimation = {
initial: { opacity: 0, y: -20 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -20 },
transition: { duration: 0.2 },
}
export const categoryCardAnimation = {
initial: { opacity: 0, x: 10 },
whileInView: { opacity: 1, x: 0 },
viewport: { once: true },
}
Dossier contenant les contextes utilisés sur l’application
Dossier permettant de regrouper les fonctions intéragissant avec les API dont celle de medusajs
Hooks utilisés généralement sur l’application (en complément de usehooks-ts )
import { useState } from "react"
export type StateType = [boolean, () => void, () => void, () => void] & {
state: boolean
open: () => void
close: () => void
toggle: () => void
}
/**
*
* @param initialState - boolean
* @returns An array like object with `state`, `open`, `close`, and `toggle` properties
* to allow both object and array destructuring
*
* ```
* const [showModal, openModal, closeModal, toggleModal] = useToggleState()
* // or
* const { state, open, close, toggle } = useToggleState()
* ```
*/
const useToggleState = (initialState = false) => {
const [state, setState] = useState<boolean>(initialState)
const close = () => {
setState(false)
}
const open = () => {
setState(true)
}
const toggle = () => {
setState((state) => !state)
}
const hookData = [state, open, close, toggle] as StateType
hookData.state = state
hookData.open = open
hookData.close = close
hookData.toggle = toggle
return hookData
}
export default useToggleState
Toutes les fonctions utilitaires de l’application
export const isEmpty = (input: any) => {
return (
input === null ||
input === undefined ||
(isObject(input) && Object.keys(input).length === 0) ||
(isArray(input) && (input as any[]).length === 0) ||
(typeof input === "string" && input.trim().length === 0)
)
}
Fichier permettant de gérer toutes les configurations de l’application
import Medusa from '@medusajs/medusa-js'
// Defaults to standard port for Medusa server
let MEDUSA_BACKEND_URL = 'http://localhost:9000'
if (process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL) {
MEDUSA_BACKEND_URL = process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL
}
export const medusaClient = new Medusa({
baseUrl: MEDUSA_BACKEND_URL,
maxRetries: 3,
})
Fichier regroupant toutes les constantes de l’application
import React from "react"
import { CreditCard } from "@medusajs/icons"
import Ideal from "@modules/common/icons/ideal"
import Bancontact from "@modules/common/icons/bancontact"
import PayPal from "@modules/common/icons/paypal"
/* Map of payment provider_id to their title and icon. Add in any payment providers you want to use. */
export const paymentInfoMap: Record<
string,
{ title: string; icon: React.JSX.Element }
> = {
stripe: {
title: "Credit card",
icon: <CreditCard />,
},
"stripe-ideal": {
title: "iDeal",
icon: <Ideal />,
},
"stripe-bancontact": {
title: "Bancontact",
icon: <Bancontact />,
},
paypal: {
title: "PayPal",
icon: <PayPal />,
},
manual: {
title: "Test payment",
icon: <CreditCard />,
},
// Add more payment providers here
}
// Add currencies that don't need to be divided by 100
export const noDivisionCurrencies = [
"krw",
"jpy",
"vnd",
"clp",
"pyg",
"xaf",
"xof",
"bif",
"djf",
"gnf",
"kmf",
"mga",
"rwf",
"xpf",
"htg",
"vuv",
"xag",
"xdr",
"xau",
]
L’objectif de ce dossier est de s’approcher de la méthodologie DDD, visant à diviser l’application en modules, chaque module doit être indépendant des autres, et doit être capable de fonctionner seul et représenter une partie de l’application.
- account
- cart
- categories
- checkout
- order
- product
- etc…
Exemple pour un module de compte :
On créer un dossier par composant (dédié à ce métier), par exemple :
- login
- register
- overview
- address-book
- address-card
- etc…
Et un fichier index.tsx dans chacun des composant avec son export default. L’objectif est d’avoir un nom cohérent, et la logique la plus simple concentré dans ce fichier.
Ce dossier permet de regrouper les point d’entrée après les pages et doit être capable de gérer les différentes vues du module, il regroupe ensuite l’appel des différents composants
"use client"
import { useState } from "react"
import Register from "@modules/account/components/register"
import Login from "@modules/account/components/login"
export enum LOGIN_VIEW {
SIGN_IN = "sign-in",
REGISTER = "register",
}
const LoginTemplate = () => {
const [currentView, setCurrentView] = useState("sign-in")
return (
<div className="w-full flex justify-start px-8 py-8">
{currentView === "sign-in" ? (
<Login setCurrentView={setCurrentView} />
) : (
<Register setCurrentView={setCurrentView} />
)}
</div>
)
}
export default LoginTemplate
De fichier regroupe les server actions du module, par exemple les appels aux API, les fonctions de traitement de données, etc…
"use server"
import {
addShippingAddress,
authenticate,
createCustomer,
deleteShippingAddress,
getToken,
updateCustomer,
updateShippingAddress,
} from "@lib/data"
import { revalidateTag } from "next/cache"
import { redirect } from "next/navigation"
import { cookies, headers } from "next/headers"
import {
Customer,
StorePostCustomersCustomerAddressesAddressReq,
StorePostCustomersCustomerAddressesReq,
StorePostCustomersCustomerReq,
StorePostCustomersReq,
} from "@medusajs/medusa"
export async function signUp(_currentState: unknown, formData: FormData) {
const customer = {
email: formData.get("email"),
password: formData.get("password"),
first_name: formData.get("first_name"),
last_name: formData.get("last_name"),
phone: formData.get("phone"),
} as StorePostCustomersReq
try {
await createCustomer(customer)
await getToken({ email: customer.email, password: customer.password }).then(
() => {
revalidateTag("customer")
}
)
} catch (error: any) {
return error.toString()
}
}
export async function logCustomerIn(
_currentState: unknown,
formData: FormData
) {
const email = formData.get("email") as string
const password = formData.get("password") as string
try {
await getToken({ email, password }).then(() => {
revalidateTag("customer")
})
} catch (error: any) {
return error.toString()
}
}
Dossier regroupant l’ensemble des configurations de tailwindcss, les styles globaux, les variables, etc…
Dossier regroupant les différents types de l’application, ils peuvent être regroupé par fichier
- builderio.ts
- global.ts
- medusa.ts