use h3 instead of trpc

This commit is contained in:
2023-12-09 21:26:41 +01:00
parent f0aa9ebdf4
commit ba067cf533
25 changed files with 1395 additions and 940 deletions

View File

@@ -1,24 +1,19 @@
export default defineNuxtConfig({
srcDir: 'src',
build: {
transpile: ['trpc-nuxt'],
},
css: [
'@/assets/css/main.scss',
],
modules: [
'@nuxt/ui',
'nuxt-auth-utils',
'@nuxt/image',
'@nuxthq/studio',
'@nuxt/content',
'@nuxt/ui',
'@pinia/nuxt',
'@pinia-plugin-persistedstate/nuxt',
'@nuxt/devtools',
'@vueuse/nuxt',
'nuxt-icon',
],
colorMode: {

View File

@@ -13,38 +13,32 @@
},
"dependencies": {
"@nuxt/content": "2.9.0",
"@nuxt/image": "1.0.0",
"@nuxt/ui": "^2.10.0",
"@nuxt/image": "1.1.0",
"@nuxt/ui": "2.11.0",
"@pinia/nuxt": "0.5.1",
"@prisma/client": "^5.6.0",
"@tresjs/nuxt": "^1.2.2",
"@trpc/client": "10.43.3",
"@trpc/server": "10.43.3",
"@prisma/client": "5.7.0",
"@vercel/analytics": "1.1.1",
"@vueuse/motion": "2.0.0",
"pinia": "2.1.7",
"postcss-custom-properties": "13.3.2",
"sass": "1.69.5",
"superjson": "2.2.1",
"tailwindcss": "3.3.5",
"three": "^0.158.0",
"trpc-nuxt": "0.10.12",
"zod": "3.22.4"
"tailwindcss": "3.3.6",
"zod": "^3.22.4"
},
"devDependencies": {
"@antfu/eslint-config": "1.1.0",
"@iconify/json": "2.2.140",
"@nuxt/devtools": "1.0.1",
"@nuxthq/studio": "1.0.3",
"@antfu/eslint-config": "2.4.2",
"@iconify/json": "2.2.153",
"@nuxthq/studio": "1.0.5",
"@pinia-plugin-persistedstate/nuxt": "1.2.0",
"@tailwindcss/typography": "^0.5.10",
"@types/node": "20.9.0",
"@vueuse/core": "10.6.0",
"@vueuse/nuxt": "10.6.0",
"eslint": "8.53.0",
"nuxt": "3.8.1",
"nuxt-icon": "0.5.0",
"prisma": "^5.6.0",
"typescript": "5.2.2"
"@types/node": "20.10.4",
"@vueuse/core": "10.7.0",
"@vueuse/nuxt": "10.7.0",
"eslint": "8.55.0",
"nuxt": "3.8.2",
"nuxt-auth-utils": "^0.0.10",
"prisma": "5.7.0",
"typescript": "5.3.3"
}
}

View File

@@ -29,8 +29,6 @@ model Category {
createdAt DateTime @default(now())
slug String
name String
type CategoryType
bookmarks CategoriesOnBookMarks[]
talents CategoriesOnTalents[]
}
@@ -56,26 +54,6 @@ model CategoriesOnTalents {
@@index([categoryId])
}
model BookMark {
id Int @id @default(autoincrement())
createdAd DateTime @default(now())
name String
description String
link String
CategoriesOnBookMarks CategoriesOnBookMarks[]
}
model CategoriesOnBookMarks {
bookmarkId Int
categoryId Int
bookmark BookMark @relation(fields: [bookmarkId], references: [id])
category Category @relation(fields: [categoryId], references: [id])
@@id([bookmarkId, categoryId])
@@index([bookmarkId])
@@index([categoryId])
}
model Post {
id Int @id @default(autoincrement())
slug String @unique
@@ -84,6 +62,14 @@ model Post {
likes Int @default(0)
}
model Suggestion {
id Int @id @default(autoincrement())
author String @unique
content String
added Boolean @default(false)
createdAt DateTime @default(now())
}
model Form {
id Int @id @default(autoincrement())
name String
@@ -91,8 +77,3 @@ model Form {
content String
createdAt DateTime @default(now())
}
enum CategoryType {
TALENT
BOOKMARK
}

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup>
const { $trpc } = useNuxtApp()
const announce = await $trpc.announcement.get.query()
const { data: announce } = await useFetch('/api/announcement')
const appConfig = useAppConfig()
function getColor() {

View File

@@ -1,24 +1,15 @@
<script setup lang="ts">
defineProps({
navigation: {
type: Boolean,
default: true,
},
})
</script>
<template>
<header class="z-30 sticky top-0 left-0 flex justify-center w-full">
<div class="w-full px-4 sm:px-6 lg:px-8 sm:mx-8 max-w-7xl py-4 flex justify-between bg-white dark:bg-zinc-900 border-b border-zinc-100 dark:border-zinc-300/10">
<div class="w-full px-4 sm:px-6 lg:px-8 sm:mx-8 max-w-7xl py-4 flex justify-between items-center bg-white dark:bg-zinc-900 border-b border-zinc-100 dark:border-zinc-300/10">
<ClientOnly>
<Logo />
<NavBar v-if="navigation" />
<div class="flex gap-2">
<NavBar />
<div class="flex gap-2 items-center">
<div class="flex items-center rounded-md p-1 gap-1 relative bg-black/5 text-sm font-medium text-zinc-700 dark:bg-zinc-800/90 dark:text-zinc-300">
<ColorPicker />
<ColorModeButton />
</div>
<MobileNavBar v-if="navigation" />
<MobileNavBar />
</div>
</ClientOnly>
</div>

View File

@@ -54,7 +54,7 @@ function isRoute(path: string) {
</script>
<template>
<div class="md:hidden">
<div class="lg:hidden">
<div class="p-1 rounded-md bg-black/5 text-sm font-medium text-zinc-700 dark:bg-zinc-800/90 dark:text-zinc-300">
<UButton
variant="ghost"
@@ -69,7 +69,7 @@ function isRoute(path: string) {
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="flex justify-between items-center">
<div>Logo</div>
<Logo />
<UButton
size="md"
icon="i-ic-round-close"

View File

@@ -6,15 +6,15 @@ const items = [
to: '/talents',
icon: 'i-ph-users-bold',
}, {
label: 'Bookmarks',
to: '/bookmarks',
icon: 'i-ph-bookmark-simple-bold',
label: 'Guestbook',
to: '/guestbook',
icon: 'i-material-symbols-book-2-outline',
}],
]
</script>
<template>
<nav class="hidden md:block z-50">
<nav class="hidden lg:block z-50">
<div class="flex items-center h-10 rounded-md p-1 gap-1 relative bg-black/5 text-sm font-medium text-zinc-700 dark:bg-zinc-800/90 dark:text-zinc-300">
<UButton to="/" size="sm" variant="ghost" color="white" :class="{ 'link-active': route.path === '/' }">
Home

View File

@@ -15,8 +15,8 @@ const isLight = computed(() => $colorMode.value === 'light')
class="flex items-center gap-2 rounded-md px-2 py-3 duration-300 md:hover:bg-gray-100 md:dark:hover:bg-neutral-800"
>
<div class="flex items-center">
<Icon v-if="isLight" :name="skill.icon.light ? skill.icon.light : skill.icon" size="20" />
<Icon v-else :name="skill.icon.dark ? skill.icon.dark : skill.icon" size="20" />
<UIcon v-if="isLight" :name="skill.icon.light ? skill.icon.light : skill.icon" size="20" dynamic />
<UIcon v-else :name="skill.icon.dark ? skill.icon.dark : skill.icon" size="20" dynamic />
</div>
<span class="text-sm text-subtitle">{{ skill.name }}</span>
</li>

View File

@@ -1,23 +1,24 @@
export async function usePost(slug: string) {
const { $trpc } = useNuxtApp()
const {
data: post,
refresh: refreshPost,
} = await useAsyncData(`blog:post-db:${slug}`, async () => await $trpc.post.createOrUpdate.mutate({
slug,
}))
} = useFetch('/api/article', {
method: 'POST',
body: {
slug,
},
})
const likes = ref(post.value!.likes)
const likes = ref(post.value?.likes)
const like = async () => {
const data = await $trpc.post.like.mutate({ slug })
likes.value = data.likes
const { data } = await useFetch('/api/like', { method: 'PUT' })
likes.value = data
}
const views = ref(post.value!.views)
const view = async () => {
const data = await $trpc.post.view.mutate({ slug })
views.value = data.views
const { data } = await useFetch('/api/view', { method: 'PUT' })
likes.value = data
}
return {

View File

@@ -1,40 +0,0 @@
import { useTalentsStore } from '~/store/talents'
export async function useTalents() {
const { $trpc } = useNuxtApp()
const { setCategory, setFavorite, getCategory, isFavorite } = useTalentsStore()
const {
data: talents,
refresh: refreshTalents,
pending,
} = await useAsyncData('talents:talents', async () => await $trpc.talents.getTalents.query({ favorite: isFavorite.value, category: getCategory.value }))
async function switchCategory(category: string) {
setCategory(category)
await refreshTalents()
}
async function toggleFavorite() {
setFavorite()
await refreshTalents()
}
function isCategory(category: string) {
return getCategory.value === category
}
const {
data: getCategories,
} = await $trpc.talents.getCategories.useQuery()
return {
talents,
getCategories,
isFavorite,
switchCategory,
toggleFavorite,
pending,
isCategory,
}
}

View File

@@ -1,9 +1,9 @@
export default defineNuxtRouteMiddleware(async (to) => {
const isMaintenance = ref<boolean>(true)
const { $trpc } = useNuxtApp()
try {
isMaintenance.value = await $trpc.maintenance.is.query()
await $fetch('/api/maintenance').then((maintenance: any) => {
isMaintenance.value = maintenance.enabled
})
}
catch (error) {
return navigateTo('/maintenance')

View File

@@ -7,8 +7,7 @@ useHead({
title: 'Site under maintenance • Arthur Danjou',
})
const { $trpc } = useNuxtApp()
const maintenance = await $trpc.maintenance.get.query()
const { data: maintenance } = await useFetch('/api/maintenance')
const format = 'DD MMMM YYYY, HH:mm'
const appConfig = useAppConfig()
@@ -47,13 +46,13 @@ const socials = [
<h1 class="text-4xl md:text-7xl font-bold">
The website is under maintenance
</h1>
<div v-if="maintenance">
<div v-if="maintenance && maintenance.maintenance">
<p :class="getColor" class="font-bold mb-8 text-xl">
{{ maintenance.reason }}
{{ maintenance.maintenance.reason }}
</p>
<div>
<p class="text-subtitle italic">
Maintenance planned from {{ useDateFormat(maintenance.beginAt, format).value }} to {{ useDateFormat(maintenance.endAt, format).value }}
Maintenance planned from {{ useDateFormat(maintenance.maintenance.beginAt, format).value }} to {{ useDateFormat(maintenance.maintenance.endAt, format).value }}
</p>
</div>
</div>

View File

@@ -1,10 +1,32 @@
<script lang="ts" setup>
import { useTalentsStore } from '~/store/talents'
useHead({
title: 'Discover new talents • Arthur Danjou',
})
const categories = ref<Array<{ label: string; slug: string }>>([{ label: 'All', slug: 'all' }])
const { getCategories, talents, isFavorite, toggleFavorite, switchCategory, pending, isCategory } = await useTalents()
const { getCategory, setCategory, isFavorite, toggleFavorite } = useTalentsStore()
const {
data: talents,
pending,
} = await useFetch('/api/talents', {
method: 'get',
query: {
favorite: isFavorite,
category: getCategory,
},
watch: [isFavorite, getCategory]
})
function isCategory(category: string) {
return getCategory.value === category
}
const {
data: getCategories,
} = await useFetch('/api/categories', { method: 'GET' })
getCategories.value?.forEach(category => categories.value.push({ label: category.name, slug: category.slug }))
@@ -28,6 +50,7 @@ function getColor() {
<div class="mb-6">
<div class="mb-2 flex items-center gap-2">
<UIcon name="i-ph-circle-wavy-question-bold" class="text-subtitle text-xl" />
<!-- TODO: use suggestions -->
<h1 class="text-lg font-bold">
Want to be here ?
</h1>
@@ -43,21 +66,14 @@ function getColor() {
<div v-if="getCategories" class="flex gap-2 w-full items-center justify-between pb-2 border-b border-zinc-100 dark:border-zinc-700/40 mb-4">
<div class="flex gap-2 overflow-x-scroll sm:overflow-x-hidden bg-gray-100 dark:bg-gray-800 rounded-lg p-1 relative">
<div
class="relative px-3 py-1 text-sm font-medium rounded-md h-8 text-gray-500 dark:text-gray-400 min-w-fit flex items-center justify-center w-full focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 transition-colors duration-200 ease-out cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white"
:class="{ 'text-gray-900 dark:text-white relative !bg-white dark:!bg-stone-900 rounded-md shadow-sm': isCategory('all') }"
@click.prevent="switchCategory('all')"
>
All
</div>
<div
v-for="category in getCategories"
v-for="category in categories"
:key="category.slug"
class="relative px-3 py-1 text-sm font-medium rounded-md h-8 text-gray-500 dark:text-gray-400 min-w-fit flex items-center justify-center w-full focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 transition-colors duration-200 ease-out cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white"
:class="{ 'text-gray-900 dark:text-white relative !bg-white dark:!bg-stone-900 rounded-md shadow-sm': isCategory(category.slug) }"
@click.prevent="switchCategory(category.slug)"
@click.prevent="setCategory(category.slug)"
>
<p class="w-full">
{{ category.name }}
{{ category.label }}
</p>
</div>
</div>

View File

@@ -31,7 +31,7 @@ const { data: projects } = await useProjects()
class="group relative flex flex-col justify-between"
>
<div class="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">
<Icon :name="project.icon" size="24" />
<UIcon :name="project.icon" size="24" dynamic />
</div>
<h2 class="mt-6 text-base font-semibold text-zinc-800 dark:text-zinc-100">
<div class="absolute -inset-y-6 -inset-x-4 z-0 scale-95 bg-zinc-50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 dark:bg-zinc-800/50 sm:-inset-x-6 sm:rounded-2xl" />

View File

@@ -1,26 +0,0 @@
import { loggerLink } from '@trpc/client'
import SuperJSON from 'superjson'
import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client'
import type { AppRouter } from '~/server/trpc/routers'
export default defineNuxtPlugin(() => {
const trpc = createTRPCNuxtClient<AppRouter>({
transformer: SuperJSON,
links: [
loggerLink({
enabled: opts =>
process.env.NODE_ENV === 'development'
|| (opts.direction === 'down' && opts.result instanceof Error),
}),
httpBatchLink({
url: '/api/trpc',
}),
],
})
return {
provide: {
trpc,
},
}
})

View File

@@ -1,8 +0,0 @@
import { createNuxtApiHandler } from 'trpc-nuxt'
import { createContext } from '~/server/trpc/context'
import { appRouter } from '~/server/trpc/routers'
export default createNuxtApiHandler({
createContext,
router: appRouter,
})

View File

@@ -1,19 +0,0 @@
import { PrismaClient } from '@prisma/client'
import type { inferAsyncReturnType } from '@trpc/server'
import type { H3Event } from 'h3'
let prisma: PrismaClient | undefined
export function createContext(_event: H3Event) {
if (!prisma) {
prisma = new PrismaClient({
log: ['warn', 'info', 'error'],
})
}
return {
prisma,
}
}
export type Context = inferAsyncReturnType<typeof createContext>

View File

@@ -1,12 +0,0 @@
import { publicProcedure, router } from '../trpc'
export default router({
get: publicProcedure
.query(async ({ ctx }) => {
return await ctx.prisma.announcement.findFirst({
orderBy: {
createdAt: 'desc',
},
})
}),
})

View File

@@ -1,14 +0,0 @@
import { router } from '../trpc'
import announcement from './announcement'
import maintenance from './maintenance'
import post from './post'
import talents from './talents'
export const appRouter = router({
announcement,
post,
talents,
maintenance,
})
export type AppRouter = typeof appRouter

View File

@@ -1,29 +0,0 @@
import { publicProcedure, router } from '../trpc'
export default router({
get: publicProcedure
.query(async ({ ctx }) => {
return await ctx.prisma.maintenance.findFirst({
orderBy: {
createdAt: 'desc',
},
})
}),
is: publicProcedure
.query(async ({ ctx }) => {
const maintenance = await ctx.prisma.maintenance.findFirst({
orderBy: {
createdAt: 'desc',
},
})
if (process.env.NODE_ENV === 'development')
return false
const today = new Date()
return !!maintenance
&& maintenance.enabled
&& maintenance.beginAt.getTime() < today.getTime()
&& maintenance.endAt.getTime() > today.getTime()
}),
})

View File

@@ -1,68 +0,0 @@
import { z } from 'zod'
import { publicProcedure, router } from '../trpc'
const PostSchema = z.object({
slug: z.string(),
})
export default router({
createOrUpdate: publicProcedure
.input(PostSchema)
.mutation(async ({ ctx, input }) => {
return await ctx.prisma.post.upsert({
where: {
slug: input.slug,
},
update: {},
create: {
slug: input.slug,
},
})
}),
getTotalViews: publicProcedure
.query(async ({ ctx }) => {
const views = await ctx.prisma.post.aggregate({
_sum: {
views: true,
},
})
return views._sum.views || 0
}),
view: publicProcedure
.input(PostSchema)
.mutation(async ({ ctx, input }) => {
return await ctx.prisma.post.update({
where: {
slug: input.slug,
},
data: {
views: {
increment: 1,
},
},
})
}),
getTotalLikes: publicProcedure
.query(async ({ ctx }) => {
const likes = await ctx.prisma.post.aggregate({
_sum: {
likes: true,
},
})
return likes._sum.likes || 0
}),
like: publicProcedure
.input(PostSchema)
.mutation(async ({ ctx, input }) => {
return await ctx.prisma.post.update({
where: {
slug: input.slug,
},
data: {
likes: {
increment: 1,
},
},
})
}),
})

View File

@@ -1,112 +0,0 @@
import { z } from 'zod'
import { publicProcedure, router } from '../trpc'
export default router({
getTalents: publicProcedure
.input(z.object({
favorite: z.boolean(),
category: z.union([z.string(), z.literal('all')]),
}))
.query(async ({ ctx, input }) => {
if (input.favorite) {
return input.category === 'all'
? await ctx.prisma.talent.findMany({
orderBy: {
createdAt: 'desc',
},
include: {
categories: {
include: {
talent: true,
category: true,
},
orderBy: {
category: {
name: 'asc',
},
},
},
},
where: {
favorite: true,
categories: { every: { category: {} } },
},
})
: await ctx.prisma.talent.findMany({
orderBy: {
createdAt: 'desc',
},
include: {
categories: {
include: {
talent: true,
category: true,
},
orderBy: {
category: {
name: 'asc',
},
},
},
},
where: {
favorite: true,
categories: { some: { category: { slug: input.category } } },
},
})
}
else {
return input.category === 'all'
? await ctx.prisma.talent.findMany({
orderBy: {
createdAt: 'desc',
},
include: {
categories: {
include: {
talent: true,
category: true,
},
orderBy: {
category: {
name: 'asc',
},
},
},
},
where: {
categories: { every: { category: {} } },
},
})
: await ctx.prisma.talent.findMany({
orderBy: {
createdAt: 'desc',
},
include: {
categories: {
include: {
talent: true,
category: true,
},
orderBy: {
category: {
name: 'asc',
},
},
},
},
where: {
categories: { some: { category: { slug: input.category } } },
},
})
}
}),
getCategories: publicProcedure
.query(async ({ ctx }) => {
return await ctx.prisma.category.findMany({
where: {
type: 'TALENT',
},
})
}),
})

View File

@@ -1,12 +0,0 @@
import { initTRPC } from '@trpc/server'
import SuperJSON from 'superjson'
import type { Context } from './context'
const trpc = initTRPC.context<Context>().create({
transformer: SuperJSON,
})
export const publicProcedure = trpc.procedure
export const router = trpc.router
export const middleware = trpc.middleware
export const mergeRouters = trpc.mergeRouters

View File

@@ -7,21 +7,20 @@ export const useTalentsStore = defineStore(
const currentFavorite = ref<boolean>(false)
const getCategory = computed(() => currentCategory)
const isFavorite = computed(() => currentFavorite)
function setCategory(category: string) {
currentCategory.value = category
function setCategory(newCategory: string) {
currentCategory.value = newCategory
}
function setFavorite() {
const isFavorite = computed(() => currentFavorite)
function toggleFavorite() {
currentFavorite.value = !currentFavorite.value
}
return {
getCategory,
setCategory,
setFavorite,
isFavorite,
toggleFavorite,
}
},
{

1790
yarn.lock

File diff suppressed because it is too large Load Diff