├── vite-env.d.ts
├── .husky
└── pre-commit
├── vercel.json
├── public
├── logo.png
└── images
│ ├── rules
│ ├── rule_1.webp
│ ├── rule_2.webp
│ ├── rule_3.webp
│ └── rule_4.webp
│ ├── rewards
│ ├── mx_keys.webp
│ ├── mx_master_3s.webp
│ ├── webcam_mx_brio.webp
│ ├── alfombrilla_logitech.webp
│ ├── bronze_medal.svg
│ ├── silver_medal.svg
│ └── gold_medal.svg
│ ├── faqs
│ └── chevron.svg
│ └── sponsors
│ ├── getmagical.svg
│ ├── logitech.svg
│ └── 4geeks.svg
├── src
├── components
│ ├── Regulation.tsx
│ ├── common
│ │ ├── Title.tsx
│ │ ├── Rule.tsx
│ │ ├── Button.tsx
│ │ ├── ButtonLink.tsx
│ │ └── Award.tsx
│ ├── Footer.tsx
│ ├── CTASection.tsx
│ ├── Home.tsx
│ ├── Sponsors.tsx
│ ├── FAQ.tsx
│ ├── Hero.tsx
│ ├── Register
│ │ ├── components
│ │ │ ├── TermsAndConditions.tsx
│ │ │ ├── ProjectForm.tsx
│ │ │ ├── Input.tsx
│ │ │ └── Participants.tsx
│ │ ├── zod.schema.ts
│ │ └── index.tsx
│ ├── Rules.tsx
│ ├── Awards.tsx
│ ├── QACollapse.tsx
│ └── Nav.tsx
├── constants.ts
├── routes
│ └── index.ts
├── main.tsx
├── common
│ └── utils
│ │ └── cn.ts
├── App.tsx
├── index.css
└── data
│ └── faqs.ts
├── vite.config.ts
├── tsconfig.node.json
├── .prettierrc
├── index.html
├── .gitignore
├── .eslintrc.cjs
├── tsconfig.json
├── uno.config.ts
├── package.json
└── README.md
/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | # .husky/pre-commit
2 | npx lint-staged
3 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | { "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }
2 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afordigital/hackaton-del-dev/HEAD/public/logo.png
--------------------------------------------------------------------------------
/src/components/Regulation.tsx:
--------------------------------------------------------------------------------
1 | export const Regulation = () => {
2 | return <>Regulation>
3 | }
4 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 |
2 | export enum AWARD_TYPE {
3 | PRICE= 'PRICE',
4 | GIFT = 'GIFT'
5 | }
6 |
--------------------------------------------------------------------------------
/public/images/rules/rule_1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afordigital/hackaton-del-dev/HEAD/public/images/rules/rule_1.webp
--------------------------------------------------------------------------------
/public/images/rules/rule_2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afordigital/hackaton-del-dev/HEAD/public/images/rules/rule_2.webp
--------------------------------------------------------------------------------
/public/images/rules/rule_3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afordigital/hackaton-del-dev/HEAD/public/images/rules/rule_3.webp
--------------------------------------------------------------------------------
/public/images/rules/rule_4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afordigital/hackaton-del-dev/HEAD/public/images/rules/rule_4.webp
--------------------------------------------------------------------------------
/public/images/rewards/mx_keys.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afordigital/hackaton-del-dev/HEAD/public/images/rewards/mx_keys.webp
--------------------------------------------------------------------------------
/src/routes/index.ts:
--------------------------------------------------------------------------------
1 | export const ROUTE = {
2 | home: '/',
3 | register: '/register',
4 | regulation: '/regulation',
5 | }
6 |
--------------------------------------------------------------------------------
/public/images/rewards/mx_master_3s.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afordigital/hackaton-del-dev/HEAD/public/images/rewards/mx_master_3s.webp
--------------------------------------------------------------------------------
/public/images/rewards/webcam_mx_brio.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afordigital/hackaton-del-dev/HEAD/public/images/rewards/webcam_mx_brio.webp
--------------------------------------------------------------------------------
/public/images/rewards/alfombrilla_logitech.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afordigital/hackaton-del-dev/HEAD/public/images/rewards/alfombrilla_logitech.webp
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 | import UnoCSS from 'unocss/vite'
4 |
5 | export default defineConfig({
6 | plugins: [react(), UnoCSS()]
7 | })
8 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "semi": false,
5 | "singleQuote": true,
6 | "jsxSingleQuote": false,
7 | "trailingComma": "none",
8 | "bracketSpacing": true,
9 | "bracketSameLine": false,
10 | "arrowParens": "always"
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/common/Title.tsx:
--------------------------------------------------------------------------------
1 | type TitleProps = {
2 | children: string
3 | }
4 |
5 | export const Title = ({ children }: TitleProps) => {
6 | return (
7 |
8 | {children}
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | export const Footer = () => {
2 | return (
3 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/CTASection.tsx:
--------------------------------------------------------------------------------
1 | import { ButtonLink } from "./common/ButtonLink"
2 | import { Title } from "./common/Title"
3 |
4 |
5 |
6 | export const CTASection = () => {
7 | return (
8 |
9 | Busca un equipo y regístrate
10 | Registrarme
11 |
12 | )
13 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Hackaton del dev
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 |
27 | # locks
28 | package-lock.json
29 | yarn.lock
30 | pnpm-lock.yaml
31 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 |
5 | import '@unocss/reset/tailwind-compat.css'
6 | import 'virtual:uno.css'
7 | import './index.css'
8 | import { BrowserRouter } from 'react-router-dom'
9 |
10 | ReactDOM.createRoot(document.getElementById('root')!).render(
11 |
12 |
13 |
14 |
15 |
16 | )
17 |
--------------------------------------------------------------------------------
/src/components/common/Rule.tsx:
--------------------------------------------------------------------------------
1 | type RuleProps = {
2 | id: number
3 | alt: string
4 | description: string
5 | }
6 |
7 | export const Rule = ({ id, alt, description }: RuleProps) => {
8 | return (
9 |
10 |

11 |
12 | {description}
13 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended'
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true }
16 | ]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/common/Button.tsx:
--------------------------------------------------------------------------------
1 | type ButtonProps = {
2 | children: React.ReactNode
3 | onClick: () => void
4 | }
5 |
6 | export const Button = ({ children, onClick }: ButtonProps) => {
7 | return (
8 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/Home.tsx:
--------------------------------------------------------------------------------
1 | import { Awards } from './Awards'
2 | import { CTASection } from './CTASection'
3 | import { Faq } from './FAQ'
4 | import { Hero } from './Hero'
5 | import { Rules } from './Rules'
6 | import { Sponsors } from './Sponsors'
7 |
8 | export const Home = () => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/common/ButtonLink.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { ROUTE } from '../../routes'
4 |
5 | type Props = {
6 | to: keyof typeof ROUTE
7 | children: React.ReactNode
8 | }
9 |
10 | export const ButtonLink: FC = ({ to, children }) => {
11 | return (
12 |
16 | {children}
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Sponsors.tsx:
--------------------------------------------------------------------------------
1 | import { Title } from './common/Title'
2 |
3 | export const Sponsors = () => {
4 | return (
5 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/common/utils/cn.ts:
--------------------------------------------------------------------------------
1 | import { ClassValue, clsx } from 'clsx'
2 | import { twMerge } from 'tailwind-merge'
3 |
4 | /**
5 | * Combines and merges Tailwind CSS classes using twMerge and clsx utility functions.
6 | * twMerge is used to handle conflicts between classes effectively.
7 | *
8 | * @param {...ClassValue} inputs - An array of class values to be combined and merged.
9 | * @returns {string} - The merged and combined class names as a string.
10 | */
11 | export const cn = (...inputs: ClassValue[]) => {
12 | return twMerge(clsx(inputs))
13 | }
14 |
15 | /**
16 | * Source:
17 | * Tailwind merge: https://github.com/dcastil/tailwind-merge/tree/v1.14.0
18 | */
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [
21 | {
22 | "path": "./tsconfig.node.json"
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/common/Award.tsx:
--------------------------------------------------------------------------------
1 | import { AWARD_TYPE } from '../../constants'
2 |
3 | type AwardProps = {
4 | img: string
5 | alt: string
6 | title: string
7 | awardType: AWARD_TYPE
8 | }
9 |
10 | export const Award = ({ img, alt, title, awardType }: AwardProps) => {
11 | return (
12 |
15 |

16 |
17 |
20 | {title}
21 |
22 |
23 | )
24 | }
25 |
26 | // flex-col items-end md:flex-row md:items-center even:items-center
27 |
--------------------------------------------------------------------------------
/src/components/FAQ.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { faqs } from '../data/faqs'
3 | import { QACollapse } from './QACollapse'
4 | import { Title } from './common/Title'
5 |
6 | export const Faq = () => {
7 | const [expandedFaq, setExpandedFaq] = useState(null)
8 |
9 | const handleExpanderClick = (id: string) => {
10 | setExpandedFaq((prev) => (prev === id ? null : id))
11 | }
12 | const renderFaqs = () =>
13 | faqs.map((faq) => (
14 |
22 | ))
23 |
24 | return (
25 |
26 | Faqs
27 | {renderFaqs()}
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/public/images/faqs/chevron.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/src/components/Hero.tsx:
--------------------------------------------------------------------------------
1 | import Spline from '@splinetool/react-spline'
2 | import { ButtonLink } from './common/ButtonLink'
3 |
4 | export const Hero = () => {
5 | return (
6 |
7 |
8 |
9 | La Hackathon Del Dev
10 |
11 |
12 | Una hackathon para cualquier dev
13 |
14 |
15 | Próxima edición 13 de abril del 2024
16 |
17 | Registrarme
18 |
19 |
20 |
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/Register/components/TermsAndConditions.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { FieldErrors, UseFormRegister } from 'react-hook-form'
3 | import { RegisterForm } from '../zod.schema'
4 |
5 | type Props = {
6 | register: UseFormRegister
7 | errors: FieldErrors
8 | }
9 |
10 | export const TermsAndConditions: FC = ({ register, errors }) => {
11 | return (
12 |
13 |
14 |
19 | Al enviar mi participación, confirmo que he leído y acepto los{' '}
20 | términos y condiciones de
21 | privacidad.
22 |
23 |
24 | {errors.terms_and_conditions && (
25 |
26 | {errors.terms_and_conditions.message}
27 |
28 | )}
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/uno.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, presetIcons, presetWebFonts, presetUno } from 'unocss'
2 |
3 | export default defineConfig({
4 | theme: {
5 | colors: {
6 | cBackground: '#161616',
7 | cPrimary: '#151515',
8 | cSecondary: '#2E2E2E',
9 | cTertiary: '#A8A8A8',
10 | cStrokeBox: '#767676',
11 | cWhite: '#EFEFEF',
12 | cGreenText: '#35D78B',
13 | cGreenButton: '#2F8F62',
14 | cGreenStroke: '#33CA86',
15 | cRed: '#EC3F3F'
16 | },
17 | dropShadow: {
18 | 'customShadow': '0 0 48px rgba(115, 134, 125, 0.30)',
19 | }
20 | },
21 | shortcuts: {
22 | 'title-gradient':
23 | 'bg-gradient-to-r text-transparent bg-clip-text from-[#EEF1F0] to-[#555555]'
24 | },
25 | presets: [
26 | presetUno(),
27 | presetWebFonts({
28 | provider: 'google',
29 | fonts: {
30 | inter: 'Inter'
31 | }
32 | }),
33 | presetIcons({
34 | cdn: 'https://esm.sh/',
35 | extraProperties: {
36 | display: 'inline-block',
37 | 'vertical-align': 'middle'
38 | }
39 | })
40 | ]
41 | })
42 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from 'react-router-dom'
2 | import { Register } from './components/Register'
3 | import { Home } from './components/Home'
4 | import { Toaster } from 'sonner'
5 | import { ROUTE } from './routes'
6 | import { Nav } from './components/Nav'
7 | import { Regulation } from './components/Regulation'
8 | import { Footer } from './components/Footer'
9 |
10 | function App() {
11 | return (
12 | <>
13 |
14 |
15 |
16 | } />
17 | } />
18 | } />
19 |
20 |
29 |
30 |
31 | >
32 | )
33 | }
34 |
35 | export default App
36 |
--------------------------------------------------------------------------------
/src/components/Register/zod.schema.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const RegisterFormSchema = z.object({
4 | project_name: z.string().min(1, { message: 'Campo requerido' }),
5 | project_description: z.string().min(12, { message: 'Campo requerido' }),
6 | project_url: z
7 | .string()
8 | .min(1, { message: 'Campo requerido' })
9 | .url()
10 | .includes('github.com', { message: 'URL de GitHub no válida' }),
11 | participants: z.array(
12 | z.object({
13 | participant_name: z.string().min(1, { message: 'Campo requerido' }),
14 | participant_country: z.string().min(1, { message: 'Campo requerido' }),
15 | participant_email: z
16 | .string()
17 | .email({ message: 'Email no válido' })
18 | .min(1, { message: 'Campo requerido' })
19 | })
20 | ),
21 | terms_and_conditions: z.boolean().refine((value) => value === true, {
22 | message: 'Debes aceptar los términos y condiciones'
23 | })
24 | })
25 |
26 | export type RegisterForm = z.infer
27 | export type RegisterFormParticipants = z.infer<
28 | typeof RegisterFormSchema
29 | >['participants'][0]
30 |
--------------------------------------------------------------------------------
/src/components/Rules.tsx:
--------------------------------------------------------------------------------
1 | import { Rule } from './common/Rule'
2 | import { Title } from './common/Title'
3 | import { ButtonLink } from './common/ButtonLink'
4 |
5 | export const Rules = () => {
6 | return (
7 |
11 | ¿En qué consiste?
12 |
34 | Ver reglamento
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/Register/components/ProjectForm.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Input } from './Input'
3 | import { FieldErrors, UseFormRegister } from 'react-hook-form'
4 | import { RegisterForm } from '../zod.schema'
5 |
6 | type Props = {
7 | register: UseFormRegister
8 | errors: FieldErrors
9 | }
10 |
11 | export const ProjectForm: FC = ({ register, errors }) => {
12 | return (
13 | <>
14 |
21 |
22 |
29 |
30 |
37 | >
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/Register/components/Input.tsx:
--------------------------------------------------------------------------------
1 | import { FieldError } from 'react-hook-form'
2 | import { Info } from 'lucide-react'
3 | import React from 'react'
4 |
5 | type InputProps = React.ComponentProps<'input'> & {
6 | id: string
7 | label: string
8 | error: FieldError | undefined
9 | }
10 |
11 | export const Input = React.forwardRef(
12 | (
13 | { label, error, ...inputProps }: InputProps,
14 | ref: React.Ref
15 | ) => {
16 | return (
17 |
18 |
26 |
31 |
37 | {error && }
38 |
39 |
40 | {error?.message}
41 |
42 |
43 | )
44 | }
45 | )
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hacktaton-del-dev",
3 | "version": "0.0.0",
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "tsc && vite build",
8 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
9 | "preview": "vite preview",
10 | "prepare": "husky"
11 | },
12 | "dependencies": {
13 | "@hookform/resolvers": "3.3.4",
14 | "@splinetool/react-spline": "^2.2.6",
15 | "@unocss/reset": "^0.56.5",
16 | "clsx": "^2.1.0",
17 | "lucide-react": "^0.360.0",
18 | "react": "^18.2.0",
19 | "react-dom": "^18.2.0",
20 | "react-hook-form": "^7.51.1",
21 | "react-router-dom": "^6.22.3",
22 | "sonner": "^1.4.41",
23 | "tailwind-merge": "^2.2.2",
24 | "uuid": "^9.0.1",
25 | "zod": "^3.22.4"
26 | },
27 | "devDependencies": {
28 | "@types/react": "^18.2.25",
29 | "@types/react-dom": "^18.2.10",
30 | "@typescript-eslint/eslint-plugin": "^6.14.0",
31 | "@typescript-eslint/parser": "^6.14.0",
32 | "@vitejs/plugin-react": "^4.0.3",
33 | "eslint": "^8.45.0",
34 | "eslint-plugin-react-hooks": "^4.6.0",
35 | "eslint-plugin-react-refresh": "^0.4.3",
36 | "prettier": "^3.2.5",
37 | "husky": "^9.0.7",
38 | "lint-staged": "^15.2.0",
39 | "typescript": "^5.3.2",
40 | "unocss": "^0.56.5",
41 | "vite": "^4.4.5"
42 | },
43 | "lint-staged": {
44 | "**/*.{js,jsx,ts,tsx}": [
45 | "npx eslint --fix",
46 | "prettier --write"
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/public/images/rewards/bronze_medal.svg:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/public/images/rewards/silver_medal.svg:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #161616;
3 | }
4 |
5 | li[data-sonner-toast][data-type='success'] svg {
6 | color: #10b981;
7 | }
8 |
9 | li[data-sonner-toast][data-type='warning'] svg {
10 | color: #f59e0b;
11 | }
12 |
13 | li[data-sonner-toast][data-type='error'] svg {
14 | color: #ef4444;
15 | }
16 |
17 | .layout-responsive {
18 | display: grid;
19 | gap: 1rem;
20 | grid-auto-rows: 22rem;
21 | grid-template-columns: repeat(4, 1fr);
22 |
23 | @media (max-width: 664.99px) {
24 | grid-template-columns: 1fr;
25 | }
26 |
27 | @media (min-width: 665px) and (max-width: 1200px) {
28 | grid-template-columns: 1fr 1fr;
29 | }
30 | }
31 |
32 | .hero-title {
33 | font-size: clamp(2.8rem, 10vw, 6rem);
34 | }
35 |
36 | .hero-description {
37 | font-size: clamp(1.4rem, 5vw, 2.25rem);
38 | }
39 |
40 | .hero-caption {
41 | font-size: clamp(1rem, 5vw, 1.5rem);
42 | }
43 |
44 | .text-fluid-title {
45 | font-size: clamp(2.2rem, 10vw, 5rem);
46 | text-align: center;
47 | }
48 |
49 | /* Animations */
50 | .animate-fade-down {
51 | animation: fadeDown ease-in-out both 200ms;
52 | }
53 |
54 | @keyframes fadeDown {
55 | 0% {
56 | opacity: 0;
57 | transform: translateY(-2rem);
58 | }
59 |
60 | 100% {
61 | opacity: 1;
62 | transform: translateY(0);
63 | }
64 | }
65 | /* delete after José fix Spline animation */
66 | canvas {
67 | width: 300px !important;
68 | margin-top: -5rem;
69 | margin-right: -2rem;
70 | }
71 |
72 | .award-container {
73 | min-height: 450px;
74 |
75 | display: flex;
76 | justify-content: center;
77 | align-items: flex-end;
78 |
79 | @media (width <= 1004px) {
80 | flex-direction: column;
81 | align-items: center;
82 | }
83 | }
84 |
85 | .box:nth-child(2) {
86 | align-self: start;
87 |
88 | @media (width <= 1004px) {
89 | order: -1;
90 | }
91 | }
--------------------------------------------------------------------------------
/public/images/rewards/gold_medal.svg:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/public/images/sponsors/getmagical.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/Awards.tsx:
--------------------------------------------------------------------------------
1 | import { AWARD_TYPE } from '../constants'
2 | import { Award } from './common/Award'
3 | import { Title } from './common/Title'
4 |
5 | export const Awards = () => {
6 | return (
7 |
8 | Premios
9 |
10 |
11 |
17 |
23 |
29 |
30 |
31 |
32 | Solo por participar, entrarás automaticamente en el sorteo de los
33 | siguientes productos
34 |
35 | *solo residentes en España*
36 |
37 |
38 |
39 |
45 |
51 |
57 |
63 |
64 |
65 | )
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/QACollapse.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '../common/utils/cn'
2 |
3 | interface QACollapseProps {
4 | /**
5 | * The question to be displayed on the Collapsible.
6 | */
7 | question?: string
8 |
9 | /**
10 | * The answer response associated with the Collapsible.
11 | */
12 | answer?: string
13 |
14 | /**
15 | * The unique identifier for the Expander component.
16 | */
17 | id: string
18 |
19 | /**
20 | * The CSS class to apply to the component.
21 | */
22 | className?: string
23 |
24 | /**
25 | * A flag indicating whether the Expander is currently expanded or not.
26 | */
27 | isExpanded?: boolean
28 |
29 | /**
30 | * The function to call when the Expander is clicked.
31 | * @param id - The id of the Expander that was clicked.
32 | */
33 | onClick: (id: string) => void
34 | }
35 |
36 | export const QACollapse = ({
37 | question,
38 | answer,
39 | id,
40 | className,
41 | isExpanded,
42 | onClick
43 | }: QACollapseProps) => {
44 | const classes = {
45 | container: cn('relative w-full flex flex-col gap-4', className),
46 | button: cn(
47 | 'flex gap-4 justify-between items-center',
48 | 'bg-transparent border-none text-white',
49 | 'z-1 relative',
50 | 'transition-colors duration-300',
51 | 'w-full h-full underline hover:text-green',
52 | 'text-2xl font-bold',
53 | {
54 | 'text-green': isExpanded
55 | }
56 | ),
57 | icon: cn(
58 | 'h-6 w-6',
59 | 'max-xs:hidden',
60 | 'transition-transform duration-300 ease-in-out',
61 | {
62 | 'rotate-90': isExpanded
63 | }
64 | ),
65 | collapse: cn('animate-fade-down', 'leading-relaxed text-lg')
66 | }
67 |
68 | const handleClick = () => onClick(id)
69 |
70 | return (
71 |
72 |
80 |
81 | {/* Expanded Content */}
82 | {isExpanded &&
{answer}}
83 |
84 | )
85 | }
86 |
--------------------------------------------------------------------------------
/src/data/faqs.ts:
--------------------------------------------------------------------------------
1 | export type FaqTypes = {
2 | question: string
3 | answer: string
4 | }
5 |
6 | export const faqs: Array = [
7 | {
8 | question: '¿Qué es una hackathon?',
9 | answer:
10 | 'Un hackathon un evento en el que los participantes colaboran y compiten desde diferentes ubicaciones, en un tiempo limitado. El objetivo es fomentar una competitividad sana, aprender a trabajar con personas externas y pasarlo bien mientras se aprende.'
11 | },
12 | {
13 | question: '¿Qué proyecto tengo que hacer?',
14 | answer:
15 | 'Ejemplo Cambiar texto, Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,'
16 | },
17 | {
18 | question: '¿Puedo participar siendo principiante?',
19 | answer:
20 | 'Ejemplo Cambiar texto, Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,'
21 | },
22 | {
23 | question: '¿Cuántos participantes puede haber por equipo?',
24 | answer:
25 | 'Ejemplo Cambiar texto, Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,'
26 | },
27 | {
28 | question: '¿Puedo participar solo?',
29 | answer:
30 | 'Ejemplo Cambiar texto, Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,'
31 | },
32 | {
33 | question: '¿No soy de España, puedo participar?',
34 | answer:
35 | 'Ejemplo Cambiar texto, Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,'
36 | },
37 | {
38 | question: '¿Es gratis?',
39 | answer:
40 | 'Ejemplo Cambiar texto, Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,Ejemplo Cambiar texto,'
41 | }
42 | ]
43 |
--------------------------------------------------------------------------------
/src/components/Register/index.tsx:
--------------------------------------------------------------------------------
1 | import { zodResolver } from '@hookform/resolvers/zod'
2 | import { useForm } from 'react-hook-form'
3 | import { toast } from 'sonner'
4 |
5 | import { RegisterForm, RegisterFormSchema } from './zod.schema'
6 | import { Participants } from './components/Participants'
7 | import { TermsAndConditions } from './components/TermsAndConditions'
8 | import { ProjectForm } from './components/ProjectForm'
9 |
10 | const DEFAULT_REGISTER_FORM_VALUES: RegisterForm = {
11 | project_name: '',
12 | project_description: '',
13 | project_url: '',
14 | participants: [
15 | {
16 | participant_name: '',
17 | participant_country: '',
18 | participant_email: ''
19 | }
20 | ],
21 | terms_and_conditions: false
22 | } as const
23 |
24 | export const Register = () => {
25 | const {
26 | register,
27 | handleSubmit,
28 | formState: { errors },
29 | reset,
30 | control,
31 | trigger
32 | } = useForm({
33 | resolver: zodResolver(RegisterFormSchema),
34 | defaultValues: DEFAULT_REGISTER_FORM_VALUES
35 | })
36 |
37 | const onSubmit = (data: RegisterForm) => {
38 | console.log('data', data)
39 | reset()
40 | toast.success('¡Felicidades! Acabas de registrar tu aplicación')
41 | }
42 |
43 | return (
44 |
45 |
46 | Regístrate
47 |
48 |
49 | Aunque te hayas prescrito, debes registrarte con todos los datos que se
50 | solicitan en el formulario.
51 |
52 |
53 | Si quieres ver el resto de detalles, revisa{' '}
54 |
55 | el reglamento
56 | {' '}
57 | antes de enviar tu participación.
58 |
59 |
76 |
77 | )
78 | }
79 |
--------------------------------------------------------------------------------
/public/images/sponsors/logitech.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [![Contributors][contributors-shield]][contributors-url]
4 | [![Forks][forks-shield]][forks-url]
5 | [![Stargazers][stars-shield]][stars-url]
6 | [![Issues][issues-shield]][issues-url]
7 |
8 | ## Hackaton del dev
9 |
10 | La Hackathon Del Dev: una hackathon para cualquier dev.\
11 | [Diseño de Figma](https://www.figma.com/file/DB7UnFLhRHdJHN1aE4dTLj/Landing-hackathon?type=design&node-id=86%3A192&mode=design&t=2RUeY0uJmcgMi0wG-1) · [Reportar error](https://github.com/afordigital/hackaton-del-dev/issues) · [Sugerir algo](https://github.com/afordigital/hackaton-del-dev/issues)
12 |
13 |
14 |
15 | ### Capturas de pantalla
16 | 
17 |
18 | ## Authors
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ## Contributors
27 |
28 |
29 |
30 |
31 |
32 | ## Getting Started
33 |
34 | 1. Clone or fork the repository:
35 |
36 | ```bash
37 | https://github.com/afordigital/hackaton-del-dev.git
38 | ```
39 |
40 | 2. Install dependencies with your favorite package manager:
41 |
42 | ```bash
43 | # with npm:
44 | npm install
45 |
46 | # with pnpm:
47 | pnpm install
48 |
49 | # with yarn:
50 | yarn install
51 |
52 | # with ultra:
53 | ultra install
54 | ```
55 |
56 | 3. Run in your terminal:
57 |
58 | ```bash
59 | # with npm:
60 | npm run dev
61 |
62 | # with pnpm:
63 | pnpm run dev
64 |
65 | # with yarn:
66 | yarn dev
67 |
68 | # with ultra:
69 | ultra dev
70 | ```
71 |
72 | and open http://localhost:3000 🌺.
73 |
74 | ## Stack
75 |
76 | - Vite > v4.4
77 | - React > v18.2
78 | - UnoCSS > v0.56
79 | - Forms - Zod and RHF
80 | - Icons - Lucide React
81 | - Id's - uuid
82 |
83 |
84 | ## Deployment
85 |
86 | Vercel: [Hackaton del dev](https://hackaton-del-dev.vercel.app/)
87 |
88 |
89 |
90 | ps://github.com/afordigital/hackaton-del-dev/stargazers
91 |
92 |
93 | [contributors-shield]: https://img.shields.io/github/contributors/afordigital/hackaton-del-dev.svg?style=for-the-badge
94 | [contributors-url]: https://github.com/afordigital/hackaton-del-dev/graphs/contributors
95 | [forks-shield]: https://img.shields.io/github/forks/afordigital/hackaton-del-dev.svg?style=for-the-badge
96 | [forks-url]: https://github.com/afordigital/hackaton-del-dev/network/members
97 | [stars-shield]: https://img.shields.io/github/stars/afordigital/hackaton-del-dev.svg?style=for-the-badge
98 | [stars-url]: https://github.com/afordigital/hackaton-del-dev/stargazers
99 | [issues-shield]: https://img.shields.io/github/issues/afordigital/hackaton-del-dev.svg?style=for-the-badge
100 | [issues-url]: https://github.com/afordigital/hackaton-del-dev/issues
101 |
--------------------------------------------------------------------------------
/src/components/Nav.tsx:
--------------------------------------------------------------------------------
1 | import { Link, useLocation } from 'react-router-dom'
2 | import { ROUTE } from '../routes'
3 | import { AlignJustify, ChevronLeft } from 'lucide-react'
4 | import { cn } from '../common/utils/cn'
5 | import { useState } from 'react'
6 |
7 | export const Nav = () => {
8 | const { pathname } = useLocation()
9 | const isHome = pathname === ROUTE.home
10 | const [isOpen, setIsOpen] = useState(false)
11 | const currentHash = window.location.hash
12 | const navItems = [
13 | { id: 'root', title: 'Inicio' },
14 | { id: 'rules', title: 'Reglas' },
15 | { id: 'sponsors', title: 'Patrocinadores' },
16 | { id: 'awards', title: 'Premios' },
17 | { id: 'faqs', title: 'Preguntas' }
18 | ]
19 |
20 | const classes = {
21 | container: cn(
22 | 'container mx-auto relative flex justify-end px-8 lg:sticky lg:top-5 lg:z-30 lg:justify-center'
23 | ),
24 | nav: cn(
25 | 'text-cWhite font-semibold text-[28px] lg:text-[20px] ',
26 | 'absolute max-lg:right-8 max-lg:top-18',
27 | 'lg:w-full lg:flex lg:mt-4 lg:justify-center ',
28 | 'transition-transform z-30',
29 | {
30 | 'max-lg:translate-y-[-300%]': !isOpen,
31 | 'max-lg:translate-y-0': isOpen
32 | }
33 | ),
34 | list: cn(
35 | 'py-5 px-10 lg:py-2 ',
36 | 'flex flex-col gap-4 gap-x-[32px]',
37 | 'lg:flex-row',
38 | 'w-fit bg-[#0f0f0f]',
39 | 'rounded-lg lg:rounded-full'
40 | ),
41 | link: (active: boolean) =>
42 | cn('hover:text-green cursor-pointer transition-colors', {
43 | 'text-green': active
44 | })
45 | }
46 |
47 | const handleClick = () => setIsOpen(!isOpen)
48 |
49 | const scrollToSection = (id: string) => {
50 | const element = document.getElementById(id)
51 | if (element) {
52 | const elementPosition = element.offsetTop
53 | window.scrollTo({ top: elementPosition - 60, behavior: 'smooth' })
54 | }
55 | }
56 |
57 | const liElements = navItems.map((item) => (
58 |
59 | {
62 | scrollToSection(item.id)
63 | }}
64 | className={classes.link(
65 | currentHash === `#${item.id}` || (!currentHash && item.id === 'root')
66 | )}
67 | >
68 | {item.title}
69 |
70 |
71 | ))
72 |
73 | return isHome ? (
74 |
75 |
81 |
82 |
92 |
93 | ) : (
94 |
102 | )
103 | }
104 |
--------------------------------------------------------------------------------
/src/components/Register/components/Participants.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { RegisterForm } from '../zod.schema'
3 | import { Input } from './Input'
4 | import {
5 | Control,
6 | FieldErrors,
7 | UseFormRegister,
8 | UseFormTrigger,
9 | useFieldArray
10 | } from 'react-hook-form'
11 | import { Plus, Trash } from 'lucide-react'
12 |
13 | type Props = {
14 | control: Control
15 | register: UseFormRegister
16 | trigger: UseFormTrigger
17 | errors: FieldErrors['participants']
18 | }
19 |
20 | export const Participants: FC = ({
21 | register,
22 | control,
23 | errors,
24 | trigger
25 | }) => {
26 | const {
27 | fields: participants,
28 | prepend: addParticipant,
29 | remove: removeParticipant
30 | } = useFieldArray({
31 | control,
32 | name: 'participants'
33 | })
34 |
35 | const handleAddParticipant = () => {
36 | trigger('participants').then((result) => {
37 | if (result) {
38 | addParticipant({
39 | participant_country: '',
40 | participant_email: '',
41 | participant_name: ''
42 | })
43 | }
44 | })
45 | }
46 |
47 | return (
48 | <>
49 |
50 |
51 | Participantes
52 |
53 |
61 |
62 |
63 | {participants.map((field, index) => {
64 | return (
65 |
66 | {0 === index ? (
67 |
93 | ) : (
94 |
95 |
96 | {field.participant_name} · {field.participant_country} ·{' '}
97 | {field.participant_email}
98 |
99 |
{
102 | removeParticipant(index)
103 | }}
104 | />
105 |
106 | )}
107 |
108 | )
109 | })}
110 | >
111 | )
112 | }
113 |
--------------------------------------------------------------------------------
/public/images/sponsors/4geeks.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------