Skip to content

About

A modern, scalable ecommerce monorepo template based on Next.js and Feature-Based Architecture.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Next Feature Based Template

A modern, scalable monorepo template for ecommerce projects based on the Feature-Based Architecture.

Architecture

This project follows the Feature-Based Architecture as described in the Style Guide section.

Core Concepts:

  • Monorepo: Managed with pnpm workspaces and Turbo.
  • Feature-Based Modules: Each app is divided into modules (e.g., product, cart, order).
  • Standardized Structure: Each module follows a strict internal structure (components, hooks, services, types.ts).
  • Kebab-case: Strict enforcement of kebab-case for all files and directories.

Project Structure

  • apps/admin: Administrative dashboard for managing products, users, and orders.
  • apps/storefront: Customer-facing shop for browsing products, managing cart, and placing orders.
  • packages/ui: Shared UI components (powered by Storybook).

Getting Started

1) Prerequisites

  • Node.js (LTS recommended)
  • pnpm installed globally
npm i -g pnpm

2) Install dependencies

From the monorepo root:

pnpm install

3) Development

Run the Admin app:

pnpm dev:admin

Run the Storefront app:

pnpm dev:storefront

4) Build

Build all apps:

pnpm build

Build a specific app:

pnpm build:admin
# or
pnpm build:storefront

Style Guide

This document defines the naming conventions, architectural patterns, and folder structure standards for React and Next.js projects. Adhering to these guidelines ensures consistency across the codebase, making it easier for team members to navigate, understand, and maintain the application.

Files

Naming convention

All file and directory names must use kebab-case (e.g., product-form.tsx, use-get-product.ts). This ensures consistency across different operating systems and improves readability.

Forms

Forms are critical parts of the application. Consistent naming helps identify their purpose and scope at a glance.

Terms

  • bulk: Operations involving multiple items simultaneously (e.g., uploading a CSV of products).
  • wizard: A step-by-step flow or multi-step process (e.g., a registration flow with personal info, then address, then payment).

Naming Patterns

  • <Domain>Form.tsx: A general-purpose form for creating and editing a single item.
  • <Domain><Action>Form.tsx: A form dedicated to a specific action (e.g., ProductCreateForm).
  • <Domain>Bulk<Action>Form.tsx: A form for performing an action on multiple items at once.
  • <Domain><Action>WizardForm.tsx: A multi-step form for a specific action on a single item.
  • <Domain>Bulk<Action>WizardForm.tsx: A multi-step form for a specific action on multiple items.

Examples

Basic Forms

  • ProductForm: Simple form that handles both creation and editing of a single product.
  • ProductCreateForm: Dedicated form for creating a new product.
  • ProductEditForm: Dedicated form for editing an existing product.

Bulk & Wizard Forms

  • ProductBulkCreateForm: Form designed to create multiple products at once.
  • ProductBulkEditForm: Form designed to edit multiple products simultaneously.
  • ProductCreateWizardForm: A multi-step form for registering a new product.

Schemas

Schemas are defined using Zod and follow a naming convention that mirrors their usage in the application.

  • <domain>Schema: Represents the core data model of a single domain.
  • <domain>FiltersSchema: Criteria used for searching or filtering domains.
  • <domain><Action>InputSchema: The data structure required for a specific action (Request body).
  • <domain><Action>OutputSchema: The data structure returned by a specific action (Response body).
  • <domain>Bulk<Action>InputSchema: Input for a bulk operation.
  • <domain>Bulk<Action>OutputSchema: Output from a bulk operation.

Examples

  • productSchema
  • productFiltersSchema
  • productCreateInputSchema
  • productCreateOutputSchema
// Example: src/modules/product/schema.ts
export const productCreateInputSchema = z.object({
  name: z.string(),
  price: z.number(),
});

Types

Standardized type naming makes it clear what data structure is being handled. Types are often inferred from schemas.

  • <Domain>: Represents the core data model of a single domain.
  • <Domain>Filters: Criteria used for searching or filtering domains.
  • <Domain><Action>Input: The data structure required for a specific action.
  • <Domain><Action>Output: The data structure returned by a specific action.
  • <Domain>Bulk<Action>Input: Input for a bulk operation.
  • <Domain>Bulk<Action>Output: Output from a bulk operation.
  • <ComponentName>Props: Props for a specific React component.
  • <HookName>Params: Params for a specific React hook.

Examples

  • Product
  • ProductFilters
  • ProductCreateInput
// Example: src/modules/product/types.ts
import { z } from "zod";
import { productCreateInputSchema } from "./schema";

export type ProductCreateInput = z.infer<typeof productCreateInputSchema>;

export type Product = {
  name: string;
  price: number;
};

If the output of various actions (create, edit, get) for a domain is identical, use the common <Domain> type instead of creating redundant output types.

// Example: src/modules/product/services/product-service.ts
export function getProducts(): Result<Product[]> {
  // ...
}

export function createProduct(input: ProductCreateInput): Result<Product> {
  // ...
}

Components & Props

When creating components, destructure props in the function body for clarity and consistency.

// Example: src/modules/product/components/product-form-dialog.tsx
type ProductFormDialogProps = {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  productId?: string | null;
};

export function ProductFormDialog(props: ProductFormDialogProps) {
  const { open, onOpenChange } = props;
  // ...
}

Pages & Composition

Pages should be treated as entry points that compose multiple smaller components. Logic for fetching data or handling forms should be kept at the page level or in high-level container components.

  • src/modules/<feature>/components/pages/<domain>-page.tsx: Main page component.
  • src/modules/<feature>/components/pages/<domain>-details-page.tsx: Details page component.

Example Page Structure:

// Example: src/modules/product/components/pages/products-page.tsx
export function ProductsPage() {
  const handleAddProduct = () => {
    // Navigate to the create product page
  };

  const pagination = {
    // ...
  };

  const filters = useMemo(() => {
    // ...
  }, []);

  const handleFiltersChange = useCallback((filters: ProductFilters) => {
    // Handle filters change logic
  }, []);

  const { data, isLoading } = useGetProducts({
    filters,
    pagination,
  });

  return (
    <div className="flex flex-col gap-4">
      <ProductListHeader onAdd={handleAddProduct} />
      <ProductFiltersBox
        filters={filters}
        onFiltersChange={handleFiltersChange}
      />
      <ProductList items={data?.data} isLoading={isLoading} />
    </div>
  );
}

Display vs. Form Components

For components that support both viewing and editing, split the logic into separate components:

  • <Domain>Display: A read-only view of the domain's data.
  • <Domain>Form: An editable form for the domain's data.
  • <Domain>: A parent component that manages the toggle state between Display and Form.
// Example: src/modules/product/components/product-details.tsx
export function ProductDetails() {
  const [isEditing, setIsEditing] = useState(false);

  if (isEditing) {
    return <ProductDetailsForm onCancel={() => setIsEditing(false)} />;
  }

  return <ProductDetailsDisplay onEdit={() => setIsEditing(true)} />;
}

Folder Structure & Module Organization

Code should be organized by feature or domain under the src/modules directory. This promotes encapsulation and modularity. Each module should follow this structure where applicable:

  • src/modules/<feature-name>/components/: React components specific to this feature. Use sub-directories for complex components.
  • src/modules/<feature-name>/hooks/: Custom React hooks, often wrapping services with @tanstack/react-query.
  • src/modules/<feature-name>/services/: Functions for API calls, data transformation, and core business logic.
  • src/modules/<feature-name>/types.ts: TypeScript interfaces and types specific to the module.
  • src/modules/<feature-name>/schema.ts: Zod validation schemas for forms and API interactions.
  • src/modules/<feature-name>/context.ts: React Context and Providers for module-level state management.
  • src/modules/<feature-name>/constants.ts: Module-specific constants, enums, and configuration.
  • src/modules/<feature-name>/utils.ts: Small utility functions that don't belong in services.
  • src/modules/<feature-name>/index.ts: The entry point for the module, exporting only what is needed by the rest of the application.

Hooks

Hooks are primarily used to manage side effects and state. When using @tanstack/react-query, hooks should be prefixed based on the operation type.

Naming Patterns

  • useGet<Domains>: For fetching a list of domains.
  • useGet<Domain>: For fetching a single domain by ID.
  • use<Action><Domain>Mutation: For specific actions (e.g., useActivateProductMutation).
  • use<Domain>Form: For managing form state and logic.
  • use<Action><Domain>Form: For managing form state and logic.

Examples

  • useGetProducts
  • useGetProduct
  • useUpdateProductMutation
  • useProductForm
  • useEditProductForm

Query Keys

Query keys should be structured as an array, starting with the domain name or module name, followed by any specific identifiers or parameters.

// Example
queryKey: ["products"];
queryKey: ["products", productId];
queryKey: ["products", { filters }];

Mutation Success Handling

Mutations should generally invalidate relevant queries upon success to ensure data consistency.

// Example: src/modules/product/services/product-service.ts
export function useUpdateProductMutation() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: ProductUpdateInput) => updateProduct(data),
    onSuccess: () => {
      void queryClient.invalidateQueries({
        queryKey: ["products"],
      });
    },
  });
}

Services

Services contain the core logic for interacting with external APIs or performing complex data transformations.

Naming Patterns

  • get<Domains>: Fetch a list of domains.
  • get<Domain>: Fetch a single domain by ID.
  • create<Domain>: Create a new domain.
  • update<Domain>: Update an existing domain.
  • delete<Domain>: Delete a domain.
  • <action><Domain>: Perform a specific action on a domain (e.g., activateProduct).

API Integration

Standardizing how we interact with APIs ensures consistent error handling and data retrieval.

Error Handling

Usage Responsibility: Error handling is the responsibility of the calling code (the top level). Use try...catch blocks in event handlers or components when calling service methods or mutations to handle potential failures and display user-friendly messages (e.g., via toast).

Error Normalization: We use a standardized ResultError type for API errors. Any error (Axios, server, or runtime) should be resolved to use the toResultError utility function to ensure it follows a consistent structure across the application.

// src/modules/product/components/products-page.tsx
const handleUpdateProduct = async (id: string) => {
  try {
    await updateProduct.mutateAsync(id);
    toast.success("Product updated successfully");
  } catch (error) {
    // Standardize any error using toResultError
    toast.error(toResultError(error).message);
  }
};

User Confirmation

When performing destructive or critical actions, use alertDialogManager to get user confirmation. This should be handled in the event handler (e.g., handleDelete).

  • Handle confirmation and later logic inside the event handler.
  • Wrap the operation in a try...catch block.
  • Pass the async operation to the onConfirm callback of alertDialogManager.confirm.
  • Provide immediate feedback via toast.
// src/modules/product/components/products-page.tsx
const handleDeleteProduct = async (id: string) => {
  try {
    const confirmed = await alertDialogManager.confirm({
      title: "Delete Product",
      description: "Are you sure you want to delete this product?",
      confirmText: "Delete",
      onConfirm: () => deleteProduct.mutateAsync(id),
    });

    if (confirmed) {
      toast.success("Product deleted successfully");
    }
  } catch (error) {
    toast.error(toResultError(error).message);
  }
};

Response Types

  • Result<T>: A standard response wrapper for a single object or simple list.
  • ResultPaginated<T>: A response wrapper that includes pagination metadata.
  • ResultError<T>: A standardized structure for API errors, including an id, message, and optional validation meta.

Constants & Utils

Constants and utilities should be named descriptively and follow camelCase.

Constants

Use PascalCase for Enums and camelCase for constant objects or arrays.

// constants.ts
export enum ProductStatus {
  Active = "active",
  Inactive = "inactive",
}

export const PRODUCT_TABS = [
  { value: "overview", label: "Overview" },
  { value: "inventory", label: "Inventory" },
];

Utils

Utility functions should be pure and focused on a single task.

Naming Patterns

  • translate<Domain><Purpose>: For translating domain-specific values.
  • format<Domain><Purpose>: For formatting values (e.g., currency, dates).
  • parse<Domain><Purpose>: For extracting data from strings or objects.
  • calculate<Domain><Purpose>: For logic-heavy computations.
  • generate<Domain><Purpose>: For creating new values (e.g., slugs, IDs).
  • normalize<Domain><Purpose>: For standardizing data structures.
  • build<Domain><Purpose>: For constructing complex objects or params.
  • map<Domain>To<Target>: For transforming one data type to another.
  • sanitize<Domain><Purpose>: For cleaning up data.
  • sort<Domain>By<Property>: For ordering collections.

Examples

  • translateProductStatus(status, t)
  • formatProductPrice(price, currency)
  • parseProductFiltersParams(searchParams)
  • calculateProductDiscount(price, compareAtPrice)
  • generateProductSlug(name)
  • normalizeProductFilters(filters)
  • buildProductFilterParams(filters)
  • mapProductToFormValues(product)
  • sanitizeProductPayload(payload)
  • sortProductsByPrice(products)
// utils.ts
export function formatProductPrice(amount: number, currency = "USD"): string {
  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency,
  }).format(amount);
}

Translations

We use next-intl for translations. Always use the useTranslations hook for managing UI text.

import { useTranslations } from "next-intl";

export function ProductDetails({ product }: { product: Product }) {
  // Translation key should be same as Component Name.
  const t = useTranslations("ProductDetails");

  return (
    <div>
      <h1>{t("title")}</h1>
      <p>{t("description", { name: product.name })}</p>
      <button>{t("actions.edit")}</button>
    </div>
  );
}

Internationalization Utils

For formatting dates and times, use the provided hooks in src/modules/i18n/hooks/utils.ts.

import {
  useDisplayDateTimeFormatter,
  useDisplayDateFormatter,
} from "@/modules/i18n/hooks/utils";

type ProductActivityItemProps = {
  date: Date;
};

export function ProductActivityItem(props: ProductActivityItemProps) {
  const { date } = props;

  const formatDateTime = useDisplayDateTimeFormatter();
  const formatDate = useDisplayDateFormatter();

  return (
    <div>
      <span>{formatDateTime(date)}</span>
      <span>{formatDate(date)}</span>
    </div>
  );
}

Translation Utilities (Mappers)

When you need to translate Enums or status codes outside of a component or for reuse, create a utility function using the translate<Domain><Purpose> pattern that accepts the translation function t.

// src/modules/product/utils.ts
import { useTranslations } from "next-intl";
import { ProductStatus } from "./constants";

export function translateProductStatus(
  status: ProductStatus,
  t: ReturnType<typeof useTranslations<never>>,
) {
  return {
    [ProductStatus.Active]: t("ProductStatus.active"),
    [ProductStatus.Inactive]: t("ProductStatus.inactive"),
  }[status];
}

Usage in a component:

// src/modules/product/components/product-status-badge.tsx
import { useTranslations } from "next-intl";
import { translateProductStatus } from "../utils";

export function ProductStatusBadge({ status }: { status: ProductStatus }) {
  const t = useTranslations("Product");

  return <Badge>{translateProductStatus(status, t)}</Badge>;
}

Small Feature-based architecture example

src/modules/product/
├── components/
│   ├── products-page.tsx                           // Compose products list page parts: useGetProducts, ProductList, ProductListFiltersBox, ...
│   ├── product-list.tsx                            // Pure component for rendering the product list/table
│   ├── product-list-columns.tsx                    // Columns config for ProductList
│   ├── product-list-actions.tsx                    // Row/bulk actions for ProductList
│   ├── product-filters-box.tsx                     // Filters related to Product list
│   ├── product-details-page.tsx                    // Compose product details page: getProduct, ProductDetailsTabs, actions, ...
│   ├── product-details-tabs.tsx                    // Tab navigation/container for ProductDetails
│   ├── product-details-overview-tab.tsx            // Overview/general product information tab
│   ├── product-details-overview.tsx                // Compose ProductDetailsOverviewForm, ProductDetailsOverviewDisplay, getProduct, ...
│   ├── product-details-overview-form.tsx           // Shared form used by one or more details tabs
│   ├── product-details-overview-display.tsx        // Shared read-only display used by one or more details tabs
│   ├── product-details-inventory-tab.tsx           // Stock/inventory-related tab
│   ├── product-details-pricing-tab.tsx             // Pricing-related tab
│   ├── product-details-images-tab.tsx              // Product images/media tab
│   ├── product-details-settings-tab.tsx            // Product settings/status/configuration tab
│   ├── product-deactivate-form-dialog.tsx          // Dialog for deactivating a product
│   ├── product-status-badge.tsx
│   ├── product-type-badge.tsx
│   └── product-thumbnail.tsx
├── hooks/
│   ├── use-product-details-overview-form.ts
│   └── use-product-service.ts                      // Hook wrapper of product services inside product-service.ts
├── services/
│   └── product-service.ts
├── constants.ts
├── schema.ts
├── types.ts
├── utils.ts
└── index.ts

Big Feature-based architecture example

src/modules/
├── product/
│   ├── components/
│   │   ├── pages/
│   │   │   ├── products-page.tsx
│   │   │   ├── product-create-page.tsx
│   │   │   ├── product-edit-page.tsx
│   │   │   ├── product-details-page.tsx
│   │   │   └── index.ts
│   │   ├── product-list/
│   │   │   ├── product-list.tsx
│   │   │   ├── product-list-columns.tsx
│   │   │   ├── product-list-actions.tsx
│   │   │   ├── product-list-toolbar.tsx
│   │   │   ├── product-list-filters-box.tsx
│   │   │   └── index.ts
│   │   ├── product-form/
│   │   │   ├── product-form.tsx
│   │   │   ├── product-form-basic-section.tsx
│   │   │   ├── product-form-description-section.tsx
│   │   │   ├── product-form-status-section.tsx
│   │   │   └── index.ts
│   │   ├── product-details/
│   │   │   ├── product-details-header.tsx
│   │   │   ├── product-details-summary-card.tsx
│   │   │   ├── product-details-actions.tsx
│   │   │   ├── product-details-tabs.tsx
│   │   │   ├── product-details-overview-tab.tsx
│   │   │   ├── product-details-activity-tab.tsx
│   │   │   └── index.ts
│   │   ├── product-dialogs/
│   │   │   ├── product-activate-dialog.tsx
│   │   │   ├── product-deactivate-dialog.tsx
│   │   │   ├── product-delete-dialog.tsx
│   │   │   ├── product-duplicate-dialog.tsx
│   │   │   └── index.ts
│   │   └── shared/
│   │       ├── product-status-badge.tsx
│   │       ├── product-type-badge.tsx
│   │       ├── product-thumbnail.tsx
│   │       ├── product-sku-text.tsx
│   │       └── index.ts
│   ├── hooks/
│   │   ├── use-get-products.ts
│   │   ├── use-get-product.ts
│   │   ├── use-create-product-mutation.ts
│   │   ├── use-update-product-mutation.ts
│   │   ├── use-delete-product-mutation.ts
│   │   ├── use-product-form.ts
│   │   └── index.ts
│   ├── services/
│   │   ├── product-service.ts
│   │   └── index.ts
│   ├── schemas/
│   │   ├── product-schema.ts
│   │   ├── product-form-schema.ts
│   │   ├── product-filters-schema.ts
│   │   └── index.ts
│   ├── types/
│   │   ├── product.ts
│   │   ├── product-form.ts
│   │   ├── product-filters.ts
│   │   └── index.ts
│   ├── constants/
│   │   ├── product-status.ts
│   │   ├── product-type.ts
│   │   ├── product-tabs.ts
│   │   └── index.ts
│   ├── utils/
│   │   ├── generate-product-slug.ts
│   │   ├── translate-product-status.ts
│   │   └── index.ts
│   └── index.ts
│
├── product-media/
│   ├── components/
│   │   ├── product-media-gallery.tsx
│   │   ├── product-media-uploader.tsx
│   │   ├── product-media-dropzone.tsx
│   │   ├── product-media-item.tsx
│   │   ├── product-media-sortable-list.tsx
│   │   ├── product-media-preview-dialog.tsx
│   │   ├── product-media-delete-dialog.tsx
│   │   └── index.ts
│   ├── hooks/
│   │   ├── use-get-product-media.ts
│   │   ├── use-upload-product-media-mutation.ts
│   │   ├── use-delete-product-media-mutation.ts
│   │   ├── use-reorder-product-media-mutation.ts
│   │   └── index.ts
│   ├── services/
│   │   ├── product-media-service.ts
│   │   └── index.ts
│   ├── schemas/
│   │   ├── product-media-schema.ts
│   │   └── index.ts
│   ├── types/
│   │   ├── product-media.ts
│   │   └── index.ts
│   └── index.ts
│
├── product-variant/
│   ├── components/
│   │   ├── product-variants-table.tsx
│   │   ├── product-variants-columns.tsx
│   │   ├── product-variant-form.tsx
│   │   ├── product-variant-create-dialog.tsx
│   │   ├── product-variant-edit-dialog.tsx
│   │   ├── product-variant-delete-dialog.tsx
│   │   ├── product-variant-options-editor.tsx
│   │   └── index.ts
│   ├── hooks/
│   │   ├── use-get-product-variants.ts
│   │   ├── use-get-product-variant.ts
│   │   ├── use-create-product-variant-mutation.ts
│   │   ├── use-update-product-variant-mutation.ts
│   │   ├── use-delete-product-variant-mutation.ts
│   │   └── index.ts
│   ├── services/
│   │   ├── product-variant-service.ts
│   │   └── index.ts
│   ├── schemas/
│   │   ├── product-variant-schema.ts
│   │   ├── product-variant-form-schema.ts
│   │   └── index.ts
│   ├── types/
│   │   ├── product-variant.ts
│   │   ├── product-variant-form.ts
│   │   └── index.ts
│   └── index.ts
│
├── product-pricing/
│   ├── components/
│   │   ├── product-price-card.tsx
│   │   ├── product-price-form.tsx
│   │   ├── product-discount-form.tsx
│   │   ├── product-sale-price-form.tsx
│   │   ├── product-tax-class-select.tsx
│   │   ├── product-currency-select.tsx
│   │   └── index.ts
│   ├── hooks/
│   │   ├── use-get-product-pricing.ts
│   │   ├── use-update-product-pricing-mutation.ts
│   │   └── index.ts
│   ├── services/
│   │   ├── product-pricing-service.ts
│   │   └── index.ts
│   ├── schemas/
│   │   ├── product-pricing-schema.ts
│   │   ├── product-pricing-form-schema.ts
│   │   └── index.ts
│   ├── types/
│   │   ├── product-pricing.ts
│   │   └── index.ts
│   └── index.ts
│
├── product-inventory/
│   ├── components/
│   │   ├── product-inventory-card.tsx
│   │   ├── product-inventory-form.tsx
│   │   ├── product-stock-status-badge.tsx
│   │   ├── product-stock-adjustment-dialog.tsx
│   │   ├── product-low-stock-alert.tsx
│   │   ├── product-warehouse-stock-list.tsx
│   │   └── index.ts
│   ├── hooks/
│   │   ├── use-get-product-inventory.ts
│   │   ├── use-update-product-inventory-mutation.ts
│   │   ├── use-adjust-product-stock-mutation.ts
│   │   └── index.ts
│   ├── services/
│   │   ├── product-inventory-service.ts
│   │   └── index.ts
│   ├── schemas/
│   │   ├── product-inventory-schema.ts
│   │   ├── product-stock-adjustment-schema.ts
│   │   └── index.ts
│   ├── types/
│   │   ├── product-inventory.ts
│   │   ├── product-stock-adjustment.ts
│   │   └── index.ts
│   └── index.ts
│
├── product-attribute/
│   ├── components/
│   │   ├── product-attributes-table.tsx
│   │   ├── product-attributes-form.tsx
│   │   ├── product-attribute-row.tsx
│   │   ├── product-attribute-value-input.tsx
│   │   ├── product-attribute-group-select.tsx
│   │   └── index.ts
│   ├── hooks/
│   │   ├── use-get-product-attributes.ts
│   │   ├── use-update-product-attributes-mutation.ts
│   │   └── index.ts
│   ├── services/
│   │   ├── product-attribute-service.ts
│   │   └── index.ts
│   ├── schemas/
│   │   ├── product-attribute-schema.ts
│   │   └── index.ts
│   ├── types/
│   │   ├── product-attribute.ts
│   │   └── index.ts
│   └── index.ts
│
├── product-category/
│   ├── components/
│   │   ├── product-category-select.tsx
│   │   ├── product-category-tree.tsx
│   │   ├── product-category-breadcrumb.tsx
│   │   ├── product-category-assignment-list.tsx
│   │   └── index.ts
│   ├── hooks/
│   │   ├── use-get-product-categories.ts
│   │   ├── use-update-product-categories-mutation.ts
│   │   └── index.ts
│   ├── services/
│   │   ├── product-category-service.ts
│   │   └── index.ts
│   ├── schemas/
│   │   ├── product-category-schema.ts
│   │   └── index.ts
│   ├── types/
│   │   ├── product-category.ts
│   │   └── index.ts
│   └── index.ts
│
├── product-seo/
│   ├── components/
│   │   ├── product-seo-card.tsx
│   │   ├── product-seo-form.tsx
│   │   ├── product-slug-input.tsx
│   │   ├── product-meta-title-input.tsx
│   │   ├── product-meta-description-input.tsx
│   │   ├── product-search-preview.tsx
│   │   └── index.ts
│   ├── hooks/
│   │   ├── use-get-product-seo.ts
│   │   ├── use-update-product-seo-mutation.ts
│   │   └── index.ts
│   ├── services/
│   │   ├── product-seo-service.ts
│   │   └── index.ts
│   ├── schemas/
│   │   ├── product-seo-schema.ts
│   │   ├── product-seo-form-schema.ts
│   │   └── index.ts
│   ├── types/
│   │   ├── product-seo.ts
│   │   └── index.ts
│   └── index.ts
│
├── product-shipping/
│   ├── components/
│   │   ├── product-shipping-card.tsx
│   │   ├── product-shipping-form.tsx
│   │   ├── product-weight-input.tsx
│   │   ├── product-dimensions-input.tsx
│   │   ├── product-shipping-class-select.tsx
│   │   └── index.ts
│   ├── hooks/
│   │   ├── use-get-product-shipping.ts
│   │   ├── use-update-product-shipping-mutation.ts
│   │   └── index.ts
│   ├── services/
│   │   ├── product-shipping-service.ts
│   │   └── index.ts
│   ├── schemas/
│   │   ├── product-shipping-schema.ts
│   │   ├── product-dimensions-schema.ts
│   │   └── index.ts
│   ├── types/
│   │   ├── product-shipping.ts
│   │   ├── product-dimensions.ts
│   │   └── index.ts
│   └── index.ts
│
└── product-relation/
    ├── components/
    │   ├── related-products-list.tsx
    │   ├── related-products-selector.tsx
    │   ├── upsell-products-selector.tsx
    │   ├── cross-sell-products-selector.tsx
    │   └── index.ts
    ├── hooks/
    │   ├── use-get-related-products.ts
    │   ├── use-update-related-products-mutation.ts
    │   ├── use-upsell-products.ts
    │   ├── use-cross-sell-products.ts
    │   └── index.ts
    ├── services/
    │   ├── product-relation-service.ts
    │   └── index.ts
    ├── schemas/
    │   ├── product-relation-schema.ts
    │   └── index.ts
    ├── types/
    │   ├── product-relation.ts
    │   └── index.ts
    └── index.ts

About

A modern, scalable ecommerce monorepo template based on Next.js and Feature-Based Architecture.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages