@promakeai/cli 0.1.0 → 0.1.2
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 +31 -31
- package/dist/registry/docs/google-adsense.md +38 -0
- package/dist/registry/docs/youtube-embed.md +42 -0
- package/dist/registry/google-adsense.json +43 -0
- package/dist/registry/header-mega.json +1 -1
- package/dist/registry/index.json +3 -1
- package/dist/registry/youtube-embed.json +40 -0
- package/package.json +2 -2
- package/template/.prettierrc +12 -1
- package/template/index.html +3 -3
- package/template/package.json +1 -1
- package/template/src/constants/constants.json +4 -4
- package/template/src/lib/env.ts +20 -0
- package/template/src/main.tsx +1 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Google AdSense
|
|
2
|
+
|
|
3
|
+
Google AdSense component for displaying responsive ads. Supports various ad formats including display, fluid, rectangle, vertical, and horizontal. Includes development placeholder and automatic script loading.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| Target | Type |
|
|
8
|
+
|--------|------|
|
|
9
|
+
| `$modules$/google-adsense/index.ts` | index |
|
|
10
|
+
| `$modules$/google-adsense/google-adsense.tsx` | component |
|
|
11
|
+
| `$modules$/google-adsense/lang/en.json` | lang |
|
|
12
|
+
| `$modules$/google-adsense/lang/tr.json` | lang |
|
|
13
|
+
|
|
14
|
+
## Exports
|
|
15
|
+
|
|
16
|
+
**Components/Functions:** `GoogleAdsense`
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { GoogleAdsense } from '@/modules/google-adsense';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
import { GoogleAdsense } from '@/modules/google-adsense';
|
|
26
|
+
|
|
27
|
+
// Basic usage
|
|
28
|
+
<GoogleAdsense slot="1234567890" />
|
|
29
|
+
|
|
30
|
+
// With all options
|
|
31
|
+
<GoogleAdsense
|
|
32
|
+
client="ca-pub-xxxxxxxx"
|
|
33
|
+
slot="1234567890"
|
|
34
|
+
format="auto"
|
|
35
|
+
responsive={true}
|
|
36
|
+
className="my-4"
|
|
37
|
+
/>
|
|
38
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# YouTube Embed
|
|
2
|
+
|
|
3
|
+
Responsive YouTube video embed component with customizable options. Supports autoplay, mute, loop, custom start time, and multiple aspect ratios. Includes loading state and error handling.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| Target | Type |
|
|
8
|
+
|--------|------|
|
|
9
|
+
| `$modules$/youtube-embed/index.ts` | index |
|
|
10
|
+
| `$modules$/youtube-embed/youtube-embed.tsx` | component |
|
|
11
|
+
| `$modules$/youtube-embed/lang/en.json` | lang |
|
|
12
|
+
| `$modules$/youtube-embed/lang/tr.json` | lang |
|
|
13
|
+
|
|
14
|
+
## Exports
|
|
15
|
+
|
|
16
|
+
**Components/Functions:** `YouTubeEmbed`
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { YouTubeEmbed } from '@/modules/youtube-embed';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
import { YouTubeEmbed } from '@/modules/youtube-embed';
|
|
26
|
+
|
|
27
|
+
// Basic usage
|
|
28
|
+
<YouTubeEmbed videoId="dQw4w9WgXcQ" />
|
|
29
|
+
|
|
30
|
+
// With all options
|
|
31
|
+
<YouTubeEmbed
|
|
32
|
+
videoId="dQw4w9WgXcQ"
|
|
33
|
+
title="Video Title"
|
|
34
|
+
autoplay={false}
|
|
35
|
+
mute={false}
|
|
36
|
+
loop={false}
|
|
37
|
+
controls={true}
|
|
38
|
+
start={30}
|
|
39
|
+
aspectRatio="16/9"
|
|
40
|
+
className="rounded-xl shadow-lg"
|
|
41
|
+
/>
|
|
42
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "google-adsense",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "Google AdSense",
|
|
5
|
+
"description": "Google AdSense component for displaying responsive ads. Supports various ad formats including display, fluid, rectangle, vertical, and horizontal. Includes development placeholder and automatic script loading.",
|
|
6
|
+
"registryDependencies": [],
|
|
7
|
+
"usage": "import { GoogleAdsense } from '@/modules/google-adsense';\n\n// Basic usage\n<GoogleAdsense slot=\"1234567890\" />\n\n// With all options\n<GoogleAdsense\n client=\"ca-pub-xxxxxxxx\"\n slot=\"1234567890\"\n format=\"auto\"\n responsive={true}\n className=\"my-4\"\n/>",
|
|
8
|
+
"files": [
|
|
9
|
+
{
|
|
10
|
+
"path": "google-adsense/index.ts",
|
|
11
|
+
"type": "registry:index",
|
|
12
|
+
"target": "$modules$/google-adsense/index.ts",
|
|
13
|
+
"content": "export * from './google-adsense';\r\n"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "google-adsense/google-adsense.tsx",
|
|
17
|
+
"type": "registry:component",
|
|
18
|
+
"target": "$modules$/google-adsense/google-adsense.tsx",
|
|
19
|
+
"content": "import { useEffect, useRef, useState } from \"react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ndeclare global {\r\n interface Window {\r\n adsbygoogle: unknown[];\r\n }\r\n}\r\n\r\ninterface GoogleAdsenseProps {\r\n client?: string;\r\n slot: string;\r\n format?: \"auto\" | \"fluid\" | \"rectangle\" | \"vertical\" | \"horizontal\";\r\n responsive?: boolean;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n testMode?: boolean;\r\n}\r\n\r\nconst ADSENSE_SCRIPT_ID = \"google-adsense-script\";\r\n\r\nexport function GoogleAdsense({\r\n client,\r\n slot,\r\n format = \"auto\",\r\n responsive = true,\r\n className,\r\n style,\r\n testMode = false,\r\n}: GoogleAdsenseProps) {\r\n const { t } = useTranslation(\"google-adsense\");\r\n const adRef = useRef<HTMLModElement>(null);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n const [hasError, setHasError] = useState(false);\r\n\r\n const adClient = client || import.meta.env.VITE_ADSENSE_CLIENT;\r\n const isDev = import.meta.env.DEV;\r\n\r\n // Load AdSense script once\r\n useEffect(() => {\r\n if (isDev && !testMode) return;\r\n if (!adClient) {\r\n setHasError(true);\r\n return;\r\n }\r\n\r\n const existingScript = document.getElementById(ADSENSE_SCRIPT_ID);\r\n if (existingScript) {\r\n setIsLoaded(true);\r\n return;\r\n }\r\n\r\n const script = document.createElement(\"script\");\r\n script.id = ADSENSE_SCRIPT_ID;\r\n script.src = `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${adClient}`;\r\n script.async = true;\r\n script.crossOrigin = \"anonymous\";\r\n\r\n script.onload = () => setIsLoaded(true);\r\n script.onerror = () => setHasError(true);\r\n\r\n document.head.appendChild(script);\r\n }, [adClient, isDev, testMode]);\r\n\r\n // Push ad when script is loaded\r\n useEffect(() => {\r\n if (!isLoaded || !adRef.current || (isDev && !testMode)) return;\r\n\r\n const adElement = adRef.current;\r\n if (adElement.dataset.adStatus) return; // Already initialized\r\n\r\n try {\r\n (window.adsbygoogle = window.adsbygoogle || []).push({});\r\n } catch (err) {\r\n console.error(\"AdSense error:\", err);\r\n setHasError(true);\r\n }\r\n }, [isLoaded, isDev, testMode]);\r\n\r\n // Development placeholder\r\n if (isDev && !testMode) {\r\n return (\r\n <div\r\n className={cn(\r\n \"flex items-center justify-center bg-muted/50 border-2 border-dashed border-muted-foreground/25 rounded-lg\",\r\n className\r\n )}\r\n style={{ minHeight: \"90px\", ...style }}\r\n >\r\n <div className=\"text-center p-4\">\r\n <p className=\"text-sm font-medium text-muted-foreground\">\r\n {t(\"devPlaceholder\", \"AdSense Placeholder\")}\r\n </p>\r\n <p className=\"text-xs text-muted-foreground/70 mt-1\">\r\n {t(\"devNote\", \"Ads will appear in production\")}\r\n </p>\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n // Error state\r\n if (hasError || !adClient) {\r\n return (\r\n <div\r\n className={cn(\r\n \"flex items-center justify-center bg-muted/30 rounded-lg\",\r\n className\r\n )}\r\n style={{ minHeight: \"90px\", ...style }}\r\n >\r\n <p className=\"text-xs text-muted-foreground\">\r\n {t(\"error\", \"Ad could not be loaded\")}\r\n </p>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <ins\r\n ref={adRef}\r\n className={cn(\"adsbygoogle block\", className)}\r\n style={{ display: \"block\", minHeight: \"90px\", ...style }}\r\n data-ad-client={adClient}\r\n data-ad-slot={slot}\r\n data-ad-format={format}\r\n data-full-width-responsive={responsive ? \"true\" : \"false\"}\r\n {...(testMode && { \"data-adtest\": \"on\" })}\r\n />\r\n );\r\n}\r\n"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "google-adsense/lang/en.json",
|
|
23
|
+
"type": "registry:lang",
|
|
24
|
+
"target": "$modules$/google-adsense/lang/en.json",
|
|
25
|
+
"content": "{\r\n \"devPlaceholder\": \"AdSense Placeholder\",\r\n \"devNote\": \"Ads will appear in production\",\r\n \"error\": \"Ad could not be loaded\"\r\n}\r\n"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "google-adsense/lang/tr.json",
|
|
29
|
+
"type": "registry:lang",
|
|
30
|
+
"target": "$modules$/google-adsense/lang/tr.json",
|
|
31
|
+
"content": "{\r\n \"devPlaceholder\": \"AdSense Yer Tutucusu\",\r\n \"devNote\": \"Reklamlar production'da görünecek\",\r\n \"error\": \"Reklam yüklenemedi\"\r\n}\r\n"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"envVars": {
|
|
35
|
+
"VITE_ADSENSE_CLIENT": ""
|
|
36
|
+
},
|
|
37
|
+
"exports": {
|
|
38
|
+
"types": [],
|
|
39
|
+
"variables": [
|
|
40
|
+
"GoogleAdsense"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"path": "header-mega/header-mega.tsx",
|
|
24
24
|
"type": "registry:component",
|
|
25
25
|
"target": "$modules$/header-mega/header-mega.tsx",
|
|
26
|
-
"content": "import { Link } from \"react-router\";\r\nimport { Book, Menu, Sunset, Trees, Zap } from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport {\r\n Accordion,\r\n AccordionContent,\r\n AccordionItem,\r\n AccordionTrigger,\r\n} from \"@/components/ui/accordion\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n NavigationMenu,\r\n NavigationMenuContent,\r\n NavigationMenuItem,\r\n NavigationMenuLink,\r\n NavigationMenuList,\r\n NavigationMenuTrigger,\r\n} from \"@/components/ui/navigation-menu\";\r\nimport {\r\n Sheet,\r\n SheetContent,\r\n SheetHeader,\r\n SheetTitle,\r\n SheetTrigger,\r\n} from \"@/components/ui/sheet\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface MenuItem {\r\n title: string;\r\n url: string;\r\n description?: string;\r\n icon?: React.ReactNode;\r\n items?: MenuItem[];\r\n}\r\n\r\ninterface HeaderMegaProps {\r\n className?: string;\r\n}\r\n\r\nexport function HeaderMega({ className }: HeaderMegaProps) {\r\n const { t } = useTranslation(\"header-mega\");\r\n\r\n const menu: MenuItem[] = [\r\n { title: t(\"home\", \"Home\"), url: \"/\" },\r\n {\r\n title: t(\"products\", \"Products\"),\r\n url: \"/products\",\r\n items: [\r\n {\r\n title: t(\"allProducts\", \"All Products\"),\r\n description: t(\r\n \"allProductsDesc\",\r\n \"Browse our complete product catalog\"\r\n ),\r\n icon: <Book className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/products\",\r\n },\r\n {\r\n title: t(\"featured\", \"Featured\"),\r\n description: t(\r\n \"featuredDesc\",\r\n \"Our handpicked selection of top products\"\r\n ),\r\n icon: <Trees className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/products?featured=true\",\r\n },\r\n {\r\n title: t(\"newArrivals\", \"New Arrivals\"),\r\n description: t(\"newArrivalsDesc\", \"Check out the latest additions\"),\r\n icon: <Sunset className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/products?is_new=true\",\r\n },\r\n {\r\n title: t(\"onSale\", \"On Sale\"),\r\n description: t(\"onSaleDesc\", \"Great deals and special offers\"),\r\n icon: <Zap className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/products?on_sale=true\",\r\n },\r\n ],\r\n },\r\n {\r\n title: t(\"company\", \"Company\"),\r\n url: \"#\",\r\n items: [\r\n {\r\n title: t(\"aboutUs\", \"About Us\"),\r\n description: t(\r\n \"aboutUsDesc\",\r\n \"Learn more about our story and mission\"\r\n ),\r\n icon: <Trees className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/about\",\r\n },\r\n {\r\n title: t(\"contact\", \"Contact\"),\r\n description: t(\"contactDesc\", \"Get in touch with our team\"),\r\n icon: <Sunset className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/contact\",\r\n },\r\n {\r\n title: t(\"blog\", \"Blog\"),\r\n description: t(\"blogDesc\", \"Read our latest articles and updates\"),\r\n icon: <Book className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/blog\",\r\n },\r\n ],\r\n },\r\n ];\r\n\r\n const renderMenuItem = (item: MenuItem) => {\r\n if (item.items) {\r\n return (\r\n <NavigationMenuItem key={item.title}>\r\n <NavigationMenuTrigger>{item.title}</NavigationMenuTrigger>\r\n <NavigationMenuContent className=\"z-50 bg-popover text-popover-foreground\">\r\n {item.items.map((subItem) => (\r\n <NavigationMenuLink asChild key={subItem.title} className=\"w-80\">\r\n <SubMenuLink item={subItem} />\r\n </NavigationMenuLink>\r\n ))}\r\n </NavigationMenuContent>\r\n </NavigationMenuItem>\r\n );\r\n }\r\n\r\n return (\r\n <NavigationMenuItem key={item.title}>\r\n <NavigationMenuLink asChild>\r\n <Link\r\n to={item.url}\r\n className=\"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-muted hover:text-accent-foreground\"\r\n >\r\n {item.title}\r\n </Link>\r\n </NavigationMenuLink>\r\n </NavigationMenuItem>\r\n );\r\n };\r\n\r\n const renderMobileMenuItem = (item: MenuItem) => {\r\n if (item.items) {\r\n return (\r\n <AccordionItem\r\n key={item.title}\r\n value={item.title}\r\n className=\"border-b-0\"\r\n >\r\n <AccordionTrigger className=\"text-md py-0 font-semibold hover:no-underline\">\r\n {item.title}\r\n </AccordionTrigger>\r\n <AccordionContent className=\"mt-2\">\r\n {item.items.map((subItem) => (\r\n <SubMenuLink key={subItem.title} item={subItem} />\r\n ))}\r\n </AccordionContent>\r\n </AccordionItem>\r\n );\r\n }\r\n\r\n return (\r\n <Link key={item.title} to={item.url} className=\"text-md font-semibold\">\r\n {item.title}\r\n </Link>\r\n );\r\n };\r\n\r\n return (\r\n <header\r\n className={cn(\r\n \"relative z-50 py-4 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60\",\r\n className\r\n )}\r\n >\r\n <div className=\"container max-w-7xl mx-auto px-4\">\r\n {/* Desktop Menu */}\r\n <nav className=\"hidden items-center justify-between lg:flex\">\r\n <div className=\"flex items-center gap-6\">\r\n {/* Logo */}\r\n <Link to=\"/\" className=\"flex items-center gap-2\">\r\n <img\r\n src=\"/images/logo.png\"\r\n className=\"h-8 w-auto\"\r\n alt={constants.site.name}\r\n onError={(e) => {\r\n e.currentTarget.style.display = \"none\";\r\n }}\r\n />\r\n <span className=\"text-lg font-semibold tracking-tight\">\r\n {constants.site.name}\r\n </span>\r\n </Link>\r\n <div className=\"flex items-center\">\r\n <NavigationMenu>\r\n <NavigationMenuList>\r\n {menu.map((item) => renderMenuItem(item))}\r\n </NavigationMenuList>\r\n </NavigationMenu>\r\n </div>\r\n </div>\r\n <div className=\"flex gap-2\">\r\n <Button asChild variant=\"outline\" size=\"sm\">\r\n <Link to=\"/login\">{t(\"login\", \"Login\")}</Link>\r\n </Button>\r\n <Button asChild size=\"sm\">\r\n <Link to=\"/register\">{t(\"signup\", \"Sign up\")}</Link>\r\n </Button>\r\n </div>\r\n </nav>\r\n\r\n {/* Mobile Menu */}\r\n <div className=\"block lg:hidden\">\r\n <div className=\"flex items-center justify-between\">\r\n {/* Logo */}\r\n <Link to=\"/\" className=\"flex items-center gap-2\">\r\n <img\r\n src=\"/images/logo.png\"\r\n className=\"h-8 w-auto\"\r\n alt={constants.site.name}\r\n onError={(e) => {\r\n e.currentTarget.style.display = \"none\";\r\n }}\r\n />\r\n <span className=\"text-lg font-semibold tracking-tight\">\r\n {constants.site.name}\r\n </span>\r\n </Link>\r\n <Sheet>\r\n <SheetTrigger asChild>\r\n <Button variant=\"outline\" size=\"icon\">\r\n <Menu className=\"h-4 w-4\" />\r\n </Button>\r\n </SheetTrigger>\r\n <SheetContent className=\"z-50 overflow-y-auto px-6\">\r\n <SheetHeader>\r\n <SheetTitle>\r\n <Link to=\"/\" className=\"flex items-center gap-2\">\r\n <img\r\n src=\"/images/logo.png\"\r\n className=\"h-8 w-auto\"\r\n alt={constants.site.name}\r\n onError={(e) => {\r\n e.currentTarget.style.display = \"none\";\r\n }}\r\n />\r\n </Link>\r\n </SheetTitle>\r\n </SheetHeader>\r\n <div className=\"flex flex-col gap-6 p-4\">\r\n <Accordion\r\n type=\"single\"\r\n collapsible\r\n className=\"flex w-full flex-col gap-4\"\r\n >\r\n {menu.map((item) => renderMobileMenuItem(item))}\r\n </Accordion>\r\n\r\n <div className=\"flex flex-col gap-3\">\r\n <Button asChild variant=\"outline\">\r\n <Link to=\"/login\">{t(\"login\", \"Login\")}</Link>\r\n </Button>\r\n <Button asChild>\r\n <Link to=\"/register\">{t(\"signup\", \"Sign up\")}</Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </SheetContent>\r\n </Sheet>\r\n </div>\r\n </div>\r\n </div>\r\n </header>\r\n );\r\n}\r\n\r\nfunction SubMenuLink({ item }: { item: MenuItem }) {\r\n return (\r\n <Link\r\n to={item.url}\r\n className=\"flex min-w-80 flex-row gap-4 rounded-md p-3 leading-none no-underline transition-colors outline-none select-none hover:bg-muted hover:text-accent-foreground\"\r\n >\r\n <div className=\"text-foreground\">{item.icon}</div>\r\n <div>\r\n <div className=\"text-sm font-semibold\">{item.title}</div>\r\n {item.description && (\r\n <p className=\"text-sm leading-snug text-muted-foreground\">\r\n {item.description}\r\n </p>\r\n )}\r\n </div>\r\n </Link>\r\n );\r\n}\r\n"
|
|
26
|
+
"content": "import { Link } from \"react-router\";\r\nimport { Book, Menu, Sunset, Trees, Zap } from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport {\r\n Accordion,\r\n AccordionContent,\r\n AccordionItem,\r\n AccordionTrigger,\r\n} from \"@/components/ui/accordion\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n NavigationMenu,\r\n NavigationMenuContent,\r\n NavigationMenuItem,\r\n NavigationMenuLink,\r\n NavigationMenuList,\r\n NavigationMenuTrigger,\r\n} from \"@/components/ui/navigation-menu\";\r\nimport {\r\n Sheet,\r\n SheetContent,\r\n SheetHeader,\r\n SheetTitle,\r\n SheetTrigger,\r\n} from \"@/components/ui/sheet\";\r\nimport constants from \"@/constants/constants.json\";\r\n\r\ninterface MenuItem {\r\n title: string;\r\n url: string;\r\n description?: string;\r\n icon?: React.ReactNode;\r\n items?: MenuItem[];\r\n}\r\n\r\ninterface HeaderMegaProps {\r\n className?: string;\r\n}\r\n\r\nexport function HeaderMega({ className }: HeaderMegaProps) {\r\n const { t } = useTranslation(\"header-mega\");\r\n\r\n const menu: MenuItem[] = [\r\n { title: t(\"home\", \"Home\"), url: \"/\" },\r\n {\r\n title: t(\"products\", \"Products\"),\r\n url: \"/products\",\r\n items: [\r\n {\r\n title: t(\"allProducts\", \"All Products\"),\r\n description: t(\r\n \"allProductsDesc\",\r\n \"Browse our complete product catalog\"\r\n ),\r\n icon: <Book className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/products\",\r\n },\r\n {\r\n title: t(\"featured\", \"Featured\"),\r\n description: t(\r\n \"featuredDesc\",\r\n \"Our handpicked selection of top products\"\r\n ),\r\n icon: <Trees className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/products?featured=true\",\r\n },\r\n {\r\n title: t(\"newArrivals\", \"New Arrivals\"),\r\n description: t(\"newArrivalsDesc\", \"Check out the latest additions\"),\r\n icon: <Sunset className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/products?is_new=true\",\r\n },\r\n {\r\n title: t(\"onSale\", \"On Sale\"),\r\n description: t(\"onSaleDesc\", \"Great deals and special offers\"),\r\n icon: <Zap className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/products?on_sale=true\",\r\n },\r\n ],\r\n },\r\n {\r\n title: t(\"company\", \"Company\"),\r\n url: \"#\",\r\n items: [\r\n {\r\n title: t(\"aboutUs\", \"About Us\"),\r\n description: t(\r\n \"aboutUsDesc\",\r\n \"Learn more about our story and mission\"\r\n ),\r\n icon: <Trees className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/about\",\r\n },\r\n {\r\n title: t(\"contact\", \"Contact\"),\r\n description: t(\"contactDesc\", \"Get in touch with our team\"),\r\n icon: <Sunset className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/contact\",\r\n },\r\n {\r\n title: t(\"blog\", \"Blog\"),\r\n description: t(\"blogDesc\", \"Read our latest articles and updates\"),\r\n icon: <Book className=\"h-5 w-5 shrink-0\" />,\r\n url: \"/blog\",\r\n },\r\n ],\r\n },\r\n ];\r\n\r\n const renderMenuItem = (item: MenuItem) => {\r\n if (item.items) {\r\n return (\r\n <NavigationMenuItem key={item.title}>\r\n <NavigationMenuTrigger>{item.title}</NavigationMenuTrigger>\r\n <NavigationMenuContent className=\"z-50 bg-popover text-popover-foreground\">\r\n {item.items.map((subItem) => (\r\n <NavigationMenuLink asChild key={subItem.title} className=\"w-80\">\r\n <SubMenuLink item={subItem} />\r\n </NavigationMenuLink>\r\n ))}\r\n </NavigationMenuContent>\r\n </NavigationMenuItem>\r\n );\r\n }\r\n\r\n return (\r\n <NavigationMenuItem key={item.title}>\r\n <NavigationMenuLink asChild>\r\n <Link\r\n to={item.url}\r\n className=\"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-muted hover:text-accent-foreground\"\r\n >\r\n {item.title}\r\n </Link>\r\n </NavigationMenuLink>\r\n </NavigationMenuItem>\r\n );\r\n };\r\n\r\n const renderMobileMenuItem = (item: MenuItem) => {\r\n if (item.items) {\r\n return (\r\n <AccordionItem\r\n key={item.title}\r\n value={item.title}\r\n className=\"border-b-0\"\r\n >\r\n <AccordionTrigger className=\"text-md py-0 font-semibold hover:no-underline\">\r\n {item.title}\r\n </AccordionTrigger>\r\n <AccordionContent className=\"mt-2\">\r\n {item.items.map((subItem) => (\r\n <SubMenuLink key={subItem.title} item={subItem} />\r\n ))}\r\n </AccordionContent>\r\n </AccordionItem>\r\n );\r\n }\r\n\r\n return (\r\n <Link key={item.title} to={item.url} className=\"text-md font-semibold\">\r\n {item.title}\r\n </Link>\r\n );\r\n };\r\n\r\n return (\r\n <header\r\n className={cn(\r\n \"relative z-50 py-4 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 overflow-visible\",\r\n className\r\n )}\r\n >\r\n <div className=\"container max-w-7xl mx-auto px-4\">\r\n {/* Desktop Menu */}\r\n <nav className=\"hidden items-center justify-between lg:flex overflow-visible\">\r\n <div className=\"flex items-center gap-6\">\r\n {/* Logo */}\r\n <Link to=\"/\" className=\"flex items-center gap-2\">\r\n <img\r\n src=\"/images/logo.png\"\r\n className=\"h-8 w-auto\"\r\n alt={constants.site.name}\r\n onError={(e) => {\r\n e.currentTarget.style.display = \"none\";\r\n }}\r\n />\r\n <span className=\"text-lg font-semibold tracking-tight\">\r\n {constants.site.name}\r\n </span>\r\n </Link>\r\n <div className=\"flex items-center\">\r\n <NavigationMenu>\r\n <NavigationMenuList>\r\n {menu.map((item) => renderMenuItem(item))}\r\n </NavigationMenuList>\r\n </NavigationMenu>\r\n </div>\r\n </div>\r\n <div className=\"flex gap-2\">\r\n <Button asChild variant=\"outline\" size=\"sm\">\r\n <Link to=\"/login\">{t(\"login\", \"Login\")}</Link>\r\n </Button>\r\n <Button asChild size=\"sm\">\r\n <Link to=\"/register\">{t(\"signup\", \"Sign up\")}</Link>\r\n </Button>\r\n </div>\r\n </nav>\r\n\r\n {/* Mobile Menu */}\r\n <div className=\"block lg:hidden\">\r\n <div className=\"flex items-center justify-between\">\r\n {/* Logo */}\r\n <Link to=\"/\" className=\"flex items-center gap-2\">\r\n <img\r\n src=\"/images/logo.png\"\r\n className=\"h-8 w-auto\"\r\n alt={constants.site.name}\r\n onError={(e) => {\r\n e.currentTarget.style.display = \"none\";\r\n }}\r\n />\r\n <span className=\"text-lg font-semibold tracking-tight\">\r\n {constants.site.name}\r\n </span>\r\n </Link>\r\n <Sheet>\r\n <SheetTrigger asChild>\r\n <Button variant=\"outline\" size=\"icon\">\r\n <Menu className=\"h-4 w-4\" />\r\n </Button>\r\n </SheetTrigger>\r\n <SheetContent className=\"z-50 overflow-y-auto px-6\">\r\n <SheetHeader>\r\n <SheetTitle>\r\n <Link to=\"/\" className=\"flex items-center gap-2\">\r\n <img\r\n src=\"/images/logo.png\"\r\n className=\"h-8 w-auto\"\r\n alt={constants.site.name}\r\n onError={(e) => {\r\n e.currentTarget.style.display = \"none\";\r\n }}\r\n />\r\n </Link>\r\n </SheetTitle>\r\n </SheetHeader>\r\n <div className=\"flex flex-col gap-6 p-4\">\r\n <Accordion\r\n type=\"single\"\r\n collapsible\r\n className=\"flex w-full flex-col gap-4\"\r\n >\r\n {menu.map((item) => renderMobileMenuItem(item))}\r\n </Accordion>\r\n\r\n <div className=\"flex flex-col gap-3\">\r\n <Button asChild variant=\"outline\">\r\n <Link to=\"/login\">{t(\"login\", \"Login\")}</Link>\r\n </Button>\r\n <Button asChild>\r\n <Link to=\"/register\">{t(\"signup\", \"Sign up\")}</Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </SheetContent>\r\n </Sheet>\r\n </div>\r\n </div>\r\n </div>\r\n </header>\r\n );\r\n}\r\n\r\nfunction SubMenuLink({ item }: { item: MenuItem }) {\r\n return (\r\n <Link\r\n to={item.url}\r\n className=\"flex min-w-80 flex-row gap-4 rounded-md p-3 leading-none no-underline transition-colors outline-none select-none hover:bg-muted hover:text-accent-foreground\"\r\n >\r\n <div className=\"text-foreground\">{item.icon}</div>\r\n <div>\r\n <div className=\"text-sm font-semibold\">{item.title}</div>\r\n {item.description && (\r\n <p className=\"text-sm leading-snug text-muted-foreground\">\r\n {item.description}\r\n </p>\r\n )}\r\n </div>\r\n </Link>\r\n );\r\n}\r\n"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"path": "header-mega/lang/en.json",
|
package/dist/registry/index.json
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"footer-detailed",
|
|
41
41
|
"footer-minimal",
|
|
42
42
|
"forgot-password-page",
|
|
43
|
+
"google-adsense",
|
|
43
44
|
"google-map",
|
|
44
45
|
"header-centered-pill",
|
|
45
46
|
"header-ecommerce",
|
|
@@ -89,5 +90,6 @@
|
|
|
89
90
|
"testimonials-carousel",
|
|
90
91
|
"testimonials-grid",
|
|
91
92
|
"timeline-section",
|
|
92
|
-
"video-hero"
|
|
93
|
+
"video-hero",
|
|
94
|
+
"youtube-embed"
|
|
93
95
|
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "youtube-embed",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "YouTube Embed",
|
|
5
|
+
"description": "Responsive YouTube video embed component with customizable options. Supports autoplay, mute, loop, custom start time, and multiple aspect ratios. Includes loading state and error handling.",
|
|
6
|
+
"registryDependencies": [],
|
|
7
|
+
"usage": "import { YouTubeEmbed } from '@/modules/youtube-embed';\n\n// Basic usage\n<YouTubeEmbed videoId=\"dQw4w9WgXcQ\" />\n\n// With all options\n<YouTubeEmbed\n videoId=\"dQw4w9WgXcQ\"\n title=\"Video Title\"\n autoplay={false}\n mute={false}\n loop={false}\n controls={true}\n start={30}\n aspectRatio=\"16/9\"\n className=\"rounded-xl shadow-lg\"\n/>",
|
|
8
|
+
"files": [
|
|
9
|
+
{
|
|
10
|
+
"path": "youtube-embed/index.ts",
|
|
11
|
+
"type": "registry:index",
|
|
12
|
+
"target": "$modules$/youtube-embed/index.ts",
|
|
13
|
+
"content": "export * from './youtube-embed';\r\n"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "youtube-embed/youtube-embed.tsx",
|
|
17
|
+
"type": "registry:component",
|
|
18
|
+
"target": "$modules$/youtube-embed/youtube-embed.tsx",
|
|
19
|
+
"content": "import { useState } from \"react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Play } from \"lucide-react\";\r\n\r\ninterface YouTubeEmbedProps {\r\n videoId: string;\r\n title?: string;\r\n autoplay?: boolean;\r\n mute?: boolean;\r\n loop?: boolean;\r\n controls?: boolean;\r\n start?: number;\r\n aspectRatio?: \"16/9\" | \"4/3\" | \"1/1\";\r\n className?: string;\r\n}\r\n\r\nexport function YouTubeEmbed({\r\n videoId,\r\n title,\r\n autoplay = false,\r\n mute = false,\r\n loop = false,\r\n controls = true,\r\n start,\r\n aspectRatio = \"16/9\",\r\n className,\r\n}: YouTubeEmbedProps) {\r\n const { t } = useTranslation(\"youtube-embed\");\r\n const [isLoading, setIsLoading] = useState(true);\r\n const [hasError, setHasError] = useState(false);\r\n\r\n // Validate videoId\r\n const isValidVideoId = /^[a-zA-Z0-9_-]{11}$/.test(videoId);\r\n\r\n // Build embed URL with parameters\r\n const buildEmbedUrl = () => {\r\n const params = new URLSearchParams();\r\n\r\n if (autoplay) params.set(\"autoplay\", \"1\");\r\n if (mute) params.set(\"mute\", \"1\");\r\n if (loop) {\r\n params.set(\"loop\", \"1\");\r\n params.set(\"playlist\", videoId);\r\n }\r\n if (!controls) params.set(\"controls\", \"0\");\r\n if (start) params.set(\"start\", start.toString());\r\n\r\n params.set(\"rel\", \"0\");\r\n\r\n const queryString = params.toString();\r\n return `https://www.youtube.com/embed/${videoId}${queryString ? `?${queryString}` : \"\"}`;\r\n };\r\n\r\n const handleLoad = () => {\r\n setIsLoading(false);\r\n };\r\n\r\n const handleError = () => {\r\n setIsLoading(false);\r\n setHasError(true);\r\n };\r\n\r\n // Invalid videoId\r\n if (!isValidVideoId) {\r\n return (\r\n <div\r\n className={cn(\r\n \"flex flex-col items-center justify-center bg-muted rounded-lg border\",\r\n className\r\n )}\r\n style={{ aspectRatio }}\r\n >\r\n <Play className=\"h-12 w-12 text-muted-foreground mb-3\" />\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"invalidId\", \"Invalid video ID\")}\r\n </p>\r\n </div>\r\n );\r\n }\r\n\r\n // Error state\r\n if (hasError) {\r\n return (\r\n <div\r\n className={cn(\r\n \"flex flex-col items-center justify-center bg-muted rounded-lg border\",\r\n className\r\n )}\r\n style={{ aspectRatio }}\r\n >\r\n <Play className=\"h-12 w-12 text-muted-foreground mb-3\" />\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"error\", \"Failed to load video\")}\r\n </p>\r\n <a\r\n href={`https://www.youtube.com/watch?v=${videoId}`}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"text-sm text-primary hover:underline mt-2\"\r\n >\r\n {t(\"watchOnYouTube\", \"Watch on YouTube\")}\r\n </a>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div\r\n className={cn(\"relative rounded-lg overflow-hidden\", className)}\r\n style={{ aspectRatio }}\r\n >\r\n {isLoading && (\r\n <div className=\"absolute inset-0 flex items-center justify-center bg-muted\">\r\n <div className=\"flex flex-col items-center gap-3\">\r\n <div className=\"h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent\" />\r\n <span className=\"text-sm text-muted-foreground\">\r\n {t(\"loading\", \"Loading video...\")}\r\n </span>\r\n </div>\r\n </div>\r\n )}\r\n <iframe\r\n src={buildEmbedUrl()}\r\n width=\"100%\"\r\n height=\"100%\"\r\n style={{ border: 0 }}\r\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\r\n allowFullScreen\r\n loading=\"lazy\"\r\n title={title || t(\"defaultTitle\", \"YouTube video player\")}\r\n onLoad={handleLoad}\r\n onError={handleError}\r\n className={cn(\"absolute inset-0\", isLoading && \"invisible\")}\r\n />\r\n </div>\r\n );\r\n}\r\n"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "youtube-embed/lang/en.json",
|
|
23
|
+
"type": "registry:lang",
|
|
24
|
+
"target": "$modules$/youtube-embed/lang/en.json",
|
|
25
|
+
"content": "{\r\n \"loading\": \"Loading video...\",\r\n \"error\": \"Failed to load video\",\r\n \"invalidId\": \"Invalid video ID\",\r\n \"watchOnYouTube\": \"Watch on YouTube\",\r\n \"defaultTitle\": \"YouTube video player\"\r\n}\r\n"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "youtube-embed/lang/tr.json",
|
|
29
|
+
"type": "registry:lang",
|
|
30
|
+
"target": "$modules$/youtube-embed/lang/tr.json",
|
|
31
|
+
"content": "{\r\n \"loading\": \"Video yükleniyor...\",\r\n \"error\": \"Video yüklenemedi\",\r\n \"invalidId\": \"Geçersiz video ID\",\r\n \"watchOnYouTube\": \"YouTube'da izle\",\r\n \"defaultTitle\": \"YouTube video oynatıcı\"\r\n}\r\n"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
"types": [],
|
|
36
|
+
"variables": [
|
|
37
|
+
"YouTubeEmbed"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
package/package.json
CHANGED
package/template/.prettierrc
CHANGED
package/template/index.html
CHANGED
|
@@ -248,10 +248,10 @@
|
|
|
248
248
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
249
249
|
|
|
250
250
|
<!-- SEO -->
|
|
251
|
-
<title>Site Name
|
|
251
|
+
<title>Site Name</title>
|
|
252
252
|
<meta
|
|
253
253
|
name="description"
|
|
254
|
-
content="
|
|
254
|
+
content="Your site description"
|
|
255
255
|
/>
|
|
256
256
|
<meta name="author" content="Site Name" />
|
|
257
257
|
|
|
@@ -259,7 +259,7 @@
|
|
|
259
259
|
<meta property="og:title" content="Site Name" />
|
|
260
260
|
<meta
|
|
261
261
|
property="og:description"
|
|
262
|
-
content="
|
|
262
|
+
content="Your site description"
|
|
263
263
|
/>
|
|
264
264
|
<meta property="og:type" content="website" />
|
|
265
265
|
<meta property="og:image" content="" />
|
package/template/package.json
CHANGED
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"timeout": 30000
|
|
27
27
|
},
|
|
28
28
|
"seo": {
|
|
29
|
-
"title": "Site Name
|
|
30
|
-
"description": "
|
|
29
|
+
"title": "Site Name",
|
|
30
|
+
"description": "Your site description",
|
|
31
31
|
"author": "Site Name",
|
|
32
32
|
"ogTitle": "Site Name",
|
|
33
|
-
"ogDescription": "
|
|
33
|
+
"ogDescription": "Your site description",
|
|
34
34
|
"ogImage": "",
|
|
35
35
|
"twitterSite": "",
|
|
36
36
|
"twitterImage": "",
|
|
@@ -61,4 +61,4 @@
|
|
|
61
61
|
"instagram": "",
|
|
62
62
|
"linkedin": ""
|
|
63
63
|
}
|
|
64
|
-
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Environment değişkenlerini window'a export et
|
|
2
|
+
declare global {
|
|
3
|
+
interface Window {
|
|
4
|
+
ENV: {
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
window.ENV = {
|
|
11
|
+
...Object.keys(import.meta.env)
|
|
12
|
+
.filter((key) => key.startsWith('VITE_'))
|
|
13
|
+
.reduce(
|
|
14
|
+
(acc, key) => ({
|
|
15
|
+
...acc,
|
|
16
|
+
[key]: import.meta.env[key],
|
|
17
|
+
}),
|
|
18
|
+
{},
|
|
19
|
+
),
|
|
20
|
+
};
|