-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/CDP-168-LandingHero #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b87291d
96ed196
a0f2984
b17b3bd
cb213e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,142 +1,11 @@ | ||
| "use client"; | ||
| import { LandingHeroSection } from "@/components/landing/LandingHeroSection"; | ||
|
|
||
| import { SignupGroupButton } from "@/components/SiginupGroupButton"; | ||
| import { BackButton } from "@/shared/BackButton"; | ||
| import { Button } from "@/shared/button"; | ||
| import { FeatureButton } from "@/shared/FeatureButton"; | ||
| import { Icon } from "@/shared/Icon"; | ||
| import { Input } from "@/shared/input"; | ||
| import { SelectModuleCard } from "@/shared/SelectModuleCard"; | ||
| import { SpecialFeatureCard } from "@/shared/SpecialFeatureCard"; | ||
| import { InputStatus } from "@/types/input"; | ||
| import { useState } from "react"; | ||
| export const revalidate = 21600; | ||
|
|
||
| export default function Home() { | ||
| const [email, setEmail] = useState(""); | ||
| const [status, setStatus] = useState<InputStatus>("default"); | ||
| const [isReady, setIsReady] = useState(false); | ||
|
|
||
| return ( | ||
| <div className="flex flex-col justify-center items-center gap-8"> | ||
| {/* Hero CTA */} | ||
| <div className="flex gap-3"> | ||
| <Button preset="hero" pill> | ||
| <span className="inline-flex items-center gap-4"> | ||
| <Icon name="monitor" /> | ||
| <span>Desktop</span> | ||
| </span> | ||
| </Button> | ||
| <Button preset="hero" pill> | ||
| <span className="inline-flex items-center gap-4"> | ||
| <Icon name="laptop" /> | ||
| <span>Mac</span> | ||
| </span> | ||
| </Button> | ||
| <Button preset="hero" pill> | ||
| <span className="inline-flex items-center gap-4"> | ||
| <Icon name="smartphone" /> | ||
| <span>iOS</span> | ||
| </span> | ||
| </Button> | ||
| </div> | ||
|
|
||
| <FeatureButton | ||
| icon={<Icon name="calendar" size={19} />} | ||
| title="μΌκ°" | ||
| description="μ€λμ μΌμ κ³Ό ν μΌμ νλμ" | ||
| /> | ||
|
|
||
| <SpecialFeatureCard | ||
| icon={<Icon name="calendar" size={23} />} | ||
| title="μ€λ§νΈ μ€μΌμ€λ§" | ||
| description="AIκ° μΆμ²νλ μ΅μ μ μΌμ λ°°μΉλ‘ ν¨μ¨μ±μ κ·ΉλννμΈμ." | ||
| /> | ||
|
|
||
| <Button preset="auth" color="black"> | ||
| λ‘κ·ΈμΈ νκΈ° | ||
| </Button> | ||
|
|
||
| <section className="space-y-2"> | ||
| <h2 className="t-18-b">μλ¬ μν</h2> | ||
| <div className="space-y-1"> | ||
| <label htmlFor="email" className="t-14-m text-muted-foreground"> | ||
| μ΄λ©μΌ | ||
| </label> | ||
|
|
||
| {/** status: "default" | "error" */} | ||
| <Input | ||
| id="email" | ||
| type="email" | ||
| value={email} | ||
| onChange={(e) => setEmail(e.target.value)} | ||
| status={status} // β cvaμ 1:1 | ||
| placeholder="μ΄λ©μΌμ μ λ ₯νμΈμ" | ||
| aria-describedby={status === "error" ? "email-error" : undefined} | ||
| onFocus={() => setStatus("default")} | ||
| /> | ||
|
|
||
| {status === "error" && ( | ||
| <p id="email-error" className="t-12-m text-destructive"> | ||
| μ΄λ©μΌ νμμ νμΈνμΈμ. | ||
| </p> | ||
| )} | ||
|
|
||
| <div className="flex gap-2 pt-2"> | ||
| <button | ||
| type="button" | ||
| onClick={() => setStatus((v: InputStatus) => (v === "error" ? "default" : "error"))} | ||
| className="rounded-md border px-3 py-1 text-sm" | ||
| > | ||
| μλ¬ ν κΈ | ||
| </button> | ||
| <button | ||
| type="button" | ||
| onClick={() => { | ||
| setEmail(""); | ||
| setStatus("default"); | ||
| }} | ||
| className="rounded-md border px-3 py-1 text-sm" | ||
| > | ||
| κ° μ΄κΈ°ν | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </section> | ||
|
|
||
| <SignupGroupButton /> | ||
|
|
||
| <BackButton>μ΄μ </BackButton> | ||
|
|
||
| <Button | ||
| preset="cta" | ||
| disabled={!isReady} // falseλ©΄ νμ±, trueλ©΄ λΉνμ± | ||
| onClick={() => alert("λ€μ λ¨κ³λ‘ μ΄λ")} | ||
| > | ||
| μ ν μλ£ | ||
| </Button> | ||
|
|
||
| <Button preset="cta" disabled={false} onClick={() => setIsReady((prev) => !prev)}> | ||
| {isReady ? "μ ν ν΄μ " : "νμ±ν ν κΈ"} | ||
| </Button> | ||
|
|
||
| <SelectModuleCard | ||
| kind="module" // "module" | "design" | ||
| title="μΌκ° νλλ" | ||
| subtitle="Daily Intelligence" | ||
| description="ν루λ₯Ό 체κ³μ μΌλ‘ κ΄λ¦¬νμΈμ" | ||
| imageSrc="/images/daily-planner.png" | ||
| onClick={() => console.log("μΌκ° νλλ ν΄λ¦")} | ||
| /> | ||
|
|
||
| <SelectModuleCard | ||
| kind="design" // "module" | "design" | ||
| title="μΌκ° νλλ" | ||
| subtitle="Daily Intelligence" | ||
| subtitleState="noSelect" | ||
| description="ν루λ₯Ό 체κ³μ μΌλ‘ κ΄λ¦¬νμΈμ" | ||
| imageSrc="/images/daily-planner.png" | ||
| onClick={() => console.log("μΌκ° νλλ ν΄λ¦")} | ||
| /> | ||
| </div> | ||
| <> | ||
| <LandingHeroSection /> | ||
| </> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| "use client"; | ||
|
|
||
| import { cn } from "@/lib/utils"; | ||
|
|
||
| export function LandingHeroAnimation() { | ||
| return ( | ||
| <div className="relative"> | ||
| <div | ||
| className={cn( | ||
| "group relative overflow-hidden rounded-2xl bg-[var(--color-white)]", | ||
| "shadow-[0_12px_40px_rgba(0,0,0,0.12)]", | ||
| "aspect-video w-full", | ||
| )} | ||
| > | ||
| <video | ||
| className="h-full w-full object-cover" | ||
| src="/videos/landing-hero.mp4" | ||
| autoPlay | ||
| loop | ||
| muted | ||
| playsInline | ||
| aria-label="PlanMate λ°μΌλ¦¬ νλλ μ¬μ© μμ μμ" | ||
| /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { cn } from "@/lib/utils"; | ||
| import { Button } from "@/shared/button"; | ||
| import { Icon } from "@/shared/Icon"; | ||
| import Link from "next/link"; | ||
|
|
||
| export function LandingHeroCtas() { | ||
| return ( | ||
| <div className={cn("flex flex-col gap-12 mt-20", "md:flex-row items-center gap-8 mt-10")}> | ||
| <Link href="/login"> | ||
| <Button preset="hero" pill> | ||
| <span className="inline-flex items-center gap-4"> | ||
| <Icon name="monitor" /> | ||
| <span>Desktop</span> | ||
| </span> | ||
| </Button> | ||
| </Link> | ||
|
Comment on lines
+9
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Each CTA wraps the shared Useful? React with πΒ / π. |
||
| <Link href="/login"> | ||
| <Button preset="hero" pill> | ||
| <span className="inline-flex items-center gap-4"> | ||
| <Icon name="laptop" /> | ||
| <span>Mac</span> | ||
| </span> | ||
| </Button> | ||
| </Link> | ||
| <Link href="/login"> | ||
| <Button preset="hero" pill> | ||
| <span className="inline-flex items-center gap-4"> | ||
| <Icon name="smartphone" /> | ||
| <span>iOS</span> | ||
| </span> | ||
| </Button> | ||
| </Link> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { LandingHeroAnimation } from "@/components/landing/LandingHeroAnimation"; | ||
| import { LandingHeroCtas } from "@/components/landing/LandingHeroCtas"; | ||
| import { LandingHeroTitle } from "@/components/landing/LandingHeroTitle"; | ||
| import { cn } from "@/lib/utils"; | ||
| import { LandingHeroSectionProps } from "@/types/landing"; | ||
|
|
||
| export function LandingHeroSection({ className }: LandingHeroSectionProps) { | ||
| return ( | ||
| <section | ||
| id="landing-hero" | ||
| aria-labelledby="landing-hero-title" | ||
| className={cn( | ||
| "grid gap-10", | ||
| "md:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)] md:items-center", | ||
| className, | ||
| )} | ||
| > | ||
| <div className="space-y-8"> | ||
| <LandingHeroTitle /> | ||
| <LandingHeroCtas /> | ||
| </div> | ||
| <LandingHeroAnimation /> | ||
| </section> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { cn } from "@/lib/utils"; | ||
|
|
||
| export function LandingHeroTitle() { | ||
| return ( | ||
| <header className="space-y-8"> | ||
| <h1 | ||
| id="landing-hero-title" | ||
| className={cn("t-40-b leading-tight text-[var(--color-gray-900)]", "md:t-58-b")} | ||
| > | ||
| λλ§μ λ°μΌλ¦¬ νλλ | ||
| <br /> | ||
| PlanMate | ||
| </h1> | ||
| <p className={cn("t-14-m text-[var(--color-gray-600)]", "md:t-18-m")}> | ||
| μνλ κΈ°λ₯λ§ κ³¨λΌ μ°λ λ§μΆ€ν μΌμ κ΄λ¦¬ μλΉμ€μ λλ€. | ||
| </p> | ||
| </header> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hero animation component references
/videos/landing-hero.mp4, but no file with that path exists underpublic/in this repository. At runtime the<video>tag will 404 and render empty, so users never see the intended animation. Either add the video asset topublic/videos/landing-hero.mp4or adjust the source to an existing file.Useful? React with πΒ / π.