mirror of
https://github.com/ArthurDanjou/website.git
synced 2026-01-26 01:40:31 +01:00
add oauth and create suggestion
This commit is contained in:
4
src/auth.d.ts
vendored
4
src/auth.d.ts
vendored
@@ -1,7 +1,9 @@
|
|||||||
declare module '#auth-utils' {
|
declare module '#auth-utils' {
|
||||||
interface UserSession {
|
interface UserSession {
|
||||||
user: {
|
user: {
|
||||||
username: string
|
email: string,
|
||||||
|
username: string,
|
||||||
|
picture: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,5 +16,6 @@ const getColor = computed(() => appConfig.ui.primary)
|
|||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
<Footer />
|
<Footer />
|
||||||
</main>
|
</main>
|
||||||
|
<UNotifications />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,31 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { providers } from '~~/types'
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Sign my guestbook • Arthur Danjou',
|
title: 'Sign my guestbook • Arthur Danjou',
|
||||||
})
|
})
|
||||||
|
|
||||||
const providers = [
|
|
||||||
{
|
|
||||||
slug: 'github',
|
|
||||||
label: 'Use Github',
|
|
||||||
icon: 'i-ph-github-logo-bold',
|
|
||||||
link: '/api/auth/github',
|
|
||||||
color: 'black',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'twitter',
|
|
||||||
label: 'Use Twitter',
|
|
||||||
icon: 'i-ph-twitter-logo-bold',
|
|
||||||
link: '/api/auth/twitter',
|
|
||||||
color: 'cyan',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'google',
|
|
||||||
label: 'Use Google',
|
|
||||||
icon: 'i-ph-google-logo-bold',
|
|
||||||
link: '/api/auth/google',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useTalentsStore } from '~/store/talents'
|
import { useTalentsStore } from '~/store/talents'
|
||||||
|
import { providers } from '~~/types'
|
||||||
|
|
||||||
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 { getCategory, setCategory, isFavorite, toggleFavorite } = useTalentsStore()
|
const { getCategory, setCategory, isFavorite, toggleFavorite } = useTalentsStore()
|
||||||
|
const { loggedIn, clear } = useUserSession()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: talents,
|
data: talents,
|
||||||
@@ -17,7 +19,7 @@ const {
|
|||||||
favorite: isFavorite,
|
favorite: isFavorite,
|
||||||
category: getCategory,
|
category: getCategory,
|
||||||
},
|
},
|
||||||
watch: [isFavorite, getCategory]
|
watch: [isFavorite, getCategory],
|
||||||
})
|
})
|
||||||
|
|
||||||
function isCategory(category: string) {
|
function isCategory(category: string) {
|
||||||
@@ -28,12 +30,38 @@ const {
|
|||||||
data: getCategories,
|
data: getCategories,
|
||||||
} = await useFetch('/api/categories', { method: 'GET' })
|
} = await useFetch('/api/categories', { method: 'GET' })
|
||||||
|
|
||||||
getCategories.value?.forEach(category => categories.value.push({ label: category.name, slug: category.slug }))
|
getCategories.value?.forEach((category: any) => categories.value.push({ label: category.name, slug: category.slug }))
|
||||||
|
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
function getColor() {
|
function getColor() {
|
||||||
return `text-${appConfig.ui.primary}-500`
|
return `text-${appConfig.ui.primary}-500`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const suggestContent = ref<string>('')
|
||||||
|
async function suggest() {
|
||||||
|
if (suggestContent.value.trim().length < 4)
|
||||||
|
return
|
||||||
|
|
||||||
|
await $fetch('/api/suggestion', {
|
||||||
|
method: 'post',
|
||||||
|
body: {
|
||||||
|
content: suggestContent.value,
|
||||||
|
},
|
||||||
|
}).then((suggestion) => {
|
||||||
|
toast.add({
|
||||||
|
title: `Your suggestion for '${suggestion.content}'' has been successfully added`,
|
||||||
|
icon: 'i-material-symbols-check-circle-outline-rounded',
|
||||||
|
timeout: 4000,
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
toast.add({
|
||||||
|
title: 'You already have suggested someone',
|
||||||
|
color: 'red',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
suggestContent.value = ''
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -59,16 +87,49 @@ function getColor() {
|
|||||||
Are you a web talent? Do you want to promote your project? Do you want to launch your career or gain visibility?
|
Are you a web talent? Do you want to promote your project? Do you want to launch your career or gain visibility?
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<NuxtLink href="mailto:arthurdanjou@outlook.fr?subject=Join your talents' list">
|
<div v-if="loggedIn" class="flex items-center justify-between gap-4">
|
||||||
<UButton label="Join the talent's list" color="primary" />
|
<div class="w-full relative flex items-center">
|
||||||
</NuxtLink>
|
<input
|
||||||
|
v-model="suggestContent"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
min="4"
|
||||||
|
class="w-full rounded-lg p-2 h-10 focus:outline-none bg-gray-100 dark:bg-stone-800"
|
||||||
|
placeholder="Suggest one name"
|
||||||
|
>
|
||||||
|
<UButton
|
||||||
|
class="absolute right-1 top-1 text-gray-900 dark:text-white rounded-md"
|
||||||
|
label="Send"
|
||||||
|
:disabled="suggestContent.trim().length < 4"
|
||||||
|
variant="soft"
|
||||||
|
@click.prevent="suggest()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<UButton
|
||||||
|
@click.prevent="clear()"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex gap-2">
|
||||||
|
<UButton
|
||||||
|
v-for="provider in providers"
|
||||||
|
:key="provider.slug"
|
||||||
|
:label="provider.label"
|
||||||
|
:color="provider.color"
|
||||||
|
variant="solid"
|
||||||
|
:icon="provider.icon"
|
||||||
|
:to="provider.link"
|
||||||
|
external
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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
|
||||||
v-for="category in categories"
|
v-for="category in categories"
|
||||||
: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 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="setCategory(category.slug)"
|
@click.prevent="setCategory(category.slug)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
const SuggestionValidator = z.object({
|
const SuggestionValidator = z.object({
|
||||||
author: z.string().trim(),
|
|
||||||
content: z.string(),
|
content: z.string(),
|
||||||
}).parse
|
}).parse
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const { author, content } = await getValidatedQuery(event, SuggestionValidator)
|
const { content } = await readValidatedBody(event, SuggestionValidator)
|
||||||
const { user } = await requireUserSession(event)
|
const { user } = await requireUserSession(event)
|
||||||
|
return await usePrisma().suggestion.upsert({
|
||||||
|
where: {
|
||||||
|
author: user.email,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
content,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
author: user.email,
|
||||||
|
content,
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
return await prisma.talent.findMany({
|
return await prisma.talent.findMany({
|
||||||
where: whereClause,
|
where: whereClause,
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: 'desc',
|
name: 'asc',
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
categories: {
|
categories: {
|
||||||
|
|||||||
18
src/server/routes/auth/github.get.ts
Normal file
18
src/server/routes/auth/github.get.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export default oauth.githubEventHandler({
|
||||||
|
config: {
|
||||||
|
emailRequired: true,
|
||||||
|
},
|
||||||
|
async onSuccess(event: any, { user }: any) {
|
||||||
|
await setUserSession(event, {
|
||||||
|
user: {
|
||||||
|
email: user.email,
|
||||||
|
picture: user.avatar_url,
|
||||||
|
username: String(user.name).trim(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return sendRedirect(event, '/')
|
||||||
|
},
|
||||||
|
onError(error: any) {
|
||||||
|
console.error('GitHub OAuth error:', error)
|
||||||
|
},
|
||||||
|
})
|
||||||
12
src/server/routes/auth/google.get.ts
Normal file
12
src/server/routes/auth/google.get.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export default oauth.githubEventHandler({
|
||||||
|
async onSuccess(event: any, { user }: any) {
|
||||||
|
await setUserSession(event, {
|
||||||
|
user: {
|
||||||
|
email: user.email,
|
||||||
|
picture: user.photoUrl,
|
||||||
|
username: String(user.displayName).trim(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return sendRedirect(event, '/')
|
||||||
|
},
|
||||||
|
})
|
||||||
25
types.ts
25
types.ts
@@ -1,3 +1,4 @@
|
|||||||
|
import exp from 'node:constants'
|
||||||
import type { MarkdownParsedContent, ParsedContent } from '@nuxt/content/dist/runtime/types'
|
import type { MarkdownParsedContent, ParsedContent } from '@nuxt/content/dist/runtime/types'
|
||||||
|
|
||||||
export enum ColorsTheme {
|
export enum ColorsTheme {
|
||||||
@@ -69,3 +70,27 @@ export interface Skill extends ParsedContent {
|
|||||||
}
|
}
|
||||||
color: string
|
color: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const providers = [
|
||||||
|
{
|
||||||
|
slug: 'github',
|
||||||
|
label: 'Use Github',
|
||||||
|
icon: 'i-ph-github-logo-bold',
|
||||||
|
link: '/auth/github',
|
||||||
|
color: 'black',
|
||||||
|
},
|
||||||
|
/* {
|
||||||
|
slug: 'twitter',
|
||||||
|
label: 'Use Twitter',
|
||||||
|
icon: 'i-ph-twitter-logo-bold',
|
||||||
|
link: '/auth/twitter',
|
||||||
|
color: 'cyan',
|
||||||
|
}, */
|
||||||
|
{
|
||||||
|
slug: 'google',
|
||||||
|
label: 'Use Google',
|
||||||
|
icon: 'i-ph-google-logo-bold',
|
||||||
|
link: '/auth/google',
|
||||||
|
color: 'red',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user