@promakeai/cli 0.0.6 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +31 -31
- package/dist/registry/about-page.json +4 -2
- package/dist/registry/about-section.json +2 -2
- package/dist/registry/announcement-bar.json +43 -0
- package/dist/registry/auth-core.json +43 -0
- package/dist/registry/auth.json +1 -1
- package/dist/registry/blog-list-page.json +3 -2
- package/dist/registry/blog-section.json +1 -1
- package/dist/registry/cart-page.json +3 -3
- package/dist/registry/case-study-page.json +48 -0
- package/dist/registry/checkout-page.json +6 -5
- package/dist/registry/coming-soon-page-minimal.json +45 -0
- package/dist/registry/coming-soon-page.json +47 -0
- package/dist/registry/contact-info-grid.json +1 -1
- package/dist/registry/contact-page-centered.json +2 -2
- package/dist/registry/contact-page-map-overlay.json +4 -3
- package/dist/registry/contact-page-map-split.json +4 -3
- package/dist/registry/contact-page-split.json +3 -3
- package/dist/registry/contact-page.json +5 -3
- package/dist/registry/cookie-consent.json +43 -0
- package/dist/registry/cookies-page.json +3 -1
- package/dist/registry/cta-section.json +1 -1
- package/dist/registry/db.json +1 -1
- package/dist/registry/docs/about-page.md +5 -0
- package/dist/registry/docs/announcement-bar.md +38 -0
- package/dist/registry/docs/auth-core.md +64 -0
- package/dist/registry/docs/blog-list-page.md +1 -0
- package/dist/registry/docs/case-study-page.md +39 -0
- package/dist/registry/docs/checkout-page.md +2 -1
- package/dist/registry/docs/coming-soon-page-minimal.md +32 -0
- package/dist/registry/docs/coming-soon-page.md +37 -0
- package/dist/registry/docs/contact-page-map-overlay.md +2 -2
- package/dist/registry/docs/contact-page-map-split.md +2 -2
- package/dist/registry/docs/contact-page.md +5 -0
- package/dist/registry/docs/cookie-consent.md +37 -0
- package/dist/registry/docs/cookies-page.md +5 -0
- package/dist/registry/docs/ecommerce-core.md +4 -3
- package/dist/registry/docs/forgot-password-page-split.md +45 -0
- package/dist/registry/docs/forgot-password-page.md +14 -5
- package/dist/registry/docs/header-ecommerce.md +1 -0
- package/dist/registry/docs/hero-carousel.md +37 -0
- package/dist/registry/docs/landing-page-app.md +43 -0
- package/dist/registry/docs/landing-page-saas.md +41 -0
- package/dist/registry/docs/login-page-split.md +13 -4
- package/dist/registry/docs/login-page.md +17 -4
- package/dist/registry/docs/logo-cloud.md +33 -0
- package/dist/registry/docs/masonry-grid.md +37 -0
- package/dist/registry/docs/my-orders-page.md +44 -0
- package/dist/registry/docs/order-confirmation-page.md +41 -0
- package/dist/registry/docs/portfolio-page.md +38 -0
- package/dist/registry/docs/pricing-page.md +38 -0
- package/dist/registry/docs/privacy-page.md +5 -0
- package/dist/registry/docs/product-quick-view.md +37 -0
- package/dist/registry/docs/reading-progress.md +31 -0
- package/dist/registry/docs/register-page-split.md +45 -0
- package/dist/registry/docs/register-page.md +14 -7
- package/dist/registry/docs/reset-password-page-split.md +45 -0
- package/dist/registry/docs/reset-password-page.md +36 -0
- package/dist/registry/docs/share-buttons.md +37 -0
- package/dist/registry/docs/team-page.md +38 -0
- package/dist/registry/docs/terms-page.md +5 -0
- package/dist/registry/docs/timeline-section.md +37 -0
- package/dist/registry/docs/video-hero.md +41 -0
- package/dist/registry/ecommerce-core.json +17 -1
- package/dist/registry/empty-page.json +1 -1
- package/dist/registry/faq-categorized.json +1 -1
- package/dist/registry/faq-simple.json +1 -1
- package/dist/registry/favorites-ecommerce-block.json +1 -1
- package/dist/registry/feature-section.json +2 -2
- package/dist/registry/footer.json +1 -1
- package/dist/registry/forgot-password-page-split.json +50 -0
- package/dist/registry/forgot-password-page.json +9 -7
- package/dist/registry/header-ecommerce.json +3 -2
- package/dist/registry/header-mega.json +1 -1
- package/dist/registry/hero-carousel.json +45 -0
- package/dist/registry/hero-cta.json +2 -2
- package/dist/registry/hero-gradient.json +1 -1
- package/dist/registry/hero.json +1 -1
- package/dist/registry/index.json +22 -2
- package/dist/registry/landing-page-app.json +47 -0
- package/dist/registry/landing-page-saas.json +47 -0
- package/dist/registry/login-page-split.json +11 -7
- package/dist/registry/login-page.json +4 -4
- package/dist/registry/logo-cloud.json +41 -0
- package/dist/registry/masonry-grid.json +43 -0
- package/dist/registry/my-orders-page.json +52 -0
- package/dist/registry/order-confirmation-page.json +49 -0
- package/dist/registry/portfolio-page.json +45 -0
- package/dist/registry/pricing-page.json +47 -0
- package/dist/registry/pricing-section.json +1 -1
- package/dist/registry/privacy-page.json +3 -1
- package/dist/registry/product-detail-block.json +1 -1
- package/dist/registry/product-quick-view.json +46 -0
- package/dist/registry/products-page.json +3 -3
- package/dist/registry/reading-progress.json +43 -0
- package/dist/registry/register-page-split.json +50 -0
- package/dist/registry/register-page.json +9 -7
- package/dist/registry/reset-password-page-split.json +50 -0
- package/dist/registry/reset-password-page.json +39 -0
- package/dist/registry/share-buttons.json +46 -0
- package/dist/registry/team-page.json +47 -0
- package/dist/registry/terms-page.json +3 -1
- package/dist/registry/testimonials-carousel.json +1 -1
- package/dist/registry/testimonials-grid.json +1 -1
- package/dist/registry/timeline-section.json +43 -0
- package/dist/registry/video-hero.json +42 -0
- package/package.json +1 -1
- package/template/index.html +5 -5
- package/template/src/App.tsx +4 -0
- package/template/src/components/GoogleAnalytics.tsx +34 -0
- package/template/src/components/Layout.tsx +1 -1
- package/template/src/components/ScriptInjector.tsx +62 -0
- package/template/src/constants/constants.json +8 -2
- package/template/src/pages/Index.tsx +5 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "landing-page-app",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "Mobile App Landing Page",
|
|
5
|
+
"description": "Mobile app promotion landing page with phone mockup hero, App Store/Play Store download buttons, 4 feature cards, screenshot carousel, user reviews, and download CTA. Includes floating notification elements and FadeIn/ScaleUp animations.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"animations"
|
|
8
|
+
],
|
|
9
|
+
"route": {
|
|
10
|
+
"path": "/app",
|
|
11
|
+
"componentName": "LandingPageApp"
|
|
12
|
+
},
|
|
13
|
+
"usage": "import { LandingPageApp } from '@/modules/landing-page-app';\n\n<Route path=\"/app\" element={<LandingPageApp\n appStoreUrl=\"https://apps.apple.com/...\"\n playStoreUrl=\"https://play.google.com/...\"\n/>} />\n\n• Phone mockup with floating elements\n• App Store & Play Store buttons\n• 4 feature cards\n• Screenshot carousel\n• 3 user reviews\n• Download CTA section",
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "landing-page-app/index.ts",
|
|
17
|
+
"type": "registry:index",
|
|
18
|
+
"target": "$modules$/landing-page-app/index.ts",
|
|
19
|
+
"content": "export * from \"./landing-page-app\";\r\nexport { default } from \"./landing-page-app\";\r\n"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "landing-page-app/landing-page-app.tsx",
|
|
23
|
+
"type": "registry:page",
|
|
24
|
+
"target": "$modules$/landing-page-app/landing-page-app.tsx",
|
|
25
|
+
"content": "import { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Layout } from \"@/components/Layout\";\nimport {\n Star,\n Download,\n Smartphone,\n Bell,\n Lock,\n Zap,\n Heart,\n CheckCircle,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { FadeIn, StaggerContainer, StaggerItem, ScaleUp } from \"@/modules/animations\";\nimport { cn } from \"@/lib/utils\";\n\ninterface LandingPageAppProps {\n className?: string;\n appStoreUrl?: string;\n playStoreUrl?: string;\n}\n\nexport function LandingPageApp({\n className,\n appStoreUrl = \"#\",\n playStoreUrl = \"#\",\n}: LandingPageAppProps) {\n const { t } = useTranslation(\"landing-page-app\");\n usePageTitle({ title: t(\"title\") });\n\n const features = [\n { icon: Zap, title: t(\"feature1Title\"), desc: t(\"feature1Desc\") },\n { icon: Bell, title: t(\"feature2Title\"), desc: t(\"feature2Desc\") },\n { icon: Lock, title: t(\"feature3Title\"), desc: t(\"feature3Desc\") },\n { icon: Heart, title: t(\"feature4Title\"), desc: t(\"feature4Desc\") },\n ];\n\n const screenshots = [\n { src: \"/images/placeholder.png\", alt: \"App Screenshot 1\" },\n { src: \"/images/placeholder.png\", alt: \"App Screenshot 2\" },\n { src: \"/images/placeholder.png\", alt: \"App Screenshot 3\" },\n ];\n\n const reviews = [\n { name: \"Alex K.\", rating: 5, content: t(\"review1\"), date: \"2 days ago\" },\n { name: \"Maria S.\", rating: 5, content: t(\"review2\"), date: \"1 week ago\" },\n { name: \"Tom B.\", rating: 5, content: t(\"review3\"), date: \"2 weeks ago\" },\n ];\n\n return (\n <Layout>\n <div className={cn(\"min-h-screen bg-background\", className)}>\n {/* Hero Section */}\n <section className=\"relative overflow-hidden py-12 md:py-20\">\n <div className=\"absolute inset-0 bg-gradient-to-b from-primary/5 via-transparent to-transparent\" />\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 relative\">\n <div className=\"grid lg:grid-cols-2 gap-12 items-center\">\n {/* Content */}\n <FadeIn className=\"text-center lg:text-left\">\n <Badge variant=\"secondary\" className=\"mb-6\">\n <Smartphone className=\"h-3 w-3 mr-1\" />\n {t(\"badge\")}\n </Badge>\n <h1 className=\"text-4xl md:text-5xl lg:text-6xl font-bold text-foreground mb-6 leading-tight\">\n {t(\"heroTitle\")}\n </h1>\n <p className=\"text-lg md:text-xl text-muted-foreground mb-8\">\n {t(\"heroDescription\")}\n </p>\n\n {/* Download Buttons */}\n <div className=\"flex flex-col sm:flex-row gap-4 justify-center lg:justify-start mb-8\">\n <a\n href={appStoreUrl}\n className=\"inline-flex items-center gap-3 px-6 py-3 bg-foreground text-background rounded-xl hover:opacity-90 transition-opacity\"\n >\n <svg className=\"h-8 w-8\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z\"/>\n </svg>\n <div className=\"text-left\">\n <div className=\"text-xs opacity-80\">{t(\"downloadOn\")}</div>\n <div className=\"text-lg font-semibold\">App Store</div>\n </div>\n </a>\n <a\n href={playStoreUrl}\n className=\"inline-flex items-center gap-3 px-6 py-3 bg-foreground text-background rounded-xl hover:opacity-90 transition-opacity\"\n >\n <svg className=\"h-8 w-8\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M3,20.5V3.5C3,2.91 3.34,2.39 3.84,2.15L13.69,12L3.84,21.85C3.34,21.6 3,21.09 3,20.5M16.81,15.12L6.05,21.34L14.54,12.85L16.81,15.12M20.16,10.81C20.5,11.08 20.75,11.5 20.75,12C20.75,12.5 20.53,12.9 20.18,13.18L17.89,14.5L15.39,12L17.89,9.5L20.16,10.81M6.05,2.66L16.81,8.88L14.54,11.15L6.05,2.66Z\"/>\n </svg>\n <div className=\"text-left\">\n <div className=\"text-xs opacity-80\">{t(\"getItOn\")}</div>\n <div className=\"text-lg font-semibold\">Google Play</div>\n </div>\n </a>\n </div>\n\n {/* Stats */}\n <div className=\"flex flex-wrap gap-8 justify-center lg:justify-start\">\n <div className=\"text-center\">\n <div className=\"text-2xl font-bold text-foreground\">4.9</div>\n <div className=\"flex gap-0.5 justify-center\">\n {[...Array(5)].map((_, i) => (\n <Star key={i} className=\"h-4 w-4 fill-yellow-400 text-yellow-400\" />\n ))}\n </div>\n </div>\n <div className=\"text-center\">\n <div className=\"text-2xl font-bold text-foreground\">1M+</div>\n <div className=\"text-sm text-muted-foreground\">{t(\"downloads\")}</div>\n </div>\n <div className=\"text-center\">\n <div className=\"text-2xl font-bold text-foreground\">#1</div>\n <div className=\"text-sm text-muted-foreground\">{t(\"inCategory\")}</div>\n </div>\n </div>\n </FadeIn>\n\n {/* Phone Mockup */}\n <ScaleUp className=\"relative flex justify-center\">\n <div className=\"relative\">\n {/* Phone frame */}\n <div className=\"relative w-[260px] md:w-[300px] h-[520px] md:h-[600px] bg-foreground rounded-[3rem] p-3 shadow-2xl\">\n <div className=\"absolute top-6 left-1/2 -translate-x-1/2 w-20 h-6 bg-foreground rounded-full z-10\" />\n <div className=\"w-full h-full bg-muted rounded-[2.5rem] overflow-hidden\">\n <img\n src=\"/images/placeholder.png\"\n alt=\"App Preview\"\n className=\"w-full h-full object-cover\"\n onError={(e) => {\n e.currentTarget.src = \"/images/placeholder.png\";\n }}\n />\n </div>\n </div>\n {/* Floating elements */}\n <div className=\"hidden sm:block absolute left-0 sm:-left-4 md:-left-8 top-1/4 bg-card rounded-xl p-3 shadow-lg border border-border\">\n <div className=\"flex items-center gap-2\">\n <CheckCircle className=\"h-5 w-5 text-green-500\" />\n <span className=\"text-sm font-medium\">{t(\"taskComplete\")}</span>\n </div>\n </div>\n <div className=\"hidden sm:block absolute right-0 sm:-right-4 md:-right-8 bottom-1/4 bg-card rounded-xl p-3 shadow-lg border border-border\">\n <div className=\"flex items-center gap-2\">\n <Bell className=\"h-5 w-5 text-primary\" />\n <span className=\"text-sm font-medium\">{t(\"newMessage\")}</span>\n </div>\n </div>\n </div>\n </ScaleUp>\n </div>\n </div>\n </section>\n\n {/* Features Section */}\n <section className=\"py-12 md:py-16 bg-muted/30\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <FadeIn className=\"text-center mb-16\">\n <p className=\"text-sm font-medium text-primary mb-2 uppercase tracking-wide\">\n {t(\"featuresLabel\")}\n </p>\n <h2 className=\"text-3xl md:text-4xl font-bold text-foreground mb-4\">\n {t(\"featuresTitle\")}\n </h2>\n </FadeIn>\n\n <StaggerContainer className=\"grid sm:grid-cols-2 lg:grid-cols-4 gap-6\">\n {features.map((feature, index) => (\n <StaggerItem key={index}>\n <Card className=\"h-full text-center hover:shadow-lg transition-shadow\">\n <CardContent className=\"p-6\">\n <div className=\"w-14 h-14 rounded-2xl bg-primary/10 flex items-center justify-center mx-auto mb-4\">\n <feature.icon className=\"h-7 w-7 text-primary\" />\n </div>\n <h3 className=\"text-lg font-semibold text-foreground mb-2\">{feature.title}</h3>\n <p className=\"text-sm text-muted-foreground\">{feature.desc}</p>\n </CardContent>\n </Card>\n </StaggerItem>\n ))}\n </StaggerContainer>\n </div>\n </section>\n\n {/* Screenshots Section */}\n <section className=\"py-12 md:py-16\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <FadeIn className=\"text-center mb-16\">\n <p className=\"text-sm font-medium text-primary mb-2 uppercase tracking-wide\">\n {t(\"screenshotsLabel\")}\n </p>\n <h2 className=\"text-3xl md:text-4xl font-bold text-foreground mb-4\">\n {t(\"screenshotsTitle\")}\n </h2>\n </FadeIn>\n\n <div className=\"flex justify-center gap-6 overflow-x-auto pb-4 snap-x snap-mandatory\">\n {screenshots.map((screenshot, index) => (\n <ScaleUp key={index} delay={index * 0.1} className=\"flex-shrink-0 snap-center\">\n <div className=\"w-[200px] md:w-[240px] h-[400px] md:h-[480px] bg-foreground rounded-[2rem] p-2 shadow-xl\">\n <img\n src={screenshot.src}\n alt={screenshot.alt}\n className=\"w-full h-full object-cover rounded-[1.75rem]\"\n onError={(e) => {\n e.currentTarget.src = \"/images/placeholder.png\";\n }}\n />\n </div>\n </ScaleUp>\n ))}\n </div>\n </div>\n </section>\n\n {/* Reviews Section */}\n <section className=\"py-12 md:py-16 bg-muted/30\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <FadeIn className=\"text-center mb-16\">\n <p className=\"text-sm font-medium text-primary mb-2 uppercase tracking-wide\">\n {t(\"reviewsLabel\")}\n </p>\n <h2 className=\"text-3xl md:text-4xl font-bold text-foreground mb-4\">\n {t(\"reviewsTitle\")}\n </h2>\n </FadeIn>\n\n <StaggerContainer className=\"grid md:grid-cols-3 gap-6 max-w-4xl mx-auto\">\n {reviews.map((review, index) => (\n <StaggerItem key={index}>\n <Card>\n <CardContent className=\"p-6\">\n <div className=\"flex gap-0.5 mb-3\">\n {[...Array(review.rating)].map((_, i) => (\n <Star key={i} className=\"h-4 w-4 fill-yellow-400 text-yellow-400\" />\n ))}\n </div>\n <p className=\"text-foreground mb-4\">\"{review.content}\"</p>\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"font-medium\">{review.name}</span>\n <span className=\"text-muted-foreground\">{review.date}</span>\n </div>\n </CardContent>\n </Card>\n </StaggerItem>\n ))}\n </StaggerContainer>\n </div>\n </section>\n\n {/* CTA Section */}\n <section className=\"py-12 md:py-16\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 text-center\">\n <FadeIn>\n <Download className=\"h-16 w-16 text-primary mx-auto mb-6\" />\n <h2 className=\"text-3xl md:text-4xl font-bold text-foreground mb-4\">\n {t(\"ctaTitle\")}\n </h2>\n <p className=\"text-lg text-muted-foreground mb-8 max-w-xl mx-auto\">\n {t(\"ctaDescription\")}\n </p>\n <div className=\"flex flex-col sm:flex-row gap-4 justify-center\">\n <Button asChild size=\"lg\" className=\"text-base\">\n <a href={appStoreUrl}>\n <Download className=\"mr-2 h-5 w-5\" />\n App Store\n </a>\n </Button>\n <Button asChild size=\"lg\" variant=\"outline\" className=\"text-base\">\n <a href={playStoreUrl}>\n <Download className=\"mr-2 h-5 w-5\" />\n Google Play\n </a>\n </Button>\n </div>\n </FadeIn>\n </div>\n </section>\n </div>\n </Layout>\n );\n}\n\nexport default LandingPageApp;\n"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "landing-page-app/lang/en.json",
|
|
29
|
+
"type": "registry:lang",
|
|
30
|
+
"target": "$modules$/landing-page-app/lang/en.json",
|
|
31
|
+
"content": "{\r\n \"title\": \"Mobile App\",\r\n \"badge\": \"Available on iOS & Android\",\r\n \"heroTitle\": \"Your App Name Here\",\r\n \"heroDescription\": \"Ask Promake to customize this description based on your app. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"downloadOn\": \"Download on the\",\r\n \"getItOn\": \"Get it on\",\r\n \"downloads\": \"Downloads\",\r\n \"inCategory\": \"In Category\",\r\n \"taskComplete\": \"Task Complete!\",\r\n \"newMessage\": \"New Message\",\r\n \"featuresLabel\": \"Features\",\r\n \"featuresTitle\": \"Why You'll Love It\",\r\n \"feature1Title\": \"Feature One\",\r\n \"feature1Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature2Title\": \"Feature Two\",\r\n \"feature2Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature3Title\": \"Feature Three\",\r\n \"feature3Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature4Title\": \"Feature Four\",\r\n \"feature4Desc\": \"Ask Promake to customize this feature description.\",\r\n \"screenshotsLabel\": \"Screenshots\",\r\n \"screenshotsTitle\": \"See It in Action\",\r\n \"reviewsLabel\": \"Reviews\",\r\n \"reviewsTitle\": \"What Users Say\",\r\n \"review1\": \"Ask Promake to add real user reviews here. Lorem ipsum dolor sit amet.\",\r\n \"review2\": \"Ask Promake to add real user reviews here. Consectetur adipiscing elit.\",\r\n \"review3\": \"Ask Promake to add real user reviews here. Sed do eiusmod tempor.\",\r\n \"ctaTitle\": \"Download Now\",\r\n \"ctaDescription\": \"Ask Promake to customize this CTA description. Available for free on iOS and Android.\"\r\n}\r\n"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"path": "landing-page-app/lang/tr.json",
|
|
35
|
+
"type": "registry:lang",
|
|
36
|
+
"target": "$modules$/landing-page-app/lang/tr.json",
|
|
37
|
+
"content": "{\r\n \"title\": \"Mobil Uygulama\",\r\n \"badge\": \"iOS ve Android'de Mevcut\",\r\n \"heroTitle\": \"Hayatınız, Tek Uygulamada Basitleştirildi\",\r\n \"heroDescription\": \"Mobil verimliliğin geleceğini deneyimleyin. Şimdi indirin ve milyonlarca mutlu kullanıcıya katılın.\",\r\n \"downloadOn\": \"Şuradan indir:\",\r\n \"getItOn\": \"Şuradan alın:\",\r\n \"downloads\": \"İndirme\",\r\n \"inCategory\": \"Kategorisinde\",\r\n \"taskComplete\": \"Görev Tamamlandı!\",\r\n \"newMessage\": \"Yeni Mesaj\",\r\n \"featuresLabel\": \"Özellikler\",\r\n \"featuresTitle\": \"Neden Seveceksiniz\",\r\n \"feature1Title\": \"Işık Hızında\",\r\n \"feature1Desc\": \"Anında yükleme ve akıcı animasyonlar\",\r\n \"feature2Title\": \"Akıllı Bildirimler\",\r\n \"feature2Desc\": \"Akıllı uyarılarla güncel kalın\",\r\n \"feature3Title\": \"Güvenli & Özel\",\r\n \"feature3Desc\": \"Verileriniz şifreli ve korumalı\",\r\n \"feature4Title\": \"Çevrimdışı Mod\",\r\n \"feature4Desc\": \"İnternet olmadan da çalışır\",\r\n \"screenshotsLabel\": \"Ekran Görüntüleri\",\r\n \"screenshotsTitle\": \"Uygulamada Görün\",\r\n \"reviewsLabel\": \"Yorumlar\",\r\n \"reviewsTitle\": \"Kullanıcılar Ne Diyor\",\r\n \"review1\": \"Kullandığım en iyi uygulama!\",\r\n \"review2\": \"Verimliliğim için oyun değiştirici\",\r\n \"review3\": \"Basit, güzel ve etkili\",\r\n \"ctaTitle\": \"Şimdi İndirin\",\r\n \"ctaDescription\": \"iOS ve Android'de ücretsiz olarak mevcut. Yolculuğunuza bugün başlayın.\"\r\n}\r\n"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"exports": {
|
|
41
|
+
"types": [],
|
|
42
|
+
"variables": [
|
|
43
|
+
"LandingPageApp",
|
|
44
|
+
"default"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "landing-page-saas",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "SaaS Landing Page",
|
|
5
|
+
"description": "Complete SaaS product landing page with hero section, product screenshot, stats, 6 feature cards, testimonials with ratings, pricing preview with 3 tiers, and full-width CTA. Fully animated with FadeIn, StaggerContainer, and ScaleUp.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"animations"
|
|
8
|
+
],
|
|
9
|
+
"route": {
|
|
10
|
+
"path": "/saas",
|
|
11
|
+
"componentName": "LandingPageSaas"
|
|
12
|
+
},
|
|
13
|
+
"usage": "import { LandingPageSaas } from '@/modules/landing-page-saas';\n\n<Route path=\"/\" element={<LandingPageSaas />} />\n\n• Hero with badge, headline, CTA buttons\n• Product screenshot mockup\n• Stats bar (users, uptime, tasks, rating)\n• 6 feature cards with icons\n• 3 testimonial cards\n• 3-tier pricing preview\n• Full-width CTA section",
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "landing-page-saas/index.ts",
|
|
17
|
+
"type": "registry:index",
|
|
18
|
+
"target": "$modules$/landing-page-saas/index.ts",
|
|
19
|
+
"content": "export * from \"./landing-page-saas\";\r\nexport { default } from \"./landing-page-saas\";\r\n"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "landing-page-saas/landing-page-saas.tsx",
|
|
23
|
+
"type": "registry:page",
|
|
24
|
+
"target": "$modules$/landing-page-saas/landing-page-saas.tsx",
|
|
25
|
+
"content": "import { Link } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Layout } from \"@/components/Layout\";\nimport {\n ArrowRight,\n Check,\n Star,\n Zap,\n Shield,\n BarChart3,\n Users,\n Clock,\n Sparkles,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Avatar, AvatarImage, AvatarFallback } from \"@/components/ui/avatar\";\nimport { FadeIn, StaggerContainer, StaggerItem, ScaleUp } from \"@/modules/animations\";\nimport { cn } from \"@/lib/utils\";\n\ninterface LandingPageSaasProps {\n className?: string;\n}\n\nexport function LandingPageSaas({ className }: LandingPageSaasProps) {\n const { t } = useTranslation(\"landing-page-saas\");\n usePageTitle({ title: t(\"title\") });\n\n const features = [\n { icon: Zap, title: t(\"feature1Title\"), desc: t(\"feature1Desc\") },\n { icon: Shield, title: t(\"feature2Title\"), desc: t(\"feature2Desc\") },\n { icon: BarChart3, title: t(\"feature3Title\"), desc: t(\"feature3Desc\") },\n { icon: Users, title: t(\"feature4Title\"), desc: t(\"feature4Desc\") },\n { icon: Clock, title: t(\"feature5Title\"), desc: t(\"feature5Desc\") },\n { icon: Sparkles, title: t(\"feature6Title\"), desc: t(\"feature6Desc\") },\n ];\n\n const testimonials = [\n { name: \"Sarah M.\", role: \"CEO, TechCorp\", content: t(\"testimonial1\"), avatar: \"/images/placeholder.png\" },\n { name: \"John D.\", role: \"CTO, StartupXYZ\", content: t(\"testimonial2\"), avatar: \"/images/placeholder.png\" },\n { name: \"Emily R.\", role: \"Product Lead\", content: t(\"testimonial3\"), avatar: \"/images/placeholder.png\" },\n ];\n\n const stats = [\n { value: \"10K+\", label: t(\"stat1Label\") },\n { value: \"99.9%\", label: t(\"stat2Label\") },\n { value: \"50M+\", label: t(\"stat3Label\") },\n { value: \"4.9/5\", label: t(\"stat4Label\") },\n ];\n\n const pricingPlans = [\n { name: t(\"starterPlan\"), price: \"$9\", features: [\"5 Projects\", \"Basic Analytics\", \"Email Support\"] },\n { name: t(\"proPlan\"), price: \"$29\", features: [\"Unlimited Projects\", \"Advanced Analytics\", \"Priority Support\", \"API Access\"], popular: true },\n { name: t(\"enterprisePlan\"), price: t(\"custom\"), features: [\"Everything in Pro\", \"Dedicated Support\", \"Custom Integrations\", \"SLA\"] },\n ];\n\n return (\n <Layout>\n <div className={cn(\"min-h-screen bg-background\", className)}>\n {/* Hero Section */}\n <section className=\"relative overflow-hidden py-12 md:py-20\">\n <div className=\"absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-purple-500/5\" />\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 relative\">\n <FadeIn className=\"text-center max-w-4xl mx-auto\">\n <Badge variant=\"secondary\" className=\"mb-6\">\n <Sparkles className=\"h-3 w-3 mr-1\" />\n {t(\"badge\")}\n </Badge>\n <h1 className=\"text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold text-foreground mb-6 leading-tight\">\n {t(\"heroTitle\")}\n </h1>\n <p className=\"text-lg md:text-xl text-muted-foreground mb-8 max-w-2xl mx-auto\">\n {t(\"heroDescription\")}\n </p>\n <div className=\"flex flex-col sm:flex-row gap-4 justify-center\">\n <Button asChild size=\"lg\" className=\"text-base\">\n <Link to=\"/register\">\n {t(\"startFree\")}\n <ArrowRight className=\"ml-2 h-5 w-5\" />\n </Link>\n </Button>\n <Button asChild variant=\"outline\" size=\"lg\" className=\"text-base\">\n <Link to=\"#demo\">{t(\"watchDemo\")}</Link>\n </Button>\n </div>\n <p className=\"text-sm text-muted-foreground mt-4\">\n {t(\"noCreditCard\")}\n </p>\n </FadeIn>\n\n {/* Product Screenshot */}\n <FadeIn delay={0.2} className=\"mt-16\">\n <div className=\"relative mx-auto max-w-5xl\">\n <div className=\"absolute inset-0 bg-gradient-to-t from-background via-transparent to-transparent z-10 pointer-events-none\" />\n <div className=\"rounded-2xl border border-border bg-card shadow-lg overflow-hidden\">\n <div className=\"bg-muted/50 px-4 py-3 flex items-center gap-2 border-b border-border rounded-t-2xl\">\n <div className=\"flex gap-1.5\">\n <div className=\"w-3 h-3 rounded-full bg-red-500\" />\n <div className=\"w-3 h-3 rounded-full bg-yellow-500\" />\n <div className=\"w-3 h-3 rounded-full bg-green-500\" />\n </div>\n </div>\n <div className=\"aspect-video bg-muted flex items-center justify-center rounded-b-2xl overflow-hidden\">\n <img\n src=\"/images/placeholder.png\"\n alt=\"Product screenshot\"\n className=\"w-full h-full object-cover\"\n onError={(e) => {\n e.currentTarget.src = \"/images/placeholder.png\";\n }}\n />\n </div>\n </div>\n </div>\n </FadeIn>\n </div>\n </section>\n\n {/* Stats Section */}\n <section className=\"py-16 border-y border-border bg-muted/30\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-8 text-center\">\n {stats.map((stat, index) => (\n <ScaleUp key={index} delay={index * 0.1}>\n <div className=\"text-3xl md:text-4xl font-bold text-foreground mb-1\">{stat.value}</div>\n <div className=\"text-sm text-muted-foreground\">{stat.label}</div>\n </ScaleUp>\n ))}\n </div>\n </div>\n </section>\n\n {/* Features Section */}\n <section className=\"py-12 md:py-16\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <FadeIn className=\"text-center mb-16\">\n <p className=\"text-sm font-medium text-primary mb-2 uppercase tracking-wide\">\n {t(\"featuresLabel\")}\n </p>\n <h2 className=\"text-3xl md:text-4xl lg:text-5xl font-bold text-foreground mb-4\">\n {t(\"featuresTitle\")}\n </h2>\n <p className=\"text-lg text-muted-foreground max-w-2xl mx-auto\">\n {t(\"featuresDescription\")}\n </p>\n </FadeIn>\n\n <StaggerContainer className=\"grid sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {features.map((feature, index) => (\n <StaggerItem key={index}>\n <Card className=\"h-full hover:shadow-lg transition-shadow\">\n <CardContent className=\"p-6\">\n <div className=\"w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center mb-4\">\n <feature.icon className=\"h-6 w-6 text-primary\" />\n </div>\n <h3 className=\"text-xl font-semibold text-foreground mb-2\">{feature.title}</h3>\n <p className=\"text-muted-foreground\">{feature.desc}</p>\n </CardContent>\n </Card>\n </StaggerItem>\n ))}\n </StaggerContainer>\n </div>\n </section>\n\n {/* Testimonials Section */}\n <section className=\"py-12 md:py-16 bg-muted/30\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <FadeIn className=\"text-center mb-16\">\n <p className=\"text-sm font-medium text-primary mb-2 uppercase tracking-wide\">\n {t(\"testimonialsLabel\")}\n </p>\n <h2 className=\"text-3xl md:text-4xl font-bold text-foreground mb-4\">\n {t(\"testimonialsTitle\")}\n </h2>\n </FadeIn>\n\n <StaggerContainer className=\"grid md:grid-cols-3 gap-8\">\n {testimonials.map((testimonial, index) => (\n <StaggerItem key={index}>\n <Card className=\"h-full\">\n <CardContent className=\"p-6\">\n <div className=\"flex gap-1 mb-4\">\n {[...Array(5)].map((_, i) => (\n <Star key={i} className=\"h-4 w-4 fill-yellow-400 text-yellow-400\" />\n ))}\n </div>\n <p className=\"text-foreground mb-6\">\"{testimonial.content}\"</p>\n <div className=\"flex items-center gap-3\">\n <Avatar>\n <AvatarImage src={testimonial.avatar} />\n <AvatarFallback>{testimonial.name[0]}</AvatarFallback>\n </Avatar>\n <div>\n <div className=\"font-medium text-foreground\">{testimonial.name}</div>\n <div className=\"text-sm text-muted-foreground\">{testimonial.role}</div>\n </div>\n </div>\n </CardContent>\n </Card>\n </StaggerItem>\n ))}\n </StaggerContainer>\n </div>\n </section>\n\n {/* Pricing Preview */}\n <section className=\"py-12 md:py-16\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <FadeIn className=\"text-center mb-16\">\n <p className=\"text-sm font-medium text-primary mb-2 uppercase tracking-wide\">\n {t(\"pricingLabel\")}\n </p>\n <h2 className=\"text-3xl md:text-4xl font-bold text-foreground mb-4\">\n {t(\"pricingTitle\")}\n </h2>\n </FadeIn>\n\n <StaggerContainer className=\"grid md:grid-cols-3 gap-8 max-w-4xl mx-auto\">\n {pricingPlans.map((plan, index) => (\n <StaggerItem key={index}>\n <Card className={cn(\"h-full relative\", plan.popular && \"border-primary shadow-lg\")}>\n {plan.popular && (\n <div className=\"absolute -top-3 left-1/2 -translate-x-1/2\">\n <Badge>{t(\"mostPopular\")}</Badge>\n </div>\n )}\n <CardContent className=\"p-6 pt-8\">\n <h3 className=\"text-lg font-semibold mb-2\">{plan.name}</h3>\n <div className=\"text-3xl font-bold mb-4\">{plan.price}<span className=\"text-sm font-normal text-muted-foreground\">/mo</span></div>\n <ul className=\"space-y-2 mb-6\">\n {plan.features.map((feature, i) => (\n <li key={i} className=\"flex items-center gap-2 text-sm\">\n <Check className=\"h-4 w-4 text-primary\" />\n {feature}\n </li>\n ))}\n </ul>\n <Button className=\"w-full\" variant={plan.popular ? \"default\" : \"outline\"}>\n {t(\"getStarted\")}\n </Button>\n </CardContent>\n </Card>\n </StaggerItem>\n ))}\n </StaggerContainer>\n\n <FadeIn className=\"text-center mt-8\">\n <Link to=\"/pricing\" className=\"text-primary hover:underline inline-flex items-center gap-1\">\n {t(\"viewAllPlans\")}\n <ArrowRight className=\"h-4 w-4\" />\n </Link>\n </FadeIn>\n </div>\n </section>\n\n {/* CTA Section */}\n <section className=\"py-12 md:py-16 bg-primary text-primary-foreground\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 text-center\">\n <FadeIn>\n <h2 className=\"text-3xl md:text-4xl lg:text-5xl font-bold mb-6\">\n {t(\"ctaTitle\")}\n </h2>\n <p className=\"text-lg opacity-90 mb-8 max-w-2xl mx-auto\">\n {t(\"ctaDescription\")}\n </p>\n <Button asChild size=\"lg\" variant=\"secondary\" className=\"text-base\">\n <Link to=\"/register\">\n {t(\"ctaButton\")}\n <ArrowRight className=\"ml-2 h-5 w-5\" />\n </Link>\n </Button>\n </FadeIn>\n </div>\n </section>\n </div>\n </Layout>\n );\n}\n\nexport default LandingPageSaas;\n"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "landing-page-saas/lang/en.json",
|
|
29
|
+
"type": "registry:lang",
|
|
30
|
+
"target": "$modules$/landing-page-saas/lang/en.json",
|
|
31
|
+
"content": "{\r\n \"title\": \"SaaS Landing\",\r\n \"badge\": \"New Feature Available\",\r\n \"heroTitle\": \"Your Product Name Here\",\r\n \"heroDescription\": \"Ask Promake to customize this description based on your product. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\r\n \"startFree\": \"Start Free Trial\",\r\n \"watchDemo\": \"Watch Demo\",\r\n \"noCreditCard\": \"No credit card required\",\r\n \"stat1Label\": \"Active Users\",\r\n \"stat2Label\": \"Uptime\",\r\n \"stat3Label\": \"Tasks Done\",\r\n \"stat4Label\": \"Rating\",\r\n \"featuresLabel\": \"Features\",\r\n \"featuresTitle\": \"Everything You Need\",\r\n \"featuresDescription\": \"Ask Promake to customize this features description. Sed do eiusmod tempor incididunt ut labore.\",\r\n \"feature1Title\": \"Feature One\",\r\n \"feature1Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature2Title\": \"Feature Two\",\r\n \"feature2Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature3Title\": \"Feature Three\",\r\n \"feature3Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature4Title\": \"Feature Four\",\r\n \"feature4Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature5Title\": \"Feature Five\",\r\n \"feature5Desc\": \"Ask Promake to customize this feature description.\",\r\n \"feature6Title\": \"Feature Six\",\r\n \"feature6Desc\": \"Ask Promake to customize this feature description.\",\r\n \"testimonialsLabel\": \"Testimonials\",\r\n \"testimonialsTitle\": \"What Our Customers Say\",\r\n \"testimonial1\": \"Ask Promake to add real customer testimonials here. Lorem ipsum dolor sit amet.\",\r\n \"testimonial2\": \"Ask Promake to add real customer testimonials here. Consectetur adipiscing elit.\",\r\n \"testimonial3\": \"Ask Promake to add real customer testimonials here. Sed do eiusmod tempor.\",\r\n \"pricingLabel\": \"Pricing\",\r\n \"pricingTitle\": \"Simple, Transparent Pricing\",\r\n \"starterPlan\": \"Starter\",\r\n \"proPlan\": \"Pro\",\r\n \"enterprisePlan\": \"Enterprise\",\r\n \"custom\": \"Custom\",\r\n \"mostPopular\": \"Most Popular\",\r\n \"getStarted\": \"Get Started\",\r\n \"viewAllPlans\": \"View all pricing plans\",\r\n \"ctaTitle\": \"Ready to Get Started?\",\r\n \"ctaDescription\": \"Ask Promake to customize this CTA description based on your goals. Lorem ipsum dolor sit amet.\",\r\n \"ctaButton\": \"Get Started\"\r\n}\r\n"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"path": "landing-page-saas/lang/tr.json",
|
|
35
|
+
"type": "registry:lang",
|
|
36
|
+
"target": "$modules$/landing-page-saas/lang/tr.json",
|
|
37
|
+
"content": "{\r\n \"title\": \"SaaS Landing\",\r\n \"badge\": \"Artık AI destekli özelliklerle\",\r\n \"heroTitle\": \"Daha İyi Ürünler, Her Zamankinden Daha Hızlı Oluşturun\",\r\n \"heroDescription\": \"Ekiplerin işbirliği yapmasına, iş akışlarını otomatikleştirmesine ve ürünleri 10 kat daha hızlı teslim etmesine yardımcı olan hepsi bir arada platform.\",\r\n \"startFree\": \"Ücretsiz Deneyin\",\r\n \"watchDemo\": \"Demo İzle\",\r\n \"noCreditCard\": \"Kredi kartı gerekmez • 14 gün ücretsiz deneme\",\r\n \"stat1Label\": \"Aktif Kullanıcı\",\r\n \"stat2Label\": \"Çalışma Süresi\",\r\n \"stat3Label\": \"Tamamlanan Görev\",\r\n \"stat4Label\": \"Kullanıcı Puanı\",\r\n \"featuresLabel\": \"Özellikler\",\r\n \"featuresTitle\": \"Başarı İçin İhtiyacınız Olan Her Şey\",\r\n \"featuresDescription\": \"Ekibinizin daha akıllı çalışmasına yardımcı olmak için tasarlanmış güçlü araçlar.\",\r\n \"feature1Title\": \"Işık Hızında\",\r\n \"feature1Desc\": \"Son teknoloji ile hız için inşa edildi\",\r\n \"feature2Title\": \"Varsayılan Olarak Güvenli\",\r\n \"feature2Desc\": \"Verileriniz için kurumsal düzeyde güvenlik\",\r\n \"feature3Title\": \"Güçlü Analitik\",\r\n \"feature3Desc\": \"İşinizi yönlendirmek için derin içgörüler\",\r\n \"feature4Title\": \"Takım İşbirliği\",\r\n \"feature4Desc\": \"Gerçek zamanlı olarak sorunsuz çalışın\",\r\n \"feature5Title\": \"7/24 Kullanılabilirlik\",\r\n \"feature5Desc\": \"%99.9 çalışma süresi garantisi\",\r\n \"feature6Title\": \"AI Destekli\",\r\n \"feature6Desc\": \"Sizden öğrenen akıllı otomasyon\",\r\n \"testimonialsLabel\": \"Referanslar\",\r\n \"testimonialsTitle\": \"Dünya Genelinde Ekipler Tarafından Seviliyor\",\r\n \"testimonial1\": \"Bu platform çalışma şeklimizi dönüştürdü. Sadece haftalar içinde inanılmaz sonuçlar.\",\r\n \"testimonial2\": \"Yaptığımız en iyi yatırım. Verimliliğimiz bir gecede iki katına çıktı.\",\r\n \"testimonial3\": \"Sezgisel, güçlü ve güvenilir. İhtiyacımız olan her şey ve daha fazlası.\",\r\n \"pricingLabel\": \"Fiyatlandırma\",\r\n \"pricingTitle\": \"Basit, Şeffaf Fiyatlandırma\",\r\n \"starterPlan\": \"Başlangıç\",\r\n \"proPlan\": \"Pro\",\r\n \"enterprisePlan\": \"Kurumsal\",\r\n \"custom\": \"Özel\",\r\n \"mostPopular\": \"En Popüler\",\r\n \"getStarted\": \"Başlayın\",\r\n \"viewAllPlans\": \"Tüm planları görüntüle\",\r\n \"ctaTitle\": \"İş Akışınızı Dönüştürmeye Hazır mısınız?\",\r\n \"ctaDescription\": \"Harika ürünler oluşturmak için platformumuzu kullanan binlerce ekibe katılın.\",\r\n \"ctaButton\": \"Ücretsiz Denemenizi Başlatın\"\r\n}\r\n"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"exports": {
|
|
41
|
+
"types": [],
|
|
42
|
+
"variables": [
|
|
43
|
+
"LandingPageSaas",
|
|
44
|
+
"default"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
"name": "login-page-split",
|
|
3
3
|
"type": "registry:page",
|
|
4
4
|
"title": "Login Page Split",
|
|
5
|
-
"description": "Split-screen login page with form on the left and full-height image on the right. Features email/password fields,
|
|
5
|
+
"description": "Split-screen login page with form on the left and full-height image on the right. Features email/password fields, remember me checkbox, sign up and forgot password links. Uses customerClient for API and useAuthStore for state management.",
|
|
6
6
|
"registryDependencies": [
|
|
7
7
|
"input",
|
|
8
|
-
"button"
|
|
8
|
+
"button",
|
|
9
|
+
"checkbox",
|
|
10
|
+
"auth-core",
|
|
11
|
+
"api"
|
|
9
12
|
],
|
|
10
|
-
"usage": "import LoginPageSplit from '@/modules/login-page-split';\n\n<LoginPageSplit\n image=\"/images/login-bg.jpg\"\n/>\n\n• Installed at: src/modules/login-page-split/\n• Customize text: src/modules/login-page-split/lang/*.json\n• Add to your router as a page component",
|
|
13
|
+
"usage": "import LoginPageSplit from '@/modules/login-page-split';\n\n<LoginPageSplit\n image=\"/images/login-bg.jpg\"\n/>\n\n• Installed at: src/modules/login-page-split/\n• Customize text: src/modules/login-page-split/lang/*.json\n• API Integration:\n - customerClient.auth.login() for authentication\n - useAuthStore for state management\n - Automatically sets token after login\n• Supports remember me (7 days vs default)\n• Redirects to previous page after login\n• Add to your router as a page component",
|
|
11
14
|
"route": {
|
|
12
15
|
"path": "/login",
|
|
13
16
|
"componentName": "LoginPageSplit"
|
|
@@ -17,13 +20,13 @@
|
|
|
17
20
|
"path": "login-page-split/index.ts",
|
|
18
21
|
"type": "registry:index",
|
|
19
22
|
"target": "$modules$/login-page-split/index.ts",
|
|
20
|
-
"content": "export * from './login-page-split';\r\n"
|
|
23
|
+
"content": "export * from './login-page-split';\r\nexport { default } from './login-page-split';\r\n"
|
|
21
24
|
},
|
|
22
25
|
{
|
|
23
26
|
"path": "login-page-split/login-page-split.tsx",
|
|
24
|
-
"type": "registry:
|
|
27
|
+
"type": "registry:page",
|
|
25
28
|
"target": "$modules$/login-page-split/login-page-split.tsx",
|
|
26
|
-
"content": "import { Link } from \"react-router\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { useTranslation } from \"react-i18next\";\r\n\r\ninterface LoginPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport
|
|
29
|
+
"content": "import { useState } from \"react\";\r\nimport { Link, useNavigate, useLocation } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Checkbox } from \"@/components/ui/checkbox\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuthStore } from \"@/modules/auth-core/auth-store\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\nimport { getErrorMessage } from \"@/modules/api/get-error-message\";\r\n\r\ninterface LoginPageSplitProps {\r\n image?: string;\r\n}\r\n\r\nexport function LoginPageSplit({\r\n image = \"/images/placeholder.png\",\r\n}: LoginPageSplitProps) {\r\n const { t } = useTranslation(\"login-page-split\");\r\n const navigate = useNavigate();\r\n const location = useLocation();\r\n const setAuth = useAuthStore((state) => state.setAuth);\r\n\r\n const [email, setEmail] = useState(\"\");\r\n const [password, setPassword] = useState(\"\");\r\n const [rememberMe, setRememberMe] = useState(false);\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n // Get redirect URL from location state or default to home\r\n const from = (location.state as { from?: string })?.from || \"/\";\r\n\r\n const handleSubmit = async (e: React.FormEvent) => {\r\n e.preventDefault();\r\n setError(null);\r\n setIsLoading(true);\r\n\r\n try {\r\n const response = await customerClient.auth.login({\r\n username: email,\r\n password,\r\n expiresInMins: rememberMe ? 60 * 24 * 7 : undefined, // 7 days if remember me\r\n });\r\n\r\n // Set auth state\r\n setAuth(\r\n { username: email, email: (response as any).email || email },\r\n {\r\n accessToken: response.accessToken,\r\n refreshToken: response.refreshToken,\r\n idToken: response.idToken,\r\n encryptionKey: response.encryptionKey,\r\n expiresAt: response.expiresIn\r\n ? Date.now() + response.expiresIn * 1000\r\n : undefined,\r\n }\r\n );\r\n\r\n // Set token for API client\r\n customerClient.setToken(response.accessToken);\r\n\r\n toast.success(t(\"loginSuccess\", \"Login successful!\"));\r\n navigate(from, { replace: true });\r\n } catch (err) {\r\n const errorMessage = getErrorMessage(\r\n err,\r\n t(\"loginError\", \"Login failed. Please check your credentials.\")\r\n );\r\n setError(errorMessage);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <section className=\"w-full md:grid md:min-h-screen md:grid-cols-2\">\r\n <div className=\"flex items-center justify-center px-4 py-12\">\r\n <div className=\"mx-auto grid w-full max-w-sm gap-6\">\r\n <Logo />\r\n <hr />\r\n <div>\r\n <h1 className=\"text-xl font-bold tracking-tight\">\r\n {t(\"title\", \"Login\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Enter your details below to login\")}\r\n </p>\r\n </div>\r\n\r\n {error && (\r\n <div className=\"p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950 dark:text-red-400 rounded-md\">\r\n {error}\r\n </div>\r\n )}\r\n\r\n <form onSubmit={handleSubmit} className=\"grid gap-4\">\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"email-split\">{t(\"email\", \"Email\")}</Label>\r\n <Input\r\n required\r\n id=\"email-split\"\r\n type=\"email\"\r\n autoComplete=\"username\"\r\n placeholder={t(\"emailPlaceholder\", \"email@example.com\")}\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"password-split\">{t(\"password\", \"Password\")}</Label>\r\n <Input\r\n required\r\n id=\"password-split\"\r\n type=\"password\"\r\n placeholder=\"••••••••••\"\r\n autoComplete=\"current-password\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n disabled={isLoading}\r\n />\r\n </div>\r\n\r\n <div className=\"flex items-center gap-2\">\r\n <Checkbox\r\n id=\"remember-me\"\r\n checked={rememberMe}\r\n onCheckedChange={(checked) => setRememberMe(checked as boolean)}\r\n disabled={isLoading}\r\n />\r\n <Label htmlFor=\"remember-me\" className=\"text-sm font-normal cursor-pointer\">\r\n {t(\"rememberMe\", \"Remember me\")}\r\n </Label>\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\r\n {isLoading ? (\r\n <>\r\n <div className=\"w-4 h-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin mr-2\" />\r\n {t(\"loggingIn\", \"Logging in...\")}\r\n </>\r\n ) : (\r\n t(\"login\", \"Login\")\r\n )}\r\n </Button>\r\n </form>\r\n\r\n <div className=\"flex flex-col gap-4 text-sm\">\r\n <p>\r\n {t(\"noAccount\", \"Don't have an account?\")}{\" \"}\r\n <Link to=\"/register\" className=\"underline\">\r\n {t(\"signUp\", \"Sign up\")}\r\n </Link>\r\n </p>\r\n <Link to=\"/forgot-password\" className=\"underline\">\r\n {t(\"forgotPassword\", \"Forgot your password?\")}\r\n </Link>\r\n </div>\r\n <hr />\r\n <p className=\"text-sm text-muted-foreground\">\r\n © {new Date().getFullYear()} {t(\"copyright\", \"All rights reserved.\")}\r\n </p>\r\n </div>\r\n </div>\r\n <div className=\"hidden p-4 md:block\">\r\n <img\r\n loading=\"lazy\"\r\n decoding=\"async\"\r\n width=\"1920\"\r\n height=\"1080\"\r\n alt={t(\"imageAlt\", \"Login background\")}\r\n src={image}\r\n className=\"size-full rounded-lg border bg-muted object-cover object-center\"\r\n />\r\n </div>\r\n </section>\r\n );\r\n}\r\n\r\nexport default LoginPageSplit;\r\n"
|
|
27
30
|
},
|
|
28
31
|
{
|
|
29
32
|
"path": "login-page-split/lang/en.json",
|
|
@@ -41,7 +44,8 @@
|
|
|
41
44
|
"exports": {
|
|
42
45
|
"types": [],
|
|
43
46
|
"variables": [
|
|
44
|
-
"LoginPageSplit"
|
|
47
|
+
"LoginPageSplit",
|
|
48
|
+
"default"
|
|
45
49
|
]
|
|
46
50
|
}
|
|
47
51
|
}
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"name": "login-page",
|
|
3
3
|
"type": "registry:page",
|
|
4
4
|
"title": "Login Page",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Presentational login page with email/password form, social OAuth buttons (Google, Microsoft), forgot password link, and create account link. Centered card layout with responsive design. Requires parent to handle authentication via callbacks.",
|
|
6
6
|
"registryDependencies": [
|
|
7
7
|
"button",
|
|
8
8
|
"input",
|
|
9
9
|
"label"
|
|
10
10
|
],
|
|
11
|
-
"usage": "import LoginPage from '@/modules/login-page';\n\n<LoginPage\n onSubmit={(email, password) =>
|
|
11
|
+
"usage": "import LoginPage from '@/modules/login-page';\n\n// Presentational component - handle auth in parent\n<LoginPage\n onSubmit={(email, password) => {\n // Handle login API call here\n }}\n onGoogleLogin={() => {\n // Handle Google OAuth\n }}\n onMicrosoftLogin={() => {\n // Handle Microsoft OAuth\n }}\n/>\n\n• Installed at: src/modules/login-page/\n• Customize text: src/modules/login-page/lang/*.json\n• This is a presentational component - no built-in API calls\n• Parent must handle authentication via callbacks\n• For built-in API integration, use login-page-split instead",
|
|
12
12
|
"route": {
|
|
13
13
|
"path": "/login",
|
|
14
14
|
"componentName": "LoginPage"
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
"path": "login-page/index.ts",
|
|
19
19
|
"type": "registry:index",
|
|
20
20
|
"target": "$modules$/login-page/index.ts",
|
|
21
|
-
"content": "export
|
|
21
|
+
"content": "export * from './login-page';\r\nexport { default } from './login-page';\r\n"
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"path": "login-page/login-page.tsx",
|
|
25
25
|
"type": "registry:page",
|
|
26
26
|
"target": "$modules$/login-page/login-page.tsx",
|
|
27
|
-
"content": "import { useState } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface LoginPageProps {\r\n onSubmit?: (email: string, password: string) => void;\r\n onGoogleLogin?: () => void;\r\n onMicrosoftLogin?: () => void;\r\n className?: string;\r\n}\r\n\r\nexport
|
|
27
|
+
"content": "import { useState } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Logo } from \"@/components/Logo\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface LoginPageProps {\r\n onSubmit?: (email: string, password: string) => void;\r\n onGoogleLogin?: () => void;\r\n onMicrosoftLogin?: () => void;\r\n className?: string;\r\n}\r\n\r\nexport function LoginPage({\r\n onSubmit,\r\n onGoogleLogin,\r\n onMicrosoftLogin,\r\n className,\r\n}: LoginPageProps) {\r\n const { t } = useTranslation(\"login-page\");\r\n usePageTitle({ title: t(\"title\", \"Sign In\") });\r\n const [email, setEmail] = useState(\"\");\r\n const [password, setPassword] = useState(\"\");\r\n\r\n const handleSubmit = (e: React.FormEvent) => {\r\n e.preventDefault();\r\n onSubmit?.(email, password);\r\n };\r\n\r\n return (\r\n <section className={cn(\"flex min-h-screen bg-muted/30 px-4 py-16 md:py-32\", className)}>\r\n <form\r\n onSubmit={handleSubmit}\r\n className=\"bg-muted m-auto h-fit w-full max-w-sm overflow-hidden rounded-xl border shadow-md\"\r\n >\r\n <div className=\"bg-card -m-px rounded-xl border p-8 pb-6\">\r\n <div className=\"text-center\">\r\n <Link to=\"/\" aria-label=\"go home\" className=\"mx-auto block w-fit\">\r\n <Logo size=\"sm\" />\r\n </Link>\r\n <h1 className=\"mb-1 mt-4 text-xl font-semibold\">\r\n {t(\"title\", \"Sign In\")}\r\n </h1>\r\n <p className=\"text-sm text-muted-foreground\">\r\n {t(\"subtitle\", \"Welcome back! Sign in to continue\")}\r\n </p>\r\n </div>\r\n\r\n <div className=\"mt-6 space-y-6\">\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"email\" className=\"block text-sm\">\r\n {t(\"email\", \"Email\")}\r\n </Label>\r\n <Input\r\n type=\"email\"\r\n required\r\n name=\"email\"\r\n id=\"email\"\r\n value={email}\r\n onChange={(e) => setEmail(e.target.value)}\r\n placeholder={t(\"emailPlaceholder\", \"you@example.com\")}\r\n />\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <div className=\"flex items-center justify-between\">\r\n <Label htmlFor=\"password\" className=\"text-sm\">\r\n {t(\"password\", \"Password\")}\r\n </Label>\r\n <Button asChild variant=\"link\" size=\"sm\" className=\"h-auto p-0\">\r\n <Link to=\"/forgot-password\" className=\"text-xs\">\r\n {t(\"forgotPassword\", \"Forgot password?\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n <Input\r\n type=\"password\"\r\n required\r\n name=\"password\"\r\n id=\"password\"\r\n value={password}\r\n onChange={(e) => setPassword(e.target.value)}\r\n placeholder=\"••••••••\"\r\n />\r\n </div>\r\n\r\n <Button type=\"submit\" className=\"w-full\">\r\n {t(\"signIn\", \"Sign In\")}\r\n </Button>\r\n </div>\r\n\r\n <div className=\"my-6 grid grid-cols-[1fr_auto_1fr] items-center gap-3\">\r\n <hr className=\"border-dashed\" />\r\n <span className=\"text-muted-foreground text-xs\">\r\n {t(\"orContinueWith\", \"Or continue with\")}\r\n </span>\r\n <hr className=\"border-dashed\" />\r\n </div>\r\n\r\n <div className=\"grid grid-cols-2 gap-3\">\r\n <Button type=\"button\" variant=\"outline\" onClick={onGoogleLogin}>\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"0.98em\"\r\n height=\"1em\"\r\n viewBox=\"0 0 256 262\"\r\n className=\"mr-2\"\r\n >\r\n <path\r\n fill=\"#4285f4\"\r\n d=\"M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027\"\r\n />\r\n <path\r\n fill=\"#34a853\"\r\n d=\"M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1\"\r\n />\r\n <path\r\n fill=\"#fbbc05\"\r\n d=\"M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z\"\r\n />\r\n <path\r\n fill=\"#eb4335\"\r\n d=\"M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251\"\r\n />\r\n </svg>\r\n <span>Google</span>\r\n </Button>\r\n <Button type=\"button\" variant=\"outline\" onClick={onMicrosoftLogin}>\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"1em\"\r\n height=\"1em\"\r\n viewBox=\"0 0 256 256\"\r\n className=\"mr-2\"\r\n >\r\n <path fill=\"#f1511b\" d=\"M121.666 121.666H0V0h121.666z\" />\r\n <path fill=\"#80cc28\" d=\"M256 121.666H134.335V0H256z\" />\r\n <path fill=\"#00adef\" d=\"M121.663 256.002H0V134.336h121.663z\" />\r\n <path fill=\"#fbbc09\" d=\"M256 256.002H134.335V134.336H256z\" />\r\n </svg>\r\n <span>Microsoft</span>\r\n </Button>\r\n </div>\r\n </div>\r\n\r\n <div className=\"p-3\">\r\n <p className=\"text-center text-sm text-muted-foreground\">\r\n {t(\"noAccount\", \"Don't have an account?\")}\r\n <Button asChild variant=\"link\" className=\"px-2\">\r\n <Link to=\"/register\">{t(\"createAccount\", \"Create account\")}</Link>\r\n </Button>\r\n </p>\r\n </div>\r\n </form>\r\n </section>\r\n );\r\n}\r\n\r\nexport default LoginPage;\r\n"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "login-page/lang/en.json",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "logo-cloud",
|
|
3
|
+
"type": "registry:component",
|
|
4
|
+
"title": "Logo Cloud",
|
|
5
|
+
"description": "Partner/client logo marquee with infinite scroll animation. Displays logos in a smooth horizontal scroll with gradient fade edges, grayscale hover effect, and pause on hover.",
|
|
6
|
+
"dependencies": [],
|
|
7
|
+
"registryDependencies": [],
|
|
8
|
+
"usage": "import { LogoCloud } from '@/modules/logo-cloud';\n\n<LogoCloud />\n<LogoCloud speed=\"normal\" />\n<LogoCloud animate={false} />\n\n• Installed at: src/modules/logo-cloud/\n• Props: logos[], title, grayscale, animate, speed (slow|normal|fast), pauseOnHover",
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "logo-cloud/logo-cloud.tsx",
|
|
12
|
+
"type": "registry:component",
|
|
13
|
+
"target": "$modules$/logo-cloud/logo-cloud.tsx",
|
|
14
|
+
"content": "\"use client\";\r\n\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Logo {\r\n name: string;\r\n image: string;\r\n url?: string;\r\n}\r\n\r\ninterface LogoCloudProps {\r\n logos?: Logo[];\r\n title?: string;\r\n grayscale?: boolean;\r\n animate?: boolean;\r\n speed?: \"slow\" | \"normal\" | \"fast\";\r\n pauseOnHover?: boolean;\r\n className?: string;\r\n}\r\n\r\nconst defaultLogos: Logo[] = [\r\n { name: \"Company 1\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 2\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 3\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 4\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 5\", image: \"/images/placeholder.png\" },\r\n { name: \"Company 6\", image: \"/images/placeholder.png\" },\r\n];\r\n\r\nconst speedDuration = {\r\n slow: \"40s\",\r\n normal: \"25s\",\r\n fast: \"15s\",\r\n};\r\n\r\nexport function LogoCloud({\r\n logos = defaultLogos,\r\n title,\r\n grayscale = true,\r\n animate = true,\r\n speed = \"slow\",\r\n pauseOnHover = true,\r\n className,\r\n}: LogoCloudProps) {\r\n const { t } = useTranslation(\"logo-cloud\");\r\n\r\n const displayTitle = title ?? t(\"title\");\r\n\r\n const renderLogo = (logo: Logo, index: number) => {\r\n const imageElement = (\r\n <img\r\n src={logo.image}\r\n alt={logo.name}\r\n className={cn(\r\n \"h-10 md:h-12 w-auto object-contain transition-all duration-300\",\r\n grayscale && \"grayscale opacity-60 hover:grayscale-0 hover:opacity-100\"\r\n )}\r\n draggable={false}\r\n />\r\n );\r\n\r\n if (logo.url) {\r\n return (\r\n <a\r\n key={index}\r\n href={logo.url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"flex items-center justify-center px-8 md:px-12 shrink-0\"\r\n >\r\n {imageElement}\r\n </a>\r\n );\r\n }\r\n\r\n return (\r\n <div key={index} className=\"flex items-center justify-center px-8 md:px-12 shrink-0\">\r\n {imageElement}\r\n </div>\r\n );\r\n };\r\n\r\n return (\r\n <section className={cn(\"py-12 md:py-16 overflow-hidden\", className)}>\r\n <div className=\"container max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {displayTitle && (\r\n <p className=\"text-center text-sm font-medium text-muted-foreground uppercase tracking-wider mb-8\">\r\n {displayTitle}\r\n </p>\r\n )}\r\n </div>\r\n\r\n {/* Marquee Container */}\r\n <div\r\n className={cn(\r\n \"relative w-full\",\r\n pauseOnHover && \"[&:hover_.marquee-content]:pause\"\r\n )}\r\n >\r\n {/* Gradient Masks */}\r\n <div className=\"absolute left-0 top-0 bottom-0 w-20 md:w-32 bg-gradient-to-r from-background to-transparent z-10 pointer-events-none\" />\r\n <div className=\"absolute right-0 top-0 bottom-0 w-20 md:w-32 bg-gradient-to-l from-background to-transparent z-10 pointer-events-none\" />\r\n\r\n {/* Scrolling Content */}\r\n <div className=\"flex overflow-hidden\">\r\n <div\r\n className={cn(\r\n \"marquee-content flex items-center\",\r\n animate && \"animate-marquee\"\r\n )}\r\n style={{\r\n [\"--marquee-duration\" as string]: speedDuration[speed],\r\n }}\r\n >\r\n {/* First set of logos */}\r\n {logos.map((logo, index) => renderLogo(logo, index))}\r\n {/* Duplicate for seamless loop */}\r\n {logos.map((logo, index) => renderLogo(logo, index + logos.length))}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <style>{`\r\n @keyframes marquee {\r\n 0% {\r\n transform: translateX(0);\r\n }\r\n 100% {\r\n transform: translateX(-50%);\r\n }\r\n }\r\n .animate-marquee {\r\n animation: marquee var(--marquee-duration, 25s) linear infinite;\r\n }\r\n .pause {\r\n animation-play-state: paused !important;\r\n }\r\n `}</style>\r\n </section>\r\n );\r\n}\r\n"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"path": "logo-cloud/index.ts",
|
|
18
|
+
"type": "registry:index",
|
|
19
|
+
"target": "$modules$/logo-cloud/index.ts",
|
|
20
|
+
"content": "export * from \"./logo-cloud\";\r\n"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "logo-cloud/lang/en.json",
|
|
24
|
+
"type": "registry:lang",
|
|
25
|
+
"target": "$modules$/logo-cloud/lang/en.json",
|
|
26
|
+
"content": "{\r\n \"title\": \"Trusted by leading companies\"\r\n}\r\n"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "logo-cloud/lang/tr.json",
|
|
30
|
+
"type": "registry:lang",
|
|
31
|
+
"target": "$modules$/logo-cloud/lang/tr.json",
|
|
32
|
+
"content": "{\r\n \"title\": \"Önde gelen şirketler tarafından tercih ediliyor\"\r\n}\r\n"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"exports": {
|
|
36
|
+
"types": [],
|
|
37
|
+
"variables": [
|
|
38
|
+
"LogoCloud"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "masonry-grid",
|
|
3
|
+
"type": "registry:block",
|
|
4
|
+
"title": "Masonry Grid",
|
|
5
|
+
"description": "Pinterest-style masonry grid for images with responsive columns, hover zoom effect, and lightbox modal. Supports tall, wide, and normal image spans. Uses motion/react for smooth animations.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"motion"
|
|
8
|
+
],
|
|
9
|
+
"registryDependencies": [],
|
|
10
|
+
"usage": "import { MasonryGrid } from '@/modules/masonry-grid';\n\nconst images = [\n { id: '1', src: '/img1.jpg', alt: 'Image 1', span: 'tall' },\n { id: '2', src: '/img2.jpg', alt: 'Image 2', span: 'normal' },\n];\n\n<MasonryGrid items={images} columns={3} />\n\n• Spans: tall, wide, normal\n• Columns: 2, 3, or 4\n• Click to open lightbox",
|
|
11
|
+
"files": [
|
|
12
|
+
{
|
|
13
|
+
"path": "masonry-grid/index.ts",
|
|
14
|
+
"type": "registry:index",
|
|
15
|
+
"target": "$modules$/masonry-grid/index.ts",
|
|
16
|
+
"content": "export { MasonryGrid } from \"./masonry-grid\";\r\n"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "masonry-grid/masonry-grid.tsx",
|
|
20
|
+
"type": "registry:block",
|
|
21
|
+
"target": "$modules$/masonry-grid/masonry-grid.tsx",
|
|
22
|
+
"content": "import { useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { X, ZoomIn } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, AnimatePresence } from \"motion/react\";\n\ninterface MasonryItem {\n id: string;\n src: string;\n alt: string;\n width?: number;\n height?: number;\n span?: \"tall\" | \"wide\" | \"normal\";\n}\n\ninterface MasonryGridProps {\n items?: MasonryItem[];\n columns?: 2 | 3 | 4;\n className?: string;\n}\n\nconst defaultItems: MasonryItem[] = [\n { id: \"1\", src: \"/images/placeholder.png\", alt: \"Image 1\", span: \"tall\" },\n { id: \"2\", src: \"/images/placeholder.png\", alt: \"Image 2\", span: \"normal\" },\n { id: \"3\", src: \"/images/placeholder.png\", alt: \"Image 3\", span: \"normal\" },\n { id: \"4\", src: \"/images/placeholder.png\", alt: \"Image 4\", span: \"wide\" },\n { id: \"5\", src: \"/images/placeholder.png\", alt: \"Image 5\", span: \"normal\" },\n { id: \"6\", src: \"/images/placeholder.png\", alt: \"Image 6\", span: \"tall\" },\n { id: \"7\", src: \"/images/placeholder.png\", alt: \"Image 7\", span: \"normal\" },\n { id: \"8\", src: \"/images/placeholder.png\", alt: \"Image 8\", span: \"normal\" },\n];\n\nexport function MasonryGrid({\n items = defaultItems,\n columns = 3,\n className,\n}: MasonryGridProps) {\n const { t } = useTranslation(\"masonry-grid\");\n const [selectedImage, setSelectedImage] = useState<MasonryItem | null>(null);\n\n const getSpanClass = (span?: string) => {\n switch (span) {\n case \"tall\":\n return \"row-span-2\";\n case \"wide\":\n return \"col-span-2\";\n default:\n return \"\";\n }\n };\n\n const getColumnsClass = () => {\n switch (columns) {\n case 2:\n return \"grid-cols-1 sm:grid-cols-2\";\n case 4:\n return \"grid-cols-2 sm:grid-cols-3 lg:grid-cols-4\";\n default:\n return \"grid-cols-2 sm:grid-cols-2 lg:grid-cols-3\";\n }\n };\n\n return (\n <>\n <section className={cn(\"py-12 md:py-20\", className)}>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\n <div className={cn(\"grid gap-4 auto-rows-[200px] [grid-auto-flow:dense]\", getColumnsClass())}>\n {items.map((item, index) => (\n <motion.div\n key={item.id}\n initial={{ opacity: 0, y: 20 }}\n whileInView={{ opacity: 1, y: 0 }}\n viewport={{ once: true }}\n transition={{ delay: index * 0.05 }}\n className={cn(\n \"group relative overflow-hidden rounded-xl bg-muted cursor-pointer\",\n getSpanClass(item.span)\n )}\n onClick={() => setSelectedImage(item)}\n >\n <div className=\"w-full h-full\">\n <img\n src={item.src}\n alt={item.alt}\n loading=\"lazy\"\n className=\"w-full h-full object-cover transition-transform duration-500 group-hover:scale-110\"\n onError={(e) => {\n e.currentTarget.src = \"/images/placeholder.png\";\n }}\n />\n </div>\n\n {/* Hover overlay */}\n <div className=\"absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors duration-300 flex items-center justify-center\">\n <div className=\"opacity-0 group-hover:opacity-100 transition-opacity duration-300\">\n <div className=\"w-12 h-12 rounded-full bg-white/90 flex items-center justify-center\">\n <ZoomIn className=\"h-5 w-5 text-foreground\" />\n </div>\n </div>\n </div>\n </motion.div>\n ))}\n </div>\n </div>\n </section>\n\n {/* Lightbox */}\n <AnimatePresence>\n {selectedImage && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/90 p-4\"\n onClick={() => setSelectedImage(null)}\n >\n <button\n onClick={() => setSelectedImage(null)}\n className=\"absolute top-4 right-4 w-10 h-10 rounded-full bg-white/10 hover:bg-white/20 flex items-center justify-center transition-colors\"\n aria-label={t(\"close\", \"Close\")}\n >\n <X className=\"h-6 w-6 text-white\" />\n </button>\n\n <motion.img\n initial={{ scale: 0.9, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0.9, opacity: 0 }}\n transition={{ type: \"spring\", damping: 25 }}\n src={selectedImage.src}\n alt={selectedImage.alt}\n className=\"max-w-full max-h-[90vh] object-contain rounded-lg\"\n onClick={(e) => e.stopPropagation()}\n />\n </motion.div>\n )}\n </AnimatePresence>\n </>\n );\n}\n"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"path": "masonry-grid/lang/en.json",
|
|
26
|
+
"type": "registry:lang",
|
|
27
|
+
"target": "$modules$/masonry-grid/lang/en.json",
|
|
28
|
+
"content": "{\r\n \"close\": \"Close\"\r\n}\r\n"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": "masonry-grid/lang/tr.json",
|
|
32
|
+
"type": "registry:lang",
|
|
33
|
+
"target": "$modules$/masonry-grid/lang/tr.json",
|
|
34
|
+
"content": "{\r\n \"close\": \"Kapat\"\r\n}\r\n"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"exports": {
|
|
38
|
+
"types": [],
|
|
39
|
+
"variables": [
|
|
40
|
+
"MasonryGrid"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-orders-page",
|
|
3
|
+
"type": "registry:page",
|
|
4
|
+
"title": "My Orders Page",
|
|
5
|
+
"description": "User orders listing page with order cards showing status, date, total, and items. Requires authentication and fetches orders from API. Features loading skeleton and empty state.",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"button",
|
|
8
|
+
"badge",
|
|
9
|
+
"skeleton",
|
|
10
|
+
"auth-core",
|
|
11
|
+
"api",
|
|
12
|
+
"ecommerce-core"
|
|
13
|
+
],
|
|
14
|
+
"usage": "import MyOrdersPage from '@/modules/my-orders-page';\n\n<MyOrdersPage />\n\n• Installed at: src/modules/my-orders-page/\n• Customize text: src/modules/my-orders-page/lang/*.json\n• Uses customerClient.orders.getMyOrders() for API\n• Requires user to be authenticated\n• Add to your router as a page component",
|
|
15
|
+
"route": {
|
|
16
|
+
"path": "/my-orders",
|
|
17
|
+
"componentName": "MyOrdersPage"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
{
|
|
21
|
+
"path": "my-orders-page/index.ts",
|
|
22
|
+
"type": "registry:index",
|
|
23
|
+
"target": "$modules$/my-orders-page/index.ts",
|
|
24
|
+
"content": "export * from \"./my-orders-page\";\r\nexport { default } from \"./my-orders-page\";\r\n"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "my-orders-page/my-orders-page.tsx",
|
|
28
|
+
"type": "registry:page",
|
|
29
|
+
"target": "$modules$/my-orders-page/my-orders-page.tsx",
|
|
30
|
+
"content": "import { useEffect, useState } from \"react\";\r\nimport { Link } from \"react-router\";\r\nimport { toast } from \"sonner\";\r\nimport {\r\n Package,\r\n Loader2,\r\n ShoppingBag,\r\n ArrowLeft,\r\n Calendar,\r\n CreditCard,\r\n Truck,\r\n CheckCircle2,\r\n Clock,\r\n XCircle,\r\n} from \"lucide-react\";\r\nimport { Layout } from \"@/components/Layout\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Card, CardContent } from \"@/components/ui/card\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { usePageTitle } from \"@/hooks/use-page-title\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { useAuth } from \"@/modules/auth-core\";\r\nimport { customerClient } from \"@/modules/api/customer-client\";\r\n\r\ninterface OrderItem {\r\n name: string;\r\n description?: string;\r\n quantity: number;\r\n amount: number;\r\n img?: string;\r\n}\r\n\r\ninterface Order {\r\n id: string;\r\n created_at: string;\r\n status: string;\r\n payment_status: string;\r\n total_amount: number;\r\n currency: string;\r\n product_data?: OrderItem[];\r\n}\r\n\r\ntype PageStatus = \"loading\" | \"success\" | \"error\" | \"unauthorized\";\r\n\r\nexport function MyOrdersPage() {\r\n const { t } = useTranslation(\"my-orders-page\");\r\n usePageTitle({ title: t(\"title\", \"My Orders\") });\r\n\r\n const { isAuthenticated, token } = useAuth();\r\n\r\n const [status, setStatus] = useState<PageStatus>(\"loading\");\r\n const [orders, setOrders] = useState<Order[]>([]);\r\n\r\n useEffect(() => {\r\n if (!isAuthenticated || !token) {\r\n setStatus(\"unauthorized\");\r\n return;\r\n }\r\n\r\n const fetchOrders = async () => {\r\n try {\r\n const response = (await (customerClient as any).orders.getMyOrders()) as any;\r\n // Handle both 'orders' and 'transactions' keys from API\r\n const ordersList = response.orders || response.transactions || [];\r\n setOrders(ordersList);\r\n setStatus(\"success\");\r\n } catch (error: any) {\r\n console.error(\"Error fetching orders:\", error);\r\n\r\n if (error?.response?.status === 401) {\r\n setStatus(\"unauthorized\");\r\n } else {\r\n setStatus(\"error\");\r\n toast.error(t(\"loadError\", \"Failed to load orders. Please try again.\"));\r\n }\r\n }\r\n };\r\n\r\n fetchOrders();\r\n }, [isAuthenticated, token, t]);\r\n\r\n const getStatusIcon = (orderStatus: string) => {\r\n const statusLower = orderStatus.toLowerCase();\r\n if ([\"paid\", \"completed\", \"tamamlandı\"].includes(statusLower)) {\r\n return <CheckCircle2 className=\"w-4 h-4\" />;\r\n }\r\n if ([\"processing\", \"hazırlanıyor\"].includes(statusLower)) {\r\n return <Clock className=\"w-4 h-4\" />;\r\n }\r\n if ([\"shipped\", \"kargoda\"].includes(statusLower)) {\r\n return <Truck className=\"w-4 h-4\" />;\r\n }\r\n if ([\"cancelled\", \"iptal\", \"failed\"].includes(statusLower)) {\r\n return <XCircle className=\"w-4 h-4\" />;\r\n }\r\n return <Package className=\"w-4 h-4\" />;\r\n };\r\n\r\n const getStatusBadgeVariant = (\r\n orderStatus: string\r\n ): \"default\" | \"secondary\" | \"destructive\" | \"outline\" => {\r\n const statusLower = orderStatus.toLowerCase();\r\n if ([\"paid\", \"completed\", \"tamamlandı\"].includes(statusLower)) {\r\n return \"default\";\r\n }\r\n if ([\"processing\", \"hazırlanıyor\"].includes(statusLower)) {\r\n return \"secondary\";\r\n }\r\n if ([\"shipped\", \"kargoda\"].includes(statusLower)) {\r\n return \"outline\";\r\n }\r\n if ([\"cancelled\", \"iptal\", \"failed\"].includes(statusLower)) {\r\n return \"destructive\";\r\n }\r\n return \"secondary\";\r\n };\r\n\r\n const getPaymentStatusBadge = (\r\n paymentStatus: string\r\n ): \"default\" | \"secondary\" | \"destructive\" => {\r\n if (paymentStatus === \"paid\") return \"default\";\r\n if (paymentStatus === \"unpaid\" || paymentStatus === \"pending\")\r\n return \"secondary\";\r\n return \"destructive\";\r\n };\r\n\r\n const formatDate = (dateString: string) => {\r\n try {\r\n return new Date(dateString).toLocaleDateString(undefined, {\r\n year: \"numeric\",\r\n month: \"long\",\r\n day: \"numeric\",\r\n hour: \"2-digit\",\r\n minute: \"2-digit\",\r\n });\r\n } catch {\r\n return dateString;\r\n }\r\n };\r\n\r\n const formatCurrency = (amount: number, currency: string) => {\r\n return new Intl.NumberFormat(\"en-US\", {\r\n style: \"currency\",\r\n currency: currency.toUpperCase(),\r\n }).format(amount / 100);\r\n };\r\n\r\n // Unauthorized\r\n if (status === \"unauthorized\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"loginRequired\", \"Login Required\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"loginRequiredDescription\",\r\n \"Please login to view your orders.\"\r\n )}\r\n </p>\r\n <div className=\"flex flex-col gap-3\">\r\n <Button asChild>\r\n <Link to=\"/login\">{t(\"login\", \"Login\")}</Link>\r\n </Button>\r\n <Button variant=\"outline\" asChild>\r\n <Link to=\"/register\">\r\n {t(\"createAccount\", \"Create Account\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Loading\r\n if (status === \"loading\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Loader2 className=\"w-16 h-16 text-primary mx-auto mb-4 animate-spin\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"loadingOrders\", \"Loading Orders\")}\r\n </h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"pleaseWait\", \"Please wait...\")}\r\n </p>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Error\r\n if (status === \"error\") {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-red-500 mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"errorTitle\", \"Something went wrong\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"errorDescription\",\r\n \"We couldn't load your orders. Please try again.\"\r\n )}\r\n </p>\r\n <Button onClick={() => window.location.reload()}>\r\n {t(\"tryAgain\", \"Try Again\")}\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // No orders\r\n if (orders.length === 0) {\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-16\">\r\n <div className=\"max-w-md mx-auto text-center\">\r\n <Card>\r\n <CardContent className=\"pt-8 pb-8\">\r\n <Package className=\"w-16 h-16 text-muted-foreground mx-auto mb-4\" />\r\n <h1 className=\"text-2xl font-bold mb-2\">\r\n {t(\"noOrders\", \"No Orders Yet\")}\r\n </h1>\r\n <p className=\"text-muted-foreground mb-6\">\r\n {t(\r\n \"noOrdersDescription\",\r\n \"You haven't placed any orders yet. Start shopping to see your orders here.\"\r\n )}\r\n </p>\r\n <Button asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"startShopping\", \"Start Shopping\")}\r\n </Link>\r\n </Button>\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n }\r\n\r\n // Success - Has orders\r\n return (\r\n <Layout>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\r\n {/* Header */}\r\n <div className=\"flex items-center gap-4 mb-8\">\r\n <Button variant=\"ghost\" size=\"icon\" asChild>\r\n <Link to=\"/\">\r\n <ArrowLeft className=\"h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n <div>\r\n <h1 className=\"text-3xl font-bold\">{t(\"title\", \"My Orders\")}</h1>\r\n <p className=\"text-muted-foreground\">\r\n {t(\"orderCount\", \"{{count}} order(s)\", { count: orders.length })}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n {/* Orders List */}\r\n <div className=\"space-y-6\">\r\n {orders.map((order) => (\r\n <Card key={order.id} className=\"overflow-hidden\">\r\n {/* Order Header */}\r\n <div className=\"bg-muted/50 px-6 py-4 border-b\">\r\n <div className=\"flex flex-col md:flex-row md:items-center md:justify-between gap-4\">\r\n <div className=\"flex flex-wrap items-center gap-x-6 gap-y-2 text-sm\">\r\n <div>\r\n <span className=\"text-muted-foreground\">\r\n {t(\"orderNumber\", \"Order\")}:\r\n </span>{\" \"}\r\n <span className=\"font-mono font-medium\">\r\n #{order.id.slice(0, 8)}\r\n </span>\r\n </div>\r\n <div className=\"flex items-center gap-1.5 text-muted-foreground\">\r\n <Calendar className=\"w-4 h-4\" />\r\n <span>{formatDate(order.created_at)}</span>\r\n </div>\r\n <div className=\"flex items-center gap-1.5 text-muted-foreground\">\r\n <CreditCard className=\"w-4 h-4\" />\r\n <Badge\r\n variant={getPaymentStatusBadge(order.payment_status)}\r\n className=\"text-xs\"\r\n >\r\n {order.payment_status === \"paid\"\r\n ? t(\"paid\", \"Paid\")\r\n : t(\"unpaid\", \"Unpaid\")}\r\n </Badge>\r\n </div>\r\n </div>\r\n <div className=\"flex items-center gap-3\">\r\n <Badge\r\n variant={getStatusBadgeVariant(order.status)}\r\n className=\"flex items-center gap-1.5\"\r\n >\r\n {getStatusIcon(order.status)}\r\n <span className=\"capitalize\">{order.status}</span>\r\n </Badge>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Order Items */}\r\n <CardContent className=\"p-6\">\r\n <div className=\"space-y-4\">\r\n {order.product_data?.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"flex gap-4 pb-4 last:pb-0 last:border-0 border-b border-border/50\"\r\n >\r\n {/* Product Image */}\r\n <div className=\"flex-shrink-0\">\r\n <img\r\n src={item.img || \"/images/placeholder.png\"}\r\n alt={item.name}\r\n className=\"w-20 h-20 md:w-24 md:h-24 object-cover rounded-lg border\"\r\n onError={(e) => {\r\n (e.target as HTMLImageElement).src =\r\n \"/images/placeholder.png\";\r\n }}\r\n />\r\n </div>\r\n\r\n {/* Product Details */}\r\n <div className=\"flex-1 min-w-0\">\r\n <h3 className=\"font-semibold text-base md:text-lg truncate\">\r\n {item.name}\r\n </h3>\r\n {item.description && (\r\n <p className=\"text-sm text-muted-foreground line-clamp-2 mt-1\">\r\n {item.description}\r\n </p>\r\n )}\r\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 mt-2 text-sm\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"quantity\", \"Qty\")}:{\" \"}\r\n <span className=\"font-medium text-foreground\">\r\n {item.quantity}\r\n </span>\r\n </span>\r\n <span className=\"font-semibold text-foreground\">\r\n {formatCurrency(item.amount, order.currency)}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n\r\n {/* Order Total */}\r\n <div className=\"flex items-center justify-between mt-6 pt-4 border-t\">\r\n <span className=\"text-muted-foreground\">\r\n {t(\"total\", \"Total\")}\r\n </span>\r\n <span className=\"text-xl font-bold\">\r\n {formatCurrency(order.total_amount, order.currency)}\r\n </span>\r\n </div>\r\n </CardContent>\r\n </Card>\r\n ))}\r\n </div>\r\n\r\n {/* Continue Shopping */}\r\n <div className=\"mt-8 text-center\">\r\n <Button variant=\"outline\" asChild>\r\n <Link to=\"/products\">\r\n <ShoppingBag className=\"w-4 h-4 mr-2\" />\r\n {t(\"continueShopping\", \"Continue Shopping\")}\r\n </Link>\r\n </Button>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n}\r\n\r\nexport default MyOrdersPage;\r\n"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "my-orders-page/lang/en.json",
|
|
34
|
+
"type": "registry:lang",
|
|
35
|
+
"target": "$modules$/my-orders-page/lang/en.json",
|
|
36
|
+
"content": "{\r\n \"title\": \"My Orders\",\r\n \"loadError\": \"Failed to load orders. Please try again.\",\r\n \"loginRequired\": \"Login Required\",\r\n \"loginRequiredDescription\": \"Please login to view your orders.\",\r\n \"login\": \"Login\",\r\n \"createAccount\": \"Create Account\",\r\n \"loadingOrders\": \"Loading Orders\",\r\n \"pleaseWait\": \"Please wait...\",\r\n \"errorTitle\": \"Something went wrong\",\r\n \"errorDescription\": \"We couldn't load your orders. Please try again.\",\r\n \"tryAgain\": \"Try Again\",\r\n \"noOrders\": \"No Orders Yet\",\r\n \"noOrdersDescription\": \"You haven't placed any orders yet. Start shopping to see your orders here.\",\r\n \"startShopping\": \"Start Shopping\",\r\n \"orderCount\": \"{{count}} order(s)\",\r\n \"orderNumber\": \"Order\",\r\n \"paid\": \"Paid\",\r\n \"unpaid\": \"Unpaid\",\r\n \"quantity\": \"Qty\",\r\n \"total\": \"Total\",\r\n \"continueShopping\": \"Continue Shopping\"\r\n}\r\n"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"path": "my-orders-page/lang/tr.json",
|
|
40
|
+
"type": "registry:lang",
|
|
41
|
+
"target": "$modules$/my-orders-page/lang/tr.json",
|
|
42
|
+
"content": "{\r\n \"title\": \"Siparişlerim\",\r\n \"loadError\": \"Siparişler yüklenemedi. Lütfen tekrar deneyin.\",\r\n \"loginRequired\": \"Giriş Gerekli\",\r\n \"loginRequiredDescription\": \"Siparişlerinizi görmek için lütfen giriş yapın.\",\r\n \"login\": \"Giriş Yap\",\r\n \"createAccount\": \"Hesap Oluştur\",\r\n \"loadingOrders\": \"Siparişler Yükleniyor\",\r\n \"pleaseWait\": \"Lütfen bekleyin...\",\r\n \"errorTitle\": \"Bir şeyler ters gitti\",\r\n \"errorDescription\": \"Siparişlerinizi yükleyemedik. Lütfen tekrar deneyin.\",\r\n \"tryAgain\": \"Tekrar Dene\",\r\n \"noOrders\": \"Henüz Sipariş Yok\",\r\n \"noOrdersDescription\": \"Henüz hiç sipariş vermediniz. Siparişlerinizi burada görmek için alışverişe başlayın.\",\r\n \"startShopping\": \"Alışverişe Başla\",\r\n \"orderCount\": \"{{count}} sipariş\",\r\n \"orderNumber\": \"Sipariş\",\r\n \"paid\": \"Ödendi\",\r\n \"unpaid\": \"Ödenmedi\",\r\n \"quantity\": \"Adet\",\r\n \"total\": \"Toplam\",\r\n \"continueShopping\": \"Alışverişe Devam Et\"\r\n}\r\n"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"exports": {
|
|
46
|
+
"types": [],
|
|
47
|
+
"variables": [
|
|
48
|
+
"MyOrdersPage",
|
|
49
|
+
"default"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|