@ -0,0 +1,48 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
amd: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
plugins: ['prettier', 'simple-import-sort'],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'prettier/@typescript-eslint',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:jsx-a11y/recommended',
|
||||||
|
'prettier/@typescript-eslint',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': ['error', {}, { usePrettierrc: true }],
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'simple-import-sort/sort': 'error',
|
||||||
|
'sort-imports': 'off',
|
||||||
|
'import/order': 'off',
|
||||||
|
'jsx-a11y/anchor-is-valid': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
components: ['Link'],
|
||||||
|
specialLink: ['hrefLeft', 'hrefRight'],
|
||||||
|
aspects: ['invalidHref', 'preferButton'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
# 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.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
docker/
|
||||||
|
.env
|
||||||
|
dump*
|
||||||
|
|
||||||
|
.log
|
||||||
|
|
||||||
|
assets/
|
@ -0,0 +1,4 @@
|
|||||||
|
.cache
|
||||||
|
package.json
|
||||||
|
package-lock.json
|
||||||
|
public
|
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
semi: false,
|
||||||
|
trailingComma: "all",
|
||||||
|
singleQuote: true,
|
||||||
|
printWidth: 90,
|
||||||
|
tabWidth: 2
|
||||||
|
};
|
@ -0,0 +1,60 @@
|
|||||||
|
# Artimañas 2020
|
||||||
|
|
||||||
|
Este es el repositorio para el código del sitio web de Artimañas 2020.
|
||||||
|
|
||||||
|
## Instrucciones para desarrollo
|
||||||
|
|
||||||
|
La aplicación web consiste de dos partes:
|
||||||
|
|
||||||
|
1. CMS: [Directus](https://directus.io/)
|
||||||
|
2. Componente de SSR (Server Side Rendering): [Next.js](https://nextjs.org/)
|
||||||
|
|
||||||
|
### Configurando el entorno
|
||||||
|
|
||||||
|
Hay 3 variables de entorno que se deben configurar en el archivo `.env`, la primera corresponde a la contraseña de la base de datos de MySQL y las otras dos a la seguridad de Directus:
|
||||||
|
|
||||||
|
- `MYSQL_PASSWORD`
|
||||||
|
- `DIRECTUS_AUTH_PUBLICKEY`
|
||||||
|
- `DIRECTUS_AUTH_SECRETKEY`
|
||||||
|
|
||||||
|
Estas pueden ser cualquier string, pero se deben asignar antes de crear los contenedores, y si se modifican posteriormente, los contenedores no funcionaran correctamente.
|
||||||
|
|
||||||
|
El script `setup-env.sh` permite generar valores para las variables de entorno automáticamente y crear e iniciar los contenedores de docker después de esto.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
./setup-env.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### CMS y base de datos
|
||||||
|
|
||||||
|
Directus depende de una base de datos SQL que se puede levantar usando [Docker](https://www.docker.com/) con el archivo de [docker-compose](https://docs.docker.com/compose/) provisto:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Cuando el contenedor de la base de datos se cree por primera vez, la base de datos se inicializará con el dump que se encuentra en `./db/init.sql`. Este contiene las tablas para las obras, biografías, e información general del sitio, así como los usuarios correspondientes a cada alumno de la materia. Para modificar los datos una vez iniciados los contenedores, se puede acceder a la interfaz web de directus en [http://localhost:8080](http://localhost:8080) con las siguientes credenciales:
|
||||||
|
|
||||||
|
- Usuario: `admin@artiweb.net`
|
||||||
|
- Contraseña: `password`
|
||||||
|
|
||||||
|
Eventualmente, este dump deberá ser actualizado con el contenido real/final, para que el entorno de desarrollo sea lo mas fiel posible con respecto al de producción. Esto se puede llevar a cabo con el script provisto en la raíz del repositorio (`manage-db`):
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
./manage-db.sh backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sitio web (front end)
|
||||||
|
|
||||||
|
Una vez que la base de datos haya sido inicializada, se puede iniciar el servidor de desarrollo (componente de SSR) con:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Los archivos en el directorio `src` se pueden editar y los cambios se verán reflejados en el navegador sin la necesidad de recargar la página.
|
||||||
|
|
||||||
|
## Screencasts
|
||||||
|
|
||||||
|
- [Configurando el servidor de desarrollo en Manjaro / Arch Linux](https://youtu.be/1_Eo37owlDw)
|
||||||
|
- [Descripción de los archivos del respositorio](https://youtu.be/5-D9CbGm-8Q)
|
@ -1,6 +1,7 @@
|
|||||||
MYSQL_HOST=mysql
|
MYSQL_PASSWORD=
|
||||||
MYSQL_DB=directus
|
|
||||||
MYSQL_PASSWORD=directus
|
|
||||||
MYSQL_USER=directus
|
|
||||||
DIRECTUS_AUTH_PUBLICKEY=
|
DIRECTUS_AUTH_PUBLICKEY=
|
||||||
DIRECTUS_AUTH_SECRETKEY=
|
DIRECTUS_AUTH_SECRETKEY=
|
||||||
|
|
||||||
|
DIRECTUS_HOST=
|
||||||
|
DIRECTUS_API_USER=
|
||||||
|
DIRECTUS_API_PASSWORD=
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
const dotenv = require('dotenv')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const fetch = require('node-fetch')
|
||||||
|
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
const { DIRECTUS_HOST, DIRECTUS_API_USER, DIRECTUS_API_PASSWORD } = process.env
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const res = await fetch(`${DIRECTUS_HOST}/_/auth/authenticate`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ email: DIRECTUS_API_USER, password: DIRECTUS_API_PASSWORD }),
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
const { data } = await res.json()
|
||||||
|
const { token } = data
|
||||||
|
|
||||||
|
const roles = await fetch(`${DIRECTUS_HOST}/_/roles?access_token=${token}`)
|
||||||
|
const rolesJson = await roles.json()
|
||||||
|
|
||||||
|
const studentsRole = rolesJson.data.find((role) => role.name === 'Alumn@')
|
||||||
|
const guestsRole = rolesJson.data.find((role) => role.name === 'Invitad@')
|
||||||
|
const studentsRoleId = studentsRole.id
|
||||||
|
const guestsRoleId = guestsRole.id
|
||||||
|
|
||||||
|
const allUsers = await fetch(`${DIRECTUS_HOST}/_/users?access_token=${token}`)
|
||||||
|
const allUsersJson = await allUsers.json()
|
||||||
|
const usersFiltered = allUsersJson.data.filter(
|
||||||
|
(user) => user.role === studentsRoleId || user.role === guestsRoleId,
|
||||||
|
)
|
||||||
|
|
||||||
|
const allEvents = await fetch(
|
||||||
|
`${DIRECTUS_HOST}/_/items/cronograma?access_token=${token}`,
|
||||||
|
)
|
||||||
|
const allEventsJson = await allEvents.json()
|
||||||
|
|
||||||
|
const allEventsWithUserNames = allEventsJson.data.map((event) => {
|
||||||
|
const user = usersFiltered.find((user) => user.id === event.user_asociado)
|
||||||
|
|
||||||
|
if (!user) return event
|
||||||
|
|
||||||
|
const user_name = `${user.first_name.split(/[ ,]+/)[0]} ${
|
||||||
|
user.last_name.split(/[ ,]+/)[0]
|
||||||
|
}`
|
||||||
|
|
||||||
|
return {
|
||||||
|
...event,
|
||||||
|
user_name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(__dirname, 'src/events.json'),
|
||||||
|
JSON.stringify(allEventsWithUserNames),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/types/global" />
|
||||||
|
|
||||||
|
declare const Textfit: any
|
||||||
|
declare module 'react-textfit'
|
@ -0,0 +1,42 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const withPlugins = require('next-compose-plugins')
|
||||||
|
const withSourceMaps = require('@zeit/next-source-maps')
|
||||||
|
const optimizedImages = require('next-optimized-images')
|
||||||
|
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||||
|
enabled: process.env.ANALYZE === 'true',
|
||||||
|
})
|
||||||
|
// const withCSS = require('@zeit/next-css')
|
||||||
|
|
||||||
|
const basePath = process.env.NODE_ENV === 'development' ? '' : ''
|
||||||
|
|
||||||
|
module.exports = withPlugins(
|
||||||
|
[
|
||||||
|
[withBundleAnalyzer({})],
|
||||||
|
[
|
||||||
|
withSourceMaps({
|
||||||
|
webpack(config) {
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
optimizedImages,
|
||||||
|
{
|
||||||
|
responsive: {
|
||||||
|
adapter: require('responsive-loader/sharp'),
|
||||||
|
publicPath: path.join(basePath, '_next/static/images/'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// [withCSS({ cssModules: true })],
|
||||||
|
],
|
||||||
|
{
|
||||||
|
publicRuntimeConfig: {
|
||||||
|
basePath,
|
||||||
|
},
|
||||||
|
serverRuntimeConfig: {
|
||||||
|
PROJECT_ROOT: __dirname,
|
||||||
|
},
|
||||||
|
basePath,
|
||||||
|
},
|
||||||
|
)
|
@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"name": "seminario-next",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev -p 13001",
|
||||||
|
"build": "next build",
|
||||||
|
"export": "next export",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
|
||||||
|
"fix": "eslint 'src/**/*.{js,jsx,ts,tsx}' --fix"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@chakra-ui/react": "^1.0.0",
|
||||||
|
"@chakra-ui/theme": "^1.0.0",
|
||||||
|
"@chakra-ui/theme-tools": "^1.0.0",
|
||||||
|
"@directus/sdk-js": "^6.3.0",
|
||||||
|
"@emotion/react": "^11.1.1",
|
||||||
|
"@emotion/styled": "^11.0.0",
|
||||||
|
"@next/bundle-analyzer": "^9.5.5",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.8.1",
|
||||||
|
"@typescript-eslint/parser": "^4.8.1",
|
||||||
|
"@zeit/next-css": "^1.0.1",
|
||||||
|
"@zeit/next-source-maps": "0.0.3",
|
||||||
|
"bent": "^7.3.12",
|
||||||
|
"eslint": "^7.13.0",
|
||||||
|
"eslint-config-prettier": "^6.15.0",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||||
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
|
"eslint-plugin-react": "^7.21.5",
|
||||||
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
|
"focus-visible": "^5.2.0",
|
||||||
|
"framer-motion": "^2.9.4",
|
||||||
|
"imagemin-mozjpeg": "^9.0.0",
|
||||||
|
"imagemin-optipng": "^8.0.0",
|
||||||
|
"lodash": "^4.17.20",
|
||||||
|
"lqip-loader": "^2.2.1",
|
||||||
|
"next": "^10.0.2",
|
||||||
|
"next-compose-plugins": "^2.2.1",
|
||||||
|
"next-optimized-images": "^2.6.2",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"prettier": "^2.1.2",
|
||||||
|
"react": "^17.0.1",
|
||||||
|
"react-dom": "^17.0.1",
|
||||||
|
"react-headroom": "^3.0.0",
|
||||||
|
"react-icons": "^3.11.0",
|
||||||
|
"react-markdown": "^5.0.3",
|
||||||
|
"react-textfit": "^1.1.0",
|
||||||
|
"responsive-loader": "^2.2.0",
|
||||||
|
"sharp": "^0.26.3",
|
||||||
|
"smoothscroll-polyfill": "^0.4.4",
|
||||||
|
"typeface-ibm-plex-sans": "^1.1.13",
|
||||||
|
"webp-loader": "^0.6.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bent": "^7.3.2",
|
||||||
|
"@types/node": "^14.14.10",
|
||||||
|
"@types/nprogress": "^0.2.0",
|
||||||
|
"@types/react": "^16.9.56",
|
||||||
|
"@types/react-dom": "^16.9.9",
|
||||||
|
"@types/react-headroom": "^2.2.1",
|
||||||
|
"@types/smoothscroll-polyfill": "^0.3.1",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"eslint-plugin-simple-import-sort": "^5.0.3",
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
|
"typescript": "^4.1.2"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 300 B |
After Width: | Height: | Size: 486 B |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 335 B |
@ -0,0 +1 @@
|
|||||||
|
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
check_available() {
|
||||||
|
# Function to check if a program is installed
|
||||||
|
which $1 &> /dev/null
|
||||||
|
|
||||||
|
if [ $? = 1 ]; then
|
||||||
|
echo "$1 is not available, please install it before running the script"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_password() {
|
||||||
|
check_available openssl
|
||||||
|
openssl rand -base64 32
|
||||||
|
}
|
||||||
|
|
||||||
|
read -p "MySQL password: (press enter to randomize): " mysql_password
|
||||||
|
mysql_password=${mysql_password:-`gen_password`}
|
||||||
|
|
||||||
|
read -p "Directus pubkey: (press enter to randomize): " directus_pubkey
|
||||||
|
directus_pubkey=${directus_pubkey:-`gen_password`}
|
||||||
|
|
||||||
|
read -p "Directus secretkey: (press enter to randomize): " directus_secret
|
||||||
|
directus_secret=${directus_secret:-`gen_password`}
|
||||||
|
|
||||||
|
if [ ! -f "./.env" ]; then
|
||||||
|
echo "Copying ./env.example to ./.env"
|
||||||
|
cp ./env.example ./.env
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed -i -e "s#MYSQL_PASSWORD=.*#MYSQL_PASSWORD=${mysql_password}#g" \
|
||||||
|
"$(dirname "$0")/.env"
|
||||||
|
|
||||||
|
sed -i -e "s#DIRECTUS_AUTH_PUBLICKEY=.*#DIRECTUS_AUTH_PUBLICKEY=${directus_pubkey}#g" \
|
||||||
|
"$(dirname "$0")/.env"
|
||||||
|
|
||||||
|
sed -i -e "s#DIRECTUS_AUTH_SECRETKEY=.*#DIRECTUS_AUTH_SECRETKEY=${directus_secret}#g" \
|
||||||
|
"$(dirname "$0")/.env"
|
||||||
|
|
||||||
|
read -p "Start docker containers? (requires docker-compose) [Y/n] " start_docker
|
||||||
|
start_docker=${start_docker:-Y}
|
||||||
|
|
||||||
|
if [[ $start_docker =~ [yY] ]]; then
|
||||||
|
check_available docker-compose
|
||||||
|
sudo docker-compose up -d
|
||||||
|
|
||||||
|
# while true; do
|
||||||
|
# sudo docker-compose logs mysql | grep "mysqld: ready for connections" &> /dev/null
|
||||||
|
# EC=$?
|
||||||
|
# if [ $EC -eq 0 ]; then
|
||||||
|
# sleep 5
|
||||||
|
# sudo docker-compose run --rm directus install --email admin@artiweb.net --password password
|
||||||
|
# break
|
||||||
|
# fi
|
||||||
|
# done
|
||||||
|
fi
|
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ChakraProps, Box } from '@chakra-ui/core'
|
||||||
|
|
||||||
|
import { ReactComponent as LogoSVG } from '../images/header/logo.svg'
|
||||||
|
|
||||||
|
interface LogoProps {
|
||||||
|
onClick?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Logo: React.FC = (props) => {
|
||||||
|
return <LogoSVG {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledLogo: React.FC<ChakraProps & LogoProps> = (props) => {
|
||||||
|
return <Box as={Logo} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StyledLogo
|
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { IconButton } from '@chakra-ui/react'
|
||||||
|
import { MdMenu, MdClose } from 'react-icons/md'
|
||||||
|
|
||||||
|
interface MenuButtonProps {
|
||||||
|
isOpen?: boolean
|
||||||
|
handleClick?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const MenuButton: React.FC<MenuButtonProps> = ({ handleClick, isOpen = false }) => {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
onClick={handleClick}
|
||||||
|
aria-label="Open menu"
|
||||||
|
icon={!isOpen ? <MdMenu /> : <MdClose />}
|
||||||
|
fontSize="2rem"
|
||||||
|
mr="1rem"
|
||||||
|
variant="ghost"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuButton
|
@ -0,0 +1,86 @@
|
|||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { AnimateSharedLayout, motion } from 'framer-motion'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { Link as ChakraLink, Flex, Box, Stack, HStack } from '@chakra-ui/react'
|
||||||
|
import Headroom from 'react-headroom'
|
||||||
|
|
||||||
|
import paths from '../paths'
|
||||||
|
|
||||||
|
const Container = motion.custom(Flex)
|
||||||
|
|
||||||
|
const containerVariants = {
|
||||||
|
open: {
|
||||||
|
opacity: 1,
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
closed: {
|
||||||
|
opacity: 0,
|
||||||
|
transition: {
|
||||||
|
when: 'afterChildren',
|
||||||
|
},
|
||||||
|
transitionEnd: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type NavProps = {
|
||||||
|
handleClick: () => void
|
||||||
|
variant?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Nav: React.FC<NavProps> = ({ handleClick, variant = undefined }) => {
|
||||||
|
const { pathname } = useRouter()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
direction="row"
|
||||||
|
wrap="wrap"
|
||||||
|
h="100%"
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
display="flex"
|
||||||
|
variants={containerVariants}
|
||||||
|
animate={variant}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
direction={['column', 'row']}
|
||||||
|
spacing={['1rem', '2.5rem']}
|
||||||
|
position={['absolute', 'static']}
|
||||||
|
align="center"
|
||||||
|
justify="center"
|
||||||
|
>
|
||||||
|
{paths.map((path) => {
|
||||||
|
const currentLocation = pathname === '/' ? '/' : pathname.substring(1)
|
||||||
|
const currentPath = path.path === '/' ? '/' : path.path.substring(1)
|
||||||
|
|
||||||
|
if (currentPath === '/') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const current =
|
||||||
|
currentLocation === currentPath ||
|
||||||
|
(currentPath !== '/' && currentLocation.includes(currentPath))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box key={path.path}>
|
||||||
|
<Link href={path.path} passHref>
|
||||||
|
<ChakraLink
|
||||||
|
onClick={handleClick}
|
||||||
|
fontWeight="bold"
|
||||||
|
color={current ? 'blue' : 'black'}
|
||||||
|
fontSize="md"
|
||||||
|
>
|
||||||
|
{path.name}
|
||||||
|
</ChakraLink>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Nav
|
@ -0,0 +1,105 @@
|
|||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import {
|
||||||
|
Image,
|
||||||
|
Link as ChakraLink,
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Spacer,
|
||||||
|
useBreakpointValue,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import Headroom from 'react-headroom'
|
||||||
|
|
||||||
|
import Nav from './Nav'
|
||||||
|
import MenuButton from './MenuButton'
|
||||||
|
|
||||||
|
export const closedNavHeight = '76px'
|
||||||
|
|
||||||
|
const headerVariants = {
|
||||||
|
open: {
|
||||||
|
height: '100vh',
|
||||||
|
transition: {
|
||||||
|
ease: 'easeInOut',
|
||||||
|
duration: 0.3,
|
||||||
|
when: 'beforeChildren',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
closed: {
|
||||||
|
height: closedNavHeight,
|
||||||
|
transition: {
|
||||||
|
when: 'afterChildren',
|
||||||
|
ease: 'easeInOut',
|
||||||
|
duration: 0.3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const MotionFlex = motion.custom(Flex)
|
||||||
|
|
||||||
|
const Header: React.FC = () => {
|
||||||
|
const [navOpen, setNavOpen] = useState(false)
|
||||||
|
const isMobile = useBreakpointValue<boolean>({
|
||||||
|
base: true,
|
||||||
|
sm: true,
|
||||||
|
md: true,
|
||||||
|
lg: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.style.setProperty('--nav-height', closedNavHeight)
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggleNav = () => {
|
||||||
|
setNavOpen(!navOpen)
|
||||||
|
|
||||||
|
const body = document.getElementsByTagName('body')[0]
|
||||||
|
body.classList.toggle('lock-scroll')
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeNav = () => {
|
||||||
|
setNavOpen(false)
|
||||||
|
|
||||||
|
const body = document.getElementsByTagName('body')[0]
|
||||||
|
body.classList.remove('lock-scroll')
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isMobile && navOpen) {
|
||||||
|
closeNav()
|
||||||
|
}
|
||||||
|
}, [isMobile, navOpen])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Headroom style={{ zIndex: 2 }}>
|
||||||
|
<MotionFlex
|
||||||
|
layout="position"
|
||||||
|
w="100%"
|
||||||
|
direction="column"
|
||||||
|
position="absolute"
|
||||||
|
variants={headerVariants}
|
||||||
|
initial="closed"
|
||||||
|
animate={navOpen ? 'open' : 'closed'}
|
||||||
|
bg="white"
|
||||||
|
>
|
||||||
|
<Flex h={closedNavHeight} align="center" px={[null, null, '2rem']}>
|
||||||
|
<Link href="/" passHref>
|
||||||
|
<ChakraLink onClick={() => closeNav()}>
|
||||||
|
<Image src="/images/logo.svg" alt="Logo del estudio" p="1rem" h="100%" />
|
||||||
|
</ChakraLink>
|
||||||
|
</Link>
|
||||||
|
<Spacer />
|
||||||
|
{isMobile ? (
|
||||||
|
<MenuButton isOpen={navOpen} handleClick={() => toggleNav()} />
|
||||||
|
) : (
|
||||||
|
<Nav variant="open" handleClick={() => closeNav()} />
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
{isMobile ? <Nav handleClick={() => closeNav()} /> : null}
|
||||||
|
</MotionFlex>
|
||||||
|
<Box w="100%" h={closedNavHeight} />
|
||||||
|
</Headroom>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Header
|
@ -0,0 +1,73 @@
|
|||||||
|
import { Image, Box, BoxProps, SystemStyleObject } from '@chakra-ui/react'
|
||||||
|
|
||||||
|
type ResponsiveImageProps = {
|
||||||
|
url: string | null
|
||||||
|
avatar?: boolean
|
||||||
|
alt: string
|
||||||
|
imageStyle?: SystemStyleObject
|
||||||
|
filter?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// const defaultSizes = 'sizes[]=300,sizes[]=600,sizes[]=900,sizes[]=1200,sizes[]=1800'
|
||||||
|
|
||||||
|
const ResponsiveImage: React.FC<ResponsiveImageProps & BoxProps> = ({
|
||||||
|
url,
|
||||||
|
avatar,
|
||||||
|
alt,
|
||||||
|
children,
|
||||||
|
imageStyle,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
if (url === null) return null
|
||||||
|
|
||||||
|
let placeholder
|
||||||
|
let responsiveImage
|
||||||
|
let responsiveImageWebp
|
||||||
|
|
||||||
|
// The sizes are hardcoded because of a bug with webpack that impedes parametrization string interpolation
|
||||||
|
// after the question mark
|
||||||
|
// see https://github.com/cyrilwanner/next-optimized-images/issues/16
|
||||||
|
if (avatar) {
|
||||||
|
placeholder = require(`../assets/${url}?lqip`)
|
||||||
|
responsiveImage = require(`../assets/${url}?resize&sizes[]=96,sizes[]=128,sizes[]=256&format=jpg`)
|
||||||
|
responsiveImageWebp = require(`../assets/${url}?resize&sizes[]=96,sizes[]=128,sizes[]=256&format=webp`)
|
||||||
|
} else {
|
||||||
|
placeholder = require(`../assets/${url}?lqip`)
|
||||||
|
responsiveImage = require(`../assets/${url}?resize&sizes[]=300,sizes[]=600,sizes[]=900,sizes[]=1200,sizes[]=1800&format=jpg`)
|
||||||
|
responsiveImageWebp = require(`../assets/${url}?resize&sizes[]=300,sizes[]=600,sizes[]=900,sizes[]=1200,sizes[]=1800&format=webp`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
width={w ?? responsiveImage.width}
|
||||||
|
height={h ?? responsiveImage.height}
|
||||||
|
background={`url(${placeholder})`}
|
||||||
|
backgroundSize="cover"
|
||||||
|
position="relative"
|
||||||
|
overflow="hidden"
|
||||||
|
objectFit="cover"
|
||||||
|
sx={{
|
||||||
|
'& picture img': imageStyle,
|
||||||
|
}}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<picture>
|
||||||
|
<source srcSet={responsiveImageWebp.srcSet} type="image/webp" />
|
||||||
|
<Image
|
||||||
|
objectFit="cover"
|
||||||
|
w="100%"
|
||||||
|
h="100%"
|
||||||
|
alt={alt}
|
||||||
|
src={responsiveImage.src}
|
||||||
|
srcSet={responsiveImage.srcSet}
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResponsiveImage
|
@ -0,0 +1,24 @@
|
|||||||
|
import Head from 'next/head'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
|
const defaultTitle = 'Estudio'
|
||||||
|
|
||||||
|
type SEOProps = {
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const SEO: React.FC<SEOProps> = ({ title }) => {
|
||||||
|
const t = title ? `${title} | ${defaultTitle}` : defaultTitle
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Head>
|
||||||
|
<title>{t}</title>
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta property="og:title" content={t} key="ogtitle" />
|
||||||
|
<meta property="og:url" content={`${router.pathname}`} />
|
||||||
|
</Head>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SEO
|
@ -0,0 +1,232 @@
|
|||||||
|
import DirectusSDK from '@directus/sdk-js'
|
||||||
|
import { IFile } from '@directus/sdk-js/dist/types/schemes/directus/File'
|
||||||
|
import bent from 'bent'
|
||||||
|
import fs from 'fs'
|
||||||
|
import getConfig from 'next/config'
|
||||||
|
import path from 'path'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
|
||||||
|
const { serverRuntimeConfig } = getConfig()
|
||||||
|
const writeFile = promisify(fs.writeFile)
|
||||||
|
const getBuffer = bent('buffer')
|
||||||
|
|
||||||
|
const assetsDir = path.join(serverRuntimeConfig.PROJECT_ROOT, '/src/assets/')
|
||||||
|
|
||||||
|
const notasImagesDir = path.join(assetsDir, '/notas/')
|
||||||
|
const workImagesDir = path.join(assetsDir, '/work/')
|
||||||
|
const avatarImagesDir = path.join(assetsDir, '/avatar/')
|
||||||
|
|
||||||
|
const createDir = (dir: string) => {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
console.info(`Creating directory ${dir}`)
|
||||||
|
fs.mkdirSync(dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createDir(assetsDir)
|
||||||
|
createDir(notasImagesDir)
|
||||||
|
createDir(workImagesDir)
|
||||||
|
|
||||||
|
export interface IFileWithData extends IFile {
|
||||||
|
filename_disk: string
|
||||||
|
data: {
|
||||||
|
full_url: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getImage(id: number) {
|
||||||
|
const images = ((await client.getFiles()) as unknown) as { data: Array<IFileWithData> }
|
||||||
|
return images.data.find((file) => file.id === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadFile(id: number, dir: string): Promise<string | null> {
|
||||||
|
const imageData = await getImage(id)
|
||||||
|
if (!imageData) return null
|
||||||
|
const fileDir = path.join(dir, imageData.filename_disk)
|
||||||
|
|
||||||
|
if (!fs.existsSync(fileDir)) {
|
||||||
|
console.info(`Downloading image with id ${id} as ${fileDir}`)
|
||||||
|
const buffer = await getBuffer(imageData.data.full_url.replace('http', 'https'))
|
||||||
|
await writeFile(fileDir, buffer as Buffer)
|
||||||
|
console.info(`Succesfully downloaded image with ${fileDir}`)
|
||||||
|
} else {
|
||||||
|
console.info(`Image ${fileDir} exists. Skipping download`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageData.filename_disk
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function extractUrlAndDownloadImage(element, imageKey: string, dir: string) {
|
||||||
|
const imageId = element.data[imageKey]
|
||||||
|
let imagenUrl = imageId ? await downloadFile(imageId, dir) : null
|
||||||
|
const returnValue = { ...element.data, [`${imageKey}_file`]: imagenUrl }
|
||||||
|
|
||||||
|
return returnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new DirectusSDK({
|
||||||
|
mode: 'jwt',
|
||||||
|
project: '_',
|
||||||
|
url: process.env.DIRECTUS_HOST,
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function login() {
|
||||||
|
return client.login({
|
||||||
|
email: process.env.DIRECTUS_API_USER,
|
||||||
|
password: process.env.DIRECTUS_API_PASSWORD,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWork {
|
||||||
|
id: number
|
||||||
|
titulo: string
|
||||||
|
banner: number
|
||||||
|
tags: string[]
|
||||||
|
cliente: string
|
||||||
|
pais: string
|
||||||
|
year: string
|
||||||
|
texto_descripcion: string
|
||||||
|
color1: string
|
||||||
|
color2: string
|
||||||
|
imagenes: number[]
|
||||||
|
slug: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWorkWithBanner extends IWork {
|
||||||
|
banner_file: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWorkWithImages extends IWorkWithBanner {
|
||||||
|
imagenes_files: string[] | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllWorks() {
|
||||||
|
return client.getItems<IWork[]>('work')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWorkById(id: number) {
|
||||||
|
return client.getItem<IWork>('work', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAbout {
|
||||||
|
texto_about: string
|
||||||
|
texto_team: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAbout() {
|
||||||
|
return client.getItem<IAbout>('about', 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInicio {
|
||||||
|
mensaje_inspirador: string
|
||||||
|
fondo: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInicioWithImage {
|
||||||
|
fondo_file: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getInicio() {
|
||||||
|
return client.getItem<IInicio[]>('inicio', 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITeamMember {
|
||||||
|
id: number
|
||||||
|
nombre: string
|
||||||
|
cita: string
|
||||||
|
avatar: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITeamMemberWithImage {
|
||||||
|
avatar_file: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllTeam() {
|
||||||
|
return client.getItems<ITeamMember[]>('el_team')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTeamById(id: number) {
|
||||||
|
return client.getItem<ITeamMember>('team', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INota {
|
||||||
|
id: number
|
||||||
|
titulo: string
|
||||||
|
tiempo_lectura: number
|
||||||
|
cuerpo: string
|
||||||
|
tags: string[]
|
||||||
|
slug: string
|
||||||
|
imagen: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INotaWithImage extends INota {
|
||||||
|
imagen_file: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllNotas() {
|
||||||
|
return client.getItems<INota[]>('notas')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getNotaById(id: number) {
|
||||||
|
return client.getItem<INota>('notas', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllNotasWithImages(): Promise<INotaWithImage[]> {
|
||||||
|
const notas = await getAllNotas()
|
||||||
|
|
||||||
|
const notasWithImages: INotaWithImage[] = await Promise.all(
|
||||||
|
notas.data.map(async (nota) =>
|
||||||
|
extractUrlAndDownloadImage(nota, 'imagen', notasImagesDir),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return notasWithImages
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getNotaWithImage(id: number): Promise<INotaWithImage> {
|
||||||
|
const nota = await getNotaById(id)
|
||||||
|
const notaWithImage = await extractUrlAndDownloadImage(nota, 'imagen', notasImagesDir)
|
||||||
|
return notaWithImage
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllWorksWithBanners(): Promise<IWorkWithBanner[]> {
|
||||||
|
const works = await getAllWorks()
|
||||||
|
|
||||||
|
const worksWithBanners: IWorkWithBanner[] = await Promise.all(
|
||||||
|
works.data.map(async (work) =>
|
||||||
|
extractUrlAndDownloadImage(work, 'banner', workImagesDir),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return worksWithBanners
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWorkWithImages(id: number): Promise<IWorkWithImages> {
|
||||||
|
const work = await getWorkById(id)
|
||||||
|
const workWithBanner = extractUrlAndDownloadImage(work, 'banner', workImagesDir)
|
||||||
|
const imageNames: string[] = await Promise.all(
|
||||||
|
work.data.imagenes.map(async (image) => downloadFile(image, workImagesDir)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return { ...workWithBanner, imagenes_files: imageNames }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTeamMembersWithImages(): Promise<ITeamMemberWithImage[]> {
|
||||||
|
const team = await getAllTeam()
|
||||||
|
|
||||||
|
const teamWithAvatars: ITeamMemberWithImage[] = await Promise.all(
|
||||||
|
team.data.map(async (member) =>
|
||||||
|
extractUrlAndDownloadImage(member, 'avatar', avatarImagesDir),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return teamWithAvatars
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getInicioWithImage(): Promise<IInicioWithImage> {
|
||||||
|
const inicio = await getInicio()
|
||||||
|
const inicioWithImage = await extractUrlAndDownloadImage(inicio, 'fondo', assetsDir)
|
||||||
|
return inicioWithImage
|
||||||
|
}
|
||||||
|
|
||||||
|
export default client
|
@ -0,0 +1,59 @@
|
|||||||
|
import { Flex, ChakraProvider } from '@chakra-ui/react'
|
||||||
|
import { AppProps } from 'next/app'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import NProgress from 'nprogress'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import Head from 'next/head'
|
||||||
|
|
||||||
|
import NavBar from '../components/NavBar'
|
||||||
|
|
||||||
|
import 'focus-visible/dist/focus-visible'
|
||||||
|
import 'typeface-ibm-plex-sans'
|
||||||
|
|
||||||
|
import theme from '../theme'
|
||||||
|
|
||||||
|
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const setDocHeight = () => {
|
||||||
|
document.documentElement.style.setProperty('--vh', `${window.innerHeight / 100}px`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const load = () => {
|
||||||
|
NProgress.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
const stop = () => {
|
||||||
|
NProgress.done()
|
||||||
|
}
|
||||||
|
|
||||||
|
router.events.on('routeChangeStart', load)
|
||||||
|
router.events.on('routeChangeComplete', stop)
|
||||||
|
router.events.on('routeChangeError', stop)
|
||||||
|
|
||||||
|
setDocHeight()
|
||||||
|
window.addEventListener('resize', function () {
|
||||||
|
setDocHeight()
|
||||||
|
})
|
||||||
|
window.addEventListener('orientationchange', function () {
|
||||||
|
setDocHeight()
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
</Head>
|
||||||
|
<ChakraProvider theme={theme} resetCSS>
|
||||||
|
<Flex direction="column" minH="calc(var(--vh, 1vh) * 100)" w="100%">
|
||||||
|
<NavBar />
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</Flex>
|
||||||
|
</ChakraProvider>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
@ -0,0 +1,57 @@
|
|||||||
|
import getConfig from 'next/config'
|
||||||
|
import Document, { Head, Html, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
|
const { publicRuntimeConfig } = getConfig()
|
||||||
|
|
||||||
|
const description =
|
||||||
|
'Sitio web para Seminario de Gestion de Contenidos para la web llevado a cabo por Lautaro Valdez e Ian Mancini'
|
||||||
|
|
||||||
|
class MyDocument extends Document {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Html lang="es">
|
||||||
|
<Head>
|
||||||
|
<meta charSet="UTF-8" />
|
||||||
|
<meta name="description" content={description} />
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="180x180"
|
||||||
|
href={`${publicRuntimeConfig.basePath}/apple-touch-icon.png`}
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href={`${publicRuntimeConfig.basePath}/favicon-32x32.png`}
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href={`${publicRuntimeConfig.basePath}/favicon-16x16.png`}
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="manifest"
|
||||||
|
href={`${publicRuntimeConfig.basePath}/site.webmanifest`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<meta property="og:description" content={description} key="ogdesc" />
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content={`${publicRuntimeConfig.basePath}/android-chrome-512x512.png`}
|
||||||
|
key="ogimage"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<link rel="icon" href={`${publicRuntimeConfig.basePath}/favicon.ico`} />
|
||||||
|
</Head>
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyDocument
|
@ -0,0 +1,48 @@
|
|||||||
|
import { Text, Box } from '@chakra-ui/react'
|
||||||
|
|
||||||
|
import ResponsiveImage from '../components/ResponsiveImage'
|
||||||
|
import SEO from '../components/SEO'
|
||||||
|
|
||||||
|
import { getInicioWithImage, login } from '../lib/api'
|
||||||
|
|
||||||
|
const Home: React.FC = ({ fondo_file, mensaje_inspirador }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SEO />
|
||||||
|
<Box direction="column" w="100%" position="relative" flex="1 0 0">
|
||||||
|
<ResponsiveImage
|
||||||
|
url={fondo_file}
|
||||||
|
alt="Fondo de página de inicio"
|
||||||
|
zIndex={0}
|
||||||
|
w="100%"
|
||||||
|
h="calc(var(--vh, 1vh) * 100 - var(--nav-height, 72px))"
|
||||||
|
overflow="hidden"
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
top="50%"
|
||||||
|
transform="translateY(-50%)"
|
||||||
|
fontSize={['3xl', '3xl', '7xl']}
|
||||||
|
fontWeight="700"
|
||||||
|
lineHeight="1.2"
|
||||||
|
pr="2rem"
|
||||||
|
zIndex={1}
|
||||||
|
position="absolute"
|
||||||
|
textAlign="right"
|
||||||
|
style={{ wordSpacing: '9999px' }}
|
||||||
|
>
|
||||||
|
{mensaje_inspirador}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home
|
||||||
|
|
||||||
|
export async function getStaticProps() {
|
||||||
|
await login()
|
||||||
|
|
||||||
|
const data = await getInicioWithImage()
|
||||||
|
|
||||||
|
return { props: data }
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
type Path = {
|
||||||
|
name: string
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const paths: Array<Path> = [
|
||||||
|
{
|
||||||
|
name: 'Inicio',
|
||||||
|
path: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Work',
|
||||||
|
path: '/work',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'About',
|
||||||
|
path: '/about',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'El team',
|
||||||
|
path: '/team',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Notas',
|
||||||
|
path: '/notas',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Contact',
|
||||||
|
path: '/contact',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default paths
|
@ -0,0 +1,78 @@
|
|||||||
|
import { extendTheme } from '@chakra-ui/react'
|
||||||
|
|
||||||
|
export const blue = '#3452FF'
|
||||||
|
|
||||||
|
const customTheme = extendTheme({
|
||||||
|
colors: {
|
||||||
|
blue,
|
||||||
|
},
|
||||||
|
fonts: {
|
||||||
|
body: 'IBM Plex Sans, sans-serif',
|
||||||
|
heading: 'IBM Plex Sans, sans-serif',
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
global: {
|
||||||
|
'.headroom': {
|
||||||
|
zIndex: '99 !important',
|
||||||
|
},
|
||||||
|
'#nprogress': {
|
||||||
|
pointerEvents: 'none',
|
||||||
|
},
|
||||||
|
'.hide-scrollbar, #__next': {
|
||||||
|
'::webkit-scrollbar': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
scrollbarWidth: 'none !important',
|
||||||
|
},
|
||||||
|
'#nprogress .bar': {
|
||||||
|
background: 'blue',
|
||||||
|
position: 'fixed',
|
||||||
|
zIndex: 1031,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '3px',
|
||||||
|
},
|
||||||
|
a: {
|
||||||
|
textDecoration: 'none !important',
|
||||||
|
},
|
||||||
|
'html, body': {
|
||||||
|
fontFamily: 'IBM Plex Sans, sans-serif',
|
||||||
|
bg: 'white',
|
||||||
|
color: 'black',
|
||||||
|
fontSize: 'xl',
|
||||||
|
lineHeight: 'tall',
|
||||||
|
width: '100%',
|
||||||
|
minHeight: '100vh',
|
||||||
|
},
|
||||||
|
'.enable-scroll': {
|
||||||
|
overflow: 'hidden auto',
|
||||||
|
},
|
||||||
|
'.lock-scroll': {
|
||||||
|
overflow: 'hidden !important',
|
||||||
|
},
|
||||||
|
// '*': {
|
||||||
|
// scrollbarWidth: 'auto',
|
||||||
|
// scrollbarColor: `${green} black`,
|
||||||
|
// },
|
||||||
|
|
||||||
|
// '*::-webkit-scrollbar': {
|
||||||
|
// width: '12px',
|
||||||
|
// height: '12px',
|
||||||
|
// },
|
||||||
|
// '*::-webkit-scrollbar-track': {
|
||||||
|
// background: `#000000`,
|
||||||
|
// },
|
||||||
|
// '*::-webkit-scrollbar-thumb': {
|
||||||
|
// backgroundColor: `${green}`,
|
||||||
|
// borderRadius: '12px',
|
||||||
|
// border: '2px solid black',
|
||||||
|
// },
|
||||||
|
// '*::-webkit-scrollbar-corner': {
|
||||||
|
// backgroundColor: `#000000`,
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default customTheme
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": false,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve"
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|