@promakeai/cli 0.3.2 → 0.3.4
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 +415 -209
- package/dist/registry/about-page.json +3 -3
- package/dist/registry/about-section.json +4 -4
- package/dist/registry/animations.json +2 -2
- package/dist/registry/announcement-bar.json +4 -4
- package/dist/registry/api.json +1 -1
- package/dist/registry/auth-core.json +3 -3
- package/dist/registry/auth.json +70 -0
- package/dist/registry/bento-grid-section.json +4 -4
- package/dist/registry/blog-core.json +5 -5
- package/dist/registry/blog-list-page.json +3 -3
- package/dist/registry/blog-section.json +4 -4
- package/dist/registry/cards-carousel-section.json +4 -4
- package/dist/registry/cart-drawer.json +3 -3
- package/dist/registry/cart-page.json +3 -3
- package/dist/registry/case-study-page.json +3 -3
- package/dist/registry/category-section.json +3 -3
- package/dist/registry/checkout-page.json +3 -3
- package/dist/registry/coming-soon-page-minimal.json +4 -4
- package/dist/registry/coming-soon-page.json +4 -4
- package/dist/registry/contact-info-grid.json +4 -4
- package/dist/registry/contact-page-centered.json +4 -4
- package/dist/registry/contact-page-split.json +4 -4
- package/dist/registry/contact-page.json +3 -3
- package/dist/registry/content-section.json +4 -4
- package/dist/registry/cookie-consent.json +4 -4
- package/dist/registry/cookies-page.json +3 -3
- package/dist/registry/cta-section.json +3 -3
- package/dist/registry/docs/reset-password-page.md +36 -0
- package/dist/registry/ecommerce-core.json +10 -10
- package/dist/registry/empty-page.json +3 -3
- package/dist/registry/faq-categorized.json +4 -4
- package/dist/registry/faq-simple.json +4 -4
- package/dist/registry/favorites-blog-block.json +1 -1
- package/dist/registry/favorites-blog-page.json +4 -4
- package/dist/registry/favorites-ecommerce-block.json +1 -1
- package/dist/registry/favorites-ecommerce-page.json +4 -4
- package/dist/registry/feature-section.json +3 -3
- package/dist/registry/featured-products.json +3 -3
- package/dist/registry/footer-detailed.json +4 -4
- package/dist/registry/footer-minimal.json +3 -3
- package/dist/registry/footer.json +3 -3
- package/dist/registry/forgot-password-page-split.json +4 -4
- package/dist/registry/forgot-password-page.json +4 -4
- package/dist/registry/google-adsense.json +4 -4
- package/dist/registry/google-map.json +2 -2
- package/dist/registry/header-centered-pill.json +4 -4
- package/dist/registry/header-ecommerce.json +3 -3
- package/dist/registry/header-mega.json +4 -4
- package/dist/registry/header-minimal.json +4 -4
- package/dist/registry/header-simple.json +3 -3
- package/dist/registry/hero-carousel.json +3 -3
- package/dist/registry/hero-cta.json +4 -4
- package/dist/registry/hero-gradient.json +4 -4
- package/dist/registry/hero-grid.json +4 -4
- package/dist/registry/hero-profile.json +3 -3
- package/dist/registry/hero.json +3 -3
- package/dist/registry/landing-page-app.json +3 -3
- package/dist/registry/landing-page-saas.json +3 -3
- package/dist/registry/login-page-split.json +4 -4
- package/dist/registry/login-page.json +4 -4
- package/dist/registry/logo-cloud.json +4 -4
- package/dist/registry/masonry-grid.json +3 -3
- package/dist/registry/my-orders-page.json +4 -4
- package/dist/registry/newsletter-section.json +4 -4
- package/dist/registry/order-card-compact.json +1 -1
- package/dist/registry/order-confirmation-page.json +4 -4
- package/dist/registry/order-detail-block.json +1 -1
- package/dist/registry/orders-list-block.json +1 -1
- package/dist/registry/payment-success-block.json +1 -1
- package/dist/registry/portfolio-page.json +4 -4
- package/dist/registry/post-card.json +3 -3
- package/dist/registry/post-detail-block.json +1 -1
- package/dist/registry/post-detail-page.json +4 -4
- package/dist/registry/pricing-card.json +3 -3
- package/dist/registry/pricing-page.json +4 -4
- package/dist/registry/pricing-section.json +4 -4
- package/dist/registry/privacy-page.json +3 -3
- package/dist/registry/product-card-detailed.json +4 -4
- package/dist/registry/product-card-hover.json +4 -4
- package/dist/registry/product-card.json +3 -3
- package/dist/registry/product-detail-block.json +1 -1
- package/dist/registry/product-detail-page.json +4 -4
- package/dist/registry/product-detail-section.json +4 -4
- package/dist/registry/product-quick-view.json +4 -4
- package/dist/registry/products-page.json +3 -3
- package/dist/registry/reading-progress.json +4 -4
- package/dist/registry/register-page-split.json +4 -4
- package/dist/registry/register-page.json +4 -4
- package/dist/registry/related-posts-block.json +1 -1
- package/dist/registry/related-products-block.json +1 -1
- package/dist/registry/reset-password-page-split.json +4 -4
- package/dist/registry/reset-password-page.json +39 -0
- package/dist/registry/service-card.json +1 -1
- package/dist/registry/share-buttons.json +4 -4
- package/dist/registry/skill-card.json +1 -1
- package/dist/registry/team-page.json +4 -4
- package/dist/registry/terms-page.json +3 -3
- package/dist/registry/testimonials-carousel.json +4 -4
- package/dist/registry/testimonials-grid.json +4 -4
- package/dist/registry/timeline-section.json +4 -4
- package/dist/registry/video-hero.json +4 -4
- package/dist/registry/youtube-embed.json +4 -4
- package/package.json +2 -2
- package/template/.env +6 -6
- package/template/public/_redirects +1 -1
- package/template/public/robots.txt +14 -14
- package/template/src/components/GoogleAnalytics.tsx +34 -34
- package/template/src/components/LanguageSwitcher.tsx +53 -53
- package/template/src/components/ScriptInjector.tsx +62 -62
- package/template/src/lib/env.ts +19 -19
- package/template/src/router.tsx +14 -14
- package/template/src/vite-env.d.ts +1 -1
|
@@ -10,25 +10,25 @@
|
|
|
10
10
|
"path": "youtube-embed/index.ts",
|
|
11
11
|
"type": "registry:index",
|
|
12
12
|
"target": "$modules$/youtube-embed/index.ts",
|
|
13
|
-
"content": "export * from './youtube-embed';\n"
|
|
13
|
+
"content": "export * from './youtube-embed';\r\n"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"path": "youtube-embed/youtube-embed.tsx",
|
|
17
17
|
"type": "registry:component",
|
|
18
18
|
"target": "$modules$/youtube-embed/youtube-embed.tsx",
|
|
19
|
-
"content": "import { useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { cn } from \"@/lib/utils\";\nimport { Play } from \"lucide-react\";\n\ninterface YouTubeEmbedProps {\n videoId: string;\n title?: string;\n autoplay?: boolean;\n mute?: boolean;\n loop?: boolean;\n controls?: boolean;\n start?: number;\n aspectRatio?: \"16/9\" | \"4/3\" | \"1/1\";\n className?: string;\n}\n\nexport function YouTubeEmbed({\n videoId,\n title,\n autoplay = false,\n mute = false,\n loop = false,\n controls = true,\n start,\n aspectRatio = \"16/9\",\n className,\n}: YouTubeEmbedProps) {\n const { t } = useTranslation(\"youtube-embed\");\n const [isLoading, setIsLoading] = useState(true);\n const [hasError, setHasError] = useState(false);\n\n // Validate videoId\n const isValidVideoId = /^[a-zA-Z0-9_-]{11}$/.test(videoId);\n\n // Build embed URL with parameters\n const buildEmbedUrl = () => {\n const params = new URLSearchParams();\n\n if (autoplay) params.set(\"autoplay\", \"1\");\n if (mute) params.set(\"mute\", \"1\");\n if (loop) {\n params.set(\"loop\", \"1\");\n params.set(\"playlist\", videoId);\n }\n if (!controls) params.set(\"controls\", \"0\");\n if (start) params.set(\"start\", start.toString());\n\n params.set(\"rel\", \"0\");\n\n const queryString = params.toString();\n return `https://www.youtube.com/embed/${videoId}${queryString ? `?${queryString}` : \"\"}`;\n };\n\n const handleLoad = () => {\n setIsLoading(false);\n };\n\n const handleError = () => {\n setIsLoading(false);\n setHasError(true);\n };\n\n // Invalid videoId\n if (!isValidVideoId) {\n return (\n <div\n className={cn(\n \"flex flex-col items-center justify-center bg-muted rounded-lg border\",\n className\n )}\n style={{ aspectRatio }}\n >\n <Play className=\"h-12 w-12 text-muted-foreground mb-3\" />\n <p className=\"text-sm text-muted-foreground\">\n {t(\"invalidId\", \"Invalid video ID\")}\n </p>\n </div>\n );\n }\n\n // Error state\n if (hasError) {\n return (\n <div\n className={cn(\n \"flex flex-col items-center justify-center bg-muted rounded-lg border\",\n className\n )}\n style={{ aspectRatio }}\n >\n <Play className=\"h-12 w-12 text-muted-foreground mb-3\" />\n <p className=\"text-sm text-muted-foreground\">\n {t(\"error\", \"Failed to load video\")}\n </p>\n <a\n href={`https://www.youtube.com/watch?v=${videoId}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-sm text-primary hover:underline mt-2\"\n >\n {t(\"watchOnYouTube\", \"Watch on YouTube\")}\n </a>\n </div>\n );\n }\n\n return (\n <div\n className={cn(\"relative rounded-lg overflow-hidden\", className)}\n style={{ aspectRatio }}\n >\n {isLoading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-muted\">\n <div className=\"flex flex-col items-center gap-3\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent\" />\n <span className=\"text-sm text-muted-foreground\">\n {t(\"loading\", \"Loading video...\")}\n </span>\n </div>\n </div>\n )}\n <iframe\n src={buildEmbedUrl()}\n width=\"100%\"\n height=\"100%\"\n style={{ border: 0 }}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowFullScreen\n loading=\"lazy\"\n title={title || t(\"defaultTitle\", \"YouTube video player\")}\n onLoad={handleLoad}\n onError={handleError}\n className={cn(\"absolute inset-0\", isLoading && \"invisible\")}\n />\n </div>\n );\n}\n"
|
|
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
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "youtube-embed/lang/en.json",
|
|
23
23
|
"type": "registry:lang",
|
|
24
24
|
"target": "$modules$/youtube-embed/lang/en.json",
|
|
25
|
-
"content": "{\n \"loading\": \"Loading video...\",\n \"error\": \"Failed to load video\",\n \"invalidId\": \"Invalid video ID\",\n \"watchOnYouTube\": \"Watch on YouTube\",\n \"defaultTitle\": \"YouTube video player\"\n}\n"
|
|
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
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "youtube-embed/lang/tr.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/youtube-embed/lang/tr.json",
|
|
31
|
-
"content": "{\n \"loading\": \"Video yükleniyor...\",\n \"error\": \"Video yüklenemedi\",\n \"invalidId\": \"Geçersiz video ID\",\n \"watchOnYouTube\": \"YouTube'da izle\",\n \"defaultTitle\": \"YouTube video oynatıcı\"\n}\n"
|
|
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
32
|
}
|
|
33
33
|
],
|
|
34
34
|
"exports": {
|
package/package.json
CHANGED
package/template/.env
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
VITE_API_PAYMENT="https://payment.promake.ai/api/v1"
|
|
2
|
-
VITE_TENANT_UUID="YWJjeHl6MTIzT0dVWkNBTg"
|
|
3
|
-
VITE_MAIL_SERVICE_URL="https://mail.promake.ai/api/v1/send-mail"
|
|
4
|
-
VITE_TENANT_MAIL="uc.erkut@gmail.com"
|
|
5
|
-
VITE_ONLINE_PAYMENT_METHODS="stripe,iyzico"
|
|
6
|
-
VITE_AVAILABLE_PAYMENT_METHODS="card,transfer,cash"
|
|
1
|
+
VITE_API_PAYMENT="https://payment.promake.ai/api/v1"
|
|
2
|
+
VITE_TENANT_UUID="YWJjeHl6MTIzT0dVWkNBTg"
|
|
3
|
+
VITE_MAIL_SERVICE_URL="https://mail.promake.ai/api/v1/send-mail"
|
|
4
|
+
VITE_TENANT_MAIL="uc.erkut@gmail.com"
|
|
5
|
+
VITE_ONLINE_PAYMENT_METHODS="stripe,iyzico"
|
|
6
|
+
VITE_AVAILABLE_PAYMENT_METHODS="card,transfer,cash"
|
|
7
7
|
VITE_PROMAKE_ENV="testb"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/* /index.html 200
|
|
1
|
+
/* /index.html 200
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
User-agent: Googlebot
|
|
2
|
-
Allow: /
|
|
3
|
-
|
|
4
|
-
User-agent: Bingbot
|
|
5
|
-
Allow: /
|
|
6
|
-
|
|
7
|
-
User-agent: Twitterbot
|
|
8
|
-
Allow: /
|
|
9
|
-
|
|
10
|
-
User-agent: facebookexternalhit
|
|
11
|
-
Allow: /
|
|
12
|
-
|
|
13
|
-
User-agent: *
|
|
14
|
-
Allow: /
|
|
1
|
+
User-agent: Googlebot
|
|
2
|
+
Allow: /
|
|
3
|
+
|
|
4
|
+
User-agent: Bingbot
|
|
5
|
+
Allow: /
|
|
6
|
+
|
|
7
|
+
User-agent: Twitterbot
|
|
8
|
+
Allow: /
|
|
9
|
+
|
|
10
|
+
User-agent: facebookexternalhit
|
|
11
|
+
Allow: /
|
|
12
|
+
|
|
13
|
+
User-agent: *
|
|
14
|
+
Allow: /
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import constants from "@/constants/constants.json";
|
|
3
|
-
|
|
4
|
-
export function GoogleAnalytics() {
|
|
5
|
-
const injected = useRef(false);
|
|
6
|
-
const gaId = constants.scripts.gaId;
|
|
7
|
-
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
if (!gaId || injected.current) return;
|
|
10
|
-
if (document.querySelector(`[data-injected="gtag"]`)) return;
|
|
11
|
-
|
|
12
|
-
injected.current = true;
|
|
13
|
-
|
|
14
|
-
// Inject gtag.js script
|
|
15
|
-
const script = document.createElement("script");
|
|
16
|
-
script.async = true;
|
|
17
|
-
script.src = `https://www.googletagmanager.com/gtag/js?id=${gaId}`;
|
|
18
|
-
script.setAttribute("data-injected", "gtag");
|
|
19
|
-
document.head.appendChild(script);
|
|
20
|
-
|
|
21
|
-
// Inject inline config script
|
|
22
|
-
const inlineScript = document.createElement("script");
|
|
23
|
-
inlineScript.setAttribute("data-injected", "gtag-config");
|
|
24
|
-
inlineScript.innerHTML = `
|
|
25
|
-
window.dataLayer = window.dataLayer || [];
|
|
26
|
-
function gtag(){dataLayer.push(arguments);}
|
|
27
|
-
gtag('js', new Date());
|
|
28
|
-
gtag('config', '${gaId}');
|
|
29
|
-
`;
|
|
30
|
-
document.head.appendChild(inlineScript);
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import constants from "@/constants/constants.json";
|
|
3
|
+
|
|
4
|
+
export function GoogleAnalytics() {
|
|
5
|
+
const injected = useRef(false);
|
|
6
|
+
const gaId = constants.scripts.gaId;
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (!gaId || injected.current) return;
|
|
10
|
+
if (document.querySelector(`[data-injected="gtag"]`)) return;
|
|
11
|
+
|
|
12
|
+
injected.current = true;
|
|
13
|
+
|
|
14
|
+
// Inject gtag.js script
|
|
15
|
+
const script = document.createElement("script");
|
|
16
|
+
script.async = true;
|
|
17
|
+
script.src = `https://www.googletagmanager.com/gtag/js?id=${gaId}`;
|
|
18
|
+
script.setAttribute("data-injected", "gtag");
|
|
19
|
+
document.head.appendChild(script);
|
|
20
|
+
|
|
21
|
+
// Inject inline config script
|
|
22
|
+
const inlineScript = document.createElement("script");
|
|
23
|
+
inlineScript.setAttribute("data-injected", "gtag-config");
|
|
24
|
+
inlineScript.innerHTML = `
|
|
25
|
+
window.dataLayer = window.dataLayer || [];
|
|
26
|
+
function gtag(){dataLayer.push(arguments);}
|
|
27
|
+
gtag('js', new Date());
|
|
28
|
+
gtag('config', '${gaId}');
|
|
29
|
+
`;
|
|
30
|
+
document.head.appendChild(inlineScript);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import { useTranslation } from "react-i18next";
|
|
2
|
-
import { Button } from "@/components/ui/button";
|
|
3
|
-
import {
|
|
4
|
-
DropdownMenu,
|
|
5
|
-
DropdownMenuContent,
|
|
6
|
-
DropdownMenuItem,
|
|
7
|
-
DropdownMenuTrigger,
|
|
8
|
-
} from "@/components/ui/dropdown-menu";
|
|
9
|
-
import { changeLanguage } from "@/lang";
|
|
10
|
-
import { cn } from "@/lib/utils";
|
|
11
|
-
import constants from "@/constants/constants.json";
|
|
12
|
-
|
|
13
|
-
interface LanguageSwitcherProps {
|
|
14
|
-
className?: string;
|
|
15
|
-
style?: React.CSSProperties;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const languages: Record<string, string> =
|
|
19
|
-
constants?.site?.availableLanguages || {};
|
|
20
|
-
|
|
21
|
-
export function LanguageSwitcher({ className, style }: LanguageSwitcherProps) {
|
|
22
|
-
const { i18n } = useTranslation();
|
|
23
|
-
const currentLang = i18n.language;
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<DropdownMenu>
|
|
27
|
-
<DropdownMenuTrigger asChild>
|
|
28
|
-
<Button
|
|
29
|
-
variant="ghost"
|
|
30
|
-
size="sm"
|
|
31
|
-
className={cn("h-9 px-2 text-sm font-medium", className)}
|
|
32
|
-
style={style}
|
|
33
|
-
>
|
|
34
|
-
{languages?.[currentLang] || currentLang.toUpperCase()}
|
|
35
|
-
</Button>
|
|
36
|
-
</DropdownMenuTrigger>
|
|
37
|
-
<DropdownMenuContent align="end">
|
|
38
|
-
{Object.entries(languages).map(([lang, label]) => (
|
|
39
|
-
<DropdownMenuItem
|
|
40
|
-
key={lang}
|
|
41
|
-
onClick={() => changeLanguage(lang)}
|
|
42
|
-
className={cn(
|
|
43
|
-
currentLang === lang ? "bg-accent" : "",
|
|
44
|
-
"hover:text-primary focus:text-primary",
|
|
45
|
-
)}
|
|
46
|
-
>
|
|
47
|
-
{label || lang.toUpperCase()}
|
|
48
|
-
</DropdownMenuItem>
|
|
49
|
-
))}
|
|
50
|
-
</DropdownMenuContent>
|
|
51
|
-
</DropdownMenu>
|
|
52
|
-
);
|
|
53
|
-
}
|
|
1
|
+
import { useTranslation } from "react-i18next";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuTrigger,
|
|
8
|
+
} from "@/components/ui/dropdown-menu";
|
|
9
|
+
import { changeLanguage } from "@/lang";
|
|
10
|
+
import { cn } from "@/lib/utils";
|
|
11
|
+
import constants from "@/constants/constants.json";
|
|
12
|
+
|
|
13
|
+
interface LanguageSwitcherProps {
|
|
14
|
+
className?: string;
|
|
15
|
+
style?: React.CSSProperties;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const languages: Record<string, string> =
|
|
19
|
+
constants?.site?.availableLanguages || {};
|
|
20
|
+
|
|
21
|
+
export function LanguageSwitcher({ className, style }: LanguageSwitcherProps) {
|
|
22
|
+
const { i18n } = useTranslation();
|
|
23
|
+
const currentLang = i18n.language;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<DropdownMenu>
|
|
27
|
+
<DropdownMenuTrigger asChild>
|
|
28
|
+
<Button
|
|
29
|
+
variant="ghost"
|
|
30
|
+
size="sm"
|
|
31
|
+
className={cn("h-9 px-2 text-sm font-medium", className)}
|
|
32
|
+
style={style}
|
|
33
|
+
>
|
|
34
|
+
{languages?.[currentLang] || currentLang.toUpperCase()}
|
|
35
|
+
</Button>
|
|
36
|
+
</DropdownMenuTrigger>
|
|
37
|
+
<DropdownMenuContent align="end">
|
|
38
|
+
{Object.entries(languages).map(([lang, label]) => (
|
|
39
|
+
<DropdownMenuItem
|
|
40
|
+
key={lang}
|
|
41
|
+
onClick={() => changeLanguage(lang)}
|
|
42
|
+
className={cn(
|
|
43
|
+
currentLang === lang ? "bg-accent" : "",
|
|
44
|
+
"hover:text-primary focus:text-primary",
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
{label || lang.toUpperCase()}
|
|
48
|
+
</DropdownMenuItem>
|
|
49
|
+
))}
|
|
50
|
+
</DropdownMenuContent>
|
|
51
|
+
</DropdownMenu>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import constants from "@/constants/constants.json";
|
|
3
|
-
|
|
4
|
-
function injectScript(html: string, target: "head" | "body", position: "start" | "end", marker: string) {
|
|
5
|
-
if (!html) return;
|
|
6
|
-
|
|
7
|
-
// Check if already injected
|
|
8
|
-
if (document.querySelector(`[data-injected="${marker}"]`)) return;
|
|
9
|
-
|
|
10
|
-
const container = document.createElement("div");
|
|
11
|
-
container.innerHTML = html;
|
|
12
|
-
|
|
13
|
-
const targetElement = target === "head" ? document.head : document.body;
|
|
14
|
-
|
|
15
|
-
// Create wrapper with marker
|
|
16
|
-
const wrapper = document.createDocumentFragment();
|
|
17
|
-
|
|
18
|
-
Array.from(container.childNodes).forEach((node) => {
|
|
19
|
-
if (node.nodeName === "SCRIPT") {
|
|
20
|
-
// Recreate script for execution
|
|
21
|
-
const script = node as HTMLScriptElement;
|
|
22
|
-
const newScript = document.createElement("script");
|
|
23
|
-
newScript.setAttribute("data-injected", marker);
|
|
24
|
-
Array.from(script.attributes).forEach((attr) => {
|
|
25
|
-
newScript.setAttribute(attr.name, attr.value);
|
|
26
|
-
});
|
|
27
|
-
if (script.innerHTML) {
|
|
28
|
-
newScript.innerHTML = script.innerHTML;
|
|
29
|
-
}
|
|
30
|
-
wrapper.appendChild(newScript);
|
|
31
|
-
} else {
|
|
32
|
-
const clone = node.cloneNode(true) as HTMLElement;
|
|
33
|
-
if (clone.setAttribute) {
|
|
34
|
-
clone.setAttribute("data-injected", marker);
|
|
35
|
-
}
|
|
36
|
-
wrapper.appendChild(clone);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if (position === "start") {
|
|
41
|
-
targetElement.insertBefore(wrapper, targetElement.firstChild);
|
|
42
|
-
} else {
|
|
43
|
-
targetElement.appendChild(wrapper);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function ScriptInjector() {
|
|
48
|
-
const injected = useRef(false);
|
|
49
|
-
const { headStart, headEnd, bodyStart, bodyEnd } = constants.scripts;
|
|
50
|
-
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
if (injected.current) return;
|
|
53
|
-
injected.current = true;
|
|
54
|
-
|
|
55
|
-
injectScript(headStart, "head", "start", "head-start");
|
|
56
|
-
injectScript(headEnd, "head", "end", "head-end");
|
|
57
|
-
injectScript(bodyStart, "body", "start", "body-start");
|
|
58
|
-
injectScript(bodyEnd, "body", "end", "body-end");
|
|
59
|
-
}, []);
|
|
60
|
-
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import constants from "@/constants/constants.json";
|
|
3
|
+
|
|
4
|
+
function injectScript(html: string, target: "head" | "body", position: "start" | "end", marker: string) {
|
|
5
|
+
if (!html) return;
|
|
6
|
+
|
|
7
|
+
// Check if already injected
|
|
8
|
+
if (document.querySelector(`[data-injected="${marker}"]`)) return;
|
|
9
|
+
|
|
10
|
+
const container = document.createElement("div");
|
|
11
|
+
container.innerHTML = html;
|
|
12
|
+
|
|
13
|
+
const targetElement = target === "head" ? document.head : document.body;
|
|
14
|
+
|
|
15
|
+
// Create wrapper with marker
|
|
16
|
+
const wrapper = document.createDocumentFragment();
|
|
17
|
+
|
|
18
|
+
Array.from(container.childNodes).forEach((node) => {
|
|
19
|
+
if (node.nodeName === "SCRIPT") {
|
|
20
|
+
// Recreate script for execution
|
|
21
|
+
const script = node as HTMLScriptElement;
|
|
22
|
+
const newScript = document.createElement("script");
|
|
23
|
+
newScript.setAttribute("data-injected", marker);
|
|
24
|
+
Array.from(script.attributes).forEach((attr) => {
|
|
25
|
+
newScript.setAttribute(attr.name, attr.value);
|
|
26
|
+
});
|
|
27
|
+
if (script.innerHTML) {
|
|
28
|
+
newScript.innerHTML = script.innerHTML;
|
|
29
|
+
}
|
|
30
|
+
wrapper.appendChild(newScript);
|
|
31
|
+
} else {
|
|
32
|
+
const clone = node.cloneNode(true) as HTMLElement;
|
|
33
|
+
if (clone.setAttribute) {
|
|
34
|
+
clone.setAttribute("data-injected", marker);
|
|
35
|
+
}
|
|
36
|
+
wrapper.appendChild(clone);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (position === "start") {
|
|
41
|
+
targetElement.insertBefore(wrapper, targetElement.firstChild);
|
|
42
|
+
} else {
|
|
43
|
+
targetElement.appendChild(wrapper);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ScriptInjector() {
|
|
48
|
+
const injected = useRef(false);
|
|
49
|
+
const { headStart, headEnd, bodyStart, bodyEnd } = constants.scripts;
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (injected.current) return;
|
|
53
|
+
injected.current = true;
|
|
54
|
+
|
|
55
|
+
injectScript(headStart, "head", "start", "head-start");
|
|
56
|
+
injectScript(headEnd, "head", "end", "head-end");
|
|
57
|
+
injectScript(bodyStart, "body", "start", "body-start");
|
|
58
|
+
injectScript(bodyEnd, "body", "end", "body-end");
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
package/template/src/lib/env.ts
CHANGED
|
@@ -1,20 +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
|
-
),
|
|
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
20
|
};
|
package/template/src/router.tsx
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { BrowserRouter, Routes, Route } from "react-router";
|
|
2
|
-
import Index from "./pages/Index";
|
|
3
|
-
import NotFound from "./pages/NotFound";
|
|
4
|
-
|
|
5
|
-
export const Router = () => {
|
|
6
|
-
return (
|
|
7
|
-
<BrowserRouter>
|
|
8
|
-
<Routes>
|
|
9
|
-
<Route path="/" element={<Index />} />
|
|
10
|
-
<Route path="*" element={<NotFound />} />
|
|
11
|
-
</Routes>
|
|
12
|
-
</BrowserRouter>
|
|
13
|
-
);
|
|
14
|
-
};
|
|
1
|
+
import { BrowserRouter, Routes, Route } from "react-router";
|
|
2
|
+
import Index from "./pages/Index";
|
|
3
|
+
import NotFound from "./pages/NotFound";
|
|
4
|
+
|
|
5
|
+
export const Router = () => {
|
|
6
|
+
return (
|
|
7
|
+
<BrowserRouter>
|
|
8
|
+
<Routes>
|
|
9
|
+
<Route path="/" element={<Index />} />
|
|
10
|
+
<Route path="*" element={<NotFound />} />
|
|
11
|
+
</Routes>
|
|
12
|
+
</BrowserRouter>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|
|
1
|
+
/// <reference types="vite/client" />
|