From 3e20516a13bcbb6e4be8c6f4d18120b4c85dae97 Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommmu Date: Wed, 22 Oct 2025 11:46:21 -0700 Subject: [PATCH] feat: exa inital version --- .../app/onboarding/welcome/features-step.tsx | 25 +- .../app/onboarding/welcome/memories-step.tsx | 262 ++++++++++++++++++ apps/web/app/onboarding/welcome/page.tsx | 38 ++- apps/web/utils/exa-api.ts | 53 ++++ 4 files changed, 363 insertions(+), 15 deletions(-) create mode 100644 apps/web/app/onboarding/welcome/memories-step.tsx create mode 100644 apps/web/utils/exa-api.ts 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" + /> +
+ + + +
+ +