assets, move memories to proper location
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Component, createSignal } from 'solid-js';
|
||||
import { Component, createSignal, onMount } from 'solid-js';
|
||||
import { trackWaitlistSignup } from '../hooks/useAnalytics';
|
||||
|
||||
interface WaitlistFormProps {
|
||||
@@ -7,14 +7,29 @@ interface WaitlistFormProps {
|
||||
buttonText?: string;
|
||||
}
|
||||
|
||||
function getUtmParams() {
|
||||
if (typeof window === 'undefined') return {};
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
return {
|
||||
utmSource: params.get('utm_source') || undefined,
|
||||
utmMedium: params.get('utm_medium') || undefined,
|
||||
utmCampaign: params.get('utm_campaign') || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const WaitlistForm: Component<WaitlistFormProps> = (props) => {
|
||||
const [email, setEmail] = createSignal('');
|
||||
const [name, setName] = createSignal('');
|
||||
const [tier, setTier] = createSignal('basic');
|
||||
const [utm, setUtm] = createSignal<Record<string, string | undefined>>({});
|
||||
const [submitted, setSubmitted] = createSignal(false);
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
const [error, setError] = createSignal('');
|
||||
|
||||
onMount(() => {
|
||||
setUtm(getUtmParams());
|
||||
});
|
||||
|
||||
const variant = props.variant || 'hero';
|
||||
|
||||
const handleSubmit = async (e: Event) => {
|
||||
@@ -36,6 +51,7 @@ const WaitlistForm: Component<WaitlistFormProps> = (props) => {
|
||||
email: email(),
|
||||
name: name() || undefined,
|
||||
tier: tier() !== 'basic' ? tier() : undefined,
|
||||
...utm(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -2,12 +2,17 @@ type EventParams = Record<string, string | number | boolean | undefined>;
|
||||
|
||||
const GA_MEASUREMENT_ID = import.meta.env.VITE_GA_MEASUREMENT_ID as string | undefined;
|
||||
const MIXPANEL_TOKEN = import.meta.env.VITE_MIXPANEL_TOKEN as string | undefined;
|
||||
const META_PIXEL_ID = import.meta.env.VITE_META_PIXEL_ID as string | undefined;
|
||||
const LINKEDIN_PARTNER_ID = import.meta.env.VITE_LINKEDIN_PARTNER_ID as string | undefined;
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
gtag?: (command: string, target: string, params?: EventParams) => void;
|
||||
mixpanel?: { track: (event: string, params?: EventParams) => void };
|
||||
mixpanel?: { track: (event: string, params?: EventParams) => void; init?: (token: string) => void };
|
||||
dataLayer?: unknown[];
|
||||
fbq?: (...args: unknown[]) => void;
|
||||
_fbq?: unknown;
|
||||
lintrk?: (...args: unknown[]) => void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +48,47 @@ function initMixpanel() {
|
||||
};
|
||||
}
|
||||
|
||||
function initMetaPixel() {
|
||||
if (!META_PIXEL_ID || typeof window === 'undefined') return;
|
||||
if (window.fbq) return;
|
||||
|
||||
window.fbq = function fbq() { window._fbq = window._fbq || []; window._fbq.push(arguments); };
|
||||
const script = document.createElement('script');
|
||||
script.async = true;
|
||||
script.src = `https://connect.facebook.net/en_US/fbevents.js`;
|
||||
document.head.appendChild(script);
|
||||
|
||||
window.fbq('init', META_PIXEL_ID);
|
||||
window.fbq('track', 'PageView');
|
||||
}
|
||||
|
||||
function initLinkedInInsight() {
|
||||
if (!LINKEDIN_PARTNER_ID || typeof window === 'undefined') return;
|
||||
if (window.lintrk) return;
|
||||
|
||||
window.lintrk = function lintrk() { window.lintrk.q.push(arguments); };
|
||||
window.lintrk.q = [];
|
||||
const script = document.createElement('script');
|
||||
script.async = true;
|
||||
script.src = `https://snap.licdn.com/li.lms-analytics/insight.min.js`;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
export function initAnalytics() {
|
||||
initGA();
|
||||
initMixpanel();
|
||||
initMetaPixel();
|
||||
initLinkedInInsight();
|
||||
}
|
||||
|
||||
export function trackMetaEvent(eventName: string, params?: EventParams) {
|
||||
if (typeof window === 'undefined' || !window.fbq) return;
|
||||
window.fbq('track', eventName, params);
|
||||
}
|
||||
|
||||
export function trackLinkedInEvent() {
|
||||
if (typeof window === 'undefined' || !window.lintrk) return;
|
||||
window.lintrk('track', { conversion_id: null });
|
||||
}
|
||||
|
||||
export function trackEvent(name: string, params?: EventParams) {
|
||||
@@ -60,12 +103,23 @@ export function trackEvent(name: string, params?: EventParams) {
|
||||
}
|
||||
}
|
||||
|
||||
function hashEmail(email: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < email.length; i++) {
|
||||
const char = email.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
return Math.abs(hash).toString(16);
|
||||
}
|
||||
|
||||
export function trackWaitlistSignup(email: string, source?: string, tier?: string) {
|
||||
trackEvent('waitlist_signup', {
|
||||
email,
|
||||
source: source || 'landing_page',
|
||||
tier: tier || 'unknown',
|
||||
});
|
||||
trackMetaEvent('Lead', { value: 5.00, currency: 'USD', eventID: hashEmail(email) });
|
||||
}
|
||||
|
||||
export function trackPageView(path: string, title?: string) {
|
||||
|
||||
@@ -855,6 +855,31 @@ img {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Blog Waitlist CTA */
|
||||
.blog-waitlist-cta {
|
||||
background: var(--bg-card);
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 80px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.blog-waitlist-cta h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.blog-waitlist-cta p {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 32px;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.blog-waitlist-cta .hero-form {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1024px) {
|
||||
.features-grid {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { render } from 'solid-js/web';
|
||||
import { Router, Route } from '@solidjs/router';
|
||||
import App from './App';
|
||||
import LandingPage from './pages/LandingPage';
|
||||
import AdsLandingPage from './pages/AdsLandingPage';
|
||||
import BlogPage from './pages/BlogPage';
|
||||
import BlogPostPage from './pages/BlogPostPage';
|
||||
import './index.css';
|
||||
@@ -12,6 +13,7 @@ if (!root) throw new Error('Root element not found');
|
||||
render(() => (
|
||||
<Router root={App}>
|
||||
<Route path="/" component={LandingPage} />
|
||||
<Route path="/ads" component={AdsLandingPage} />
|
||||
<Route path="/blog" component={BlogPage} />
|
||||
<Route path="/blog/:slug" component={BlogPostPage} />
|
||||
</Router>
|
||||
|
||||
24
packages/web/src/pages/AdsLandingPage.tsx
Normal file
24
packages/web/src/pages/AdsLandingPage.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Component, onMount } from 'solid-js';
|
||||
import { initAnalytics, trackPageView } from '../hooks/useAnalytics';
|
||||
import HeroSection from '../components/HeroSection';
|
||||
import FeaturesSection from '../components/FeaturesSection';
|
||||
import TierComparison from '../components/TierComparison';
|
||||
import Footer from '../components/Footer';
|
||||
|
||||
const AdsLandingPage: Component = () => {
|
||||
onMount(() => {
|
||||
initAnalytics();
|
||||
trackPageView('/ads', 'ShieldAI — Ads | AI-Powered Identity Protection');
|
||||
});
|
||||
|
||||
return (
|
||||
<main>
|
||||
<HeroSection />
|
||||
<FeaturesSection />
|
||||
<TierComparison />
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdsLandingPage;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, createSignal, onMount, For } from 'solid-js';
|
||||
import { initAnalytics, trackPageView } from '../hooks/useAnalytics';
|
||||
import WaitlistForm from '../components/WaitlistForm';
|
||||
import Footer from '../components/Footer';
|
||||
|
||||
interface BlogPost {
|
||||
@@ -104,6 +105,14 @@ const BlogPage: Component = () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="blog-waitlist-cta">
|
||||
<div class="container">
|
||||
<h2>Stay Protected</h2>
|
||||
<p>Get notified when we publish new guides and early access to ShieldAI.</p>
|
||||
<WaitlistForm variant="hero" buttonText="Get Notified" placeholder="your@email.com" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user