Skip to content

Commit 641db19

Browse files
authored
chore: quick bugs squash across the elements and added few more changes (supermemoryai#671)
1 parent 34c58c3 commit 641db19

19 files changed

Lines changed: 7174 additions & 96 deletions

File tree

apps/web/app/(auth)/login/new/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Title1Bold } from "@ui/text/title/title-1-bold"
1313
import { InitialHeader } from "@/components/initial-header"
1414
import { useRouter, useSearchParams } from "next/navigation"
1515
import { useState, useEffect } from "react"
16-
import { motion } from "framer-motion"
16+
import { motion } from "motion/react"
1717
import { dmSansClassName } from "@/utils/fonts"
1818
import { cn } from "@lib/utils"
1919
import { Logo } from "@ui/assets/Logo"

apps/web/app/api/og/route.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import ogs from "open-graph-scraper"
2+
3+
export const runtime = "nodejs"
4+
5+
interface OGResponse {
6+
title: string
7+
description: string
8+
image?: string
9+
}
10+
11+
function isValidUrl(urlString: string): boolean {
12+
try {
13+
const url = new URL(urlString)
14+
return url.protocol === "http:" || url.protocol === "https:"
15+
} catch {
16+
return false
17+
}
18+
}
19+
20+
function isPrivateHost(hostname: string): boolean {
21+
const lowerHost = hostname.toLowerCase()
22+
23+
// Block localhost variants
24+
if (
25+
lowerHost === "localhost" ||
26+
lowerHost === "127.0.0.1" ||
27+
lowerHost === "::1" ||
28+
lowerHost.startsWith("127.") ||
29+
lowerHost.startsWith("0.0.0.0")
30+
) {
31+
return true
32+
}
33+
34+
// Block RFC 1918 private IP ranges
35+
const privateIpPatterns = [
36+
/^10\./,
37+
/^172\.(1[6-9]|2[0-9]|3[01])\./,
38+
/^192\.168\./,
39+
]
40+
41+
return privateIpPatterns.some((pattern) => pattern.test(hostname))
42+
}
43+
44+
function extractImageUrl(image: unknown): string | undefined {
45+
if (!image) return undefined
46+
47+
if (typeof image === "string") {
48+
return image
49+
}
50+
51+
if (Array.isArray(image) && image.length > 0) {
52+
const first = image[0]
53+
if (first && typeof first === "object" && "url" in first) {
54+
return String(first.url)
55+
}
56+
}
57+
58+
if (typeof image === "object" && image !== null && "url" in image) {
59+
return String(image.url)
60+
}
61+
62+
return undefined
63+
}
64+
65+
function resolveImageUrl(
66+
imageUrl: string | undefined,
67+
baseUrl: string,
68+
): string | undefined {
69+
if (!imageUrl) return undefined
70+
71+
try {
72+
const url = new URL(imageUrl)
73+
return url.href
74+
} catch {
75+
try {
76+
const base = new URL(baseUrl)
77+
return new URL(imageUrl, base.href).href
78+
} catch {
79+
return undefined
80+
}
81+
}
82+
}
83+
84+
export async function GET(request: Request) {
85+
try {
86+
const { searchParams } = new URL(request.url)
87+
const url = searchParams.get("url")
88+
89+
if (!url || !url.trim()) {
90+
return Response.json(
91+
{ error: "Missing or invalid url parameter" },
92+
{ status: 400 },
93+
)
94+
}
95+
96+
const trimmedUrl = url.trim()
97+
98+
if (!isValidUrl(trimmedUrl)) {
99+
return Response.json(
100+
{ error: "Invalid URL. Must be http:// or https://" },
101+
{ status: 400 },
102+
)
103+
}
104+
105+
const urlObj = new URL(trimmedUrl)
106+
if (isPrivateHost(urlObj.hostname)) {
107+
return Response.json(
108+
{ error: "Private/localhost URLs are not allowed" },
109+
{ status: 400 },
110+
)
111+
}
112+
113+
const { result, error } = await ogs({
114+
url: trimmedUrl,
115+
timeout: 8000,
116+
fetchOptions: {
117+
headers: {
118+
"User-Agent":
119+
"Mozilla/5.0 (compatible; SuperMemory/1.0; +https://supermemory.ai)",
120+
},
121+
},
122+
})
123+
124+
if (error || !result) {
125+
console.error("OG scraping error:", error)
126+
return Response.json(
127+
{ error: "Failed to fetch Open Graph data" },
128+
{ status: 500 },
129+
)
130+
}
131+
132+
const ogTitle = result.ogTitle || result.twitterTitle || ""
133+
const ogDescription =
134+
result.ogDescription || result.twitterDescription || ""
135+
136+
const ogImageUrl =
137+
extractImageUrl(result.ogImage) || extractImageUrl(result.twitterImage)
138+
139+
const resolvedImageUrl = resolveImageUrl(ogImageUrl, trimmedUrl)
140+
141+
const response: OGResponse = {
142+
title: ogTitle,
143+
description: ogDescription,
144+
...(resolvedImageUrl && { image: resolvedImageUrl }),
145+
}
146+
147+
return Response.json(response, {
148+
headers: {
149+
"Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
150+
},
151+
})
152+
} catch (error) {
153+
console.error("OG route error:", error)
154+
return Response.json({ error: "Internal server error" }, { status: 500 })
155+
}
156+
}

apps/web/app/new/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { AddDocumentModal } from "@/components/new/add-document"
99
import { MCPModal } from "@/components/new/mcp-modal"
1010
import { HotkeysProvider } from "react-hotkeys-hook"
1111
import { useHotkeys } from "react-hotkeys-hook"
12-
import { AnimatePresence } from "framer-motion"
12+
import { AnimatePresence } from "motion/react"
1313

1414
export default function NewPage() {
1515
const [isAddDocumentOpen, setIsAddDocumentOpen] = useState(false)

apps/web/app/onboarding/extension-form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from "lucide-react"
1313
import { NavMenu } from "./nav-menu"
1414
import { useOnboarding } from "./onboarding-context"
15-
import { motion, AnimatePresence, type ResolvedValues } from "framer-motion"
15+
import { motion, AnimatePresence, type ResolvedValues } from "motion/react"
1616
import { useEffect, useMemo, useRef, useState, useLayoutEffect } from "react"
1717
import React from "react"
1818
import { cn } from "@lib/utils"

apps/web/app/onboarding/mcp-form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { CheckIcon, CircleCheckIcon, CopyIcon, LoaderIcon } from "lucide-react"
1414
import { TextMorph } from "@/components/text-morph"
1515
import { NavMenu } from "./nav-menu"
1616
import { cn } from "@lib/utils"
17-
import { motion, AnimatePresence } from "framer-motion"
17+
import { motion, AnimatePresence } from "motion/react"
1818
import { useQuery } from "@tanstack/react-query"
1919
import { $fetch } from "@lib/api"
2020

apps/web/components/chrome-extension-button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
TwitterIcon,
1212
} from "lucide-react"
1313
import { useEffect, useState } from "react"
14-
import { motion } from "framer-motion"
14+
import { motion } from "motion/react"
1515
import Image from "next/image"
1616
import { analytics } from "@/lib/analytics"
1717
import { useIsMobile } from "@hooks/use-mobile"

apps/web/components/connect-ai-modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { z } from "zod/v4"
3232
import { analytics } from "@/lib/analytics"
3333
import { cn } from "@lib/utils"
3434
import type { Project } from "@repo/lib/types"
35-
import { motion, AnimatePresence } from "framer-motion"
35+
import { motion, AnimatePresence } from "motion/react"
3636

3737
const clients = {
3838
cursor: "Cursor",

apps/web/components/new/add-document/connections.tsx

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
5353
const [isProUser, setIsProUser] = useState(false)
5454
const [connectingProvider, setConnectingProvider] =
5555
useState<ConnectorProvider | null>(null)
56+
const [isUpgrading, setIsUpgrading] = useState(false)
5657

5758
// Check Pro status
5859
useEffect(() => {
@@ -65,6 +66,20 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
6566
}
6667
}, [autumn.isLoading, autumn.customer])
6768

69+
const handleUpgrade = async () => {
70+
setIsUpgrading(true)
71+
try {
72+
await autumn.attach({
73+
productId: "consumer_pro",
74+
successUrl: window.location.href,
75+
})
76+
} catch (error) {
77+
console.error("Upgrade error:", error)
78+
toast.error("Failed to start upgrade process")
79+
setIsUpgrading(false)
80+
}
81+
}
82+
6883
// Check connections feature limits
6984
const { data: connectionsCheck } = fetchConnectionsFeature(
7085
autumn,
@@ -359,15 +374,25 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
359374
{!isProUser ? (
360375
<>
361376
<p className="text-[14px] text-[#737373] mb-4 text-center">
362-
<a
363-
href="/pricing"
364-
className="underline text-[#737373] hover:text-white"
365-
>
366-
Upgrade to Pro
367-
</a>{" "}
368-
to get
369-
<br />
370-
Supermemory Connections
377+
{isUpgrading || autumn.isLoading ? (
378+
<span className="inline-flex items-center gap-2">
379+
<Loader className="h-4 w-4 animate-spin" />
380+
Upgrading...
381+
</span>
382+
) : (
383+
<>
384+
<button
385+
type="button"
386+
onClick={handleUpgrade}
387+
className="underline text-[#737373] hover:text-white transition-colors cursor-pointer"
388+
>
389+
Upgrade to Pro
390+
</button>{" "}
391+
to get
392+
<br />
393+
Supermemory Connections
394+
</>
395+
)}
371396
</p>
372397
<div className="space-y-2 text-[14px]">
373398
<div className="flex items-center gap-2">

apps/web/components/new/add-document/index.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ import {
3030
DropdownMenuTrigger,
3131
} from "@repo/ui/components/dropdown-menu"
3232
import { toast } from "sonner"
33-
import { useDocumentMutations } from "./useDocumentMutations"
33+
import { useDocumentMutations } from "../../../hooks/use-document-mutations"
34+
import { useCustomer } from "autumn-js/react"
35+
import { useMemoriesUsage } from "@/hooks/use-memories-usage"
3436

3537
type TabType = "note" | "link" | "file" | "connect"
3638

@@ -132,6 +134,15 @@ export function AddDocument({
132134
onClose,
133135
})
134136

137+
const autumn = useCustomer()
138+
const {
139+
memoriesUsed,
140+
memoriesLimit,
141+
hasProProduct,
142+
isLoading: isLoadingMemories,
143+
usagePercent,
144+
} = useMemoriesUsage(autumn)
145+
135146
useEffect(() => {
136147
setLocalSelectedProject(globalSelectedProject)
137148
}, [globalSelectedProject])
@@ -242,7 +253,7 @@ export function AddDocument({
242253
noteMutation.isPending || linkMutation.isPending || fileMutation.isPending
243254

244255
return (
245-
<div className="h-full flex flex-row text-white space-x-6">
256+
<div className="h-full flex flex-row text-white space-x-5">
246257
<div className="w-1/3 flex flex-col justify-between">
247258
<div className="flex flex-col gap-1">
248259
{tabs.map((tab) => (
@@ -266,7 +277,7 @@ export function AddDocument({
266277
"0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset",
267278
}}
268279
>
269-
<div className="flex justify-between items-center mb-2">
280+
<div className="flex justify-between items-center">
270281
<span
271282
className={cn(
272283
"text-white text-[16px] font-medium",
@@ -276,19 +287,25 @@ export function AddDocument({
276287
Memories
277288
</span>
278289
<span className={cn("text-[#737373] text-sm", dmSansClassName())}>
279-
120/200
290+
{isLoadingMemories
291+
? "…"
292+
: hasProProduct
293+
? "Unlimited"
294+
: `${memoriesUsed}/${memoriesLimit}`}
280295
</span>
281296
</div>
282-
<div className="h-1.5 bg-[#0D121A] rounded-full overflow-hidden">
283-
<div
284-
className="h-full bg-[#2261CA] rounded-full"
285-
style={{ width: "60%" }}
286-
/>
287-
</div>
297+
{!hasProProduct && (
298+
<div className="h-1.5 bg-[#0D121A] rounded-full overflow-hidden mt-2">
299+
<div
300+
className="h-full bg-[#2261CA] rounded-full"
301+
style={{ width: `${usagePercent}%` }}
302+
/>
303+
</div>
304+
)}
288305
</div>
289306
</div>
290307

291-
<div className="w-2/3 overflow-auto flex flex-col justify-between">
308+
<div className="w-2/3 overflow-auto flex flex-col justify-between px-1 scrollbar-thin">
292309
{activeTab === "note" && (
293310
<NoteContent
294311
onSubmit={handleNoteSubmit}

0 commit comments

Comments
 (0)