Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
5 changes: 5 additions & 0 deletions apps/web/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ STRIPE_SECRET_KEY=""
STRIPE_WEBHOOK_SECRET=""

VITE_STRIPE_PUBLISHABLE_KEY=""

VITE_POSTHOG_API_KEY=""
VITE_POSTHOG_HOST="https://us.i.posthog.com"

LOOPS_KEY=""
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@nangohq/frontend": "^0.69.7",
"@nangohq/node": "^0.69.7",
"@netlify/vite-plugin-tanstack-start": "^1.1.8",
"@posthog/react": "^1.3.0",
"@sentry/tanstackstart-react": "^10.22.0",
"@stripe/stripe-js": "^8.2.0",
"@supabase/ssr": "^0.7.0",
Expand All @@ -37,6 +38,7 @@
"exa-js": "^1.10.2",
"lucide-react": "^0.544.0",
"postgres": "^3.4.7",
"posthog-js": "^1.284.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"rehype-autolink-headings": "^7.1.0",
Expand Down
Binary file added apps/web/public/hyprnote/calendar.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/hyprnote/chat.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/hyprnote/contacts.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/hyprnote/editor.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/hyprnote/no-bots.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/hyprnote/no-wifi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/hyprnote/poster-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/hyprnote/summary.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/hyprnote/transcript.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions apps/web/public/patterns/dots.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions apps/web/public/patterns/slash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 3 additions & 15 deletions apps/web/src/components/download-button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { cn } from "@hypr/utils";

import { Icon } from "@iconify-icon/react";
import { Link } from "@tanstack/react-router";

export function DownloadButton() {
Expand All @@ -13,21 +14,8 @@ export function DownloadButton() {
"transition-all",
])}
>
Download now
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="h-5 w-5 ml-2 group-hover:translate-x-1 transition-transform"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
</svg>
<Icon icon="mdi:apple" className="text-xl mr-2" />
Download for Mac
</Link>
);
}
8 changes: 8 additions & 0 deletions apps/web/src/components/github-open-source.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ export function GitHubOpenSource() {

return (
<section className="border-t border-neutral-100">
<div
className="border-b border-neutral-100 bg-neutral-50 h-4"
style={{ backgroundImage: "url(/patterns/slash.svg)" }}
/>
<div className="px-4 py-8">
<div className="lg:hidden max-w-4xl mx-auto">
<OpenSourceButton showStars={true} starCount={STARS_COUNT} />
Expand Down Expand Up @@ -276,6 +280,10 @@ export function GitHubOpenSource() {
</div>
</div>
</div>
<div
className="border-t border-neutral-100 bg-neutral-50 h-4"
style={{ backgroundImage: "url(/patterns/slash.svg)" }}
/>
</section>
);
}
2 changes: 1 addition & 1 deletion apps/web/src/components/join-waitlist-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { cn } from "@hypr/utils";
export function JoinWaitlistButton() {
return (
<a
href="https://tally.so/r/mJaRDY"
href="/join-waitlist"
target="_blank"
rel="noopener noreferrer"
className={cn([
Expand Down
14 changes: 7 additions & 7 deletions apps/web/src/components/logo-cloud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function LogoCloud() {
/>

<LogoCard
className="border-b border-neutral-100 md:border-r"
className="md:border-r border-b border-neutral-100"
logo={{
src: "/icons/meta.svg",
alt: "Meta Logo",
Expand All @@ -62,39 +62,39 @@ export function LogoCloud() {
/>

<LogoCard
className="relative border-b border-neutral-100 md:border-r"
className="border-b border-neutral-100 md:border-r"
logo={{
src: "/icons/richmond_american.svg",
alt: "Richmond American Logo",
}}
/>

<LogoCard
className="relative border-b border-neutral-100"
className="relative border-r border-b border-neutral-100 md:border-r-0"
logo={{
src: "/icons/wayfair.svg",
alt: "Wayfair Logo",
}}
/>

<LogoCard
className="md:border-r border-neutral-100"
className="border-b border-neutral-100 md:border-r md:border-b-0"
logo={{
src: "/icons/amazon.svg",
alt: "Amazon Logo",
}}
/>

<LogoCard
className="border-r border-neutral-100"
className="border-r border-b border-neutral-100 md:border-b-0"
logo={{
src: "/icons/palantir.svg",
alt: "Palantir Logo",
}}
/>

<LogoCard
className="border-r border-neutral-100"
className="border-b border-neutral-100 md:border-r md:border-b-0"
logo={{
src: "/icons/disney.svg",
alt: "Disney Logo",
Expand All @@ -110,7 +110,7 @@ export function LogoCloud() {
/>

<LogoCard
className=""
className="border-neutral-100"
logo={{
src: "/icons/bain.svg",
alt: "Bain Logo",
Expand Down
27 changes: 17 additions & 10 deletions apps/web/src/components/social-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,12 @@ export function SocialCard({
const config = platformConfig[platform];

return (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
<div
className={cn(
"block border border-neutral-100 bg-white p-6 transition-all duration-200 text-left hover:bg-neutral-50",
className,
[
"block border border-neutral-100 bg-white p-6 transition-all duration-200 text-left hover:bg-neutral-50",
className,
],
)}
>
<div className="space-y-4 text-left">
Expand All @@ -70,12 +69,20 @@ export function SocialCard({
</p>
{config.subtitle && <p className="text-sm text-neutral-600">{config.subtitle}</p>}
</div>
<svg className={cn("size-5 shrink-0", config.iconColor)} fill="currentColor" viewBox="0 0 24 24">
<path d={config.iconPath} />
</svg>
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="hover:opacity-70 transition-opacity"
onClick={(e) => e.stopPropagation()}
>
<svg className={cn("size-5 shrink-0", config.iconColor)} fill="currentColor" viewBox="0 0 24 24">
<path d={config.iconPath} />
</svg>
</a>
</div>
<p className="text-neutral-700 leading-relaxed line-clamp-4 md:line-clamp-15 overflow-hidden">{body}</p>
</div>
</a>
</div>
);
}
6 changes: 1 addition & 5 deletions apps/web/src/components/video-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,16 @@ export function VideoModal({ playbackId, isOpen, onClose }: VideoModalProps) {
className="relative w-full max-w-6xl aspect-video"
onClick={(e) => e.stopPropagation()}
>
{/* Close button */}
<button
onClick={onClose}
className={cn(
"absolute -top-12 right-0 p-2",
"text-white hover:text-neutral-300",
"transition-colors duration-200",
["absolute -top-12 right-0 p-2", "text-white hover:text-neutral-300", "transition-colors duration-200"],
)}
aria-label="Close video"
>
<Icon icon="mdi:close" className="text-3xl" />
</button>

{/* Video */}
<MuxPlayer
ref={playerRef}
playbackId={playbackId}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/video-thumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ export function VideoThumbnail({
<div className={cn(["relative w-full h-full overflow-hidden group cursor-pointer", className])} onClick={onPlay}>
<MuxPlayer
playbackId={playbackId}
poster="/hyprnote/poster-image.png"
muted
playsInline
className="w-full h-full object-cover pointer-events-none"
className="w-full h-full object-cover pointer-events-none aspect-video"
style={{
"--controls": "none",
aspectRatio: "16/9",
} as React.CSSProperties}
/>

Expand Down
70 changes: 70 additions & 0 deletions apps/web/src/hooks/use-platform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useEffect, useState } from "react";

export type Platform = "mac" | "windows" | "linux" | "mobile" | "unknown";

export function usePlatform(): Platform {
const [platform, setPlatform] = useState<Platform>("unknown");

useEffect(() => {
if (typeof window === "undefined") {
return;
}

const userAgent = window.navigator.userAgent.toLowerCase();
const isMobile = /mobile|android|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent);

if (isMobile) {
setPlatform("mobile");
} else if (userAgent.includes("mac")) {
setPlatform("mac");
} else if (userAgent.includes("win")) {
setPlatform("windows");
} else if (userAgent.includes("linux")) {
setPlatform("linux");
} else {
setPlatform("unknown");
}
}, []);

return platform;
}

export function getPlatformCTA(platform: Platform): {
label: string;
action: "download" | "waitlist";
} {
if (platform === "mac") {
return { label: "Download", action: "download" };
}
return { label: "Join waitlist", action: "waitlist" };
}

export function getHeroCTA(platform: Platform): {
buttonLabel: string;
showInput: boolean;
inputPlaceholder: string;
subtext: string;
} {
if (platform === "mac") {
return {
buttonLabel: "Download",
showInput: false,
inputPlaceholder: "",
subtext: "Free and open-source",
};
} else if (platform === "mobile") {
return {
buttonLabel: "Remind me",
showInput: true,
inputPlaceholder: "Enter your email",
subtext: "Get an email reminder that you can check later",
};
}

return {
buttonLabel: "Join",
showInput: true,
inputPlaceholder: "Enter your email",
subtext: "Join the waitlist and get notified as soon as the app is released",
};
}
45 changes: 45 additions & 0 deletions apps/web/src/hooks/use-posthog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { usePostHog } from "@posthog/react";
import { useCallback } from "react";

export { usePostHog };

/**
* Hook for type-safe PostHog event tracking
*/
export function useAnalytics() {
const posthog = usePostHog();

const track = useCallback(
(eventName: string, properties?: Record<string, any>) => {
if (!posthog) {
return;
}
posthog.capture(eventName, properties);
},
[posthog],
);

const identify = useCallback(
(userId: string, properties?: Record<string, any>) => {
if (!posthog) {
return;
}
posthog.identify(userId, properties);
},
[posthog],
);

const reset = useCallback(() => {
if (!posthog) {
return;
}
posthog.reset();
}, [posthog]);

return {
track,
identify,
reset,
posthog,
};
}
26 changes: 26 additions & 0 deletions apps/web/src/providers/posthog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { PostHogProvider as PostHogReactProvider } from "@posthog/react";
import posthog from "posthog-js";
import { useEffect } from "react";

if (typeof window !== "undefined") {
const apiKey = import.meta.env.VITE_POSTHOG_API_KEY;
const apiHost = import.meta.env.VITE_POSTHOG_HOST || "https://us.i.posthog.com";

if (apiKey) {
posthog.init(apiKey, {
api_host: apiHost,
});
}
}

export function PostHogProvider({ children }: { children: React.ReactNode }) {
return <PostHogReactProvider client={posthog}>{children}</PostHogReactProvider>;
}

export function usePostHogPageView() {
useEffect(() => {
if (typeof window !== "undefined") {
posthog.capture("$pageview");
}
}, []);
}
Loading
Loading