mirror of
https://github.com/ArthurDanjou/website.git
synced 2026-02-04 06:07:55 +01:00
use h3 instead of trpc
This commit is contained in:
@@ -1,24 +1,19 @@
|
|||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
srcDir: 'src',
|
srcDir: 'src',
|
||||||
|
|
||||||
build: {
|
|
||||||
transpile: ['trpc-nuxt'],
|
|
||||||
},
|
|
||||||
|
|
||||||
css: [
|
css: [
|
||||||
'@/assets/css/main.scss',
|
'@/assets/css/main.scss',
|
||||||
],
|
],
|
||||||
|
|
||||||
modules: [
|
modules: [
|
||||||
|
'@nuxt/ui',
|
||||||
|
'nuxt-auth-utils',
|
||||||
'@nuxt/image',
|
'@nuxt/image',
|
||||||
'@nuxthq/studio',
|
'@nuxthq/studio',
|
||||||
'@nuxt/content',
|
'@nuxt/content',
|
||||||
'@nuxt/ui',
|
|
||||||
'@pinia/nuxt',
|
'@pinia/nuxt',
|
||||||
'@pinia-plugin-persistedstate/nuxt',
|
'@pinia-plugin-persistedstate/nuxt',
|
||||||
'@nuxt/devtools',
|
|
||||||
'@vueuse/nuxt',
|
'@vueuse/nuxt',
|
||||||
'nuxt-icon',
|
|
||||||
],
|
],
|
||||||
|
|
||||||
colorMode: {
|
colorMode: {
|
||||||
|
|||||||
38
package.json
38
package.json
@@ -13,38 +13,32 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/content": "2.9.0",
|
"@nuxt/content": "2.9.0",
|
||||||
"@nuxt/image": "1.0.0",
|
"@nuxt/image": "1.1.0",
|
||||||
"@nuxt/ui": "^2.10.0",
|
"@nuxt/ui": "2.11.0",
|
||||||
"@pinia/nuxt": "0.5.1",
|
"@pinia/nuxt": "0.5.1",
|
||||||
"@prisma/client": "^5.6.0",
|
"@prisma/client": "5.7.0",
|
||||||
"@tresjs/nuxt": "^1.2.2",
|
|
||||||
"@trpc/client": "10.43.3",
|
|
||||||
"@trpc/server": "10.43.3",
|
|
||||||
"@vercel/analytics": "1.1.1",
|
"@vercel/analytics": "1.1.1",
|
||||||
"@vueuse/motion": "2.0.0",
|
"@vueuse/motion": "2.0.0",
|
||||||
"pinia": "2.1.7",
|
"pinia": "2.1.7",
|
||||||
"postcss-custom-properties": "13.3.2",
|
"postcss-custom-properties": "13.3.2",
|
||||||
"sass": "1.69.5",
|
"sass": "1.69.5",
|
||||||
"superjson": "2.2.1",
|
"superjson": "2.2.1",
|
||||||
"tailwindcss": "3.3.5",
|
"tailwindcss": "3.3.6",
|
||||||
"three": "^0.158.0",
|
"zod": "^3.22.4"
|
||||||
"trpc-nuxt": "0.10.12",
|
|
||||||
"zod": "3.22.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "1.1.0",
|
"@antfu/eslint-config": "2.4.2",
|
||||||
"@iconify/json": "2.2.140",
|
"@iconify/json": "2.2.153",
|
||||||
"@nuxt/devtools": "1.0.1",
|
"@nuxthq/studio": "1.0.5",
|
||||||
"@nuxthq/studio": "1.0.3",
|
|
||||||
"@pinia-plugin-persistedstate/nuxt": "1.2.0",
|
"@pinia-plugin-persistedstate/nuxt": "1.2.0",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@types/node": "20.9.0",
|
"@types/node": "20.10.4",
|
||||||
"@vueuse/core": "10.6.0",
|
"@vueuse/core": "10.7.0",
|
||||||
"@vueuse/nuxt": "10.6.0",
|
"@vueuse/nuxt": "10.7.0",
|
||||||
"eslint": "8.53.0",
|
"eslint": "8.55.0",
|
||||||
"nuxt": "3.8.1",
|
"nuxt": "3.8.2",
|
||||||
"nuxt-icon": "0.5.0",
|
"nuxt-auth-utils": "^0.0.10",
|
||||||
"prisma": "^5.6.0",
|
"prisma": "5.7.0",
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ model Category {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
slug String
|
slug String
|
||||||
name String
|
name String
|
||||||
type CategoryType
|
|
||||||
bookmarks CategoriesOnBookMarks[]
|
|
||||||
talents CategoriesOnTalents[]
|
talents CategoriesOnTalents[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,26 +54,6 @@ model CategoriesOnTalents {
|
|||||||
@@index([categoryId])
|
@@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 {
|
model Post {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
slug String @unique
|
slug String @unique
|
||||||
@@ -84,6 +62,14 @@ model Post {
|
|||||||
likes Int @default(0)
|
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 {
|
model Form {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String
|
name String
|
||||||
@@ -91,8 +77,3 @@ model Form {
|
|||||||
content String
|
content String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CategoryType {
|
|
||||||
TALENT
|
|
||||||
BOOKMARK
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const { $trpc } = useNuxtApp()
|
const { data: announce } = await useFetch('/api/announcement')
|
||||||
const announce = await $trpc.announcement.get.query()
|
|
||||||
|
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
function getColor() {
|
function getColor() {
|
||||||
|
|||||||
@@ -1,24 +1,15 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
defineProps({
|
|
||||||
navigation: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="z-30 sticky top-0 left-0 flex justify-center w-full">
|
<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>
|
<ClientOnly>
|
||||||
<Logo />
|
<Logo />
|
||||||
<NavBar v-if="navigation" />
|
<NavBar />
|
||||||
<div class="flex gap-2">
|
<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">
|
<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 />
|
<ColorPicker />
|
||||||
<ColorModeButton />
|
<ColorModeButton />
|
||||||
</div>
|
</div>
|
||||||
<MobileNavBar v-if="navigation" />
|
<MobileNavBar />
|
||||||
</div>
|
</div>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ function isRoute(path: string) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<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
|
<UButton
|
||||||
variant="ghost"
|
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' }">
|
<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>
|
<template #header>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div>Logo</div>
|
<Logo />
|
||||||
<UButton
|
<UButton
|
||||||
size="md"
|
size="md"
|
||||||
icon="i-ic-round-close"
|
icon="i-ic-round-close"
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ const items = [
|
|||||||
to: '/talents',
|
to: '/talents',
|
||||||
icon: 'i-ph-users-bold',
|
icon: 'i-ph-users-bold',
|
||||||
}, {
|
}, {
|
||||||
label: 'Bookmarks',
|
label: 'Guestbook',
|
||||||
to: '/bookmarks',
|
to: '/guestbook',
|
||||||
icon: 'i-ph-bookmark-simple-bold',
|
icon: 'i-material-symbols-book-2-outline',
|
||||||
}],
|
}],
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<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 === '/' }">
|
<UButton to="/" size="sm" variant="ghost" color="white" :class="{ 'link-active': route.path === '/' }">
|
||||||
Home
|
Home
|
||||||
|
|||||||
@@ -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"
|
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">
|
<div class="flex items-center">
|
||||||
<Icon v-if="isLight" :name="skill.icon.light ? skill.icon.light : skill.icon" size="20" />
|
<UIcon v-if="isLight" :name="skill.icon.light ? skill.icon.light : skill.icon" size="20" dynamic />
|
||||||
<Icon v-else :name="skill.icon.dark ? skill.icon.dark : skill.icon" size="20" />
|
<UIcon v-else :name="skill.icon.dark ? skill.icon.dark : skill.icon" size="20" dynamic />
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm text-subtitle">{{ skill.name }}</span>
|
<span class="text-sm text-subtitle">{{ skill.name }}</span>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
export async function usePost(slug: string) {
|
export async function usePost(slug: string) {
|
||||||
const { $trpc } = useNuxtApp()
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: post,
|
data: post,
|
||||||
refresh: refreshPost,
|
refresh: refreshPost,
|
||||||
} = await useAsyncData(`blog:post-db:${slug}`, async () => await $trpc.post.createOrUpdate.mutate({
|
} = useFetch('/api/article', {
|
||||||
slug,
|
method: 'POST',
|
||||||
}))
|
body: {
|
||||||
|
slug,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const likes = ref(post.value!.likes)
|
const likes = ref(post.value?.likes)
|
||||||
const like = async () => {
|
const like = async () => {
|
||||||
const data = await $trpc.post.like.mutate({ slug })
|
const { data } = await useFetch('/api/like', { method: 'PUT' })
|
||||||
likes.value = data.likes
|
likes.value = data
|
||||||
}
|
}
|
||||||
|
|
||||||
const views = ref(post.value!.views)
|
const views = ref(post.value!.views)
|
||||||
const view = async () => {
|
const view = async () => {
|
||||||
const data = await $trpc.post.view.mutate({ slug })
|
const { data } = await useFetch('/api/view', { method: 'PUT' })
|
||||||
views.value = data.views
|
likes.value = data
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
export default defineNuxtRouteMiddleware(async (to) => {
|
export default defineNuxtRouteMiddleware(async (to) => {
|
||||||
const isMaintenance = ref<boolean>(true)
|
const isMaintenance = ref<boolean>(true)
|
||||||
const { $trpc } = useNuxtApp()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isMaintenance.value = await $trpc.maintenance.is.query()
|
await $fetch('/api/maintenance').then((maintenance: any) => {
|
||||||
|
isMaintenance.value = maintenance.enabled
|
||||||
|
})
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
return navigateTo('/maintenance')
|
return navigateTo('/maintenance')
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ useHead({
|
|||||||
title: 'Site under maintenance • Arthur Danjou',
|
title: 'Site under maintenance • Arthur Danjou',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { $trpc } = useNuxtApp()
|
const { data: maintenance } = await useFetch('/api/maintenance')
|
||||||
const maintenance = await $trpc.maintenance.get.query()
|
|
||||||
const format = 'DD MMMM YYYY, HH:mm'
|
const format = 'DD MMMM YYYY, HH:mm'
|
||||||
|
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
@@ -47,13 +46,13 @@ const socials = [
|
|||||||
<h1 class="text-4xl md:text-7xl font-bold">
|
<h1 class="text-4xl md:text-7xl font-bold">
|
||||||
The website is under maintenance
|
The website is under maintenance
|
||||||
</h1>
|
</h1>
|
||||||
<div v-if="maintenance">
|
<div v-if="maintenance && maintenance.maintenance">
|
||||||
<p :class="getColor" class="font-bold mb-8 text-xl">
|
<p :class="getColor" class="font-bold mb-8 text-xl">
|
||||||
{{ maintenance.reason }}
|
{{ maintenance.maintenance.reason }}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-subtitle italic">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,32 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useTalentsStore } from '~/store/talents'
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Discover new talents • Arthur Danjou',
|
title: 'Discover new talents • Arthur Danjou',
|
||||||
})
|
})
|
||||||
|
|
||||||
const categories = ref<Array<{ label: string; slug: string }>>([{ label: 'All', slug: 'all' }])
|
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 }))
|
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-6">
|
||||||
<div class="mb-2 flex items-center gap-2">
|
<div class="mb-2 flex items-center gap-2">
|
||||||
<UIcon name="i-ph-circle-wavy-question-bold" class="text-subtitle text-xl" />
|
<UIcon name="i-ph-circle-wavy-question-bold" class="text-subtitle text-xl" />
|
||||||
|
<!-- TODO: use suggestions -->
|
||||||
<h1 class="text-lg font-bold">
|
<h1 class="text-lg font-bold">
|
||||||
Want to be here ?
|
Want to be here ?
|
||||||
</h1>
|
</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 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="flex gap-2 overflow-x-scroll sm:overflow-x-hidden bg-gray-100 dark:bg-gray-800 rounded-lg p-1 relative">
|
||||||
<div
|
<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"
|
v-for="category in categories"
|
||||||
: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"
|
|
||||||
:key="category.slug"
|
: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="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) }"
|
: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">
|
<p class="w-full">
|
||||||
{{ category.name }}
|
{{ category.label }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const { data: projects } = await useProjects()
|
|||||||
class="group relative flex flex-col justify-between"
|
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">
|
<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>
|
</div>
|
||||||
<h2 class="mt-6 text-base font-semibold text-zinc-800 dark:text-zinc-100">
|
<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" />
|
<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" />
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -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,
|
|
||||||
})
|
|
||||||
@@ -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>
|
|
||||||
@@ -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',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@@ -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
|
|
||||||
@@ -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()
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@@ -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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@@ -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',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@@ -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
|
|
||||||
@@ -7,21 +7,20 @@ export const useTalentsStore = defineStore(
|
|||||||
const currentFavorite = ref<boolean>(false)
|
const currentFavorite = ref<boolean>(false)
|
||||||
|
|
||||||
const getCategory = computed(() => currentCategory)
|
const getCategory = computed(() => currentCategory)
|
||||||
const isFavorite = computed(() => currentFavorite)
|
function setCategory(newCategory: string) {
|
||||||
|
currentCategory.value = newCategory
|
||||||
function setCategory(category: string) {
|
|
||||||
currentCategory.value = category
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFavorite() {
|
const isFavorite = computed(() => currentFavorite)
|
||||||
|
function toggleFavorite() {
|
||||||
currentFavorite.value = !currentFavorite.value
|
currentFavorite.value = !currentFavorite.value
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getCategory,
|
getCategory,
|
||||||
setCategory,
|
setCategory,
|
||||||
setFavorite,
|
|
||||||
isFavorite,
|
isFavorite,
|
||||||
|
toggleFavorite,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user