From 9a62caf106cb578000d444608f5a785a1373ddd5 Mon Sep 17 00:00:00 2001 From: Xenia Levchenko Date: Wed, 11 Mar 2026 13:35:29 +0300 Subject: [PATCH 1/5] add veil landing --- template/apps/web/components.json | 5 +- template/apps/web/package.json | 5 +- .../web/src/components/PublicHeader/index.tsx | 60 +++--- template/apps/web/src/pages/index.page.tsx | 2 +- .../pages/landings/veil/components/CTA.tsx | 34 ++++ .../pages/landings/veil/components/FAQ.tsx | 80 ++++++++ .../landings/veil/components/Features.tsx | 186 ++++++++++++++++++ .../pages/landings/veil/components/Footer.tsx | 60 ++++++ .../pages/landings/veil/components/Header.tsx | 69 +++++++ .../pages/landings/veil/components/Hero.tsx | 113 +++++++++++ .../pages/landings/veil/components/Logos.tsx | 39 ++++ .../landings/veil/components/Pricing.tsx | 103 ++++++++++ .../landings/veil/components/Testimonials.tsx | 59 ++++++ .../pages/landings/veil/components/index.ts | 9 + .../landings/veil/components/ui/accordion.tsx | 48 +++++ .../landings/veil/components/ui/button.tsx | 57 ++++++ .../landings/veil/components/ui/card.tsx | 73 +++++++ .../veil/components/ui/svgs/claude.tsx | 12 ++ .../veil/components/ui/svgs/clerk.tsx | 32 +++ .../veil/components/ui/svgs/figma.tsx | 35 ++++ .../veil/components/ui/svgs/firebase.tsx | 24 +++ .../veil/components/ui/svgs/linear.tsx | 12 ++ .../veil/components/ui/svgs/slack.tsx | 26 +++ .../veil/components/ui/svgs/supabase.tsx | 119 +++++++++++ .../veil/components/ui/svgs/twilio.tsx | 15 ++ .../veil/components/ui/svgs/vercel.tsx | 30 +++ .../web/src/pages/landings/veil/index.tsx | 35 ++++ template/pnpm-lock.yaml | 16 +- 28 files changed, 1316 insertions(+), 42 deletions(-) create mode 100644 template/apps/web/src/pages/landings/veil/components/CTA.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/FAQ.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/Features.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/Footer.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/Header.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/Hero.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/Logos.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/Pricing.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/Testimonials.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/index.ts create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/accordion.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/button.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/card.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/svgs/claude.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/svgs/clerk.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/svgs/figma.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/svgs/firebase.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/svgs/linear.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/svgs/slack.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/svgs/supabase.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/svgs/twilio.tsx create mode 100644 template/apps/web/src/pages/landings/veil/components/ui/svgs/vercel.tsx create mode 100644 template/apps/web/src/pages/landings/veil/index.tsx diff --git a/template/apps/web/components.json b/template/apps/web/components.json index 353e3ee79..85c215dcf 100644 --- a/template/apps/web/components.json +++ b/template/apps/web/components.json @@ -9,6 +9,7 @@ "baseColor": "slate", "cssVariables": true }, + "iconLibrary": "lucide", "aliases": { "components": "@/components", "utils": "@/lib/utils", @@ -16,5 +17,7 @@ "lib": "@/lib", "hooks": "@/hooks" }, - "iconLibrary": "lucide" + "registries": { + "@tailark": "https://tailark.com/r/{name}.json" + } } diff --git a/template/apps/web/package.json b/template/apps/web/package.json index 247ca0ea4..b79eb4aa7 100644 --- a/template/apps/web/package.json +++ b/template/apps/web/package.json @@ -25,14 +25,14 @@ "@radix-ui/react-label": "2.1.8", "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-select": "2.2.6", - "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-slot": "^1.2.4", "@svgr/webpack": "8.1.0", "@tabler/icons-react": "3.10.0", "@tailwindcss/typography": "0.5.19", "@tanstack/react-query": "5.74.4", "@tanstack/react-table": "8.19.2", "axios": "1.12.2", - "class-variance-authority": "^0.7.0", + "class-variance-authority": "^0.7.1", "clsx": "2.1.1", "date-fns": "4.1.0", "dayjs": "1.11.10", @@ -52,6 +52,7 @@ "react-dom": "catalog:", "react-dropzone": "15.0.0", "react-hook-form": "7.57.0", + "react-icons": "5.6.0", "shared": "workspace:*", "socket.io-client": "4.7.5", "sonner": "^1.7.4", diff --git a/template/apps/web/src/components/PublicHeader/index.tsx b/template/apps/web/src/components/PublicHeader/index.tsx index 53c52695e..799d63b83 100644 --- a/template/apps/web/src/components/PublicHeader/index.tsx +++ b/template/apps/web/src/components/PublicHeader/index.tsx @@ -22,19 +22,19 @@ const PublicHeader: FC = () => { const { theme, setTheme } = useTheme(); return ( -
-
-
- - +
+
+
+ + -
-
+
-
- {account ? ( - + ) : ( + <> + + - ) : ( - <> - - - - )} -
+ + )}
diff --git a/template/apps/web/src/pages/index.page.tsx b/template/apps/web/src/pages/index.page.tsx index a40384caa..165afeff8 100644 --- a/template/apps/web/src/pages/index.page.tsx +++ b/template/apps/web/src/pages/index.page.tsx @@ -1,3 +1,3 @@ -import Landing from './landings/light'; +import Landing from './landings/veil'; export default Landing; diff --git a/template/apps/web/src/pages/landings/veil/components/CTA.tsx b/template/apps/web/src/pages/landings/veil/components/CTA.tsx new file mode 100644 index 000000000..709c5eaaa --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/CTA.tsx @@ -0,0 +1,34 @@ +import Link from 'next/link'; +import { ArrowRight } from 'lucide-react'; + +import { Button } from './ui/button'; +import { Card } from './ui/card'; + +export const CTA = () => { + return ( +
+
+
+
+
+
+ +
+ +
Ready to Ship?
+

Build better. Launch faster.

+

+ Join hundreds of developers who have already transformed their development workflow. Start building today. +

+ + +
+
+
+ ); +}; diff --git a/template/apps/web/src/pages/landings/veil/components/FAQ.tsx b/template/apps/web/src/pages/landings/veil/components/FAQ.tsx new file mode 100644 index 000000000..e73b3d5bc --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/FAQ.tsx @@ -0,0 +1,80 @@ +'use client'; + +import Link from 'next/link'; + +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './ui/accordion'; + +const faqItems = [ + { + id: 'item-1', + question: 'What tech stack does Ship use?', + answer: + 'Ship is built on Next.js 14, Node.js, MongoDB, and Redis. It uses TypeScript throughout for type safety, Turborepo for monorepo management, and includes Shadcn UI components.', + }, + { + id: 'item-2', + question: 'Is there a recurring subscription?', + answer: + 'No, Ship is a one-time purchase. You get lifetime access to all updates and can use it for unlimited projects depending on your license.', + }, + { + id: 'item-3', + question: 'What payment integrations are included?', + answer: + 'Ship includes full Stripe integration with subscriptions, one-time payments, customer portals, and webhook handling. Everything is pre-configured and ready to monetize.', + }, + { + id: 'item-4', + question: 'Can I use Ship for client projects?', + answer: 'Yes! The Pro license allows unlimited projects. You can use Ship for as many client projects as you want.', + }, + { + id: 'item-5', + question: 'What kind of support is available?', + answer: + 'Starter includes email support, Pro includes priority Slack support, and Enterprise includes dedicated 24/7 support with on-site training options.', + }, +]; + +export const FAQ = () => { + return ( +
+
+
+
+

FAQs

+

Your questions answered

+

+ Need more help?{' '} + + Contact us + +

+
+ +
+ + {faqItems.map((item) => ( + + + {item.question} + + +

{item.answer}

+
+
+ ))} +
+ +

+ Need more help?{' '} + + Contact us + +

+
+
+
+
+ ); +}; diff --git a/template/apps/web/src/pages/landings/veil/components/Features.tsx b/template/apps/web/src/pages/landings/veil/components/Features.tsx new file mode 100644 index 000000000..334a9bec3 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/Features.tsx @@ -0,0 +1,186 @@ +import { Activity, CreditCard, Database, Gauge, Lock, Shield, Zap } from 'lucide-react'; + +import { Card } from './ui/card'; + +export const Features = () => { + return ( +
+
+
+

Everything You Need to Launch

+

+ Stop wasting time on boilerplate. Ship comes with all the essential infrastructure pre-configured. +

+
+ +
+ +
+

Authentication & RBAC

+

+ Pre-configured auth with Google OAuth, email verification, and role-based access control. +

+
+ +
+
+
+
+ + OAuth +
+ +
+ + RBAC +
+
+ +
+
+
+ JWT +
+ +
+ Sessions +
+
+ +
+
+
+ Email +
+ +
+ 2FA +
+
+
+
+ + +
+

Stripe Payments

+

+ Subscriptions, one-time payments, customer portals, and webhooks ready to go. +

+
+ +
+
+
+
+
+
+ +
+
+ + +
+

Modern Full-Stack

+

+ Next.js 14, Node.js, MongoDB, Redis with TypeScript and Turborepo. +

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+

MongoDB & Redis

+

+ Optimized database layer with MongoDB for data and Redis for caching. +

+
+ +
+ + +
+
+ + +
+

Docker & CI/CD

+

+ Production-ready Docker setup with GitHub Actions for deployment. +

+
+ +
+
+ +
+
+ +
+
Deploy
+
Anywhere
+
+
+
+
+ + +
+

Mixpanel Analytics

+

+ Built-in analytics integration for tracking user behavior and KPIs. +

+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ ); +}; diff --git a/template/apps/web/src/pages/landings/veil/components/Footer.tsx b/template/apps/web/src/pages/landings/veil/components/Footer.tsx new file mode 100644 index 000000000..e49f274d8 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/Footer.tsx @@ -0,0 +1,60 @@ +import Link from 'next/link'; +import { Github, Linkedin, Twitter } from 'lucide-react'; + +import { LogoImage } from 'public/images'; + +const links = [ + { label: 'Features', href: '#features' }, + { label: 'Pricing', href: '#pricing' }, + { label: 'FAQ', href: '#faq' }, + { label: 'Docs', href: '' }, +]; + +const social = [ + { icon: Twitter, href: '', label: 'Twitter' }, + { icon: Github, href: '', label: 'GitHub' }, + { icon: Linkedin, href: '', label: 'LinkedIn' }, +]; + +export const Footer = () => { + return ( +
+
+
+ + + + + + +
+ {social.map((item) => ( + + + + ))} +
+ +

+ © {new Date().getFullYear()} Ship. Built by Paralect. +

+
+
+
+ ); +}; diff --git a/template/apps/web/src/pages/landings/veil/components/Header.tsx b/template/apps/web/src/pages/landings/veil/components/Header.tsx new file mode 100644 index 000000000..d5d3a4d7f --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/Header.tsx @@ -0,0 +1,69 @@ +import Link from 'next/link'; +import { useTheme } from 'next-themes'; +import { useApiQuery } from 'hooks'; +import { Moon, Sun } from 'lucide-react'; + +import { LogoImage } from 'public/images'; + +import { apiClient } from 'services/api-client.service'; + +import { Button } from './ui/button'; + +export const Header = () => { + const { data: account } = useApiQuery(apiClient.account.get); + const { theme, setTheme } = useTheme(); + + return ( +
+
+
+ + + + + +
+ +
+ + + {account ? ( + + ) : ( + <> + + + + )} +
+
+
+ ); +}; diff --git a/template/apps/web/src/pages/landings/veil/components/Hero.tsx b/template/apps/web/src/pages/landings/veil/components/Hero.tsx new file mode 100644 index 000000000..d282142d4 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/Hero.tsx @@ -0,0 +1,113 @@ +import Link from 'next/link'; +import { ChevronRight } from 'lucide-react'; + +import { Button } from './ui/button'; +import { Card } from './ui/card'; +import { Claude } from './ui/svgs/claude'; +import { ClerkIconLight as Clerk } from './ui/svgs/clerk'; +import { Figma } from './ui/svgs/figma'; +import { Firebase } from './ui/svgs/firebase'; +import { Linear } from './ui/svgs/linear'; +import { Slack } from './ui/svgs/slack'; +import { Supabase } from './ui/svgs/supabase'; +import { Twilio } from './ui/svgs/twilio'; +import { Vercel } from './ui/svgs/vercel'; + +export const Hero = () => { + return ( +
+
+
+
+
+
+
+
+
+

+ Ship your SaaS in days, not months. +

+

+ The high-performance toolkit for developers. Authentication, payments, emails, and more — all configured + and ready to go. +

+ + +
+ +
+
+
+ + + Supabase + +
+ +
+ + + Slack + +
+ +
+ + + Figma + +
+ +
+ + + Vercel + +
+ +
+ + + Firebase + +
+ +
+ + + Linear + +
+ +
+ + + Twilio + +
+ +
+ + + Claude AI + +
+ +
+ + + Clerk + +
+
+
+
+
+
+ ); +}; diff --git a/template/apps/web/src/pages/landings/veil/components/Logos.tsx b/template/apps/web/src/pages/landings/veil/components/Logos.tsx new file mode 100644 index 000000000..61359073d --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/Logos.tsx @@ -0,0 +1,39 @@ +import { + SiDocker, + SiMongodb, + SiNextdotjs, + SiNodedotjs, + SiRedis, + SiStripe, + SiTailwindcss, + SiTypescript, +} from 'react-icons/si'; + +const technologies = [ + { name: 'TypeScript', Icon: SiTypescript }, + { name: 'Next.js', Icon: SiNextdotjs }, + { name: 'Node.js', Icon: SiNodedotjs }, + { name: 'MongoDB', Icon: SiMongodb }, + { name: 'Stripe', Icon: SiStripe }, + { name: 'Tailwind', Icon: SiTailwindcss }, + { name: 'Redis', Icon: SiRedis }, + { name: 'Docker', Icon: SiDocker }, +]; + +export const Logos = () => { + return ( +
+
+

Built with modern technologies

+
+ {technologies.map((tech) => ( +
+ + {tech.name} +
+ ))} +
+
+
+ ); +}; diff --git a/template/apps/web/src/pages/landings/veil/components/Pricing.tsx b/template/apps/web/src/pages/landings/veil/components/Pricing.tsx new file mode 100644 index 000000000..53a2943b7 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/Pricing.tsx @@ -0,0 +1,103 @@ +import Link from 'next/link'; +import { ArrowRight, Check } from 'lucide-react'; + +import { Button } from './ui/button'; +import { Card } from './ui/card'; + +import { cn } from '@/lib/utils'; + +const plans = [ + { + name: 'Starter', + price: '$49', + period: 'one-time', + description: 'Perfect for side projects and MVPs', + features: [ + 'Single App License', + 'Next.js & Node.js API', + 'MongoDB Database', + 'Standard Email Support', + 'Lifetime Updates', + ], + }, + { + name: 'Pro', + price: '$99', + period: 'one-time', + description: 'Ideal for startups and teams', + features: [ + 'Unlimited App Licenses', + 'Stripe Integration', + 'Priority Slack Support', + 'Docker & CI/CD Setup', + 'Everything in Starter', + ], + highlighted: true, + badge: 'Most Popular', + }, + { + name: 'Enterprise', + price: 'Contact', + period: '', + description: 'For teams needing high-touch support', + features: [ + 'White-glove Implementation', + 'Initial Audit & Setup', + '24/7 Dedicated Support', + 'On-site Training', + 'Custom Extensions', + ], + }, +]; + +export const Pricing = () => { + return ( +
+
+
+

Straightforward Pricing

+

+ No monthly fees. Pay once and build unlimited projects. +

+
+
+ {plans.map((plan) => ( + +
+

{plan.name}

+

{plan.description}

+
+
+ {plan.price} + {plan.period} +
+ +
    + {plan.features.map((feature) => ( +
  • + + {feature} +
  • + ))} +
+ + +
+ ))} +
+

+ All plans include lifetime updates and community support. +

+
+
+ ); +}; diff --git a/template/apps/web/src/pages/landings/veil/components/Testimonials.tsx b/template/apps/web/src/pages/landings/veil/components/Testimonials.tsx new file mode 100644 index 000000000..61e6cd82b --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/Testimonials.tsx @@ -0,0 +1,59 @@ +import { User } from 'lucide-react'; + +const testimonials = [ + { + name: 'Sarah Chen', + role: 'CEO at TechStart', + quote: 'Ship saved us weeks of development time. We launched our SaaS in just 2 weeks instead of 2 months.', + }, + { + name: 'Michael Rodriguez', + role: 'Senior Developer at DevCorp', + quote: 'The code quality is exceptional. Clean architecture, proper TypeScript usage, and great documentation.', + }, + { + name: 'Emily Johnson', + role: 'Founder at LaunchPad', + quote: + 'Finally, a boilerplate that actually works out of the box. No more spending hours fixing configuration issues.', + }, + { + name: 'David Park', + role: 'CTO at GrowthLabs', + quote: "Best investment we've made. The authentication and payment integrations alone are worth it.", + }, +]; + +export const Testimonials = () => { + return ( +
+
+
+

Trusted by Developers

+

+ Join hundreds of developers who have already transformed their development workflow. +

+
+ +
+ {testimonials.map((testimonial) => ( +
+
+
+ +
+

+ {testimonial.name} {testimonial.role} +

+
+

{testimonial.quote}

+
+ ))} +
+
+
+ ); +}; diff --git a/template/apps/web/src/pages/landings/veil/components/index.ts b/template/apps/web/src/pages/landings/veil/components/index.ts new file mode 100644 index 000000000..b3b3a9478 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/index.ts @@ -0,0 +1,9 @@ +export { CTA } from './CTA'; +export { FAQ } from './FAQ'; +export { Features } from './Features'; +export { Footer } from './Footer'; +export { Header } from './Header'; +export { Hero } from './Hero'; +export { Logos } from './Logos'; +export { Pricing } from './Pricing'; +export { Testimonials } from './Testimonials'; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/accordion.tsx b/template/apps/web/src/pages/landings/veil/components/ui/accordion.tsx new file mode 100644 index 000000000..3d90a8917 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/accordion.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import * as AccordionPrimitive from '@radix-ui/react-accordion'; +import { ChevronDown } from 'lucide-react'; + +import { cn } from '@/lib/utils'; + +const Accordion = AccordionPrimitive.Root; + +const AccordionItem = ({ className, ...props }: React.ComponentProps) => ( + +); +AccordionItem.displayName = 'AccordionItem'; + +const AccordionTrigger = ({ + className, + children, + ...props +}: React.ComponentProps) => ( + + svg]:rotate-180', + className, + )} + {...props} + > + {children} + + + +); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionContent = ({ + className, + children, + ...props +}: React.ComponentProps) => ( + +
{children}
+
+); +AccordionContent.displayName = AccordionPrimitive.Content.displayName; + +export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/button.tsx b/template/apps/web/src/pages/landings/veil/components/ui/button.tsx new file mode 100644 index 000000000..59fa3c72c --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import type { VariantProps } from 'class-variance-authority'; +import { cva } from 'class-variance-authority'; + +import { cn } from '@/lib/utils'; + +const buttonVariants = cva( + 'cursor-pointer active:scale-99 duration-200 font-medium inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + { + variants: { + variant: { + default: 'bg-foreground text-background hover:brightness-95', + neutral: 'bg-foreground text-background hover:brightness-95', + destructive: 'bg-destructive text-destructive-foreground shadow-md hover:bg-destructive/90', + outline: + 'shadow-sm text-foreground shadow-black/6.5 border border-transparent bg-card ring-1 ring-foreground/15 duration-200 hover:bg-muted/50', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-foreground/5 text-foreground/75 hover:text-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-8 px-3 py-2', + sm: 'h-7 px-2.5 text-sm', + lg: 'h-11 px-6 font-medium text-base', + icon: 'size-9', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = ({ + ref, + className, + variant, + size, + asChild = false, + ...props +}: ButtonProps & { ref?: React.RefObject }) => { + const Comp = asChild ? Slot : 'button'; + + return ; +}; + +Button.displayName = 'Button'; + +export { Button }; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/card.tsx b/template/apps/web/src/pages/landings/veil/components/ui/card.tsx new file mode 100644 index 000000000..4c8448db0 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/card.tsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import type { VariantProps } from 'class-variance-authority'; +import { cva } from 'class-variance-authority'; + +import { cn } from '@/lib/utils'; + +const cardVariants = cva('text-card-foreground rounded-2xl', { + variants: { + variant: { + default: 'bg-card ring-1 ring-foreground/6.5 shadow-lg shadow-foreground/5 dark:shadow-black/10', + soft: 'bg-muted', + mixed: 'bg-muted border', + outline: 'bg-card ring-1 ring-border', + }, + }, + defaultVariants: { + variant: 'default', + }, +}); + +export interface CardProps extends React.HTMLAttributes, VariantProps {} + +const Card = ({ ref, className, variant, ...props }: CardProps & { ref?: React.RefObject }) => ( +
+); +Card.displayName = 'Card'; + +const CardHeader = ({ + ref, + className, + ...props +}: React.HTMLAttributes & { ref?: React.RefObject }) => ( +
+); +CardHeader.displayName = 'CardHeader'; + +const CardTitle = ({ + ref, + className, + ...props +}: React.HTMLAttributes & { ref?: React.RefObject }) => ( +
+); +CardTitle.displayName = 'CardTitle'; + +const CardDescription = ({ + ref, + className, + ...props +}: React.HTMLAttributes & { ref?: React.RefObject }) => ( +
+); +CardDescription.displayName = 'CardDescription'; + +const CardContent = ({ + ref, + className, + ...props +}: React.HTMLAttributes & { ref?: React.RefObject }) => ( +
+); +CardContent.displayName = 'CardContent'; + +const CardFooter = ({ + ref, + className, + ...props +}: React.HTMLAttributes & { ref?: React.RefObject }) => ( +
+); +CardFooter.displayName = 'CardFooter'; + +export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/svgs/claude.tsx b/template/apps/web/src/pages/landings/veil/components/ui/svgs/claude.tsx new file mode 100644 index 000000000..c672c1dc6 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/svgs/claude.tsx @@ -0,0 +1,12 @@ +import type { SVGProps } from 'react'; + +const Claude = (props: SVGProps) => ( + + + +); + +export { Claude }; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/svgs/clerk.tsx b/template/apps/web/src/pages/landings/veil/components/ui/svgs/clerk.tsx new file mode 100644 index 000000000..c8c6d3432 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/svgs/clerk.tsx @@ -0,0 +1,32 @@ +import type { SVGProps } from 'react'; + +const ClerkIconDark = (props: SVGProps) => ( + + + + + +); + +const ClerkIconLight = (props: SVGProps) => ( + + + + + +); + +export { ClerkIconDark, ClerkIconLight }; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/svgs/figma.tsx b/template/apps/web/src/pages/landings/veil/components/ui/svgs/figma.tsx new file mode 100644 index 000000000..bd1792eb8 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/svgs/figma.tsx @@ -0,0 +1,35 @@ +import type { SVGProps } from 'react'; + +const Figma = (props: SVGProps) => ( + + + + + + + + + + + + + + +); + +export { Figma }; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/svgs/firebase.tsx b/template/apps/web/src/pages/landings/veil/components/ui/svgs/firebase.tsx new file mode 100644 index 000000000..b774ecff4 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/svgs/firebase.tsx @@ -0,0 +1,24 @@ +import type { SVGProps } from 'react'; + +const Firebase = (props: SVGProps) => ( + + + + + + +); + +export { Firebase }; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/svgs/linear.tsx b/template/apps/web/src/pages/landings/veil/components/ui/svgs/linear.tsx new file mode 100644 index 000000000..515116859 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/svgs/linear.tsx @@ -0,0 +1,12 @@ +import type { SVGProps } from 'react'; + +const Linear = (props: SVGProps) => ( + + + +); + +export { Linear }; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/svgs/slack.tsx b/template/apps/web/src/pages/landings/veil/components/ui/svgs/slack.tsx new file mode 100644 index 000000000..98bef5844 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/svgs/slack.tsx @@ -0,0 +1,26 @@ +import type { SVGProps } from 'react'; + +const Slack = (props: SVGProps) => ( + + + + + + + + +); + +export { Slack }; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/svgs/supabase.tsx b/template/apps/web/src/pages/landings/veil/components/ui/svgs/supabase.tsx new file mode 100644 index 000000000..aaaa1bf83 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/svgs/supabase.tsx @@ -0,0 +1,119 @@ +import type { SVGProps } from 'react'; + +const Supabase = (props: SVGProps) => ( + + + + + + + + + + + + + + + +); + +const SupabaseFull = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + +); + +export { Supabase, SupabaseFull }; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/svgs/twilio.tsx b/template/apps/web/src/pages/landings/veil/components/ui/svgs/twilio.tsx new file mode 100644 index 000000000..ec862a686 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/svgs/twilio.tsx @@ -0,0 +1,15 @@ +import type { SVGProps } from 'react'; + +const Twilio = (props: SVGProps) => ( + + + + + + + + + +); + +export { Twilio }; diff --git a/template/apps/web/src/pages/landings/veil/components/ui/svgs/vercel.tsx b/template/apps/web/src/pages/landings/veil/components/ui/svgs/vercel.tsx new file mode 100644 index 000000000..c2d4f22dc --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/components/ui/svgs/vercel.tsx @@ -0,0 +1,30 @@ +import type { SVGProps } from 'react'; +export const Vercel = (props: SVGProps) => ( + + + +); + +export const VercelFull = (props: SVGProps) => ( + + + +); diff --git a/template/apps/web/src/pages/landings/veil/index.tsx b/template/apps/web/src/pages/landings/veil/index.tsx new file mode 100644 index 000000000..867fd5021 --- /dev/null +++ b/template/apps/web/src/pages/landings/veil/index.tsx @@ -0,0 +1,35 @@ +import Head from 'next/head'; + +import { CTA, FAQ, Features, Footer, Header, Hero, Logos, Pricing, Testimonials } from './components'; + +const VeilLanding = () => { + return ( + <> + + Veil | Modern Integration Platform + + + +
+
+ +
+ + + + + + + +
+ +
+
+ + ); +}; + +export default VeilLanding; diff --git a/template/pnpm-lock.yaml b/template/pnpm-lock.yaml index 909f897c1..288eacebc 100644 --- a/template/pnpm-lock.yaml +++ b/template/pnpm-lock.yaml @@ -260,7 +260,7 @@ importers: specifier: 2.2.6 version: 2.2.6(@types/react-dom@19.0.4(@types/react@19.0.14))(@types/react@19.0.14)(react-dom@19.0.3(react@19.0.3))(react@19.0.3) '@radix-ui/react-slot': - specifier: ^1.1.0 + specifier: ^1.2.4 version: 1.2.4(@types/react@19.0.14)(react@19.0.3) '@svgr/webpack': specifier: 8.1.0 @@ -281,7 +281,7 @@ importers: specifier: 1.12.2 version: 1.12.2 class-variance-authority: - specifier: ^0.7.0 + specifier: ^0.7.1 version: 0.7.1 clsx: specifier: 2.1.1 @@ -340,6 +340,9 @@ importers: react-hook-form: specifier: 7.57.0 version: 7.57.0(react@19.0.3) + react-icons: + specifier: 5.6.0 + version: 5.6.0(react@19.0.3) shared: specifier: workspace:* version: link:../../packages/shared @@ -6649,6 +6652,11 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-icons@5.6.0: + resolution: {integrity: sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==} + peerDependencies: + react: '*' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -15192,6 +15200,10 @@ snapshots: dependencies: react: 19.0.3 + react-icons@5.6.0(react@19.0.3): + dependencies: + react: 19.0.3 + react-is@16.13.1: {} react-promise-suspense@0.3.4: From da7458ae37b8bfbad52424b30ddffdcf753d5f47 Mon Sep 17 00:00:00 2001 From: Xenia Levchenko Date: Wed, 11 Mar 2026 13:59:09 +0300 Subject: [PATCH 2/5] refactor --- .../apps/web/src/components/ui/dialog.tsx | 136 ------------------ .../apps/web/src/components/ui/dropzone.tsx | 83 ----------- template/apps/web/src/components/ui/form.tsx | 136 ------------------ .../apps/web/src/components/ui/textarea.tsx | 18 --- .../pages/landings/dark/components/CTA.tsx | 2 +- .../landings/dark/components/Features.tsx | 2 +- .../pages/landings/dark/components/Hero.tsx | 3 +- .../landings/dark/components/Pricing.tsx | 3 +- .../landings/dark/components/ui/button.tsx | 63 ++++++++ .../dark}/components/ui/glowing-effect.tsx | 7 +- .../pages/landings/light/components/CTA.tsx | 2 +- .../pages/landings/light/components/Hero.tsx | 2 +- .../landings/light/components/Pricing.tsx | 2 +- .../landings/light/components/ui/button.tsx | 63 ++++++++ 14 files changed, 139 insertions(+), 383 deletions(-) delete mode 100644 template/apps/web/src/components/ui/dialog.tsx delete mode 100644 template/apps/web/src/components/ui/dropzone.tsx delete mode 100644 template/apps/web/src/components/ui/form.tsx delete mode 100644 template/apps/web/src/components/ui/textarea.tsx create mode 100644 template/apps/web/src/pages/landings/dark/components/ui/button.tsx rename template/apps/web/src/{ => pages/landings/dark}/components/ui/glowing-effect.tsx (96%) create mode 100644 template/apps/web/src/pages/landings/light/components/ui/button.tsx diff --git a/template/apps/web/src/components/ui/dialog.tsx b/template/apps/web/src/components/ui/dialog.tsx deleted file mode 100644 index d3639a90f..000000000 --- a/template/apps/web/src/components/ui/dialog.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import * as React from 'react'; -import { XIcon } from 'lucide-react'; -import { Dialog as DialogPrimitive } from 'radix-ui'; - -import { cn } from '@/lib/utils'; -import { Button } from '@/components/ui/button'; - -function Dialog({ ...props }: React.ComponentProps) { - return ; -} - -function DialogTrigger({ ...props }: React.ComponentProps) { - return ; -} - -function DialogPortal({ ...props }: React.ComponentProps) { - return ; -} - -function DialogClose({ ...props }: React.ComponentProps) { - return ; -} - -function DialogOverlay({ className, ...props }: React.ComponentProps) { - return ( - - ); -} - -function DialogContent({ - className, - children, - showCloseButton = true, - ...props -}: React.ComponentProps & { - showCloseButton?: boolean; -}) { - return ( - - - - {children} - {showCloseButton && ( - - - Close - - )} - - - ); -} - -function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
- ); -} - -function DialogFooter({ - className, - showCloseButton = false, - children, - ...props -}: React.ComponentProps<'div'> & { - showCloseButton?: boolean; -}) { - return ( -
- {children} - {showCloseButton && ( - - - - )} -
- ); -} - -function DialogTitle({ className, ...props }: React.ComponentProps) { - return ( - - ); -} - -function DialogDescription({ className, ...props }: React.ComponentProps) { - return ( - - ); -} - -export { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogOverlay, - DialogPortal, - DialogTitle, - DialogTrigger, -}; diff --git a/template/apps/web/src/components/ui/dropzone.tsx b/template/apps/web/src/components/ui/dropzone.tsx deleted file mode 100644 index 3fb56dee3..000000000 --- a/template/apps/web/src/components/ui/dropzone.tsx +++ /dev/null @@ -1,83 +0,0 @@ -'use client'; - -import * as React from 'react'; -import { UploadCloud, X } from 'lucide-react'; -import { useDropzone, type DropzoneOptions, type FileRejection } from 'react-dropzone'; - -import { cn } from '@/lib/utils'; -import { Button } from '@/components/ui/button'; - -export interface DropzoneProps extends Omit { - className?: string; - value?: File[]; - onChange?: (files: File[]) => void; - onReject?: (rejections: FileRejection[]) => void; -} - -const Dropzone = ({ className, value = [], onChange, onReject, ...props }: DropzoneProps) => { - const onDrop = React.useCallback( - (acceptedFiles: File[], rejectedFiles: FileRejection[]) => { - if (rejectedFiles.length > 0 && onReject) { - onReject(rejectedFiles); - } - if (acceptedFiles.length > 0) { - onChange?.([...value, ...acceptedFiles]); - } - }, - [onChange, onReject, value], - ); - - const removeFile = (index: number) => { - const newFiles = [...value]; - newFiles.splice(index, 1); - onChange?.(newFiles); - }; - - const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({ - onDrop, - ...props, - }); - - return ( -
-
- - - {isDragActive ? ( -

{isDragReject ? 'File type not accepted' : 'Drop files here'}

- ) : ( - <> -

Drag & drop files here

-

or click to browse

- - )} -
- - {value.length > 0 && ( -
    - {value.map((file, index) => ( -
  • - {file.name} - -
  • - ))} -
- )} -
- ); -}; - -export { Dropzone }; diff --git a/template/apps/web/src/components/ui/form.tsx b/template/apps/web/src/components/ui/form.tsx deleted file mode 100644 index 10ba9eee1..000000000 --- a/template/apps/web/src/components/ui/form.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import * as React from 'react'; -import type { Label as LabelPrimitive } from 'radix-ui'; -import { Slot } from 'radix-ui'; -import { - Controller, - FormProvider, - useFormContext, - useFormState, - type ControllerProps, - type FieldPath, - type FieldValues, -} from 'react-hook-form'; - -import { cn } from '@/lib/utils'; -import { Label } from '@/components/ui/label'; - -const Form = FormProvider; - -type FormFieldContextValue< - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, -> = { - name: TName; -}; - -const FormFieldContext = React.createContext({} as FormFieldContextValue); - -const FormField = < - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, ->({ - ...props -}: ControllerProps) => { - return ( - - - - ); -}; - -const useFormField = () => { - const fieldContext = React.useContext(FormFieldContext); - const itemContext = React.useContext(FormItemContext); - const { getFieldState } = useFormContext(); - const formState = useFormState({ name: fieldContext.name }); - const fieldState = getFieldState(fieldContext.name, formState); - - if (!fieldContext) { - throw new Error('useFormField should be used within '); - } - - const { id } = itemContext; - - return { - id, - name: fieldContext.name, - formItemId: `${id}-form-item`, - formDescriptionId: `${id}-form-item-description`, - formMessageId: `${id}-form-item-message`, - ...fieldState, - }; -}; - -type FormItemContextValue = { - id: string; -}; - -const FormItemContext = React.createContext({} as FormItemContextValue); - -function FormItem({ className, ...props }: React.ComponentProps<'div'>) { - const id = React.useId(); - - return ( - -
- - ); -} - -function FormLabel({ className, ...props }: React.ComponentProps) { - const { error, formItemId } = useFormField(); - - return ( -