diff --git a/packages/frontend/public/favicon.svg b/packages/frontend/public/favicon.svg index 78f3473..6bcba19 100644 --- a/packages/frontend/public/favicon.svg +++ b/packages/frontend/public/favicon.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/frontend/src/components/server-configuration.tsx b/packages/frontend/src/components/server-configuration.tsx index 41b62d4..779730b 100644 --- a/packages/frontend/src/components/server-configuration.tsx +++ b/packages/frontend/src/components/server-configuration.tsx @@ -114,7 +114,7 @@ export function ServerConfiguration({ trigger }: { trigger?: ReactNode }) { {trigger || ( - + )} diff --git a/packages/frontend/src/components/ui/icon.tsx b/packages/frontend/src/components/ui/icon.tsx index a57b270..ff3529e 100644 --- a/packages/frontend/src/components/ui/icon.tsx +++ b/packages/frontend/src/components/ui/icon.tsx @@ -2,6 +2,7 @@ import { Alert as PixelAlert, Check as PixelCheck, Checkbox as PixelCheckbox, + CheckboxOn as PixelCheckboxOn, ChevronDown as PixelChevronDown, ChevronLeft as PixelChevronLeft, ChevronRight as PixelChevronRight, @@ -10,8 +11,14 @@ import { Clock as PixelClock, Close as PixelClose, MessagePlus as PixelCommentSend, + CreditCard as PixelCreditCard, + CreditCardDelete as PixelCreditCardDelete, + Dashboard as PixelDashboard, Debug as PixelDebug, + DebugOff as PixelDebugOff, Edit as PixelEdit, + EyeClosed as PixelEyeClosed, + AddGrid as PixelGridAdd, Home as PixelHome, InfoBox as PixelInfo, Link as PixelLink, @@ -24,14 +31,18 @@ import { Play as PixelPlay, Plus as PixelPlus, Server as PixelServer, + Shield as PixelShield, + Ship as PixelShip, CheckboxOn as PixelStop, Sun as PixelSun, Trash as PixelTrash, Undo as PixelUndo, User as PixelUser, ViewportWide as PixelViewportWide, + Zap as PixelZap, } from "@nsmr/pixelart-react"; import { + ArrowCounterClockwiseIcon as PhosphorArrowCounterClockwise, BugIcon as PhosphorBug, CheckIcon as PhosphorCheck, CheckCircleIcon as PhosphorCheckCircle, @@ -41,15 +52,19 @@ import { CaretRightIcon as PhosphorChevronRight, CaretUpIcon as PhosphorChevronUp, CircleIcon as PhosphorCircle, - ClockIcon as PhosphorClock, ChatTextIcon as PhosphorComment, + CreditCardIcon as PhosphorCreditCard, + CubeIcon as PhosphorCube, DotsSixVerticalIcon as PhosphorDotsSixVertical, DotsThreeVerticalIcon as PhosphorDotsThreeVertical, PencilSimpleIcon as PhosphorEdit, + EyeClosedIcon as PhosphorEyeClosed, HashIcon as PhosphorHash, HashStraightIcon as PhosphorHashStraight, HouseIcon as PhosphorHome, InfoIcon as PhosphorInfo, + LayoutIcon as PhosphorLayout, + LightningIcon as PhosphorLightning, LinkIcon as PhosphorLink, SpinnerGapIcon as PhosphorLoader, SignOutIcon as PhosphorLogOut, @@ -59,11 +74,14 @@ import { PlayIcon as PhosphorPlay, PlusIcon as PhosphorPlus, QuestionIcon as PhosphorQuestion, + RocketLaunchIcon as PhosphorRocketLaunch, HardDrivesIcon as PhosphorServer, + ShieldCheckIcon as PhosphorShieldCheck, + StackPlusIcon as PhosphorStackPlus, StopIcon as PhosphorStop, SunIcon as PhosphorSun, + TimerIcon as PhosphorTimer, TrashIcon as PhosphorTrash, - ArrowCounterClockwiseIcon as PhosphorUndo, UserIcon as PhosphorUser, WarningIcon as PhosphorWarning, XIcon as PhosphorX, @@ -71,7 +89,9 @@ import { import type { IconStyle } from "@sprint/shared"; import { AlertTriangle, + Box, Bug, + BugOff, Check, CheckIcon, ChevronDown, @@ -79,15 +99,18 @@ import { ChevronLeftIcon, ChevronRightIcon, ChevronUp, - ChevronUpIcon, CircleCheckIcon, CircleIcon, CircleQuestionMark, + CreditCard, Edit, EllipsisVertical, + EyeClosed, + Grid2x2Plus as GridAdd, GripVerticalIcon, Hash, InfoIcon, + LayoutDashboard, Link, Loader, Loader2Icon, @@ -99,17 +122,22 @@ import { Pause, Play, Plus, + Rocket, + RotateCcw, ServerIcon, + ShieldCheck, Square, SquareCheck, Sun, Timer, + TimerOff, Trash, TriangleAlertIcon, Undo, Undo2, UserRound, X, + Zap, } from "lucide-react"; import { useSessionSafe } from "@/components/session-provider"; @@ -118,7 +146,9 @@ import { useSessionSafe } from "@/components/session-provider"; // phosphor: https://phosphoricons.com/ const icons = { alertTriangle: { lucide: AlertTriangle, pixel: PixelAlert, phosphor: PhosphorWarning }, + box: { lucide: Box, pixel: PixelCheckboxOn, phosphor: PhosphorCube }, bug: { lucide: Bug, pixel: PixelDebug, phosphor: PhosphorBug }, + bugOff: { lucide: BugOff, pixel: PixelDebugOff, phosphor: PhosphorBug }, check: { lucide: Check, pixel: PixelCheck, phosphor: PhosphorCheck }, checkIcon: { lucide: CheckIcon, pixel: PixelCheck, phosphor: PhosphorCheck }, checkBox: { lucide: SquareCheck, pixel: PixelCheckbox, phosphor: PhosphorCheckSquare }, @@ -127,17 +157,20 @@ const icons = { chevronLeftIcon: { lucide: ChevronLeftIcon, pixel: PixelChevronLeft, phosphor: PhosphorChevronLeft }, chevronRightIcon: { lucide: ChevronRightIcon, pixel: PixelChevronRight, phosphor: PhosphorChevronRight }, chevronUp: { lucide: ChevronUp, pixel: PixelChevronUp, phosphor: PhosphorChevronUp }, - chevronUpIcon: { lucide: ChevronUpIcon, pixel: PixelChevronUp, phosphor: PhosphorChevronUp }, - circleCheckIcon: { lucide: CircleCheckIcon, pixel: PixelCheck, phosphor: PhosphorCheckCircle }, + circleCheck: { lucide: CircleCheckIcon, pixel: PixelCheck, phosphor: PhosphorCheckCircle }, circleIcon: { lucide: CircleIcon, pixel: PixelCircle, phosphor: PhosphorCircle }, circleQuestionMark: { lucide: CircleQuestionMark, pixel: PixelNoteDelete, phosphor: PhosphorQuestion }, comment: { lucide: MessageSquarePlus, pixel: PixelCommentSend, phosphor: PhosphorComment }, + creditCard: { lucide: CreditCard, pixel: PixelCreditCard, phosphor: PhosphorCreditCard }, + creditCardDelete: { lucide: CreditCard, pixel: PixelCreditCardDelete, phosphor: PhosphorCreditCard }, edit: { lucide: Edit, pixel: PixelEdit, phosphor: PhosphorEdit }, ellipsisVertical: { lucide: EllipsisVertical, pixel: PixelMoreVertical, phosphor: PhosphorDotsThreeVertical, }, + eyeClosed: { lucide: EyeClosed, pixel: PixelEyeClosed, phosphor: PhosphorEyeClosed }, + gridAdd: { lucide: GridAdd, pixel: PixelGridAdd, phosphor: PhosphorStackPlus }, gripVerticalIcon: { lucide: GripVerticalIcon, pixel: PixelViewportWide, @@ -145,26 +178,32 @@ const icons = { }, hash: { lucide: Hash, pixel: PhosphorHashStraight, phosphor: PhosphorHash }, home: { lucide: LucideHome, pixel: PixelHome, phosphor: PhosphorHome }, - infoIcon: { lucide: InfoIcon, pixel: PixelInfo, phosphor: PhosphorInfo }, + info: { lucide: InfoIcon, pixel: PixelInfo, phosphor: PhosphorInfo }, + layoutDashboard: { lucide: LayoutDashboard, pixel: PixelDashboard, phosphor: PhosphorLayout }, link: { lucide: Link, pixel: PixelLink, phosphor: PhosphorLink }, loader: { lucide: Loader, pixel: PixelLoader, phosphor: PhosphorLoader }, - loader2Icon: { lucide: Loader2Icon, pixel: PixelLoader, phosphor: PhosphorLoader }, + loader2: { lucide: Loader2Icon, pixel: PixelLoader, phosphor: PhosphorLoader }, logOut: { lucide: LogOut, pixel: PixelLogout, phosphor: PhosphorLogOut }, moon: { lucide: Moon, pixel: PixelMoon, phosphor: PhosphorMoon }, - octagonXIcon: { lucide: OctagonXIcon, pixel: PixelClose, phosphor: PhosphorOctagon }, + octagonX: { lucide: OctagonXIcon, pixel: PixelClose, phosphor: PhosphorOctagon }, pause: { lucide: Pause, pixel: PixelPause, phosphor: PhosphorPause }, play: { lucide: Play, pixel: PixelPlay, phosphor: PhosphorPlay }, plus: { lucide: Plus, pixel: PixelPlus, phosphor: PhosphorPlus }, - serverIcon: { lucide: ServerIcon, pixel: PixelServer, phosphor: PhosphorServer }, + rocket: { lucide: Rocket, pixel: PixelShip, phosphor: PhosphorRocketLaunch }, + rotateCcw: { lucide: RotateCcw, pixel: PixelUndo, phosphor: PhosphorArrowCounterClockwise }, + server: { lucide: ServerIcon, pixel: PixelServer, phosphor: PhosphorServer }, + shieldCheck: { lucide: ShieldCheck, pixel: PixelShield, phosphor: PhosphorShieldCheck }, sun: { lucide: Sun, pixel: PixelSun, phosphor: PhosphorSun }, stop: { lucide: Square, pixel: PixelStop, phosphor: PhosphorStop }, - timer: { lucide: Timer, pixel: PixelClock, phosphor: PhosphorClock }, + timer: { lucide: Timer, pixel: PixelClock, phosphor: PhosphorTimer }, + timerOff: { lucide: TimerOff, pixel: PixelClock, phosphor: PhosphorTimer }, trash: { lucide: Trash, pixel: PixelTrash, phosphor: PhosphorTrash }, - triangleAlertIcon: { lucide: TriangleAlertIcon, pixel: PixelAlert, phosphor: PhosphorWarning }, - undo: { lucide: Undo, pixel: PixelUndo, phosphor: PhosphorUndo }, - undo2: { lucide: Undo2, pixel: PixelUndo, phosphor: PhosphorUndo }, + triangleAlert: { lucide: TriangleAlertIcon, pixel: PixelAlert, phosphor: PhosphorWarning }, + undo: { lucide: Undo, pixel: PixelUndo, phosphor: PhosphorArrowCounterClockwise }, + undo2: { lucide: Undo2, pixel: PixelUndo, phosphor: PhosphorArrowCounterClockwise }, userRound: { lucide: UserRound, pixel: PixelUser, phosphor: PhosphorUser }, x: { lucide: X, pixel: PixelClose, phosphor: PhosphorX }, + zap: { lucide: Zap, pixel: PixelZap, phosphor: PhosphorLightning }, }; export type IconName = keyof typeof icons; @@ -202,7 +241,7 @@ export default function Icon({ // lucide fills sillily if (color && resolvedStyle !== "lucide") { fill = color; - } else if (resolvedStyle === "pixel" && ["bug", "moon", "hash"].includes(icon)) { + } else if (resolvedStyle === "pixel" && ["bug", "moon", "hash", "bugOff"].includes(icon)) { fill = "var(--foreground)"; } else if (resolvedStyle === "phosphor") { fill = "var(--foreground)"; diff --git a/packages/frontend/src/components/ui/select.tsx b/packages/frontend/src/components/ui/select.tsx index 2b0ea18..c9c4d1e 100644 --- a/packages/frontend/src/components/ui/select.tsx +++ b/packages/frontend/src/components/ui/select.tsx @@ -198,7 +198,7 @@ function SelectScrollUpButton({ className={cn("flex cursor-default items-center justify-center py-1", className)} {...props} > - + ); } diff --git a/packages/frontend/src/components/ui/sonner.tsx b/packages/frontend/src/components/ui/sonner.tsx index 48d6dec..9aa9c83 100644 --- a/packages/frontend/src/components/ui/sonner.tsx +++ b/packages/frontend/src/components/ui/sonner.tsx @@ -10,11 +10,11 @@ const Toaster = ({ ...props }: ToasterProps) => { theme={theme as ToasterProps["theme"]} className="toaster group" icons={{ - success: , - info: , - warning: , - error: , - loading: , + success: , + info: , + warning: , + error: , + loading: , }} style={ { diff --git a/packages/frontend/src/components/ui/switch.tsx b/packages/frontend/src/components/ui/switch.tsx index 15f72b1..c2d02a1 100644 --- a/packages/frontend/src/components/ui/switch.tsx +++ b/packages/frontend/src/components/ui/switch.tsx @@ -5,10 +5,12 @@ import { cn } from "@/lib/utils"; function Switch({ className, + thumbClassName, size = "default", ...props }: React.ComponentProps & { - size?: "sm" | "default"; + thumbClassName?: string; + size?: "sm" | "default" | "lg"; }) { return ( diff --git a/packages/frontend/src/pages/Landing.tsx b/packages/frontend/src/pages/Landing.tsx index 70bc3af..030a631 100644 --- a/packages/frontend/src/pages/Landing.tsx +++ b/packages/frontend/src/pages/Landing.tsx @@ -1,83 +1,497 @@ -import { Icon } from "@iconify/react"; +import { useState } from "react"; import { Link } from "react-router-dom"; import { useSession } from "@/components/session-provider"; import ThemeToggle from "@/components/theme-toggle"; import { Button } from "@/components/ui/button"; +import Icon from "@/components/ui/icon"; +import { Switch } from "@/components/ui/switch"; +import { cn } from "@/lib/utils"; + +const pricingTiers = [ + { + name: "Starter", + price: "£0", + priceAnnual: "£0", + period: "Free forever", + periodAnnual: "Free forever", + description: "Perfect for side projects and solo developers", + tagline: "For solo devs and small projects", + features: [ + "1 organisation (owned or joined)", + "1 project", + "100 issues", + "Up to 5 team members", + "Email support", + ], + cta: "Get started free", + ctaLink: "/login", + highlighted: false, + }, + { + name: "Pro", + price: "£11.99", + priceAnnual: "£9.99", + period: "per user/month", + periodAnnual: "per user/month", + description: "For growing teams and professionals", + tagline: "Most Popular", + features: [ + "Everything in starter", + "Unlimited organisations", + "Unlimited projects", + "Unlimited issues", + "Advanced time tracking & reports", + "Custom issue statuses", + "Priority email support", + ], + cta: "Try pro free for 14 days", + ctaLink: "/login", + highlighted: true, + }, +]; + +const faqs = [ + { + question: "Can I switch plans?", + answer: + "Yes, you can upgrade or downgrade at any time. Changes take effect immediately, and we'll prorate any charges.", + }, + { + question: "Is there a free trial?", + answer: "Yes, pro plan includes a 14-day free trial with full access. No credit card required to start.", + }, + { + question: "What payment methods do you accept?", + answer: "We accept all major credit cards.", + }, + { + question: "What if I need more users?", + answer: + "Pro plan pricing scales with your team. Add or remove users anytime, and we'll adjust your billing automatically.", + }, + { + question: "What happens when my trial ends?", + answer: + "You'll automatically downgrade to the free starter plan. No charges unless you actively upgrade to pro.", + }, + { + question: "Can I cancel anytime?", + answer: + "Absolutely. Cancel anytime with no questions asked. You'll keep access until the end of your billing period.", + }, + { + question: "Do you offer refunds?", + answer: "Yes, we offer a 30-day money-back guarantee. If Sprint isn't right for you, just let us know.", + }, +]; export default function Landing() { const { user, isLoading } = useSession(); + const [billingPeriod, setBillingPeriod] = useState<"monthly" | "annual">("monthly"); return ( -
-
-
Sprint
- +
-
-
-

Need a snappy project management tool?

-

- Build your next project with Sprint. -

-

- Sick of Jira? Say hello to your new favorite project management tool. -

-
+
+
+ {/* hero section */} +
+
+ Sprint +
+
+

+ Ship faster without the chaos +

+

+ Sprint is project management that stays out of your way. Track issues, manage sprints, and + keep your team moving—without the Jira headache. +

+
-
- {!isLoading && user ? ( - - ) : ( - - )} -
- - - - GitHub - - - {"<-- you can self-host me!"} - - +
+ {!isLoading && user ? ( + + ) : ( + <> + + + + )} +
+ +

No credit card required · Full access for 14 days

+
+ + {/* problem section */} +
+

+ Tired of spending more time managing Jira than building products? +

+
+
+ +

+ Wasting hours configuring workflows instead of shipping features +

+
+
+ +

+ Drowning in features you'll never use while missing the basics +

+
+
+ +

+ The software is full of bugs. Your flow state doesn't stand a chance +

+
+
+
+ + {/* solution section */} +
+

+ Everything you need, nothing you don't +

+
+
+ +

See your work at a glance

+

+ Beautiful, intuitive issue tracking with customizable statuses. Organize by projects and + sprints. Find what you need in seconds, not minutes. +

+
+ +
+ +

Track time without thinking

+

+ Built-in time tracking that actually works. Start, pause, resume. See where your time goes + without juggling another tool. +

+
+ +
+ +

Ship in sprints

+

+ Sprint planning that actually works. Set date ranges, assign issues, track velocity. Keep + your team aligned without the ceremony. +

+
+ +
+ +

Only use what you need

+

+ Every feature is optional. Sprints, time tracking, and other modules can be enabled or + disabled individually at the organization level. Keep your interface as minimal as your + workflow. +

+
+
+
+ + {/* features list */} +
+

+ Built for developers, by a developer +

+
+ {[ + "Organization and project management", + "Customizable issue statuses", + "Sprint planning with date ranges", + "Built-in time tracking", + "Native desktop app (Tauri)", + "Clean, resizable interface", + "Issue assignment and collaboration", + "Individual feature toggles (org level)", + ].map((feature) => ( +
+ + {feature} +
+ ))} +
+
+ + {/* pricing section */} +
+
+

Simple, transparent pricing

+

+ Choose the plan that fits your team. Scale as you grow. +

+ + {/* billing toggle */} +
+ + setBillingPeriod(checked ? "annual" : "monthly")} + className="bg-border data-[state=checked]:bg-border! data-[state=unchecked]:bg-border!" + thumbClassName="bg-personality dark:bg-personality data-[state=checked]:bg-personality! data-[state=unchecked]:bg-personality!" + aria-label="toggle billing period" + /> + + + Save 17% + +
+
+ +
+ {pricingTiers.map((tier) => ( +
+ {tier.highlighted && ( +
+ {tier.tagline} +
+ )} + +
+

{tier.name}

+
+ + {billingPeriod === "annual" ? tier.priceAnnual : tier.price} + + + {billingPeriod === "annual" ? tier.periodAnnual : tier.period} + +
+

{tier.description}

+
+ +
    + {tier.features.map((feature) => ( +
  • + + {feature} +
  • + ))} +
+ + +
+ ))} +
+ + {/* trust signals */} +
+
+ +

Secure & Encrypted

+

Your data is safe with us

+
+
+ +

No Card Required

+

Start your trial instantly

+
+
+ +

Money Back Guarantee

+

30-day no-risk policy

+
+
+ + {/* faq section */} +
+
+

Frequently Asked Questions

+
+ {faqs.map((faq) => ( +
+

{faq.question}

+

{faq.answer}

+
+ ))} +
+
+
+
+ + {/* TODO:> commented out until we have actual testimonies */} + {/* social proof placeholder */} + {/*
+

Join developers who've escaped Jira

+
+
+

+ "Finally, a project management tool that doesn't slow me down" +

+

Early user feedback

+
+
+

+ "Built by someone who actually understands developer workflows" +

+

Early user feedback

+
+
+

+ "The simplicity is refreshing. No bloat, just what we need" +

+

Early user feedback

+
+
+
*/} + + {/* final cta */} +
+

Ready to ship faster?

+

+ Start tracking issues, managing sprints, and shipping products in minutes +

+
+ {!isLoading && user ? ( + + ) : ( + + )} +
+

+ No credit card required · 14-day free trial · Cancel anytime +