-
Notifications
You must be signed in to change notification settings - Fork 1
Update navigation and hero components with new messaging and links #55
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 2 commits
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,44 +1,21 @@ | ||
| 'use client'; | ||
| import { zodResolver } from '@hookform/resolvers/zod'; | ||
| import { FC, useState, FormEvent, startTransition, useActionState, useRef } from 'react'; | ||
| import { useForm } from 'react-hook-form'; | ||
| import { formSchema } from '../utils/waitlist/schema'; | ||
| import { submitWaitlistForm } from '../utils/waitlist/action'; | ||
| import * as z from 'zod'; | ||
|
|
||
| type FormValues = z.infer<typeof formSchema>; | ||
| import { FC } from 'react'; | ||
| import { Chrome, Github } from 'lucide-react'; | ||
|
Contributor
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. Bug — dead code left behind: The waitlist form was the only consumer of Similarly, |
||
|
|
||
| export const CTA: FC = () => { | ||
|
Contributor
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. Cleanup — unused npm dependencies. With the waitlist form removed, these
Keeping unused dependencies inflates install time and bundle size.
Owner
Author
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. They will be reused later |
||
| const [email, setEmail] = useState<string>(''); | ||
| const [submitted, setSubmitted] = useState<boolean>(false); | ||
| const [, formAction] = useActionState(submitWaitlistForm, { message: '' }); | ||
|
|
||
| const form = useForm<FormValues>({ | ||
| resolver: zodResolver(formSchema), | ||
| defaultValues: { email: '' }, | ||
| }); | ||
|
|
||
| const formRef = useRef<HTMLFormElement>(null); | ||
|
|
||
| const handleSubmit = (e: FormEvent<HTMLFormElement>): void => { | ||
| e.preventDefault(); | ||
| startTransition(() => { | ||
| formAction(new FormData(formRef.current!)); | ||
| form.reset(); | ||
| }); | ||
| setSubmitted(true); | ||
| }; | ||
|
|
||
| return ( | ||
| <section className="py-36 px-6 md:px-12 relative overflow-hidden bg-ink" id="get-access"> | ||
| <section | ||
| className="py-36 px-6 md:px-12 relative overflow-hidden bg-ink" | ||
| id="get-access" | ||
|
Contributor
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. Edge case — orphaned anchor |
||
| > | ||
| <div className="absolute inset-0 grid-bg opacity-70" /> | ||
| <div className="absolute top-0 left-0 right-0 h-px bg-edge" /> | ||
|
|
||
| <div className="relative max-w-xl mx-auto text-center"> | ||
| <div className="inline-flex items-center gap-2 border border-edge bg-ink2/80 px-4 py-1.5 mb-10 rounded-full"> | ||
| <span className="w-1.5 h-1.5 rounded-full bg-amber animate-blink" /> | ||
| <span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-blink" /> | ||
| <span className="font-mono text-[10px] tracking-[0.18em] uppercase text-fog"> | ||
| Early access open | ||
| Available now — free | ||
| </span> | ||
| </div> | ||
|
|
||
|
|
@@ -49,45 +26,31 @@ export const CTA: FC = () => { | |
| </h2> | ||
|
|
||
| <p className="text-[14px] text-mist mb-10 leading-relaxed font-body"> | ||
| Be among the first to bring persistent memory to every AI you use. | ||
| Install the extension and bring persistent memory to every AI you use. | ||
| <br className="hidden md:block" /> | ||
| No credit card. No setup fees. | ||
| Free, open-source, takes 30 seconds. | ||
| </p> | ||
|
|
||
| {!submitted ? ( | ||
| <form | ||
| ref={formRef} | ||
| onSubmit={handleSubmit} | ||
| className="flex flex-col sm:flex-row gap-3 max-w-sm mx-auto" | ||
| <div className="flex flex-col sm:flex-row items-center justify-center gap-4"> | ||
| <a | ||
| href="https://chromewebstore.google.com/detail/jjlelibnopjfeefpdplponpnocjfcega?utm_source=item-share-cb" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="inline-flex items-center gap-2.5 bg-amber text-white px-7 py-3.5 text-[12px] tracking-widest uppercase font-medium hover:bg-amber/90 transition-colors rounded-sm" | ||
| > | ||
| <input | ||
| type="email" | ||
| name="email" | ||
| value={email} | ||
| onChange={(e) => setEmail(e.target.value)} | ||
| placeholder="your@email.com" | ||
| className="flex-1 bg-ink2 border border-edge text-cream placeholder:text-fog px-4 py-3 text-[13px] font-body focus:outline-none focus:border-amber/40 transition-colors rounded-sm" | ||
| required | ||
| /> | ||
| <button | ||
| type="submit" | ||
| className="bg-amber text-white px-7 py-3 text-[11px] tracking-widest uppercase font-medium hover:bg-amber/90 transition-colors whitespace-nowrap rounded-sm" | ||
| > | ||
| Get Access | ||
| </button> | ||
| </form> | ||
| ) : ( | ||
| <div className="flex items-center justify-center gap-3 border border-edge bg-ink2/80 px-8 py-4 max-w-sm mx-auto animate-fade-in rounded-sm"> | ||
| <span className="text-amber">✦</span> | ||
| <span className="font-mono text-[12px] text-mist"> | ||
| You're on the list. We'll be in touch. | ||
| </span> | ||
| </div> | ||
| )} | ||
|
|
||
| <p className="mt-8 font-mono text-[10px] text-fog tracking-wide"> | ||
| Built with Go · Qdrant · PostgreSQL · MCP | ||
| </p> | ||
| <Chrome size={20} strokeWidth={1.75} /> | ||
| Add to Chrome | ||
| </a> | ||
| <a | ||
| href="https://github.com/freedisch/havril" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="inline-flex items-center gap-2.5 border border-edge bg-ink2/60 text-cream px-7 py-3.5 text-[12px] tracking-widest uppercase font-medium hover:bg-ink2 hover:border-edge2 transition-colors rounded-sm" | ||
| > | ||
| <Github size={18} strokeWidth={1.75} /> | ||
| Star on GitHub | ||
| </a> | ||
| </div> | ||
| </div> | ||
| </section> | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,14 @@ | ||
| 'use client'; | ||
| import { FC, useEffect, useState } from 'react'; | ||
| import { Chrome, Github } from 'lucide-react'; | ||
| import { useTypewriter } from '../utils/customState'; | ||
|
|
||
| export const Hero: FC = () => { | ||
| const memories = [ | ||
| '"User is building a Go REST API"', | ||
| '"User prefers minimal dependencies"', | ||
| '"User is based in Kigali, Rwanda"', | ||
| '"User uses Chi router + PostgreSQL"', | ||
| '"User is writing a thesis on climate policy"', | ||
| '"User prefers APA citations and concise summaries"', | ||
| '"User is preparing for the GRE in August"', | ||
| '"User is learning French at intermediate level"', | ||
| ]; | ||
| const typed = useTypewriter(memories, 44, 2600); | ||
|
|
||
|
|
@@ -17,9 +18,9 @@ export const Hero: FC = () => { | |
|
|
||
| {/* Badge */} | ||
| <div className="relative z-10 flex items-center gap-2 border border-edge bg-ink2/80 px-4 py-1.5 mb-10 rounded-full animate-fade-up delay-0"> | ||
| <span className="w-1.5 h-1.5 rounded-full bg-amber animate-blink" /> | ||
| <span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-blink" /> | ||
| <span className="font-mono text-[10px] tracking-widest uppercase text-fog"> | ||
| Now in development | ||
| Live on Chrome Web Store | ||
| </span> | ||
| </div> | ||
|
|
||
|
|
@@ -43,16 +44,22 @@ export const Hero: FC = () => { | |
| {/* CTAs */} | ||
| <div className="relative z-10 mt-9 flex flex-col sm:flex-row items-center gap-4 animate-fade-up delay-300"> | ||
| <a | ||
| href="#get-access" | ||
| className="bg-amber text-white px-7 py-3 text-[12px] tracking-widest uppercase font-medium rounded-sm hover:bg-amber/90 transition-colors" | ||
| href="https://chromewebstore.google.com/detail/jjlelibnopjfeefpdplponpnocjfcega?utm_source=item-share-cb" | ||
|
Contributor
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. DRY violation — hardcoded Chrome Web Store URL duplicated 4×. This exact URL appears in Consider extracting to a shared constant: // app/utils/constants.ts
export const CHROME_STORE_URL = 'https://chromewebstore.google.com/detail/jjlelibnopjfeefpdplponpnocjfcega?utm_source=item-share-cb';
export const GITHUB_REPO_URL = 'https://github.com/freedisch/havril';The GitHub URL ( |
||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="inline-flex items-center gap-2.5 bg-amber text-white px-7 py-3.5 text-[12px] tracking-widest uppercase font-medium rounded-sm hover:bg-amber/90 transition-colors" | ||
| > | ||
| Get early access | ||
| <Chrome size={20} strokeWidth={1.75} /> | ||
| Add to Chrome | ||
| </a> | ||
| <a | ||
| href="#integrations" | ||
| className="text-[12px] tracking-widest uppercase text-fog hover:text-mist transition-colors" | ||
| href="https://github.com/freedisch/havril" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="inline-flex items-center gap-2.5 border border-edge bg-ink2/60 text-cream px-7 py-3.5 text-[12px] tracking-widest uppercase font-medium rounded-sm hover:bg-ink2 hover:border-edge2 transition-colors" | ||
| > | ||
| See it in action | ||
| <Github size={18} strokeWidth={1.75} /> | ||
| Star on GitHub | ||
| </a> | ||
| </div> | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| 'use client'; | ||
| import { FC, useState } from 'react'; | ||
| import { Chrome } from 'lucide-react'; | ||
| import { useScrolled } from '../utils/customState'; | ||
|
|
||
| export const Nav: FC = () => { | ||
|
|
@@ -41,10 +42,13 @@ export const Nav: FC = () => { | |
| </svg> | ||
| </a> | ||
| <a | ||
| href="#get-access" | ||
| className="text-[11px] tracking-widest uppercase bg-amber text-white px-5 py-2.5 font-medium hover:bg-amber/90 transition-colors rounded-sm" | ||
| href="https://chromewebstore.google.com/detail/jjlelibnopjfeefpdplponpnocjfcega?utm_source=item-share-cb" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="inline-flex items-center gap-2 text-[11px] tracking-widest uppercase bg-amber text-white px-5 py-2.5 font-medium hover:bg-amber/90 transition-colors rounded-sm" | ||
| > | ||
| Get Early Access | ||
| <Chrome size={16} strokeWidth={1.75} /> | ||
| Add to Chrome | ||
| </a> | ||
| </div> | ||
|
|
||
|
|
@@ -83,8 +87,15 @@ export const Nav: FC = () => { | |
| > | ||
| X / Twitter | ||
| </a> | ||
| <a href="#get-access" className="text-[11px] uppercase bg-amber text-white px-5 py-3 font-medium text-center rounded-sm"> | ||
| Get Early Access | ||
| <a | ||
| href="https://chromewebstore.google.com/detail/jjlelibnopjfeefpdplponpnocjfcega?utm_source=item-share-cb" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| onClick={() => setMenuOpen(false)} | ||
| className="inline-flex items-center justify-center gap-2 text-[11px] uppercase bg-amber text-white px-5 py-3 font-medium text-center rounded-sm" | ||
|
Contributor
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. Styling inconsistency — mobile CTA missing |
||
| > | ||
| <Chrome size={16} strokeWidth={1.75} /> | ||
| Add to Chrome | ||
| </a> | ||
| </div> | ||
| )} | ||
|
|
||
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.
Convention —
'use client'is no longer needed here. This component was rewritten to be purely declarative — nouseState, nouseRef, no event handlers. The'use client'directive forces the entire component into a client bundle unnecessarily. Removing it would let Next.js render this as a server component, reducing JS shipped to the browser.