Skip to content

Commit

Permalink
Merge branch 'main' into feat/comments
Browse files Browse the repository at this point in the history
# Conflicts:
#	.gitignore
#	env.mjs
#	package.json
#	pnpm-lock.yaml
  • Loading branch information
CaliCastle committed Jun 3, 2023
2 parents 0f410a6 + d2b72c6 commit 8b37f15
Show file tree
Hide file tree
Showing 46 changed files with 3,200 additions and 590 deletions.
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,4 @@ next-env.d.ts

static/

# Supabase
**/supabase/.branches
**/supabase/.temp
.react-email/
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
- [Tailwind CSS](https://tailwindcss.com/)
- [Framer Motion](https://www.framer.com/motion/)
- [Radix UI](https://www.radix-ui.com/)
- [PlanetScale](https://planetscale.com/)
- [Drizzle ORM](https://ore.drizzle.team/)
- [Sanity](https://www.sanity.io/)
- [React Email](https://react.email)
- [Resend](https://resend.com/)

### 本地开发

Expand Down
14 changes: 10 additions & 4 deletions app/(main)/Newsletter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export function Newsletter() {
const onSubmit = React.useCallback(
async (data: NewsletterForm) => {
try {
if (isSubmitting) return

va.track('Newsletter:Subscribe')

const response = await fetch('/api/newsletter', {
Expand All @@ -57,12 +59,12 @@ export function Newsletter() {
console.error(error)
}
},
[reward, reset]
[isSubmitting, reset, reward]
)

React.useEffect(() => {
if (isSubscribed) {
setTimeout(() => setIsSubscribed(false), 6000)
setTimeout(() => setIsSubscribed(false), 60000)
}
}, [isSubscribed])

Expand Down Expand Up @@ -98,7 +100,11 @@ export function Newsletter() {
className="min-w-0 flex-auto appearance-none rounded-lg border border-zinc-900/10 bg-white px-3 py-[calc(theme(spacing.2)-1px)] placeholder:text-zinc-400 focus:border-lime-500 focus:outline-none focus:ring-4 focus:ring-lime-500/10 dark:border-zinc-700 dark:bg-zinc-700/[0.15] dark:text-zinc-200 dark:placeholder:text-zinc-500 dark:focus:border-lime-400/50 dark:focus:ring-lime-400/5 sm:text-sm"
{...register('email')}
/>
<Button type="submit" className="ml-2 flex-none">
<Button
type="submit"
className="ml-2 flex-none"
disabled={isSubmitting}
>
订阅
</Button>
</motion.div>
Expand All @@ -109,7 +115,7 @@ export function Newsletter() {
animate={{ opacity: 1, y: 0 }}
exit="initial"
>
🎉 感谢你的订阅 🥳
请查收订阅确认邮件 🥳
</motion.p>
)}
</AnimatePresence>
Expand Down
44 changes: 0 additions & 44 deletions app/(main)/_actions.ts

This file was deleted.

1 change: 1 addition & 0 deletions app/(main)/blog/BlogPostPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export function BlogPostPage({
className="select-none"
unoptimized
fill
aria-hidden={true}
/>
</div>
<Image
Expand Down
2 changes: 1 addition & 1 deletion app/(main)/blog/BlogPosts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export async function BlogPosts({ limit = 5 }) {
key={idx}
href={`/blog/${slug}`}
prefetch={false}
className="group relative flex aspect-[240/135] w-full flex-col justify-end gap-16 rounded-3xl bg-white p-4 transition-shadow after:absolute after:inset-0 after:rounded-3xl after:bg-[linear-gradient(180deg,transparent,rgba(0,0,0,.7)_55%,#000_82.5%,#000)] after:opacity-100 after:ring-2 after:ring-zinc-200 after:transition-opacity hover:shadow-xl hover:after:opacity-70 dark:after:ring-zinc-800/70 md:p-6"
className="group relative flex aspect-[240/135] w-full flex-col justify-end gap-16 rounded-3xl bg-white p-4 transition-shadow after:absolute after:inset-0 after:rounded-3xl after:bg-[linear-gradient(180deg,transparent,rgba(0,0,0,.7)_55%,rgba(0,0,0,.85)_82.5%,rgba(0,0,0,.9))] after:opacity-90 after:ring-2 after:ring-zinc-200 after:transition-opacity hover:shadow-xl hover:after:opacity-60 dark:after:ring-zinc-800/70 md:p-6"
>
<Image
src={mainImage.asset.url}
Expand Down
21 changes: 21 additions & 0 deletions app/(main)/confirm/[token]/SubbedCelebration.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use client'

import React from 'react'
import { useReward } from 'react-rewards'

export function SubbedCelebration() {
const { reward } = useReward('subbed-celebration', 'confetti', {
position: 'absolute',
elementCount: 160,
spread: 80,
elementSize: 8,
lifetime: 400,
})

React.useEffect(() => {
setTimeout(() => reward(), 500)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

return <div className="pb-16"></div>
}
49 changes: 49 additions & 0 deletions app/(main)/confirm/[token]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { eq } from 'drizzle-orm'
import { redirect } from 'next/navigation'

import { Container } from '~/components/ui/Container'
import { db } from '~/db'
import { subscribers } from '~/db/schema'

import { SubbedCelebration } from './SubbedCelebration'

export const runtime = 'edge'

export const metadata = {
title: '感谢你的订阅',
}

export default async function ConfirmPage({
params,
}: {
params: { token: string }
}) {
const [subscriber] = await db
.select()
.from(subscribers)
.where(eq(subscribers.token, params.token))

if (!subscriber || subscriber.subscribedAt) {
redirect('/')
}

await db
.update(subscribers)
.set({ subscribedAt: new Date(), token: null })
.where(eq(subscribers.id, subscriber.id))

return (
<Container className="mt-16 sm:mt-32">
<header className="relative mx-auto flex w-full max-w-2xl items-center justify-center">
<h1
className="w-full text-center text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl"
id="subbed-celebration"
>
🥳 感谢你的订阅 🎉
</h1>
<p className="mt-6 text-base text-zinc-600 dark:text-zinc-400"></p>
</header>
<SubbedCelebration />
</Container>
)
}
95 changes: 95 additions & 0 deletions app/(main)/projects/ProjectCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use client'

import {
AnimatePresence,
useMotionTemplate,
useMotionValue,
} from 'framer-motion'
import { motion } from 'framer-motion'
import Image from 'next/image'
import React from 'react'

import { ExternalLinkIcon } from '~/assets'
import { Card } from '~/components/ui/Card'
import { urlForImage } from '~/sanity/lib/image'
import { type Project } from '~/sanity/schemas/project'

export function ProjectCard({ project }: { project: Project }) {
const { _id, url, icon, name, description } = project

const mouseX = useMotionValue(0)
const mouseY = useMotionValue(0)
const radius = useMotionValue(0)
const handleMouseMove = React.useCallback(
({ clientX, clientY, currentTarget }: React.MouseEvent) => {
const bounds = currentTarget.getBoundingClientRect()
mouseX.set(clientX - bounds.left)
mouseY.set(clientY - bounds.top)
radius.set(Math.sqrt(bounds.width ** 2 + bounds.height ** 2) / 2)
console.log({
mouseX: mouseX.get(),
mouseY: mouseY.get(),
radius: radius.get(),
})
},
[mouseX, mouseY, radius]
)
const maskBackground = useMotionTemplate`radial-gradient(circle ${radius}px at ${mouseX}px ${mouseY}px, black 40%, transparent)`
const [isHovering, setIsHovering] = React.useState(false)

return (
<Card
as="li"
key={_id}
onMouseEnter={() => setIsHovering(true)}
onMouseMove={handleMouseMove}
onMouseLeave={() => setIsHovering(false)}
>
<div className="relative z-10 flex h-12 w-12 items-center justify-center rounded-full bg-white shadow-md shadow-zinc-800/5 ring-1 ring-zinc-900/5 dark:border dark:border-zinc-700/50 dark:bg-zinc-800 dark:ring-0">
<Image
src={urlForImage(icon)?.size(100, 100).auto('format').url()}
alt=""
width={36}
height={36}
className="h-9 w-9 rounded-full"
unoptimized
/>
</div>
<h2 className="mt-6 text-base font-bold text-zinc-800 dark:text-zinc-100">
<Card.Link href={url} target="_blank">
{name}
</Card.Link>
</h2>
<Card.Description>{description}</Card.Description>
<p className="pointer-events-none relative z-40 mt-6 flex items-center text-sm font-medium text-zinc-400 transition group-hover:-translate-y-0.5 group-hover:text-lime-600 dark:text-zinc-200 dark:group-hover:text-lime-400">
<span className="mr-2">{new URL(url).host}</span>
<ExternalLinkIcon className="h-4 w-4 flex-none" />
</p>

<AnimatePresence>
{isHovering && (
<motion.footer
className="pointer-events-none absolute -inset-x-4 -inset-y-6 z-30 select-none px-4 py-6 sm:-inset-x-6 sm:rounded-2xl sm:px-6"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
style={{
WebkitMaskImage: maskBackground,
}}
exit={{ opacity: 0 }}
>
<div className="absolute inset-x-px inset-y-px rounded-2xl border border-dashed border-zinc-900/30 dark:border-zinc-100/20" />
<div className="flex h-12 w-12 items-center justify-center rounded-full border border-dashed border-zinc-900/20 bg-white dark:border-zinc-100/20 dark:bg-zinc-800">
<div className="h-9 w-9 rounded-full border border-dashed border-zinc-900/40 dark:border-zinc-100/60 dark:bg-zinc-900/20" />
</div>
<h2 className="mt-6 text-base font-bold text-zinc-50 [text-shadow:rgb(0,0,0)_-0.5px_0.5px_0px,rgb(0,0,0)_0.5px_0.5px_0px,rgb(0,0,0)_0.5px_-0.5px_0px,rgb(0,0,0)_-0.5px_-0.5px_0px] dark:text-zinc-900 dark:[text-shadow:rgb(255,255,255)_-0.5px_0.5px_0px,rgb(255,255,255)_0.5px_0.5px_0px,rgb(255,255,255)_0.5px_-0.5px_0px,rgb(255,255,255)_-0.5px_-0.5px_0px]">
{name}
</h2>
<p className="mt-2 text-sm text-zinc-600 opacity-50 dark:text-zinc-400">
{description}
</p>
</motion.footer>
)}
</AnimatePresence>
</Card>
)
}
17 changes: 17 additions & 0 deletions app/(main)/projects/Projects.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ProjectCard } from '~/app/(main)/projects/ProjectCard'
import { getSettings } from '~/sanity/queries'

export async function Projects() {
const { projects } = await getSettings()

return (
<ul
role="list"
className="grid grid-cols-1 gap-x-12 gap-y-16 sm:grid-cols-2 lg:grid-cols-3"
>
{projects.map((project) => (
<ProjectCard project={project} key={project._id} />
))}
</ul>
)
}
Binary file added app/(main)/projects/opengraph-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 34 additions & 2 deletions app/(main)/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,41 @@
import { type Metadata } from 'next'

import { Projects } from '~/app/(main)/projects/Projects'
import { Container } from '~/components/ui/Container'

const title = '我的项目'
const description =
'多年来,我一直在做各种各样的小项目,这里就是我筛选出来我觉得还不错的项目合集,也是我在技术领域中尝试和探索的最好见证。'
export const metadata = {
title,
description,
openGraph: {
title,
description,
},
twitter: {
title,
description,
card: 'summary_large_image',
},
} satisfies Metadata

export default function ProjectsPage() {
return (
<Container>
<h1 className="mt-10">给我点时间开发一下...</h1>
<Container className="mt-16 sm:mt-32">
<header className="max-w-2xl">
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
我过去的项目冒险之旅。
</h1>
<p className="mt-6 text-base text-zinc-600 dark:text-zinc-400">
多年来,我一直在做各种各样的小项目,有<b>开源</b>的,有<b>实验</b>
的,也有 <b>just for fun </b>
的,下面就是我筛选出来我觉得还不错的项目合集,也是我在技术领域中尝试和探索的最好见证。
</p>
</header>
<div className="mt-16 sm:mt-20">
<Projects />
</div>
</Container>
)
}
Expand Down
Binary file added app/(main)/projects/twitter-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 8b37f15

Please sign in to comment.