diff --git a/apps/web/app/onboarding/welcome/features-step.tsx b/apps/web/app/onboarding/welcome/features-step.tsx index 6ac6e72d..6deca0bb 100644 --- a/apps/web/app/onboarding/welcome/features-step.tsx +++ b/apps/web/app/onboarding/welcome/features-step.tsx @@ -1,8 +1,19 @@ import { motion } from "motion/react" import { Button } from "@ui/components/button" -import Link from "next/link" -export function FeaturesStep() { +interface FeaturesStepProps { + setCurrentStep: ( + step: + | "input" + | "greeting" + | "welcome" + | "username" + | "features" + | "memories", + ) => void +} + +export function FeaturesStep({ setCurrentStep }: FeaturesStepProps) { return ( diff --git a/apps/web/app/onboarding/welcome/memories-step.tsx b/apps/web/app/onboarding/welcome/memories-step.tsx new file mode 100644 index 00000000..f1da7567 --- /dev/null +++ b/apps/web/app/onboarding/welcome/memories-step.tsx @@ -0,0 +1,262 @@ +import { motion } from "motion/react" +import { Button } from "@ui/components/button" +import { useState } from "react" +import { fetchContentFromUrls, type ExaContentResult } from "@/utils/exa-api" + +export function MemoriesStep() { + const [twitterHandle, setTwitterHandle] = useState("") + const [linkedinProfile, setLinkedinProfile] = useState("") + const [otherLinks, setOtherLinks] = useState([""]) + const [description, setDescription] = useState("") + const [isSubmitting, setIsSubmitting] = useState(false) + + const addOtherLink = () => { + if (otherLinks.length < 3) { + setOtherLinks([...otherLinks, ""]) + } + } + + const updateOtherLink = (index: number, value: string) => { + const updated = [...otherLinks] + updated[index] = value + setOtherLinks(updated) + } + + const removeOtherLink = (index: number) => { + if (otherLinks.length > 1) { + const updated = otherLinks.filter((_, i) => i !== index) + setOtherLinks(updated) + } + } + + // URL validation and filtering helpers + const isValidUrl = (url: string): boolean => { + try { + new URL(url) + return true + } catch { + return false + } + } + + const normalizeUrl = (url: string): string => { + if (!url.trim()) return "" + if (url.startsWith("http://") || url.startsWith("https://")) { + return url + } + return `https://${url}` + } + + const isTwitterUrl = (url: string): boolean => { + const normalizedUrl = url.toLowerCase() + return ( + normalizedUrl.includes("twitter.com") || normalizedUrl.includes("x.com") + ) + } + + const isLinkedInProfileUrl = (url: string): boolean => { + const normalizedUrl = url.toLowerCase() + return ( + normalizedUrl.includes("linkedin.com/in/") && + !normalizedUrl.includes("linkedin.com/company/") + ) + } + + const collectValidUrls = (): string[] => { + const urls: string[] = [] + + // Add LinkedIn profile if valid and not empty + if (linkedinProfile.trim()) { + const normalizedLinkedIn = normalizeUrl(linkedinProfile.trim()) + if ( + isValidUrl(normalizedLinkedIn) && + isLinkedInProfileUrl(normalizedLinkedIn) + ) { + urls.push(normalizedLinkedIn) + } + } + + // Add other links if valid and not empty, excluding Twitter + otherLinks + .filter((link) => link.trim()) + .forEach((link) => { + const normalizedLink = normalizeUrl(link.trim()) + if (isValidUrl(normalizedLink) && !isTwitterUrl(normalizedLink)) { + urls.push(normalizedLink) + } + }) + + return urls + } + + return ( + +

+ Let's add your memories +

+ +
+
+ + setTwitterHandle(e.target.value)} + className="w-full px-4 py-2 bg-[#070E1B] border border-[#525966]/20 rounded-xl text-white placeholder-[#8A8A8A] focus:outline-none focus:border-[#4A4A4A] transition-colors" + /> +
+ +
+ + setLinkedinProfile(e.target.value)} + className="w-full px-4 py-2 bg-[#070E1B] border border-[#525966]/20 rounded-xl text-white placeholder-[#8A8A8A] focus:outline-none focus:border-[#4A4A4A] transition-colors" + /> +
+ + + +
+ +