@promakeai/cli 0.2.7 → 0.2.9
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.
- package/dist/index.js +234 -234
- package/dist/registry/blog-list-page.json +1 -1
- package/dist/registry/products-page.json +1 -1
- package/package.json +1 -2
- package/template/index.html +101 -153
- package/template/src/lang/en/about.json +0 -4
- package/template/src/lang/en/contact.json +0 -39
- package/template/src/lang/en/cookies.json +0 -4
- package/template/src/lang/en/footer.json +0 -12
- package/template/src/lang/en/forgotPassword.json +0 -37
- package/template/src/lang/en/header.json +0 -10
- package/template/src/lang/en/hero.json +0 -8
- package/template/src/lang/en/index.json +0 -3
- package/template/src/lang/en/login.json +0 -18
- package/template/src/lang/en/privacy.json +0 -4
- package/template/src/lang/en/register.json +0 -25
- package/template/src/lang/en/terms.json +0 -4
- package/template/src/lang/tr/about.json +0 -4
- package/template/src/lang/tr/contact.json +0 -39
- package/template/src/lang/tr/cookies.json +0 -4
- package/template/src/lang/tr/footer.json +0 -12
- package/template/src/lang/tr/forgotPassword.json +0 -37
- package/template/src/lang/tr/header.json +0 -10
- package/template/src/lang/tr/hero.json +0 -8
- package/template/src/lang/tr/index.json +0 -3
- package/template/src/lang/tr/login.json +0 -18
- package/template/src/lang/tr/privacy.json +0 -4
- package/template/src/lang/tr/register.json +0 -25
- package/template/src/lang/tr/terms.json +0 -4
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"path": "blog-list-page/blog-list-page.tsx",
|
|
25
25
|
"type": "registry:page",
|
|
26
26
|
"target": "$modules$/blog-list-page/blog-list-page.tsx",
|
|
27
|
-
"content": "import { useState, useEffect } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { Search, Filter } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport { Input } from \"@/components/ui/input\";\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 { PostCard } from \"@/modules/post-card/post-card\";\nimport { usePosts, useBlogCategories, type BlogCategory } from \"@/modules/blog-core\";\n\ninterface FilterSectionProps {\n t: (key: string, fallback?: string) => string;\n searchTerm: string;\n setSearchTerm: (term: string) => void;\n categories: BlogCategory[];\n selectedCategories: string[];\n handleCategoryChange: (slug: string, checked: boolean) => void;\n allTags: string[];\n selectedTags: string[];\n handleTagChange: (tag: string, checked: boolean) => void;\n clearFilters: () => void;\n}\n\nfunction FilterSection({\n t,\n searchTerm,\n setSearchTerm,\n categories,\n selectedCategories,\n handleCategoryChange,\n allTags,\n selectedTags,\n handleTagChange,\n clearFilters,\n}: FilterSectionProps) {\n return (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-3 flex items-center gap-2\">\n <Search className=\"h-4 w-4\" />\n {t(\"search\")}\n </h3>\n <Input\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n />\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"categories\")}</h3>\n <div className=\"space-y-2\">\n {categories.map((category) => (\n <div key={category.slug} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`category-${category.slug}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n />\n <label\n htmlFor={`category-${category.slug}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n {allTags.length > 0 && (\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"tags\")}</h3>\n <div className=\"space-y-2 max-h-48 overflow-y-auto\">\n {allTags.slice(0, 20).map((tag) => (\n <div key={tag} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`tag-${tag}`}\n checked={selectedTags.includes(tag)}\n onCheckedChange={(checked) =>\n handleTagChange(tag, checked as boolean)\n }\n />\n <label\n htmlFor={`tag-${tag}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {tag}\n </label>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {(searchTerm ||\n selectedCategories.length > 0 ||\n selectedTags.length > 0) && (\n <Button variant=\"outline\" onClick={clearFilters} className=\"w-full\">\n {t(\"clearFilters\")}\n </Button>\n )}\n </div>\n );\n}\n\nexport function BlogListPage() {\n const { t } = useTranslation(\"blog-list-page\");\n usePageTitle({ title: t(\"title\") });\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [searchTerm, setSearchTerm] = useState(\n searchParams.get(\"search\") || \"\"\n );\n const [selectedCategories, setSelectedCategories] = useState<string[]>(\n searchParams.get(\"categories\")?.split(\",\").filter(Boolean) || []\n );\n const [selectedTags, setSelectedTags] = useState<string[]>(\n searchParams.get(\"tags\")?.split(\",\").filter(Boolean) || []\n );\n const [sortBy, setSortBy] = useState(searchParams.get(\"sort\") || \"newest\");\n const [viewMode, _setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n\n const { posts, loading, error } = usePosts();\n const { categories } = useBlogCategories();\n\n const filteredPosts = posts.filter((post) => {\n if (searchTerm) {\n const searchLower = searchTerm.toLowerCase();\n if (\n !post.title.toLowerCase().includes(searchLower) &&\n !post.excerpt.toLowerCase().includes(searchLower) &&\n !post.content.toLowerCase().includes(searchLower)\n ) {\n return false;\n }\n }\n\n if (selectedCategories.length > 0) {\n const hasMatchingCategory = selectedCategories.some(\n (categorySlug) =>\n post.category === categorySlug ||\n post.categories?.some((cat) => cat.slug === categorySlug)\n );\n if (!hasMatchingCategory) return false;\n }\n\n if (selectedTags.length > 0) {\n const hasMatchingTag = selectedTags.some((tag) =>\n post.tags.includes(tag)\n );\n if (!hasMatchingTag) return false;\n }\n\n return true;\n });\n\n const sortedPosts = [...filteredPosts].sort((a, b) => {\n switch (sortBy) {\n case \"oldest\":\n return (\n new Date(a.published_at).getTime() -\n new Date(b.published_at).getTime()\n );\n case \"popular\":\n return (b.view_count || 0) - (a.view_count || 0);\n case \"reading-time\":\n return (a.read_time || 0) - (b.read_time || 0);\n case \"newest\":\n default:\n return (\n new Date(b.published_at).getTime() -\n new Date(a.published_at).getTime()\n );\n }\n });\n\n useEffect(() => {\n const params = new URLSearchParams();\n if (searchTerm) params.set(\"search\", searchTerm);\n if (selectedCategories.length)\n params.set(\"categories\", selectedCategories.join(\",\"));\n if (selectedTags.length) params.set(\"tags\", selectedTags.join(\",\"));\n if (sortBy !== \"newest\") params.set(\"sort\", sortBy);\n\n setSearchParams(params);\n }, [searchTerm, selectedCategories, selectedTags, sortBy, setSearchParams]);\n\n const handleCategoryChange = (categorySlug: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories([...selectedCategories, categorySlug]);\n } else {\n setSelectedCategories(\n selectedCategories.filter((c) => c !== categorySlug)\n );\n }\n };\n\n const handleTagChange = (tag: string, checked: boolean) => {\n if (checked) {\n setSelectedTags([...selectedTags, tag]);\n } else {\n setSelectedTags(selectedTags.filter((t) => t !== tag));\n }\n };\n\n const allTags = Array.from(new Set(posts.flatMap((post) => post.tags)));\n\n const clearFilters = () => {\n setSearchTerm(\"\");\n setSelectedCategories([]);\n setSelectedTags([]);\n setSortBy(\"newest\");\n };\n\n const filterProps: FilterSectionProps = {\n t,\n searchTerm,\n setSearchTerm,\n categories,\n selectedCategories,\n handleCategoryChange,\n allTags,\n selectedTags,\n handleTagChange,\n clearFilters,\n };\n\n if (loading) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <div className=\"animate-pulse space-y-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-48 bg-muted rounded-lg\"></div>\n ))}\n </div>\n </div>\n </Layout>\n );\n }\n\n if (error) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\n <p className=\"text-destructive\">{t(\"error\")}</p>\n </div>\n </Layout>\n );\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=\"flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold mb-2\">{t(\"title\")}</h1>\n <p className=\"text-muted-foreground\">{t(\"subtitle\")}</p>\n </div>\n\n <div className=\"flex items-center gap-4\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-[180px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"newest\">{t(\"sortNewest\")}</SelectItem>\n <SelectItem value=\"oldest\">{t(\"sortOldest\")}</SelectItem>\n <SelectItem value=\"popular\">{t(\"sortPopular\")}</SelectItem>\n <SelectItem value=\"reading-time\">\n {t(\"sortReadingTime\")}\n </SelectItem>\n </SelectContent>\n </Select>\n\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"lg:hidden\">\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>{t(\"filters\")}</SheetTitle>\n <SheetDescription>{t(\"filterDescription\")}</SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSection {...filterProps} />\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </FadeIn>\n\n <div className=\"flex flex-col lg:flex-row gap-8\">\n <div className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-4\">\n <FilterSection {...filterProps} />\n </div>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between mb-6\">\n <p className=\"text-sm text-muted-foreground\">\n {t(\"showing\")} {sortedPosts.length} {t(\"of\")} {posts.length}{\" \"}\n {t(\"posts\")}\n {searchTerm && (\n <span className=\"ml-1\">\n {t(\"for\")} \"<strong>{searchTerm}</strong>\"\n </span>\n )}\n </p>\n </div>\n\n {sortedPosts.length > 0 ? (\n <div\n className={`grid gap-6 ${\n viewMode === \"grid\"\n ? \"grid-cols-1 md:grid-cols-2 xl:grid-cols-3\"\n : \"grid-cols-1\"\n }`}\n >\n {sortedPosts.map((post) => (\n <PostCard key={post.id} post={post} layout={viewMode} />\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground mb-4\">\n {t(\"noPostsFound\")}\n </p>\n <Button onClick={clearFilters} variant=\"outline\">\n {t(\"clearFilters\")}\n </Button>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default BlogListPage;\n"
|
|
27
|
+
"content": "import { useState, useEffect } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { Search, Filter } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport { Input } from \"@/components/ui/input\";\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 { PostCard } from \"@/modules/post-card/post-card\";\nimport { usePosts, useBlogCategories, type BlogCategory } from \"@/modules/blog-core\";\n\ninterface FilterSectionProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n t: (key: string, options?: any) => string;\n searchTerm: string;\n setSearchTerm: (term: string) => void;\n categories: BlogCategory[];\n selectedCategories: string[];\n handleCategoryChange: (slug: string, checked: boolean) => void;\n allTags: string[];\n selectedTags: string[];\n handleTagChange: (tag: string, checked: boolean) => void;\n clearFilters: () => void;\n}\n\nfunction FilterSection({\n t,\n searchTerm,\n setSearchTerm,\n categories,\n selectedCategories,\n handleCategoryChange,\n allTags,\n selectedTags,\n handleTagChange,\n clearFilters,\n}: FilterSectionProps) {\n return (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-3 flex items-center gap-2\">\n <Search className=\"h-4 w-4\" />\n {t(\"search\")}\n </h3>\n <Input\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n />\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"categories\")}</h3>\n <div className=\"space-y-2\">\n {categories.map((category) => (\n <div key={category.slug} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`category-${category.slug}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n />\n <label\n htmlFor={`category-${category.slug}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n {allTags.length > 0 && (\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"tags\")}</h3>\n <div className=\"space-y-2 max-h-48 overflow-y-auto\">\n {allTags.slice(0, 20).map((tag) => (\n <div key={tag} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`tag-${tag}`}\n checked={selectedTags.includes(tag)}\n onCheckedChange={(checked) =>\n handleTagChange(tag, checked as boolean)\n }\n />\n <label\n htmlFor={`tag-${tag}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {tag}\n </label>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {(searchTerm ||\n selectedCategories.length > 0 ||\n selectedTags.length > 0) && (\n <Button variant=\"outline\" onClick={clearFilters} className=\"w-full\">\n {t(\"clearFilters\")}\n </Button>\n )}\n </div>\n );\n}\n\nexport function BlogListPage() {\n const { t } = useTranslation(\"blog-list-page\");\n usePageTitle({ title: t(\"title\") });\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [searchTerm, setSearchTerm] = useState(\n searchParams.get(\"search\") || \"\"\n );\n const [selectedCategories, setSelectedCategories] = useState<string[]>(\n searchParams.get(\"categories\")?.split(\",\").filter(Boolean) || []\n );\n const [selectedTags, setSelectedTags] = useState<string[]>(\n searchParams.get(\"tags\")?.split(\",\").filter(Boolean) || []\n );\n const [sortBy, setSortBy] = useState(searchParams.get(\"sort\") || \"newest\");\n const [viewMode, _setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n\n const { posts, loading, error } = usePosts();\n const { categories } = useBlogCategories();\n\n const filteredPosts = posts.filter((post) => {\n if (searchTerm) {\n const searchLower = searchTerm.toLowerCase();\n if (\n !post.title.toLowerCase().includes(searchLower) &&\n !post.excerpt.toLowerCase().includes(searchLower) &&\n !post.content.toLowerCase().includes(searchLower)\n ) {\n return false;\n }\n }\n\n if (selectedCategories.length > 0) {\n const hasMatchingCategory = selectedCategories.some(\n (categorySlug) =>\n post.category === categorySlug ||\n post.categories?.some((cat) => cat.slug === categorySlug)\n );\n if (!hasMatchingCategory) return false;\n }\n\n if (selectedTags.length > 0) {\n const hasMatchingTag = selectedTags.some((tag) =>\n post.tags.includes(tag)\n );\n if (!hasMatchingTag) return false;\n }\n\n return true;\n });\n\n const sortedPosts = [...filteredPosts].sort((a, b) => {\n switch (sortBy) {\n case \"oldest\":\n return (\n new Date(a.published_at).getTime() -\n new Date(b.published_at).getTime()\n );\n case \"popular\":\n return (b.view_count || 0) - (a.view_count || 0);\n case \"reading-time\":\n return (a.read_time || 0) - (b.read_time || 0);\n case \"newest\":\n default:\n return (\n new Date(b.published_at).getTime() -\n new Date(a.published_at).getTime()\n );\n }\n });\n\n useEffect(() => {\n const params = new URLSearchParams();\n if (searchTerm) params.set(\"search\", searchTerm);\n if (selectedCategories.length)\n params.set(\"categories\", selectedCategories.join(\",\"));\n if (selectedTags.length) params.set(\"tags\", selectedTags.join(\",\"));\n if (sortBy !== \"newest\") params.set(\"sort\", sortBy);\n\n setSearchParams(params);\n }, [searchTerm, selectedCategories, selectedTags, sortBy, setSearchParams]);\n\n const handleCategoryChange = (categorySlug: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories([...selectedCategories, categorySlug]);\n } else {\n setSelectedCategories(\n selectedCategories.filter((c) => c !== categorySlug)\n );\n }\n };\n\n const handleTagChange = (tag: string, checked: boolean) => {\n if (checked) {\n setSelectedTags([...selectedTags, tag]);\n } else {\n setSelectedTags(selectedTags.filter((t) => t !== tag));\n }\n };\n\n const allTags = Array.from(new Set(posts.flatMap((post) => post.tags)));\n\n const clearFilters = () => {\n setSearchTerm(\"\");\n setSelectedCategories([]);\n setSelectedTags([]);\n setSortBy(\"newest\");\n };\n\n const filterProps: FilterSectionProps = {\n t,\n searchTerm,\n setSearchTerm,\n categories,\n selectedCategories,\n handleCategoryChange,\n allTags,\n selectedTags,\n handleTagChange,\n clearFilters,\n };\n\n if (loading) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <div className=\"animate-pulse space-y-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-48 bg-muted rounded-lg\"></div>\n ))}\n </div>\n </div>\n </Layout>\n );\n }\n\n if (error) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\n <p className=\"text-destructive\">{t(\"error\")}</p>\n </div>\n </Layout>\n );\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=\"flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold mb-2\">{t(\"title\")}</h1>\n <p className=\"text-muted-foreground\">{t(\"subtitle\")}</p>\n </div>\n\n <div className=\"flex items-center gap-4\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-[180px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"newest\">{t(\"sortNewest\")}</SelectItem>\n <SelectItem value=\"oldest\">{t(\"sortOldest\")}</SelectItem>\n <SelectItem value=\"popular\">{t(\"sortPopular\")}</SelectItem>\n <SelectItem value=\"reading-time\">\n {t(\"sortReadingTime\")}\n </SelectItem>\n </SelectContent>\n </Select>\n\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"lg:hidden\">\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>{t(\"filters\")}</SheetTitle>\n <SheetDescription>{t(\"filterDescription\")}</SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSection {...filterProps} />\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </FadeIn>\n\n <div className=\"flex flex-col lg:flex-row gap-8\">\n <div className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-4\">\n <FilterSection {...filterProps} />\n </div>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between mb-6\">\n <p className=\"text-sm text-muted-foreground\">\n {t(\"showing\")} {sortedPosts.length} {t(\"of\")} {posts.length}{\" \"}\n {t(\"posts\")}\n {searchTerm && (\n <span className=\"ml-1\">\n {t(\"for\")} \"<strong>{searchTerm}</strong>\"\n </span>\n )}\n </p>\n </div>\n\n {sortedPosts.length > 0 ? (\n <div\n className={`grid gap-6 ${\n viewMode === \"grid\"\n ? \"grid-cols-1 md:grid-cols-2 xl:grid-cols-3\"\n : \"grid-cols-1\"\n }`}\n >\n {sortedPosts.map((post) => (\n <PostCard key={post.id} post={post} layout={viewMode} />\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground mb-4\">\n {t(\"noPostsFound\")}\n </p>\n <Button onClick={clearFilters} variant=\"outline\">\n {t(\"clearFilters\")}\n </Button>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default BlogListPage;\n"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "blog-list-page/lang/en.json",
|
|
@@ -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 t: (key: string, fallback?: string) => string;\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 { 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: (key: string, options?: any) => string;\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"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "products-page/lang/en.json",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promakeai/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"promake": "dist/index.js"
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
"release": "bun run build && npm publish --access public"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@lightpanda/browser": "^1.0.1",
|
|
34
33
|
"adm-zip": "^0.5.16",
|
|
35
34
|
"archiver": "^7.0.1",
|
|
36
35
|
"chalk": "^5.3.0",
|
package/template/index.html
CHANGED
|
@@ -15,20 +15,17 @@
|
|
|
15
15
|
while (!![]) {
|
|
16
16
|
try {
|
|
17
17
|
var _0x3ca241 =
|
|
18
|
-
(-parseInt(_0xe13615(0x152)) / 0x1) *
|
|
19
|
-
|
|
20
|
-
(parseInt(_0xe13615(0x164)) / 0x3) *
|
|
21
|
-
(-parseInt(_0xe13615(0x16e)) / 0x4) +
|
|
18
|
+
(-parseInt(_0xe13615(0x152)) / 0x1) * (parseInt(_0xe13615(0x16d)) / 0x2) +
|
|
19
|
+
(parseInt(_0xe13615(0x164)) / 0x3) * (-parseInt(_0xe13615(0x16e)) / 0x4) +
|
|
22
20
|
parseInt(_0xe13615(0x15b)) / 0x5 +
|
|
23
21
|
parseInt(_0xe13615(0x169)) / 0x6 +
|
|
24
22
|
parseInt(_0xe13615(0x14d)) / 0x7 +
|
|
25
|
-
(-parseInt(_0xe13615(0x159)) / 0x8) *
|
|
26
|
-
(-parseInt(_0xe13615(0x174)) / 0x9) +
|
|
23
|
+
(-parseInt(_0xe13615(0x159)) / 0x8) * (-parseInt(_0xe13615(0x174)) / 0x9) +
|
|
27
24
|
-parseInt(_0xe13615(0x170)) / 0xa;
|
|
28
25
|
if (_0x3ca241 === _0x17ecdb) break;
|
|
29
|
-
else _0x174225[
|
|
26
|
+
else _0x174225['push'](_0x174225['shift']());
|
|
30
27
|
} catch (_0x37837a) {
|
|
31
|
-
_0x174225[
|
|
28
|
+
_0x174225['push'](_0x174225['shift']());
|
|
32
29
|
}
|
|
33
30
|
}
|
|
34
31
|
})(_0x24c0, 0x6e935),
|
|
@@ -38,28 +35,22 @@
|
|
|
38
35
|
_0x1da352 = function (_0x417e31) {
|
|
39
36
|
var _0x2dbf05 = _0x3db6;
|
|
40
37
|
(console[_0x2dbf05(0x17e)](_0x2dbf05(0x15a), _0x417e31),
|
|
41
|
-
window[
|
|
42
|
-
window[_0x2dbf05(0x156)][
|
|
43
|
-
JSON[
|
|
38
|
+
window['parent'] !== window &&
|
|
39
|
+
window[_0x2dbf05(0x156)]['postMessage'](
|
|
40
|
+
JSON['stringify']({
|
|
44
41
|
type: _0x2dbf05(0x14e),
|
|
45
42
|
data: _0x417e31,
|
|
46
43
|
}),
|
|
47
|
-
|
|
44
|
+
'*',
|
|
48
45
|
));
|
|
49
46
|
};
|
|
50
|
-
(window[
|
|
47
|
+
(window['addEventListener'](_0x5c943e(0x154), function (_0x2c973b) {
|
|
51
48
|
var _0x2fab61 = _0x5c943e;
|
|
52
|
-
if (
|
|
53
|
-
_0x2c973b[_0x2fab61(0x15c)] &&
|
|
54
|
-
_0x2c973b[_0x2fab61(0x15c)]["indexOf"](_0x2fab61(0x153)) > -0x1
|
|
55
|
-
)
|
|
56
|
-
return;
|
|
49
|
+
if (_0x2c973b[_0x2fab61(0x15c)] && _0x2c973b[_0x2fab61(0x15c)]['indexOf'](_0x2fab61(0x153)) > -0x1) return;
|
|
57
50
|
if (
|
|
58
51
|
_0x2c973b[_0x2fab61(0x154)] &&
|
|
59
52
|
_0x2c973b[_0x2fab61(0x154)][_0x2fab61(0x160)] &&
|
|
60
|
-
_0x2c973b[_0x2fab61(0x154)][_0x2fab61(0x160)][_0x2fab61(0x167)](
|
|
61
|
-
_0x2fab61(0x153),
|
|
62
|
-
) > -0x1
|
|
53
|
+
_0x2c973b[_0x2fab61(0x154)][_0x2fab61(0x160)][_0x2fab61(0x167)](_0x2fab61(0x153)) > -0x1
|
|
63
54
|
)
|
|
64
55
|
return;
|
|
65
56
|
var _0x3097d2 = {
|
|
@@ -68,50 +59,34 @@
|
|
|
68
59
|
fileName: _0x2c973b[_0x2fab61(0x15c)],
|
|
69
60
|
lineNumber: _0x2c973b[_0x2fab61(0x178)],
|
|
70
61
|
columnNumber: _0x2c973b[_0x2fab61(0x171)],
|
|
71
|
-
stack: _0x2c973b[
|
|
72
|
-
? _0x2c973b[_0x2fab61(0x154)][_0x2fab61(0x160)]
|
|
73
|
-
: undefined,
|
|
62
|
+
stack: _0x2c973b['error'] ? _0x2c973b[_0x2fab61(0x154)][_0x2fab61(0x160)] : undefined,
|
|
74
63
|
};
|
|
75
|
-
(_0x5882f3[
|
|
64
|
+
(_0x5882f3['push'](_0x3097d2), _0x1da352(_0x3097d2));
|
|
76
65
|
}),
|
|
77
66
|
window[_0x5c943e(0x163)](_0x5c943e(0x15d), function (_0xb1cac3) {
|
|
78
67
|
var _0x34319f = _0x5c943e,
|
|
79
68
|
_0x54e49d = _0xb1cac3[_0x34319f(0x166)],
|
|
80
69
|
_0x554051 = {
|
|
81
70
|
type: _0x34319f(0x150),
|
|
82
|
-
message:
|
|
83
|
-
_0x54e49d && _0x54e49d["message"]
|
|
84
|
-
? _0x54e49d["message"]
|
|
85
|
-
: String(_0x54e49d),
|
|
71
|
+
message: _0x54e49d && _0x54e49d['message'] ? _0x54e49d['message'] : String(_0x54e49d),
|
|
86
72
|
stack: _0x54e49d ? _0x54e49d[_0x34319f(0x160)] : undefined,
|
|
87
73
|
fileName: _0x54e49d ? _0x54e49d[_0x34319f(0x17a)] : undefined,
|
|
88
|
-
lineNumber: _0x54e49d
|
|
89
|
-
|
|
90
|
-
: undefined,
|
|
91
|
-
columnNumber: _0x54e49d
|
|
92
|
-
? _0x54e49d[_0x34319f(0x177)]
|
|
93
|
-
: undefined,
|
|
74
|
+
lineNumber: _0x54e49d ? _0x54e49d[_0x34319f(0x16a)] : undefined,
|
|
75
|
+
columnNumber: _0x54e49d ? _0x54e49d[_0x34319f(0x177)] : undefined,
|
|
94
76
|
};
|
|
95
77
|
(_0x5882f3[_0x34319f(0x162)](_0x554051), _0x1da352(_0x554051));
|
|
96
78
|
}));
|
|
97
|
-
var _0xda070c = console[
|
|
79
|
+
var _0xda070c = console['error'];
|
|
98
80
|
((console[_0x5c943e(0x154)] = function () {
|
|
99
81
|
var _0x4975cd = _0x5c943e;
|
|
100
82
|
_0xda070c[_0x4975cd(0x16c)](console, arguments);
|
|
101
|
-
var _0x5db5e3 =
|
|
102
|
-
Array[_0x4975cd(0x165)][_0x4975cd(0x173)]["call"](arguments),
|
|
83
|
+
var _0x5db5e3 = Array[_0x4975cd(0x165)][_0x4975cd(0x173)]['call'](arguments),
|
|
103
84
|
_0xc5b3c = _0x5db5e3[_0x4975cd(0x15e)](function (_0x49279) {
|
|
104
85
|
var _0x2a41ec = _0x4975cd;
|
|
105
|
-
return typeof _0x49279 === _0x2a41ec(0x17f)
|
|
106
|
-
|
|
107
|
-
: String(_0x49279);
|
|
108
|
-
})[_0x4975cd(0x17c)]("\x20"),
|
|
86
|
+
return typeof _0x49279 === _0x2a41ec(0x17f) ? JSON[_0x2a41ec(0x155)](_0x49279) : String(_0x49279);
|
|
87
|
+
})[_0x4975cd(0x17c)]('\x20'),
|
|
109
88
|
_0x5de5ff = null;
|
|
110
|
-
for (
|
|
111
|
-
var _0x5de710 = 0x0;
|
|
112
|
-
_0x5de710 < _0x5db5e3["length"];
|
|
113
|
-
_0x5de710++
|
|
114
|
-
) {
|
|
89
|
+
for (var _0x5de710 = 0x0; _0x5de710 < _0x5db5e3['length']; _0x5de710++) {
|
|
115
90
|
if (_0x5db5e3[_0x5de710] instanceof Error) {
|
|
116
91
|
_0x5de5ff = _0x5db5e3[_0x5de710];
|
|
117
92
|
break;
|
|
@@ -131,52 +106,31 @@
|
|
|
131
106
|
var _0x26e489 = _0x5c943e;
|
|
132
107
|
_0x1f5a1c[_0x26e489(0x176)](function (_0x51ea7c) {
|
|
133
108
|
var _0x4fb91f = _0x26e489;
|
|
134
|
-
_0x51ea7c[_0x4fb91f(0x151)][_0x4fb91f(0x176)](
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
_0x56219e[
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
_0x6e73e2 = {
|
|
160
|
-
type: "vite",
|
|
161
|
-
message: _0x2d4036,
|
|
162
|
-
fileName: _0x1f3b99 ? _0x1f3b99[0x1] : undefined,
|
|
163
|
-
lineNumber: _0x1f3b99
|
|
164
|
-
? parseInt(_0x1f3b99[0x2])
|
|
165
|
-
: undefined,
|
|
166
|
-
columnNumber: _0x1f3b99
|
|
167
|
-
? parseInt(_0x1f3b99[0x3])
|
|
168
|
-
: undefined,
|
|
169
|
-
frame: _0x12ddd5
|
|
170
|
-
? _0x12ddd5[_0x5727af(0x157)][_0x5727af(0x168)]()
|
|
171
|
-
: undefined,
|
|
172
|
-
plugin: _0x4c91ae
|
|
173
|
-
? _0x4c91ae[_0x5727af(0x157)]["trim"]()
|
|
174
|
-
: undefined,
|
|
175
|
-
};
|
|
176
|
-
(_0x5882f3["push"](_0x6e73e2), _0x1da352(_0x6e73e2));
|
|
177
|
-
} catch (_0xdfdd55) {}
|
|
178
|
-
},
|
|
179
|
-
);
|
|
109
|
+
_0x51ea7c[_0x4fb91f(0x151)][_0x4fb91f(0x176)](function (_0x56219e) {
|
|
110
|
+
var _0x5727af = _0x4fb91f;
|
|
111
|
+
if (_0x56219e['nodeType'] === 0x1 && _0x56219e['tagName'] === _0x5727af(0x180))
|
|
112
|
+
try {
|
|
113
|
+
var _0x2bf1f0 = _0x56219e[_0x5727af(0x14f)];
|
|
114
|
+
if (!_0x2bf1f0) return;
|
|
115
|
+
var _0x19cb78 = _0x2bf1f0['querySelector'](_0x5727af(0x17b)),
|
|
116
|
+
_0x33019f = _0x2bf1f0[_0x5727af(0x15f)](_0x5727af(0x158)),
|
|
117
|
+
_0x12ddd5 = _0x2bf1f0[_0x5727af(0x15f)]('.frame'),
|
|
118
|
+
_0x4c91ae = _0x2bf1f0[_0x5727af(0x15f)]('.plugin'),
|
|
119
|
+
_0x2d4036 = _0x19cb78 ? _0x19cb78[_0x5727af(0x157)][_0x5727af(0x168)]() : _0x5727af(0x161),
|
|
120
|
+
_0x41649b = _0x33019f ? _0x33019f['textContent'][_0x5727af(0x168)]() : '',
|
|
121
|
+
_0x1f3b99 = _0x41649b['match'](/(.+):(\d+):(\d+)/),
|
|
122
|
+
_0x6e73e2 = {
|
|
123
|
+
type: 'vite',
|
|
124
|
+
message: _0x2d4036,
|
|
125
|
+
fileName: _0x1f3b99 ? _0x1f3b99[0x1] : undefined,
|
|
126
|
+
lineNumber: _0x1f3b99 ? parseInt(_0x1f3b99[0x2]) : undefined,
|
|
127
|
+
columnNumber: _0x1f3b99 ? parseInt(_0x1f3b99[0x3]) : undefined,
|
|
128
|
+
frame: _0x12ddd5 ? _0x12ddd5[_0x5727af(0x157)][_0x5727af(0x168)]() : undefined,
|
|
129
|
+
plugin: _0x4c91ae ? _0x4c91ae[_0x5727af(0x157)]['trim']() : undefined,
|
|
130
|
+
};
|
|
131
|
+
(_0x5882f3['push'](_0x6e73e2), _0x1da352(_0x6e73e2));
|
|
132
|
+
} catch (_0xdfdd55) {}
|
|
133
|
+
});
|
|
180
134
|
});
|
|
181
135
|
})[_0x5c943e(0x179)](document[_0x5c943e(0x17d)], {
|
|
182
136
|
childList: !![],
|
|
@@ -186,58 +140,58 @@
|
|
|
186
140
|
})());
|
|
187
141
|
function _0x24c0() {
|
|
188
142
|
var _0x29fe81 = [
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
143
|
+
'[EarlyErrorCapture]',
|
|
144
|
+
'2836930shcHOs',
|
|
145
|
+
'filename',
|
|
146
|
+
'unhandledrejection',
|
|
147
|
+
'map',
|
|
148
|
+
'querySelector',
|
|
149
|
+
'stack',
|
|
150
|
+
'Vite\x20error',
|
|
151
|
+
'push',
|
|
152
|
+
'addEventListener',
|
|
153
|
+
'2301FTEyID',
|
|
154
|
+
'prototype',
|
|
155
|
+
'reason',
|
|
156
|
+
'indexOf',
|
|
157
|
+
'trim',
|
|
158
|
+
'135492SKEjin',
|
|
159
|
+
'lineNumber',
|
|
160
|
+
'__earlyErrors',
|
|
161
|
+
'apply',
|
|
162
|
+
'77906UQSxhx',
|
|
163
|
+
'2428FoaSHg',
|
|
164
|
+
'javascript',
|
|
165
|
+
'5220660SMjKxQ',
|
|
166
|
+
'colno',
|
|
167
|
+
'message',
|
|
168
|
+
'slice',
|
|
169
|
+
'2314215VITTAJ',
|
|
170
|
+
'console',
|
|
171
|
+
'forEach',
|
|
172
|
+
'columnNumber',
|
|
173
|
+
'lineno',
|
|
174
|
+
'observe',
|
|
175
|
+
'fileName',
|
|
176
|
+
'.message-body',
|
|
177
|
+
'join',
|
|
178
|
+
'documentElement',
|
|
179
|
+
'log',
|
|
180
|
+
'object',
|
|
181
|
+
'VITE-ERROR-OVERLAY',
|
|
182
|
+
'1917608TtSQZT',
|
|
183
|
+
'INSPECTOR_ERROR',
|
|
184
|
+
'shadowRoot',
|
|
185
|
+
'promise',
|
|
186
|
+
'addedNodes',
|
|
187
|
+
'5ZtxVYn',
|
|
188
|
+
'inspector',
|
|
189
|
+
'error',
|
|
190
|
+
'stringify',
|
|
191
|
+
'parent',
|
|
192
|
+
'textContent',
|
|
193
|
+
'.file',
|
|
194
|
+
'24ybIoAN',
|
|
241
195
|
];
|
|
242
196
|
_0x24c0 = function () {
|
|
243
197
|
return _0x29fe81;
|
|
@@ -249,18 +203,12 @@
|
|
|
249
203
|
|
|
250
204
|
<!-- SEO -->
|
|
251
205
|
<title>Site Name</title>
|
|
252
|
-
<meta
|
|
253
|
-
name="description"
|
|
254
|
-
content="Your site description"
|
|
255
|
-
/>
|
|
206
|
+
<meta name="description" content="Your site description" />
|
|
256
207
|
<meta name="author" content="Site Name" />
|
|
257
208
|
|
|
258
209
|
<!-- Open Graph -->
|
|
259
210
|
<meta property="og:title" content="Site Name" />
|
|
260
|
-
<meta
|
|
261
|
-
property="og:description"
|
|
262
|
-
content="Your site description"
|
|
263
|
-
/>
|
|
211
|
+
<meta property="og:description" content="Your site description" />
|
|
264
212
|
<meta property="og:type" content="website" />
|
|
265
213
|
<meta property="og:image" content="" />
|
|
266
214
|
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"title": "Contact Us",
|
|
3
|
-
"getInTouch": "Get in Touch",
|
|
4
|
-
"emailUs": "Email Us",
|
|
5
|
-
"callUs": "Call Us",
|
|
6
|
-
"visitUs": "Visit Us",
|
|
7
|
-
"businessHours": "Business Hours",
|
|
8
|
-
"sendMessage": "Send us a Message",
|
|
9
|
-
"formNotAvailable": "Form is not available at the moment.",
|
|
10
|
-
"fullName": "Full Name",
|
|
11
|
-
"emailAddress": "Email Address",
|
|
12
|
-
"phoneNumber": "Phone Number",
|
|
13
|
-
"subject": "Subject",
|
|
14
|
-
"message": "Message",
|
|
15
|
-
"submit": "Send Message",
|
|
16
|
-
"sending": "Sending...",
|
|
17
|
-
"success": "Thank you for your message! We will get back to you soon.",
|
|
18
|
-
"error": "Failed to send message. Please try again later.",
|
|
19
|
-
"description": "Have questions about our content or need support? We're here to help! Get in touch with us through any of the methods below.",
|
|
20
|
-
"email": "Email",
|
|
21
|
-
"phone": "Phone",
|
|
22
|
-
"address": "Address",
|
|
23
|
-
"fullNamePlaceholder": "Your full name",
|
|
24
|
-
"emailPlaceholder": "your@email.com",
|
|
25
|
-
"phonePlaceholder": "+1 (555) 123-4567",
|
|
26
|
-
"subjectPlaceholder": "What is this regarding?",
|
|
27
|
-
"messagePlaceholder": "Tell us how we can help you...",
|
|
28
|
-
"loading": "Loading contact information...",
|
|
29
|
-
"emailResponse": "We typically respond within 24 hours",
|
|
30
|
-
"needSupport": "Need Support?",
|
|
31
|
-
"supportDescription": "For technical support or content inquiries, contact our dedicated support team:",
|
|
32
|
-
"supportEmail": "Support Email",
|
|
33
|
-
"monday_friday": "Monday - Friday",
|
|
34
|
-
"saturday": "Saturday",
|
|
35
|
-
"sunday": "Sunday",
|
|
36
|
-
"closed": "Closed",
|
|
37
|
-
"am": "AM",
|
|
38
|
-
"pm": "PM"
|
|
39
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"company": "Company",
|
|
3
|
-
"aboutUs": "About Us",
|
|
4
|
-
"contactUs": "Contact Us",
|
|
5
|
-
"getInTouch": "Get in Touch",
|
|
6
|
-
"description": "Discover sustainable, high-quality products that make a difference. Shop with purpose, live with intention.",
|
|
7
|
-
"allRightsReserved": "All rights reserved.",
|
|
8
|
-
"privacyPolicy": "Privacy Policy",
|
|
9
|
-
"termsOfService": "Terms of Service",
|
|
10
|
-
"cookiePolicy": "Cookie Policy",
|
|
11
|
-
"copyright": "© 2026 {{siteName}}. All rights reserved."
|
|
12
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"title": "Forgot Password",
|
|
3
|
-
"descriptionRequest": "Enter your username and we'll send you a code to reset your password.",
|
|
4
|
-
"descriptionReset": "Enter the code sent to your email and your new password.",
|
|
5
|
-
"cardTitleRequest": "Request Reset Code",
|
|
6
|
-
"cardTitleReset": "Reset Password",
|
|
7
|
-
"cardDescRequest": "Step 1 of 2: Request a reset code",
|
|
8
|
-
"cardDescReset": "Step 2 of 2: Enter code and new password",
|
|
9
|
-
"username": "Username",
|
|
10
|
-
"usernamePlaceholder": "Enter your username",
|
|
11
|
-
"code": "Reset Code",
|
|
12
|
-
"codePlaceholder": "Enter 6-digit code",
|
|
13
|
-
"newPassword": "New Password",
|
|
14
|
-
"newPasswordPlaceholder": "Enter new password",
|
|
15
|
-
"confirmPassword": "Confirm Password",
|
|
16
|
-
"confirmPasswordPlaceholder": "Confirm new password",
|
|
17
|
-
"passwordRequirements": "At least 8 characters, 1 letter and 1 number",
|
|
18
|
-
"sendCode": "Send Reset Code",
|
|
19
|
-
"sending": "Sending...",
|
|
20
|
-
"resetPassword": "Reset Password",
|
|
21
|
-
"resetting": "Resetting...",
|
|
22
|
-
"backToLogin": "Back to Login",
|
|
23
|
-
"changeUsername": "Change username",
|
|
24
|
-
"resendCode": "Resend code",
|
|
25
|
-
"codeFor": "Reset code for:",
|
|
26
|
-
"codeSentTitle": "Code Sent!",
|
|
27
|
-
"codeSentDesc": "A password reset code has been sent to your email.",
|
|
28
|
-
"resetSuccessTitle": "Password Reset!",
|
|
29
|
-
"resetSuccessDesc": "Your password has been successfully reset.",
|
|
30
|
-
"successTitle": "Password Reset Successfully!",
|
|
31
|
-
"successDescription": "Your password has been changed. You can now login with your new password.",
|
|
32
|
-
"goToLogin": "Go to Login",
|
|
33
|
-
"passwordMismatch": "Passwords do not match",
|
|
34
|
-
"errorTitle": "Error",
|
|
35
|
-
"errorGeneric": "Failed to send reset code. Please try again.",
|
|
36
|
-
"errorResetGeneric": "Failed to reset password. Please try again."
|
|
37
|
-
}
|