Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app/(marketing)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LandingFeaturesSection1 } from "@/components/landing/LandingFeaturesSection1";
import { LandingHeroSection } from "@/components/landing/LandingHeroSection";

export const revalidate = 21600;
Expand All @@ -6,6 +7,7 @@ export default function Home() {
return (
<>
<LandingHeroSection />
<LandingFeaturesSection1 />
</>
);
}
27 changes: 27 additions & 0 deletions src/components/landing/LandingFeatureGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";

import { FEATURES } from "@/lib/constants";
import { cn } from "@/lib/utils";
import { FeatureButton } from "@/shared/FeatureButton";
import { Icon } from "@/shared/Icon";
import { useFeaturePreviewStore } from "@/stores/featurePreviewStore";
import type { LandingFeatureGridProps } from "@/types/landing";

export function LandingFeatureGrid({ className }: LandingFeatureGridProps) {
const setActiveFeature = useFeaturePreviewStore((state) => state.setActiveFeature);

return (
<div className={cn("grid grid-cols-1 gap-3", "md:grid-cols-2 md:gap-8", className)}>
{FEATURES.map((feature) => (
<FeatureButton
key={feature.title}
icon={<Icon name={feature.iconName} size={19} />}
title={feature.title}
description={feature.description}
onMouseEnter={() => setActiveFeature(feature)}
onFocus={() => setActiveFeature(feature)}
/>
))}
</div>
);
}
57 changes: 57 additions & 0 deletions src/components/landing/LandingFeaturesSection1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client";

import { LandingFeatureGrid } from "@/components/landing/LandingFeatureGrid";
import { cn } from "@/lib/utils";
import { useFeaturePreviewStore } from "@/stores/featurePreviewStore";
import type { LandingFeaturesSection1Props } from "@/types/landing";
import Image from "next/image";

export function LandingFeaturesSection1({ className }: LandingFeaturesSection1Props) {
const activeFeature = useFeaturePreviewStore((state) => state.activeFeature);

return (
<section
id="landing-features1"
aria-labelledby="landing-features1-title"
className={cn(
"grid gap-18",
"md:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)] md:items-center",
className,
)}
>
{/* Left: ์ œ๋ชฉ + ๊ธฐ๋Šฅ ๋ฆฌ์ŠคํŠธ */}
<div className="space-y-8">
<header className="space-y-6">
<h2
id="landing-features1-title"
className="t-30-b md:t-40-b text-[var(--color-gray-900)]"
>
ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋งŒ ๊ณจ๋ผ ์“ฐ๋Š”
<br />
๋ฐ์ผ๋ฆฌ ํ”Œ๋ž˜๋„ˆ
</h2>
<p className="t-12-m md:t-16-m text-[var(--color-gray-600)]">
๋‚˜์—๊ฒŒ ํ•„์š”ํ•œ ๋ชจ๋“ˆ๋งŒ ์„ ํƒํ•ด ๋‚˜๋งŒ์˜ ํ”Œ๋ž˜๋„ˆ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
</p>
</header>

<LandingFeatureGrid />
</div>

{/* Right: ํ™”๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์นด๋“œ */}
<div className="relative">
<div className="relative mx-auto max-w-full rounded-[32px] bg-[var(--color-gray-50)]">
<div className="relative aspect-[16/10] w-full overflow-hidden rounded-3xl bg-[var(--color-white)] shadow-[0_18px_60px_rgba(0,0,0,0.12)]">
<Image
src={activeFeature.previewImageSrc}
alt={`${activeFeature.title} ํ”Œ๋ž˜๋„ˆ ํ™”๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐ`}
fill
className="object-cover"
sizes="(min-width: 1024px) 480px, 100vw"
/>
</div>
</div>
</div>
</section>
);
}
4 changes: 1 addition & 3 deletions src/components/landing/LandingHeroCtas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import Link from "next/link";

export function LandingHeroCtas() {
return (
<div
className={cn("flex flex-col gap-12 mt-20", "md:flex-row md:items-center md:gap-8 md:mt-10")}
>
<div className={cn("flex items-center flex-col gap-8 mt-10", "md:flex-row md:gap-12 md:mt-20")}>
<Link href="/login">
<Button preset="hero" pill>
<span className="inline-flex items-center gap-4">
Expand Down
7 changes: 6 additions & 1 deletion src/components/landing/LandingMainSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ export function LandingMainSection({ children, className }: LandingMainSectionPr
return (
<main
id="landing-main"
className={cn("mx-auto w-full max-w-[128rem] px-6 mt-70", "space-y-[10rem]", className)}
className={cn(
"mx-auto w-full max-w-[128rem] px-6 mt-30",
"space-y-[35rem]",
"md:mt-45 lg:mt-70",
className,
)}
>
{children}
</main>
Expand Down
50 changes: 49 additions & 1 deletion src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FeatureItem } from "@/types/landing";

/* -------------------------------------------------
๐Ÿ“ฆ ํ”„๋กœ์ ํŠธ ๊ณตํ†ต ์ƒ์ˆ˜ (Global Constants)
- SEO / OG / ์‚ฌ์ดํŠธ ๋ฉ”ํƒ€ ์ •๋ณด
Expand Down Expand Up @@ -30,12 +32,13 @@ export const LOCALE = "ko_KR";
- SignupGroupButton์—์„œ ์‚ฌ์šฉ
- bg: cva variant key์™€ 1:1 ๋งคํ•‘
------------------------------------------------- */

export const SIGNUP_BTNS = [
{
key: "email",
bg: "basic" as const,
label: "์ผ๋ฐ˜ ํšŒ์›๊ฐ€์ž…",
icon: { kind: "lucide" as const, name: "user-plus2" as const, size: 28 }, // 28px โ‰ˆ w-7 h-7
icon: { kind: "lucide" as const, name: "user-plus2" as const, size: 28 }, // 28px โ‰’ w-7 h-7
},
{
key: "google",
Expand All @@ -52,3 +55,48 @@ export const SIGNUP_BTNS = [
] as const;

export type SignupButtonKey = (typeof SIGNUP_BTNS)[number]["key"];

/* -------------------------------------------------
๐Ÿงฉ ๋žœ๋”ฉ ํŽ˜์ด์ง€ โ€” ๊ธฐ๋Šฅ ์†Œ๊ฐœ ์„น์…˜ 1
- LandingFeatureGrid / LandingFeaturesSection1์—์„œ ์‚ฌ์šฉ
- โ€œ์ผ๊ฐ„ / ์ฃผ๊ฐ„ / ์›”๊ฐ„ / To-Do / ์Šต๊ด€ / ๋ฉ”๋ชจโ€ 6๊ฐœ ๋ชจ๋“ˆ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ
------------------------------------------------- */

export const FEATURES: FeatureItem[] = [
{
title: "์ผ๊ฐ„",
description: "์˜ค๋Š˜์˜ ์ผ์ •๊ณผ ํ•  ์ผ์„ ํ•œ๋ˆˆ์—",
iconName: "calendar",
previewImageSrc: "/images/feature-daily.png",
},
{
title: "์ฃผ๊ฐ„",
description: "์ผ์ฃผ์ผ ๋‹จ์œ„๋กœ ๊ณ„ํš์„ ์„ธ์šฐ๊ณ  ๊ด€๋ฆฌ",
iconName: "calendarDays",
previewImageSrc: "/images/feature-weekly.png",
},
{
title: "์›”๊ฐ„",
description: "ํ•œ ๋‹ฌ ์ „์ฒด ์ผ์ •์„ ์กฐ๋งํ•˜๊ณ  ๊ณ„ํš",
iconName: "calendarRange",
previewImageSrc: "/images/feature-monthly.png",
},
{
title: "To-Do",
description: "ํ•ด์•ผ ํ•  ์ผ์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ •๋ฆฌ",
iconName: "checkSquare",
previewImageSrc: "/images/feature-todo.png",
},
{
title: "์Šต๊ด€",
description: "์ข‹์€ ์Šต๊ด€์„ ๋งŒ๋“ค๊ณ  ์ง€์†์ ์œผ๋กœ ์ถ”์ ",
iconName: "rotateCcw",
previewImageSrc: "/images/feature-habit.png",
},
{
title: "๋ฉ”๋ชจ",
description: "์ค‘์š”ํ•œ ์ƒ๊ฐ๊ณผ ์•„์ด๋””์–ด๋ฅผ ๊ธฐ๋ก",
iconName: "stickyNote",
previewImageSrc: "/images/feature-memo.png",
},
];
Comment on lines +65 to +102

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Provide assets for Landing feature previews

The new FEATURES metadata references six local preview images (e.g. "/images/feature-daily.png") that are rendered in LandingFeaturesSection1, but the public/images directory only contains logo.png. At runtime the preview <Image> in the landing page will try to load these files and each request will return 404, so the main visual element of the new feature section will appear broken. Please add the images or point the constants at existing assets.

Useful? React with ๐Ÿ‘ย / ๐Ÿ‘Ž.

15 changes: 15 additions & 0 deletions src/stores/featurePreviewStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import { FEATURES } from "@/lib/constants";
import type { FeatureItem } from "@/types/landing";
import { create } from "zustand";

type FeaturePreviewState = {
activeFeature: FeatureItem;
setActiveFeature: (feature: FeatureItem) => void;
};

export const useFeaturePreviewStore = create<FeaturePreviewState>((set) => ({
activeFeature: FEATURES[0], // ๊ธฐ๋ณธ: "์ผ๊ฐ„"
setActiveFeature: (feature) => set({ activeFeature: feature }),
}));
18 changes: 0 additions & 18 deletions src/stores/useUserStore.ts

This file was deleted.

61 changes: 52 additions & 9 deletions src/types/landing.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,31 @@
export type LandingWrapperProps = {
children: React.ReactNode;
import type { ReactNode } from "react";

/* -------------------------------------------------
๊ณตํ†ต ๋ฒ ์ด์Šค ํƒ€์ž…
------------------------------------------------- */

type WithChildren = {
children: ReactNode;
};

type WithClassName = {
className?: string;
};

type WithChildrenAndClassName = WithChildren & WithClassName;

/* -------------------------------------------------
๋ ˆ์ด์•„์›ƒ / ๋ž˜ํผ
------------------------------------------------- */

export type LandingWrapperProps = WithChildrenAndClassName;

export type LandingMainSectionProps = WithChildrenAndClassName;

/* -------------------------------------------------
Footer
------------------------------------------------- */

export type FooterLink = {
label: string;
href: string;
Expand All @@ -13,11 +36,31 @@ export type LandingFooterColumnProps = {
links: FooterLink[];
};

export type LandingMainSectionProps = {
children: React.ReactNode;
className?: string;
};
/* -------------------------------------------------
Hero / Features ์„น์…˜
------------------------------------------------- */

export type LandingHeroSectionProps = {
className?: string;
};
export type LandingHeroSectionProps = WithClassName;

export type LandingFeaturesSection1Props = WithClassName;

export type LandingFeatureGridProps = WithClassName;

/* -------------------------------------------------
๊ธฐ๋Šฅ ์†Œ๊ฐœ ์„น์…˜ 1 โ€” Feature ๋ฆฌ์ŠคํŠธ
------------------------------------------------- */

export type FeatureIconName =
| "calendar"
| "calendarDays"
| "calendarRange"
| "checkSquare"
| "rotateCcw"
| "stickyNote";

export interface FeatureItem {
title: string;
description: string;
iconName: FeatureIconName;
previewImageSrc: string;
}