Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .claudeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Environment variables
.env
.env.local
.env.*.local
.env.development.local
.env.test.local
.env.production.local
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,12 @@ node_modules
.nextra-cache
.nextra-dist
.nextra-static
_pagefind/
_pagefind/

# Environment variables
.env
.env.local
.env.*.local
.env.development.local
.env.test.local
.env.production.local
35 changes: 35 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Agent Guidelines

## Language

- **Code, comments, and file names** — English
- **Documentation content** (`content/`) — English
- **PR titles and descriptions** — English
- **Commit messages** — English

## Content

When writing or editing content for this project, follow these rules:

- Do not use icons in text content.
- Do not use the em dash (-). Use a regular hyphen (-) instead.

## Project

This is a [Nextra](https://nextra.site/)-based documentation site for [Codee](https://codee.sh) — a platform built on top of [MedusaJS](https://medusajs.com).

## Repository structure

- `content/` — MDX documentation pages
- `app/` — Next.js app directory (layouts, pages, components)
- `app/components/` — Shared React components
- `app/wizard/` — Interactive resource configuration wizard
- `data/` — Static JSON data (resources, solutions)
- `scripts/` — Utility scripts (e.g. fetching resources)

## Conventions

- Components: TypeScript (`.tsx`), functional, no class components
- Styling: Tailwind CSS
- Data fetching for static content: scripts in `scripts/`, output to `data/`
- Do not commit `.env*` files
2 changes: 1 addition & 1 deletion app/[[...mdxPath]]/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ export default async function Page(props) {
<MDXContent {...props} params={params} />
</Wrapper>
)
}
}
64 changes: 64 additions & 0 deletions app/components/wizard/StepUseCases.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client";

import type { UseCase, WizardState } from "../../types/wizard";

const USE_CASES: { id: UseCase; label: string; description: string }[] = [
{
id: "b2c",
label: "B2C",
description: "Store for individual customers",
},
{
id: "b2b",
label: "B2B",
description: "Platform for business customers",
},
];

interface Props {
state: WizardState;
onChange: (state: Partial<WizardState>) => void;
}

export function StepUseCases({ state, onChange }: Props) {
function toggle(id: UseCase) {
const current = state.useCases;
const next = current.includes(id)
? current.filter((u) => u !== id)
: [...current, id];
onChange({ useCases: next });
}

return (
<div className="flex flex-col items-center gap-8">
<div className="text-center">
<h2 className="text-2xl font-bold">What kind of store do you want to build?</h2>
<p className="mt-2 text-gray-500 dark:text-gray-400">
You can choose both
</p>
</div>

<div className="flex gap-6">
{USE_CASES.map(({ id, label, description }) => {
const selected = state.useCases.includes(id);
return (
<button
key={id}
onClick={() => toggle(id)}
className={`w-48 rounded-xl border-2 p-8 text-center transition-all cursor-pointer
${selected
? "border-blue-500 bg-blue-50 dark:bg-blue-950"
: "border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600"
}`}
>
<div className="text-3xl font-bold">{label}</div>
<div className="mt-2 text-sm text-gray-500 dark:text-gray-400">
{description}
</div>
</button>
);
})}
</div>
</div>
);
}
105 changes: 105 additions & 0 deletions app/components/wizard/WizardShell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"use client";

import { useState } from "react";
import { WIZARD_STEPS, type WizardState } from "../../types/wizard";
import { StepUseCases } from "./StepUseCases";

const INITIAL_STATE: WizardState = {
useCases: [],
selectedCategoryIds: [],
};

function StepIndicator({ currentIndex }: { currentIndex: number }) {
return (
<div className="flex items-center gap-2">
{WIZARD_STEPS.map((step, i) => (
<div key={step.id} className="flex items-center gap-2">
<div
className={`flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold transition-colors
${i < currentIndex
? "bg-blue-500 text-white"
: i === currentIndex
? "border-2 border-blue-500 text-blue-500"
: "border-2 border-gray-200 text-gray-400 dark:border-gray-700"
}`}
>
{i < currentIndex ? "✓" : i + 1}
</div>
<span
className={`text-sm ${i === currentIndex ? "font-semibold" : "text-gray-400 dark:text-gray-500"}`}
>
{step.label}
</span>
{i < WIZARD_STEPS.length - 1 && (
<div className={`h-px w-8 ${i < currentIndex ? "bg-blue-500" : "bg-gray-200 dark:bg-gray-700"}`} />
)}
</div>
))}
</div>
);
}

function canProceed(step: number, state: WizardState): boolean {
if (step === 0) return state.useCases.length > 0;
if (step === 1) return state.selectedCategoryIds.length > 0;
return true;
}

export function WizardShell() {
const [step, setStep] = useState(0);
const [state, setState] = useState<WizardState>(INITIAL_STATE);

function update(partial: Partial<WizardState>) {
setState((prev) => ({ ...prev, ...partial }));
}

function renderStep() {
switch (step) {
case 0:
return <StepUseCases state={state} onChange={update} />;
default:
return (
<div className="text-center text-gray-400">
Step {step + 1} - coming soon
</div>
);
}
}

return (
<div className="flex min-h-[calc(100vh-var(--nextra-navbar-height)-var(--nextra-footer-height,80px))] flex-col">
{/* Step indicator */}
<div className="flex justify-center border-b border-gray-200 py-4 dark:border-gray-800">
<StepIndicator currentIndex={step} />
</div>

{/* Content */}
<div className="flex flex-1 items-center justify-center px-6 py-16">
{renderStep()}
</div>

{/* Fixed bottom bar */}
<div className="sticky bottom-0 border-t border-gray-200 bg-white px-8 py-4 dark:border-gray-800 dark:bg-gray-950">
<div className="mx-auto flex max-w-4xl items-center justify-between">
<button
onClick={() => setStep((s) => s - 1)}
disabled={step === 0}
className="rounded-lg border border-gray-200 px-6 py-2 text-sm font-medium transition-colors hover:bg-gray-50 disabled:opacity-30 dark:border-gray-700 dark:hover:bg-gray-900"
>
← Back
</button>
<span className="text-sm text-gray-400">
{step + 1} / {WIZARD_STEPS.length}
</span>
<button
onClick={() => setStep((s) => s + 1)}
disabled={!canProceed(step, state) || step === WIZARD_STEPS.length - 1}
className="rounded-lg bg-blue-500 px-6 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-600 disabled:opacity-30"
>
Next →
</button>
</div>
</div>
</div>
);
}
58 changes: 13 additions & 45 deletions app/layout.jsx
Original file line number Diff line number Diff line change
@@ -1,74 +1,42 @@
import { Footer, Layout, Navbar } from 'nextra-theme-docs'
import { Banner, Head } from 'nextra/components'
import { Head } from 'nextra/components'
import { getPageMap } from 'nextra/page-map'
import 'nextra-theme-docs/style.css'
import './globals.css'

export const metadata = {
title: 'Medusa Hub',
description: 'Community plugins, starters, and guides extending Medusa.js.',
}

// const banner = <Banner storageKey="some-key">Nextra 4.0 is released 🎉</Banner>

const navbar = (
<Navbar
logo={
<b>Medusa Hub</b>
}
// ... Your additional navbar options
/>
<Navbar logo={<b>Medusa Hub</b>} />
)
const currentYear = typeof window === 'undefined' ? new Date().getFullYear() : new Date().getFullYear()

const currentYear = new Date().getFullYear()
const footer = (
<Footer>
<p>
© {currentYear}{' '}
<a
href="https://codee.dev"
target="_blank"
rel="noopener noreferrer"
style={{ color: 'inherit', textDecoration: 'none' }}
>
<a href="https://codee.dev" target="_blank" rel="noopener noreferrer" style={{ color: 'inherit', textDecoration: 'none' }}>
Codee
</a>
{' · '}
<a
href="https://github.com/codee-sh/medusa-hub"
target="_blank"
rel="noopener noreferrer"
style={{ color: 'inherit', textDecoration: 'none' }}
>
<a href="https://github.com/codee-sh/medusa-hub" target="_blank" rel="noopener noreferrer" style={{ color: 'inherit', textDecoration: 'none' }}>
Contribute on GitHub
</a>
</p>
</Footer>
)

export default async function RootLayout({ children }) {
const pageMap = await getPageMap()

return (
<html
// Not required, but good for SEO
lang="en"
// Required to be set
dir="ltr"
// Suggested by `next-themes` package https://github.com/pacocoursey/next-themes#with-app
suppressHydrationWarning
>
<Head
// ... Your additional head options
>
{/* Your additional tags should be passed as `children` of `<Head>` element */}
</Head>
<html lang="en" dir="ltr" suppressHydrationWarning>
<Head />
<body>
<Layout
// banner={banner}
navbar={navbar}
pageMap={pageMap || []}
footer={footer}
// ... Your additional layout options
>
<Layout navbar={navbar} pageMap={pageMap || []} footer={footer}>
{children}
</Layout>
</body>
Expand Down
28 changes: 28 additions & 0 deletions app/types/wizard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export type UseCase = "b2c" | "b2b";

export type SolutionStatus = "ready" | "configurable" | "plugin" | "buildable" | "missing";

export interface Solution {
id: string;
name: string;
useCases: UseCase[];
status?: SolutionStatus;
description?: string;
resourceIds?: string[];
notes?: string;
children?: Solution[];
}

export interface WizardState {
useCases: UseCase[];
selectedCategoryIds: string[];
}

export const WIZARD_STEPS = [
{ id: "use-cases", label: "Use Case" },
{ id: "categories", label: "Categories" },
{ id: "features", label: "Features" },
{ id: "summary", label: "Summary" },
] as const;

export type WizardStepId = (typeof WIZARD_STEPS)[number]["id"];
6 changes: 6 additions & 0 deletions app/wizard/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { ReactNode } from "react";
import "./wizard.css";

export default function WizardLayout({ children }: { children: ReactNode }) {
return <div className="wizard-layout">{children}</div>;
}
9 changes: 9 additions & 0 deletions app/wizard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { WizardShell } from "../components/wizard/WizardShell";

export const metadata = {
title: "Configurator — Medusa Hub",
};

export default function WizardPage() {
return <WizardShell />;
}
11 changes: 11 additions & 0 deletions app/wizard/wizard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* Hide Nextra sidebar and TOC on wizard pages */
.wizard-layout ~ aside,
.nextra-sidebar-container,
.nextra-toc {
display: none !important;
}

/* Make content full width */
.wizard-layout {
width: 100%;
}
6 changes: 6 additions & 0 deletions content/medusajs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ asIndexPage: true

This section contains modules, plugins, and guides for extending Medusa.js.

## Resources

Browse built-in Medusa commerce modules and official third-party integrations.

- [Browse Resources](/medusajs/resources)

## Plugins

Plugins extend Medusa.js functionality with additional features and integrations.
Expand Down
Loading