Skip to content

Commit

Permalink
feat: seo
Browse files Browse the repository at this point in the history
  • Loading branch information
xinyao27 committed Dec 12, 2024
1 parent 98a4360 commit 4462fe4
Show file tree
Hide file tree
Showing 25 changed files with 1,157 additions and 49 deletions.
6 changes: 6 additions & 0 deletions packages/client/src/components/side-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ const menuItems = [
link: '/network',
icon: 'i-ri-earth-line',
},
{
value: 'seo',
label: 'SEO',
link: '/seo',
icon: 'i-ri-seo-line',
},
{
value: 'terminal',
label: 'Terminal',
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/components/ui/resizable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const ResizableHandle = ({
{...props}
>
{withHandle ? (
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded border">
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-sm border">
<i className="i-ri-draggable h-2.5 w-2.5" />
</div>
) : null}
Expand Down
17 changes: 11 additions & 6 deletions packages/client/src/hooks/use-search-element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,17 @@ export default function useSearchElement<T>(
const element = useMemo(() => {
return (
<div className={cn('space-y-1 border-b p-4', props?.className)}>
<Input
placeholder="Search..."
prefix={<i className="i-ri-search-line text-muted-foreground size-4" />}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
<div className="relative">
<Input
className="peer ps-9"
placeholder="Search..."
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
<div className="text-muted-foreground/80 pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-3 peer-disabled:opacity-50">
<i className="i-ri-search-line size-4" />
</div>
</div>

<div className="text-sm opacity-50">
{searchText ? <span>{filteredData.length} matched · </span> : null}
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/pages/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ function Main({ children }: Props) {
)}
>
<SideBar />
<div className="h-screen p-2 pl-0">
<div className="dark:shadow-accent h-full overflow-hidden rounded-lg bg-white shadow-md dark:bg-black dark:shadow-[0_0_10px_1px]">
<div className="h-screen overflow-hidden p-2 pl-0">
<div className="h-full overflow-hidden rounded-lg border bg-white dark:bg-black">
<div className="h-full overflow-y-auto overflow-x-hidden">{children}</div>
</div>
</div>
Expand Down
31 changes: 20 additions & 11 deletions packages/client/src/pages/routes/(components)/current-route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import React from 'react'
import { useQuery } from '@tanstack/react-query'
import { Input } from '@/components/ui/input'
import { getQueryClient, useMessageClient } from '@/lib/client'
import { cn } from '@/lib/utils'

export default function CurrentRoute() {
interface CurrentRouteProps {
className?: string
actions?: React.ReactNode
}

export default function CurrentRoute({ className, actions }: CurrentRouteProps) {
const messageClient = useMessageClient()
const { data } = useQuery({
queryKey: ['getRoute'],
Expand All @@ -25,7 +31,7 @@ export default function CurrentRoute() {
}, [data])

return (
<div className="space-y-1 border-b p-4">
<div className={cn('space-y-1 border-b p-4', className)}>
<div>
{data === currentRoute ? (
<span className="opacity-50">Current Route</span>
Expand All @@ -35,15 +41,18 @@ export default function CurrentRoute() {
</div>
)}
</div>
<Input
value={currentRoute}
onChange={(e) => setCurrentRoute(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleNavigate(currentRoute)
}
}}
/>
<div className="flex items-center gap-2">
<Input
value={currentRoute}
onChange={(e) => setCurrentRoute(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleNavigate(currentRoute)
}
}}
/>
{actions}
</div>
<div className="text-sm opacity-50">Edit path above to navigate</div>
</div>
)
Expand Down
43 changes: 43 additions & 0 deletions packages/client/src/pages/seo/(components)/facebook-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import type { SEOMetadata } from '@next-devtools/shared/types'

interface FacebookCardProps {
data?: SEOMetadata
}

const FacebookCard = ({ data }: FacebookCardProps) => {
if (!data || !data.openGraph)
return (
<div className="text-muted-foreground text-center">
<p className="mb-2">Missing `og:` related metadata</p>
<a className="text-blue-500 hover:underline" href="https://ogp.me/" rel="noopener noreferrer" target="_blank">
Learn more
</a>
</div>
)

const og = data?.openGraph
const title = (og?.title as string) || (data?.title as string) || data.name
const description = og?.description || data?.description
const image = (og?.images as any[])?.[0]
const siteName = og?.siteName || data?.applicationName || ''

return (
<div className="max-w-[527px] overflow-hidden border border-[#dadde1] bg-[#f2f3f5]">
{image ? (
<div className="h-[274px] w-full">
<img alt="" className="size-full object-cover" src={image.url} />
</div>
) : null}
<div className="max-h-[190px] px-[12px] py-[10px] text-[12px] text-[#4b4f56]">
<div className="truncate whitespace-nowrap text-[12px] uppercase leading-[16px] text-[#606770]">{siteName}</div>
<div className="mt-[5px] truncate text-[16px] font-semibold leading-[20px]">{title}</div>
<div className="mt-[3px] max-h-[80px] truncate font-[Helvetica,Arial,sans-serif] text-[13px] text-[#606770]">
{description}
</div>
</div>
</div>
)
}

export default FacebookCard
83 changes: 83 additions & 0 deletions packages/client/src/pages/seo/(components)/google-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react'
import { formatDistanceToNow } from 'date-fns'
import type { SEOMetadata } from '@next-devtools/shared/types'

interface GoogleCardProps {
data?: SEOMetadata
}

const GoogleCard = ({ data }: GoogleCardProps) => {
if (!data) return null

const icon = (data.icons as any)?.icon
const name = data.name
const title = data.title as string
const description = data.description
const url = typeof window !== 'undefined' ? window.location.origin : ''
const updatedAt = React.useMemo(() => {
// JSON-LD
if (data.jsonLd) {
const jsonLd = Array.isArray(data.jsonLd) ? data.jsonLd[0] : data.jsonLd
if ('datePublished' in jsonLd) {
return new Date(jsonLd.datePublished as string)
}
if ('dateModified' in jsonLd) {
return new Date(jsonLd.dateModified as string)
}
}

// OpenGraph
if ((data.openGraph as any)?.published_time) {
return new Date((data.openGraph as any).published_time)
} else if ((data.openGraph as any)?.modifiedTime) {
return new Date((data.openGraph as any).modifiedTime)
}

return null
}, [data])

return (
<div className="group max-w-[600px] overflow-hidden">
<div className="flex flex-col gap-[10px]">
<div className="flex items-center gap-3">
<div className="flex size-[26px] items-center justify-center rounded-full border border-[#dadce0] bg-[#f1f3f4]">
{icon ? (
<img className="block size-[18px]" src={icon} />
) : (
<svg
className="size-[18px] fill-current text-[#5e5e5e]"
focusable="false"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" />
</svg>
)}
</div>

<div className="flex flex-col">
<div className="text-nowrap font-[Arial,sans-serif] text-[14px] leading-[18px] text-[#202124]">{name}</div>
<div className="truncate text-[12px] leading-[18px] text-[#4d5156]">{url}</div>
</div>
</div>

<div className="flex-1 space-y-1">
<h3 className="mb-[3px] cursor-pointer font-[Arial,sans-serif] text-[20px] leading-[26px] text-[#1a0dab] hover:underline">
{title}
</h3>
<div className="line-clamp-2 font-[Arial,sans-serif] text-[14px] leading-[22px] text-[#474747]">
{updatedAt ? (
<span className="font-[Arial,sans-serif] text-[14px] leading-[22px] text-[#5e5e5e]">
{formatDistanceToNow(updatedAt)}
{' — '}
</span>
) : null}
{description}
</div>
</div>
</div>
</div>
)
}

export default GoogleCard
Loading

0 comments on commit 4462fe4

Please sign in to comment.