diff --git a/.env.example b/.env.example index 32612d6..898b932 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,6 @@ # App variables SITE_NAME="EntGamers" -DISCORD_JOIN_WEBHOOK_URL="https://discord.com/api/webhooks/XXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # Deployment variables @@ -14,4 +13,15 @@ DEPLOY_PATH="" # Github actions variables SSH_PRIVATE_KEY="" -SSH_KNOWN_HOSTS="" \ No newline at end of file +SSH_KNOWN_HOSTS="" + +# Appwrite required variables + +NEXT_PUBLIC_APPWRITE_ENDPOINT="" +NEXT_PUBLIC_APPWRITE_PROJECT_ID="" +APPWRITE_API_KEY="" + +# Website Variables + +NEXT_PUBLIC_SITE_URL="https://entgamers.com" +IMAGE_DOMAINS="https://domain.com,http://another.domain.com/route/" \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..b13ab05 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +src/styled-system \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 4b11d9a..fb761d9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,28 +1,33 @@ { "env": { "browser": true, - "es2021": true + "es2021": true, + "node": true }, "extends": [ - "plugin:react/recommended", - "standard", - "plugin:@next/next/recommended" + "next/core-web-vitals", + "standard-with-typescript", + "plugin:react/recommended" ], - "parser": "@typescript-eslint/parser", "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, "ecmaVersion": "latest", "sourceType": "module" }, "plugins": [ - "react", - "@emotion", - "@typescript-eslint" + "react" ], "rules": { - "indent": [ + "react/react-in-jsx-scope": "off", + "react/jsx-uses-react": "off", + "indent": "off", + "@typescript-eslint/indent": [ + "error", + 2, + { + "SwitchCase": 1 + } + ], + "react/jsx-indent": [ "error", 2 ], @@ -30,23 +35,21 @@ "@typescript-eslint/no-use-before-define": [ "error" ], - "react/react-in-jsx-scope": "off", - "react/jsx-filename-extension": [ - "warn", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", { - "extensions": [ - ".tsx" - ] + "argsIgnorePattern": "^_" } ], - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": "error", - "@emotion/pkg-renaming": "error", - "react/no-unknown-property": ["error", { "ignore": ["css"] }] - }, - "settings": { - "react": { - "version": "detect" - } + "react-hooks/exhaustive-deps": "off", + "react/no-unknown-property": [ + "error", + { + "ignore": [ + "css" + ] + } + ] } } \ No newline at end of file diff --git a/.gitignore b/.gitignore index 740722a..ae2b362 100644 --- a/.gitignore +++ b/.gitignore @@ -189,4 +189,10 @@ dist # and uncomment the following lines # .pnp.* -# End of https://www.toptal.com/developers/gitignore/api/node,yarn,nextjs \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/node,yarn,nextjs + +# Panda Css +src/styled-system + +# Vscode +.vscode diff --git a/.postcssrc.json b/.postcssrc.json new file mode 100644 index 0000000..fddfc00 --- /dev/null +++ b/.postcssrc.json @@ -0,0 +1,5 @@ +{ + "plugins": { + "@pandacss/dev/postcss": {} + } +} \ No newline at end of file diff --git a/.yarnrc.yml b/.yarnrc.yml deleted file mode 100644 index 3186f3f..0000000 --- a/.yarnrc.yml +++ /dev/null @@ -1 +0,0 @@ -nodeLinker: node-modules diff --git a/bun.lockb b/bun.lockb index 74b899b..9406c5d 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/next.config.js b/next.config.js index 9cd069e..f7de370 100644 --- a/next.config.js +++ b/next.config.js @@ -1,5 +1,15 @@ -/** @type {import('next').NextConfig} */ +const imageDomains = (process.env.IMAGE_DOMAINS ?? '').split(',').map(domain => { + const getDataRegex = /(?[\w]+)?:\/\/(?[\w.-]+)?((?<=[\d]{0,4}):(?[\d]{0,4}))?\/?(?.*)?$/ + const groups = getDataRegex.exec(domain).groups ?? {} + return groups +}) -module.exports = { - reactStrictMode: true +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + images: { + remotePatterns: imageDomains + } } + +module.exports = nextConfig diff --git a/package.json b/package.json index 9f2f1d8..a7958a7 100644 --- a/package.json +++ b/package.json @@ -1,52 +1,60 @@ { - "name": "next-template", + "name": "entgamers_pro", "version": "0.1.0", "private": true, "scripts": { "develop": "next dev", "build": "next build", - "prestart": "yarn install && next build", + "prestart": "bun install && next build", "start": "next start", "lint": "next lint", - "prepare": "husky install" + "prepare": "panda codegen && husky install" }, "dependencies": { - "@emotion/react": "^11.11.1", - "@emotion/styled": "^11.11.0", - "@fortawesome/fontawesome-svg-core": "^6.4.2", - "@fortawesome/free-brands-svg-icons": "^6.4.2", - "@fortawesome/free-solid-svg-icons": "^6.4.2", + "@fontsource/open-sans": "^5.0.20", + "@fontsource/permanent-marker": "^5.0.8", + "@fortawesome/fontawesome-svg-core": "^6.5.1", + "@fortawesome/free-brands-svg-icons": "^6.5.1", + "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-fontawesome": "^0.2.0", - "@mui/material": "^5.14.9", - "formik": "^2.4.4", - "gsap": "^3.12.2", + "@reduxjs/toolkit": "^2.0.1", + "@tanstack/match-sorter-utils": "^8.11.8", + "@tanstack/react-table": "^8.19.3", + "appwrite": "^13.0.1", + "date-fns": "^3.3.1", + "entgamers-database": "0.0.26", + "entgamers-panda-preset": "0.1.5", + "formik": "^2.4.5", + "framer-motion": "^10.17.6", "isomorphic-fetch": "^3.0.0", - "next": "13.4.19", - "next-connect": "^1.0.0", + "next": "^14.0.4", + "node-appwrite": "^11.1.0", "react": "18.2.0", "react-dom": "18.2.0", - "sharp": "^0.32.5", - "swiper": "^10.2.0", - "yup": "^1.2.0" + "react-redux": "^9.0.4", + "sharp": "^0.33.1", + "yup": "^1.3.3" }, "devDependencies": { - "@commitlint/cli": "^17.7.1", - "@commitlint/config-conventional": "^17.7.0", - "@emotion/eslint-plugin": "^11.11.0", - "@types/isomorphic-fetch": "^0.0.36", - "@types/node": "20.6.0", - "@types/react": "18.2.21", - "@typescript-eslint/eslint-plugin": "^5.38.0", + "@commitlint/cli": "^18.4.4", + "@commitlint/config-conventional": "^18.4.4", + "@pandacss/dev": "^0.23.0", + "@types/isomorphic-fetch": "^0.0.39", + "@types/node": "^20.10.6", + "@types/react": "^18.2.46", + "@types/react-dom": "^18.2.18", + "@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/parser": "^5.38.0", - "eslint": "^8.23.1", + "eslint": "^8.0.1", "eslint-config-next": "latest", "eslint-config-standard": "^17.0.0", + "eslint-config-standard-with-typescript": "latest", "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.2.5", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^6.0.1", - "eslint-plugin-react": "^7.31.8", + "eslint-plugin-promise": "^6.0.0", + "eslint-plugin-react": "latest", "husky": "^8.0.3", - "typescript": "5.2.2" + "typescript": "*" } -} +} \ No newline at end of file diff --git a/panda.config.ts b/panda.config.ts new file mode 100644 index 0000000..b2f4a59 --- /dev/null +++ b/panda.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from '@pandacss/dev' + +export default defineConfig({ + presets: ['entgamers-panda-preset'], + gitignore: true, + preflight: true, + include: ['./src/**/*.{js,jsx,ts,tsx}'], + exclude: [], + outdir: 'src/styled-system', + jsxFactory: 'panda', + jsxFramework: 'react' +}) diff --git a/public/fonts/open-sans-v29-latin-300.woff b/public/fonts/open-sans-v29-latin-300.woff deleted file mode 100644 index fd25416..0000000 Binary files a/public/fonts/open-sans-v29-latin-300.woff and /dev/null differ diff --git a/public/fonts/open-sans-v29-latin-300.woff2 b/public/fonts/open-sans-v29-latin-300.woff2 deleted file mode 100644 index e7341dc..0000000 Binary files a/public/fonts/open-sans-v29-latin-300.woff2 and /dev/null differ diff --git a/public/fonts/open-sans-v29-latin-500.woff b/public/fonts/open-sans-v29-latin-500.woff deleted file mode 100644 index ce838eb..0000000 Binary files a/public/fonts/open-sans-v29-latin-500.woff and /dev/null differ diff --git a/public/fonts/open-sans-v29-latin-500.woff2 b/public/fonts/open-sans-v29-latin-500.woff2 deleted file mode 100644 index b9bc72c..0000000 Binary files a/public/fonts/open-sans-v29-latin-500.woff2 and /dev/null differ diff --git a/public/fonts/open-sans-v29-latin-700.woff b/public/fonts/open-sans-v29-latin-700.woff deleted file mode 100644 index 1b546f6..0000000 Binary files a/public/fonts/open-sans-v29-latin-700.woff and /dev/null differ diff --git a/public/fonts/open-sans-v29-latin-700.woff2 b/public/fonts/open-sans-v29-latin-700.woff2 deleted file mode 100644 index 00582c6..0000000 Binary files a/public/fonts/open-sans-v29-latin-700.woff2 and /dev/null differ diff --git a/public/fonts/open-sans-v29-latin-regular.woff b/public/fonts/open-sans-v29-latin-regular.woff deleted file mode 100644 index b9f6e3e..0000000 Binary files a/public/fonts/open-sans-v29-latin-regular.woff and /dev/null differ diff --git a/public/fonts/open-sans-v29-latin-regular.woff2 b/public/fonts/open-sans-v29-latin-regular.woff2 deleted file mode 100644 index f143cd4..0000000 Binary files a/public/fonts/open-sans-v29-latin-regular.woff2 and /dev/null differ diff --git a/public/fonts/permanent-marker-v16-latin-regular.woff b/public/fonts/permanent-marker-v16-latin-regular.woff deleted file mode 100644 index 458b4bb..0000000 Binary files a/public/fonts/permanent-marker-v16-latin-regular.woff and /dev/null differ diff --git a/public/fonts/permanent-marker-v16-latin-regular.woff2 b/public/fonts/permanent-marker-v16-latin-regular.woff2 deleted file mode 100644 index 203ccae..0000000 Binary files a/public/fonts/permanent-marker-v16-latin-regular.woff2 and /dev/null differ diff --git a/src/assets/images/Clanes.png b/public/images/Clanes.png similarity index 100% rename from src/assets/images/Clanes.png rename to public/images/Clanes.png diff --git a/src/assets/images/EntGamers.png b/public/images/EntGamers.png similarity index 100% rename from src/assets/images/EntGamers.png rename to public/images/EntGamers.png diff --git a/src/assets/images/EntGamersOutlineBlanco.png b/public/images/EntGamersOutlineBlanco.png similarity index 100% rename from src/assets/images/EntGamersOutlineBlanco.png rename to public/images/EntGamersOutlineBlanco.png diff --git a/src/assets/images/EntGamersOutlineNegro.png b/public/images/EntGamersOutlineNegro.png similarity index 100% rename from src/assets/images/EntGamersOutlineNegro.png rename to public/images/EntGamersOutlineNegro.png diff --git a/src/assets/images/EntGamersTextNoOutline.png b/public/images/EntGamersTextNoOutline.png similarity index 100% rename from src/assets/images/EntGamersTextNoOutline.png rename to public/images/EntGamersTextNoOutline.png diff --git a/src/assets/images/EntGamersTextOutlineBlanco.png b/public/images/EntGamersTextOutlineBlanco.png similarity index 100% rename from src/assets/images/EntGamersTextOutlineBlanco.png rename to public/images/EntGamersTextOutlineBlanco.png diff --git a/src/assets/images/EntGamersTextOutlineNegro.png b/public/images/EntGamersTextOutlineNegro.png similarity index 100% rename from src/assets/images/EntGamersTextOutlineNegro.png rename to public/images/EntGamersTextOutlineNegro.png diff --git a/public/images/defaults/og.jpg b/public/images/defaults/og.jpg deleted file mode 100644 index 7766d11..0000000 Binary files a/public/images/defaults/og.jpg and /dev/null differ diff --git a/src/assets/images/gaming/Badge.png b/public/images/gaming/Badge.png similarity index 100% rename from src/assets/images/gaming/Badge.png rename to public/images/gaming/Badge.png diff --git a/src/assets/images/gaming/BadgeBook.png b/public/images/gaming/BadgeBook.png similarity index 100% rename from src/assets/images/gaming/BadgeBook.png rename to public/images/gaming/BadgeBook.png diff --git a/src/assets/images/gaming/BadgeShield.png b/public/images/gaming/BadgeShield.png similarity index 100% rename from src/assets/images/gaming/BadgeShield.png rename to public/images/gaming/BadgeShield.png diff --git a/src/assets/images/gaming/BadgeSword.png b/public/images/gaming/BadgeSword.png similarity index 100% rename from src/assets/images/gaming/BadgeSword.png rename to public/images/gaming/BadgeSword.png diff --git a/src/assets/images/gaming/Button.png b/public/images/gaming/Button.png similarity index 100% rename from src/assets/images/gaming/Button.png rename to public/images/gaming/Button.png diff --git a/src/assets/images/gaming/ButtonA.png b/public/images/gaming/ButtonA.png similarity index 100% rename from src/assets/images/gaming/ButtonA.png rename to public/images/gaming/ButtonA.png diff --git a/src/assets/images/gaming/ButtonAPressed.png b/public/images/gaming/ButtonAPressed.png similarity index 100% rename from src/assets/images/gaming/ButtonAPressed.png rename to public/images/gaming/ButtonAPressed.png diff --git a/src/assets/images/gaming/ButtonB.png b/public/images/gaming/ButtonB.png similarity index 100% rename from src/assets/images/gaming/ButtonB.png rename to public/images/gaming/ButtonB.png diff --git a/src/assets/images/gaming/ButtonBPressed.png b/public/images/gaming/ButtonBPressed.png similarity index 100% rename from src/assets/images/gaming/ButtonBPressed.png rename to public/images/gaming/ButtonBPressed.png diff --git a/src/assets/images/gaming/ButtonPressed.png b/public/images/gaming/ButtonPressed.png similarity index 100% rename from src/assets/images/gaming/ButtonPressed.png rename to public/images/gaming/ButtonPressed.png diff --git a/src/assets/images/gaming/ButtonX.png b/public/images/gaming/ButtonX.png similarity index 100% rename from src/assets/images/gaming/ButtonX.png rename to public/images/gaming/ButtonX.png diff --git a/src/assets/images/gaming/ButtonXPressed.png b/public/images/gaming/ButtonXPressed.png similarity index 100% rename from src/assets/images/gaming/ButtonXPressed.png rename to public/images/gaming/ButtonXPressed.png diff --git a/src/assets/images/gaming/ButtonY.png b/public/images/gaming/ButtonY.png similarity index 100% rename from src/assets/images/gaming/ButtonY.png rename to public/images/gaming/ButtonY.png diff --git a/src/assets/images/gaming/ButtonYPressed.png b/public/images/gaming/ButtonYPressed.png similarity index 100% rename from src/assets/images/gaming/ButtonYPressed.png rename to public/images/gaming/ButtonYPressed.png diff --git a/src/assets/images/gaming/LevelUpAmarillo.png b/public/images/gaming/LevelUpAmarillo.png similarity index 100% rename from src/assets/images/gaming/LevelUpAmarillo.png rename to public/images/gaming/LevelUpAmarillo.png diff --git a/src/assets/images/gaming/LevelUpAzul.png b/public/images/gaming/LevelUpAzul.png similarity index 100% rename from src/assets/images/gaming/LevelUpAzul.png rename to public/images/gaming/LevelUpAzul.png diff --git a/src/assets/images/gaming/LevelUpRojo.png b/public/images/gaming/LevelUpRojo.png similarity index 100% rename from src/assets/images/gaming/LevelUpRojo.png rename to public/images/gaming/LevelUpRojo.png diff --git a/src/assets/images/gaming/LevelUpVerde.png b/public/images/gaming/LevelUpVerde.png similarity index 100% rename from src/assets/images/gaming/LevelUpVerde.png rename to public/images/gaming/LevelUpVerde.png diff --git a/src/assets/images/gaming/Talking.gif b/public/images/gaming/Talking.gif similarity index 100% rename from src/assets/images/gaming/Talking.gif rename to public/images/gaming/Talking.gif diff --git a/src/adapters/.gitkeep b/src/adapters/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/Clanes.tsx b/src/app/Clanes.tsx new file mode 100644 index 0000000..e718ca0 --- /dev/null +++ b/src/app/Clanes.tsx @@ -0,0 +1,68 @@ +import Typography from '@/components/ui/Typography' +import { css, cx } from '@/styled-system/css' +import { Center } from '@/styled-system/jsx' +import { center, container } from '@/styled-system/patterns' +import { button, card } from '@/styled-system/recipes' +import NextImage from 'next/image' +import NextLink from 'next/link' +import { type FC } from 'react' + +const Clanes: FC = () => { + return ( +
+
+
+ Clanes +
+
+ Los clanes son espacios donde compartir nuestros gustos con otros usuarios, dándonos la oportunidad de organizar proyectos y eventos en los cuales formar parte. +
+ + Ver Clanes + + +
+
+
+ +
+
+
+
+
+ ) +} +export default Clanes diff --git a/src/app/FeedbackConsumer.tsx b/src/app/FeedbackConsumer.tsx new file mode 100644 index 0000000..f1b964b --- /dev/null +++ b/src/app/FeedbackConsumer.tsx @@ -0,0 +1,78 @@ +'use client' +import IconButton from '@/components/ui/IconButton' +import Typography from '@/components/ui/Typography' +import { useAppDispatch } from '@/hooks/useAppDispatch' +import { useAppSelector } from '@/hooks/useAppSelector' +import { removeAlert } from '@/state/feedbackSlice' +import { css } from '@/styled-system/css' +import { alert } from '@/styled-system/recipes/alert' +import { faTimes } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { AnimatePresence, motion } from 'framer-motion' +import { type FC } from 'react' +import { createPortal } from 'react-dom' + +const FeedbackConsumer: FC = () => { + const { alerts } = useAppSelector(state => state.feedback) + const dispatch = useAppDispatch() + return ( + <> + {alerts.length > 0 && createPortal( + ( + + + + {alerts.map((currentAlert) => ( + + dispatch(removeAlert(currentAlert.id))} + > + + + + {currentAlert.title} + + + {currentAlert.message} + + + ))} + + + + ), + document.body, + 'alerts' + )} + + ) +} +export default FeedbackConsumer diff --git a/src/app/Hero.tsx b/src/app/Hero.tsx new file mode 100644 index 0000000..43c1bc1 --- /dev/null +++ b/src/app/Hero.tsx @@ -0,0 +1,132 @@ +import Typography from '@/components/ui/Typography' +import { css, cx } from '@/styled-system/css' +import { Center, Container } from '@/styled-system/jsx' +import { iconButton } from '@/styled-system/recipes' +import { faArrowDown } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import NextImage from 'next/image' +import { type FC } from 'react' + +const layerCss = css({ + backgroundPositionY: 'bottom', + backgroundPositionX: 'x-start', + backgroundRepeat: 'repeat', + backgroundSize: 'initial', + height: '100vh', + width: '100%', + willChange: 'background-position-y', + animationName: 'bgMotion', + animationTimingFunction: 'linear', + animationIterationCount: 'infinite' +}) + +const Hero: FC = () => { + return ( +
+
+
+
+
+
+ +
+ + EntGamers + + + Comunidad de y para los gamers + +
+
+ +
+
+
+
+
+
+
+ + + +
+ ) +} +export default Hero diff --git a/src/app/SessionConsumer.tsx b/src/app/SessionConsumer.tsx new file mode 100644 index 0000000..6c60c0a --- /dev/null +++ b/src/app/SessionConsumer.tsx @@ -0,0 +1,61 @@ +'use client' +import { useAppDispatch } from '@/hooks/useAppDispatch' +import { useAppSelector } from '@/hooks/useAppSelector' +import { setClanes, setCurrentUser, setSession, setStatus } from '@/state/sessionSlice' +import { AppwriteException } from 'appwrite' +import { getClanes } from 'entgamers-database/frontend/clanes' +import { getCurrentUser, getSession } from 'entgamers-database/frontend/session' +import { useCallback, useEffect, type FC } from 'react' + +const SessionConsumer: FC = () => { + const { status, session, user, clanes } = useAppSelector((state) => state.session) + const dispatch = useAppDispatch() + + const ensureSession = useCallback(async () => { + try { + if (status !== 'initializing' || session !== undefined) return + dispatch(setStatus('loading')) + const currentSession = await getSession('current') + const currentUser = await getCurrentUser() + dispatch(setSession(currentSession)) + dispatch(setCurrentUser(currentUser)) + } catch (error) { + dispatch(setSession()) + dispatch(setCurrentUser()) + throw error + } finally { + dispatch(setStatus('idle')) + } + }, []) + + useEffect(() => { + ensureSession() + .catch((error) => { + if (error instanceof AppwriteException) { + console.error(error) + } + }) + }, []) + + useEffect(() => { + if (user !== undefined && clanes === undefined) { + getClanes() + .then((clanes) => { + dispatch(setClanes(clanes)) + }) + .catch((error) => { + if (error instanceof AppwriteException) { + console.error(error) + } + }) + } else if (user === undefined && clanes !== undefined) { + dispatch(setClanes()) + } + }, [user]) + + return ( + <> + + ) +} +export default SessionConsumer diff --git a/src/app/Social.tsx b/src/app/Social.tsx new file mode 100644 index 0000000..35f2364 --- /dev/null +++ b/src/app/Social.tsx @@ -0,0 +1,75 @@ +import Typography from '@/components/ui/Typography' +import { css, cx } from '@/styled-system/css' +import { Center } from '@/styled-system/jsx' +import { container } from '@/styled-system/patterns' +import { button, card } from '@/styled-system/recipes' +import { type FC } from 'react' + +const layerCss = css({ + backgroundPositionY: 'bottom', + backgroundPositionX: 'x-start', + backgroundRepeat: 'repeat', + backgroundSize: 'initial', + minHeight: '75vh', + width: '100%', + willChange: 'background-position-y', + animationName: 'bgMotion', + animationTimingFunction: 'linear', + animationIterationCount: 'infinite' +}) + +const Social: FC = () => { + return ( +
+
+
+ +
+
+
+ Redes Sociales + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptate deleniti dolore quas sed nemo sit, officia in rem nesciunt quisquam possimus ab! Labore sed reprehenderit quae, hic earum tempora placeat cumque id eos itaque perferendis nulla officia fuga porro, quis, unde facere accusamus repudiandae non? + +
+ + Nuestros Links + +
+
+
+
+
+
+
+ ) +} +export default Social diff --git a/src/app/StateProvider.tsx b/src/app/StateProvider.tsx new file mode 100644 index 0000000..61ef270 --- /dev/null +++ b/src/app/StateProvider.tsx @@ -0,0 +1,19 @@ +'use client' +import store from '@/state/store' +import { type FC, type ReactNode } from 'react' +import { Provider } from 'react-redux' + +export interface StateProviderProps { + children: ReactNode +} + +const StateProvider: FC = ({ children }) => { + return ( + + {children} + + ) +} +export default StateProvider diff --git a/src/app/Team.tsx b/src/app/Team.tsx new file mode 100644 index 0000000..6c92e26 --- /dev/null +++ b/src/app/Team.tsx @@ -0,0 +1,123 @@ +import { css, cx } from '@/styled-system/css' +import { Container } from '@/styled-system/jsx' +import { center } from '@/styled-system/patterns' +import { button, card, iconButton } from '@/styled-system/recipes' +import { type TeamMember } from '@/types/User' +import { faFacebook, faInstagram, faTwitch, faTwitter, faYoutube } from '@fortawesome/free-brands-svg-icons' +import { faGlobe } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import NextImage from 'next/image' +import NextLink from 'next/link' +import { type FC } from 'react' + +const team: TeamMember[] = [ + { + image: '/images/team/SrJuggernaut.png', + name: 'SrJuggernaut', + role: 'administrator', + description: 'Soy desarrollador web y me gusta jugar videojuegos.', + socialNetworks: [ + { url: 'https://www.facebook.com/SrJuggernaut', label: 'SrJuggernaut Facebook', icon: faFacebook }, + { url: 'https://twitter.com/SrJuggernaut', label: 'SrJuggernaut Twitter', icon: faTwitter }, + { url: 'https://youtube.com/juggernautplays', label: 'SrJuggernaut YouTube', icon: faYoutube }, + { url: 'https://twitch.tv/juggernautplays', label: 'SrJuggernaut Twitch', icon: faTwitch }, + { url: 'https://www.instagram.com/sr_juggernaut', label: 'SrJuggernaut Instagram', icon: faInstagram }, + { url: 'https://srjuggernaut.dev/', label: 'SrJuggernaut Website', icon: faGlobe } + ] + } +] + +const Team: FC = () => { + return ( +
+ + +
+ {team.map((member, index) => ( +
+
+ +
+
+

{member.name}

+

{member.description}

+
+ {member.socialNetworks.map((socialNetwork, index) => ( + + + + ))} +
+
+
+ ))} + +
+
+ + Ver el equipo completo + + + Únete al equipo + +
+
+
+ ) +} +export default Team diff --git a/src/app/clanes/page.tsx b/src/app/clanes/page.tsx new file mode 100644 index 0000000..ac7e6c2 --- /dev/null +++ b/src/app/clanes/page.tsx @@ -0,0 +1,59 @@ +import Typography from '@/components/ui/Typography' +import { css } from '@/styled-system/css' +import { Container } from '@/styled-system/jsx' +import { faChevronRight } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { type FC } from 'react' + +const ClanesPage: FC = () => { + return ( + + Clanes + Los clanes son espacios donde compartir nuestros gustos con otros usuarios, dándonos la oportunidad de organizar proyectos y eventos en los cuales formar parte. +
+
+ Beneficios de los clanes + La intención de EntGamers es brindar beneficios a los clanes que les permitan operar en un ambiente de comunicación y colaboración. +
    +
  • Espacio en el servidor de Discord.
  • +
  • Apoyo de la administración con proyectos y eventos.
  • +
  • Apoyo del equipo de moderación.
  • +
+
+
+ Requisitos para formar un clan + Todos los clanes deben cumplir con los siguientes requisitos: +
    +
  • Tener un encargado.
  • +
  • Fomentar el compañerismo y la comunidad.
  • +
  • Aportar contenido de forma periódica para la comunidad.
  • +
  • Realizar al menos una actividad mensual con los integrantes.
  • +
+
+
+ Clanes activos +
+ Esta sección está en construcción. Puedes ver los clanes activos en nuestro Servidor de Discord. +
+
+ ) +} +export default ClanesPage diff --git a/src/app/cuenta/CuentaTabs.tsx b/src/app/cuenta/CuentaTabs.tsx new file mode 100644 index 0000000..8c2690e --- /dev/null +++ b/src/app/cuenta/CuentaTabs.tsx @@ -0,0 +1,77 @@ +'use client' +import Button from '@/components/ui/Button' +import ButtonGroup from '@/components/ui/ButtonGroup' +import useSession from '@/hooks/useSession' +import { css } from '@/styled-system/css' +import { AnimatePresence, motion } from 'framer-motion' +import { useState, type FC } from 'react' +import UpdateEmail from './UpdateEmail' +import UpdatePassword from './UpdatePassword' +import UpdateUserName from './UpdateUserName' +import UpdateUserPreferences from './UpdateUserPreferences' + +type Tab = 'perfil' | 'login' + +const CuentaTabs: FC = () => { + useSession('/login') + const [currentTab, setCurrentTab] = useState('perfil') + + return ( + <> + + + + +
+ + {currentTab === 'login' && ( + + + + + )} + {currentTab === 'perfil' && ( + + + + + )} + +
+ + ) +} + +export default CuentaTabs diff --git a/src/app/cuenta/UpdateEmail.tsx b/src/app/cuenta/UpdateEmail.tsx new file mode 100644 index 0000000..ad8f439 --- /dev/null +++ b/src/app/cuenta/UpdateEmail.tsx @@ -0,0 +1,129 @@ +'use client' +import Button from '@/components/ui/Button' +import Typography from '@/components/ui/Typography' +import FormGroup from '@/components/ui/form/FormGroup' +import Input from '@/components/ui/form/Input' +import PasswordInput from '@/components/ui/form/PasswordInput' +import { useAppDispatch } from '@/hooks/useAppDispatch' +import useSession from '@/hooks/useSession' +import { addAlert } from '@/state/feedbackSlice' +import { nanoid } from '@reduxjs/toolkit' +import { AppwriteException } from 'appwrite' +import { updateEmail } from 'entgamers-database/frontend/session' +import { useFormik } from 'formik' +import { type FC } from 'react' +import { object, string } from 'yup' + +interface UpdateEmailData { + email: string + password: string +} + +const updateEmailSchema = object({ + email: string().email('El correo electrónico no es válido').required('El correo electrónico es requerido'), + password: string().required('La contraseña es requerida') +}) + +const UpdateEmail: FC = () => { + const { status, session } = useSession('/login') + const dispatch = useAppDispatch() + + const formik = useFormik({ + initialValues: { + email: '', + password: '' + }, + onSubmit: async ({ email, password }) => { + try { + await updateEmail(email, password) + dispatch(addAlert({ + id: nanoid(), + title: 'Correo actualizado', + message: 'Ahora puedes iniciar sesión', + severity: 'success' + })) + } catch (error) { + if (error instanceof AppwriteException) { + dispatch(addAlert({ + id: nanoid(), + message: error.message, + title: 'Error mientras se actualizaba el correo', + severity: 'error' + })) + } else { + dispatch(addAlert({ + id: nanoid(), + message: 'Error desconocido', + title: 'Error mientras se actualizaba el correo', + severity: 'error' + })) + } + } + }, + validationSchema: updateEmailSchema, + validateOnMount: true + }) + + if (status !== 'idle' || session === undefined) { + // TODO: Replace with Skeleton + return null + } + + return ( + <> + Cambia tu correo +
+ + + + {formik.touched.email !== undefined && formik.errors.email !== undefined && ( + {formik.errors.email} + )} + + + + + {formik.touched.password !== undefined && formik.errors.password !== undefined && ( + {formik.errors.password} + )} + + + + +
+ + ) +} + +export default UpdateEmail diff --git a/src/app/cuenta/UpdatePassword.tsx b/src/app/cuenta/UpdatePassword.tsx new file mode 100644 index 0000000..bc75507 --- /dev/null +++ b/src/app/cuenta/UpdatePassword.tsx @@ -0,0 +1,160 @@ +'use client' +import Button from '@/components/ui/Button' +import Typography from '@/components/ui/Typography' +import FormGroup from '@/components/ui/form/FormGroup' +import PasswordInput from '@/components/ui/form/PasswordInput' +import { useAppDispatch } from '@/hooks/useAppDispatch' +import useSession from '@/hooks/useSession' +import { addAlert } from '@/state/feedbackSlice' +import { nanoid } from '@reduxjs/toolkit' +import { AppwriteException } from 'appwrite' +import { updatePassword } from 'entgamers-database/frontend/session' +import { useFormik } from 'formik' +import { type FC } from 'react' +import { object, ref, string } from 'yup' + +interface UpdatePasswordData { + password: string + confirmPassword: string + currentPassword: string +} + +const updatePasswordSchema = object({ + password: string() + .min(6, 'La contraseña debe tener al menos 6 caracteres') + .matches(/[a-z]/, 'La contraseña debe tener al menos una letra minúscula') + .matches(/[A-Z]/, 'La contraseña debe tener al menos una letra mayúscula') + .matches(/[0-9]/, 'La contraseña debe tener al menos un número') + .required('La contraseña es requerida'), + confirmPassword: string().oneOf([ref('password')], 'Las contraseñas no coinciden').required('La confirmación de la contraseña es requerida'), + currentPassword: string().required('La contraseña actual es requerida') +}) + +const UpdatePassword: FC = () => { + const { status, session } = useSession('/login') + const dispatch = useAppDispatch() + + const formik = useFormik({ + initialValues: { + password: '', + confirmPassword: '', + currentPassword: '' + }, + onSubmit: async ({ password, currentPassword }) => { + try { + await updatePassword(password, currentPassword) + dispatch(addAlert({ + id: nanoid(), + title: 'Contrasenya actualizada', + message: 'Ahora puedes iniciar sesión', + severity: 'success' + })) + } catch (error) { + if (error instanceof AppwriteException) { + dispatch(addAlert({ + id: nanoid(), + message: error.message, + title: 'Error mientras se actualizaba la contraseña', + severity: 'error' + })) + } else { + dispatch(addAlert({ + id: nanoid(), + message: 'Error desconocido', + title: 'Error mientras se actualizaba la contraseña', + severity: 'error' + })) + } + } + }, + validationSchema: updatePasswordSchema, + validateOnMount: true + }) + + if (status !== 'idle' || session === undefined) { + // TODO: Replace with Skeleton + return null + } + + return ( + <> + Actualizar contraseña +
+ + + + {formik.touched.password !== undefined && formik.errors.password !== undefined && ( + + {formik.errors.password} + + )} + + + + + {formik.touched.confirmPassword !== undefined && formik.errors.confirmPassword !== undefined && ( + + {formik.errors.confirmPassword} + + )} + + + + + {formik.touched.currentPassword !== undefined && formik.errors.currentPassword !== undefined && ( + + {formik.errors.currentPassword} + + )} + + + + +
+ + ) +} +export default UpdatePassword diff --git a/src/app/cuenta/UpdateUserName.tsx b/src/app/cuenta/UpdateUserName.tsx new file mode 100644 index 0000000..a69a1d5 --- /dev/null +++ b/src/app/cuenta/UpdateUserName.tsx @@ -0,0 +1,115 @@ +'use client' +import Button from '@/components/ui/Button' +import Typography from '@/components/ui/Typography' +import FormGroup from '@/components/ui/form/FormGroup' +import Input from '@/components/ui/form/Input' +import { useAppDispatch } from '@/hooks/useAppDispatch' +import useSession from '@/hooks/useSession' +import { addAlert } from '@/state/feedbackSlice' +import { nanoid } from '@reduxjs/toolkit' +import { AppwriteException } from 'appwrite' +import { updateName } from 'entgamers-database/frontend/session' +import { useFormik } from 'formik' +import { useEffect, type FC } from 'react' +import { object, string } from 'yup' + +interface UpdateUserNameData { + name: string +} + +const UpdateUserNameSchema = object({ + name: string().required('El nombre es requerido') +}) + +const UpdateUserName: FC = () => { + const { status, session, user } = useSession('/login') + const dispatch = useAppDispatch() + + const formik = useFormik({ + initialValues: { + name: '' + }, + onSubmit: async ({ name }) => { + try { + await updateName(name) + dispatch(addAlert({ + id: nanoid(), + title: 'Nombre actualizado', + message: 'Se actualizo correctamente el nombre', + severity: 'success' + })) + } catch (error) { + if (error instanceof AppwriteException) { + dispatch(addAlert({ + id: nanoid(), + message: error.message, + title: 'Error mientras se actualizaba el nombre', + severity: 'error' + })) + } else { + dispatch(addAlert({ + id: nanoid(), + message: 'Error desconocido', + title: 'Error mientras se actualizaba el nombre', + severity: 'error' + })) + } + } + }, + validationSchema: UpdateUserNameSchema, + validateOnMount: true, + initialTouched: { name: true } + }) + + useEffect(() => { + if (status === 'idle' && session !== undefined && user !== undefined) { + formik.setValues({ + name: user?.name ?? '' + }) + .catch(console.error) + } + }, [status, session, user]) + + if (status !== 'idle' || session === undefined) { + // TODO: Replace with Skeleton + return null + } + return ( + <> + Cambia tu nombre de usuario +
+ + + + {formik.touched.name !== undefined && formik.errors.name !== undefined && ( + {formik.errors.name} + )} + + + + +
+ + ) +} +export default UpdateUserName diff --git a/src/app/cuenta/UpdateUserPreferences.tsx b/src/app/cuenta/UpdateUserPreferences.tsx new file mode 100644 index 0000000..01152b6 --- /dev/null +++ b/src/app/cuenta/UpdateUserPreferences.tsx @@ -0,0 +1,111 @@ +'use client' +import Button from '@/components/ui/Button' +import Typography from '@/components/ui/Typography' +import FormGroup from '@/components/ui/form/FormGroup' +import TextArea from '@/components/ui/form/TextArea' +import { useAppDispatch } from '@/hooks/useAppDispatch' +import useManageError from '@/hooks/useManageError' +import useSession from '@/hooks/useSession' +import { setCurrentUser } from '@/state/sessionSlice' +import { updatePreferences, type UserPreferences } from 'entgamers-database/frontend/session' +import { useFormik } from 'formik' +import { useEffect, type FC } from 'react' +import { array, object, string, type ObjectSchema } from 'yup' + +const socialLinksSchema: ObjectSchema = object({ + bio: string().max(280, 'La descripción debe tener menos de 280 caracteres'), + profilePicture: string().url('La imagen debe ser una URL'), + socialLinks: array().of( + object({ + label: string().required('La etiqueta es requerida'), + url: string().url('La URL debe ser una URL').required('La URL es requerida') + }) + ).min(0) +}) + +const UpdateUserPreferences: FC = () => { + const { status, session, user } = useSession('/login') + const dispatch = useAppDispatch() + const { manageError } = useManageError() + + const formik = useFormik({ + initialValues: { + bio: '', + profilePicture: '', + socialLinks: [] + }, + onSubmit: async ({ bio, profilePicture, socialLinks }) => { + try { + const updatedUserWithPreferences = await updatePreferences({ bio, profilePicture, socialLinks }) + dispatch(setCurrentUser(updatedUserWithPreferences)) + } catch (error) { + manageError(error, 'Error mientras se actualizaba las preferencias', ' Error desconocido mientras se actualizaba las preferencias', 'error') + } + }, + validationSchema: socialLinksSchema, + validateOnMount: true + }) + + useEffect(() => { + if (status === 'idle' && session !== undefined) { + formik.setValues({ + bio: user?.prefs.bio ?? '', + profilePicture: user?.prefs.profilePicture ?? '', + socialLinks: user?.prefs.socialLinks ?? [] + }) + .catch(console.error) + } + }, [status, session]) + + if (status !== 'idle' || session === undefined) { + // TODO: Replace with Skeleton + return null + } + + return ( + <> + + Preferencias + +
+ + +