feat: features breakdown
This commit is contained in:
4007
src/assets/activity-logs-dark.json
Normal file
4007
src/assets/activity-logs-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
4007
src/assets/activity-logs-light.json
Normal file
4007
src/assets/activity-logs-light.json
Normal file
File diff suppressed because it is too large
Load Diff
7956
src/assets/board-visibility-dark.json
Normal file
7956
src/assets/board-visibility-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
7956
src/assets/board-visibility-light.json
Normal file
7956
src/assets/board-visibility-light.json
Normal file
File diff suppressed because it is too large
Load Diff
6013
src/assets/comments-dark.json
Normal file
6013
src/assets/comments-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
6017
src/assets/comments-light.json
Normal file
6017
src/assets/comments-light.json
Normal file
File diff suppressed because it is too large
Load Diff
3490
src/assets/imports-dark.json
Normal file
3490
src/assets/imports-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
3490
src/assets/imports-light.json
Normal file
3490
src/assets/imports-light.json
Normal file
File diff suppressed because it is too large
Load Diff
2323
src/assets/integrations-dark.json
Normal file
2323
src/assets/integrations-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
2323
src/assets/integrations-light.json
Normal file
2323
src/assets/integrations-light.json
Normal file
File diff suppressed because it is too large
Load Diff
1675
src/assets/labels-dark.json
Normal file
1675
src/assets/labels-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
1675
src/assets/labels-light.json
Normal file
1675
src/assets/labels-light.json
Normal file
File diff suppressed because it is too large
Load Diff
6287
src/assets/templates-dark.json
Normal file
6287
src/assets/templates-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
6291
src/assets/templates-light.json
Normal file
6291
src/assets/templates-light.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import chatIconLight from "~/assets/chat-light.json";
|
||||
import chatIconDark from "~/assets/chat-dark.json";
|
||||
|
||||
const FeedbackButton: React.FC = () => {
|
||||
const { theme } = useTheme();
|
||||
const { activeTheme } = useTheme();
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
@@ -23,7 +23,7 @@ const FeedbackButton: React.FC = () => {
|
||||
>
|
||||
<LottieIcon
|
||||
index={index}
|
||||
json={theme === "dark" ? chatIconDark : chatIconLight}
|
||||
json={activeTheme === "dark" ? chatIconDark : chatIconLight}
|
||||
isPlaying={isHovered}
|
||||
/>
|
||||
<span className="ml-1">Feedback</span>
|
||||
|
||||
@@ -28,23 +28,25 @@ export default function SideNavigation({
|
||||
isLoading,
|
||||
}: SideNavigationProps) {
|
||||
const pathname = usePathname();
|
||||
const { theme } = useTheme();
|
||||
const { activeTheme } = useTheme();
|
||||
|
||||
const isDarkMode = activeTheme === "dark";
|
||||
|
||||
const navigation = [
|
||||
{
|
||||
name: "Boards",
|
||||
href: "/boards",
|
||||
icon: theme === "dark" ? boardsIconDark : boardsIconLight,
|
||||
icon: isDarkMode ? boardsIconDark : boardsIconLight,
|
||||
},
|
||||
{
|
||||
name: "Members",
|
||||
href: "/members",
|
||||
icon: theme === "dark" ? membersIconDark : membersIconLight,
|
||||
icon: isDarkMode ? membersIconDark : membersIconLight,
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
href: "/settings",
|
||||
icon: theme === "dark" ? settingsIconDark : settingsIconLight,
|
||||
icon: isDarkMode ? settingsIconDark : settingsIconLight,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function UserMenu({
|
||||
isLoading,
|
||||
}: UserMenuProps) {
|
||||
const router = useRouter();
|
||||
const { theme, switchTheme } = useTheme();
|
||||
const { themePreference, switchTheme } = useTheme();
|
||||
|
||||
const handleLogout = async () => {
|
||||
const db = createClient();
|
||||
@@ -86,7 +86,7 @@ export default function UserMenu({
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
theme === "system" ? "visible" : "invisible",
|
||||
themePreference === "system" ? "visible" : "invisible",
|
||||
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
||||
)}
|
||||
/>
|
||||
@@ -100,7 +100,7 @@ export default function UserMenu({
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
theme === "dark" ? "visible" : "invisible",
|
||||
themePreference === "dark" ? "visible" : "invisible",
|
||||
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
||||
)}
|
||||
/>
|
||||
@@ -114,7 +114,7 @@ export default function UserMenu({
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
theme === "light" ? "visible" : "invisible",
|
||||
themePreference === "light" ? "visible" : "invisible",
|
||||
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -7,8 +7,9 @@ import React, {
|
||||
} from "react";
|
||||
|
||||
interface ThemeContextProps {
|
||||
theme: string;
|
||||
switchTheme: (theme: string) => void;
|
||||
themePreference: "light" | "dark" | "system";
|
||||
activeTheme: "light" | "dark";
|
||||
switchTheme: (theme: "light" | "dark" | "system") => void;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextProps | undefined>(undefined);
|
||||
@@ -16,45 +17,45 @@ const ThemeContext = createContext<ThemeContextProps | undefined>(undefined);
|
||||
export const ThemeProvider: React.FC<{ children: ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [theme, setTheme] = useState("");
|
||||
const [themePreference, setThemePreference] = useState<
|
||||
"light" | "dark" | "system"
|
||||
>("system");
|
||||
const [activeTheme, setActiveTheme] = useState<"light" | "dark">("light");
|
||||
|
||||
const switchTheme = (theme: string) => {
|
||||
const switchTheme = (theme: "light" | "dark" | "system") => {
|
||||
if (theme === "system") {
|
||||
localStorage.removeItem("theme");
|
||||
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
setActiveTheme(isDark ? "dark" : "light");
|
||||
document.documentElement.classList.toggle("dark", isDark);
|
||||
} else {
|
||||
if (theme === "dark") {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
|
||||
const isDark = theme === "dark";
|
||||
document.documentElement.classList.toggle("dark", isDark);
|
||||
localStorage.theme = theme;
|
||||
setActiveTheme(isDark ? "dark" : "light");
|
||||
}
|
||||
setTheme(theme);
|
||||
setThemePreference(theme);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!("theme" in localStorage)) {
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
setTheme("system");
|
||||
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
document.documentElement.classList.toggle("dark", isDark);
|
||||
setActiveTheme(isDark ? "dark" : "light");
|
||||
setThemePreference("system");
|
||||
} else {
|
||||
if (localStorage.theme === "dark") {
|
||||
document.documentElement.classList.add("dark");
|
||||
setTheme("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
setTheme("light");
|
||||
}
|
||||
const isDark = localStorage.theme === "dark";
|
||||
document.documentElement.classList.toggle("dark", isDark);
|
||||
setActiveTheme(isDark ? "dark" : "light");
|
||||
setThemePreference(localStorage.theme as "light" | "dark");
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ switchTheme, theme }}>
|
||||
{theme.length ? children : null}
|
||||
<ThemeContext.Provider
|
||||
value={{ switchTheme, themePreference, activeTheme }}
|
||||
>
|
||||
{themePreference.length ? children : null}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
155
src/views/home/components/Features.tsx
Normal file
155
src/views/home/components/Features.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import LottieIcon from "~/components/LottieIcon";
|
||||
|
||||
import boardVisibilityIconLight from "~/assets/board-visibility-light.json";
|
||||
import boardVisibilityIconDark from "~/assets/board-visibility-dark.json";
|
||||
import membersIconLight from "~/assets/members-light.json";
|
||||
import membersIconDark from "~/assets/members-dark.json";
|
||||
import commentsIconLight from "~/assets/comments-light.json";
|
||||
import commentsIconDark from "~/assets/comments-dark.json";
|
||||
import integrationsIconLight from "~/assets/integrations-light.json";
|
||||
import integrationsIconDark from "~/assets/integrations-dark.json";
|
||||
import labelsIconLight from "~/assets/labels-light.json";
|
||||
import labelsIconDark from "~/assets/labels-dark.json";
|
||||
import importsIconLight from "~/assets/imports-light.json";
|
||||
import importsIconDark from "~/assets/imports-dark.json";
|
||||
import activityLogsIconLight from "~/assets/activity-logs-light.json";
|
||||
import activityLogsIconDark from "~/assets/activity-logs-dark.json";
|
||||
import templatesIconLight from "~/assets/templates-light.json";
|
||||
import templatesIconDark from "~/assets/templates-dark.json";
|
||||
|
||||
const FeatureItem = ({
|
||||
feature,
|
||||
}: {
|
||||
feature: {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: Record<string, unknown>;
|
||||
comingSoon?: boolean;
|
||||
};
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setIsHovered(true);
|
||||
setIndex((index) => index + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={handleMouseEnter}
|
||||
className="group relative flex h-56 w-56 flex-col items-center justify-center overflow-hidden rounded-3xl border border-light-200 bg-light-50 dark:border-dark-200 dark:bg-dark-50"
|
||||
>
|
||||
<div className="absolute left-8 top-8 h-2 w-2 rounded-full bg-light-200 dark:bg-dark-200 " />
|
||||
<div className="absolute right-8 top-8 h-2 w-2 rounded-full bg-light-200 dark:bg-dark-200" />
|
||||
<div className="absolute bottom-8 left-8 h-2 w-2 rounded-full bg-light-200 dark:bg-dark-200" />
|
||||
<div className="absolute bottom-8 right-8 h-2 w-2 rounded-full bg-light-200 dark:bg-dark-200" />
|
||||
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl border border-light-300 bg-light-200 dark:border-dark-600 dark:bg-dark-200">
|
||||
<LottieIcon index={index} json={feature.icon} isPlaying={isHovered} />
|
||||
</div>
|
||||
|
||||
<div className="relative mt-2 w-full px-4 text-center">
|
||||
<p className="text-sm font-bold text-light-1000 transition-opacity duration-200 group-hover:opacity-0 dark:text-dark-1000">
|
||||
{feature.title}
|
||||
</p>
|
||||
<p className="absolute inset-0 px-4 text-sm text-light-950 opacity-0 transition-opacity duration-200 group-hover:opacity-100 dark:text-dark-900">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{feature.comingSoon && (
|
||||
<div className="absolute right-4 top-4 rounded-full border border-light-300 px-2 py-1 text-[10px] text-light-1000 dark:border-dark-600 dark:bg-dark-50 dark:text-dark-900">
|
||||
Coming soon
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Features = ({ theme }: { theme: "light" | "dark" }) => {
|
||||
const isDark = theme === "dark";
|
||||
const features = [
|
||||
{
|
||||
title: "Board visibility",
|
||||
description: "Control who can view and edit your boards.",
|
||||
icon: isDark ? boardVisibilityIconDark : boardVisibilityIconLight,
|
||||
},
|
||||
{
|
||||
title: "Workspace members",
|
||||
description: "Collaborate seamlessly with your team.",
|
||||
icon: isDark ? membersIconDark : membersIconLight,
|
||||
},
|
||||
{
|
||||
title: "Trello imports",
|
||||
description: "Import your Trello boards and hit the ground running.",
|
||||
icon: isDark ? importsIconDark : importsIconLight,
|
||||
},
|
||||
{
|
||||
title: "Labels & Filters",
|
||||
description:
|
||||
"Organize and find cards quickly with powerful filtering tools.",
|
||||
icon: isDark ? labelsIconDark : labelsIconLight,
|
||||
},
|
||||
{
|
||||
title: "Comments",
|
||||
description: "Discuss and collaborate on cards.",
|
||||
icon: isDark ? commentsIconDark : commentsIconLight,
|
||||
},
|
||||
{
|
||||
title: "Activity logs",
|
||||
description: "Track all card changes with detailed activity history.",
|
||||
icon: isDark ? activityLogsIconDark : activityLogsIconLight,
|
||||
},
|
||||
{
|
||||
title: "Templates",
|
||||
description: "Save time with reusable board templates.",
|
||||
icon: isDark ? templatesIconDark : templatesIconLight,
|
||||
comingSoon: true,
|
||||
},
|
||||
{
|
||||
title: "Integrations",
|
||||
description: "Connect your favorite tools to streamline your workflow.",
|
||||
icon: isDark ? integrationsIconDark : integrationsIconLight,
|
||||
comingSoon: true,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col items-center justify-center pb-24">
|
||||
<div className="flex items-center gap-2 rounded-full border bg-light-50 px-4 py-1 text-center text-sm text-light-1000 dark:border-dark-300 dark:bg-dark-50 dark:text-dark-900">
|
||||
<p>Features</p>
|
||||
</div>
|
||||
|
||||
<p className="mt-2 text-center text-4xl font-bold text-light-1000 dark:text-dark-1000">
|
||||
Kanban simplified
|
||||
</p>
|
||||
<p className="mt-3 max-w-[600px] text-center text-lg text-dark-900">
|
||||
Simple, visual task management that just works. Drag and drop cards,
|
||||
collaborate with your team, and get more done.
|
||||
</p>
|
||||
<div className="mt-16 grid grid-cols-4 gap-6 [mask-image:linear-gradient(to_bottom,black_80%,transparent_100%)]">
|
||||
{features.map((feature, index) => {
|
||||
return <FeatureItem key={`feature-${index}`} feature={feature} />;
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="mt-8 flex items-center gap-2 rounded-full border bg-light-50 px-4 py-1 text-center text-sm text-light-1000 dark:border-dark-300 dark:bg-dark-50 dark:text-dark-900">
|
||||
<p>
|
||||
{`We're just getting started. `}
|
||||
<Link href="/roadmap" className="underline">
|
||||
View our roadmap.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Features;
|
||||
@@ -64,7 +64,7 @@ const Header = ({ isLoggedIn }: { isLoggedIn: boolean }) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isScrolled && <div className="h-[4rem] min-h-[4rem]"></div>}
|
||||
{isScrolled && <div className="h-[5rem] min-h-[5rem]"></div>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import Button from "~/components/Button";
|
||||
import PatternedBackground from "~/components/PatternedBackground";
|
||||
|
||||
import Header from "./components/Header";
|
||||
import Features from "./components/Features";
|
||||
import Pricing from "./components/Pricing";
|
||||
|
||||
import { api } from "~/utils/api";
|
||||
@@ -28,13 +29,16 @@ export default function HomeView() {
|
||||
|
||||
const isLoggedIn = !!data;
|
||||
|
||||
const isDarkMode = theme.activeTheme === "dark";
|
||||
|
||||
return (
|
||||
<>
|
||||
<style jsx global>{`
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
body {
|
||||
background-color: ${theme.theme === "light"
|
||||
? "hsl(0deg 0% 98.8%)"
|
||||
: "#161616"};
|
||||
background-color: ${!isDarkMode ? "hsl(0deg 0% 98.8%)" : "#161616"};
|
||||
}
|
||||
`}</style>
|
||||
<div className="mx-auto flex h-full min-h-screen flex-col items-center bg-light-100 dark:bg-dark-50">
|
||||
@@ -84,11 +88,11 @@ export default function HomeView() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-10 rounded-[24px] border border-light-300 bg-light-50 p-2 shadow-md dark:border-dark-300 dark:bg-dark-50">
|
||||
<div className="mb-24 rounded-[24px] border border-light-300 bg-light-50 p-2 shadow-md dark:border-dark-300 dark:bg-dark-50">
|
||||
<div className="rounded-[16px] border border-light-300 bg-light-200 p-2 dark:border-dark-300 dark:bg-dark-200">
|
||||
<div className="overflow-hidden rounded-[16px] shadow-sm">
|
||||
<Image
|
||||
src={`/hero-${theme.theme}.png`}
|
||||
src={`/hero-${isDarkMode ? "dark" : "light"}.png`}
|
||||
alt="kanban"
|
||||
width={1100}
|
||||
height={1000}
|
||||
@@ -96,7 +100,12 @@ export default function HomeView() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-10" id="pricing">
|
||||
<div className="relative pt-10">
|
||||
<div id="features" className="absolute -top-20" />
|
||||
<Features theme={theme.activeTheme} />
|
||||
</div>
|
||||
<div className="relative pt-10">
|
||||
<div id="pricing" className="absolute -top-20" />
|
||||
<Pricing />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user