Skip to content
This repository has been archived by the owner on Dec 27, 2024. It is now read-only.

Commit

Permalink
done
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexey Tukachev committed Jan 26, 2023
1 parent 0823b0d commit c43051c
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 31 deletions.
61 changes: 61 additions & 0 deletions src/app/icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
export type IconName = keyof typeof ICONS

export function Icons({ name }: { name: IconName }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
viewBox="0 0 24 24"
strokeWidth="2"
stroke="currentColor"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
>
{ICONS[name]}
</svg>
)
}

const ICONS = {
behance: (
<path d="M3 18v-12h4.5a3 3 0 0 1 0 6a3 3 0 0 1 0 6h-4.5m0 -6l4.5 0m6.5 1h7a3.5 3.5 0 0 0 -7 0v2a3.5 3.5 0 0 0 6.64 1m-4.64 -10l3 0" />
),
discord: (
<path d="M9 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0m7 0m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0m-6.5 -4.5c3.5 -1 5.5 -1 9 0m-9.5 9c3.5 1 6.5 1 10 0m-1.5 .5c0 1 1.5 3 2 3c1.5 0 2.833 -1.667 3.5 -3c.667 -1.667 .5 -5.833 -1.5 -11.5c-1.457 -1.015 -3 -1.34 -4.5 -1.5l-1 2.5m-5.5 10.5c0 1 -1.356 3 -1.832 3c-1.429 0 -2.698 -1.667 -3.333 -3c-.635 -1.667 -.476 -5.833 1.428 -11.5c1.388 -1.015 2.782 -1.34 4.237 -1.5l1 2.5" />
),
dribbble: (
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0m6 -8.4c5 6 7 10.5 7.5 16.2m-10.1 -.8c3.5 -3.5 6 -6.5 14.5 -6.4m-17.8 -1.85c5 0 9.814 -.38 15.314 -5" />
),
email: <path d="M10 21v-6.5a3.5 3.5 0 0 0 -7 0v6.5h18v-6a4 4 0 0 0 -4 -4h-10.5m5.5 0v-8h4l2 2l-2 2h-4m-6 8h1" />,
facebook: <path d="M7 10v4h3v7h4v-7h3l1 -4h-4v-2a1 1 0 0 1 1 -1h3v-4h-3a5 5 0 0 0 -5 5v2h-3" />,
github: (
<path d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5" />
),
instagram: (
<path d="M4 4m0 4a4 4 0 0 1 4 -4h8a4 4 0 0 1 4 4v8a4 4 0 0 1 -4 4h-8a4 4 0 0 1 -4 -4zm8 4m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0m7.5 -4.5l0 0" />
),
linkedin: (
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2zm4 5l0 5m0 -8l0 .01m4 7.99l0 -5m4 5v-3a2 2 0 0 0 -4 0" />
),
mastodon: (
<path d="M18.648 15.254c-1.816 1.763 -6.648 1.626 -6.648 1.626a18.262 18.262 0 0 1 -3.288 -.256c1.127 1.985 4.12 2.81 8.982 2.475c-1.945 2.013 -13.598 5.257 -13.668 -7.636l-.026 -1.154c0 -3.036 .023 -4.115 1.352 -5.633c1.671 -1.91 6.648 -1.666 6.648 -1.666s4.977 -.243 6.648 1.667c1.329 1.518 1.352 2.597 1.352 5.633s-.456 4.074 -1.352 4.944zm-6.648 -4.05v-2.926c0 -1.258 -.895 -2.278 -2 -2.278s-2 1.02 -2 2.278v4.722m4 -4.722c0 -1.258 .895 -2.278 2 -2.278s2 1.02 2 2.278v4.722" />
),
pinterest: (
<path d="M8 20l4 -9m-1.3 3c.437 1.263 1.43 2 2.55 2c2.071 0 3.75 -1.554 3.75 -4a5 5 0 1 0 -9.7 1.7m4.7 -1.7m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
),
telegram: <path d="M15 10l-4 4l6 6l4 -16l-18 7l4 2l2 6l3 -4" />,
tiktok: <path d="M9 12a4 4 0 1 0 4 4v-12a5 5 0 0 0 5 5" />,
twitch: (
<path d="M4 5v11a1 1 0 0 0 1 1h2v4l4 -4h5.584c.266 0 .52 -.105 .707 -.293l2.415 -2.414c.187 -.188 .293 -.442 .293 -.708v-8.585a1 1 0 0 0 -1 -1h-14a1 1 0 0 0 -1 1zm12 3l0 4m-4 -4l0 4" />
),
twitter: (
<path d="M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c0 -.249 1.51 -2.772 1.818 -4.013z" />
),
whatsapp: (
<path d="M3 21l1.65 -3.8a9 9 0 1 1 3.4 2.9l-5.05 .9m6 -11a0.5 .5 0 0 0 1 0v-1a0.5 .5 0 0 0 -1 0v1a5 5 0 0 0 5 5h1a0.5 .5 0 0 0 0 -1h-1a0.5 .5 0 0 0 0 1" />
),
youtube: <path d="M3 5m0 4a4 4 0 0 1 4 -4h10a4 4 0 0 1 4 4v6a4 4 0 0 1 -4 4h-10a4 4 0 0 1 -4 -4zm7 0l5 3l-5 3z" />,
chevron: <path d="M6 9l6 6l6 -6"></path>,
} as const
2 changes: 1 addition & 1 deletion src/app/image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ export function Image({
)
}
// eslint-disable-next-line @next/next/no-img-element
return <img src={src} alt={alt} width={width} height={height} loading="lazy" />
return <img src={src} alt={alt} width={width} height={height} loading="lazy" className={props.className} />
}
13 changes: 9 additions & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import './globals.css';
import './globals.css'

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head />
<body>{children}</body>
<body className="relative">
<div className="max-w-screen fixed inset-0 flex max-h-screen items-center justify-center overflow-hidden">
<div className="h-[150vmax] w-[150vmax] flex-shrink-0 animate-rotation opacity-25 [background-image:linear-gradient(32deg,_#85eaf2_0%,_#e485cf_47%,_#fdf67a_100%)]" />
</div>
{children}
</body>
</html>
);
}
)
}
104 changes: 86 additions & 18 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
import { getBlocktData, getData, getSocialsData } from '~/utils/data'
import { Suspense } from 'react'
import { getBlockRecordMap, getBlocktData, getData, getSocialsData } from '~/utils/data'
import { ClientComponent } from './client'
import { type IconName, Icons } from './icons'
import { Image } from './image'

export default async function Page() {
const data = await getData()

return (
<main className="flex flex-col items-center gap-2">
<main className="relative flex min-h-screen flex-col items-center justify-between gap-4">
<div className="grid aspect-[10/4] w-full place-items-center overflow-hidden">
<Image src={data?.cover?.url} width={1920} id={data.id} alt="" />
</div>
<div className="relative -mt-14 h-24 w-24 overflow-hidden rounded-full object-cover">
<div className="relative -mt-16 h-24 w-24 overflow-hidden rounded-full object-cover">
<Image src={data?.avatar?.url} width={96} height={96} id={data.id} alt={data?.name ?? ''} />
</div>
<h1 className="text-2xl font-bold">{data?.name}</h1>
{data?.info && <p className="px-4">{data.info}</p>}
<div className="grid w-full gap-2 px-4">
<div className="grid w-full gap-4 px-4">
{data?.links?.map((link) => {
switch (link.type) {
case 'bookmark':
return (
<div key={link.id} className="min-w-0">
<a href={link.href} target="_blank" rel="noreferrer" className="inline-block max-w-full truncate">
{link.title || link.href}
</a>
</div>
)
case 'bookmark': {
// @ts-expect-error RSC
return <Bookmark key={link.id} link={link} />
}
case 'toggle':
return (
<details key={link.id} className="group">
<summary>{link.title}</summary>
<div className="hidden group-open:grid">
<details key={link.id} className="group min-w-0">
<summary className="flex cursor-pointer items-center gap-4 overflow-hidden bg-white p-1 shadow-lg hover:scale-105 hover:transition-transform">
<h3 className="max-w-full flex-1 truncate pl-3">{link.title}</h3>
<div className="grid h-12 w-12 flex-shrink-0 place-items-center overflow-hidden rounded-md transition-colors hover:bg-gray-100">
<div className="h-4 w-4 transition-transform group-open:rotate-180">
<Icons name="chevron" />
</div>
</div>
</summary>
<div className="hidden py-4 group-open:grid">
<ClientComponent>
{/* @ts-expect-error RSC */}
<BlockContent id={link.id} />
<Suspense fallback={'loading...'}>
{/* @ts-expect-error RSC */}
<BlockContent id={link.id} />
</Suspense>
</ClientComponent>
</div>
</details>
Expand All @@ -45,10 +52,52 @@ export default async function Page() {
</div>
{/* @ts-expect-error RSC */}
<Socials id={data.socials} />
<div className="w-full p-4 text-center text-xs">
made with 🤍 by{' '}
<a
className="bg-gradient-to-tr from-violet-500 via-pink-500 to-yellow-500 bg-clip-text text-transparent"
href="https://beta.accio.pro"
target="_blank"
rel="noreferrer"
>
accio
</a>
</div>
</main>
)
}

async function Bookmark({
link,
}: {
link: {
id: string
type: 'bookmark'
href: string
title: string
}
}) {
const data = await getBlockRecordMap(link.id)

return (
<a
key={link.id}
href={link.href}
target="_blank"
rel="noreferrer"
className="flex min-w-0 items-center gap-4 overflow-hidden bg-white p-1 shadow-lg hover:scale-105 hover:transition-transform"
title={data?.description || data?.title || link.title || link.href}
>
<div className="h-12 w-12 flex-shrink-0 overflow-hidden rounded-md bg-gray-100">
{data?.cover && (
<Image src={data.cover} className="h-full w-full object-cover" width={48} height={48} alt={link.title} />
)}
</div>
<h3 className="max-w-full flex-1 truncate">{data?.title || link.title || link.href}</h3>
</a>
)
}

async function BlockContent({ id }: { id: string }) {
const data = await getBlocktData(id)
return (
Expand Down Expand Up @@ -84,7 +133,26 @@ async function BlockContent({ id }: { id: string }) {

async function Socials({ id }: { id: string | undefined }) {
const data = await getSocialsData(id)
return <>{data && <pre className="w-full overflow-x-auto">{JSON.stringify(data, null, 2)}</pre>}</>
return (
<div className="mt-auto flex gap-4 p-4">
{data?.map((item) => {
return (
<a
key={item.id}
className="relative grid h-12 w-12 flex-shrink-0 place-items-center overflow-hidden transition-colors before:absolute before:inset-0 before:rounded-full before:opacity-0 before:transition-opacity before:[background-image:linear-gradient(32deg,_#85eaf2_0%,_#e485cf_47%,_#fdf67a_100%)] hover:text-white hover:before:opacity-100"
href={item.url ?? ''}
title={item.title}
target="_blank"
rel="noreferrer"
>
<div className="relative inline-block h-6 w-6">
<Icons name={item.media as IconName} />
</div>
</a>
)
})}
</div>
)
}

export const runtime = 'experimental-edge'
Expand Down
99 changes: 94 additions & 5 deletions src/utils/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export async function getData() {
const content = getBlockContent(block, block.type)
if (!content?.url) return []
return {
id: idFromUUID(block.id),
id: block.id,
type: block.type,
href: content.url,
title: richTextToPlainText(content.caption),
Expand All @@ -43,7 +43,7 @@ export async function getData() {
if (block.type === 'toggle' && block.has_children) {
const content = getBlockContent(block, block.type)
return {
id: idFromUUID(block.id),
id: block.id,
type: block.type,
title: richTextToPlainText(content?.rich_text),
}
Expand All @@ -60,7 +60,7 @@ export async function getBlocktData(id: string) {
if (block.type === 'paragraph') {
const content = getBlockContent(block, block.type)
return {
id: idFromUUID(block.id),
id: block.id,
type: block.type,
text: richTextToPlainText(content?.rich_text),
}
Expand All @@ -69,7 +69,7 @@ export async function getBlocktData(id: string) {
const content = getBlockContent(block, block.type)
if (content?.type !== 'external') return []
return {
id: idFromUUID(block.id),
id: block.id,
type: block.type,
url: content.external.url,
}
Expand All @@ -84,7 +84,7 @@ export async function getSocialsData(id: string | undefined) {
if (!isFullPage(item)) return []
if (item.object === 'page') {
return {
id: idFromUUID(item.id),
id: item.id,
title: richTextToPlainText(getProperty(item.properties, 'title', 'title')),
media: getProperty(item.properties, 'media', 'select')?.name ?? null,
url: getProperty(item.properties, 'link', 'url') ?? null,
Expand All @@ -94,6 +94,8 @@ export async function getSocialsData(id: string | undefined) {
})
}

// #region api

export async function getPage(id: string | undefined) {
const page = await notion.pages.retrieve({
page_id: idFromUUID(id),
Expand All @@ -117,6 +119,48 @@ export async function getDatabase(id: string | undefined) {
return database
}

export async function getBlockRecordMap(id: string | undefined) {
if (!id) return null

try {
const data = await fetch('https://www.notion.so/api/v3/syncRecordValues', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Cookie: `token_v2=${env.NOTION_TOKEN}`,
},
body: JSON.stringify({
requests: [
{
pointer: {
table: 'block',
id,
},
version: 1,
},
],
}),
})
const json = (await data.json()) as { recordMap: RecordMap }
const recordMap = json.recordMap
const properties = recordMap.block?.[id]?.value?.properties
const format = recordMap.block?.[id]?.value?.format
return {
id,
title: properties?.title?.[0]?.[0] ?? null,
description: properties?.description?.[0]?.[0] ?? null,
icon: format?.bookmark_icon ?? null,
cover: format?.bookmark_cover ?? null,
}
} catch (error) {
console.error(error)
}
}

// #endregion

// #region helpers

export function idFromUUID(id: string | null | undefined): string {
return id?.replace(/-/g, '') ?? ''
}
Expand Down Expand Up @@ -164,3 +208,48 @@ type Block = PartialBlockObjectResponse | BlockObjectResponse
type Properties = PageObjectResponse['properties']
type Property = NonNullable<Properties[keyof Properties]>
type File = Extract<Property, { type: 'files' }>['files'][number]

export interface RecordMap {
block: TBlock
}

export interface TBlock {
[key: string]: TEditor
}

export interface TEditor {
role: string
value: Value
}

export interface Value {
id: string
version: number
type: string
properties: TProperties
format: Format
created_time: number
last_edited_time: number
parent_id: string
parent_table: string
alive: boolean
created_by_table: string
created_by_id: string
last_edited_by_table: string
last_edited_by_id: string
space_id: string
}

export interface TProperties {
link: string[][]
title: string[][]
caption: string[][]
description: string[][]
}

export interface Format {
bookmark_icon: string
bookmark_cover: string
}

// #endregion
Loading

1 comment on commit c43051c

@vercel
Copy link

@vercel vercel bot commented on c43051c Jan 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.