@promakeai/cli 0.3.9 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/index.js +3 -3
  2. package/dist/registry/auth-core.json +1 -1
  3. package/dist/registry/blog-core.json +16 -16
  4. package/dist/registry/blog-list-page.json +2 -2
  5. package/dist/registry/blog-section.json +1 -1
  6. package/dist/registry/cards-carousel-section.json +2 -2
  7. package/dist/registry/cart-drawer.json +1 -1
  8. package/dist/registry/category-section.json +1 -1
  9. package/dist/registry/checkout-page.json +1 -1
  10. package/dist/registry/docs/blog-core.md +6 -6
  11. package/dist/registry/docs/blog-list-page.md +1 -1
  12. package/dist/registry/docs/ecommerce-core.md +6 -6
  13. package/dist/registry/docs/featured-products.md +1 -1
  14. package/dist/registry/docs/post-detail-page.md +2 -2
  15. package/dist/registry/docs/product-detail-page.md +2 -2
  16. package/dist/registry/docs/products-page.md +1 -1
  17. package/dist/registry/docs/reset-password-page.md +15 -9
  18. package/dist/registry/ecommerce-core.json +15 -15
  19. package/dist/registry/favorites-blog-block.json +1 -1
  20. package/dist/registry/favorites-blog-page.json +1 -1
  21. package/dist/registry/favorites-ecommerce-block.json +1 -1
  22. package/dist/registry/favorites-ecommerce-page.json +1 -1
  23. package/dist/registry/featured-products.json +2 -2
  24. package/dist/registry/header-ecommerce.json +1 -1
  25. package/dist/registry/my-orders-page.json +1 -1
  26. package/dist/registry/order-confirmation-page.json +1 -1
  27. package/dist/registry/post-card.json +1 -1
  28. package/dist/registry/post-detail-page.json +3 -3
  29. package/dist/registry/product-detail-page.json +3 -3
  30. package/dist/registry/products-page.json +2 -2
  31. package/dist/registry/related-posts-block.json +1 -1
  32. package/dist/registry/related-products-block.json +1 -1
  33. package/dist/registry/reset-password-page.json +19 -10
  34. package/package.json +1 -1
  35. package/template/bun.lock +2 -2
  36. package/template/package.json +2 -2
@@ -12,7 +12,7 @@
12
12
  "path": "/products",
13
13
  "componentName": "ProductsPage"
14
14
  },
15
- "usage": "import ProductsPage from '@/modules/products-page';\n\n<Route path=\"/products\" element={<ProductsPage />} />\n\n• Installed at: src/modules/products-page/\n• Add link: <Link to=\"/products\">Browse Products</Link>\n• Supports filters, sorting, grid/list view, pagination\n• Uses useProducts hook for data fetching",
15
+ "usage": "import ProductsPage from '@/modules/products-page';\n\n<Route path=\"/products\" element={<ProductsPage />} />\n\n• Installed at: src/modules/products-page/\n• Add link: <Link to=\"/products\">Browse Products</Link>\n• Supports filters, sorting, grid/list view, pagination\n• Uses useDbProducts hook for data fetching",
16
16
  "files": [
17
17
  {
18
18
  "path": "products-page/index.ts",
@@ -24,7 +24,7 @@
24
24
  "path": "products-page/products-page.tsx",
25
25
  "type": "registry:page",
26
26
  "target": "$modules$/products-page/products-page.tsx",
27
- "content": "import { useState, useRef, useCallback, useMemo } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Filter, Grid, List } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useProducts, useCategories } from \"@/modules/ecommerce-core\";\nimport type { Product, Category } from \"@/modules/ecommerce-core/types\";\n\ninterface FilterSidebarProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n t: any;\n categories: Category[];\n selectedCategories: string[];\n handleCategoryChange: (category: string, checked: boolean) => void;\n selectedFeatures: string[];\n handleFeatureChange: (feature: string, checked: boolean) => void;\n minPriceRef: React.RefObject<HTMLInputElement | null>;\n maxPriceRef: React.RefObject<HTMLInputElement | null>;\n searchParams: URLSearchParams;\n handlePriceFilter: () => void;\n}\n\nfunction FilterSidebar({\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n}: FilterSidebarProps) {\n return (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"categories\", \"Categories\")}\n </h3>\n <div className=\"space-y-3\">\n {categories.map((category) => (\n <div\n key={category.id}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={`category-${category.id}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={`category-${category.id}`}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"priceRange\", \"Price Range\")}\n </h3>\n <div className=\"space-y-3 p-3 bg-muted/30 rounded-lg\">\n <div className=\"grid grid-cols-2 gap-3\">\n <input\n ref={minPriceRef}\n type=\"number\"\n placeholder={t(\"minPrice\", \"Min\")}\n defaultValue={searchParams.get(\"minPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n <input\n ref={maxPriceRef}\n type=\"number\"\n placeholder={t(\"maxPrice\", \"Max\")}\n defaultValue={searchParams.get(\"maxPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n </div>\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"features\", \"Features\")}\n </h3>\n <div className=\"space-y-3\">\n {[\n { key: \"on_sale\", label: t(\"onSale\", \"On Sale\") },\n { key: \"is_new\", label: t(\"newArrivals\", \"New Arrivals\") },\n { key: \"featured\", label: t(\"featuredLabel\", \"Featured\") },\n { key: \"in_stock\", label: t(\"inStock\", \"In Stock\") },\n ].map((feature) => (\n <div\n key={feature.key}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={feature.key}\n checked={selectedFeatures.includes(feature.key)}\n onCheckedChange={(checked) =>\n handleFeatureChange(feature.key, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={feature.key}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {feature.label}\n </label>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nexport function ProductsPage() {\n const { t } = useTranslation(\"products-page\");\n usePageTitle({ title: t(\"pageTitle\", \"Products\") });\n const { products, loading: productsLoading } = useProducts();\n const { categories, loading: categoriesLoading } = useCategories();\n const loading = productsLoading || categoriesLoading;\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n const [sortBy, setSortBy] = useState(\"featured\");\n const [selectedCategories, setSelectedCategories] = useState<string[]>(() => {\n const categorySlug = searchParams.get(\"category\");\n return categorySlug ? [categorySlug] : [];\n });\n const [selectedFeatures, setSelectedFeatures] = useState<string[]>([]);\n const searchQuery = searchParams.get(\"search\") || \"\";\n const minPriceRef = useRef<HTMLInputElement>(null);\n const maxPriceRef = useRef<HTMLInputElement>(null);\n\n const filteredProducts = useMemo(() => {\n const minPrice = parseFloat(searchParams.get(\"minPrice\") || \"0\") || 0;\n const maxPrice =\n parseFloat(searchParams.get(\"maxPrice\") || \"999999\") || 999999;\n\n let filtered = products.filter((product) => {\n const currentPrice =\n product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n return currentPrice >= minPrice && currentPrice <= maxPrice;\n });\n\n if (selectedCategories.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedCategories.some((selectedCategory) => {\n if (product.category === selectedCategory) return true;\n return product.categories?.some(\n (cat) => cat.slug === selectedCategory\n );\n });\n });\n }\n\n if (selectedFeatures.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedFeatures.every((feature) => {\n switch (feature) {\n case \"on_sale\":\n return product.on_sale;\n case \"is_new\":\n return product.is_new;\n case \"featured\":\n return product.featured;\n case \"in_stock\":\n return product.stock > 0;\n default:\n return true;\n }\n });\n });\n }\n\n // Apply sorting\n return [...filtered].sort((a, b) => {\n switch (sortBy) {\n case \"price-low\":\n return (\n (a.on_sale ? a.sale_price || a.price : a.price) -\n (b.on_sale ? b.sale_price || b.price : b.price)\n );\n case \"price-high\":\n return (\n (b.on_sale ? b.sale_price || b.price : b.price) -\n (a.on_sale ? a.sale_price || a.price : a.price)\n );\n case \"newest\":\n return (\n new Date(b.created_at || 0).getTime() -\n new Date(a.created_at || 0).getTime()\n );\n case \"featured\":\n default:\n return (b.featured ? 1 : 0) - (a.featured ? 1 : 0);\n }\n });\n }, [products, searchParams, selectedFeatures, selectedCategories, sortBy]);\n\n const handlePriceFilter = useCallback(() => {\n const minPrice = minPriceRef.current?.value || \"\";\n const maxPrice = maxPriceRef.current?.value || \"\";\n const params = new URLSearchParams(searchParams);\n if (minPrice) params.set(\"minPrice\", minPrice);\n else params.delete(\"minPrice\");\n if (maxPrice) params.set(\"maxPrice\", maxPrice);\n else params.delete(\"maxPrice\");\n setSearchParams(params);\n }, [searchParams, setSearchParams]);\n\n const handleCategoryChange = useCallback(\n (category: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories((prev) => [...prev, category]);\n } else {\n setSelectedCategories((prev) => prev.filter((c) => c !== category));\n }\n },\n []\n );\n\n const handleFeatureChange = useCallback(\n (feature: string, checked: boolean) => {\n if (checked) {\n setSelectedFeatures((prev) => [...prev, feature]);\n } else {\n setSelectedFeatures((prev) => prev.filter((f) => f !== feature));\n }\n },\n []\n );\n\n const sortOptions = [\n { value: \"featured\", label: t(\"featured\", \"Featured\") },\n { value: \"price-low\", label: t(\"sortPriceLow\", \"Price: Low to High\") },\n { value: \"price-high\", label: t(\"sortPriceHigh\", \"Price: High to Low\") },\n { value: \"newest\", label: t(\"sortNewest\", \"Newest\") },\n ];\n\n const filterSidebarProps: FilterSidebarProps = {\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n };\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"mb-8\">\n <div className=\"flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6\">\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl lg:text-3xl font-bold\">\n {searchQuery\n ? t(\"searchResultsFor\", `Search Results for \"${searchQuery}\"`)\n : t(\"allProducts\", \"All Products\")}\n </h1>\n <p className=\"text-sm lg:text-base text-muted-foreground\">\n {t(\"showing\", \"Showing\")} {filteredProducts.length}{\" \"}\n {t(\"of\", \"of\")} {products.length} {t(\"products\", \"products\")}\n </p>\n </div>\n {searchQuery && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setSearchParams({})}\n className=\"w-fit\"\n >\n {t(\"clearSearch\", \"Clear Search\")}\n </Button>\n )}\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-stretch sm:items-center justify-between\">\n <Sheet>\n <SheetTrigger asChild>\n <Button\n variant=\"outline\"\n className=\"lg:hidden w-full sm:w-auto\"\n >\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\", \"Filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent side=\"left\" className=\"w-[300px]\">\n <SheetHeader>\n <SheetTitle>{t(\"filters\", \"Filters\")}</SheetTitle>\n <SheetDescription>\n {t(\"refineSearch\", \"Refine your product search\")}\n </SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </SheetContent>\n </Sheet>\n\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-3\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-full sm:w-[160px]\">\n <SelectValue placeholder={t(\"sortBy\", \"Sort by\")} />\n </SelectTrigger>\n <SelectContent>\n {sortOptions.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <div className=\"flex border rounded-lg p-1 w-full sm:w-auto\">\n <Button\n variant={viewMode === \"grid\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"grid\")}\n className=\"flex-1 sm:flex-none\"\n >\n <Grid className=\"h-4 w-4\" />\n </Button>\n <Button\n variant={viewMode === \"list\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"list\")}\n className=\"flex-1 sm:flex-none\"\n >\n <List className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </div>\n </FadeIn>\n\n <div className=\"flex gap-8\">\n <aside className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-24\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </aside>\n\n <div className=\"flex-1\">\n {loading ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {[...Array(6)].map((_, i) => (\n <div\n key={i}\n className=\"animate-pulse bg-card rounded-lg shadow-md overflow-hidden\"\n >\n <div className=\"aspect-square bg-muted mb-4\"></div>\n <div className=\"p-4\">\n <div className=\"h-4 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-3 bg-muted rounded w-1/2 mb-3\"></div>\n <div className=\"h-4 bg-muted rounded w-1/3\"></div>\n </div>\n </div>\n ))}\n </div>\n ) : viewMode === \"grid\" ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {filteredProducts.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"grid\"\n />\n ))}\n </div>\n ) : (\n <div className=\"space-y-6\">\n {filteredProducts.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"list\"\n />\n ))}\n </div>\n )}\n\n {!loading && filteredProducts.length === 0 && (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground\">\n {t(\n \"noProductsFound\",\n \"No products found matching your criteria.\"\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default ProductsPage;\n"
27
+ "content": "import { useState, useRef, useCallback, useMemo } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Filter, Grid, List } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useDbProducts, useDbCategories } from \"@/modules/ecommerce-core\";\nimport type { Product, Category } from \"@/modules/ecommerce-core/types\";\n\ninterface FilterSidebarProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n t: any;\n categories: Category[];\n selectedCategories: string[];\n handleCategoryChange: (category: string, checked: boolean) => void;\n selectedFeatures: string[];\n handleFeatureChange: (feature: string, checked: boolean) => void;\n minPriceRef: React.RefObject<HTMLInputElement | null>;\n maxPriceRef: React.RefObject<HTMLInputElement | null>;\n searchParams: URLSearchParams;\n handlePriceFilter: () => void;\n}\n\nfunction FilterSidebar({\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n}: FilterSidebarProps) {\n return (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"categories\", \"Categories\")}\n </h3>\n <div className=\"space-y-3\">\n {categories.map((category) => (\n <div\n key={category.id}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n data-db-table=\"product_categories\"\n data-db-id={category.id}\n >\n <Checkbox\n id={`category-${category.id}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={`category-${category.id}`}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"priceRange\", \"Price Range\")}\n </h3>\n <div className=\"space-y-3 p-3 bg-muted/30 rounded-lg\">\n <div className=\"grid grid-cols-2 gap-3\">\n <input\n ref={minPriceRef}\n type=\"number\"\n placeholder={t(\"minPrice\", \"Min\")}\n defaultValue={searchParams.get(\"minPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n <input\n ref={maxPriceRef}\n type=\"number\"\n placeholder={t(\"maxPrice\", \"Max\")}\n defaultValue={searchParams.get(\"maxPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n </div>\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"features\", \"Features\")}\n </h3>\n <div className=\"space-y-3\">\n {[\n { key: \"on_sale\", label: t(\"onSale\", \"On Sale\") },\n { key: \"is_new\", label: t(\"newArrivals\", \"New Arrivals\") },\n { key: \"featured\", label: t(\"featuredLabel\", \"Featured\") },\n { key: \"in_stock\", label: t(\"inStock\", \"In Stock\") },\n ].map((feature) => (\n <div\n key={feature.key}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={feature.key}\n checked={selectedFeatures.includes(feature.key)}\n onCheckedChange={(checked) =>\n handleFeatureChange(feature.key, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={feature.key}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {feature.label}\n </label>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nexport function ProductsPage() {\n const { t } = useTranslation(\"products-page\");\n usePageTitle({ title: t(\"pageTitle\", \"Products\") });\n const { products, loading: productsLoading } = useDbProducts();\n const { categories, loading: categoriesLoading } = useDbCategories();\n const loading = productsLoading || categoriesLoading;\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n const [sortBy, setSortBy] = useState(\"featured\");\n const [selectedCategories, setSelectedCategories] = useState<string[]>(() => {\n const categorySlug = searchParams.get(\"category\");\n return categorySlug ? [categorySlug] : [];\n });\n const [selectedFeatures, setSelectedFeatures] = useState<string[]>([]);\n const searchQuery = searchParams.get(\"search\") || \"\";\n const minPriceRef = useRef<HTMLInputElement>(null);\n const maxPriceRef = useRef<HTMLInputElement>(null);\n\n const filteredProducts = useMemo(() => {\n const minPrice = parseFloat(searchParams.get(\"minPrice\") || \"0\") || 0;\n const maxPrice =\n parseFloat(searchParams.get(\"maxPrice\") || \"999999\") || 999999;\n\n let filtered = products.filter((product) => {\n const currentPrice =\n product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n return currentPrice >= minPrice && currentPrice <= maxPrice;\n });\n\n if (selectedCategories.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedCategories.some((selectedCategory) => {\n if (product.category === selectedCategory) return true;\n return product.categories?.some(\n (cat) => cat.slug === selectedCategory\n );\n });\n });\n }\n\n if (selectedFeatures.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedFeatures.every((feature) => {\n switch (feature) {\n case \"on_sale\":\n return product.on_sale;\n case \"is_new\":\n return product.is_new;\n case \"featured\":\n return product.featured;\n case \"in_stock\":\n return product.stock > 0;\n default:\n return true;\n }\n });\n });\n }\n\n // Apply sorting\n return [...filtered].sort((a, b) => {\n switch (sortBy) {\n case \"price-low\":\n return (\n (a.on_sale ? a.sale_price || a.price : a.price) -\n (b.on_sale ? b.sale_price || b.price : b.price)\n );\n case \"price-high\":\n return (\n (b.on_sale ? b.sale_price || b.price : b.price) -\n (a.on_sale ? a.sale_price || a.price : a.price)\n );\n case \"newest\":\n return (\n new Date(b.created_at || 0).getTime() -\n new Date(a.created_at || 0).getTime()\n );\n case \"featured\":\n default:\n return (b.featured ? 1 : 0) - (a.featured ? 1 : 0);\n }\n });\n }, [products, searchParams, selectedFeatures, selectedCategories, sortBy]);\n\n const handlePriceFilter = useCallback(() => {\n const minPrice = minPriceRef.current?.value || \"\";\n const maxPrice = maxPriceRef.current?.value || \"\";\n const params = new URLSearchParams(searchParams);\n if (minPrice) params.set(\"minPrice\", minPrice);\n else params.delete(\"minPrice\");\n if (maxPrice) params.set(\"maxPrice\", maxPrice);\n else params.delete(\"maxPrice\");\n setSearchParams(params);\n }, [searchParams, setSearchParams]);\n\n const handleCategoryChange = useCallback(\n (category: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories((prev) => [...prev, category]);\n } else {\n setSelectedCategories((prev) => prev.filter((c) => c !== category));\n }\n },\n []\n );\n\n const handleFeatureChange = useCallback(\n (feature: string, checked: boolean) => {\n if (checked) {\n setSelectedFeatures((prev) => [...prev, feature]);\n } else {\n setSelectedFeatures((prev) => prev.filter((f) => f !== feature));\n }\n },\n []\n );\n\n const sortOptions = [\n { value: \"featured\", label: t(\"featured\", \"Featured\") },\n { value: \"price-low\", label: t(\"sortPriceLow\", \"Price: Low to High\") },\n { value: \"price-high\", label: t(\"sortPriceHigh\", \"Price: High to Low\") },\n { value: \"newest\", label: t(\"sortNewest\", \"Newest\") },\n ];\n\n const filterSidebarProps: FilterSidebarProps = {\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n };\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"mb-8\">\n <div className=\"flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6\">\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl lg:text-3xl font-bold\">\n {searchQuery\n ? t(\"searchResultsFor\", `Search Results for \"${searchQuery}\"`)\n : t(\"allProducts\", \"All Products\")}\n </h1>\n <p className=\"text-sm lg:text-base text-muted-foreground\">\n {t(\"showing\", \"Showing\")} {filteredProducts.length}{\" \"}\n {t(\"of\", \"of\")} {products.length} {t(\"products\", \"products\")}\n </p>\n </div>\n {searchQuery && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setSearchParams({})}\n className=\"w-fit\"\n >\n {t(\"clearSearch\", \"Clear Search\")}\n </Button>\n )}\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-stretch sm:items-center justify-between\">\n <Sheet>\n <SheetTrigger asChild>\n <Button\n variant=\"outline\"\n className=\"lg:hidden w-full sm:w-auto\"\n >\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\", \"Filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent side=\"left\" className=\"w-[300px]\">\n <SheetHeader>\n <SheetTitle>{t(\"filters\", \"Filters\")}</SheetTitle>\n <SheetDescription>\n {t(\"refineSearch\", \"Refine your product search\")}\n </SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </SheetContent>\n </Sheet>\n\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-3\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-full sm:w-[160px]\">\n <SelectValue placeholder={t(\"sortBy\", \"Sort by\")} />\n </SelectTrigger>\n <SelectContent>\n {sortOptions.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <div className=\"flex border rounded-lg p-1 w-full sm:w-auto\">\n <Button\n variant={viewMode === \"grid\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"grid\")}\n className=\"flex-1 sm:flex-none\"\n >\n <Grid className=\"h-4 w-4\" />\n </Button>\n <Button\n variant={viewMode === \"list\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"list\")}\n className=\"flex-1 sm:flex-none\"\n >\n <List className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </div>\n </FadeIn>\n\n <div className=\"flex gap-8\">\n <aside className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-24\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </aside>\n\n <div className=\"flex-1\">\n {loading ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {[...Array(6)].map((_, i) => (\n <div\n key={i}\n className=\"animate-pulse bg-card rounded-lg shadow-md overflow-hidden\"\n >\n <div className=\"aspect-square bg-muted mb-4\"></div>\n <div className=\"p-4\">\n <div className=\"h-4 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-3 bg-muted rounded w-1/2 mb-3\"></div>\n <div className=\"h-4 bg-muted rounded w-1/3\"></div>\n </div>\n </div>\n ))}\n </div>\n ) : viewMode === \"grid\" ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {filteredProducts.map((product) => (\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\n <ProductCard\n product={product}\n variant=\"grid\"\n />\n </div>\n ))}\n </div>\n ) : (\n <div className=\"space-y-6\">\n {filteredProducts.map((product) => (\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\n <ProductCard\n product={product}\n variant=\"list\"\n />\n </div>\n ))}\n </div>\n )}\n\n {!loading && filteredProducts.length === 0 && (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground\">\n {t(\n \"noProductsFound\",\n \"No products found matching your criteria.\"\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default ProductsPage;\n"
28
28
  },
29
29
  {
30
30
  "path": "products-page/lang/en.json",
@@ -19,7 +19,7 @@
19
19
  "path": "related-posts-block/related-posts-block.tsx",
20
20
  "type": "registry:block",
21
21
  "target": "$modules$/related-posts-block/related-posts-block.tsx",
22
- "content": "import { PostCard } from \"@/modules/post-card/post-card\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Post } from \"@/modules/blog-core/types\";\n\ninterface RelatedPostsBlockProps {\n posts: Post[];\n title?: string;\n}\n\nexport function RelatedPostsBlock({ posts, title }: RelatedPostsBlockProps) {\n const { t } = useTranslation(\"related-posts-block\");\n\n if (posts.length === 0) {\n return null;\n }\n\n return (\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-12\">\n <div className=\"max-w-6xl mx-auto\">\n <h2 className=\"text-3xl font-bold mb-8\">\n {title || t(\"title\", \"Related Posts\")}\n </h2>\n <div className=\"grid gap-6 md:grid-cols-2 lg:grid-cols-3\">\n {posts.map((post) => (\n <PostCard key={post.id} post={post} layout=\"grid\" />\n ))}\n </div>\n </div>\n </div>\n );\n}\n"
22
+ "content": "import { PostCard } from \"@/modules/post-card/post-card\";\nimport { useTranslation } from \"react-i18next\";\nimport type { Post } from \"@/modules/blog-core/types\";\n\ninterface RelatedPostsBlockProps {\n posts: Post[];\n title?: string;\n}\n\nexport function RelatedPostsBlock({ posts, title }: RelatedPostsBlockProps) {\n const { t } = useTranslation(\"related-posts-block\");\n\n if (posts.length === 0) {\n return null;\n }\n\n return (\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-12\">\n <div className=\"max-w-6xl mx-auto\">\n <h2 className=\"text-3xl font-bold mb-8\">\n {title || t(\"title\", \"Related Posts\")}\n </h2>\n <div className=\"grid gap-6 md:grid-cols-2 lg:grid-cols-3\">\n {posts.map((post) => (\n <div key={post.id} className=\"contents\" data-db-table=\"posts\" data-db-id={post.id}>\n <PostCard post={post} layout=\"grid\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n"
23
23
  },
24
24
  {
25
25
  "path": "related-posts-block/lang/en.json",
@@ -19,7 +19,7 @@
19
19
  "path": "related-products-block/related-products-block.tsx",
20
20
  "type": "registry:block",
21
21
  "target": "$modules$/related-products-block/related-products-block.tsx",
22
- "content": "import { Link } from \"react-router\";\nimport { Star } from \"lucide-react\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\nimport { formatPrice } from \"@/modules/ecommerce-core/format-price\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\n\ninterface RelatedProductsBlockProps {\n products: Product[];\n title?: string;\n}\n\nexport function RelatedProductsBlock({\n products,\n title,\n}: RelatedProductsBlockProps) {\n const { t } = useTranslation(\"related-products-block\");\n\n if (products.length === 0) {\n return null;\n }\n\n return (\n <div>\n <h2 className=\"text-2xl font-bold mb-6\">\n {title || t(\"title\", \"Related Products\")}\n </h2>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6\">\n {products.map((product) => (\n <Card\n key={product.id}\n className=\"group overflow-hidden border-0 p-0 shadow-sm hover:shadow-lg transition-all duration-300\"\n >\n <Link to={`/products/${product.slug}`}>\n <div className=\"relative aspect-square overflow-hidden cursor-pointer\">\n <img\n src={product.images[0] || \"/images/placeholder.png\"}\n alt={product.name}\n className=\"w-full h-full object-cover group-hover:scale-105 transition-transform duration-300\"\n />\n </div>\n </Link>\n <CardContent className=\"p-4\">\n <Link to={`/products/${product.slug}`}>\n <h3 className=\"font-semibold hover:text-primary transition-colors line-clamp-1\">\n {product.name}\n </h3>\n </Link>\n <div className=\"flex items-center justify-between mt-2\">\n <span className=\"font-semibold\">\n {formatPrice(product.price, constants.site.currency)}\n </span>\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 fill-current text-yellow-400\" />\n <span className=\"text-xs\">{product.rating}</span>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n </div>\n );\n}\n"
22
+ "content": "import { Link } from \"react-router\";\nimport { Star } from \"lucide-react\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\nimport constants from \"@/constants/constants.json\";\nimport { formatPrice } from \"@/modules/ecommerce-core/format-price\";\nimport type { Product } from \"@/modules/ecommerce-core/types\";\n\ninterface RelatedProductsBlockProps {\n products: Product[];\n title?: string;\n}\n\nexport function RelatedProductsBlock({\n products,\n title,\n}: RelatedProductsBlockProps) {\n const { t } = useTranslation(\"related-products-block\");\n\n if (products.length === 0) {\n return null;\n }\n\n return (\n <div>\n <h2 className=\"text-2xl font-bold mb-6\">\n {title || t(\"title\", \"Related Products\")}\n </h2>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6\">\n {products.map((product) => (\n <div key={product.id} className=\"contents\" data-db-table=\"products\" data-db-id={product.id}>\n <Card\n className=\"group overflow-hidden border-0 p-0 shadow-sm hover:shadow-lg transition-all duration-300\"\n >\n <Link to={`/products/${product.slug}`}>\n <div className=\"relative aspect-square overflow-hidden cursor-pointer\">\n <img\n src={product.images[0] || \"/images/placeholder.png\"}\n alt={product.name}\n className=\"w-full h-full object-cover group-hover:scale-105 transition-transform duration-300\"\n />\n </div>\n </Link>\n <CardContent className=\"p-4\">\n <Link to={`/products/${product.slug}`}>\n <h3 className=\"font-semibold hover:text-primary transition-colors line-clamp-1\">\n {product.name}\n </h3>\n </Link>\n <div className=\"flex items-center justify-between mt-2\">\n <span className=\"font-semibold\">\n {formatPrice(product.price, constants.site.currency)}\n </span>\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 fill-current text-yellow-400\" />\n <span className=\"text-xs\">{product.rating}</span>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n ))}\n </div>\n </div>\n );\n}\n"
23
23
  },
24
24
  {
25
25
  "path": "related-products-block/lang/en.json",
@@ -2,14 +2,12 @@
2
2
  "name": "reset-password-page",
3
3
  "type": "registry:page",
4
4
  "title": "Reset Password Page",
5
- "description": "Split-screen password reset page with form on the left and full-height image on the right. Features new password input with confirmation, validates reset code from URL. Clean minimal design with API integration and responsive layout.",
5
+ "description": "Centered card password reset page with Layout wrapper. Validates reset code and username from URL parameters (?code=&username=). Features new password input with confirmation and show/hide toggle. Uses useAuth hook from auth-core for password reset.",
6
6
  "registryDependencies": [
7
- "button",
8
- "input",
9
- "auth",
7
+ "auth-core",
10
8
  "api"
11
9
  ],
12
- "usage": "import ResetPasswordPage from '@/modules/reset-password-page';\n\n<ResetPasswordPage\n image=\"/images/reset-bg.jpg\"\n/>\n\n• Installed at: src/modules/reset-password-page/\n• Customize text: src/modules/reset-password-page/lang/*.json\n• Uses customerClient.auth.resetPassword() for API\n• Expects ?code= URL parameter from email link\n• Add to your router as a page component",
10
+ "usage": "import ResetPasswordPage from '@/modules/reset-password-page';\n\n<ResetPasswordPage />\n\n• Installed at: src/modules/reset-password-page/\n• Customize text: src/modules/reset-password-page/lang/*.json\n• Uses useAuth() hook from auth-core:\n const { resetPassword } = useAuth();\n await resetPassword(username, code, newPassword);\n• Expects ?code= and ?username= URL parameters from email link\n• Add to your router as a page component",
13
11
  "route": {
14
12
  "path": "/reset-password",
15
13
  "componentName": "ResetPasswordPage"
@@ -18,22 +16,33 @@
18
16
  {
19
17
  "path": "reset-password-page/index.ts",
20
18
  "type": "registry:index",
21
- "target": "$modules$/reset-password-page/index.ts"
19
+ "target": "$modules$/reset-password-page/index.ts",
20
+ "content": "export * from './reset-password-page';\r\nexport { default } from './reset-password-page';\r\n"
22
21
  },
23
22
  {
24
23
  "path": "reset-password-page/reset-password-page.tsx",
25
24
  "type": "registry:page",
26
- "target": "$modules$/reset-password-page/reset-password-page.tsx"
25
+ "target": "$modules$/reset-password-page/reset-password-page.tsx",
26
+ "content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useSearchParams } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { getErrorMessage } from \"@/modules/api\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport {\r\n Card,\r\n CardContent,\r\n CardHeader,\r\n CardTitle,\r\n CardDescription,\r\n} from \"@/components/ui/card\";\r\nimport { KeyRound, ArrowLeft, Eye, EyeOff, CheckCircle2 } from \"lucide-react\";\r\n\r\nexport function ResetPasswordPage() {\r\n const { t } = useTranslation(\"reset-password-page\");\r\n usePageTitle({ title: t(\"title\", \"Reset Password\") });\r\n const navigate = useNavigate();\r\n const [searchParams] = useSearchParams();\r\n const { resetPassword } = useAuth();\r\n\r\n const code = searchParams.get(\"code\") || \"\";\r\n const username =\r\n searchParams.get(\"username\") || searchParams.get(\"email\") || \"\";\r\n\r\n const [password, setPassword] = useState(\"\");\r\n const [confirmPassword, setConfirmPassword] = useState(\"\");\r\n const [showPassword, setShowPassword] = useState(false);\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const [isSuccess, setIsSuccess] = useState(false);\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n\r\n if (password !== confirmPassword) {\r\n setError(t(\"passwordMismatch\", \"Passwords do not match\"));\r\n return;\r\n }\r\n\r\n if (!code || !username) {\r\n setError(\r\n t(\r\n \"invalidLink\",\r\n \"Invalid or expired reset link. Please request a new one.\",\r\n ),\r\n );\r\n return;\r\n }\r\n\r\n setIsLoading(true);\r\n\r\n try {\r\n await resetPassword(username, code, password);\r\n setIsSuccess(true);\r\n toast.success(\r\n t(\"passwordResetSuccess\", \"Password reset successfully!\"),\r\n );\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\r\n \"resetPasswordError\",\r\n \"Failed to reset password. Please try again.\",\r\n ),\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n // Success state\r\n if (isSuccess) {\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8 text-center\">\r\n <CheckCircle2 className=\"w-16 h-16 text-green-500 mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"passwordReset\", \"Password Reset!\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"passwordResetDescription\",\r\n \"Your password has been reset successfully. You can now log in with your new password.\",\r\n )}\r\n </p>\r\n <Button asChild className=\"w-full\">\r\n <Link to=\"/login\">\r\n {t(\"goToLogin\", \"Go to Login\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Invalid link state\r\n if (!code || !username) {\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8 text-center\">\r\n <h1 className=\"text-2xl font-bold mb-2 text-red-600\">\r\n {t(\"invalidLinkTitle\", \"Invalid Reset Link\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"invalidLinkDescription\",\r\n \"This password reset link is invalid or has expired. Please request a new one.\",\r\n )}\r\n </p>\r\n <Button\r\n onClick={() => navigate(\"/forgot-password\")}\r\n className=\"w-full mb-3\"\r\n >\r\n {t(\"requestNewLink\", \"Request New Link\")}\r\n </Button>\r\n <Link\r\n to=\"/login\"\r\n className=\"inline-flex items-center justify-center gap-1 text-sm text-muted-foreground hover:text-primary\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to Login\")}\r\n </Link>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n return (\r\n <Layout>\r\n <div className=\"min-h-screen bg-muted/30 py-12\">\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n <div className=\"text-center mb-12\">\r\n <h1 className=\"text-4xl font-bold text-foreground mb-4\">\r\n {t(\"title\", \"Reset Password\")}\r\n </h1>\r\n <div className=\"w-16 h-1 bg-primary mx-auto mb-6\"></div>\r\n <p className=\"text-lg text-muted-foreground max-w-xl mx-auto\">\r\n {t(\"subtitle\", \"Enter your new password below\")}\r\n </p>\r\n </div>\r\n\r\n <div className=\"max-w-md mx-auto\">\r\n <Card>\r\n <CardHeader>\r\n <CardTitle className=\"flex items-center gap-2\">\r\n <KeyRound className=\"w-5 h-5 text-primary\" />\r\n {t(\"resetPassword\", \"Reset Password\")}\r\n </CardTitle>\r\n <CardDescription>\r\n {t(\"subtitle\", \"Enter your new password below\")}\r\n </CardDescription>\r\n </CardHeader>\r\n <CardContent>\r\n <form onSubmit={handleSubmit} className=\"space-y-6\">\r\n <div>\r\n <Label htmlFor=\"password\">\r\n {t(\"newPassword\", \"New Password\")} *\r\n </Label>\r\n <div className=\"relative\">\r\n <Input\r\n id=\"password\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n placeholder={t(\r\n \"newPasswordPlaceholder\",\r\n \"Enter new password\",\r\n )}\r\n required\r\n className=\"mt-1 pr-10\"\r\n autoComplete=\"new-password\"\r\n minLength={8}\r\n disabled={isLoading}\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={() => setShowPassword(!showPassword)}\r\n className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\r\n >\r\n {showPassword ? (\r\n <EyeOff className=\"w-4 h-4\" />\r\n ) : (\r\n <Eye className=\"w-4 h-4\" />\r\n )}\r\n </button>\r\n </div>\r\n <p className=\"text-xs text-muted-foreground mt-1\">\r\n {t(\r\n \"passwordRequirements\",\r\n \"At least 8 characters, 1 letter and 1 number\",\r\n )}\r\n </p>\r\n </div>\r\n\r\n <div>\r\n <Label htmlFor=\"confirmPassword\">\r\n {t(\"confirmPassword\", \"Confirm Password\")} *\r\n </Label>\r\n <Input\r\n id=\"confirmPassword\"\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={confirmPassword}\r\n onChange={(e) => setConfirmPassword(e.target.value)}\r\n placeholder={t(\r\n \"confirmPasswordPlaceholder\",\r\n \"Confirm new password\",\r\n )}\r\n required\r\n className=\"mt-1\"\r\n autoComplete=\"new-password\"\r\n minLength={8}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-4 bg-red-50 border border-red-200 rounded-lg\">\r\n <p className=\"text-red-800 text-sm font-medium\">\r\n {error}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <Button\r\n type=\"submit\"\r\n size=\"lg\"\r\n className=\"w-full\"\r\n disabled={isLoading}\r\n >\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"resetting\", \"Resetting...\")}\r\n </>\r\n ) : (\r\n t(\"resetPassword\", \"Reset Password\")\r\n )}\r\n </Button>\r\n\r\n <div className=\"text-center\">\r\n <Link\r\n to=\"/login\"\r\n className=\"text-sm text-muted-foreground hover:text-primary inline-flex items-center gap-1\"\r\n >\r\n <ArrowLeft className=\"w-4 h-4\" />\r\n {t(\"backToLogin\", \"Back to Login\")}\r\n </Link>\r\n </div>\r\n </form>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default ResetPasswordPage;\r\n"
27
27
  },
28
28
  {
29
29
  "path": "reset-password-page/lang/en.json",
30
30
  "type": "registry:lang",
31
- "target": "$modules$/reset-password-page/lang/en.json"
31
+ "target": "$modules$/reset-password-page/lang/en.json",
32
+ "content": "{\r\n \"title\": \"Reset Password\",\r\n \"subtitle\": \"Enter your new password below\",\r\n \"newPassword\": \"New Password\",\r\n \"newPasswordPlaceholder\": \"Enter new password\",\r\n \"confirmPassword\": \"Confirm Password\",\r\n \"confirmPasswordPlaceholder\": \"Confirm new password\",\r\n \"resetPassword\": \"Reset Password\",\r\n \"resetting\": \"Resetting...\",\r\n \"passwordRequirements\": \"At least 8 characters, 1 letter and 1 number\",\r\n \"passwordMismatch\": \"Passwords do not match\",\r\n \"invalidLink\": \"Invalid or expired reset link. Please request a new one.\",\r\n \"passwordResetSuccess\": \"Password reset successfully!\",\r\n \"resetPasswordError\": \"Failed to reset password. Please try again.\",\r\n \"passwordReset\": \"Password Reset!\",\r\n \"passwordResetDescription\": \"Your password has been reset successfully. You can now log in with your new password.\",\r\n \"goToLogin\": \"Go to Login\",\r\n \"invalidLinkTitle\": \"Invalid Reset Link\",\r\n \"invalidLinkDescription\": \"This password reset link is invalid or has expired. Please request a new one.\",\r\n \"requestNewLink\": \"Request New Link\",\r\n \"backToLogin\": \"Back to Login\"\r\n}\r\n"
32
33
  },
33
34
  {
34
35
  "path": "reset-password-page/lang/tr.json",
35
36
  "type": "registry:lang",
36
- "target": "$modules$/reset-password-page/lang/tr.json"
37
+ "target": "$modules$/reset-password-page/lang/tr.json",
38
+ "content": "{\r\n \"title\": \"Şifre Sıfırla\",\r\n \"subtitle\": \"Yeni şifrenizi aşağıya girin\",\r\n \"newPassword\": \"Yeni Şifre\",\r\n \"newPasswordPlaceholder\": \"Yeni şifre girin\",\r\n \"confirmPassword\": \"Şifreyi Onayla\",\r\n \"confirmPasswordPlaceholder\": \"Yeni şifreyi onaylayın\",\r\n \"resetPassword\": \"Şifreyi Sıfırla\",\r\n \"resetting\": \"Sıfırlanıyor...\",\r\n \"passwordRequirements\": \"En az 8 karakter, 1 harf ve 1 rakam\",\r\n \"passwordMismatch\": \"Şifreler eşleşmiyor\",\r\n \"invalidLink\": \"Geçersiz veya süresi dolmuş sıfırlama bağlantısı. Lütfen yeni bir tane isteyin.\",\r\n \"passwordResetSuccess\": \"Şifre başarıyla sıfırlandı!\",\r\n \"resetPasswordError\": \"Şifre sıfırlanamadı. Lütfen tekrar deneyin.\",\r\n \"passwordReset\": \"Şifre Sıfırlandı!\",\r\n \"passwordResetDescription\": \"Şifreniz başarıyla sıfırlandı. Artık yeni şifrenizle giriş yapabilirsiniz.\",\r\n \"goToLogin\": \"Girişe Git\",\r\n \"invalidLinkTitle\": \"Geçersiz Sıfırlama Bağlantısı\",\r\n \"invalidLinkDescription\": \"Bu şifre sıfırlama bağlantısı geçersiz veya süresi dolmuş. Lütfen yeni bir tane isteyin.\",\r\n \"requestNewLink\": \"Yeni Bağlantı İste\",\r\n \"backToLogin\": \"Girişe Dön\"\r\n}\r\n"
37
39
  }
38
- ]
40
+ ],
41
+ "exports": {
42
+ "types": [],
43
+ "variables": [
44
+ "ResetPasswordPage",
45
+ "default"
46
+ ]
47
+ }
39
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promakeai/cli",
3
- "version": "0.3.9",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "promake": "dist/index.js"
package/template/bun.lock CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "new",
6
6
  "dependencies": {
7
7
  "@hookform/resolvers": "^5.2.2",
8
- "@promakeai/customer-backend-client": "^1.0.8",
8
+ "@promakeai/customer-backend-client": "^1.1.0",
9
9
  "@promakeai/inspector": "^1.5.1",
10
10
  "@radix-ui/react-accordion": "^1.2.12",
11
11
  "@radix-ui/react-alert-dialog": "^1.1.15",
@@ -228,7 +228,7 @@
228
228
 
229
229
  "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
230
230
 
231
- "@promakeai/customer-backend-client": ["@promakeai/customer-backend-client@1.0.8", "", { "dependencies": { "axios": "^1.7.0", "zod": "^4.1.13" }, "peerDependencies": { "@tanstack/react-query": ">=5.0.0", "react": ">=18.0.0" }, "optionalPeers": ["@tanstack/react-query", "react"] }, "sha512-doPnoJQsa6mpOjNyUQocSk6qglHRSSSm84ZS5m8A1Y2+ZE+eM9wzDdBZsauYSfRGe2bYhWu5Y3k2pv8q4QoC1A=="],
231
+ "@promakeai/customer-backend-client": ["@promakeai/customer-backend-client@1.1.0", "", { "dependencies": { "axios": "^1.7.0", "zod": "^4.1.13" }, "peerDependencies": { "@tanstack/react-query": ">=5.0.0", "react": ">=18.0.0" }, "optionalPeers": ["@tanstack/react-query", "react"] }, "sha512-Ql56YtwRQyJ9toFyfSkkn0deAdlVodNdK7d2xd9nVVAS7C2F8fxIuJ3xCtWtdLfXjponHhG2qdy9ILq8V4fpbA=="],
232
232
 
233
233
  "@promakeai/inspector": ["@promakeai/inspector@1.5.1", "", { "dependencies": { "@promakeai/inspector-types": "1.5.1", "clsx": "^2.1.1", "lodash": "^4.17.21", "lucide-react": "^0.554.0", "react-colorful": "^5.6.1", "vite-plugin-component-debugger": "^2.2.0", "zustand": "^5.0.8" }, "peerDependencies": { "react": ">=18.0.0", "vite": ">=5.0.0" }, "optionalPeers": ["vite"] }, "sha512-sKclusU4D9xsTJZZ629cFFq2GXyD2l/aOKQdQ7qq4eNfYW85nmoS+D4aQ9M88LcNngO+uFmLCUBnpZHwamYS/A=="],
234
234
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@promakeai/template",
3
3
  "private": true,
4
- "version": "0.0.1",
4
+ "version": "0.1.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "@hookform/resolvers": "^5.2.2",
16
- "@promakeai/customer-backend-client": "^1.0.8",
16
+ "@promakeai/customer-backend-client": "^1.1.0",
17
17
  "@promakeai/inspector": "^1.5.1",
18
18
  "@radix-ui/react-accordion": "^1.2.12",
19
19
  "@radix-ui/react-alert-dialog": "^1.1.15",