first commit
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
.env
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
.vercel
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 120
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"workbench.colorCustomizations": {
|
||||||
|
"activityBar.background": "#2F2F07",
|
||||||
|
"titleBar.activeBackground": "#41420A",
|
||||||
|
"titleBar.activeForeground": "#FBFBE7"
|
||||||
|
}
|
||||||
|
}
|
||||||
49
README.md
|
|
@ -1,3 +1,48 @@
|
||||||
# LaLiga-FrontEnd
|
# Para mi yo futuro o a quien le pueda servir
|
||||||
|
### Este template está creado para empezar con unos mínimos que puede ahorrarme tiempo a la hora de iniciar un proyecto personal.
|
||||||
|
`**No te enfoques en el estilo actual, intento hacerlo de lo mas neutral posible **`
|
||||||
|
|
||||||
La Liga Front End Repo
|
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||||
|
|
||||||
|
## Para empezar, aunque no hace falta ni decirlo, pero bueno por si acaso.
|
||||||
|
|
||||||
|
First, run the development server: `(y a darle sin miedo)`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
# ¿Que me voy a encontrar?
|
||||||
|
Cuando abras el proyecto, tendras un layout, con su navbar y su footer con un children para que puedas meter todo lo que quieras.
|
||||||
|
```js
|
||||||
|
<div>
|
||||||
|
<Navbar />
|
||||||
|
<div>{children}</div>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
Entre otras cosas, que me parecen interesantes para tener ya preparadas, como es un `cambio de lenguaje`, tiene instalado **i18next**, configurado con `Inglés y español`, con la libertad para añadir los idiomas necesarios, las carpetas de locales, donde ya se irán ***`¡Ojo!`*** creando automáticamente los archivos json de las traducciones.
|
||||||
|
|
||||||
|
===> Si dudas y no la conoces [i18next](https://www.i18next.com/) <==
|
||||||
|
### Navbar responsivo
|
||||||
|
Con su boton hamburguesa, y sus iconos, para cerrar y desplegar menu.
|
||||||
|
|
||||||
|
### Componentes de ejemplos
|
||||||
|
Para llegar y desembarcar. Modificarlo al gusto y listo.
|
||||||
|
|
||||||
|
# Librerías que he instalado.
|
||||||
|
`Libertad para borrar la que no se quiera `
|
||||||
|
- **i18next** => Ya dicho anteriormente, para las traducciones de idioma.
|
||||||
|
- **react-iconst** => Iconos. Ya hay alguno en el navbar.
|
||||||
|
- **react-loading** => Para animaciones, está ya en un componente que tiene de todo, y una de las opciones usa esta librería.
|
||||||
|
- **sass** => Ni falta hace que decirlo, pero ahí esta para darle alegría a mi estilo con tailwindcss.
|
||||||
|
- **TailwindCSS** => Otro tanto, la clave para volar con los estilos.
|
||||||
|
- **Typescript** => Bendiciones para tener controlado todo.
|
||||||
|
|
||||||
|
Y poco más. Con esto empezar será más fácil.
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/** @type {import('next-i18next').UserConfig} */
|
||||||
|
module.exports = {
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: 'es',
|
||||||
|
locales: ['es', 'en']
|
||||||
|
},
|
||||||
|
serializeConfig: false,
|
||||||
|
saveMissing: true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const { i18n } = require('./next-i18next.config')
|
||||||
|
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: false,
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: 'es',
|
||||||
|
locales: ['es', 'en']
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
domains: ['lh3.googleusercontent.com', 'avatars.githubusercontent.com']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"name": "next-tailwind-boilerplate",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@auth/prisma-adapter": "^1.0.12",
|
||||||
|
"@headlessui/react": "^1.7.18",
|
||||||
|
"@hookform/resolvers": "^3.3.3",
|
||||||
|
"@prisma/client": "^5.8.1",
|
||||||
|
"@types/node": "20.4.1",
|
||||||
|
"@types/react": "18.2.14",
|
||||||
|
"@types/react-dom": "18.2.6",
|
||||||
|
"autoprefixer": "10.4.14",
|
||||||
|
"eslint": "8.44.0",
|
||||||
|
"eslint-config-next": "13.4.9",
|
||||||
|
"i18next": "^23.2.10",
|
||||||
|
"next": "13.4.9",
|
||||||
|
"next-auth": "^4.24.5",
|
||||||
|
"next-i18next": "^14.0.0",
|
||||||
|
"postcss": "8.4.25",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"react-hook-form": "^7.49.2",
|
||||||
|
"react-i18next": "^13.0.2",
|
||||||
|
"react-icons": "^4.11.0",
|
||||||
|
"react-loading": "^2.0.3",
|
||||||
|
"sass": "^1.63.6",
|
||||||
|
"swr": "^2.2.4",
|
||||||
|
"tailwindcss": "3.3.2",
|
||||||
|
"typescript": "5.1.6",
|
||||||
|
"yup": "^1.3.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prisma": "^5.8.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
export const getAllComments = async (postSlug?: string) => {
|
||||||
|
try {
|
||||||
|
const comments = await prisma.comment.findMany({
|
||||||
|
orderBy: [{ createdAt: 'desc' }],
|
||||||
|
where: {
|
||||||
|
...(postSlug && { postSlug: postSlug })
|
||||||
|
},
|
||||||
|
include: { user: true }
|
||||||
|
})
|
||||||
|
return comments
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching comments:', error)
|
||||||
|
throw new Error('Error fetching comments')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createComment = async (body: any, userEmail: any, session: any) => {
|
||||||
|
if (!session) {
|
||||||
|
throw new Error('Not Authenticated')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const comment = await prisma.comment.create({
|
||||||
|
data: { description: body.description, postSlug: body.postSlug, userEmail: userEmail }
|
||||||
|
})
|
||||||
|
return comment
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating comment:', error)
|
||||||
|
throw new Error('Error creating comment')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteComment = async (id: string) => {
|
||||||
|
await prisma.comment.delete({
|
||||||
|
where: {
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
export const addFavorite = async (postId: any, userEmail: any, session: any) => {
|
||||||
|
try {
|
||||||
|
if (!session) {
|
||||||
|
throw new Error('Not Authenticated')
|
||||||
|
}
|
||||||
|
const existingFavorite = await prisma.favorite.findUnique({
|
||||||
|
where: { postId_userEmail: { postId, userEmail } }
|
||||||
|
})
|
||||||
|
if (existingFavorite) {
|
||||||
|
throw new Error('El usuario ya marcó este post como favorito.')
|
||||||
|
}
|
||||||
|
await prisma.favorite.create({
|
||||||
|
data: {
|
||||||
|
postId,
|
||||||
|
userEmail
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const updatedFavorites = await prisma.post
|
||||||
|
.findUnique({
|
||||||
|
where: { id: postId }
|
||||||
|
})
|
||||||
|
.Favorite()
|
||||||
|
|
||||||
|
return updatedFavorites
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al añadir a favoritos:', error)
|
||||||
|
throw new Error('Error al añadir a favoritos')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteFavorite = async (postId: any, userEmail: string, session: any) => {
|
||||||
|
try {
|
||||||
|
// Verificar si el usuario ha marcado el post como favorito
|
||||||
|
const existingFavorite = await prisma.favorite.findUnique({
|
||||||
|
where: { postId_userEmail: { postId, userEmail } }
|
||||||
|
})
|
||||||
|
if (!existingFavorite) {
|
||||||
|
throw new Error('El usuario no ha marcado este post como favorito.')
|
||||||
|
}
|
||||||
|
// Eliminar de favoritos
|
||||||
|
await prisma.favorite.delete({
|
||||||
|
where: { postId_userEmail: { postId, userEmail } }
|
||||||
|
})
|
||||||
|
|
||||||
|
const updatedFavorites = await prisma.post
|
||||||
|
.findUnique({
|
||||||
|
where: { id: postId }
|
||||||
|
})
|
||||||
|
.Favorite()
|
||||||
|
|
||||||
|
return updatedFavorites
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al quitar de favoritos:', error)
|
||||||
|
throw new Error('Error al quitar de favoritos')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
export const addLike = async (postId: any, userEmail: any, session: any) => {
|
||||||
|
try {
|
||||||
|
if (!session) {
|
||||||
|
throw new Error('Not Authenticated')
|
||||||
|
}
|
||||||
|
const existingLike = await prisma.like.findUnique({
|
||||||
|
where: { postId_userEmail: { postId, userEmail } }
|
||||||
|
})
|
||||||
|
if (existingLike) {
|
||||||
|
throw new Error('El usuario ya dio like a este post.')
|
||||||
|
}
|
||||||
|
await prisma.like.create({
|
||||||
|
data: {
|
||||||
|
postId,
|
||||||
|
userEmail
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const updatedLikes = await prisma.post
|
||||||
|
.findUnique({
|
||||||
|
where: { id: postId }
|
||||||
|
})
|
||||||
|
.Like()
|
||||||
|
|
||||||
|
return updatedLikes
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al añadir el like:', error)
|
||||||
|
throw new Error('Error al añadir el like')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteLike = async (postId: any, userEmail: string, session: any) => {
|
||||||
|
try {
|
||||||
|
// Verificar si el usuario ha dado like al post
|
||||||
|
const existingLike = await prisma.like.findUnique({
|
||||||
|
where: { postId_userEmail: { postId, userEmail } }
|
||||||
|
})
|
||||||
|
if (!existingLike) {
|
||||||
|
throw new Error('El usuario no ha dado like a este post.')
|
||||||
|
}
|
||||||
|
// Eliminar el like
|
||||||
|
await prisma.like.delete({
|
||||||
|
where: { postId_userEmail: { postId, userEmail } }
|
||||||
|
})
|
||||||
|
|
||||||
|
const updatedLikes = await prisma.post
|
||||||
|
.findUnique({
|
||||||
|
where: { id: postId }
|
||||||
|
})
|
||||||
|
.Like()
|
||||||
|
|
||||||
|
return updatedLikes
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al quitar el like:', error)
|
||||||
|
throw new Error('Error al quitar el like')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
export const deletePost = async (slug: string) => {
|
||||||
|
try {
|
||||||
|
const post = await prisma.post.findUnique({
|
||||||
|
where: {
|
||||||
|
slug: slug
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
comments: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!post) {
|
||||||
|
throw new Error('Post not found')
|
||||||
|
}
|
||||||
|
await prisma.like.deleteMany({
|
||||||
|
where: {
|
||||||
|
postId: post.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await prisma.comment.deleteMany({
|
||||||
|
where: {
|
||||||
|
postSlug: slug
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await prisma.post.delete({
|
||||||
|
where: {
|
||||||
|
slug: slug
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error, 'Error deleting post')
|
||||||
|
throw new Error('Error deleting post')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const editPost = async (slug: string, newData: any) => {
|
||||||
|
try {
|
||||||
|
// Buscar el post que se va a editar
|
||||||
|
const existingPost = await prisma.post.findUnique({
|
||||||
|
where: {
|
||||||
|
slug: slug
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Verificar si el post existe
|
||||||
|
if (!existingPost) {
|
||||||
|
throw new Error('Post not found')
|
||||||
|
}
|
||||||
|
// Actualizar el post con los nuevos datos
|
||||||
|
const updatedPost = await prisma.post.update({
|
||||||
|
where: {
|
||||||
|
slug: slug
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...newData
|
||||||
|
// Tags: {
|
||||||
|
// set: newData.Tags.map((tag: any) => ({ id: tag.id }))
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return updatedPost
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error, 'Error editing post')
|
||||||
|
throw new Error('Error editing post')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "mongodb"
|
||||||
|
url = env("MONGODB_URI")
|
||||||
|
}
|
||||||
|
|
||||||
|
// datasource db {
|
||||||
|
// provider = "mongodb"
|
||||||
|
// url = env("DATABASE_URL")
|
||||||
|
// }
|
||||||
|
|
||||||
|
model Account {
|
||||||
|
id String @id @default(cuid()) @map("_id")
|
||||||
|
userId String
|
||||||
|
type String
|
||||||
|
provider String
|
||||||
|
providerAccountId String
|
||||||
|
refresh_token String?
|
||||||
|
access_token String?
|
||||||
|
expires_at Int?
|
||||||
|
token_type String?
|
||||||
|
scope String?
|
||||||
|
id_token String?
|
||||||
|
session_state String?
|
||||||
|
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([provider, providerAccountId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id @default(cuid()) @map("_id")
|
||||||
|
sessionToken String @unique
|
||||||
|
userId String
|
||||||
|
expires DateTime
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid()) @map("_id")
|
||||||
|
name String?
|
||||||
|
email String @unique
|
||||||
|
emailVerified DateTime?
|
||||||
|
image String?
|
||||||
|
isAdmin Boolean @unique @default(false)
|
||||||
|
accounts Account[]
|
||||||
|
sessions Session[]
|
||||||
|
Post Post[]
|
||||||
|
Like Like[]
|
||||||
|
Comment Comment[]
|
||||||
|
Favorite Favorite[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model VerificationToken {
|
||||||
|
identifier String @id @map("_id")
|
||||||
|
token String @unique
|
||||||
|
expires DateTime
|
||||||
|
|
||||||
|
@@unique([identifier, token])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Category {
|
||||||
|
id String @id @default(cuid()) @map("_id")
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
img String?
|
||||||
|
color String?
|
||||||
|
Posts Post[]
|
||||||
|
Comment Comment[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// model Tag {
|
||||||
|
// id String @id @default(cuid()) @map("_id")
|
||||||
|
// name String @unique
|
||||||
|
// slug String @unique
|
||||||
|
// color String?
|
||||||
|
// // Posts PostTag[]
|
||||||
|
// Post Post? @relation(fields: [postId], references: [id])
|
||||||
|
// postId String?
|
||||||
|
// }
|
||||||
|
|
||||||
|
// model PostTag {
|
||||||
|
// id String @id @default(cuid()) @map("_id")
|
||||||
|
// postId String
|
||||||
|
// tagId String
|
||||||
|
// post Post @relation(fields: [postId], references: [id])
|
||||||
|
// tag Tag @relation(fields: [tagId], references: [id])
|
||||||
|
|
||||||
|
// @@unique([postId, tagId])
|
||||||
|
// }
|
||||||
|
|
||||||
|
model Post {
|
||||||
|
id String @id @default(cuid()) @map("_id")
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
description String
|
||||||
|
img String?
|
||||||
|
views Int @default(0)
|
||||||
|
catSlug String?
|
||||||
|
Category Category? @relation(fields: [catSlug], references: [slug])
|
||||||
|
userEmail String
|
||||||
|
user User @relation(fields: [userEmail], references: [email], onDelete: Cascade)
|
||||||
|
Like Like[]
|
||||||
|
comments Comment[]
|
||||||
|
url String?
|
||||||
|
twitterShareCount Int @default(0)
|
||||||
|
whatsappShareCount Int @default(0)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
Favorite Favorite[]
|
||||||
|
// PostTag PostTag[]
|
||||||
|
// Tags Tag[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Like {
|
||||||
|
id String @id @default(cuid()) @map("_id")
|
||||||
|
postId String
|
||||||
|
post Post @relation(fields: [postId], references: [id])
|
||||||
|
userEmail String
|
||||||
|
user User @relation(fields: [userEmail], references: [email], onDelete: Cascade)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@unique([postId, userEmail])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Favorite {
|
||||||
|
id String @id @default(cuid()) @map("_id")
|
||||||
|
postId String
|
||||||
|
post Post @relation(fields: [postId], references: [id])
|
||||||
|
userEmail String
|
||||||
|
user User @relation(fields: [userEmail], references: [email], onDelete: Cascade)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@unique([postId, userEmail])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Comment {
|
||||||
|
id String @id @default(cuid()) @map("_id")
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
description String
|
||||||
|
userEmail String
|
||||||
|
user User @relation(fields: [userEmail], references: [email], onDelete: Cascade)
|
||||||
|
postSlug String
|
||||||
|
post Post @relation(fields: [postSlug], references: [slug])
|
||||||
|
|
||||||
|
Category Category? @relation(fields: [categoryId], references: [id])
|
||||||
|
categoryId String?
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
export const getAllStats = async () => {
|
||||||
|
try {
|
||||||
|
const totalPosts = await prisma.post.count()
|
||||||
|
const totalUsers = await prisma.user.count()
|
||||||
|
const totalViews = await prisma.post.aggregate({ _sum: { views: true } })
|
||||||
|
const totalShares = await prisma.post.aggregate({
|
||||||
|
_sum: { twitterShareCount: true, whatsappShareCount: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalPosts,
|
||||||
|
totalUsers,
|
||||||
|
totalViews: totalViews._sum || 0,
|
||||||
|
totalShares: totalShares._sum || 0
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching statistics:', error)
|
||||||
|
throw new Error('Error fetching statistics')
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
// import { PrismaClient } from '@prisma/client'
|
||||||
|
// const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
// //CREAR NUEVO TAG
|
||||||
|
|
||||||
|
// export const createTag = async (tagData: { name: string; slug: string; color?: string }) => {
|
||||||
|
// try {
|
||||||
|
// const normalizedTagSlug = tagData.slug.toLowerCase()
|
||||||
|
// const existingTagMinus = await prisma.tag.findUnique({
|
||||||
|
// where: { slug: normalizedTagSlug }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if (existingTagMinus) {
|
||||||
|
// console.error(`Error al crear el Tag: El SLUG "${tagData.slug}" ya está en uso.`)
|
||||||
|
// throw new Error(`Error al crear el Tag: El SLUG "${tagData.slug}" ya está en uso.`)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const newTag = await prisma.tag.create({
|
||||||
|
// data: { ...tagData, slug: normalizedTagSlug }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// return newTag
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('Error al crear el Tag:', error)
|
||||||
|
// throw new Error('Error al crear el Tag')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // OBTENER TODOS LOS TAGS
|
||||||
|
|
||||||
|
// export const getAllTags = async () => {
|
||||||
|
// try {
|
||||||
|
// const tags = await prisma.tag.findMany()
|
||||||
|
// return tags
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('Error al obtener todos los Tags:', error)
|
||||||
|
// throw new Error('Error al obtener todos los Tags')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // OBTENER UN TAG POR SU ID
|
||||||
|
|
||||||
|
// export const getTagById = async (tagId: string) => {
|
||||||
|
// try {
|
||||||
|
// const tag = await prisma.tag.findUnique({
|
||||||
|
// where: { id: tagId }
|
||||||
|
// })
|
||||||
|
// return tag
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('Error al obtener el Tag por ID:', error)
|
||||||
|
// throw new Error('Error al obtener el Tag por ID')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //ACTUALIZAR UN TAG
|
||||||
|
|
||||||
|
// export const updateTag = async (tagData: { id: string; name?: string; slug?: string; color?: string }) => {
|
||||||
|
// try {
|
||||||
|
// const updatedTag = await prisma.tag.update({
|
||||||
|
// where: { id: tagData.id },
|
||||||
|
// data: { name: tagData.name, slug: tagData.slug, color: tagData.color }
|
||||||
|
// })
|
||||||
|
// return updatedTag
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('Error al actualizar el Tag:', error)
|
||||||
|
// throw new Error('Error al actualizar el Tag')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // ELIMINAR UN TAG
|
||||||
|
|
||||||
|
// export const deleteTag = async (tagId: string) => {
|
||||||
|
// try {
|
||||||
|
// const deletedTag = await prisma.tag.delete({
|
||||||
|
// where: { id: tagId }
|
||||||
|
// })
|
||||||
|
// return deletedTag
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('Error al eliminar el Tag:', error)
|
||||||
|
// throw new Error('Error al eliminar el Tag')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
export const getAllUsers = async () => {
|
||||||
|
try {
|
||||||
|
const users = await prisma.user.findMany()
|
||||||
|
return users
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching users:', error)
|
||||||
|
throw new Error('Error fetching users')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getUserByEmail = async (email: string) => {
|
||||||
|
try {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
email: email
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Like: true,
|
||||||
|
Favorite: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return user
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user by ID:', error)
|
||||||
|
throw new Error('Error fetching user by ID')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteUserByEmail = async (email: string) => {
|
||||||
|
try {
|
||||||
|
const userDeleted = await prisma.user.delete({
|
||||||
|
where: {
|
||||||
|
email: email
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Like: true,
|
||||||
|
Comment: true,
|
||||||
|
sessions: true,
|
||||||
|
accounts: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return userDeleted
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error deleting user with email ${email}:`, error)
|
||||||
|
throw new Error(`Unable to delete user with email ${email}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateUserByEmail = async (email: string, newData: any) => {
|
||||||
|
try {
|
||||||
|
const { name } = newData
|
||||||
|
if (name) {
|
||||||
|
const existingUserWithSameName = await prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
name: name,
|
||||||
|
email: { not: email }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (existingUserWithSameName) {
|
||||||
|
return { success: false, status: 409, error: `Name ${name} is already in use by another user` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Verificar si el usuario existe
|
||||||
|
const existingUser = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
email: email
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!existingUser) {
|
||||||
|
return { status: 404, error: `User with email ${email} not found` }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar el usuario con los nuevos datos
|
||||||
|
const updatedUser = await prisma.user.update({
|
||||||
|
where: {
|
||||||
|
email: email
|
||||||
|
},
|
||||||
|
data: newData
|
||||||
|
})
|
||||||
|
|
||||||
|
return updatedUser
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error updating user with email ${email}:`, error)
|
||||||
|
return { status: 500, error: `Unable to update user with email ${email}` }
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 32 32" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
.st1{fill:#3A559F;}
|
||||||
|
.st2{fill:#F4F4F4;}
|
||||||
|
.st3{fill:#FF0084;}
|
||||||
|
.st4{fill:#0063DB;}
|
||||||
|
.st5{fill:#00ACED;}
|
||||||
|
.st6{fill:#FFEC06;}
|
||||||
|
.st7{fill:#FF0000;}
|
||||||
|
.st8{fill:#25D366;}
|
||||||
|
.st9{fill:#0088FF;}
|
||||||
|
.st10{fill:#314358;}
|
||||||
|
.st11{fill:#EE6996;}
|
||||||
|
.st12{fill:#01AEF3;}
|
||||||
|
.st13{fill:#FFFEFF;}
|
||||||
|
.st14{fill:#F06A35;}
|
||||||
|
.st15{fill:#00ADEF;}
|
||||||
|
.st16{fill:#1769FF;}
|
||||||
|
.st17{fill:#1AB7EA;}
|
||||||
|
.st18{fill:#6001D1;}
|
||||||
|
.st19{fill:#E41214;}
|
||||||
|
.st20{fill:#05CE78;}
|
||||||
|
.st21{fill:#7B519C;}
|
||||||
|
.st22{fill:#FF4500;}
|
||||||
|
.st23{fill:#00F076;}
|
||||||
|
.st24{fill:#FFC900;}
|
||||||
|
.st25{fill:#00D6FF;}
|
||||||
|
.st26{fill:#FF3A44;}
|
||||||
|
.st27{fill:#FF6A36;}
|
||||||
|
.st28{fill:#0061FE;}
|
||||||
|
.st29{fill:#F7981C;}
|
||||||
|
.st30{fill:#EE1B22;}
|
||||||
|
.st31{fill:#EF3561;}
|
||||||
|
.st32{fill:none;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;}
|
||||||
|
.st33{fill:#0097D3;}
|
||||||
|
.st34{fill:#01308A;}
|
||||||
|
.st35{fill:#019CDE;}
|
||||||
|
.st36{fill:#FFD049;}
|
||||||
|
.st37{fill:#16A05D;}
|
||||||
|
.st38{fill:#4486F4;}
|
||||||
|
.st39{fill:none;}
|
||||||
|
.st40{fill:#34A853;}
|
||||||
|
.st41{fill:#4285F4;}
|
||||||
|
.st42{fill:#FBBC05;}
|
||||||
|
.st43{fill:#EA4335;}
|
||||||
|
</style>
|
||||||
|
<path class="st8" d="M17,0C8.7,0,2,6.7,2,15c0,3.4,1.1,6.6,3.2,9.2l-2.1,6.4c-0.1,0.4,0,0.8,0.3,1.1C3.5,31.9,3.8,32,4,32
|
||||||
|
c0.1,0,0.3,0,0.4-0.1l6.9-3.1C13.1,29.6,15,30,17,30c8.3,0,15-6.7,15-15S25.3,0,17,0z"/>
|
||||||
|
<path class="st0" d="M25.7,20.5c-0.4,1.2-1.9,2.2-3.2,2.4C22.2,23,21.9,23,21.5,23c-0.8,0-2-0.2-4.1-1.1c-2.4-1-4.8-3.1-6.7-5.8
|
||||||
|
L10.7,16C10.1,15.1,9,13.4,9,11.6c0-2.2,1.1-3.3,1.5-3.8c0.5-0.5,1.2-0.8,2-0.8c0.2,0,0.3,0,0.5,0c0.7,0,1.2,0.2,1.7,1.2l0.4,0.8
|
||||||
|
c0.3,0.8,0.7,1.7,0.8,1.8c0.3,0.6,0.3,1.1,0,1.6c-0.1,0.3-0.3,0.5-0.5,0.7c-0.1,0.2-0.2,0.3-0.3,0.3c-0.1,0.1-0.1,0.1-0.2,0.2
|
||||||
|
c0.3,0.5,0.9,1.4,1.7,2.1c1.2,1.1,2.1,1.4,2.6,1.6l0,0c0.2-0.2,0.4-0.6,0.7-0.9l0.1-0.2c0.5-0.7,1.3-0.9,2.1-0.6
|
||||||
|
c0.4,0.2,2.6,1.2,2.6,1.2l0.2,0.1c0.3,0.2,0.7,0.3,0.9,0.7C26.2,18.5,25.9,19.8,25.7,20.5z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||||
|
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 507.425 507.425" xml:space="preserve" fill="#000000">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<g id="SVGRepo_iconCarrier"> <path style="fill:#DF5C4E;" d="M329.312,129.112l13.6,22l150.8,242.4c22.4,36,6,65.2-36.8,65.2h-406.4c-42.4,0-59.2-29.6-36.8-65.6 l198.8-320.8c22.4-36,58.8-36,81.2,0L329.312,129.112z"/> <g> <path style="fill:#F4EFEF;" d="M253.712,343.512c-10.8,0-20-8.8-20-20v-143.2c0-10.8,9.2-20,20-20s20,8.8,20,20v143.2 C273.712,334.312,264.512,343.512,253.712,343.512z"/> <path style="fill:#F4EFEF;" d="M253.312,407.112c-5.2,0-10.4-2-14-6c-3.6-3.6-6-8.8-6-14s2-10.4,6-14c3.6-3.6,8.8-6,14-6 s10.4,2,14,6c3.6,3.6,6,8.8,6,14s-2,10.4-6,14C263.712,404.712,258.512,407.112,253.312,407.112z"/> </g> <path d="M456.912,465.512h-406.4c-22,0-38.4-7.6-46-21.6s-5.6-32.8,6-51.2l198.8-321.6c11.6-18.8,27.2-29.2,44.4-29.2l0,0 c16.8,0,32.4,10,43.6,28.4l35.2,56.4l0,0l13.6,22l150.8,243.6c11.6,18.4,13.6,37.2,6,51.2 C495.312,457.912,478.912,465.512,456.912,465.512z M253.312,49.912L253.312,49.912c-14,0-27.2,8.8-37.6,25.2l-198.8,321.6 c-10,16-12,31.6-5.6,43.2s20.4,17.6,39.2,17.6h406.4c18.8,0,32.8-6.4,39.2-17.6c6.4-11.2,4.4-27.2-5.6-43.2l-150.8-243.6l-13.6-22 l-35.2-56.4C280.512,58.712,267.312,49.912,253.312,49.912z"/> <path d="M249.712,347.512c-13.2,0-24-10.8-24-24v-143.2c0-13.2,10.8-24,24-24s24,10.8,24,24v143.2 C273.712,336.712,262.912,347.512,249.712,347.512z M249.712,164.312c-8.8,0-16,7.2-16,16v143.2c0,8.8,7.2,16,16,16s16-7.2,16-16 v-143.2C265.712,171.512,258.512,164.312,249.712,164.312z"/> <path d="M249.712,411.112L249.712,411.112c-6.4,0-12.4-2.4-16.8-6.8c-4.4-4.4-6.8-10.8-6.8-16.8c0-6.4,2.4-12.4,6.8-16.8 c4.4-4.4,10.8-7.2,16.8-7.2c6.4,0,12.4,2.4,16.8,7.2c4.4,4.4,7.2,10.4,7.2,16.8s-2.4,12.4-7.2,16.8 C262.112,408.312,256.112,411.112,249.712,411.112z M249.712,371.112c-4,0-8.4,1.6-11.2,4.8c-2.8,2.8-4.8,7.2-4.8,11.2 c0,4.4,1.6,8.4,4.8,11.2c2.8,2.8,7.2,4.8,11.2,4.8s8.4-1.6,11.2-4.8c2.8-2.8,4.8-7.2,4.8-11.2s-1.6-8.4-4.8-11.2 C258.112,372.712,253.712,371.112,249.712,371.112z"/> </g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 366.34 366.34" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#00214e;}.cls-2{fill:#f2a196;}.cls-3{fill:#e88870;}.cls-4{fill:#845161;}.cls-5,.cls-6{fill:none;stroke-miterlimit:10;}.cls-5{stroke:#00214e;}.cls-6{stroke:#f2a196;}</style></defs><title>Artboards_Diversity_Avatars_by_Netguru</title><path class="cls-1" d="M256.93,135.87c-2.42,7.2-2.81,17-7.06,23.38-9.35,14-27.49,16.4-42.25,21.44a11.74,11.74,0,0,0-2.82.91l-5.22,1.65q-7.53,2.37-15,4.8c-6.92,2.25-16.43,3.63-21.41,9.31a109.81,109.81,0,0,0-8,10.33c-.48.71-1.07,1.49-1.92,1.56-.54,0-1-.71-1.45-1-8.88-4.78-15.41-11.34-20.88-19.66a126,126,0,0,1-19-86.17c.06-.44.12-.87.19-1.31a51.6,51.6,0,0,1,12.78-26.06A86.79,86.79,0,0,1,148.39,57a148.21,148.21,0,0,1,16.18-7.35A110,110,0,0,1,202.25,42,94.36,94.36,0,0,1,238,49.07c15.62,6.25,27.73,17.92,28.74,35.58C267.74,102.06,262.46,119.41,256.93,135.87Z"/><circle class="cls-2" cx="134.83" cy="162.86" r="17"/><circle class="cls-3" cx="140.68" cy="161.86" r="16"/><path class="cls-2" d="M296.41,286a184.56,184.56,0,0,1-226.48-1l48.66-22.81a46.83,46.83,0,0,0,6.65-3.82l1.11-.78.78-.6a46.35,46.35,0,0,0,12.78-15.09c4-7.55,5.32-15.89,5.38-24.39,0-2.87-.06-5.74-.15-8.61s-.19-5.7-.22-8.56q-.06-4.76-.1-9.51l1.84.95.14.07,5.2,2.69,2.41.41,27.88,4.74,11.05,1.88L213.41,205l.94,32,.39,13.3.07,2.24v.33l12.1,4.92.75.31Z"/><path class="cls-3" d="M145.14,202.68,178,233.5a51.66,51.66,0,0,0,36.77,12.79l-.39-13.3-.94-32-20.07-3.42c-2.74,1.24-5.48,2.48-8.22,3.56-8.2,3.23-17.47,2-25.42-1.36a36.93,36.93,0,0,1-14.87-12Z"/><path class="cls-4" d="M296.41,286a184.56,184.56,0,0,1-226.48-1l48.66-22.81a46.83,46.83,0,0,0,6.65-3.82l1.11-.78c24.36,16.61,56.82,26.66,85,14a37.81,37.81,0,0,0,16.31-13.51Z"/><path class="cls-2" d="M254.46,171.31a106,106,0,0,1-3.51,21.6c-4.31,16.92-11.45,33.65-15,39.91-2.66,4.67-6.37,5.57-12.24,7a51.47,51.47,0,0,1-42.29-8.94L145,204c-.51-5-1.37-10.25-2.33-15.6-2.78-15.54-6.38-32-4.82-46.54a17,17,0,0,1,5.64-10.69c8.38-7.94,24-11.54,33.74-14,14.62-3.76,29.64-6,44.05-10.43,10.61-3.3,22.06-8.32,28.42-17.88,0,0,1.1,10.81,1.35,13.28.53,5.09.52,9.51,1.07,14.53a52,52,0,0,1-1,16.45c-.83,3.73-1.81,7.45-2.51,11.21-.67,3.6.28,3.66,2.13,6.21C254.54,155.74,254.69,165.14,254.46,171.31Z"/><path class="cls-1" d="M187.56,143c6.1,0,6.1,9.38,0,9.43h-.27c-6.1,0-6.1-9.38,0-9.43h.27Z"/><path class="cls-1" d="M237.69,141.6c5.66.05,5.66,8.7,0,8.75h-.25c-5.67,0-5.67-8.7,0-8.75h.25Z"/><path class="cls-5" d="M219.4,147c-.05.2,4.79,8.56,7.31,17.59a58,58,0,0,1,1.62,14.75H214.82"/><path class="cls-1" d="M251,192.91c-4.31,16.92-11.45,33.65-15,39.91-2.66,4.67-6.37,5.57-12.24,7a51.47,51.47,0,0,1-42.29-8.94L145,204c-.6-5.89-2.75-11.53-4-17.33-.54-2.62-1-5.36-.36-8a6.84,6.84,0,0,0,1.71,3.73c4.72,6.42,10.24,13.67,18.1,16.31,8.2,2.75,17.54,3.18,25.45-.7,6.89-3.38,13.64-7.79,20.83-10.57a35.05,35.05,0,0,1,15.11-2.61c7.34.5,10.48,3.81,15.85,8.18,1.76,1.44,3.73,2.94,6,2.95C246.29,196,248.79,194.56,251,192.91Z"/><path class="cls-6" d="M204.37,198.32a29.8,29.8,0,0,0,20.89-1.21"/><path class="cls-5" d="M172.49,135a80.35,80.35,0,0,1,28.13-.8"/><path class="cls-5" d="M231.54,135.13A55.7,55.7,0,0,1,249,133.92"/><path class="cls-5" d="M203.49,174.34a3.4,3.4,0,0,0,2.11,6.38"/><polygon class="cls-1" points="141.67 188.44 142.67 188.38 143.33 189.16 145.48 202.98 145 203.16 144.28 202.17 141.67 188.44"/></svg>
|
||||||
|
After Width: | Height: | Size: 3.4 KiB |
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 366.34 366.34" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style>.cls-1{fill:#00214e;}.cls-2{fill:#f2a196;}.cls-3{fill:#e88870;}.cls-4{fill:none;stroke:#00214e;stroke-miterlimit:10;}.cls-5{fill:#f2d4cf;}.cls-6{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="109.87" y1="140.89" x2="166.58" y2="65.41" gradientUnits="userSpaceOnUse"><stop offset="0.29" stop-color="#00214e"/><stop offset="0.51" stop-color="#6878b1"/><stop offset="0.79" stop-color="#00214e"/></linearGradient></defs><title>Artboards_Diversity_Avatars_by_Netguru</title><path class="cls-1" d="M258.38,149.67c-.41-33.19-6.33-69.59-36.31-89.15C195,42.86,158.32,43.72,131.45,61a77.55,77.55,0,0,0-10.22,7.8,105.09,105.09,0,0,0-26.66,39,130.84,130.84,0,0,0-9,54.2c1.67,37.14-2.36,74.39-2.36,111.68,0,1.29,0,2.77,1,3.59a4.27,4.27,0,0,0,3,.59c15.62-.87,29.45-3.57,44.53-6.08,18.8-3.12,39.07-2.23,58.1-3.42,11.33-.66,23.24-2.49,34.57-2,3.61.16,35.22,4.85,35.36,6.8C256.75,231.8,258.89,191,258.38,149.67ZM148,145.49l-14.09-10a130.31,130.31,0,0,0,31.94-16,70.63,70.63,0,0,0,21.84-23l5.71,3.61Z"/><path class="cls-2" d="M296.41,280.37a184.56,184.56,0,0,1-226.48-1l48.66-22.81a47.68,47.68,0,0,0,4.35-2.34l1.12-.7c.4-.25.79-.51,1.18-.78a46.54,46.54,0,0,0,14.67-16.47c4-7.55,5.32-15.89,5.38-24.39,0-4.67-.19-9.34-.31-14q0-1.57-.06-3.15-.06-4.75-.1-9.51l.07,0,1.91,1,5.2,2.69,30.29,5.15,31.12,5.3.71,24,.2,6.88,0,1.07.47,15.87,11.47,4.67,9,3.64Z"/><path class="cls-3" d="M214.12,223.37a12.12,12.12,0,0,1-7.34,2.11C192,223.89,163.14,212.3,145,190.85q0-1.57-.06-3.15l0-2.47,1.91,1,5.2,2.69,30.29,5.15,31.12,5.3Z"/><circle class="cls-2" cx="119.82" cy="153.19" r="17"/><circle class="cls-3" cx="125.82" cy="151.19" r="17"/><path class="cls-2" d="M235.36,127.45c11.74,40.68-13.2,89.87-28.54,89.87-21,0-72-16.78-83.73-57.46S127,78.94,158,70,223.62,86.76,235.36,127.45Z"/><path class="cls-4" d="M177.31,176.87a29.74,29.74,0,0,0,18.54,9.69"/><path class="cls-4" d="M197.93,131.73c-.05.2,4.79,8.56,7.31,17.59a58,58,0,0,1,1.62,14.76H193.35"/><path class="cls-4" d="M210.34,123.22a31.18,31.18,0,0,1,22.85-2.16"/><path class="cls-4" d="M151.19,127.2a36.75,36.75,0,0,1,31.23-1"/><path class="cls-1" d="M168.73,136.06c6.1.05,6.1,9.38,0,9.42h-.27c-6.1,0-6.1-9.37,0-9.42h.27Z"/><path class="cls-1" d="M218.86,134.67c5.66,0,5.66,8.7,0,8.74h-.25c-5.67,0-5.67-8.7,0-8.74h.25Z"/><path class="cls-1" d="M198.93,59.32a113.91,113.91,0,0,1-2.27,14,81,81,0,0,1-9,23.18,70.63,70.63,0,0,1-21.84,23,130.31,130.31,0,0,1-31.94,16,202.94,202.94,0,0,1-27.46,7.23c.31-7.63,0-17.07.94-25.93.7-6.36,2.1-12.43,4.92-17.32a79.54,79.54,0,0,1,32.19-30.3l.12-.06C159.38,61.51,182.29,54,198.93,59.32Z"/><path class="cls-5" d="M296.41,280.37a184.56,184.56,0,0,1-226.48-1l48.66-22.81a46.83,46.83,0,0,0,6.65-3.82c.64-.44,1.28-.9,1.89-1.38a46.35,46.35,0,0,0,12.78-15.09,44.69,44.69,0,0,0,4.64-14.48,28.66,28.66,0,0,0,2.22,1.94A95.14,95.14,0,0,0,166.59,235a99,99,0,0,0,10.46,3.69,93.52,93.52,0,0,0,33,3.49c1.54-.12,3.09-.27,4.63-.38l.15,5.08v.33l12.1,4.92Z"/><path class="cls-6" d="M187.64,96.54a70.63,70.63,0,0,1-21.84,23,130.31,130.31,0,0,1-31.94,16l-26.52-18.7-12.77-9a105.09,105.09,0,0,1,26.66-39A77.55,77.55,0,0,1,131.45,61l13,8.22Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 512 512" xml:space="preserve">
|
||||||
|
<circle style="fill:#FAD24D;" cx="256" cy="256" r="256"/>
|
||||||
|
<ellipse style="fill:#EDB937;" cx="256" cy="421.64" rx="182.28" ry="14.369"/>
|
||||||
|
<path style="fill:#666666;" d="M65.982,67.222h380.036c12.486,0,22.7,10.217,22.7,22.703V324.03H43.282V89.925
|
||||||
|
C43.282,77.439,53.496,67.222,65.982,67.222z"/>
|
||||||
|
<path style="fill:#15BDB2;" d="M58.695,308.614h394.607V89.922c0-3.979-3.308-7.287-7.287-7.287H65.979
|
||||||
|
c-3.976,0-7.287,3.308-7.287,7.287v218.693L58.695,308.614L58.695,308.614z"/>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFFFFF;" d="M468.718,324.03v24.826c0,12.489-11.261,22.703-25.026,22.703H68.305
|
||||||
|
c-13.765,0-25.026-10.214-25.026-22.703V324.03h425.436H468.718z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M144.153,234.3H131.29l-2.201-14.717h-15.643l-2.201,14.717H99.54l12.979-81.119h18.657
|
||||||
|
l12.979,81.119H144.153z M115.064,208.573h12.284l-6.14-41.024L115.064,208.573z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M168.837,153.181c6.413,0,11.182,1.7,14.314,5.099c3.129,3.399,4.693,8.384,4.693,14.951v10.544
|
||||||
|
c0,6.568-1.563,11.549-4.693,14.949c-3.129,3.399-7.901,5.099-14.314,5.099h-6.025v30.476h-12.748V153.18h18.773V153.181z
|
||||||
|
M162.812,164.769v27.465h6.025c2.009,0,3.554-0.54,4.635-1.624c1.081-1.081,1.621-3.09,1.621-6.025v-12.168
|
||||||
|
c0-2.935-0.54-4.944-1.621-6.025c-1.083-1.081-2.628-1.621-4.635-1.621h-6.025V164.769z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M193.75,153.181h12.748V234.3H193.75V153.181z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M367.541,206.507l4.024-15.339l-9.107-3.575c0.307-3.438,0.288-6.901-0.055-10.34l9.073-3.67
|
||||||
|
l-4.184-15.295l-9.663,1.458c-1.437-3.101-3.179-6.091-5.228-8.932l6.02-7.709l-11.274-11.155l-7.646,6.101
|
||||||
|
c-2.861-2.02-5.872-3.733-8.983-5.136l1.358-9.679l-15.339-4.024l-3.575,9.107c-3.438-0.307-6.903-0.291-10.34,0.055l-3.667-9.073
|
||||||
|
l-15.297,4.184l1.458,9.663l0.005-0.002c-3.098,1.435-6.091,3.179-8.932,5.228l-7.709-6.022l-11.155,11.274l6.101,7.646
|
||||||
|
c-2.02,2.861-3.733,5.87-5.136,8.983l-9.679-1.358l-4.024,15.339l9.107,3.575c-0.307,3.438-0.288,6.901,0.055,10.34l-9.073,3.667
|
||||||
|
l4.184,15.297l9.66-1.458c1.437,3.098,3.179,6.091,5.228,8.932l-6.02,7.709L269,233.453l7.646-6.101
|
||||||
|
c2.858,2.02,5.87,3.733,8.983,5.136l-1.358,9.676l15.339,4.026l3.575-9.107c3.438,0.307,6.901,0.288,10.34-0.055l3.67,9.073
|
||||||
|
l15.295-4.184l-1.458-9.66c3.101-1.437,6.091-3.179,8.932-5.228l7.709,6.02l11.155-11.271l-6.101-7.646
|
||||||
|
c2.02-2.858,3.733-5.87,5.136-8.983L367.541,206.507z M325.84,200.282c-9.713,9.813-25.538,9.894-35.35,0.184
|
||||||
|
c-9.813-9.713-9.895-25.537-0.184-35.35c9.713-9.813,25.538-9.894,35.35-0.184C335.469,174.645,335.55,190.47,325.84,200.282z"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:#ECF0F1;" d="M308.071,150.421c-17.826,0-32.279,14.45-32.279,32.279c0,17.826,14.451,32.279,32.279,32.279
|
||||||
|
c17.826,0,32.279-14.451,32.279-32.279C340.35,164.874,325.9,150.421,308.071,150.421z M325.84,200.284
|
||||||
|
c-9.713,9.813-25.538,9.894-35.35,0.184c-9.813-9.712-9.895-25.537-0.184-35.35c9.713-9.813,25.538-9.894,35.35-0.184
|
||||||
|
C335.469,174.647,335.55,190.472,325.84,200.284z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M417.06,242.49c0.098-1.815,0.006-3.649-0.285-5.477l5.244-2.987l-5.957-12.386l-5.65,2.028
|
||||||
|
c-1.236-1.38-2.605-2.605-4.076-3.67l1.598-5.821l-12.971-4.546l-2.563,5.429c-1.815-0.098-3.649-0.006-5.477,0.285l-2.986-5.241
|
||||||
|
l-12.386,5.959l2.028,5.647c-1.377,1.236-2.605,2.605-3.67,4.076l-5.821-1.598l-4.546,12.971l5.429,2.565
|
||||||
|
c-0.097,1.812-0.008,3.649,0.283,5.474l-5.244,2.987l5.957,12.386l5.65-2.028c1.238,1.377,2.607,2.605,4.076,3.67l-1.595,5.821
|
||||||
|
l12.971,4.546l2.563-5.432c1.812,0.1,3.649,0.008,5.477-0.283l2.985,5.244l12.386-5.957l-2.028-5.647
|
||||||
|
c1.38-1.239,2.605-2.608,3.67-4.079l5.821,1.598l4.546-12.971L417.06,242.49z M400.732,250.72c-5.309,5.367-13.963,5.408-19.329,0.1
|
||||||
|
c-5.363-5.31-5.408-13.963-0.1-19.329c5.309-5.365,13.963-5.408,19.329-0.1C405.997,236.702,406.041,245.353,400.732,250.72z"/>
|
||||||
|
<path style="fill:#ECF0F1;" d="M391.016,221.639c-10.752,0-19.468,8.716-19.468,19.468s8.716,19.468,19.468,19.468
|
||||||
|
c10.752,0,19.468-8.716,19.468-19.468C410.484,230.355,401.768,221.639,391.016,221.639z M400.73,250.72
|
||||||
|
c-5.309,5.365-13.963,5.408-19.326,0.1c-5.366-5.309-5.411-13.963-0.103-19.326v-0.003c5.312-5.367,13.963-5.411,19.329-0.1
|
||||||
|
C405.995,236.699,406.039,245.353,400.73,250.72z"/>
|
||||||
|
<circle style="fill:#B6B6B8;" cx="256" cy="346.8" r="7.814"/>
|
||||||
|
<path style="fill:#C2C2C4;" d="M305.065,407.271l36.095,11.564H170.836l29.123-11.564v-35.712h105.104v35.712H305.065z"/>
|
||||||
|
<path style="fill:#B6B6B8;" d="M305.065,407.271l-105.104-35.712h105.104V407.271z"/>
|
||||||
|
<path style="fill:#ECF0F1;" d="M199.959,407.271h105.104l36.095,11.564v4.981h-85.161h-85.163v-4.981l29.123-11.564H199.959z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.8 KiB |
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="212px" height="212px" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="2.304"/>
|
||||||
|
<g id="SVGRepo_iconCarrier"> <rect width="48" height="48" fill="white" fill-opacity="0.01"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M44 40.8361C39.1069 34.8632 34.7617 31.4739 30.9644 30.6682C27.1671 29.8625 23.5517 29.7408 20.1182 30.303V41L4 23.5453L20.1182 7V17.167C26.4667 17.2172 31.8638 19.4948 36.3095 24C40.7553 28.5052 43.3187 34.1172 44 40.8361Z" fill="#a0a1a2" stroke="#000000" stroke-width="4" stroke-linejoin="round"/> </g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 907 B |
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="none" height="2500" width="2183" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 124 141.53"><path d="M10.383 126.892L0 0l124 .255-10.979 126.637-50.553 14.638z" fill="#1b73ba"/><path d="M62.468 129.275V12.085l51.064.17-9.106 104.85z" fill="#1c88c7"/><path d="M100.851 27.064H22.298l2.128 15.318h37.276l-36.68 15.745 2.127 14.808h54.043l-1.958 20.68-18.298 3.575-16.595-4.255-1.277-11.745H27.83l2.042 24.426 32.681 9.106 31.32-9.957 4-47.745H64.765l36.085-14.978z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 495 B |
|
|
@ -0,0 +1,77 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 512 512" xml:space="preserve">
|
||||||
|
<path style="fill:#E4EAF8;" d="M486.881,324.409H161.937c-9.446,0-17.102-7.656-17.102-17.102V50.772
|
||||||
|
c0-9.446,7.656-17.102,17.102-17.102h324.944c9.446,0,17.102,7.656,17.102,17.102v256.534
|
||||||
|
C503.983,316.753,496.326,324.409,486.881,324.409z"/>
|
||||||
|
<rect x="409.921" y="67.875" style="fill:#7DF5A5;" width="42.756" height="222.33"/>
|
||||||
|
<rect x="332.96" y="136.284" style="fill:#FFDC64;" width="42.756" height="153.921"/>
|
||||||
|
<rect x="256" y="204.693" style="fill:#FF5050;" width="42.756" height="85.511"/>
|
||||||
|
<path style="fill:#D29B6E;" d="M210.119,363.267L182.2,353.96c-6.984-2.328-11.694-8.863-11.694-16.225v-21.877H119.2v21.878
|
||||||
|
c0,7.361-4.71,13.897-11.694,16.225l-27.918,9.307c-6.984,2.328-11.694,8.863-11.694,16.225v98.837h153.921v-98.838
|
||||||
|
C221.813,372.13,217.103,365.595,210.119,363.267z"/>
|
||||||
|
<path style="fill:#5B5D6E;" d="M210.119,363.267L182.2,353.96c-1.677-0.559-3.205-1.386-4.586-2.384
|
||||||
|
c-7.844,9.446-19.527,15.589-32.762,15.589c-13.235,0-24.918-6.143-32.762-15.589c-1.381,0.997-2.908,1.826-4.586,2.384
|
||||||
|
l-27.918,9.307c-6.984,2.328-11.694,8.863-11.694,16.225v98.838h153.921v-98.838C221.813,372.13,217.103,365.595,210.119,363.267z"
|
||||||
|
/>
|
||||||
|
<ellipse style="fill:#694B4B;" cx="144.856" cy="243.173" rx="68.409" ry="64.134"/>
|
||||||
|
<path style="fill:#5A4146;" d="M161.382,181.003c-5.303-1.236-10.819-1.964-16.529-1.964c-37.781,0-68.409,28.714-68.409,64.134
|
||||||
|
c0,30.414,22.607,55.826,52.91,62.418c0-11.005,0-27.379,0-51.729C129.354,216.819,147.19,193.639,161.382,181.003z"/>
|
||||||
|
<path style="fill:#F0C087;" d="M187.973,311.4l-27.823,13.912c-9.63,4.814-20.964,4.814-30.594,0L101.733,311.4
|
||||||
|
c-5.115-2.557-8.612-7.501-9.322-13.175l-5.194-41.55c-0.612-4.898,3.011-9.269,7.935-9.591
|
||||||
|
c15.938-1.043,49.178-5.044,69.669-20.388c3.456-2.588,8.28-2.132,11.233,1.018l23.773,25.354c1.739,1.855,2.562,4.387,2.247,6.909
|
||||||
|
l-4.781,38.247C196.586,303.898,193.087,308.843,187.973,311.4z"/>
|
||||||
|
<path style="fill:#E6AF78;" d="M199.829,253.068l-23.774-25.355c-2.974-3.171-7.815-3.569-11.298-0.968
|
||||||
|
c-10.381,7.751-24.006,12.583-37.006,15.618v0.001c-12.713,2.967-24.825,4.214-32.68,4.726c-4.954,0.323-8.469,4.661-7.853,9.586
|
||||||
|
l5.193,41.548c0.71,5.675,4.207,10.619,9.323,13.177l27.823,13.912c4.155,2.077,8.639,3.078,13.155,3.364l-10.446-20.892
|
||||||
|
c-2.968-5.936-4.514-12.481-4.514-19.118v-13.239c0-4.003,2.779-7.391,6.673-8.321c10.865-2.595,22.152-6.299,32.494-11.632
|
||||||
|
l14.197,15.142c4.765,5.083,11.825,9.027,18.599,8.257l2.362-18.895C202.392,257.455,201.568,254.923,199.829,253.068z"/>
|
||||||
|
<path style="fill:#5B5D6E;" d="M196.16,170.489H93.546l-6.545,26.18c-1.118,4.474,1.512,9.029,5.946,10.296l47.207,13.487
|
||||||
|
c3.071,0.878,6.326,0.878,9.397,0l47.207-13.487c4.435-1.267,7.065-5.822,5.946-10.296L196.16,170.489z"/>
|
||||||
|
<path style="fill:#464655;" d="M93.546,170.489L87,196.669c-1.118,4.474,1.512,9.029,5.946,10.296l47.208,13.488
|
||||||
|
c1.536,0.438,3.117,0.658,4.699,0.658v-50.623H93.546z"/>
|
||||||
|
<path style="fill:#707487;" d="M138.103,137.222l-76.489,21.652c-3.006,0.851-3.006,5.276,0,6.127l76.489,21.652
|
||||||
|
c4.419,1.251,9.08,1.251,13.5,0l76.489-21.652c3.006-0.851,3.006-5.276,0-6.127l-76.489-21.652
|
||||||
|
C147.183,135.972,142.522,135.972,138.103,137.222z"/>
|
||||||
|
<path style="fill:#D5DCED;" d="M187.591,478.33h-85.511c-4.722,0-8.551-3.829-8.551-8.551v-51.307c0-4.722,3.829-8.551,8.551-8.551
|
||||||
|
h85.511c4.722,0,8.551,3.829,8.551,8.551v51.307C196.142,474.501,192.313,478.33,187.591,478.33z"/>
|
||||||
|
<path d="M486.881,25.653H161.937c-13.851,0-25.119,11.268-25.119,25.119v78.507c-0.311,0.078-0.624,0.142-0.934,0.23l-76.489,21.65
|
||||||
|
c-4.838,1.369-8.088,5.7-8.088,10.778c0,5.077,3.251,9.408,8.087,10.778l25.379,7.184l-4.595,13.787
|
||||||
|
c-1.134,3.403-1.098,7.094,0.021,10.47c-7.739,11.356-11.808,24.387-11.808,38.007c0,14.823,4.845,28.938,14.019,40.973
|
||||||
|
l2.011,16.084c1.044,8.352,6.162,15.588,13.691,19.351l13.035,6.517v12.648c0,3.917-2.496,7.381-6.212,8.618l-27.92,9.307
|
||||||
|
c-10.273,3.425-17.175,13.001-17.175,23.83v90.823H8.017c-4.427,0-8.017,3.589-8.017,8.017s3.589,8.017,8.017,8.017h273.637
|
||||||
|
c4.427,0,8.017-3.589,8.017-8.017s-3.589-8.017-8.017-8.017h-51.859v-90.822c0-10.829-6.903-20.406-17.176-23.83l-27.918-9.307
|
||||||
|
c-3.715-1.239-6.212-4.702-6.212-8.618v-5.31h308.394c13.851,0,25.119-11.268,25.119-25.119V50.772
|
||||||
|
C512,36.922,500.732,25.653,486.881,25.653z M140.251,144.936C140.252,144.936,140.252,144.936,140.251,144.936
|
||||||
|
c2.989-0.845,6.146-0.845,9.133,0l60.062,17.002l-60.061,17.002c-2.988,0.846-6.146,0.846-9.133,0l-60.062-17.002L140.251,144.936z
|
||||||
|
M144.817,359.148c-7.962,0-15.622-2.746-21.748-7.662c2.613-3.992,4.111-8.732,4.111-13.75v-4.676
|
||||||
|
c5.572,2.56,11.602,3.857,17.637,3.857s12.065-1.297,17.637-3.857v4.676c0,5.017,1.496,9.757,4.11,13.749
|
||||||
|
C160.442,356.403,152.796,359.148,144.817,359.148z M84.496,239.589c0.434-8.676,3.059-17.005,7.706-24.49l50.413,14.404
|
||||||
|
c0.032,0.01,0.066,0.012,0.098,0.021c-25.228,9.725-56.273,9.91-56.686,9.91C85.509,239.434,84.997,239.492,84.496,239.589z
|
||||||
|
M197.433,215.1c5.104,8.223,7.776,17.463,7.776,27.062c0,1.583-0.081,3.156-0.228,4.719l-25.099-26.767L197.433,215.1z
|
||||||
|
M194.246,198.755c0.013,0.038,0.024,0.125,0.012,0.23c-0.154,0.151-0.295,0.311-0.434,0.471l-40.99,11.711v-16.575
|
||||||
|
c0.306-0.077,0.614-0.14,0.917-0.226l35.666-10.096L194.246,198.755z M135.885,194.367c0.304,0.086,0.61,0.149,0.915,0.226v16.575
|
||||||
|
l-40.967-11.704c-0.148-0.171-0.298-0.339-0.462-0.498c-0.006-0.095,0.005-0.173,0.017-0.208l4.828-14.485L135.885,194.367z
|
||||||
|
M100.33,297.23l-5.261-42.096c16.635-1.028,52.262-5.215,74.813-22.248l24.236,25.848l-4.811,38.496
|
||||||
|
c-0.378,3.022-2.229,5.637-4.953,7l0,0l-27.823,13.912c-7.335,3.666-16.09,3.665-23.424,0l-27.823-13.912
|
||||||
|
C102.559,302.868,100.708,300.251,100.33,297.23z M75.874,379.491c0-3.918,2.496-7.381,6.212-8.62l27.92-9.307
|
||||||
|
c0.056-0.018,0.108-0.043,0.162-0.062c7.402,6.91,16.685,11.446,26.636,13.038l0.011,27.362h-34.736
|
||||||
|
c-9.136,0-16.568,7.432-16.568,16.568v51.307c0,0.181,0.021,0.356,0.027,0.534h-9.665v-90.821H75.874z M102.079,470.313
|
||||||
|
c-0.295,0-0.534-0.239-0.534-0.534v-51.307c0-0.295,0.239-0.534,0.534-0.534h42.756c0.001,0,0.002,0,0.003,0h42.753
|
||||||
|
c0.295,0,0.534,0.239,0.534,0.534v51.307c0,0.295-0.239,0.534-0.534,0.534H102.079z M213.761,379.491v90.822h-9.63
|
||||||
|
c0.005-0.178,0.027-0.354,0.027-0.534v-51.307c0-9.136-7.432-16.568-16.568-16.568h-34.742l-0.011-27.364
|
||||||
|
c9.961-1.591,19.239-6.114,26.641-13.032c0.051,0.017,0.099,0.04,0.151,0.057l27.918,9.307
|
||||||
|
C211.265,372.111,213.761,375.575,213.761,379.491z M495.967,307.307c0,5.01-4.076,9.086-9.086,9.086H195.086
|
||||||
|
c5.558-4.047,9.26-10.219,10.129-17.174l0.125-0.997h264.439c4.427,0,8.017-3.589,8.017-8.017c0-4.427-3.589-8.017-8.017-8.017
|
||||||
|
h-9.086V67.875c0-4.427-3.589-8.017-8.017-8.017h-42.756c-4.427,0-8.017,3.589-8.017,8.017v214.313h-18.171V136.284
|
||||||
|
c0-4.427-3.589-8.017-8.017-8.017H332.96c-4.427,0-8.017,3.589-8.017,8.017v145.904h-18.171v-77.495
|
||||||
|
c0-4.427-3.589-8.017-8.017-8.017H256c-4.427,0-8.017,3.589-8.017,8.017v77.495H207.93c8.708-11.832,13.313-25.63,13.313-40.026
|
||||||
|
c0-13.62-4.068-26.651-11.808-38.007c1.119-3.376,1.155-7.066,0.021-10.47l-4.596-13.787l17.451-4.94v38.286
|
||||||
|
c0,4.427,3.589,8.017,8.017,8.017s8.017-3.589,8.017-8.017v-51.307c0-0.305-0.02-0.605-0.053-0.901
|
||||||
|
c-0.359-4.683-3.503-8.589-8.052-9.877l-76.489-21.652c-0.298-0.084-0.6-0.145-0.899-0.221V50.772c0-5.01,4.076-9.086,9.086-9.086
|
||||||
|
H486.88c5.01,0,9.086,4.076,9.086,9.086v256.534H495.967z M290.739,282.188h-26.722V212.71h26.722V282.188z M367.699,282.188
|
||||||
|
h-26.722V144.301h26.722V282.188z M444.66,282.188h-26.722V75.891h26.722V282.188z"/>
|
||||||
|
<path d="M144.878,436.109h-0.086c-4.427,0-7.974,3.589-7.974,8.017c0,4.427,3.632,8.017,8.059,8.017s8.017-3.589,8.017-8.017
|
||||||
|
C152.895,439.698,149.305,436.109,144.878,436.109z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="#878787" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve" stroke="#878787">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<g id="SVGRepo_iconCarrier"> <path d="M256,0C114.6,0,0,114.6,0,256s114.6,256,256,256s256-114.6,256-256S397.4,0,256,0z M64,256c0-106.1,86-192,192-192 c42.1,0,81,13.7,112.6,36.7L100.7,368.6C77.7,337,64,298.1,64,256z M256,448c-42.1,0-81-13.7-112.6-36.7l267.9-267.9 c23,31.7,36.7,70.5,36.7,112.6C448,362.1,362,448,256,448z"/> </g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 898 B |
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 512 512" xml:space="preserve">
|
||||||
|
<rect x="11.77" y="11.77" style="fill:#ECF0F1;" width="488.46" height="488.46"/>
|
||||||
|
<rect x="11.77" y="11.77" style="fill:#9E9E9E;" width="488.46" height="76.506"/>
|
||||||
|
<rect x="423.724" y="11.77" style="fill:#B71C1C;" width="76.506" height="76.506"/>
|
||||||
|
<rect x="284.86" y="296.054" style="fill:#FFC107;" width="159.073" height="141.241"/>
|
||||||
|
<rect x="141.241" y="138.923" style="fill:#2196F3;" width="229.517" height="52.966"/>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#231F20;" d="M500.23,0H11.77C5.269,0,0,5.269,0,11.77v488.46C0,506.731,5.269,512,11.77,512h488.46
|
||||||
|
c6.501,0,11.77-5.269,11.77-11.77V11.77C512,5.269,506.731,0,500.23,0z M488.46,23.54v52.966h-52.966V23.54H488.46z M23.54,23.54
|
||||||
|
h388.414v52.966H23.54V23.54z M23.54,488.46V100.046h464.92V488.46H23.54z"/>
|
||||||
|
<path style="fill:#231F20;" d="M443.93,237.208H68.07c-6.501,0-11.77,5.269-11.77,11.77c0,6.501,5.269,11.77,11.77,11.77h375.859
|
||||||
|
c6.501,0,11.77-5.269,11.77-11.77C455.699,242.477,450.431,237.208,443.93,237.208z"/>
|
||||||
|
<path style="fill:#231F20;" d="M233.073,284.288H110.566c-6.501,0-11.77,5.269-11.77,11.77s5.269,11.77,11.77,11.77h122.508
|
||||||
|
c6.501,0,11.77-5.269,11.77-11.77S239.574,284.288,233.073,284.288z"/>
|
||||||
|
<path style="fill:#231F20;" d="M68.07,307.829h2.183c6.501,0,11.77-5.269,11.77-11.77s-5.269-11.77-11.77-11.77H68.07
|
||||||
|
c-6.501,0-11.77,5.269-11.77,11.77S61.571,307.829,68.07,307.829z"/>
|
||||||
|
<path style="fill:#231F20;" d="M233.073,331.369H68.07c-6.501,0-11.77,5.269-11.77,11.77s5.269,11.77,11.77,11.77h165.003
|
||||||
|
c6.501,0,11.77-5.269,11.77-11.77S239.574,331.369,233.073,331.369z"/>
|
||||||
|
<path style="fill:#231F20;" d="M233.073,378.449h-1.275c-6.501,0-11.77,5.269-11.77,11.77s5.269,11.77,11.77,11.77h1.275
|
||||||
|
c6.501,0,11.77-5.269,11.77-11.77S239.574,378.449,233.073,378.449z"/>
|
||||||
|
<path style="fill:#231F20;" d="M68.07,401.989h126.357c6.501,0,11.77-5.269,11.77-11.77s-5.269-11.77-11.77-11.77H68.07
|
||||||
|
c-6.501,0-11.77,5.269-11.77,11.77S61.571,401.989,68.07,401.989z"/>
|
||||||
|
<path style="fill:#231F20;" d="M233.073,425.53H68.07c-6.501,0-11.77,5.269-11.77,11.77s5.269,11.77,11.77,11.77h165.003
|
||||||
|
c6.501,0,11.77-5.269,11.77-11.77S239.574,425.53,233.073,425.53z"/>
|
||||||
|
<path style="fill:#231F20;" d="M443.93,284.288H284.861c-6.501,0-11.77,5.269-11.77,11.77V437.3c0,6.501,5.27,11.77,11.77,11.77
|
||||||
|
H443.93c6.501,0,11.77-5.269,11.77-11.77V296.058C455.7,289.558,450.431,284.288,443.93,284.288z M432.16,425.53H296.632V307.829
|
||||||
|
H432.16V425.53z"/>
|
||||||
|
<path style="fill:#231F20;" d="M141.241,203.663h229.517c6.501,0,11.77-5.27,11.77-11.77v-52.966c0-6.501-5.269-11.77-11.77-11.77
|
||||||
|
H141.241c-6.501,0-11.77,5.269-11.77,11.77v52.966C129.471,198.394,134.741,203.663,141.241,203.663z M153.011,150.697h205.977
|
||||||
|
v29.425H153.011V150.697z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
|
||||||
|
<title>github [#142]</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
|
||||||
|
</defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Dribbble-Light-Preview" transform="translate(-140.000000, -7559.000000)" fill="#000000">
|
||||||
|
<g id="icons" transform="translate(56.000000, 160.000000)">
|
||||||
|
<path d="M94,7399 C99.523,7399 104,7403.59 104,7409.253 C104,7413.782 101.138,7417.624 97.167,7418.981 C96.66,7419.082 96.48,7418.762 96.48,7418.489 C96.48,7418.151 96.492,7417.047 96.492,7415.675 C96.492,7414.719 96.172,7414.095 95.813,7413.777 C98.04,7413.523 100.38,7412.656 100.38,7408.718 C100.38,7407.598 99.992,7406.684 99.35,7405.966 C99.454,7405.707 99.797,7404.664 99.252,7403.252 C99.252,7403.252 98.414,7402.977 96.505,7404.303 C95.706,7404.076 94.85,7403.962 94,7403.958 C93.15,7403.962 92.295,7404.076 91.497,7404.303 C89.586,7402.977 88.746,7403.252 88.746,7403.252 C88.203,7404.664 88.546,7405.707 88.649,7405.966 C88.01,7406.684 87.619,7407.598 87.619,7408.718 C87.619,7412.646 89.954,7413.526 92.175,7413.785 C91.889,7414.041 91.63,7414.493 91.54,7415.156 C90.97,7415.418 89.522,7415.871 88.63,7414.304 C88.63,7414.304 88.101,7413.319 87.097,7413.247 C87.097,7413.247 86.122,7413.234 87.029,7413.87 C87.029,7413.87 87.684,7414.185 88.139,7415.37 C88.139,7415.37 88.726,7417.2 91.508,7416.58 C91.513,7417.437 91.522,7418.245 91.522,7418.489 C91.522,7418.76 91.338,7419.077 90.839,7418.982 C86.865,7417.627 84,7413.783 84,7409.253 C84,7403.59 88.478,7399 94,7399" id="github-[#142]">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--noto" preserveAspectRatio="xMidYMid meet">
|
||||||
|
<path d="M12.75 118.55c6.65 6.65 14.73 4.34 19.4-.98c4.77-5.44 7.31-8.6 7.31-8.6c5.95-8.76 30.53-38.04 51.51-59.11c2.92 1.2 6.02 1.83 9.13 1.83c5.54 0 11.09-1.91 15.53-5.74a23.83 23.83 0 0 0 8.3-18.36c-.01-.49-.27-.95-.7-1.19c-.43-.24-.95-.24-1.38 0l-14.45 8.34c-6.98-2.41-11.11-9.59-9.69-16.77l14.4-8.31c.43-.25.69-.7.69-1.2s-.26-.95-.69-1.2c-9.35-5.47-21.23-3.93-28.89 3.73a23.915 23.915 0 0 0-6.97 17.61a23.66 23.66 0 0 0 2.92 10.72C55.99 62.43 27.71 87.42 20.35 93c0 0-4.43 3.21-8.66 7.56c-4.17 4.29-5.72 11.21 1.06 17.99zm4.87-10.23c0-2.94 2.38-5.33 5.33-5.33s5.33 2.38 5.33 5.33c0 2.94-2.38 5.33-5.33 5.33s-5.33-2.39-5.33-5.33z" fill="#82aec0">
|
||||||
|
</path>
|
||||||
|
<path d="M76 42.47c1.04-1.03 2.1-2.07 3.18-3.15a23.66 23.66 0 0 1-2.92-10.72c-.08-2.6.27-5.17 1.01-7.62c.66-1.08 1.7-2.24 2.36-1.54c-.27 8.51 2.2 17.38 8.12 23.5c2.94 3.04 6.76 5.23 10.86 6.26c2.02.51 4.12.74 6.21.62c1.23-.07 4.67-1.34 5.36-.4v.04a23.902 23.902 0 0 1-10.08 2.22c-3.11 0-6.22-.63-9.13-1.83c-6.79 6.82-12.41 12.96-17.13 18.45c2.36-4.46 9.31-12.68 11.17-15.82c.42-.71 1-2.53-.32-4.17c-1.87-2.3-5.7-4.44-8.69-5.84z" fill="#2f7889">
|
||||||
|
</path>
|
||||||
|
<path d="M47.68 77.75c.54-.56.03-1.5-.73-1.33c-1.42.31-3.47 1.12-5.91 3.1c-4.78 3.88-17.4 14.36-18.96 16.43s4.7-.03 6.9-.42c1.81-.33 14.42-13.33 18.7-17.78z" fill="#b9e4ea">
|
||||||
|
</path>
|
||||||
|
<path d="M91.43 10.93c-2.67 1.91-5.07 4.18-7.57 6.31c-.64.55-1.59 1.1-2.25.58c-.72-.57-.27-1.72.27-2.46c4.73-6.5 13.56-11.15 22.1-9.3c-4.5 1.45-8.55 2.01-12.55 4.87z" fill="#b9e4ea">
|
||||||
|
</path>
|
||||||
|
<path d="M112.66 33.24c-1.05.63-2.51 1.47-3.34 1.71c-1.01.28-3.87-.83-4.7-1.49c3.42-2.23 7.23-4.72 10.65-6.95c.39-.25.78-.51 1.23-.65c.53-.17 1.11-.15 1.67-.13c.78.04 4.59-.09 5.04.58c.45.68-1.13 1.34-1.65 1.65c-2.97 1.77-5.94 3.52-8.9 5.28z" fill="#2f7889">
|
||||||
|
</path>
|
||||||
|
<path d="M71.8 70.17l-11.19-12.9c-4.05 3.81-8.06 7.52-11.89 11.03l3.26-.71c1.43-.3 2.91.14 3.95 1.17l5.22 5.85c1.1 1.1 1.28 3.01.86 4.51l-.58 2.77c3.3-3.81 6.79-7.76 10.37-11.72z" fill="#2f7889">
|
||||||
|
</path>
|
||||||
|
<g>
|
||||||
|
<path d="M121.39 116.71l-6.7 5.79c-2.58 2.23-6.5 1.93-8.71-.65L26.57 26l10.21-7.68l85.12 89.77a6.01 6.01 0 0 1-.51 8.62z" fill="#a06841">
|
||||||
|
</path>
|
||||||
|
<path d="M33.83 34.76l18.9 22.82c2.95-4.44 4.45-9.76 5.66-16.48L40.14 21.85a16.546 16.546 0 0 0-3.73 4.31c-1.55 2.61-2.36 5.57-2.58 8.6z" fill="#7d5133">
|
||||||
|
</path>
|
||||||
|
<path d="M71.29 4.94c-17.34-.2-23.76 1.34-33.42 9.69c-2.9 2.5-5.79 5-8.69 7.51c-3.15 2.72-7.34 5.1-6.68 9.8c.24 1.72.77 3.46.45 5.16c-.31 1.61-2.18 2.41-3.51 1.49c-1.25-.86-2.63-1.92-4.17-2.1c-1.44-.16-2.96.29-4.05 1.26L4.5 43.72s-.96 3.91 6.56 12.42s12.36 7.9 12.36 7.9l6.32-5.56c1.06-.93 1.61-2.3 1.58-3.71c-.03-1.65-.99-2.93-1.57-4.41c-.11-.28-.74-1.28.36-2.19c.98-.85 3-.56 4.15-.25c1.15.31 2.25.8 3.41 1.1c2.26.59 3.32-.46 4.89-1.81c1.39-1.2 9.76-8.43 12.55-10.85c5.57-4.82-2.92-13.26-2.92-13.26c-4-4.53 20.27-15.92 20.27-15.92c1.78-.62 1.24-2.22-1.17-2.24z" fill="#82aec0">
|
||||||
|
</path>
|
||||||
|
<path d="M37.68 49.03c.47.12.88.16 1.26.15v-.19c-.1-1.08-.69-2.06-1.29-2.97A64.622 64.622 0 0 0 23.9 30.96c-.44-.35-.9-.7-1.38-1c-.1.61-.12 1.27-.02 1.98c.24 1.72.77 3.46.45 5.16c-.34 1.76-2.18 2.25-3.59 1.59c3.67 2.37 6.81 5.53 9.1 9.25c.31.5.62 1.03.98 1.51c.05-.49.27-.96.68-1.27c.98-.85 3-.56 4.15-.25c1.16.3 2.25.79 3.41 1.1z" fill="#2f7889">
|
||||||
|
</path>
|
||||||
|
<path d="M17 51.15c5.27 5.51 8.23 11.22 6.61 12.77c-1.61 1.54-7.19-1.67-12.46-7.17S2.89 45.27 4.5 43.72c1.61-1.54 7.23 1.92 12.5 7.43z" fill="#2f7889">
|
||||||
|
</path>
|
||||||
|
<path d="M37.51 22.68c4.19-1.78 7.92-5.6 12.8-9.81c1.39-1.2 3.16-2.34 4.97-3.2c.68-.32.43-1.34-.32-1.33c-2.51.04-4.75.8-6.95 1.76c-3.08 1.34-5.8 3.37-8.42 5.47c-1.8 1.44-6.02 5-8.68 7.25c-.5.42-.11 1.08.54 1.08c1.87.02 2.92.12 6.06-1.22z" fill="#b9e4ea">
|
||||||
|
</path>
|
||||||
|
<path d="M11.12 40.16c-1.77 1.99.49 2.53 4.46 5.81c2.8 2.32 5.78.17 5.81-2c.02-1.95-.47-3-3.3-4.78s-5.37-.83-6.97.97z" fill="#b9e4ea">
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
|
||||||
|
<title>javascript [#155]</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
|
||||||
|
</defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Dribbble-Light-Preview" transform="translate(-420.000000, -7479.000000)" fill="#000000">
|
||||||
|
<g id="icons" transform="translate(56.000000, 160.000000)">
|
||||||
|
<path d="M379.328,7337.432 C377.583,7337.432 376.455,7336.6 375.905,7335.512 L375.905,7335.512 L377.435,7334.626 C377.838,7335.284 378.361,7335.767 379.288,7335.767 C380.066,7335.767 380.563,7335.378 380.563,7334.841 C380.563,7334.033 379.485,7333.717 378.724,7333.391 C377.368,7332.814 376.468,7332.089 376.468,7330.558 C376.468,7329.149 377.542,7328.075 379.221,7328.075 C380.415,7328.075 381.275,7328.491 381.892,7329.578 L380.429,7330.518 C380.107,7329.941 379.758,7329.713 379.221,7329.713 C378.67,7329.713 378.321,7330.062 378.321,7330.518 C378.321,7331.082 378.67,7331.31 379.476,7331.659 C381.165,7332.383 382.443,7332.952 382.443,7334.814 C382.443,7336.506 381.114,7337.432 379.328,7337.432 L379.328,7337.432 Z M375,7334.599 C375,7336.546 373.801,7337.575 372.136,7337.575 C370.632,7337.575 369.731,7337 369.288,7336 L369.273,7336 L369.266,7336 L369.262,7336 L370.791,7334.931 C371.086,7335.454 371.352,7335.825 371.996,7335.825 C372.614,7335.825 373,7335.512 373,7334.573 L373,7328 L375,7328 L375,7334.599 Z M364,7339 L384,7339 L384,7319 L364,7319 L364,7339 Z" id="javascript-[#155]">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2500" viewBox="0 0 1052 1052"><path fill="#f0db4f" d="M0 0h1052v1052H0z"/><path d="M965.9 801.1c-7.7-48-39-88.3-131.7-125.9-32.2-14.8-68.1-25.399-78.8-49.8-3.8-14.2-4.3-22.2-1.9-30.8 6.9-27.9 40.2-36.6 66.6-28.6 17 5.7 33.1 18.801 42.8 39.7 45.4-29.399 45.3-29.2 77-49.399-11.6-18-17.8-26.301-25.4-34-27.3-30.5-64.5-46.2-124-45-10.3 1.3-20.699 2.699-31 4-29.699 7.5-58 23.1-74.6 44-49.8 56.5-35.6 155.399 25 196.1 59.7 44.8 147.4 55 158.6 96.9 10.9 51.3-37.699 67.899-86 62-35.6-7.4-55.399-25.5-76.8-58.4-39.399 22.8-39.399 22.8-79.899 46.1 9.6 21 19.699 30.5 35.8 48.7 76.2 77.3 266.899 73.5 301.1-43.5 1.399-4.001 10.6-30.801 3.199-72.101zm-394-317.6h-98.4c0 85-.399 169.4-.399 254.4 0 54.1 2.8 103.7-6 118.9-14.4 29.899-51.7 26.2-68.7 20.399-17.3-8.5-26.1-20.6-36.3-37.699-2.8-4.9-4.9-8.7-5.601-9-26.699 16.3-53.3 32.699-80 49 13.301 27.3 32.9 51 58 66.399 37.5 22.5 87.9 29.4 140.601 17.3 34.3-10 63.899-30.699 79.399-62.199 22.4-41.3 17.6-91.3 17.4-146.6.5-90.2 0-180.4 0-270.9z" fill="#323330"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 512.004 512.004" xml:space="preserve">
|
||||||
|
<rect x="39.151" style="fill:#00DDC0;" width="113.52" height="512.004"/>
|
||||||
|
<rect x="95.911" style="fill:#00AC93;" width="56.76" height="512.004"/>
|
||||||
|
<rect x="152.671" style="fill:#FFAD1D;" width="113.52" height="512.004"/>
|
||||||
|
<rect x="209.442" style="fill:#FF8900;" width="56.76" height="512.004"/>
|
||||||
|
<rect x="39.151" y="437.501" style="fill:#006659;" width="113.52" height="74.493"/>
|
||||||
|
<rect x="95.911" y="437.501" style="fill:#005349;" width="56.76" height="74.493"/>
|
||||||
|
<rect x="152.671" y="437.501" style="fill:#FF4F18;" width="113.52" height="74.493"/>
|
||||||
|
<rect x="209.442" y="437.501" style="fill:#FF3400;" width="56.76" height="74.493"/>
|
||||||
|
<rect x="315.22" y="9.326" transform="matrix(-0.9806 0.1962 -0.1962 -0.9806 783.7764 437.2287)" style="fill:#00A5FF;" width="110.026" height="496.216"/>
|
||||||
|
<rect x="369.697" y="3.927" transform="matrix(-0.9806 0.1962 -0.1962 -0.9806 836.1343 421.2428)" style="fill:#0082D2;" width="55.013" height="496.216"/>
|
||||||
|
<polygon style="fill:#006DF3;" points="472.845,489.894 364.963,511.484 350.333,438.373 458.261,417.012 "/>
|
||||||
|
<polygon style="fill:#005FD1;" points="472.845,489.894 418.905,500.689 404.297,427.692 458.261,417.012 "/>
|
||||||
|
<rect x="73.978" y="51.2" style="fill:#FFFFFF;" width="44.522" height="33.391"/>
|
||||||
|
<rect x="96.236" y="51.2" style="fill:#E1E1E4;" width="22.261" height="33.391"/>
|
||||||
|
<rect x="187.509" y="51.2" style="fill:#FFFFFF;" width="44.522" height="33.391"/>
|
||||||
|
<rect x="209.442" y="51.2" style="fill:#E1E1E4;" width="22.594" height="33.391"/>
|
||||||
|
<rect x="318.886" y="59.425" transform="matrix(-0.1896 -0.9819 0.9819 -0.1896 319.6778 425.8492)" style="fill:#FFFFFF;" width="33.392" height="43.145"/>
|
||||||
|
<polygon style="fill:#E1E1E4;" points="338.215,97.479 331.662,64.735 353.595,60.502 359.925,93.287 "/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="24" cy="24" r="20" fill="#0077B5"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.7747 14.2839C18.7747 15.529 17.8267 16.5366 16.3442 16.5366C14.9194 16.5366 13.9713 15.529 14.0007 14.2839C13.9713 12.9783 14.9193 12 16.3726 12C17.8267 12 18.7463 12.9783 18.7747 14.2839ZM14.1199 32.8191V18.3162H18.6271V32.8181H14.1199V32.8191Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.2393 22.9446C22.2393 21.1357 22.1797 19.5935 22.1201 18.3182H26.0351L26.2432 20.305H26.3322C26.9254 19.3854 28.4079 17.9927 30.8101 17.9927C33.7752 17.9927 35.9995 19.9502 35.9995 24.219V32.821H31.4922V24.7838C31.4922 22.9144 30.8404 21.6399 29.2093 21.6399C27.9633 21.6399 27.2224 22.4999 26.9263 23.3297C26.8071 23.6268 26.7484 24.0412 26.7484 24.4574V32.821H22.2411V22.9446H22.2393Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9 12C9 13.3807 7.88071 14.5 6.5 14.5C5.11929 14.5 4 13.3807 4 12C4 10.6193 5.11929 9.5 6.5 9.5C7.88071 9.5 9 10.6193 9 12Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||||
|
<path d="M14 6.5L9 10" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M14 17.5L9 14" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M19 18.5C19 19.8807 17.8807 21 16.5 21C15.1193 21 14 19.8807 14 18.5C14 17.1193 15.1193 16 16.5 16C17.8807 16 19 17.1193 19 18.5Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||||
|
<path d="M19 5.5C19 6.88071 17.8807 8 16.5 8C15.1193 8 14 6.88071 14 5.5C14 4.11929 15.1193 3 16.5 3C17.8807 3 19 4.11929 19 5.5Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 922 B |
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<g id="SVGRepo_iconCarrier"> <path fill-rule="evenodd" clip-rule="evenodd" d="M13.803 5.33333C13.803 3.49238 15.3022 2 17.1515 2C19.0008 2 20.5 3.49238 20.5 5.33333C20.5 7.17428 19.0008 8.66667 17.1515 8.66667C16.2177 8.66667 15.3738 8.28596 14.7671 7.67347L10.1317 10.8295C10.1745 11.0425 10.197 11.2625 10.197 11.4872C10.197 11.9322 10.109 12.3576 9.94959 12.7464L15.0323 16.0858C15.6092 15.6161 16.3473 15.3333 17.1515 15.3333C19.0008 15.3333 20.5 16.8257 20.5 18.6667C20.5 20.5076 19.0008 22 17.1515 22C15.3022 22 13.803 20.5076 13.803 18.6667C13.803 18.1845 13.9062 17.7255 14.0917 17.3111L9.05007 13.9987C8.46196 14.5098 7.6916 14.8205 6.84848 14.8205C4.99917 14.8205 3.5 13.3281 3.5 11.4872C3.5 9.64623 4.99917 8.15385 6.84848 8.15385C7.9119 8.15385 8.85853 8.64725 9.47145 9.41518L13.9639 6.35642C13.8594 6.03359 13.803 5.6896 13.803 5.33333Z" fill="#000000"/> </g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
|
@ -0,0 +1,106 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 512 512" xml:space="preserve">
|
||||||
|
<circle style="fill:#8AD5DD;" cx="256" cy="256" r="256"/>
|
||||||
|
<rect id="SVGCleanerId_0" x="115" y="68" style="fill:#FFFFFF;" width="282" height="376"/>
|
||||||
|
<rect id="SVGCleanerId_1" x="146.336" y="339.8" style="fill:#2D2D2D;" width="97.696" height="8"/>
|
||||||
|
<rect id="SVGCleanerId_2" x="146.336" y="371.12" style="fill:#E0E0E0;" width="97.696" height="8"/>
|
||||||
|
<rect id="SVGCleanerId_3" x="146.336" y="386.8" style="fill:#E0E0E0;" width="97.696" height="8"/>
|
||||||
|
<rect id="SVGCleanerId_4" x="146.336" y="402.48" style="fill:#E0E0E0;" width="97.696" height="8"/>
|
||||||
|
<path id="SVGCleanerId_5" style="fill:#DB2B42;" d="M355.872,316.384H156.128c-6.464,0-11.752-5.288-11.752-11.752v-144.92
|
||||||
|
c0-6.464,5.288-11.752,11.752-11.752H355.88c6.464,0,11.752,5.288,11.752,11.752v144.92
|
||||||
|
C367.624,311.096,362.336,316.384,355.872,316.384z"/>
|
||||||
|
<g>
|
||||||
|
<rect id="SVGCleanerId_0_1_" x="115" y="68" style="fill:#FFFFFF;" width="282" height="376"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect id="SVGCleanerId_1_1_" x="146.336" y="339.8" style="fill:#2D2D2D;" width="97.696" height="8"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect id="SVGCleanerId_2_1_" x="146.336" y="371.12" style="fill:#E0E0E0;" width="97.696" height="8"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect id="SVGCleanerId_3_1_" x="146.336" y="386.8" style="fill:#E0E0E0;" width="97.696" height="8"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect id="SVGCleanerId_4_1_" x="146.336" y="402.48" style="fill:#E0E0E0;" width="97.696" height="8"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path id="SVGCleanerId_5_1_" style="fill:#DB2B42;" d="M355.872,316.384H156.128c-6.464,0-11.752-5.288-11.752-11.752v-144.92
|
||||||
|
c0-6.464,5.288-11.752,11.752-11.752H355.88c6.464,0,11.752,5.288,11.752,11.752v144.92
|
||||||
|
C367.624,311.096,362.336,316.384,355.872,316.384z"/>
|
||||||
|
</g>
|
||||||
|
<polygon style="fill:#FFFFFF;" points="232.496,191.472 303,232.176 232.496,272.88 "/>
|
||||||
|
<g>
|
||||||
|
<rect x="269.928" y="339.04" style="fill:#E0E0E0;" width="97.696" height="19.016"/>
|
||||||
|
<rect x="269.928" y="365.624" style="fill:#E0E0E0;" width="97.696" height="19.016"/>
|
||||||
|
<rect x="269.928" y="392.24" style="fill:#E0E0E0;" width="97.696" height="19.016"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect x="269.928" y="392.24" style="fill:#2D2D2D;" width="20.304" height="19.016"/>
|
||||||
|
<rect x="269.928" y="365.624" style="fill:#2D2D2D;" width="20.304" height="19.016"/>
|
||||||
|
<rect x="269.928" y="339.04" style="fill:#2D2D2D;" width="20.304" height="19.016"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#E0E0E0;" d="M365.664,100.072v19.616h-135.24v-19.616H365.664 M367.624,98.112h-139.16v23.536h139.16
|
||||||
|
L367.624,98.112L367.624,98.112z"/>
|
||||||
|
<rect x="341.312" y="98.112" style="fill:#E0E0E0;" width="26.312" height="23.536"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:#DB2B42;" d="M207.656,121.648H173.2c-1.312,0-2.384-1.072-2.384-2.384v-18.768c0-1.312,1.072-2.384,2.384-2.384
|
||||||
|
h34.456c1.312,0,2.384,1.072,2.384,2.384v18.768C210.04,120.576,208.96,121.648,207.656,121.648z"/>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFFFFF;" d="M178.008,107.264h-2.784v-1.488h7.384v1.488h-2.816v8.28h-1.784L178.008,107.264L178.008,107.264z"
|
||||||
|
/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M189.28,113.424c0,0.84,0.032,1.544,0.056,2.12h-1.568l-0.088-1.08h-0.024
|
||||||
|
c-0.304,0.504-1,1.232-2.264,1.232c-1.288,0-2.464-0.768-2.464-3.072v-4.144h1.784v3.84c0,1.176,0.384,1.936,1.32,1.936
|
||||||
|
c0.712,0,1.176-0.504,1.36-0.96c0.064-0.16,0.104-0.344,0.104-0.552v-4.264h1.784V113.424z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M191.144,115.544c0.032-0.48,0.064-1.264,0.064-1.984v-8.312h1.784v4.2h0.032
|
||||||
|
c0.432-0.672,1.2-1.128,2.256-1.128c1.728,0,2.96,1.432,2.944,3.584c0,2.536-1.608,3.8-3.208,3.8c-0.912,0-1.728-0.352-2.232-1.216
|
||||||
|
h-0.032l-0.088,1.056H191.144z M192.992,112.632c0,0.152,0.016,0.288,0.04,0.424c0.192,0.712,0.808,1.256,1.584,1.256
|
||||||
|
c1.12,0,1.8-0.904,1.8-2.32c0-1.256-0.592-2.264-1.784-2.264c-0.72,0-1.376,0.528-1.584,1.304
|
||||||
|
c-0.024,0.128-0.056,0.288-0.056,0.464V112.632z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M200.952,112.512c0.04,1.28,1.04,1.832,2.176,1.832c0.824,0,1.416-0.12,1.96-0.32l0.264,1.232
|
||||||
|
c-0.616,0.248-1.456,0.44-2.472,0.44c-2.288,0-3.64-1.408-3.64-3.576c0-1.952,1.192-3.8,3.448-3.8c2.296,0,3.048,1.888,3.048,3.44
|
||||||
|
c0,0.336-0.032,0.592-0.056,0.752C205.68,112.512,200.952,112.512,200.952,112.512z M204.056,111.264
|
||||||
|
c0.016-0.648-0.272-1.728-1.464-1.728c-1.104,0-1.568,1.008-1.64,1.728H204.056z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#2D2D2D;" d="M147.464,115.544v-4.072l-3.088-5.696h2.032l1.176,2.504c0.336,0.728,0.584,1.28,0.848,1.944h0.024
|
||||||
|
c0.248-0.624,0.52-1.232,0.856-1.944l1.176-2.504h2.016l-3.248,5.656v4.112H147.464z"/>
|
||||||
|
<path style="fill:#2D2D2D;" d="M158.864,111.944c0,2.6-1.824,3.76-3.624,3.76c-2,0-3.536-1.376-3.536-3.632
|
||||||
|
c0-2.328,1.52-3.744,3.656-3.744C157.464,108.32,158.864,109.8,158.864,111.944z M153.544,112.024c0,1.36,0.664,2.392,1.752,2.392
|
||||||
|
c1.016,0,1.728-1,1.728-2.424c0-1.104-0.496-2.368-1.712-2.368C154.056,109.624,153.544,110.84,153.544,112.024z"/>
|
||||||
|
<path style="fill:#2D2D2D;" d="M166.656,113.424c0,0.84,0.032,1.544,0.056,2.12h-1.568l-0.088-1.08h-0.024
|
||||||
|
c-0.304,0.504-1,1.232-2.264,1.232c-1.288,0-2.464-0.768-2.464-3.072v-4.144h1.784v3.84c0,1.176,0.384,1.936,1.32,1.936
|
||||||
|
c0.712,0,1.176-0.504,1.36-0.96c0.064-0.16,0.104-0.344,0.104-0.552v-4.264h1.784V113.424z"/>
|
||||||
|
<path style="fill:#2D2D2D;" d="M353.936,113.776c-3.096,0-5.616-2.52-5.616-5.616s2.52-5.616,5.616-5.616s5.616,2.52,5.616,5.616
|
||||||
|
C359.552,111.256,357.032,113.776,353.936,113.776z M353.936,103.816c-2.392,0-4.344,1.952-4.344,4.344s1.952,4.344,4.344,4.344
|
||||||
|
s4.344-1.952,4.344-4.344S356.328,103.816,353.936,103.816z"/>
|
||||||
|
|
||||||
|
<rect x="356.02" y="113.813" transform="matrix(-0.6044 -0.7967 0.7967 -0.6044 484.7665 469.5985)" style="fill:#2D2D2D;" width="5.896" height="1.272"/>
|
||||||
|
<path style="fill:#2D2D2D;" d="M240.088,113.168c0.48,0.296,1.184,0.544,1.92,0.544c1.096,0,1.736-0.576,1.736-1.416
|
||||||
|
c0-0.768-0.44-1.216-1.56-1.648c-1.352-0.48-2.192-1.184-2.192-2.352c0-1.296,1.072-2.248,2.68-2.248
|
||||||
|
c0.848,0,1.464,0.192,1.832,0.4l-0.296,0.872c-0.272-0.152-0.824-0.392-1.576-0.392c-1.136,0-1.56,0.672-1.56,1.24
|
||||||
|
c0,0.776,0.504,1.152,1.648,1.6c1.4,0.544,2.12,1.216,2.12,2.432c0,1.28-0.952,2.392-2.904,2.392c-0.8,0-1.68-0.24-2.12-0.528
|
||||||
|
L240.088,113.168z"/>
|
||||||
|
<path style="fill:#2D2D2D;" d="M246.984,111.688c0.024,1.472,0.96,2.064,2.048,2.064c0.776,0,1.24-0.128,1.648-0.304l0.184,0.776
|
||||||
|
c-0.384,0.176-1.032,0.368-1.984,0.368c-1.832,0-2.928-1.208-2.928-3s1.056-3.216,2.792-3.216c1.952,0,2.464,1.704,2.464,2.808
|
||||||
|
c0,0.216-0.024,0.392-0.04,0.496h-4.184V111.688z M250.16,110.92c0.016-0.688-0.288-1.76-1.504-1.76
|
||||||
|
c-1.096,0-1.576,1.008-1.664,1.76H250.16z"/>
|
||||||
|
<path style="fill:#2D2D2D;" d="M255.88,114.472l-0.088-0.752h-0.032c-0.336,0.472-0.968,0.888-1.824,0.888
|
||||||
|
c-1.208,0-1.824-0.848-1.824-1.704c0-1.448,1.28-2.232,3.584-2.216v-0.128c0-0.488-0.136-1.384-1.36-1.384
|
||||||
|
c-0.552,0-1.128,0.176-1.552,0.448l-0.248-0.72c0.488-0.312,1.208-0.528,1.96-0.528c1.824,0,2.264,1.24,2.264,2.432v2.232
|
||||||
|
c0,0.52,0.024,1.024,0.104,1.424h-0.984V114.472z M255.72,111.44c-1.184-0.024-2.528,0.184-2.528,1.336
|
||||||
|
c0,0.704,0.472,1.032,1.024,1.032c0.776,0,1.272-0.488,1.44-1c0.04-0.112,0.064-0.24,0.064-0.344V111.44z"/>
|
||||||
|
<path style="fill:#2D2D2D;" d="M258.552,110.376c0-0.704-0.016-1.304-0.056-1.864h0.944l0.04,1.176h0.04
|
||||||
|
c0.272-0.8,0.928-1.304,1.656-1.304c0.12,0,0.2,0.008,0.304,0.032v1.024c-0.112-0.024-0.224-0.032-0.368-0.032
|
||||||
|
c-0.768,0-1.312,0.576-1.456,1.392c-0.024,0.144-0.056,0.312-0.056,0.496v3.176h-1.072v-4.096H258.552z"/>
|
||||||
|
<path style="fill:#2D2D2D;" d="M266.72,114.256c-0.288,0.152-0.912,0.344-1.72,0.344c-1.792,0-2.968-1.216-2.968-3.04
|
||||||
|
c0-1.84,1.264-3.168,3.208-3.168c0.64,0,1.208,0.168,1.504,0.304l-0.248,0.84c-0.264-0.152-0.664-0.28-1.264-0.28
|
||||||
|
c-1.368,0-2.104,1.008-2.104,2.248c0,1.384,0.888,2.232,2.064,2.232c0.616,0,1.024-0.168,1.328-0.296L266.72,114.256z"/>
|
||||||
|
<path style="fill:#2D2D2D;" d="M268.016,105.736h1.08v3.712h0.024c0.176-0.304,0.448-0.576,0.776-0.76
|
||||||
|
c0.312-0.184,0.704-0.304,1.104-0.304c0.808,0,2.08,0.488,2.08,2.544v3.544H272v-3.424c0-0.96-0.352-1.768-1.376-1.768
|
||||||
|
c-0.704,0-1.264,0.488-1.456,1.08c-0.064,0.152-0.08,0.304-0.08,0.52v3.6h-1.08v-8.744H268.016z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 73 73" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
|
||||||
|
<title>build-tools/typescript</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
<rect id="path-1" x="4" y="4" width="69" height="69" rx="14">
|
||||||
|
</rect>
|
||||||
|
</defs>
|
||||||
|
<g id="build-tools/typescript" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="container" transform="translate(-2.000000, -2.000000)">
|
||||||
|
<rect id="mask" stroke="#003355" stroke-width="2" fill="#FFFFFF" fill-rule="nonzero" x="3" y="3" width="71" height="71" rx="14">
|
||||||
|
</rect>
|
||||||
|
<mask id="mask-2" fill="white">
|
||||||
|
<use xlink:href="#path-1">
|
||||||
|
</use>
|
||||||
|
</mask>
|
||||||
|
<rect stroke="#003355" stroke-width="2" x="3" y="3" width="71" height="71" rx="14">
|
||||||
|
</rect>
|
||||||
|
<g id="logo" mask="url(#mask-2)" fill="#007ACC" fill-rule="nonzero">
|
||||||
|
<g id="Group" transform="translate(36.500000, 36.500000) scale(-1, 1) rotate(-180.000000) translate(-36.500000, -36.500000) ">
|
||||||
|
<path d="M0,36.5 L0,0 L36.5,0 L73,0 L73,36.5 L73,73 L36.5,73 L0,73 L0,36.5 Z M58.8287302,39.4084127 C60.6826984,38.9449206 62.0963492,38.1222222 63.394127,36.7780952 C64.0661905,36.0596825 65.0626984,34.7503175 65.1438095,34.4374603 C65.1669841,34.3447619 61.9920635,32.2126984 60.0685714,31.0192063 C59.9990476,30.9728571 59.7209524,31.274127 59.4080952,31.737619 C58.4695238,33.1049206 57.4846032,33.695873 55.978254,33.8001587 C53.7650794,33.9507937 52.3398413,32.7920635 52.3514286,30.8569841 C52.3514286,30.2892063 52.4325397,29.9531746 52.6642857,29.4896825 C53.1509524,28.4815873 54.0547619,27.8790476 56.8936508,26.6507937 C62.1195238,24.4028571 64.355873,22.9196825 65.7463492,20.8107937 C67.2990476,18.4585714 67.6466667,14.7042857 66.5922222,11.911746 C65.4334921,8.87587302 62.5598413,6.81333333 58.515873,6.12968254 C57.2644444,5.90952381 54.2980952,5.94428571 52.9539683,6.18761905 C50.022381,6.70904762 47.2414286,8.15746032 45.5265079,10.0577778 C44.8544444,10.7993651 43.5450794,12.7344444 43.6261905,12.8734921 C43.6609524,12.9198413 43.9622222,13.1052381 44.298254,13.3022222 C44.6226984,13.487619 45.8509524,14.1944444 47.0096825,14.8665079 L49.1069841,16.0831746 L49.5473016,15.4342857 C50.1614286,14.4957143 51.5055556,13.2095238 52.3166667,12.7807937 C54.6457143,11.5525397 57.8438095,11.7263492 59.4196825,13.14 C60.091746,13.754127 60.3698413,14.3914286 60.3698413,15.33 C60.3698413,16.175873 60.2655556,16.5466667 59.8252381,17.1839683 C59.2574603,17.9950794 58.0987302,18.6787302 54.8079365,20.1039683 C51.0420635,21.7261905 49.4198413,22.7342857 47.9366667,24.3333333 C47.0792063,25.2603175 46.2680952,26.7434921 45.9320635,27.9833333 C45.6539683,29.0146032 45.5844444,31.5985714 45.8046032,32.6414286 C46.5809524,36.2798413 49.3271429,38.8174603 53.29,39.5706349 C54.5761905,39.8139683 57.5657143,39.7212698 58.8287302,39.4084127 Z M41.6911111,36.3609524 L41.7142857,33.3714286 L36.9634921,33.3714286 L32.2126984,33.3714286 L32.2126984,19.8722222 L32.2126984,6.37301587 L28.852381,6.37301587 L25.4920635,6.37301587 L25.4920635,19.8722222 L25.4920635,33.3714286 L20.7412698,33.3714286 L15.9904762,33.3714286 L15.9904762,36.3030159 C15.9904762,37.9252381 16.0252381,39.2809524 16.0715873,39.3157143 C16.1063492,39.3620635 21.8884127,39.3852381 28.8987302,39.3736508 L41.6563492,39.3388889 L41.6911111,36.3609524 Z" id="Shape">
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5 5.5C5 4.11929 6.11929 3 7.5 3C8.88071 3 10 4.11929 10 5.5C10 6.88071 8.88071 8 7.5 8C6.11929 8 5 6.88071 5 5.5Z" fill="#000000"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 0C3.35786 0 0 3.35786 0 7.5C0 11.6421 3.35786 15 7.5 15C11.6421 15 15 11.6421 15 7.5C15 3.35786 11.6421 0 7.5 0ZM1 7.5C1 3.91015 3.91015 1 7.5 1C11.0899 1 14 3.91015 14 7.5C14 9.34956 13.2275 11.0187 11.9875 12.2024C11.8365 10.4086 10.3328 9 8.5 9H6.5C4.66724 9 3.16345 10.4086 3.01247 12.2024C1.77251 11.0187 1 9.34956 1 7.5Z" fill="#000000"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 768 B |
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M27 4H5C3.34315 4 2 5.34315 2 7V25C2 26.6569 3.34315 28 5 28H27C28.6569 28 30 26.6569 30 25V7C30 5.34315 28.6569 4 27 4Z" fill="#B71C1C"/>
|
||||||
|
<path d="M25 24H7C6.73478 24 6.48043 23.8946 6.29289 23.7071C6.10536 23.5196 6 23.2652 6 23C6 22.7348 6.10536 22.4804 6.29289 22.2929C6.48043 22.1054 6.73478 22 7 22H25C25.2652 22 25.5196 22.1054 25.7071 22.2929C25.8946 22.4804 26 22.7348 26 23C26 23.2652 25.8946 23.5196 25.7071 23.7071C25.5196 23.8946 25.2652 24 25 24Z" fill="#EEEEEE"/>
|
||||||
|
<path d="M19 25C18.7348 25 18.4804 24.8946 18.2929 24.7071C18.1054 24.5196 18 24.2652 18 24V22C18 21.7348 18.1054 21.4804 18.2929 21.2929C18.4804 21.1054 18.7348 21 19 21C19.2652 21 19.5196 21.1054 19.7071 21.2929C19.8946 21.4804 20 21.7348 20 22V24C20 24.2652 19.8946 24.5196 19.7071 24.7071C19.5196 24.8946 19.2652 25 19 25Z" fill="#EEEEEE"/>
|
||||||
|
<path d="M20.45 12.67L13.45 9.16996C13.2978 9.09325 13.1285 9.05673 12.9581 9.06386C12.7878 9.071 12.6222 9.12155 12.4769 9.21072C12.3316 9.2999 12.2115 9.42473 12.1281 9.57336C12.0446 9.722 12.0005 9.8895 12 10.06V17.94C12.0013 18.1182 12.0502 18.2928 12.1416 18.4457C12.233 18.5987 12.3637 18.7244 12.52 18.81C12.6648 18.897 12.831 18.942 13 18.94C13.1872 18.9406 13.3709 18.8886 13.53 18.79L20.53 14.41C20.6816 14.3156 20.8051 14.1823 20.8877 14.024C20.9704 13.8658 21.0091 13.6883 21 13.51C20.9905 13.3339 20.9347 13.1635 20.8381 13.0159C20.7415 12.8684 20.6076 12.7491 20.45 12.67Z" fill="#EEEEEE"/>
|
||||||
|
<path d="M5 4C4.20435 4 3.44129 4.31607 2.87868 4.87868C2.31607 5.44129 2 6.20435 2 7V25C2 25.7956 2.31607 26.5587 2.87868 27.1213C3.44129 27.6839 4.20435 28 5 28H16V4H5Z" fill="#E53935"/>
|
||||||
|
<path d="M7 22C6.73478 22 6.48043 22.1054 6.29289 22.2929C6.10536 22.4804 6 22.7348 6 23C6 23.2652 6.10536 23.5196 6.29289 23.7071C6.48043 23.8946 6.73478 24 7 24H16V22H7Z" fill="#FAFAFA"/>
|
||||||
|
<path d="M13.45 9.16996C13.2978 9.09325 13.1285 9.05673 12.9581 9.06386C12.7878 9.071 12.6222 9.12155 12.4769 9.21072C12.3316 9.2999 12.2115 9.42473 12.1281 9.57336C12.0446 9.722 12.0005 9.8895 12 10.06V17.94C12.0013 18.1182 12.0502 18.2928 12.1416 18.4457C12.233 18.5987 12.3637 18.7244 12.52 18.81C12.6648 18.897 12.831 18.942 13 18.94C13.1872 18.9406 13.3709 18.8886 13.53 18.79L16 17.24V10.44L13.45 9.16996Z" fill="#FFEBEE"/>
|
||||||
|
<path d="M27 4H5C4.20435 4 3.44129 4.31607 2.87868 4.87868C2.31607 5.44129 2 6.20435 2 7V25C2 25.7956 2.31607 26.5587 2.87868 27.1213C3.44129 27.6839 4.20435 28 5 28H27C27.7956 28 28.5587 27.6839 29.1213 27.1213C29.6839 26.5587 30 25.7956 30 25V7C30 6.20435 29.6839 5.44129 29.1213 4.87868C28.5587 4.31607 27.7956 4 27 4ZM28 25C28 25.2652 27.8946 25.5196 27.7071 25.7071C27.5196 25.8946 27.2652 26 27 26H5C4.73478 26 4.48043 25.8946 4.29289 25.7071C4.10536 25.5196 4 25.2652 4 25V7C4 6.73478 4.10536 6.48043 4.29289 6.29289C4.48043 6.10536 4.73478 6 5 6H27C27.2652 6 27.5196 6.10536 27.7071 6.29289C27.8946 6.48043 28 6.73478 28 7V25Z" fill="#263238"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 512"><path d="M256 0c141.385 0 256 114.615 256 256S397.385 512 256 512 0 397.385 0 256 114.615 0 256 0z"/><path fill="#fff" fill-rule="nonzero" d="M318.64 157.549h33.401l-72.973 83.407 85.85 113.495h-67.222l-52.647-68.836-60.242 68.836h-33.423l78.052-89.212-82.354-107.69h68.924l47.59 62.917 55.044-62.917zm-11.724 176.908h18.51L205.95 176.493h-19.86l120.826 157.964z"/></svg>
|
||||||
|
After Width: | Height: | Size: 580 B |
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"TodoTest": "Doing Tests",
|
||||||
|
"HomePage": "Home",
|
||||||
|
"contact": "Contact",
|
||||||
|
"about": "About",
|
||||||
|
"login": "Login"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"TodoTest": "Haciendo pruebas",
|
||||||
|
"HomePage": "Página principal",
|
||||||
|
"contact": "Contacto",
|
||||||
|
"about": "Acerca de la página",
|
||||||
|
"login": "Identificarse"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"HomePage": "HomePage",
|
||||||
|
"contact": "contact",
|
||||||
|
"about": "about",
|
||||||
|
"login": "login"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// import NextAuth from 'next-auth'
|
||||||
|
// import { authOptions } from '../../../utils/auth'
|
||||||
|
|
||||||
|
// export default NextAuth(authOptions)
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
type Data = {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<Data>
|
||||||
|
) {
|
||||||
|
res.status(200).json({ name: 'John Doe' })
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { FaSearch } from 'react-icons/fa'
|
||||||
|
import { Post } from '../../../type'
|
||||||
|
|
||||||
|
const AutoCompleteInput = ({
|
||||||
|
textSearched,
|
||||||
|
setTextSearched,
|
||||||
|
posts
|
||||||
|
}: {
|
||||||
|
textSearched: string
|
||||||
|
setTextSearched: (text: string) => void
|
||||||
|
posts: Post[]
|
||||||
|
}) => {
|
||||||
|
const [showPosts, setShowPosts] = useState(false)
|
||||||
|
const router = useRouter()
|
||||||
|
const filteredPosts = posts.filter((post) => {
|
||||||
|
const searchText = textSearched.toLowerCase()
|
||||||
|
const postTitle = post.title.toLowerCase()
|
||||||
|
const searchWords = searchText.split(' ')
|
||||||
|
|
||||||
|
return searchWords.every((word) => postTitle.includes(word))
|
||||||
|
})
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setTextSearched(e.target.value)
|
||||||
|
setShowPosts(!!e.target.value)
|
||||||
|
}
|
||||||
|
const highlightMatches = (text: string) => {
|
||||||
|
const searchText = textSearched.toLowerCase()
|
||||||
|
const regex = new RegExp(`(${searchText})`, 'gi')
|
||||||
|
return text.replace(regex, '<strong>$1</strong>')
|
||||||
|
}
|
||||||
|
const handlePostClick = (slug: string) => {
|
||||||
|
router.push(`/post/${slug}`)
|
||||||
|
setShowPosts(false)
|
||||||
|
}
|
||||||
|
const noResults = showPosts && filteredPosts.length === 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full max-w-lg" data-te-input-wrapper-init id="basic">
|
||||||
|
<div className="relative ">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="peer block min-h-[auto] w-full py-2 px-4 rounded-lg border border-black "
|
||||||
|
placeholder="Busca por títulos..."
|
||||||
|
value={textSearched}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<span className="absolute md:flex hidden inset-y-0 right-4 pl-3 items-center pointer-events-none">
|
||||||
|
<FaSearch className="h-5 w-5 text-gray-400" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{noResults && (
|
||||||
|
<div className="absolute top-full left-0 w-full bg-white shadow-lg border border-gray-200 rounded-lg z-10 p-4">
|
||||||
|
No hay coincidencias.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showPosts && (
|
||||||
|
<div className="absolute top-full left-0 w-full bg-white shadow-lg border border-gray-200 rounded-lg z-10 max-h-48 overflow-auto">
|
||||||
|
{filteredPosts.map((post) => (
|
||||||
|
<div
|
||||||
|
key={post.id}
|
||||||
|
className="py-2 px-4 hover:bg-gray-100 cursor-pointer"
|
||||||
|
onClick={() => handlePostClick(post.slug)}
|
||||||
|
>
|
||||||
|
<p dangerouslySetInnerHTML={{ __html: highlightMatches(post.title) }} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AutoCompleteInput
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import ReactLoading from 'react-loading'
|
||||||
|
|
||||||
|
type ButtonProps = {
|
||||||
|
children: React.ReactNode
|
||||||
|
className?: string
|
||||||
|
buttonClassName?: string
|
||||||
|
icon?: React.ReactNode
|
||||||
|
loading?: boolean
|
||||||
|
[others: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Button = ({ children, className, icon, loading, buttonClassName, ...others }: ButtonProps) => {
|
||||||
|
return (
|
||||||
|
<div className={`${className} rounded-md relative overflow-hidden w-fit select-none `}>
|
||||||
|
{loading && (
|
||||||
|
<div className="flex justify-center items-center bg-white opacity-90 absolute inset-0">
|
||||||
|
<ReactLoading type="spin" width={30} height={30} color="black" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button className={`flex items-center ${buttonClassName}`} {...others}>
|
||||||
|
{children}
|
||||||
|
{icon && <span className="ml-2">{icon}</span>}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Disclosure } from '@headlessui/react'
|
||||||
|
import { IoIosArrowForward } from 'react-icons/io'
|
||||||
|
|
||||||
|
export default function DisclosureIndividual({
|
||||||
|
classNameArrow,
|
||||||
|
className,
|
||||||
|
text,
|
||||||
|
children
|
||||||
|
}: {
|
||||||
|
classNameArrow?: string
|
||||||
|
className?: string
|
||||||
|
text?: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="w-full rounded-2xl bg-white p-2">
|
||||||
|
<Disclosure>
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Disclosure.Button
|
||||||
|
className={` ${className} flex w-full justify-between rounded-lg px-4 py-2 text-left text-sm font-medium focus:outline-none focus-visible:ring `}
|
||||||
|
>
|
||||||
|
<span>{text}</span>
|
||||||
|
<IoIosArrowForward className={`${open ? 'rotate-90 transform' : ''} h-5 w-5 ${classNameArrow} `} />
|
||||||
|
</Disclosure.Button>
|
||||||
|
<Disclosure.Panel className=" pb-2 pt-2 text-sm text-gray-500">{children}</Disclosure.Panel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Disclosure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
import { Menu, Transition } from '@headlessui/react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { Fragment, useEffect, useState } from 'react'
|
||||||
|
import ShareWhatsapp from '../../../public/images/IconWhatsapp.svg'
|
||||||
|
import Share from '../../../public/images/shared1.svg'
|
||||||
|
import ShareTwitter from '../../../public/images/xSocial.svg'
|
||||||
|
|
||||||
|
export const DropDownShare = ({
|
||||||
|
slug,
|
||||||
|
id,
|
||||||
|
counTwitter,
|
||||||
|
countWhatsapp
|
||||||
|
}: {
|
||||||
|
slug?: string
|
||||||
|
id: string
|
||||||
|
counTwitter?: number
|
||||||
|
countWhatsapp?: number
|
||||||
|
}) => {
|
||||||
|
const [textShare, setTextShare] = useState('')
|
||||||
|
useEffect(() => {
|
||||||
|
if (slug) {
|
||||||
|
setTextShare(
|
||||||
|
'Tienes que ver este recurso, ' +
|
||||||
|
location.href +
|
||||||
|
'post/' +
|
||||||
|
slug +
|
||||||
|
' lo he encontrado aquí, pásate hay más ' +
|
||||||
|
location.origin
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setTextShare(
|
||||||
|
'Tienes que ver este recurso, ' + location.href + ' lo he encontrado aquí, pásate hay más ' + location.origin
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
{
|
||||||
|
/* <div className="flex items-center justify-center bg-white rounded-full w-8 h-8 hover:bg-opacity-80 hover:cursor-pointer">
|
||||||
|
<FaRegComment className="w-5 h-5" />
|
||||||
|
</div> */
|
||||||
|
}
|
||||||
|
const sharePost = async (postId, platform) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/share', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ postId, platform })
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json()
|
||||||
|
// Actualiza la interfaz de usuario según sea necesario
|
||||||
|
} else {
|
||||||
|
console.error('Error sharing post:', response.statusText)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sharing post:', error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu as="div" className="relative ">
|
||||||
|
<>
|
||||||
|
<Menu.Button className="flex items-center w-5 h-5 p-0">
|
||||||
|
{/* <Menu.Button className="flex items-center bg-white rounded-full border w-8 h-8 p-0"> */}
|
||||||
|
{/* <div className="w-8 h-8 flex items-center justify-center cursor-pointer"> */}
|
||||||
|
<Image quality={100} src={Share} alt={'user'} width={20} height={20} />
|
||||||
|
{/* </div> */}
|
||||||
|
</Menu.Button>
|
||||||
|
<Transition as={Fragment}>
|
||||||
|
<Transition.Child
|
||||||
|
className="absolute top-[18px] left-[17px] w-full z-10"
|
||||||
|
enter="transition ease-out duration-500"
|
||||||
|
enterFrom="opacity-20 -translate-y-10"
|
||||||
|
enterTo="opacity-100 translate-y-0"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
|
leaveTo="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Menu.Items className="bg-white flex flex-col absolute right-0 mt-2 w-40 p-1 rounded-lg z-10 border border-gray-200 ">
|
||||||
|
<Menu.Item>
|
||||||
|
{({ active }) => (
|
||||||
|
<a
|
||||||
|
className={`flex justify-between p-2 rounded-lg cursor-pointer items-center hover:no-underline hover:bg-slate-200 ${
|
||||||
|
active && 'bg-secondary-200'
|
||||||
|
}`}
|
||||||
|
href={`https://twitter.com/intent/tweet?text=${textShare}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
onClick={() => {
|
||||||
|
sharePost(id, 'twitter')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex">
|
||||||
|
<Image quality={100} src={ShareTwitter} alt={'twitter'} width={26} height={26} />
|
||||||
|
<span className="pl-1">Twitter</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs">{counTwitter}</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item>
|
||||||
|
{({ active }) => (
|
||||||
|
<a
|
||||||
|
className={`flex justify-between p-2 rounded-lg cursor-pointer items-center hover:no-underline hover:bg-slate-200 ${
|
||||||
|
active && 'bg-secondary-200'
|
||||||
|
}`}
|
||||||
|
href={`https://api.whatsapp.com/send?text=${textShare}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
onClick={() => {
|
||||||
|
sharePost(id, 'whatsapp')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex">
|
||||||
|
<Image quality={100} src={ShareWhatsapp} alt={'whatsapp'} width={26} height={26} />
|
||||||
|
<span className="pl-1">Whatsapp</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs">{countWhatsapp}</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition.Child>
|
||||||
|
</Transition>
|
||||||
|
</>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Dialog } from '@headlessui/react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import AlertIcon from '../../../public/images/alert.svg'
|
||||||
|
|
||||||
|
const Modal = ({
|
||||||
|
setIsOpen,
|
||||||
|
isOpen,
|
||||||
|
icon,
|
||||||
|
tittle,
|
||||||
|
description,
|
||||||
|
textTrue,
|
||||||
|
textFalse = 'Cancelar',
|
||||||
|
functionTrue,
|
||||||
|
functionFalse
|
||||||
|
}: {
|
||||||
|
isOpen: boolean
|
||||||
|
setIsOpen: (value: boolean) => void
|
||||||
|
icon: boolean
|
||||||
|
tittle: string
|
||||||
|
description: string
|
||||||
|
textTrue: string
|
||||||
|
textFalse?: string
|
||||||
|
functionTrue: () => void
|
||||||
|
functionFalse: () => void
|
||||||
|
}) => {
|
||||||
|
const router = useRouter()
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={isOpen}
|
||||||
|
onClose={() => setIsOpen(false)}
|
||||||
|
className="fixed inset-0 flex items-center justify-center z-50"
|
||||||
|
>
|
||||||
|
<Dialog.Panel className="bg-white w-80 h-64 border border-black rounded-lg p-6">
|
||||||
|
{icon && (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Image src={AlertIcon} width={50} height={50} alt="alert" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<h3 className="font-semibold text-center">{tittle}</h3>
|
||||||
|
<div className="flex flex-col justify-center items-center gap-4 mt-5">
|
||||||
|
<p className="text-center">{description}</p>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
className="w-28 bg-red-200 border border-red-200 hover:bg-red-400 hover:border-black"
|
||||||
|
onClick={() => {
|
||||||
|
// router.back()
|
||||||
|
setIsOpen(false)
|
||||||
|
functionFalse()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{textFalse}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="w-28 bg-blue-300 border border-blue-200 hover:bg-blue-400 hover:border-black"
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(false)
|
||||||
|
functionTrue()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{textTrue}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Modal
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
import { Dialog } from '@headlessui/react'
|
||||||
|
import { useSession } from 'next-auth/react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||||
|
import AlertIcon from '../../../public/images/alert.svg'
|
||||||
|
|
||||||
|
interface FormData {
|
||||||
|
name: string
|
||||||
|
slug: string
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalCreateTag = ({
|
||||||
|
setIsOpen,
|
||||||
|
isOpen,
|
||||||
|
icon,
|
||||||
|
tittle,
|
||||||
|
description,
|
||||||
|
textTrue,
|
||||||
|
textFalse = 'Cancelar',
|
||||||
|
functionTrue,
|
||||||
|
functionFalse
|
||||||
|
}: {
|
||||||
|
isOpen: boolean
|
||||||
|
setIsOpen: (value: boolean) => void
|
||||||
|
icon?: boolean
|
||||||
|
tittle: string
|
||||||
|
description?: string
|
||||||
|
textTrue: string
|
||||||
|
textFalse?: string
|
||||||
|
functionTrue: () => void
|
||||||
|
functionFalse: () => void
|
||||||
|
}) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const { data: session } = useSession()
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
getValues,
|
||||||
|
formState: { errors }
|
||||||
|
} = useForm<FormData>()
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<FormData> = async (dataForm) => {
|
||||||
|
const { name, slug, color } = dataForm
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const response = await fetch('/api/tag', {
|
||||||
|
// method: 'POST',
|
||||||
|
// headers: {
|
||||||
|
// 'Content-Type': 'application/json'
|
||||||
|
// },
|
||||||
|
|
||||||
|
// body: JSON.stringify({
|
||||||
|
// name,
|
||||||
|
// slug: slugify(name),
|
||||||
|
// color,
|
||||||
|
// userEmail: session?.user?.email
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// // if (!response.ok) {
|
||||||
|
// // throw new Error('You have already used this title')
|
||||||
|
// // }
|
||||||
|
// if (!response.ok) {
|
||||||
|
// const errorData = await response.json()
|
||||||
|
// }
|
||||||
|
// const data = await response.json()
|
||||||
|
// setIsOpen(false)
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error(error, 'Error fetching data')
|
||||||
|
// const errorMessage = (error as { message?: string })?.message || 'Error creating post'
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={isOpen}
|
||||||
|
onClose={() => setIsOpen(false)}
|
||||||
|
className="fixed inset-0 flex items-center justify-center z-50"
|
||||||
|
>
|
||||||
|
<Dialog.Panel className="bg-white w-80 h-64 border border-black rounded-lg p-6">
|
||||||
|
{icon && (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Image src={AlertIcon} width={50} height={50} alt="alert" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<h3 className="font-semibold text-center">{tittle}</h3>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="flex flex-col justify-center items-center gap-4 mt-5">
|
||||||
|
<p className="text-center">{description}</p>
|
||||||
|
<div className="w-full">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="px-4 py-2 rounded-md w-full border border-gray-400"
|
||||||
|
placeholder="Nombre"
|
||||||
|
{...register('name', { required: 'El campo del nombre es obligatorio' })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full justify-center items-center gap-2">
|
||||||
|
<label className="text-gray-600">Color</label>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
className="w-7 h-8 p-1 rounded-md"
|
||||||
|
{...register('color', { required: 'El campo del color es obligatorio' })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
className="w-28 bg-red-200 border border-red-200 hover:bg-red-400 hover:border-black"
|
||||||
|
onClick={() => {
|
||||||
|
// router.back()
|
||||||
|
setIsOpen(false)
|
||||||
|
functionFalse()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{textFalse}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-28 bg-blue-300 border border-blue-200 hover:bg-blue-400 hover:border-black"
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(false)
|
||||||
|
functionTrue()
|
||||||
|
handleSubmit(onSubmit)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{textTrue}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalCreateTag
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
const StatIndividual = ({ stat, tittle }: { stat: number; tittle: string }) => {
|
||||||
|
return (
|
||||||
|
<div className="text-center md:border-r">
|
||||||
|
<h6 className="text-4xl font-bold lg:text-5xl xl:text-6xl">{stat}</h6>
|
||||||
|
<p className="text-sm font-medium tracking-widest text-gray-800 uppercase lg:text-base">{tittle}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StatIndividual
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { languageRedirect } from '@/helpers/changeLanguage'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export const Tab = ({ className }: { className?: string }) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const currentLanguage = router.locale || 'es'
|
||||||
|
const [selectedTab, setSelectedTab] = useState(currentLanguage)
|
||||||
|
const handleTabClick = (tab: string) => {
|
||||||
|
languageRedirect(router, tab)
|
||||||
|
setSelectedTab(tab)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={`${className}`}>
|
||||||
|
<div className="hidden text-sm font-medium text-center text-gray-500 divide-x divide-gray-200 rounded-lg shadow sm:flex dark:divide-gray-700 dark:text-gray-400">
|
||||||
|
<span
|
||||||
|
className={`inline-block w-full p-1 px-2 rounded-l-lg cursor-pointer ${
|
||||||
|
selectedTab === 'es'
|
||||||
|
? 'bg-blue-100 hover:bg-blue-100 dark:bg-gray-800 dark:hover:bg-gray-700'
|
||||||
|
: 'bg-white hover:text-gray-700 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700'
|
||||||
|
}`}
|
||||||
|
onClick={() => handleTabClick('es')}
|
||||||
|
>
|
||||||
|
Es
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`inline-block w-full p-1 px-2 rounded-r-lg cursor-pointer ${
|
||||||
|
selectedTab === 'en'
|
||||||
|
? 'bg-blue-100 hover:bg-blue-100 dark:bg-gray-800 dark:hover:bg-gray-700'
|
||||||
|
: 'bg-white hover:text-gray-700 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700'
|
||||||
|
}`}
|
||||||
|
onClick={() => handleTabClick('en')}
|
||||||
|
>
|
||||||
|
En
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
type Tag = {
|
||||||
|
color: string
|
||||||
|
name: string
|
||||||
|
className?: string
|
||||||
|
[others: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tag = ({ color, name, className, others }: Tag) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`flex justify-between items-center px-2 py-1 w-fit h-6 rounded-xl border-[1px] border-black border-solid gap-2 `}
|
||||||
|
style={{ backgroundColor: `${color}80` }}
|
||||||
|
>
|
||||||
|
<div className={`${className}`} {...others}>
|
||||||
|
<p className="text-xs font-bold ">{name}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Tag
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { Footer } from '../organism/Footer'
|
||||||
|
import { Navbar } from '../organism/Navbar'
|
||||||
|
|
||||||
|
type LayoutProps = {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
export const Layout = ({ children }: LayoutProps) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Navbar />
|
||||||
|
<div className="h-full">{children}</div>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,301 @@
|
||||||
|
import Image from 'next/image'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { FaRegComment } from 'react-icons/fa'
|
||||||
|
// import { FaLink } from 'react-icons/fa6'
|
||||||
|
// import { MdDelete, MdEdit } from 'react-icons/md'
|
||||||
|
import { useSession } from 'next-auth/react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { CgLoadbarSound } from 'react-icons/cg'
|
||||||
|
import { FaRegStar, FaStar } from 'react-icons/fa'
|
||||||
|
import { FcLike } from 'react-icons/fc'
|
||||||
|
import { FiHeart } from 'react-icons/fi'
|
||||||
|
import { Post } from '../../../type'
|
||||||
|
import { DropDownShare } from '../atoms/DropDownShare'
|
||||||
|
import Modal from '../atoms/Modal'
|
||||||
|
|
||||||
|
interface CardProps {
|
||||||
|
post: Post
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Card({ post }: CardProps) {
|
||||||
|
// const { postData, updatePostData } = usePostContext()
|
||||||
|
const {
|
||||||
|
description,
|
||||||
|
title,
|
||||||
|
Category,
|
||||||
|
slug,
|
||||||
|
url,
|
||||||
|
Like,
|
||||||
|
id,
|
||||||
|
comments,
|
||||||
|
views,
|
||||||
|
twitterShareCount,
|
||||||
|
whatsappShareCount,
|
||||||
|
Favorite
|
||||||
|
// Tags
|
||||||
|
} = post
|
||||||
|
const { data: session } = useSession()
|
||||||
|
// const likeOfUser = Like?.some((user) => user.userEmail === session?.user?.email)
|
||||||
|
const [isLike, setIsLike] = useState<boolean>()
|
||||||
|
const [isFavorite, setIsFavorite] = useState<boolean>()
|
||||||
|
const [likesCount, setLikesCount] = useState<number>(Like?.length || 0)
|
||||||
|
const [favoriteCount, setFavoriteCount] = useState<number>(Favorite?.length || 0)
|
||||||
|
let [isOpen, setIsOpen] = useState(false)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session && Like?.some((like) => like?.userEmail === session?.user?.email)) {
|
||||||
|
setIsLike(true)
|
||||||
|
} else {
|
||||||
|
setIsLike(false)
|
||||||
|
}
|
||||||
|
}, [Like, session])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session && Favorite?.some((fav) => fav?.userEmail === session?.user?.email)) {
|
||||||
|
setIsFavorite(true)
|
||||||
|
} else {
|
||||||
|
setIsFavorite(false)
|
||||||
|
}
|
||||||
|
}, [Favorite, session])
|
||||||
|
|
||||||
|
const handleAddLike = async () => {
|
||||||
|
if (!session) {
|
||||||
|
setIsOpen(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const response = await fetch(`/api/like`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: session.user.email,
|
||||||
|
postId: id,
|
||||||
|
session: session
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const data = await response.json()
|
||||||
|
if (response.ok) {
|
||||||
|
data && setIsLike(true)
|
||||||
|
setLikesCount((prevCount) => prevCount + 1)
|
||||||
|
} else {
|
||||||
|
console.error('Error al dar like:', data.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleDeleteLike = async () => {
|
||||||
|
if (!session) {
|
||||||
|
setIsOpen(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const response = await fetch('/api/like', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: session.user.email,
|
||||||
|
postId: id,
|
||||||
|
session: session
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setIsLike(false)
|
||||||
|
setLikesCount((prevCount) => Math.max(0, prevCount - 1))
|
||||||
|
} else {
|
||||||
|
console.error('Error al quitar like:', data.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleAddFavorite = async () => {
|
||||||
|
if (!session) {
|
||||||
|
setIsOpen(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const response = await fetch(`/api/favorite`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: session.user.email,
|
||||||
|
postId: id,
|
||||||
|
session: session
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const data = await response.json()
|
||||||
|
if (response.ok) {
|
||||||
|
data && setIsFavorite(true)
|
||||||
|
setFavoriteCount((prevCount) => prevCount + 1)
|
||||||
|
} else {
|
||||||
|
console.error('Error al dar like:', data.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleDeleteFavorite = async () => {
|
||||||
|
if (!session) {
|
||||||
|
setIsOpen(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const response = await fetch('/api/favorite', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: session.user.email,
|
||||||
|
postId: id,
|
||||||
|
session: session
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setIsFavorite(false)
|
||||||
|
setFavoriteCount((prevCount) => Math.max(0, prevCount - 1))
|
||||||
|
} else {
|
||||||
|
console.error('Error al quitar like:', data.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex lg:flex-row flex-col rounded-xl border-black border-4 gap-4 p-6 mb-5 justify-between items-center lg:min-h-full min-h-[450px]"
|
||||||
|
style={{ backgroundColor: `${Category?.color}80` }}
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-start">
|
||||||
|
<div
|
||||||
|
className="relative h-[120px] w-[120px] border-white border-2 m-2 p-4 rounded-xl"
|
||||||
|
style={{ backgroundColor: `${Category?.color}` }}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={`/images/${Category?.img}.svg`}
|
||||||
|
height={100}
|
||||||
|
width={100}
|
||||||
|
alt="img post"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
></Image>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col w-full justify-between m-2">
|
||||||
|
<div className="mb-6">
|
||||||
|
<Link href={`/post/${slug}`}>
|
||||||
|
<div className="flex text-center lg:text-left items-center">
|
||||||
|
<h2 className="flextext-black mb-4 hover:opacity-50 lg:text-3xl text-xl font-extrabold ">{title}</h2>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<div className="lg:flex lg:flex-row flex-col gap-2">
|
||||||
|
<p className="text-black">
|
||||||
|
{description?.length > 80 ? `${description.substring(0, 80)}...` : description}
|
||||||
|
</p>{' '}
|
||||||
|
{description?.length > 80 && (
|
||||||
|
<a
|
||||||
|
className="font-medium flex justify-center text-[#0066cc] hover:text-[#726edf] visited:text-[#800080] whitespace-pre"
|
||||||
|
href={`/post/${slug}`}
|
||||||
|
>
|
||||||
|
ver más
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex lg:flex-row flex-col md:justify-between justify-center items-center gap-2">
|
||||||
|
{/* <div className="flex flex-wrap gap-1">
|
||||||
|
{Tags &&
|
||||||
|
Tags?.slice(0, 4).map((tag) => (
|
||||||
|
<div
|
||||||
|
key={tag?.id}
|
||||||
|
className={`flex justify-between items-center px-2 py-1 w-fit h-6 rounded-xl border-[1px] border-black border-solid gap-2 `}
|
||||||
|
style={{ backgroundColor: `${tag?.color}80` }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-bold ">{tag?.name}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div> */}
|
||||||
|
<div className="flex md:justify-end justify-center gap-2">
|
||||||
|
{/* <div className="flex items-center justify-center bg-white rounded-full w-8 h-8 hover:opacity-50 hover:cursor-pointer">
|
||||||
|
<a href={url} target="_blank" rel="noopener noreferrer">
|
||||||
|
<FaLink className="w-5 h-5" />
|
||||||
|
</a>
|
||||||
|
</div> */}
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center"
|
||||||
|
onClick={() => {
|
||||||
|
isLike ? handleDeleteLike() : handleAddLike()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isLike ? (
|
||||||
|
<FcLike className="w-5 h-5 cursor-pointer hover:scale-110" />
|
||||||
|
) : (
|
||||||
|
<FiHeart className="w-5 h-5 cursor-pointer hover:opacity-50 hover:scale-110" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* <AiOutlineLike className="w-5 h-5" /> */}
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-gray-600 w-4 h-4 flex justify-center items-center font-bold">
|
||||||
|
{likesCount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center"
|
||||||
|
onClick={() => {
|
||||||
|
isFavorite ? handleDeleteFavorite() : handleAddFavorite()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isFavorite ? (
|
||||||
|
<FaStar className="w-5 h-5 cursor-pointer hover:scale-110 text-yellow-500" />
|
||||||
|
) : (
|
||||||
|
<FaRegStar className="w-5 h-5 cursor-pointer hover:scale-110" />
|
||||||
|
)}
|
||||||
|
<span className="text-xs text-gray-600 w-4 h-4 flex justify-center items-center font-bold">
|
||||||
|
{favoriteCount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Link href={`/post/${slug}`}>
|
||||||
|
<div className="flex items-center justify-center w-5 h-5 hover:bg-opacity-80 hover:cursor-pointer hover:opacity-50 hover:scale-110">
|
||||||
|
<FaRegComment className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<span className="text-xs text-gray-600 w-4 h-4 flex justify-center items-center font-bold ">
|
||||||
|
{comments?.length}
|
||||||
|
</span>{' '}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center ">
|
||||||
|
<div className="">
|
||||||
|
<DropDownShare slug={slug} id={id} counTwitter={twitterShareCount} countWhatsapp={twitterShareCount} />
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-gray-600 w-4 h-4 flex justify-center items-center font-bold">
|
||||||
|
{twitterShareCount + whatsappShareCount}
|
||||||
|
</span>{' '}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<CgLoadbarSound className="w-6 h-6" />
|
||||||
|
<span className="text-xs text-gray-600 w-4 h-4 flex justify-center items-center font-bold">
|
||||||
|
{views}
|
||||||
|
</span>{' '}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Modal
|
||||||
|
setIsOpen={setIsOpen}
|
||||||
|
isOpen={isOpen}
|
||||||
|
icon={true}
|
||||||
|
tittle="No estas logeado"
|
||||||
|
description="No puedes comentar sin estar registrado antes."
|
||||||
|
textTrue="Login"
|
||||||
|
textFalse="Cancelar"
|
||||||
|
functionTrue={() => router.push('/login')}
|
||||||
|
functionFalse={() => router.back()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { usePostContext } from '@/context/PostContext'
|
||||||
|
import { Card } from './Card'
|
||||||
|
import Pagination from './Pagination'
|
||||||
|
|
||||||
|
export function CardList({ page, cat, count }: any) {
|
||||||
|
// const [dataCards, setDataCards] = useState<Post[]>([])
|
||||||
|
// const [countN, setCount] = useState<number>(count)
|
||||||
|
const { postData, updatePostData } = usePostContext()
|
||||||
|
|
||||||
|
// const getData = async (page: any, cat: any) => {
|
||||||
|
// console.log('funciona')
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const response = await fetch(`http://localhost:3000/api/posts?page=${page || 1}&cat=${cat || ''}`, {
|
||||||
|
// cache: 'no-store'
|
||||||
|
// })
|
||||||
|
// console.log(response)
|
||||||
|
|
||||||
|
// if (!response.ok) {
|
||||||
|
// throw new Error('Failed')
|
||||||
|
// }
|
||||||
|
// const result = await response.json()
|
||||||
|
// updatePostData(result.posts)
|
||||||
|
// // setCount(result.count)
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error(error)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// useEffect(() => {
|
||||||
|
// getData(page, cat)
|
||||||
|
// }, [page, cat])
|
||||||
|
|
||||||
|
// NO LO TENGO MUY CLARO PERO CREO QUE ESTO NO ME HACE FALTA, TENGO QUE REVISARLO CON CALMA
|
||||||
|
|
||||||
|
const POST_PER_PAGE = 4
|
||||||
|
const hasPrev = POST_PER_PAGE * (page - 1) > 0
|
||||||
|
const hasNext = POST_PER_PAGE * (page - 1) + POST_PER_PAGE < count
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col ">
|
||||||
|
{Array.isArray(postData) && postData?.map((item) => <Card {...item} post={item} key={item.id} />)}
|
||||||
|
</div>
|
||||||
|
<div className="py-4">
|
||||||
|
<Pagination page={page} hasPrev={hasPrev} hasNext={hasNext} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,279 @@
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { FaRegComment } from 'react-icons/fa'
|
||||||
|
// import { FaLink } from 'react-icons/fa6'
|
||||||
|
// import { MdDelete, MdEdit } from 'react-icons/md'
|
||||||
|
import { useSession } from 'next-auth/react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { CgLoadbarSound } from 'react-icons/cg'
|
||||||
|
import { FaRegStar, FaStar } from 'react-icons/fa'
|
||||||
|
import { FcLike } from 'react-icons/fc'
|
||||||
|
import { FiHeart } from 'react-icons/fi'
|
||||||
|
import { Post } from '../../../type'
|
||||||
|
import { DropDownShare } from '../atoms/DropDownShare'
|
||||||
|
import Modal from '../atoms/Modal'
|
||||||
|
|
||||||
|
interface CardPerfilProps {
|
||||||
|
post: Post
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CardPerfil({ post }: CardPerfilProps) {
|
||||||
|
// const { postData, updatePostData } = usePostContext()
|
||||||
|
const {
|
||||||
|
description,
|
||||||
|
title,
|
||||||
|
Category,
|
||||||
|
slug,
|
||||||
|
url,
|
||||||
|
Like,
|
||||||
|
id,
|
||||||
|
comments,
|
||||||
|
views,
|
||||||
|
twitterShareCount,
|
||||||
|
whatsappShareCount,
|
||||||
|
Favorite
|
||||||
|
} = post
|
||||||
|
const { data: session } = useSession()
|
||||||
|
// const likeOfUser = Like?.some((user) => user.userEmail === session?.user?.email)
|
||||||
|
const [isLike, setIsLike] = useState<boolean>()
|
||||||
|
const [isFavorite, setIsFavorite] = useState<boolean>()
|
||||||
|
const [likesCount, setLikesCount] = useState<number>(Like?.length || 0)
|
||||||
|
const [favoriteCount, setFavoriteCount] = useState<number>(Favorite?.length || 0)
|
||||||
|
let [isOpen, setIsOpen] = useState(false)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session && Like?.some((like) => like?.userEmail === session?.user?.email)) {
|
||||||
|
setIsLike(true)
|
||||||
|
} else {
|
||||||
|
setIsLike(false)
|
||||||
|
}
|
||||||
|
}, [Like, session])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session && Favorite?.some((fav) => fav?.userEmail === session?.user?.email)) {
|
||||||
|
setIsFavorite(true)
|
||||||
|
} else {
|
||||||
|
setIsFavorite(false)
|
||||||
|
}
|
||||||
|
}, [Favorite, session])
|
||||||
|
|
||||||
|
const handleAddLike = async () => {
|
||||||
|
if (!session) {
|
||||||
|
setIsOpen(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const response = await fetch(`/api/like`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: session.user.email,
|
||||||
|
postId: id,
|
||||||
|
session: session
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const data = await response.json()
|
||||||
|
if (response.ok) {
|
||||||
|
data && setIsLike(true)
|
||||||
|
setLikesCount((prevCount) => prevCount + 1)
|
||||||
|
} else {
|
||||||
|
console.error('Error al dar like:', data.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleDeleteLike = async () => {
|
||||||
|
if (!session) {
|
||||||
|
setIsOpen(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const response = await fetch('/api/like', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: session.user.email,
|
||||||
|
postId: id,
|
||||||
|
session: session
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setIsLike(false)
|
||||||
|
setLikesCount((prevCount) => Math.max(0, prevCount - 1))
|
||||||
|
} else {
|
||||||
|
console.error('Error al quitar like:', data.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleAddFavorite = async () => {
|
||||||
|
if (!session) {
|
||||||
|
setIsOpen(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const response = await fetch(`/api/favorite`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: session.user.email,
|
||||||
|
postId: id,
|
||||||
|
session: session
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const data = await response.json()
|
||||||
|
if (response.ok) {
|
||||||
|
data && setIsFavorite(true)
|
||||||
|
setFavoriteCount((prevCount) => prevCount + 1)
|
||||||
|
} else {
|
||||||
|
console.error('Error al dar like:', data.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleDeleteFavorite = async () => {
|
||||||
|
if (!session) {
|
||||||
|
setIsOpen(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const response = await fetch('/api/favorite', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: session.user.email,
|
||||||
|
postId: id,
|
||||||
|
session: session
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setIsFavorite(false)
|
||||||
|
setFavoriteCount((prevCount) => Math.max(0, prevCount - 1))
|
||||||
|
} else {
|
||||||
|
console.error('Error al quitar like:', data.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex flex-row rounded-xl border-black border-4 lg:gap-4 lg:p-3 p-1 mb-5 justify-center items-center "
|
||||||
|
style={{ backgroundColor: `${Category?.color}80` }}
|
||||||
|
>
|
||||||
|
{/* <div className="lg:block hidden justify-center items-start">
|
||||||
|
<div
|
||||||
|
className="relative h-[40px] w-[40px] border-white border-2 m-2 p-1 rounded-xl"
|
||||||
|
style={{ backgroundColor: `${Category?.color}` }}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={`/images/${Category?.img}.svg`}
|
||||||
|
height={100}
|
||||||
|
width={100}
|
||||||
|
alt="img post"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
></Image>
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
<div className="flex flex-col w-full justify-between m-2">
|
||||||
|
<div className="flex">
|
||||||
|
<Link href={`/post/${slug}`}>
|
||||||
|
<div className="flex text-center lg:text-left">
|
||||||
|
<h3 className="flex text-gray-700 mb-4 hover:opacity-50 lg:text-lg text-xs font-extrabold ">{title}</h3>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex md:justify-between justify-center items-center">
|
||||||
|
<div
|
||||||
|
className={`md:flex hidden justify-between items-center px-4 py-1 w-fit h-10 rounded-xl border-[1px200psx20] border-black border-solid gap-2 `}
|
||||||
|
style={{ backgroundColor: Category?.color }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold ">{Category?.title}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex md:justify-end justify-start gap-0 mobile:gap-2">
|
||||||
|
{/* <div className="flex items-center justify-center bg-white rounded-full w-8 h-8 hover:opacity-50 hover:cursor-pointer">
|
||||||
|
<a href={url} target="_blank" rel="noopener noreferrer">
|
||||||
|
<FaLink className="w-5 h-5" />
|
||||||
|
</a>
|
||||||
|
</div> */}
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center"
|
||||||
|
onClick={() => {
|
||||||
|
isLike ? handleDeleteLike() : handleAddLike()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isLike ? (
|
||||||
|
<FcLike className="w-5 h-5 cursor-pointer hover:scale-110 " />
|
||||||
|
) : (
|
||||||
|
<FiHeart className="w-5 h-5 cursor-pointer hover:opacity-50 hover:scale-110" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* <AiOutlineLike className="w-5 h-5" /> */}
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-gray-600 w-4 h-4 flex justify-center items-center font-bold">
|
||||||
|
{likesCount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center"
|
||||||
|
onClick={() => {
|
||||||
|
isFavorite ? handleDeleteFavorite() : handleAddFavorite()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isFavorite ? (
|
||||||
|
<FaStar className="w-5 h-5 cursor-pointer hover:scale-110 text-yellow-500" />
|
||||||
|
) : (
|
||||||
|
<FaRegStar className="w-5 h-5 cursor-pointer hover:scale-110" />
|
||||||
|
)}
|
||||||
|
<span className="text-xs text-gray-600 w-4 h-4 flex justify-center items-center font-bold">
|
||||||
|
{favoriteCount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Link href={`/post/${slug}`}>
|
||||||
|
<div className="flex items-center justify-center w-5 h-5 hover:bg-opacity-80 hover:cursor-pointer hover:opacity-50 hover:scale-110">
|
||||||
|
<FaRegComment className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<span className="text-xs text-gray-600 w-4 h-4 flex justify-center items-center font-bold ">
|
||||||
|
{comments?.length}
|
||||||
|
</span>{' '}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center ">
|
||||||
|
<div className="">
|
||||||
|
<DropDownShare slug={slug} id={id} counTwitter={twitterShareCount} countWhatsapp={twitterShareCount} />
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-gray-600 w-4 h-4 flex justify-center items-center font-bold">
|
||||||
|
{twitterShareCount + whatsappShareCount}
|
||||||
|
</span>{' '}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<CgLoadbarSound className="w-6 h-6" />
|
||||||
|
<span className="text-xs text-gray-600 w-4 h-4 flex justify-center items-center font-bold">
|
||||||
|
{views}
|
||||||
|
</span>{' '}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Modal
|
||||||
|
setIsOpen={setIsOpen}
|
||||||
|
isOpen={isOpen}
|
||||||
|
icon={true}
|
||||||
|
tittle="No estas logeado"
|
||||||
|
description="No puedes comentar sin estar registrado antes."
|
||||||
|
textTrue="Login"
|
||||||
|
textFalse="Cancelar"
|
||||||
|
functionTrue={() => router.push('/login')}
|
||||||
|
functionFalse={() => router.back()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useSession } from 'next-auth/react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { MdDelete } from 'react-icons/md'
|
||||||
|
import useSWR from 'swr'
|
||||||
|
import { Comment } from '../../../type'
|
||||||
|
import Modal from '../atoms/Modal'
|
||||||
|
|
||||||
|
interface CommentProps {
|
||||||
|
postSlug?: string | string[] | undefined
|
||||||
|
// comments?: Comment[] | [] | undefined
|
||||||
|
// setComments: React.Dispatch<React.SetStateAction<Comment[]>>
|
||||||
|
}
|
||||||
|
export default function Comment({
|
||||||
|
postSlug
|
||||||
|
}: // comments, setComments
|
||||||
|
CommentProps) {
|
||||||
|
const router = useRouter()
|
||||||
|
const session = useSession()
|
||||||
|
const [showModalComment, setShowModalComment] = useState<boolean>(false)
|
||||||
|
const fetcher = async (url: string) => {
|
||||||
|
const res = await fetch(url)
|
||||||
|
const data = await res.json()
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('Failed to fetch comments')
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
// const [comments, setComments] = useState<Comment[]>([])
|
||||||
|
// const [loading, setLoading] = useState<boolean>(true)
|
||||||
|
const { data, mutate, isLoading } = useSWR(`/api/comments/?postSlug=${postSlug}`, fetcher)
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
const options: Intl.DateTimeFormatOptions = {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
}
|
||||||
|
return new Date(dateString).toLocaleDateString('en-US', options)
|
||||||
|
}
|
||||||
|
const deleteComment = async (commentId: string) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/comments`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id: commentId })
|
||||||
|
})
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete comment')
|
||||||
|
}
|
||||||
|
mutate()
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mb-6">
|
||||||
|
{isLoading
|
||||||
|
? '...loading'
|
||||||
|
: data?.map((comment: Comment) => (
|
||||||
|
<div key={comment.id} className="w-full flex flex-col justify-start gap-4">
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="flex justify-between items-center font-semibold gap-3">
|
||||||
|
<div className="flex">
|
||||||
|
{comment?.user?.image && (
|
||||||
|
<div className="relative h-[60px] w-[60px] border-gray-300 border-2 m-2 rounded-full overflow-hidden">
|
||||||
|
<Image
|
||||||
|
src={comment?.user?.image}
|
||||||
|
width={50}
|
||||||
|
height={50}
|
||||||
|
alt="avatar Mujer"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-col justify-center text-gray-500 gap-1">
|
||||||
|
<p>{comment?.user?.name}</p>
|
||||||
|
<p className="font-semibold">{formatDate(comment?.createdAt)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{comment?.user?.email === session.data?.user?.email && (
|
||||||
|
<div onClick={() => setShowModalComment(true)}>
|
||||||
|
<MdDelete className="w-5 h-5 hover:cursor-pointer hover:w-6 hover:h-6" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="font-semibold p-2">{comment.description}</p>
|
||||||
|
</div>
|
||||||
|
{/* MODAL PARA ELIMINAR COMENTARIO */}
|
||||||
|
<Modal
|
||||||
|
setIsOpen={setShowModalComment}
|
||||||
|
isOpen={showModalComment}
|
||||||
|
icon={false}
|
||||||
|
tittle="¿Estás seguro de borrar?"
|
||||||
|
description="No puedes recuperar este comentario una vez borrado."
|
||||||
|
textTrue="Borrar"
|
||||||
|
textFalse="Cancelar"
|
||||||
|
functionTrue={() => deleteComment(comment?.id)}
|
||||||
|
functionFalse={() => setShowModalComment(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { useSession } from 'next-auth/react'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useSWRConfig } from 'swr'
|
||||||
|
import { Button } from '../atoms/Button'
|
||||||
|
|
||||||
|
export default function CommentWrite({ postSlug }: any) {
|
||||||
|
const [description, setDesc] = useState('')
|
||||||
|
const { data: session } = useSession()
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const { mutate } = useSWRConfig()
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setDesc(e.target.value)
|
||||||
|
}
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
|
const response = await fetch('/api/comments', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ description, postSlug, session })
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to create comment')
|
||||||
|
}
|
||||||
|
|
||||||
|
setDesc('')
|
||||||
|
mutate(`/api/comments/?postSlug=${postSlug}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="mb-6">
|
||||||
|
<h2 className="font-bold my-5">Comments</h2>
|
||||||
|
<div className="w-full flex lg:flex-row flex-col justify-center items-center gap-4">
|
||||||
|
<textarea
|
||||||
|
className="w-full border-2 p-5 rounded-md placeholder-gray-500 bg- focus:placeholder-gray-500 resize-none "
|
||||||
|
placeholder="Write a comment..."
|
||||||
|
value={description}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
loading={loading}
|
||||||
|
className="h-fit"
|
||||||
|
buttonClassName="flex justify-center w-full text-white content-center font-bold bg-teal-600 py-2 px-6"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
import { useSession } from 'next-auth/react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||||
|
import { Category } from '../../../type'
|
||||||
|
import { Button } from '../atoms/Button'
|
||||||
|
|
||||||
|
interface FormData {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
url: string
|
||||||
|
catSlug: string
|
||||||
|
// tag?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CreatePostForm() {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
getValues,
|
||||||
|
formState: { errors }
|
||||||
|
} = useForm<FormData>()
|
||||||
|
// console.log(errors)
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
// const session = useSession()
|
||||||
|
const { data: session } = useSession()
|
||||||
|
const [categories, setCategories] = useState<Category[]>([])
|
||||||
|
const [categorySelected, setCategorySelected] = useState<Category>()
|
||||||
|
const [error, setError] = useState<string>()
|
||||||
|
// const [showCreateTag, setShowCreateTag] = useState<boolean>(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/categories', {
|
||||||
|
cache: 'no-store'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json()
|
||||||
|
setCategories(result)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getData()
|
||||||
|
}, [])
|
||||||
|
const slugify = (str: string) => {
|
||||||
|
return str
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/[^\w\s-]/g, '')
|
||||||
|
.replace(/[\s_-]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '')
|
||||||
|
}
|
||||||
|
const onSubmit: SubmitHandler<FormData> = async (dataForm) => {
|
||||||
|
const { title, description, url, catSlug } = dataForm
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/post', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
|
||||||
|
body: JSON.stringify({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
slug: slugify(title),
|
||||||
|
catSlug: catSlug,
|
||||||
|
url,
|
||||||
|
userEmail: session?.user?.email
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// if (!response.ok) {
|
||||||
|
// throw new Error('You have already used this title')
|
||||||
|
// }
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json()
|
||||||
|
if (errorData.type === 'duplicate') throw new Error(error)
|
||||||
|
if (errorData.type === 'unAuthorized') throw new Error(error)
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
router.push('/')
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error, 'Error fetching data')
|
||||||
|
const errorMessage = (error as { message?: string })?.message || 'Error creating post'
|
||||||
|
setError(errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
const category = getValues('catSlug')
|
||||||
|
const categorySelect = categories.find((cat) => cat.slug === category)
|
||||||
|
categorySelect && setCategorySelected(categorySelect)
|
||||||
|
}, [watch('catSlug')])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// hago esto para comprobar si el usuario logeado (session) es admin.
|
||||||
|
// Para eso hago un get del usuario para traerme los datos de este y ver si es admin.
|
||||||
|
const fetchUser = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/user?email=${session?.user?.email}`)
|
||||||
|
const userData = await response.json()
|
||||||
|
!userData.isAdmin && router.push('/')
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error fetching user:', error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchUser()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div
|
||||||
|
className="flex lg:flex-row flex-col rounded-xl border-black border-4 gap-4 p-6 mb-5"
|
||||||
|
style={{ backgroundColor: categorySelected ? `${categorySelected?.color}80` : '#c7c7c7' }}
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-start">
|
||||||
|
<div
|
||||||
|
className="relative h-[200px] w-[200px] border-white border-2 p-4 rounded-xl"
|
||||||
|
style={{ backgroundColor: categorySelected ? categorySelected?.color : '#c7c7c7' }}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={categorySelected ? `/images/${categorySelected.img}.svg` : `/images/error.svg`}
|
||||||
|
width={200}
|
||||||
|
height={200}
|
||||||
|
alt="img post"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
></Image>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-col justify-between lg:gap-1 gap-5">
|
||||||
|
<div className="flex lg:flex-row flex-col gap-5">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="px-4 py-2 rounded-md w-full"
|
||||||
|
placeholder="Titulo"
|
||||||
|
{...register('title', { required: 'El campo del título es obligatorio' })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<select {...register('catSlug')} className="w-full h-full px-4 py-2 rounded-md text-gray-600">
|
||||||
|
<option value="">Seleccionar categoría</option>
|
||||||
|
{categories.map((category: Category) => (
|
||||||
|
<option key={category.id} value={category.slug}>
|
||||||
|
{category.title}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<textarea
|
||||||
|
className="w-full border-2 p-2 rounded-md placeholder-gray-500 bg- focus:placeholder-gray-500 resize-none "
|
||||||
|
placeholder="Write a comment..."
|
||||||
|
{...register('description', { required: 'El campo de comentario es obligatorio' })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
className="px-4 py-2 rounded-md w-full"
|
||||||
|
placeholder="Url"
|
||||||
|
{...register('url', {
|
||||||
|
required: 'La url es obligatoria'
|
||||||
|
// pattern: {
|
||||||
|
// value: /^(ftp|http|https):\/\/[^ "]+$/,
|
||||||
|
// message: 'Ingrese una URL válida'
|
||||||
|
// }
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{errors && errors.title && (
|
||||||
|
<div className="bg-red-500 rounded-md flex justify-center font-bold mb-5">
|
||||||
|
<p className="text-white text-sm p-2 uppercase">{errors.title.message}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{errors && errors.description && (
|
||||||
|
<div className="bg-red-500 rounded-md flex justify-center font-bold mb-5">
|
||||||
|
<p className="text-white text-sm p-2 uppercase">{errors.description.message}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{errors && errors.url && (
|
||||||
|
<div className="bg-red-500 rounded-md flex justify-center font-bold mb-5">
|
||||||
|
<p className="text-white text-sm p-2 uppercase">{errors.url.message}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{error && (
|
||||||
|
<div className="bg-red-500 rounded-md flex justify-center font-bold mb-5">
|
||||||
|
<p className="text-white text-sm p-2 uppercase">{error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* <ModalCreateTag
|
||||||
|
setIsOpen={setShowCreateTag}
|
||||||
|
isOpen={showCreateTag}
|
||||||
|
tittle="Crear un tag nuevo"
|
||||||
|
// description="No puedes comentar sin estar registrado antes."
|
||||||
|
textTrue="Crear"
|
||||||
|
textFalse="Cancelar"
|
||||||
|
functionTrue={() => console.log('nada')}
|
||||||
|
functionFalse={() => console.log('nada')}
|
||||||
|
/> */}
|
||||||
|
<div className="flex lg:justify-end justify-center gap-4">
|
||||||
|
{/* <div
|
||||||
|
className="font-medium py-2 px-4 rounded-lg bg-red-200 border border-red-200 hover:bg-red-400 hover:border-black cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/editTag')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Editar tag
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="font-medium py-2 px-4 rounded-lg bg-red-200 border border-red-200 hover:bg-red-400 hover:border-black cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/createTag')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Añadir tag
|
||||||
|
</div> */}
|
||||||
|
<Button
|
||||||
|
className="h-fit"
|
||||||
|
buttonClassName="flex justify-center w-full text-white content-center font-bold bg-teal-600 py-2 px-6 hover:opacity-80"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Crear
|
||||||
|
</Button>{' '}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,291 @@
|
||||||
|
import { useSession } from 'next-auth/react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||||
|
import { Category, Post } from '../../../type'
|
||||||
|
import { Button } from '../atoms/Button'
|
||||||
|
|
||||||
|
interface FormData {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
url: string
|
||||||
|
catSlug: string
|
||||||
|
// Tags?: Tag[]
|
||||||
|
}
|
||||||
|
type EditProps = {
|
||||||
|
dataPost: Post
|
||||||
|
}
|
||||||
|
export function EditPost({ dataPost }: EditProps) {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
getValues,
|
||||||
|
reset,
|
||||||
|
formState: { errors }
|
||||||
|
} = useForm<FormData>()
|
||||||
|
// console.log(errors)
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
// const session = useSession()
|
||||||
|
const { data: session } = useSession()
|
||||||
|
const [categories, setCategories] = useState<Category[]>([])
|
||||||
|
// const [tags, setTags] = useState<Tag[]>()
|
||||||
|
const [categorySelected, setCategorySelected] = useState<Category>()
|
||||||
|
// const [selectedTags, setSelectedTags] = useState<Tag[]>()
|
||||||
|
|
||||||
|
const [error, setError] = useState<string>()
|
||||||
|
useEffect(() => {
|
||||||
|
const getData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/categories', {
|
||||||
|
cache: 'no-store'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json()
|
||||||
|
setCategories(result)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getData()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// const slugify = (str: string) => {
|
||||||
|
// return str
|
||||||
|
// .toLowerCase()
|
||||||
|
// .trim()
|
||||||
|
// .replace(/[^\w\s-]/g, '')
|
||||||
|
// .replace(/[\s_-]+/g, '-')
|
||||||
|
// .replace(/^-+|-+$/g, '')
|
||||||
|
// }
|
||||||
|
const onSubmit: SubmitHandler<FormData> = async (dataForm) => {
|
||||||
|
const { title, description, url, catSlug } = dataForm
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/post/${dataPost?.slug}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
|
||||||
|
body: JSON.stringify({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
// slug: slugify(title),
|
||||||
|
catSlug: catSlug,
|
||||||
|
url
|
||||||
|
// Tags: selectedTags
|
||||||
|
// userEmail: session?.user?.email
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// if (!response.ok) {
|
||||||
|
// throw new Error('You have already used this title')
|
||||||
|
// }
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json()
|
||||||
|
if (errorData.type === 'duplicate') throw new Error(error)
|
||||||
|
if (errorData.type === 'unAuthorized') throw new Error(error)
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
router.push('/')
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error, 'Error fetching data')
|
||||||
|
const errorMessage = (error as { message?: string })?.message || 'Error creating post'
|
||||||
|
setError(errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
const category = getValues('catSlug')
|
||||||
|
const categorySelect = categories.find((cat) => cat.slug === category)
|
||||||
|
categorySelect && setCategorySelected(categorySelect)
|
||||||
|
}, [watch('catSlug')])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// hago esto para comprobar si el usuario logeado (session) es admin.
|
||||||
|
// Para eso hago un get del usuario para traerme los datos de este y ver si es admin.
|
||||||
|
const fetchUser = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/user?email=${session?.user?.email}`)
|
||||||
|
const userData = await response.json()
|
||||||
|
!userData.isAdmin && router.push('/')
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error fetching user:', error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchUser()
|
||||||
|
}, [session])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
title: dataPost?.title,
|
||||||
|
catSlug: dataPost?.catSlug,
|
||||||
|
description: dataPost?.description,
|
||||||
|
url: dataPost?.url
|
||||||
|
// Tags: dataPost?.Tags
|
||||||
|
})
|
||||||
|
// setSelectedTags(dataPost?.Tags?.map((tag) => tag) || [])
|
||||||
|
}, [dataPost])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="flex justify-center mb-4 uppercase">
|
||||||
|
<h1>Editar post</h1>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="flex lg:flex-row flex-col rounded-xl border-black border-4 gap-4 p-6 mb-5"
|
||||||
|
style={{ backgroundColor: categorySelected ? `${categorySelected?.color}80` : '#c7c7c7' }}
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-start">
|
||||||
|
<div
|
||||||
|
className="relative h-[200px] w-[200px] border-white border-2 p-4 rounded-xl"
|
||||||
|
style={{ backgroundColor: categorySelected ? categorySelected?.color : '#c7c7c7' }}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={categorySelected ? `/images/${categorySelected.img}.svg` : `/images/error.svg`}
|
||||||
|
width={200}
|
||||||
|
height={200}
|
||||||
|
alt="img post"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
></Image>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-col justify-between gap-4">
|
||||||
|
<div className="flex flex-col lg:flex-row gap-5">
|
||||||
|
<div className="w-full">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full px-4 py-2 rounded-md"
|
||||||
|
placeholder="Titulo"
|
||||||
|
{...register('title', { required: 'El campo del título es obligatorio' })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<select {...register('catSlug')} className="w-full h-full px-4 py-2 rounded-md text-gray-600">
|
||||||
|
<option value="">Seleccionar categoría</option>
|
||||||
|
{categories.map((category: Category) => (
|
||||||
|
<option key={category.id} value={category.slug}>
|
||||||
|
{category.title}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{/* <div className="w-full">
|
||||||
|
<select {...register('tags')} className="w-full h-full px-4 py-2 rounded-md text-gray-600">
|
||||||
|
<option value="">Seleccionar tag</option>
|
||||||
|
{tags?.map((tag: Tag) => (
|
||||||
|
<option key={tag.id} value={tag.slug}>
|
||||||
|
{tag.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div> */}
|
||||||
|
{/* <div className="w-full">
|
||||||
|
<select
|
||||||
|
{...register('Tag')}
|
||||||
|
className="w-full h-full px-4 py-2 rounded-md text-gray-600"
|
||||||
|
onChange={(e) => {
|
||||||
|
const selectedTag = e.target.value
|
||||||
|
setSelectedTags((prevTags) => [...prevTags, selectedTag]) // Permite seleccionar múltiples tags
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="">Seleccionar tag</option>
|
||||||
|
{tags?.map((tag: Tag) => (
|
||||||
|
<option key={tag.id} value={tag.slug}>
|
||||||
|
{tag.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{/* {tags?.map((tag) => (
|
||||||
|
<div
|
||||||
|
key={tag?.id}
|
||||||
|
className={`flex justify-between items-center px-2 py-1 w-fit h-6 rounded-xl border-[1px] border-black border-solid gap-2 `}
|
||||||
|
style={{ backgroundColor: `${tag?.color}80` }}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={tag?.slug}
|
||||||
|
value={tag?.slug}
|
||||||
|
checked={selectedTags.map((tag) => tag.slug).includes(tag.slug)}
|
||||||
|
onChange={(e) => {
|
||||||
|
const tagSlug = e.target.value
|
||||||
|
const selectedTag = tags.find((tag) => tag.slug === tagSlug)
|
||||||
|
setSelectedTags((prevTags) => {
|
||||||
|
if (prevTags.some((prevTag) => prevTag.slug === tagSlug)) {
|
||||||
|
// Si el tag ya está seleccionado, quítalo de la lista
|
||||||
|
return prevTags.filter((prevTag) => prevTag.slug !== tagSlug)
|
||||||
|
} else {
|
||||||
|
// Si el tag no está seleccionado, agrégalo a la lista
|
||||||
|
return [...prevTags, selectedTag]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label htmlFor={tag?.slug}>{tag?.name}</label>
|
||||||
|
</div>
|
||||||
|
))} */}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<textarea
|
||||||
|
className="w-full border-2 p-2 rounded-md placeholder-gray-500 bg- focus:placeholder-gray-500 resize-none "
|
||||||
|
placeholder="Write a comment..."
|
||||||
|
{...register('description', { required: 'El campo de comentario es obligatorio' })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
className="px-4 py-2 rounded-md w-full"
|
||||||
|
placeholder="Url"
|
||||||
|
{...register('url', {
|
||||||
|
required: 'La url es obligatoria'
|
||||||
|
// pattern: {
|
||||||
|
// value: /^(ftp|http|https):\/\/[^ "]+$/,
|
||||||
|
// message: 'Ingrese una URL válida'
|
||||||
|
// }
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{errors && errors.title && (
|
||||||
|
<div className="bg-red-500 rounded-md flex justify-center font-bold mb-5">
|
||||||
|
<p className="text-white text-sm p-2 uppercase">{errors.title.message}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{errors && errors.description && (
|
||||||
|
<div className="bg-red-500 rounded-md flex justify-center font-bold mb-5">
|
||||||
|
<p className="text-white text-sm p-2 uppercase">{errors.description.message}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{errors && errors.url && (
|
||||||
|
<div className="bg-red-500 rounded-md flex justify-center font-bold mb-5">
|
||||||
|
<p className="text-white text-sm p-2 uppercase">{errors.url.message}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{error && (
|
||||||
|
<div className="bg-red-500 rounded-md flex justify-center font-bold mb-5">
|
||||||
|
<p className="text-white text-sm p-2 uppercase">{error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-end ">
|
||||||
|
<Button
|
||||||
|
className="h-fit"
|
||||||
|
buttonClassName="flex justify-center w-full text-white content-center font-bold bg-teal-600 py-2 px-6"
|
||||||
|
>
|
||||||
|
Editar
|
||||||
|
</Button>{' '}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
import { useSession } from 'next-auth/react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||||
|
import { User } from '../../../type'
|
||||||
|
import { Button } from '../atoms/Button'
|
||||||
|
|
||||||
|
type EditProps = {
|
||||||
|
user: User
|
||||||
|
}
|
||||||
|
export function EditUser({ user }: EditProps) {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
getValues,
|
||||||
|
reset,
|
||||||
|
setError,
|
||||||
|
formState: { errors }
|
||||||
|
} = useForm<User>({
|
||||||
|
defaultValues: {}
|
||||||
|
})
|
||||||
|
const router = useRouter()
|
||||||
|
// const [userData, setUser] = useState<User>()
|
||||||
|
|
||||||
|
const { data: session, status } = useSession()
|
||||||
|
const [errorShow, setShowError] = useState('')
|
||||||
|
const onSubmit: SubmitHandler<User> = async (dataForm) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/user/?email=${user?.email}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: dataForm.name
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.status === 409) {
|
||||||
|
const errorData = await response.json()
|
||||||
|
setError('name', { type: 'manual', message: errorData.message })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Error updating user. Please try again later.')
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
setShowError(data?.message)
|
||||||
|
// router.push('/')
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error, 'Error fetching data')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
status === 'unauthenticated' && router.push('/')
|
||||||
|
}, [session, status, router])
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
name: user?.name,
|
||||||
|
email: user?.email
|
||||||
|
})
|
||||||
|
}, [user])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="flex justify-center mb-4 uppercase">
|
||||||
|
<h1 className="lg:text-3xl text-xl font-semibold">Editar usuario</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex lg:flex-row flex-col rounded-xl border-black border-4 gap-4 p-6 mb-5">
|
||||||
|
<div className="flex justify-center items-start">
|
||||||
|
<div className="relative h-[160px] w-[160px] border-white border-2 p-4 rounded-xl">
|
||||||
|
<Image
|
||||||
|
src={user?.image}
|
||||||
|
width={200}
|
||||||
|
height={200}
|
||||||
|
alt="img post"
|
||||||
|
className="w-full h-full object-cover rounded-lg"
|
||||||
|
></Image>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-col py-4 gap-2">
|
||||||
|
<div className="flex gap-5">
|
||||||
|
<div className="flex w-full lg:flex-row flex-col items-center lg:gap-6 gap-2">
|
||||||
|
<label className="font-bold lg:w-10 w-full">Apodo:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full lg:w-2/4 px-4 py-2 rounded-md border border-gray-400"
|
||||||
|
placeholder="Name"
|
||||||
|
{...register('name', { required: 'El campo del name es obligatorio' })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-5">
|
||||||
|
<div className="flex w-full lg:flex-row flex-col items-center lg:gap-6 gap-2">
|
||||||
|
<label className="font-bold lg:w-10 w-full">Email:</label>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
type="email"
|
||||||
|
className="w-full lg:w-2/4 px-4 py-2 rounded-md border border-gray-400"
|
||||||
|
placeholder="Email"
|
||||||
|
{...register('email', { required: 'El campo del email es obligatorio' })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{errors && errors.name && (
|
||||||
|
<div className="bg-red-500 rounded-md flex justify-center font-bold mb-5">
|
||||||
|
<p className="text-white text-sm p-2 uppercase">{errors.name.message}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{errors && errors.email && (
|
||||||
|
<div className="bg-red-500 rounded-md flex justify-center font-bold mb-5">
|
||||||
|
<p className="text-white text-sm p-2 uppercase">{errors.email.message}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{errorShow && (
|
||||||
|
<div className="bg-red-500 rounded-md flex justify-center font-bold mb-5">
|
||||||
|
<p className="text-white text-sm p-2 uppercase">{errorShow}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-end ">
|
||||||
|
<Button
|
||||||
|
className="h-fit"
|
||||||
|
buttonClassName="flex justify-center w-full text-white content-center font-bold bg-teal-600 py-2 px-6"
|
||||||
|
>
|
||||||
|
Guardar
|
||||||
|
</Button>{' '}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { signOut, useSession } from 'next-auth/react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { User } from '../../../type'
|
||||||
|
import { Button } from '../atoms/Button'
|
||||||
|
interface MenuNavbarProps {
|
||||||
|
// userCurrent: User | undefined
|
||||||
|
dataUserCurrent: User
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MenuNavbar = ({ dataUserCurrent }: MenuNavbarProps) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const { status, data: session } = useSession()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="md:flex gap-2 hidden md:items-center">
|
||||||
|
<Button
|
||||||
|
buttonClassName="content-center font-bold hover:opacity-50 p-2"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</Button>
|
||||||
|
{dataUserCurrent && dataUserCurrent.isAdmin && (
|
||||||
|
<Button
|
||||||
|
buttonClassName="content-center font-bold hover:opacity-50 p-2"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/create')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{dataUserCurrent && (
|
||||||
|
<Button
|
||||||
|
className="btn-primary-500 font-bold hover:opacity-50 "
|
||||||
|
buttonClassName="content-center font-bold hover:opacity-50 p-2"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/perfil')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Perfil
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{status === 'authenticated' ? (
|
||||||
|
<Button
|
||||||
|
buttonClassName="btn-primary-500 font-bold hover:opacity-50 p-2"
|
||||||
|
onClick={() => {
|
||||||
|
signOut()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
buttonClassName="btn-primary-500 font-bold hover:opacity-50 p-2"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/login')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!dataUserCurrent && (
|
||||||
|
<Button
|
||||||
|
buttonClassName="btn-primary-500 font-bold hover:opacity-50 p-2"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/register')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{/* <Tab /> */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { Button } from '../atoms/Button'
|
||||||
|
|
||||||
|
const Pagination = ({ page, hasPrev, hasNext }: any) => {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-3 justify-between">
|
||||||
|
<Button
|
||||||
|
className="min-w-[110px] h-fit"
|
||||||
|
buttonClassName="flex justify-center w-full text-white content-center font-bold bg-teal-600 py-2 px-6 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
disabled={!hasPrev}
|
||||||
|
onClick={() => {
|
||||||
|
router.push(`?page=${Number(page) - 1}`)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="min-w-[110px] h-fit"
|
||||||
|
buttonClassName="flex justify-center w-full text-white content-center font-bold bg-teal-600 py-2 px-6 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
disabled={!hasNext}
|
||||||
|
onClick={() => {
|
||||||
|
router.push(`?page=${Number(page) + 1}`)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Pagination
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Stats } from '../../../type'
|
||||||
|
|
||||||
|
const Stats = () => {
|
||||||
|
const [stats, setStats] = useState<Stats>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/stats')
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch data')
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
setStats(data)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching stats:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchData()
|
||||||
|
}, [])
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{stats ? (
|
||||||
|
<div className="grid grid-cols-2 row-gap-8 md:grid-cols-4">
|
||||||
|
<div className="flex flex-col text-center md:border-r gap-2">
|
||||||
|
<h6 className="text-4xl font-bold lg:text-5xl xl:text-6xl">{stats.totalPosts}</h6>
|
||||||
|
<p className="text-sm font-medium tracking-widest text-gray-800 uppercase lg:text-base">Recursos totales</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col text-center md:border-r gap-2">
|
||||||
|
<h6 className="text-4xl font-bold lg:text-5xl xl:text-6xl">{stats.totalUsers}</h6>
|
||||||
|
<p className="text-sm font-medium tracking-widest text-gray-800 uppercase lg:text-base">Usuarios totales</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col text-center md:border-r gap-2">
|
||||||
|
<div>
|
||||||
|
<h6 className="text-4xl font-bold lg:text-5xl xl:text-6xl">
|
||||||
|
{stats.totalShares.twitterShareCount + stats.totalShares.whatsappShareCount}
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-medium tracking-widest text-gray-800 uppercase lg:text-base">
|
||||||
|
Recursos compartidos
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-center flex flex-col gap-2">
|
||||||
|
<h6 className="text-4xl font-bold lg:text-5xl xl:text-6xl">{stats.totalViews.views}</h6>
|
||||||
|
<p className="text-sm font-medium tracking-widest text-gray-800 uppercase lg:text-base">
|
||||||
|
Vistas totales de recursos
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p>Loading...</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Stats
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import github from '../../../public/images/github.svg'
|
||||||
|
import linkedin from '../../../public/images/linkedin.svg'
|
||||||
|
|
||||||
|
export function Footer() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col justify-between w-full bg-black lg:px-28 lg:py-12 p-6 text-white">
|
||||||
|
<div className="flex lg:flex-row gap-8 lg:justify-between flex-col lg:text-left text-center lg:mt-0 mt-8">
|
||||||
|
<div className="lg:w-[280px]">
|
||||||
|
<h4 className="font-extrabold text-base lg:text-2xl lg:block mb-4">
|
||||||
|
Encuentra recursos, guarda, y compartelos.
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
{/* <div className="lg:text-left">
|
||||||
|
<ul className="flex flex-col gap-4">
|
||||||
|
<li className="text-green-600 font-bold">Titulo Footer</li>
|
||||||
|
<li className="cursor-pointer">Texto footer 1</li>
|
||||||
|
<li>Texto footer 2</li>
|
||||||
|
</ul>
|
||||||
|
</div> */}
|
||||||
|
{/* <div className="lg:text-left ">
|
||||||
|
<ul className="flex flex-col gap-4">
|
||||||
|
<li className="text-green-600 font-bold">Titulo Footer 2</li>
|
||||||
|
<li>Texto footer 1</li>
|
||||||
|
<li>Texto footer 2</li>
|
||||||
|
</ul>
|
||||||
|
</div> */}
|
||||||
|
{/* <div className="lg:text-left ">
|
||||||
|
<ul className="flex flex-col gap-4">
|
||||||
|
<li className="text-green-600 font-bold">Titulo Footer 3</li>
|
||||||
|
<li>Texto footer 1</li>
|
||||||
|
<li>Texto footer 2</li>
|
||||||
|
</ul>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
<div className="flex lg:flex-row lg:justify-between flex-col items-center">
|
||||||
|
<p>Manuel Cebreiro.</p>
|
||||||
|
<div className="flex items-center w-[120px] lg:justify-between justify-evenly my-5">
|
||||||
|
<Link href={'https://github.com/ManuelCebreiro'} target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="flex items-center justify-center bg-white border-2 border-white rounded-full w-12 h-12 cursor-pointer">
|
||||||
|
<Image src={github} alt="github" className="w-10 h-10" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'https://www.linkedin.com/in/manuelcebreiro'} target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="flex items-center justify-center bg-white border-2 border-white rounded-full w-12 h-12 cursor-pointer">
|
||||||
|
<Image src={linkedin} alt="github" className="w-12 h-12" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { Button } from '@/components/atoms/Button'
|
||||||
|
import { signIn, useSession } from 'next-auth/react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||||
|
|
||||||
|
type FormData = {
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FormLogin() {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors }
|
||||||
|
} = useForm<FormData>()
|
||||||
|
const onSubmit: SubmitHandler<FormData> = (data) => {}
|
||||||
|
const router = useRouter()
|
||||||
|
const { data, status } = useSession()
|
||||||
|
|
||||||
|
if (status === 'loading') {
|
||||||
|
return <div>...loading</div>
|
||||||
|
}
|
||||||
|
status === 'authenticated' && router.push('/')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col justify-center items-center py-48">
|
||||||
|
<h1 className="mb-5">Login</h1>
|
||||||
|
{/* <p className="mb-7 text-center">Estamos en proceso de añadir mas maneras de registrarse y logearse...</p> */}
|
||||||
|
{/* <form onSubmit={handleSubmit(onSubmit)} className="flex flex-col justify-center items-center mb-5">
|
||||||
|
<div className="flex flex-col mb-4 w-60">
|
||||||
|
<label htmlFor="email">Email</label>
|
||||||
|
<input className="border-2 p-2" type="text" id="email" {...register('email')} />
|
||||||
|
<label htmlFor="password">Password</label>
|
||||||
|
<input className="border-2 p-2" type="password" id="password" {...register('password')} />
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="h-fit"
|
||||||
|
buttonClassName="flex justify-center w-full text-white content-center font-bold bg-teal-600 py-2 px-6"
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</form> */}
|
||||||
|
{/* <h3 className="flex items-center w-60 mb-5">
|
||||||
|
<span className="flex-grow bg-gray-200 rounded h-1"></span>
|
||||||
|
<span className="mx-3 text-lg font-medium">or</span>
|
||||||
|
<span className="flex-grow bg-gray-200 rounded h-1"></span>
|
||||||
|
</h3>{' '} */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex w-60">
|
||||||
|
<Button
|
||||||
|
className="w-full bg-[#252424]"
|
||||||
|
buttonClassName="w-full flex justify-center text-white content-center font-bold px-5 py-4"
|
||||||
|
onClick={() => signIn('github')}
|
||||||
|
>
|
||||||
|
Sign in with Github
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-60">
|
||||||
|
<Button
|
||||||
|
className="w-full bg-[#ff5555]"
|
||||||
|
buttonClassName="w-full flex justify-center text-white content-center font-bold px-5 py-4"
|
||||||
|
onClick={() => signIn('google')}
|
||||||
|
>
|
||||||
|
Sign in with Google
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
import { signOut, useSession } from 'next-auth/react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { GiHamburgerMenu } from 'react-icons/gi'
|
||||||
|
import { GrClose } from 'react-icons/gr'
|
||||||
|
import iconWorldResource from '../../../public/images/devWorldResources-removebg.svg'
|
||||||
|
import { User } from '../../../type'
|
||||||
|
import { Button } from '../atoms/Button'
|
||||||
|
import { MenuNavbar } from '../molecules/MenuNavbar'
|
||||||
|
|
||||||
|
export function Navbar() {
|
||||||
|
const router = useRouter()
|
||||||
|
const [open, setOpen] = useState<Boolean>()
|
||||||
|
// const [users, setUsers] = useState<User[]>()
|
||||||
|
const [dataUserCurrent, setDataUserCurrent] = useState<User>()
|
||||||
|
const { status, data: session } = useSession()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Hago un get del usuario para traerme los datos de este.
|
||||||
|
const fetchUser = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/user?email=${session?.user?.email}`)
|
||||||
|
const userData = await response.json()
|
||||||
|
setDataUserCurrent(userData)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error fetching user:', error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session?.user?.email) {
|
||||||
|
fetchUser()
|
||||||
|
}
|
||||||
|
}, [session])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center justify-between w-full h-20 lg:py-4 lg:px-14 px-4 mb-1 bg-white">
|
||||||
|
<div className="w-40 h-14">
|
||||||
|
<Image
|
||||||
|
quality={100}
|
||||||
|
src={iconWorldResource}
|
||||||
|
alt="Home review"
|
||||||
|
width={100}
|
||||||
|
height={100}
|
||||||
|
className="object-contain cursor-pointer w-full"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="font-semibold text-xl">{}</span>
|
||||||
|
<MenuNavbar dataUserCurrent={dataUserCurrent} />
|
||||||
|
<div className="md:hidden">
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(!open)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{open && <GrClose />}
|
||||||
|
{!open && <GiHamburgerMenu />}
|
||||||
|
</div>
|
||||||
|
{open ? (
|
||||||
|
<div className=" gap-3 absolute top-20 left-0 w-full">
|
||||||
|
<div className="w-full absolute bg-gray-100 flex flex-col items-center gap-5 p-4 z-30">
|
||||||
|
<Button
|
||||||
|
className="!w-full py-2 px-4 flex justify-center"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</Button>
|
||||||
|
{dataUserCurrent?.isAdmin && (
|
||||||
|
<Button
|
||||||
|
className="!w-full py-2 px-4 flex justify-center"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/create')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{dataUserCurrent && (
|
||||||
|
<Button
|
||||||
|
className="!w-full py-2 px-4 flex justify-center"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/perfil')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Perfil
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!dataUserCurrent && <Button className="!w-full py-2 px-4 flex justify-center">Register</Button>}
|
||||||
|
{status === 'authenticated' ? (
|
||||||
|
<Button
|
||||||
|
className="!w-full py-2 px-4 flex justify-center"
|
||||||
|
onClick={() => {
|
||||||
|
signOut()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
buttonClassName="btn-primary-500 font-bold hover:opacity-50"
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/login')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { FaFilterCircleXmark } from 'react-icons/fa6'
|
||||||
|
import { Post } from '../../../type'
|
||||||
|
import AutoCompleteInput from '../atoms/AutoCompleteInput'
|
||||||
|
import { Button } from '../atoms/Button'
|
||||||
|
|
||||||
|
// type SectionCategoryProps = {
|
||||||
|
// onCategoryClick: (value: string) => void
|
||||||
|
// caterogySelect: string
|
||||||
|
// page: number
|
||||||
|
// selectedTags: string[]
|
||||||
|
// onTagClick: (value: string) => void
|
||||||
|
// }
|
||||||
|
|
||||||
|
export function SectionCategories({
|
||||||
|
onCategoryClick,
|
||||||
|
caterogySelect,
|
||||||
|
page
|
||||||
|
}: // selectedTags,
|
||||||
|
// onTagClick
|
||||||
|
// setSelectedTags
|
||||||
|
any) {
|
||||||
|
const [category, setCategory] = useState([])
|
||||||
|
const [textSearched, setTextSearched] = useState('')
|
||||||
|
const [posts, setPosts] = useState<Post[]>([])
|
||||||
|
|
||||||
|
// const [tags, setTags] = useState([])
|
||||||
|
const router = useRouter()
|
||||||
|
useEffect(() => {
|
||||||
|
const getData = async () => {
|
||||||
|
try {
|
||||||
|
const responseCategory = await fetch('api/categories', {
|
||||||
|
cache: 'no-store'
|
||||||
|
})
|
||||||
|
if (!responseCategory.ok) {
|
||||||
|
throw new Error('Failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultCategory = await responseCategory.json()
|
||||||
|
setCategory(resultCategory)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getData()
|
||||||
|
}, [caterogySelect])
|
||||||
|
useEffect(() => {
|
||||||
|
const getData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`api/allPosts?category=${caterogySelect}`, {
|
||||||
|
cache: 'no-store'
|
||||||
|
})
|
||||||
|
// router.push(`?page=1`)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed')
|
||||||
|
}
|
||||||
|
const result = await response.json()
|
||||||
|
setPosts(result)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getData()
|
||||||
|
}, [caterogySelect])
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col justify-center items-center gap-6">
|
||||||
|
<div className="w-full flex flex-wrap justify-center lg:gap-3 gap-1">
|
||||||
|
{category?.map((cat: any) => (
|
||||||
|
<div
|
||||||
|
key={cat.id}
|
||||||
|
onClick={() => onCategoryClick(cat.slug)}
|
||||||
|
className={`cursor-pointer ${caterogySelect === cat.slug ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`flex justify-between items-center px-4 py-2 w-fit h-12 rounded-xl border-2 border-gray-500 gap-2`}
|
||||||
|
style={{ backgroundColor: cat.color }}
|
||||||
|
>
|
||||||
|
<div className="w-8 h-8 ">
|
||||||
|
{/* bg-white rounded-full */}
|
||||||
|
<Image
|
||||||
|
src={`/images/${cat.img}.svg`}
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
alt="img post"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
></Image>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-bold ">{cat.title}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{caterogySelect && (
|
||||||
|
<div className="flex justify-center w-full md:px-8 md:gap-4 gap-2">
|
||||||
|
<AutoCompleteInput setTextSearched={setTextSearched} textSearched={textSearched} posts={posts} />
|
||||||
|
{caterogySelect && (
|
||||||
|
<Button
|
||||||
|
className="flex justify-center lg:z-0 "
|
||||||
|
buttonClassName="px-0 py-0 button"
|
||||||
|
onClick={() => {
|
||||||
|
// setSelectedTags('')
|
||||||
|
onCategoryClick('')
|
||||||
|
}}
|
||||||
|
disabled={!caterogySelect}
|
||||||
|
>
|
||||||
|
{/* <p className=" p-2">Quitar filtros</p> */}
|
||||||
|
<FaFilterCircleXmark className="w-6 h-6" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* {caterogySelect && (
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<>
|
||||||
|
<Tag
|
||||||
|
key={tag.id}
|
||||||
|
color={tag.color}
|
||||||
|
name={tag.name}
|
||||||
|
onClick={() => {
|
||||||
|
console.log('Clicked')
|
||||||
|
|
||||||
|
onTagClick(tag.slug)
|
||||||
|
}}
|
||||||
|
className="cursor-pointer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
key={tag.id}
|
||||||
|
className={`flex justify-between items-center px-2 py-1 w-fit h-6 rounded-xl border-[1px] border-black border-solid gap-2 `}
|
||||||
|
style={{ backgroundColor: `${tag.color}80` }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
selectedTags.includes(tag.slug) ? 'opacity-20 cursor-not-allowed' : 'cursor-pointer'
|
||||||
|
} `}
|
||||||
|
onClick={() => {
|
||||||
|
console.log('Clicked')
|
||||||
|
onTagClick(tag.slug)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p className="text-xs font-bold ">{tag.name}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)} */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
export const slugify = (str: string) => {
|
||||||
|
return str
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.replace(/[^a-zA-Z0-9\s]/g, '')
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { createContext, useContext, useState } from 'react'
|
||||||
|
|
||||||
|
const AppContext = createContext()
|
||||||
|
|
||||||
|
export const PostContext = ({ children }) => {
|
||||||
|
const [postData, setPostData] = useState([])
|
||||||
|
|
||||||
|
const updatePostData = (newData) => {
|
||||||
|
setPostData(newData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <AppContext.Provider value={{ postData, updatePostData }}>{children}</AppContext.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePostContext = () => {
|
||||||
|
return useContext(AppContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { NextRouter } from 'next/router'
|
||||||
|
|
||||||
|
export const languageRedirect = (router: NextRouter, language: string) => {
|
||||||
|
const { pathname, asPath, query } = router
|
||||||
|
router.push({ pathname, query }, asPath, { locale: language })
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import AuthProvider from '@/providers/AuthProvider'
|
||||||
|
import '@/styles/globals.scss'
|
||||||
|
import { appWithTranslation } from 'next-i18next'
|
||||||
|
import type { AppProps } from 'next/app'
|
||||||
|
import { PostContext } from '../context/PostContext'
|
||||||
|
const i18nextConfig = require('../../next-i18next.config')
|
||||||
|
|
||||||
|
const App = ({ Component, pageProps }: AppProps) => {
|
||||||
|
return (
|
||||||
|
<PostContext>
|
||||||
|
<AuthProvider>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</AuthProvider>
|
||||||
|
</PostContext>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default appWithTranslation(App, i18nextConfig)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Head, Html, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
|
export default function Document() {
|
||||||
|
return (
|
||||||
|
<Html lang="es">
|
||||||
|
<Head>
|
||||||
|
</Head>
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prisma from '../../../utils/connect'
|
||||||
|
|
||||||
|
export default async function allPosts(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const { category } = req.query
|
||||||
|
|
||||||
|
if (req.method === 'GET') {
|
||||||
|
try {
|
||||||
|
let posts
|
||||||
|
|
||||||
|
if (category) {
|
||||||
|
const categoryFilter = typeof category === 'string' ? category : category[0]
|
||||||
|
|
||||||
|
posts = await prisma.post.findMany({
|
||||||
|
where: {
|
||||||
|
Category: {
|
||||||
|
slug: categoryFilter
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
include: {
|
||||||
|
Category: true,
|
||||||
|
Like: true,
|
||||||
|
comments: true,
|
||||||
|
Favorite: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Si no se proporciona una categoría, obtenemos todos los posts
|
||||||
|
posts = await prisma.post.findMany({
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
include: {
|
||||||
|
Category: true,
|
||||||
|
Like: true,
|
||||||
|
comments: true,
|
||||||
|
Favorite: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json(posts)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.setHeader('Allow', ['GET'])
|
||||||
|
res.status(405).end(`Method ${req.method} Not Allowed`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import NextAuth from 'next-auth'
|
||||||
|
import { authOptions } from '../../../utils/auth'
|
||||||
|
|
||||||
|
export default NextAuth(authOptions)
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prisma from '../../utils/connect'
|
||||||
|
|
||||||
|
export default async function GET(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
const categories = await prisma.category.findMany()
|
||||||
|
res.status(200).json(categories)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
// import { createComment, deleteComment, getAllComments } from '../../../../prisma/comment'
|
||||||
|
|
||||||
|
// export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
// try {
|
||||||
|
// switch (req.method) {
|
||||||
|
// case 'GET': {
|
||||||
|
// const comments = await getAllComments(req.query.postSlug as string)
|
||||||
|
// console.log(comments, 'comments')
|
||||||
|
|
||||||
|
// res.status(200).json(comments)
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// case 'POST': {
|
||||||
|
// console.log('EMPIEZA')
|
||||||
|
// // Manejar la lógica para el método POST
|
||||||
|
// // const session = await getAuthSession()
|
||||||
|
|
||||||
|
// console.log(req.body.session, 'session')
|
||||||
|
// await createComment(req.body, req.body.session?.user?.email, req.body.session)
|
||||||
|
// console.log('aqui llega?')
|
||||||
|
// res.status(200).json({ message: 'Comment created successfully' })
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// case 'DELETE': {
|
||||||
|
// console.log('DELETE')
|
||||||
|
// await deleteComment(req.body.id)
|
||||||
|
// res.status(200).json({ message: 'Comment deleted successfully ' })
|
||||||
|
// }
|
||||||
|
// default:
|
||||||
|
// // Manejar otros métodos HTTP si es necesario
|
||||||
|
// res.status(405).json({ message: 'Method Not Allowed' })
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// } catch (error: any) {
|
||||||
|
// console.error(error)
|
||||||
|
// const status = error.message === 'Not Authenticated' ? 401 : 500
|
||||||
|
// res.status(status).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { createComment, deleteComment, getAllComments } from '../../../../prisma/comment'
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
switch (req.method) {
|
||||||
|
case 'GET': {
|
||||||
|
const comments = await getAllComments(req.query.postSlug as string)
|
||||||
|
|
||||||
|
res.status(200).json(comments)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'POST': {
|
||||||
|
await createComment(req.body, req.body.session?.user?.email, req.body.session)
|
||||||
|
res.status(200).json({ message: 'Comment created successfully' })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'DELETE': {
|
||||||
|
await deleteComment(req.body.id)
|
||||||
|
res.status(200).json({ message: 'Comment deleted successfully ' })
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
res.status(405).json({ message: 'Method Not Allowed' })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
const status = error.message === 'Not Authenticated' ? 401 : 500
|
||||||
|
res.status(status).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { addFavorite, deleteFavorite } from '../../../../prisma/favorite'
|
||||||
|
import { getUserByEmail } from '../../../../prisma/user'
|
||||||
|
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
try {
|
||||||
|
switch (req.method) {
|
||||||
|
case 'POST': {
|
||||||
|
const { email, postId, session } = req.body
|
||||||
|
if (!email) {
|
||||||
|
res.status(400).json({ message: 'User email is required' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!postId) {
|
||||||
|
res.status(400).json({ message: 'Post ID is required' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const user = await getUserByEmail(email as string)
|
||||||
|
if (!user) {
|
||||||
|
res.status(404).json({ message: 'User not found' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const newFavorite = await addFavorite(postId, user.email, session)
|
||||||
|
res.status(200).json(newFavorite)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'DELETE': {
|
||||||
|
const { email, postId, session } = req.body
|
||||||
|
if (!email) {
|
||||||
|
res.status(400).json({ message: 'User email is required' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!postId) {
|
||||||
|
res.status(400).json({ message: 'Post ID is required' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const user = await getUserByEmail(email as string)
|
||||||
|
if (!user) {
|
||||||
|
res.status(404).json({ message: 'User not found' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await deleteFavorite(postId, user.email, session)
|
||||||
|
res.status(200).json({ message: 'Favorite removed successfully.' })
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'Error removing favorite', error: error.message })
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
res.status(405).json({ message: 'Method Not Allowed' })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
const status = error.message === 'Not Authenticated' ? 401 : 500
|
||||||
|
res.status(status).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { addLike, deleteLike } from '../../../../prisma/like'
|
||||||
|
import { getUserByEmail } from '../../../../prisma/user'
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
switch (req.method) {
|
||||||
|
case 'POST': {
|
||||||
|
const { email, postId, session } = req.body
|
||||||
|
if (!email) {
|
||||||
|
res.status(400).json({ message: 'User email is required' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!postId) {
|
||||||
|
res.status(400).json({ message: 'Post ID is required' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const user = await getUserByEmail(email as string)
|
||||||
|
if (!user) {
|
||||||
|
res.status(404).json({ message: 'User not found' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const newLike = await addLike(postId, user.email, session)
|
||||||
|
res.status(200).json(newLike)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'DELETE': {
|
||||||
|
const { email, postId, session } = req.body
|
||||||
|
if (!email) {
|
||||||
|
res.status(400).json({ message: 'User email is required' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!postId) {
|
||||||
|
res.status(400).json({ message: 'Post ID is required' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const user = await getUserByEmail(email as string)
|
||||||
|
if (!user) {
|
||||||
|
res.status(404).json({ message: 'User not found' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await deleteLike(postId, user.email, session)
|
||||||
|
res.status(200).json({ message: 'Like eliminado correctamente.' })
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'Error al quitar el like', error: error.message })
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
res.status(405).json({ message: 'Method Not Allowed' })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
const status = error.message === 'Not Authenticated' ? 401 : 500
|
||||||
|
res.status(status).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prisma from '../../utils/connect'
|
||||||
|
|
||||||
|
export default async function POST(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const { userEmail, slug } = req.body
|
||||||
|
if (!userEmail) {
|
||||||
|
res.status(401).json({ error: 'Not Authenticated' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
email: userEmail
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user || !user.isAdmin) {
|
||||||
|
res.status(403).json({ error: 'Not Authorized', type: 'unAuthorized' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const existingPost = await prisma.post.findUnique({
|
||||||
|
where: {
|
||||||
|
slug: slug
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (existingPost) {
|
||||||
|
res.status(400).json({ error: 'Post with this slug already exists', type: 'duplicate' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = await prisma.post.create({
|
||||||
|
data: { ...req.body }
|
||||||
|
})
|
||||||
|
res.status(200).json(post)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating post:', error)
|
||||||
|
res.status(500).json({ error: 'Error creating post' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { deletePost, editPost } from '../../../../prisma/post'
|
||||||
|
import prisma from '../../../utils/connect'
|
||||||
|
|
||||||
|
//GET SINGLE POST
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
// try {
|
||||||
|
// const { slug } = req.query
|
||||||
|
|
||||||
|
// const post = await prisma.post.update({
|
||||||
|
// where: { slug: String(slug) },
|
||||||
|
// data: { views: { increment: 1 } },
|
||||||
|
// include: { user: true }
|
||||||
|
// // include: {
|
||||||
|
// // comments: {
|
||||||
|
// // include: {
|
||||||
|
// // user: true
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// })
|
||||||
|
// // console.log(post, 'POST BACK')
|
||||||
|
|
||||||
|
// res.status(200).json(post)
|
||||||
|
// } catch (error: any) {
|
||||||
|
// console.error(error)
|
||||||
|
// res.status(500).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
// }
|
||||||
|
try {
|
||||||
|
const { slug } = req.query
|
||||||
|
const { body } = req
|
||||||
|
switch (req.method) {
|
||||||
|
case 'GET': {
|
||||||
|
const post = await prisma.post.update({
|
||||||
|
where: { slug: String(slug) },
|
||||||
|
data: { views: { increment: 1 } },
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
Category: true,
|
||||||
|
Like: true,
|
||||||
|
comments: true,
|
||||||
|
Favorite: true
|
||||||
|
// , Tags: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
res.status(200).json(post)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'DELETE': {
|
||||||
|
await deletePost(String(slug))
|
||||||
|
res.status(200).json({ message: 'Post deleted successfully' })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'PUT': {
|
||||||
|
await editPost(String(slug), body)
|
||||||
|
res.status(200).json({ message: 'Post updated successfully' })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
res.status(405).json({ message: 'Method Not Allowed' })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { resolve } from 'url'
|
||||||
|
import prisma from '../../utils/connect'
|
||||||
|
|
||||||
|
export default async function GET(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const url = resolve(`${process.env.NEXTAUTH_URL_INTERNAL}`, req.url || 'page=1') //pendiente de quitar /api/posts?page=1 ESTO ES UNA PRUEBA QUITALO SI NO VA
|
||||||
|
const { searchParams } = new URL(url)
|
||||||
|
const page = searchParams.get('page')
|
||||||
|
let cat = searchParams.get('cat')
|
||||||
|
// let tags = searchParams.get('tags')
|
||||||
|
|
||||||
|
const POST_PER_PAGE = 4
|
||||||
|
const pageNumber = page ? parseInt(page, 10) : 1
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
take: POST_PER_PAGE,
|
||||||
|
skip: POST_PER_PAGE * (pageNumber - 1),
|
||||||
|
where: {
|
||||||
|
...(cat && { catSlug: cat })
|
||||||
|
// ...(tags && {
|
||||||
|
// Tags: {
|
||||||
|
// some: {
|
||||||
|
// slug: {
|
||||||
|
// in: tags.split(',')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
include: {
|
||||||
|
Category: true,
|
||||||
|
Like: true,
|
||||||
|
comments: true,
|
||||||
|
Favorite: true
|
||||||
|
// Tags: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// const [posts, count] = await prisma.$transaction([
|
||||||
|
// prisma.post.findMany(query),
|
||||||
|
// prisma.post.count({ where: query.where })
|
||||||
|
// ])
|
||||||
|
const posts = await prisma.post.findMany({
|
||||||
|
...query,
|
||||||
|
orderBy: { createdAt: 'desc' }
|
||||||
|
})
|
||||||
|
const count = await prisma.post.count({ where: query.where })
|
||||||
|
res.status(200).json({ posts, count })
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prisma from '../../../utils/connect'
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
if (req.method !== 'GET') {
|
||||||
|
return res.status(405).json({ message: 'Method Not Allowed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const userEmail = req.query.userEmail as string
|
||||||
|
if (!userEmail) {
|
||||||
|
return res.status(400).json({ message: 'User email is required' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { email: userEmail },
|
||||||
|
include: {
|
||||||
|
Favorite: {
|
||||||
|
include: {
|
||||||
|
post: {
|
||||||
|
include: {
|
||||||
|
Like: true,
|
||||||
|
Favorite: true,
|
||||||
|
Category: true,
|
||||||
|
comments: true
|
||||||
|
// _count: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ message: 'User not found' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const favoritePosts = user.Favorite.map((favorite) => favorite.post)
|
||||||
|
|
||||||
|
return res.status(200).json(favoritePosts)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prisma from '../../../utils/connect'
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
if (req.method !== 'GET') {
|
||||||
|
return res.status(405).json({ message: 'Method Not Allowed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const userEmail = req.query.userEmail as string
|
||||||
|
if (!userEmail) {
|
||||||
|
return res.status(400).json({ message: 'User email is required' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { email: userEmail },
|
||||||
|
include: {
|
||||||
|
Like: {
|
||||||
|
include: {
|
||||||
|
post: {
|
||||||
|
include: {
|
||||||
|
Like: true,
|
||||||
|
Category: true,
|
||||||
|
Favorite: true,
|
||||||
|
comments: true
|
||||||
|
// _count: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ message: 'User not found' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const favoritePosts = user.Like.map((favorite) => favorite.post)
|
||||||
|
|
||||||
|
return res.status(200).json(favoritePosts)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ message: 'Method Not Allowed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const { postId, platform } = req.body
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedPost = await prisma.post.update({
|
||||||
|
where: { id: postId },
|
||||||
|
data: {
|
||||||
|
whatsappShareCount: platform === 'whatsapp' ? { increment: 1 } : undefined,
|
||||||
|
twitterShareCount: platform === 'twitter' ? { increment: 1 } : undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(200).json({ message: 'Post shared successfully', post: updatedPost })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sharing post:', error)
|
||||||
|
return res.status(500).json({ message: 'Internal Server Error' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { getAllStats } from '../../../../prisma/stats'
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
const { method } = req
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case 'GET':
|
||||||
|
const stats = await getAllStats()
|
||||||
|
res.status(200).json(stats)
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
res.status(405).json({ message: 'Method Not Allowed' })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).json({ message: 'Something went wrong', error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||