├── .env ├── .env.example ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── 1-bug-report.yml │ ├── 2-failing-test.yml │ ├── 3-docs-bug.yml │ ├── 4-feature-request.yml │ ├── 5-enhancement-request.yml │ ├── 6-security-report.yml │ └── 7-question-support.yml ├── PULL_REQUEST_TEMPLATE.md ├── labels.yml └── workflows │ ├── ci.yml │ ├── labels.yml │ ├── vercel-preview.yml │ └── vercel-prod.yml ├── .gitignore ├── .vscode ├── css_custom_data.json ├── extensions.json └── settings.json ├── README.md ├── app ├── [userId] │ ├── opengraph-image.tsx │ └── page.tsx ├── components │ ├── FAQ.tsx │ ├── Footer.tsx │ ├── Hero.tsx │ ├── Nav.tsx │ ├── Schedule.tsx │ ├── Sponsors.tsx │ ├── Ticket.tsx │ ├── TicketDownload.tsx │ ├── WelcomeHero.tsx │ ├── common │ │ ├── Button.tsx │ │ ├── Contributors.tsx │ │ ├── Countdown.tsx │ │ ├── Divider.tsx │ │ ├── Link.tsx │ │ ├── Summary.tsx │ │ ├── Table.tsx │ │ ├── Tag.tsx │ │ ├── Talk.tsx │ │ └── hooks │ │ │ ├── index.ts │ │ │ └── useContributors.ts │ ├── config │ │ ├── components │ │ │ └── button.ts │ │ ├── constants │ │ │ └── general.ts │ │ └── utils │ │ │ └── cn.ts │ ├── icons │ │ └── Logo.tsx │ ├── logos.tsx │ ├── ui │ │ └── button.tsx │ └── utils │ │ ├── index.ts │ │ └── uploadTicket.ts ├── cursors-context.tsx ├── cursors.tsx ├── globals.css ├── hooks │ ├── useAuth.ts │ ├── useOnClickOutside.ts │ └── useTimezone.ts ├── layout.tsx ├── middleware.ts ├── opengraph-image.tsx ├── other-cursor.tsx ├── page.tsx ├── store │ └── useUserStore.ts └── utils │ └── supabase │ ├── client.ts │ ├── middleware.ts │ └── server.ts ├── next.config.mjs ├── package.json ├── party ├── index.ts └── server.ts ├── partykit.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public ├── default-og.png ├── favicon.ico ├── imgs │ ├── afordin-sponsor.png │ ├── ikurotime.png │ ├── logo.png │ ├── readme-ticket.png │ ├── speakers │ │ ├── speaker-1.png │ │ ├── speaker-2.jpg │ │ ├── speaker-3.jpg │ │ ├── speaker-4.jpeg │ │ └── speaker-5.png │ └── ticket │ │ ├── aforshow.png │ │ ├── avatar.png │ │ ├── bg.png │ │ └── sponsor_1.webp ├── next.svg └── waves.svg ├── tailwind.config.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_PROJECT_URL=https://uuljbqkwvruhxomkmxaj.supabase.co 2 | NEXT_PUBLIC_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV1bGpicWt3dnJ1aHhvbWtteGFqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MjIzNjA1NTcsImV4cCI6MjAzNzkzNjU1N30.N42cAcYzp2Q7g2prpu1v-43_Dsk_kWwmK_-45eeNauU 3 | NEXT_PUBLIC_STORAGE_BUCKET= 4 | NEXT_PUBLIC_BASE_URL = https://afor.show 5 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_PROJECT_URL=https://uuljbqkwvruhxomkmxaj.supabase.co 2 | NEXT_PUBLIC_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV1bGpicWt3dnJ1aHhvbWtteGFqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MjIzNjA1NTcsImV4cCI6MjAzNzkzNjU1N30.N42cAcYzp2Q7g2prpu1v-43_Dsk_kWwmK_-45eeNauU 3 | NEXT_PUBLIC_STORAGE_BUCKET= 4 | NEXT_PUBLIC_BASE_URL = https://afor.show 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | # **Issue Report** 13 | 14 | ## **Description** 15 | 16 | 17 | 18 | ### **Media** 19 | 20 | 21 | 22 | --- 23 | 24 | ## **Environment** 25 | 26 | 27 | 28 | - Operating System: 29 | - Node.js version: 30 | - npm version: 31 | - Browser and version: 32 | 33 | --- 34 | 35 | ## **Additional context** 36 | 37 | 38 | 39 | - 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug Report 2 | description: File a bug report 3 | title: '[Bug]: ' 4 | labels: ['Type: Bug'] 5 | assignees: 6 | - octocat 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thank you for taking the time to report a bug. Your feedback helps us improve the quality of our product! 12 | - type: input 13 | id: contact 14 | attributes: 15 | label: Discord Username 16 | description: How can we get in touch with you if we need more information? 17 | placeholder: Please provide your Discord sername or other contact details 18 | validations: 19 | required: false 20 | - type: dropdown 21 | id: severity 22 | attributes: 23 | label: Severity Level 24 | description: How severe is the bug? 25 | options: 26 | - Low 27 | - Medium 28 | - High 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: what-happened 33 | attributes: 34 | label: Description of the Bug 35 | description: Please describe what happened and what you were expecting to happen instead 36 | placeholder: Please describe the bug you encountered 37 | value: 'A bug happened!' 38 | validations: 39 | required: true 40 | - type: dropdown 41 | id: os 42 | attributes: 43 | label: Operating System 44 | description: What operating system are you using? 45 | options: 46 | - Windows 47 | - macOS 48 | - Linux 49 | - iOS 50 | - Android 51 | validations: 52 | required: true 53 | - type: dropdown 54 | id: browsers 55 | attributes: 56 | label: Browser(s) 57 | multiple: true 58 | description: Which browser(s) are you seeing the problem on? 59 | options: 60 | - Firefox 61 | - Chrome 62 | - Safari 63 | - Microsoft Edge 64 | - Opera 65 | - Other (please specify in the bug description) 66 | validations: 67 | required: true 68 | - type: dropdown 69 | id: browser-version 70 | attributes: 71 | label: Browser Version(s) 72 | multiple: true 73 | description: What version(s) of the browser(s) are you using? 74 | options: 75 | - Latest 76 | - Previous version 77 | - Specific version (please specify in the bug description) 78 | validations: 79 | required: true 80 | - type: textarea 81 | id: logs 82 | attributes: 83 | label: Relevant Log Output 84 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so there is no need to include backticks. 85 | render: zsh 86 | - type: checkboxes 87 | id: terms 88 | attributes: 89 | label: Code of Conduct 90 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/afordin/hackafor-2/blob/main/docs/CODE_OF_CONDUCT.md) 91 | options: 92 | - label: I agree to follow this project's Code of Conduct 93 | required: true 94 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-failing-test.yml: -------------------------------------------------------------------------------- 1 | name: 💉 Failing Tests 2 | description: Report failing tests or CI jobs. 3 | title: '[Test]: ' 4 | labels: ['Type: Test'] 5 | assignees: 6 | - octocat 7 | 8 | body: 9 | - type: markdown 10 | attributes: 11 | value: | 12 | Thank you for taking the time to report this problem! 13 | - type: markdown 14 | attributes: 15 | value: | 16 | Before submitting a new issue, please search both open and closed issues to ensure that your problem hasn't already been reported. 17 | 18 | - type: textarea 19 | id: what-happened 20 | attributes: 21 | label: Which test(s) or job(s) are failing? 22 | description: Please describe the failing tests or jobs in as much detail as possible, and tell us what you were expecting to happen. 23 | placeholder: 'Describe the failing tests or jobs' 24 | validations: 25 | required: true 26 | 27 | - type: textarea 28 | id: reason-of-failure 29 | attributes: 30 | label: Reason for failure 31 | description: Please explain why the tests are failing or the jobs are not passing, and tell us what we might be missing to make them pass. 32 | placeholder: 'Explain the reason for the failure' 33 | validations: 34 | required: true 35 | 36 | - type: textarea 37 | id: media-proof 38 | attributes: 39 | label: Media proof (optional) 40 | description: If applicable, please attach screenshots or videos to help explain the problem. 41 | placeholder: 'Attach media proof (optional)' 42 | validations: 43 | required: false 44 | 45 | - type: textarea 46 | id: additional-context 47 | attributes: 48 | label: Additional context (optional) 49 | description: Please provide any additional context or information that might be helpful in diagnosing the issue. 50 | placeholder: 'Add any additional context or information (optional)' 51 | validations: 52 | required: false 53 | 54 | - type: checkboxes 55 | id: terms 56 | attributes: 57 | label: Code of Conduct 58 | description: By submitting this issue, you agree to abide by our [Code of Conduct](https://github.com/afordin/hackafor-2/blob/main/docs/CODE_OF_CONDUCT.md). 59 | options: 60 | - label: I agree to follow this project's Code of Conduct 61 | required: true 62 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-docs-bug.yml: -------------------------------------------------------------------------------- 1 | name: 📚 Documentation or README.md issue report 2 | description: Report an issue in the project's documentation or README.md file. 3 | title: '[DOC]' 4 | labels: ['Documentation'] 5 | assignees: 6 | - octocat 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | To expedite issue processing, please search open and closed issues before submitting a new one. 12 | 13 | - type: textarea 14 | id: what-happened 15 | attributes: 16 | label: Describe the bug 17 | description: A clear and concise description of what the bug is. 18 | placeholder: Tell us what you see! 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | id: reason-of-failure 24 | attributes: 25 | label: To Reproduce 26 | description: | 27 | Steps to reproduce the error: 28 | (e.g.:) 29 | 1. Use x argument / navigate to 30 | 2. Fill this information 31 | 3. Go to... 32 | 4. See error 33 | placeholder: Tell us what you see! 34 | value: | 35 | ``` 36 | 1. 37 | 2. 38 | 3. 39 | 4. 40 | ``` 41 | validations: 42 | required: true 43 | - type: textarea 44 | id: media-proof 45 | attributes: 46 | label: Media proof 47 | description: If applicable, add screenshots or videos to help explain your problem. 48 | placeholder: Insert Images or Video 49 | validations: 50 | required: false 51 | 52 | - type: textarea 53 | id: solution 54 | attributes: 55 | label: Describe the solution you'd like 56 | description: A clear and concise description of what you want to happen. 57 | placeholder: Tell us your solution to this problem! 58 | validations: 59 | required: false 60 | 61 | - type: textarea 62 | id: additional-context 63 | attributes: 64 | label: Additional context 65 | description: Add any other context or additional information about the problem here. 66 | validations: 67 | required: false 68 | 69 | - type: checkboxes 70 | id: terms 71 | attributes: 72 | label: Code of Conduct 73 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/afordin/hackafor-2/blob/main/docs/CODE_OF_CONDUCT.md) 74 | options: 75 | - label: I agree to follow this project's Code of Conduct 76 | required: true 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/4-feature-request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀🆕 Feature Request 2 | description: Suggest an idea or possible new feature for this project. 3 | # title: " " 4 | labels: ['Type: Feature'] 5 | assignees: 6 | - octocat 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Before submitting a new question or support request, please search open and closed issues to avoid duplication. 12 | 13 | - type: textarea 14 | id: what-happened 15 | attributes: 16 | label: What problem does your feature request solve? 17 | description: A clear and concise description of the problem your feature request solves. 18 | placeholder: Describe the problem you want to resolve 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | id: solution 24 | attributes: 25 | label: Describe the solution you'd like 26 | description: A clear and concise description of the solution or feature you want to request. 27 | placeholder: Describe the feature you want to see implemented 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | id: media-proof 33 | attributes: 34 | label: Describe alternatives you've considered 35 | description: A clear and concise description of any alternative solutions or features you've considered. 36 | placeholder: Describe any alternative solutions you've considered 37 | validations: 38 | required: false 39 | 40 | - type: textarea 41 | id: additional-context 42 | attributes: 43 | label: Additional context 44 | description: Add any other context or additional information about the problem or feature here. 45 | validations: 46 | required: false 47 | 48 | - type: checkboxes 49 | id: terms 50 | attributes: 51 | label: Code of Conduct 52 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/afordin/hackafor-2/blob/main/docs/CODE_OF_CONDUCT.md) 53 | options: 54 | - label: I agree to follow this project's Code of Conduct 55 | required: true 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/5-enhancement-request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀➕ Enhancement Request 2 | description: Suggest an enhancement for this project or improve an existing feature. 3 | # title: " " 4 | labels: ['Type: Enhancement'] 5 | assignees: 6 | - octocat 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Before submitting a new question or support request, please search open and closed issues to avoid duplication. 12 | 13 | - type: textarea 14 | id: what-happened 15 | attributes: 16 | label: What problem would you like to solve or improve? 17 | description: A clear and concise description of the problem or limitation. 18 | placeholder: Describe the problem or limitation you want to address. 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | id: solution 24 | attributes: 25 | label: Describe your proposed solution or enhancement. 26 | description: A clear and concise description of what you want to happen. 27 | placeholder: Describe the solution or enhancement you are proposing. 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | id: media-proof 33 | attributes: 34 | label: Describe any alternative solutions you've considered (optional). 35 | description: A clear and concise description of any alternative solutions or features you've considered. 36 | placeholder: Describe any alternative solutions or features you've considered (optional). 37 | validations: 38 | required: false 39 | 40 | - type: textarea 41 | id: additional-context 42 | attributes: 43 | label: Additional context or information (optional). 44 | description: Add any other context or additional information about the problem here. 45 | placeholder: Add any other context or additional information about the problem here (optional). 46 | validations: 47 | required: false 48 | 49 | - type: checkboxes 50 | id: terms 51 | attributes: 52 | label: Code of Conduct 53 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/afordin/hackafor-2/blob/main/docs/CODE_OF_CONDUCT.md). 54 | options: 55 | - label: I agree to follow this project's Code of Conduct. 56 | required: true 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/6-security-report.yml: -------------------------------------------------------------------------------- 1 | name: 🚨 Security Report 2 | description: Report an issue to help the project improve. 3 | # title: " " 4 | labels: ['Type: Security'] 5 | assignees: 6 | - octocat 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Before submitting a new question or support request, please search open and closed issues to avoid duplication. 12 | 13 | **Before submitting a new issue, please read the SECURITY.md file located in `.github/SECURITY.md`.** 14 | 15 | ## Security Issue Reporting Guidelines 16 | 17 | If your security report contains sensitive or private data, please do not post it publicly here. Instead, please send an email to (email@example.com) with the least amount of sensitive or private data as possible. We will deeply analyze and try to fix the issue as fast as possible. 18 | 19 | The following types of information should not be posted in the public issue tracker: 20 | 21 | * Legal and/or full names 22 | * Names or usernames combined with other identifiers like phone numbers or email addresses 23 | * Health or financial information (including insurance information, social security numbers, etc.) 24 | * Information about political or religious affiliations 25 | * Information about race, ethnicity, sexual orientation, gender, or other identifying information that could be used for discriminatory purposes 26 | 27 | - type: textarea 28 | id: what-happened 29 | attributes: 30 | label: Describe the security issue 31 | description: A clear and concise description of what the bug is. 32 | placeholder: Tell us what the security issue 33 | validations: 34 | required: true 35 | 36 | - type: textarea 37 | id: reason-of-failure 38 | attributes: 39 | label: To Reproduce 40 | description: | 41 | Steps to reproduce the error: 42 | (e.g.:) 43 | 1. Use x argument / navigate to 44 | 2. Fill this information 45 | 3. Go to... 46 | 4. See error 47 | placeholder: Describe 48 | value: | 49 | 1. 50 | 2. 51 | 3. 52 | 4. 53 | validations: 54 | required: true 55 | 56 | - type: textarea 57 | id: expected-behaviour 58 | attributes: 59 | label: Expected behaviour 60 | description: A clear and concise description of what you expected to happen. 61 | placeholder: Describe 62 | validations: 63 | required: false 64 | 65 | - type: textarea 66 | id: alternatives 67 | attributes: 68 | label: Alternatives considered 69 | description: A clear and concise description of any alternative solutions or features you've considered. 70 | placeholder: Describe 71 | validations: 72 | required: false 73 | 74 | - type: dropdown 75 | id: os 76 | attributes: 77 | label: Operating System 78 | description: What operating system are you using? 79 | options: 80 | - Windows 81 | - macOS 82 | - Linux 83 | - iOS 84 | - Android 85 | 86 | - type: dropdown 87 | id: node-version 88 | attributes: 89 | label: Node Version 90 | description: What NodeJs version are you using? 91 | options: 92 | - 19.x 93 | - 18.x 94 | - 17.x 95 | - 16.x 96 | - 15.x 97 | - Other (please specify in Additional context description) 98 | 99 | - type: dropdown 100 | id: npm-version 101 | attributes: 102 | label: Npm Version 103 | description: What npm version are you using? 104 | options: 105 | - 9.x 106 | - 8.x 107 | - 7.x 108 | - 6.x 109 | - Other (please specify in Additional context description) 110 | 111 | - type: dropdown 112 | id: browsers 113 | attributes: 114 | label: Browser(s) 115 | multiple: true 116 | description: What browsers are you seeing the problem on? 117 | options: 118 | - Firefox 119 | - Chrome 120 | - Safari 121 | - Microsoft Edge 122 | - Opera 123 | - Other (please specify in Additional context description) 124 | 125 | - type: textarea 126 | id: additional-context 127 | attributes: 128 | label: Additional context 129 | description: Add any other context or additional information about the problem here. 130 | validations: 131 | required: false 132 | 133 | - type: checkboxes 134 | id: terms 135 | attributes: 136 | label: Code of Conduct 137 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/afordin/hackafor-2/blob/main/docs/CODE_OF_CONDUCT.md) 138 | options: 139 | - label: I agree to follow this project's Code of Conduct 140 | required: true 141 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/7-question-support.yml: -------------------------------------------------------------------------------- 1 | name: ❓ Question or Support Request 2 | description: Questions and requests for support. 3 | title: 'Seeking assistance with [brief description of the issue]' 4 | labels: ['Type: Question'] 5 | assignees: 6 | - octocat 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Before submitting a new question or support request, please search open and closed issues to avoid duplication. 12 | 13 | - type: textarea 14 | id: what-happened 15 | attributes: 16 | label: Describe your question or request for support 17 | description: Please provide a clear and concise description of your issue or question. 18 | placeholder: Type your question or support request here 19 | validations: 20 | required: true 21 | 22 | - type: checkboxes 23 | id: terms 24 | attributes: 25 | label: Code of Conduct 26 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/afordin/hackafor-2/blob/main/docs/CODE_OF_CONDUCT.md) 27 | options: 28 | - label: I agree to follow this project's Code of Conduct 29 | required: true 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Changes Made 🎉 2 | 3 | 4 | 5 | - [ ] feat: added new feature to improve user experience 6 | - [ ] fix: corrected a bug with login functionality 7 | - [ ] refactor: improved code readability and organization 8 | - [ ] docs: updated README with new instructions 9 | - [ ] chore: updated dependencies and configuration files 10 | 11 | ### Describe Changes 12 | 13 | ## Related Issue(s) 14 | 15 | - Issue Number 16 | 17 | ## Visuals (Optional) 18 | 19 | 20 | 21 | ## Checklist ✅ 22 | 23 | - [ ] My code follows the code style of this project. 24 | - [ ] My change requires a change to the documentation. 25 | - [ ] I have updated the documentation accordingly. 26 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - name: "Type: Bug" 2 | color: "e80c0c" 3 | description: Something isn't working as expected. 4 | from_name: "Bug" 5 | 6 | - name: "Design" 7 | color: "FFA07A" 8 | description: Something isn't working as expected. 9 | 10 | - name: "Database" 11 | color: "FBCA04" 12 | description: Issues related to the database or data storage. 13 | 14 | - name: "Good for NewComers" 15 | color: "40E0D0" 16 | description: Issues suitable for new contributors who want to get involved in the project. 17 | 18 | - name: "Help wanted" 19 | color: "35b6c0" 20 | description: Issues that need help or assistance from the community to be resolved. 21 | 22 | - name: "Need details" 23 | color: "FF69B4" 24 | description: Issues that require more information or details to be properly addressed. 25 | 26 | - name: "POC" 27 | color: "0E8A16" 28 | description: Issues related to creating or testing a proof of concept for a new feature or idea. 29 | 30 | - name: "Release" 31 | color: "32CD32" 32 | description: Issues related to preparing or managing a software release. 33 | 34 | - name: "Research" 35 | color: "C2E0C6" 36 | description: Issues related to research or investigation of a problem or potential feature. 37 | 38 | - name: "Tech Debt" 39 | color: "FBCA04" 40 | description: Issues related to technical debt, such as refactoring or cleaning up code. 41 | 42 | - name: "Discord" 43 | color: "5319E7" 44 | description: Issues related to the project's Discord community or communication channels. 45 | 46 | - name: "Type: Enhancement" 47 | color: "54b2ff" 48 | description: Suggest an improvement for an existing feature. 49 | from_name: "Enhancement" 50 | 51 | - name: "Type: Feature" 52 | color: "54b2ff" 53 | description: Suggest a new feature. 54 | 55 | - name: "Type: Security" 56 | color: "fbff00" 57 | description: A problem or enhancement related to a security issue. 58 | 59 | - name: "Type: Question" 60 | color: "9309ab" 61 | description: Request for information. 62 | from_name: "Question" 63 | 64 | - name: "Type: Test" 65 | color: "ce54e3" 66 | description: A problem or enhancement related to a test. 67 | 68 | - name: "Duplicate" 69 | color: "EB862D" 70 | description: Duplicate of another issue. 71 | 72 | - name: "Invalid" 73 | color: "faef50" 74 | description: This issue doesn't seem right. 75 | 76 | - name: "Documentation" 77 | color: "2fbceb" 78 | description: An issue/change with the documentation. 79 | 80 | - name: "Won't fix" 81 | color: "C8D9E6" 82 | description: Reported issue is working as intended. 83 | 84 | - name: "3rd party issue" 85 | color: "e88707" 86 | description: This issue might be caused by a 3rd party script/package/other reasons 87 | 88 | - name: "OS: Windows" 89 | color: "AEB1C2" 90 | description: Is Windows-specific 91 | 92 | - name: "OS: Mac" 93 | color: "AEB1C2" 94 | description: Is Mac-specific 95 | 96 | - name: "OS: Linux" 97 | color: "AEB1C2" 98 | description: Is Linux-specific 99 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Hackafor Project CI 2 | 3 | on: 4 | pull_request: 5 | branches: ['*'] 6 | push: 7 | branches: ['main'] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout Repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Node.js (v18) 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 18 21 | 22 | - name: Setup pnpm 23 | uses: pnpm/action-setup@v3 24 | with: 25 | version: latest 26 | 27 | - name: Cache pnpm Store 28 | uses: actions/cache@v4 29 | with: 30 | path: ~/.pnpm-store 31 | key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} 32 | restore-keys: ${{ runner.os }}-pnpm- 33 | 34 | - name: Install Dependencies 35 | run: pnpm install 36 | 37 | - name: Lint Code 38 | run: pnpm run lint 39 | 40 | - name: Build Project 41 | run: pnpm run build 42 | -------------------------------------------------------------------------------- /.github/workflows/labels.yml: -------------------------------------------------------------------------------- 1 | name: labeler 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | paths: 8 | - '.github/labels.yml' 9 | - '.github/workflows/labels.yml' 10 | 11 | jobs: 12 | labeler: 13 | permissions: write-all 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | - name: Run Labeler 19 | if: success() 20 | uses: crazy-max/ghaction-github-labeler@v5 21 | with: 22 | github-token: ${{ secrets.GITHUB_TOKEN }} 23 | yaml-file: .github/labels.yml 24 | skip-delete: false 25 | dry-run: false 26 | exclude: | 27 | help* 28 | *issue 29 | 30 | # How to use this action https://github.com/crazy-max/ghaction-github-labeler 31 | -------------------------------------------------------------------------------- /.github/workflows/vercel-preview.yml: -------------------------------------------------------------------------------- 1 | name: Vercel Deploy Preview 2 | 3 | env: 4 | VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} 5 | VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} 6 | 7 | on: 8 | workflow_dispatch: 9 | workflow_run: 10 | workflows: ["Hackafor Project CI"] 11 | types: 12 | - completed 13 | branches-ignore: 14 | - main 15 | 16 | jobs: 17 | Deploy-Preview: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Setup pnpm 22 | uses: pnpm/action-setup@v3 23 | with: 24 | version: latest 25 | - name: Install Vercel CLI 26 | run: npm install --global vercel@latest 27 | - name: Pull Vercel Environment Information 28 | run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} 29 | - name: Build Project Artifacts 30 | run: vercel build --token=${{ secrets.VERCEL_TOKEN }} 31 | - name: Deploy Project Artifacts to Vercel 32 | run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/vercel-prod.yml: -------------------------------------------------------------------------------- 1 | name: Vercel Deploy Prod 2 | 3 | env: 4 | VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} 5 | VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} 6 | 7 | on: 8 | workflow_dispatch: 9 | workflow_run: 10 | workflows: ["Hackafor Project CI"] 11 | types: 12 | - completed 13 | branches: 14 | - main 15 | 16 | jobs: 17 | Deploy-Production: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Setup pnpm 22 | uses: pnpm/action-setup@v3 23 | with: 24 | version: latest 25 | - name: Install Vercel CLI 26 | run: npm install --global vercel@latest 27 | - name: Pull Vercel Environment Information 28 | run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} 29 | - name: Build Project Artifacts 30 | run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} 31 | - name: Deploy Project Artifacts to Vercel 32 | run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | package-lock.json 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /.vscode/css_custom_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1.0, 3 | "atDirectives": [ 4 | { 5 | "name": "@tailwind", 6 | "description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.", 7 | "references": [ 8 | { 9 | "name": "Tailwind Documentation — Functions and directives", 10 | "url": "https://tailwindcss.com/docs/functions-and-directives#tailwind" 11 | } 12 | ] 13 | }, 14 | { 15 | "name": "@layer", 16 | "description": "Use the `@layer` directive to tell Tailwind which “bucket” a set of custom styles belong to. Valid layers are `base`, `components`, and `utilities`.", 17 | "references": [ 18 | { 19 | "name": "Tailwind Documentation — Functions and directives", 20 | "url": "https://tailwindcss.com/docs/functions-and-directives#layer" 21 | } 22 | ] 23 | }, 24 | { 25 | "name": "@apply", 26 | "description": "Use `@apply` to inline any existing utility classes into your own custom CSS.", 27 | "references": [ 28 | { 29 | "name": "Tailwind Documentation — Functions and directives", 30 | "url": "https://tailwindcss.com/docs/functions-and-directives#apply" 31 | } 32 | ] 33 | }, 34 | { 35 | "name": "@config", 36 | "description": "Use the `@config` directive to specify which config file Tailwind should use when compiling that CSS file. This is useful for projects that need to use different configuration files for different CSS entry points.", 37 | "references": [ 38 | { 39 | "name": "Tailwind Documentation — Functions and directives", 40 | "url": "https://tailwindcss.com/docs/functions-and-directives#config" 41 | } 42 | ] 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "bradlc.vscode-tailwindcss" 5 | ] 6 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.customData": [".vscode/css_custom_data.json"] 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AFORSHOW 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 | Aforshow is a programming event made for developers by developers to give them the opportunity to give a talk. 9 |
10 | You can demonstrate your talent by participating giving a talk. 11 | 12 | [Figma Design 13 | ](https://www.figma.com/design/AbwPBnjj7gh4SPmDVrPktQ/Comuafor-1?node-id=0-1&t=35wTzz3bkeE9Fsyv-1) · [Report a bug](https://github.com/Afordin/aforshow-2024/issues) 14 | 15 | 16 | 17 | ## Screenshots 18 | 19 | ![image](https://github.com/user-attachments/assets/c4db8507-b178-4ace-92a0-a00dde346a3a) 20 | ![image](https://github.com/user-attachments/assets/6d7e4535-64a4-48de-9a27-b32684cbe165) 21 | 22 | ## Getting started 23 | 24 | 1. clone or fork this repository 25 | 26 | ```sh 27 | git clone https://github.com/Afordin/aforshow-2024.git 28 | ``` 29 | 30 | 2. install dependencies 31 | 32 | ```bash 33 | pnpm install 34 | ``` 35 | 36 | 3. run the project 37 | ```bash 38 | pnpm run dev 39 | ``` 40 | 4. Open your browser and visit 41 | 42 | [http://localhost:3000 🌺](http://localhost:3000) 43 | 44 | ## Contributing to a project 45 | 46 | 1. **Cloning a fork:** 47 | Click on the [_fork_](https://github.com/Afordin/aforshow-2024/fork) button at the top right corner of the repository to create a copy of the project in your GitHub account. 48 | 2. **Clone the Repository:** Clone your forked repository to your local machine using the command (`git clone `) in your terminal. 49 | 3. **Set upstream branch:** To keep your forked repository updated with the original repository, use the command (`git remote add upstream `). 50 | 4. **Create branch:** (`git checkout -b feature/some-feature`). 51 | 5. **Stage the changed files:** by using git-add to incrementally "add" changes to the index before using the commit command (`git add `). 52 | 6. **Record changes to the repository:** Create a new commit containing the current contents of the index and the given log message describing the changes(`git commit -m 'Add: some feature'`). 53 | 7. **Submit your Contribution:** Upload your branch with the changes to forked repository on GitHub using (`git push origin feature/some-feature`). 54 | 8. **Generate a request:** To complete the process of creating your PR, simply hit [_pull request_](https://github.com/Afordin/aforshow-2024/pulls) 55 | 56 | ## Authors 57 | 58 | 59 | 60 | 61 | 62 | ### Contribution from Stackblitz 63 | 64 | If you want to contribute in a simpler way, you can start this project from _Stackblitz_ using your GitHub account: 65 | 66 | [![Open in Stackblitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/Afordin/aforshow-2024) 67 | 68 | **Thanks to all the contributors who have made this project possible!** 69 | 70 | [![Contributors](https://contrib.rocks/image?repo=Afordin/aforshow-2024)](https://github.com/Afordin/aforshow-2024/graphs/contributors) 71 | 72 | ## 🛠️ Stack 73 | 74 | [![Next][next-badge]][next-url] 75 | [![React][react-badge]][react-url] 76 | [![Tailwind][tailwind-badge]][tailwind-url] 77 | 78 | [contributors-shield]: https://img.shields.io/github/contributors/Afordin/aforshow-2024.svg?style=for-the-badge 79 | [contributors-url]: https://github.com/Afordin/aforshow-2024/graphs/contributors 80 | [forks-shield]: https://img.shields.io/github/forks/Afordin/aforshow-2024.svg?style=for-the-badge 81 | [forks-url]: https://github.com/Afordin/aforshow-2024/network/members 82 | [stars-shield]: https://img.shields.io/github/stars/Afordin/aforshow-2024.svg?style=for-the-badge 83 | [stars-url]: https://github.com/Afordin/aforshow-2024/stargazers 84 | [issues-shield]: https://img.shields.io/github/issues/Afordin/aforshow-2024.svg?style=for-the-badge 85 | [issues-url]: https://github.com/Afordin/aforshow-2024/issues 86 | [next-url]: https://nextjs.org/ 87 | [react-url]: https://reactjs.org/ 88 | [tailwind-url]: https://tailwindcss.com/ 89 | [next-badge]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=333 90 | [react-badge]: https://img.shields.io/badge/React-61DAFB?style=for-the-badge&logo=react&logoColor=333 91 | [tailwind-badge]: https://img.shields.io/badge/-Tailwind%20CSS-%231a202c?style=for-the-badge&logo=tailwind-css 92 | -------------------------------------------------------------------------------- /app/[userId]/opengraph-image.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | import { createClient } from '@/utils/supabase/server' 3 | import { ImageResponse } from 'next/og' 4 | 5 | 6 | export const size = { 7 | width: 1200, 8 | height: 580, 9 | } 10 | export const contentType = 'image/png' 11 | 12 | 13 | 14 | export default async function Image({ params: { userId } }) { 15 | const apiClient = createClient() 16 | const { data: user, error } = await apiClient.from('profiles').select('*').eq('id', userId).single() 17 | 18 | if(!user) return new ImageResponse( 19 |
20 | {`Aforshow`} 21 |
22 | , { ...size }) 23 | 24 | const imageUrl = `https://uuljbqkwvruhxomkmxaj.supabase.co/storage/v1/object/public/aforshow/public/${user.id}.png` 25 | 26 | return new ImageResponse( 27 |
28 | {`Aforshow`} 29 |
30 | , 31 | { ...size } 32 | ); 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/[userId]/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { Footer } from "@/components/Footer"; 3 | import Hero from "@/components/Hero"; 4 | import { Sponsors } from "@/components/Sponsors"; 5 | import { Ticket } from "@/components/Ticket"; 6 | import { WelcomeHero } from "@/components/WelcomeHero"; 7 | import { User } from "@/store/useUserStore"; 8 | import { apiClient } from "@/utils/supabase/client"; 9 | 10 | export default async function Page({ params: { userId } }) { 11 | const { data, error } = await apiClient.from('profiles').select('*').eq('id', userId).single(); 12 | const user = data as User 13 | 14 | return ( 15 |
16 |
17 |
18 |
19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 | ) 30 | } -------------------------------------------------------------------------------- /app/components/FAQ.tsx: -------------------------------------------------------------------------------- 1 | import { Summary } from "./common/Summary"; 2 | 3 | const questions = [ 4 | { 5 | title: "¿Donde puedo ver el evento?", 6 | description: ( 7 |

8 | El evento será totalmente gratuito y realizará en{" "} 9 | 10 | twitch.tv/afor_digital 11 | 12 |

13 | ), 14 | }, 15 | { 16 | title: "¿Tengo que estar suscrito al canal para verlo?", 17 | description: ( 18 |

19 | No, es totalmente gratuito. Pero si quieres dejar tu prime nadie te dirá 20 | nada. 21 |

22 | ), 23 | }, 24 | { 25 | title: "¿Puedo presentar mi charla?", 26 | description: ( 27 |

28 | Las inscripciones ya están cerradas, puedes ver las charlas 29 | seleccionadas en el apartado de horarios. 30 |

31 | ), 32 | }, 33 | { 34 | title: "¿Se podrán ver las charlas más tarde?", 35 | description: ( 36 |

37 | Sí, todas las charlas se podrán ver más tarde en{" "} 38 | 39 | afor lives 40 | 41 |

42 | ), 43 | }, 44 | ]; 45 | 46 | export const FAQ = () => { 47 | return ( 48 |
49 |

50 | Preguntas frecuentes 51 |

52 |
53 | {questions.map((question, index) => ( 54 | 55 | {question.description} 56 | 57 | ))} 58 |
59 |
60 | ); 61 | }; 62 | -------------------------------------------------------------------------------- /app/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "./utils"; 2 | import Image from "next/image"; 3 | import { Github, Twitch, Instagram, X, Discord } from "@/components/logos"; 4 | 5 | interface SocialIcon { 6 | icon: JSX.Element; 7 | url: string; 8 | } 9 | 10 | const socialIcons: SocialIcon[] = [ 11 | { 12 | icon: , 13 | url: "https://discord.com/invite/comuafor", 14 | }, 15 | { 16 | icon: , 17 | url: "https://www.twitch.tv/afor_digital", 18 | }, 19 | { 20 | icon: , 21 | url: "https://www.instagram.com/afor_digital", 22 | }, 23 | { 24 | icon: , 25 | url: "https://github.com/Afordin", 26 | }, 27 | { 28 | icon: , 29 | url: "https://twitter.com/afor_digital", 30 | }, 31 | ]; 32 | 33 | export const Footer = () => { 34 | const classes = { 35 | container: cn( 36 | "relative z-20 text-cWhite bg-gradient-to-r from-[#19101D] to-[#0D0D0E] py-5 w-full font-dmsans" 37 | ), 38 | innerContainer: cn( 39 | "max-w-7xl w-full mx-auto text-center mb-10 flex flex-col justify-between items-center" 40 | ), 41 | socialIcon: cn("inline-flex"), 42 | copyRight: cn("text-sm mt-5 inset-x-0 bottom-2 text-center px-2"), 43 | }; 44 | 45 | const renderSocialIcons = () => 46 | socialIcons.map((socialIcon, index) => ( 47 | 55 | {socialIcon.icon} 56 | 57 | )); 58 | 59 | const handleScrollToTop = (e: React.MouseEvent) => { 60 | e.preventDefault(); 61 | window.scrollTo({ top: 0, behavior: 'smooth' }); 62 | }; 63 | 64 | return ( 65 |
66 |
67 |
68 |
69 | {/* Social Sections */} 70 |
74 | 80 | Event Logo 87 | 88 | 89 |
90 |

91 | Más información del evento 92 |

93 | 99 |
100 |
101 | 102 | {/* Copyrights */} 103 |
104 | © 2024 Inspirado en  105 | 109 | Ana Rangel. 110 | 111 |  Desarrollado por  112 | 116 | Comuafor 117 | 118 |
119 |
120 |
121 |
122 | ); 123 | }; -------------------------------------------------------------------------------- /app/components/Hero.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { Nav } from "./Nav"; 5 | import { WelcomeHero } from "./WelcomeHero"; 6 | import { Sponsors } from "./Sponsors"; 7 | 8 | export default function Hero() { 9 | return ( 10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /app/components/Nav.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useRef, useState } from "react"; 4 | import { useRouter } from "next/navigation"; 5 | import Image from "next/image"; 6 | import { Menu, X } from "lucide-react"; 7 | import { useCursors } from "app/cursors-context"; 8 | import { Button } from "./ui/button"; 9 | import Logo from "app/components/icons/Logo"; 10 | import { cn } from "./utils"; 11 | import { useUserStore } from "@/store/useUserStore"; 12 | import { useAuth } from "@/hooks/useAuth"; 13 | import { useOnClickOutside } from "@/hooks/useOnClickOutside"; 14 | import { Link } from "./common/Link"; 15 | 16 | export const Nav = () => { 17 | const router = useRouter(); 18 | const { cursors, disabled, setDisabled } = useCursors(); 19 | const { signInWithDiscord, signOut } = useAuth(); 20 | const user = useUserStore((state) => state.user); 21 | 22 | const [isOpen, setIsOpen] = useState(false); 23 | const [showNavbar, setShowNavbar] = useState(true); 24 | const [lastScrollY, setLastScrollY] = useState(0); 25 | const [mobileMenuOpen, setMobileMenuOpen] = useState(false); 26 | const modalRef = useRef(null); 27 | const trigger = useOnClickOutside( 28 | modalRef, 29 | ({ isSameTrigger }) => setIsOpen(isSameTrigger) 30 | ); 31 | 32 | const slice = 3; 33 | const cursorsSlice = cursors.slice(-slice); 34 | 35 | const imgCircleClass = cn( 36 | "relative rounded-full h-8 w-8 ring-2 ring-white overflow-hidden group-hover:ring-[3px]" 37 | ); 38 | 39 | useEffect(() => { 40 | const handleScroll = () => { 41 | const currentScrollY = window.scrollY; 42 | 43 | if (currentScrollY > lastScrollY) { 44 | setShowNavbar(false); 45 | setMobileMenuOpen(false); 46 | } else { 47 | setShowNavbar(true); 48 | } 49 | 50 | setLastScrollY(currentScrollY); 51 | }; 52 | 53 | window.addEventListener("scroll", handleScroll); 54 | 55 | return () => { 56 | window.removeEventListener("scroll", handleScroll); 57 | }; 58 | }, [lastScrollY]); 59 | 60 | useEffect(() => { 61 | const handleResize = () => { 62 | setMobileMenuOpen(false); 63 | }; 64 | 65 | window.addEventListener("resize", handleResize); 66 | 67 | return () => { 68 | window.removeEventListener("resize", handleResize); 69 | }; 70 | }, []); 71 | 72 | return ( 73 | 215 | ); 216 | }; 217 | -------------------------------------------------------------------------------- /app/components/Schedule.tsx: -------------------------------------------------------------------------------- 1 | import { useTimezone } from "@/hooks/useTimezone"; 2 | import { Talk } from "./common/Talk"; 3 | import { Tag } from "./common/Tag"; 4 | import { Table } from "./common/Table"; 5 | 6 | export const Schedule = () => { 7 | const timezone = useTimezone(); 8 | return ( 9 |
10 |

11 | Horarios y charlas 12 |

13 | 14 | 15 | Zona horaria: {timezone} 16 | 17 | 18 |
19 | 26 | 33 | 38 | 45 | 52 |
57 | 64 | 65 | 66 | ); 67 | }; 68 | -------------------------------------------------------------------------------- /app/components/Sponsors.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Image from 'next/image'; 3 | 4 | interface SponsorImage { 5 | src: string; 6 | alt: string; 7 | } 8 | 9 | interface MarqueeProps { 10 | sponsors: SponsorImage[]; 11 | speed?: number; 12 | direction?: 'left' | 'right'; 13 | } 14 | 15 | const Marquee: React.FC = ({ 16 | sponsors, 17 | speed = 50, 18 | direction = 'left' 19 | }) => { 20 | return ( 21 |
22 |
26 | {[...sponsors, ...sponsors].map((sponsor, index) => ( 27 |
28 | 34 |
35 | ))} 36 |
37 |
38 | ); 39 | }; 40 | 41 | export const Sponsors: React.FC = () => { 42 | const sponsors: SponsorImage[] = [ 43 | { src: "/imgs/afordin-sponsor.png", alt: "afordin-logo-sponsor" }, 44 | { src: "/imgs/afordin-sponsor.png", alt: "afordin-logo-sponsor" }, 45 | { src: "/imgs/afordin-sponsor.png", alt: "afordin-logo-sponsor" }, 46 | { src: "/imgs/afordin-sponsor.png", alt: "afordin-logo-sponsor" }, 47 | { src: "/imgs/afordin-sponsor.png", alt: "afordin-logo-sponsor" }, 48 | ]; 49 | 50 | return ( 51 |
52 |

53 | Evento sponsorizado gracias a 54 |

55 | 56 | 57 | ); 58 | }; -------------------------------------------------------------------------------- /app/components/Ticket.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | import "atropos/css"; 3 | import { Atropos } from "atropos/react"; 4 | import { forwardRef } from "react"; 5 | 6 | interface TicketProps { 7 | avatar?: string; 8 | name?: string; 9 | number?: number; 10 | } 11 | 12 | export const sponsors = [ 13 | { 14 | name: "Afordin", 15 | logo: "/imgs/ticket/sponsor_1.webp", 16 | }, 17 | ]; 18 | export const Ticket = forwardRef(function Ticket( 19 | { name = "tpicj aforcita", number = 1, avatar = "/imgs/ticket/avatar.png" }, 20 | ref 21 | ) { 22 | return ( 23 |
24 | 29 |
33 |
34 |
35 | {`Avatar 42 |
43 |

15 ? "text-2xl" : "text-4xl" 48 | }`} 49 | > 50 | {name} 51 |

52 | 56 | 57 |

58 | twitch.tv/afor_digital 59 |

60 |
61 |
62 |
63 |
64 |
65 |

66 | SEPT 67 | 20 68 |

69 |
70 |
71 | {sponsors.map((sponsor) => ( 72 | {`Logo 78 | ))} 79 |
80 |
81 |
82 |

86 | #{number.toString().padStart(5, "0")} 87 |

88 |
89 |
90 | 91 |
92 | Hackafor 2024 Announcement Logo 98 |
99 |
100 |
101 |
102 | ); 103 | }); 104 | -------------------------------------------------------------------------------- /app/components/TicketDownload.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowDown } from "lucide-react"; 2 | import { Button } from "./ui/button"; 3 | import { X } from "@/components/logos"; 4 | import { useUserStore } from "@/store/useUserStore"; 5 | import { Ticket } from "./Ticket"; 6 | import { useRef } from "react"; 7 | import { toPng } from "html-to-image"; 8 | import { useAuth } from "@/hooks/useAuth"; 9 | import { uploadTicket } from "./utils/uploadTicket"; 10 | 11 | export const TicketDownload = () => { 12 | const { signInWithDiscord } = useAuth(); 13 | const user = useUserStore((state) => state.user); 14 | const ticketRef = useRef(null); 15 | 16 | const downloadTicket = async () => { 17 | if (ticketRef.current) { 18 | try { 19 | const dataUrl = await toPng(ticketRef.current); 20 | 21 | if (!dataUrl) { 22 | console.error("Could not capture image"); 23 | return; 24 | } 25 | 26 | const link = document.createElement("a"); 27 | link.download = "hackafor-ticket.png"; 28 | link.href = dataUrl; 29 | document.body.appendChild(link); 30 | link.click(); 31 | document.body.removeChild(link); 32 | } catch (error) { 33 | console.error("Could not capture image:", error); 34 | } 35 | } 36 | }; 37 | 38 | const shareTwitter = async () => { 39 | if (!user || !ticketRef.current) return; 40 | 41 | await uploadTicket(user.id, ticketRef.current); 42 | 43 | const urlstring = 44 | process.env.NEXT_PUBLIC_BASE_URL || "https://aforshow-2024.vercel.app"; 45 | const url = `${urlstring}/${user.id}/`; 46 | 47 | const message = 48 | "Este es tu ticket exclusivo para el Aforshow, habrá charlas, premios y sorteos. ¡Te esperamos! 🚀🎉"; 49 | const hashtags = ["aforshow"]; 50 | 51 | const encodedText = encodeURIComponent(message); 52 | const encodedUrl = encodeURIComponent(url); 53 | const hashtagsEncoded = encodeURIComponent(hashtags.join(",")); 54 | 55 | const twitterUrl = `https://twitter.com/intent/tweet?text=${encodedText}&url=${encodedUrl}&hashtags=${hashtagsEncoded}`; 56 | 57 | window.open(twitterUrl, "_blank"); 58 | }; 59 | 60 | return ( 61 |
65 |

66 | Descarga tu ticket y compártelo en redes sociales 67 |

68 | {user && ( 69 | <> 70 | 71 | 77 | 78 |
79 | 83 | 84 | 93 |
94 | 95 | )} 96 | 97 | {user === undefined && ( 98 |
99 | 100 |
101 | )} 102 | 103 | {user === null && ( 104 | <> 105 |
106 |
107 | 108 |
109 | 118 |
119 | 120 | )} 121 |
122 | ); 123 | }; 124 | -------------------------------------------------------------------------------- /app/components/WelcomeHero.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowUpRight, Ticket } from "lucide-react"; 2 | import { Button } from "./ui/button"; 3 | import { Countdown } from "./common/Countdown"; 4 | import { FC, PropsWithChildren } from "react"; 5 | import { cn } from "./utils"; 6 | 7 | interface Props extends PropsWithChildren<{}> { 8 | variant: "home" | "ticket"; 9 | } 10 | 11 | export const WelcomeHero: FC = ({ variant, children }) => { 12 | return ( 13 |
19 |
20 |
21 |

22 | ¡Gracias por estar presente en el {' '} 23 | 27 | Aforshow! 28 | 29 |

30 | {children} 31 |
32 |
33 |

34 | Puedes ver la repetición del evento aquí: 35 |

36 |
37 | 38 |
39 |
40 | 41 |
42 | {variant === "home" && ( 43 | 44 | 48 | 49 | )} 50 | {variant === "ticket" && ( 51 | 52 | 56 | 57 | )} 58 |
59 |
60 |
61 | ); 62 | }; 63 | -------------------------------------------------------------------------------- /app/components/common/Button.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes, ReactNode } from "react"; 2 | import { ButtonSize, HtmlType } from "../config/components/button"; 3 | import { cn } from "../config/utils/cn"; 4 | import { Variant } from "../config/constants/general"; 5 | 6 | const Sizes: Record = { 7 | [ButtonSize.xs]: "py-1 px-3 text-xs font-semibold h-6", 8 | [ButtonSize.sm]: "py-1.5 px-4 text-sm font-semibold h-8", 9 | [ButtonSize.base]: "py-2 px-8 text-sm font-semibold h-10", 10 | [ButtonSize.lg]: "py-3 px-6 text-base font-semibold h-12", 11 | [ButtonSize.xl]: "py-3 px-6 text-lg font-semibold h-14", 12 | }; 13 | 14 | const Variants: Record> = { 15 | [Variant.primary]: [ 16 | "rounded-full", 17 | "bg-gradient-to-rb from-primary-600 via-secondary-500 to-white text-cWhite", 18 | "hover:text-cBlack hover:to-100%", 19 | "buttonBgTransition", 20 | ], 21 | [Variant.secondary]: [ 22 | "text-cWhite", 23 | "bg-gradient-to-rb bg-gradient-to-rb from-black via-[#331e22] to-[#2c2130]", 24 | "from-100% hover:from-0%", 25 | "rounded-full", 26 | "buttonBgTransition", 27 | ], 28 | [Variant.ghost]: [ 29 | "bg-black rounded-full relative ", 30 | "before:absolute before:inset-0 before:-z-1", 31 | 'before:content-[""]', 32 | "before:bg-gradient-to-rb before:from-primary-600/10 before:to-secondary-500/10", 33 | "before:opacity-0 before:hover:opacity-100", 34 | "before:rounded-full", 35 | "before:transition-opacity before:duration-300", 36 | ], 37 | [Variant.twitch]: [ 38 | "rounded-full", 39 | "bg-gradient-to-rb from-[#4b2a88] via-[#7b4dda] to-[#2e195c] text-cWhite", 40 | "hover:to-100%", 41 | "buttonBgTransition", 42 | ], 43 | }; 44 | 45 | interface ButtonProps extends ButtonHTMLAttributes { 46 | /** 47 | * Text inside the button. 48 | */ 49 | children: ReactNode | Array | string; 50 | 51 | /** 52 | * Specify an optional className to be added to the component 53 | */ 54 | className?: string; 55 | 56 | /** 57 | * Optional size (e.g., 'sm', 'md'), affects padding/font size. 58 | */ 59 | size?: ButtonSize; 60 | 61 | /** 62 | * Style Variant (e.g., 'primary', 'secondary'), defines appearance. 63 | */ 64 | variant?: Variant; 65 | 66 | /** 67 | * If true, disables user interaction. 68 | */ 69 | isDisabled?: boolean; 70 | 71 | /** 72 | * If true, button width extends to 100%. 73 | */ 74 | isFullWidth?: boolean; 75 | 76 | /** 77 | * HTML button type attribute ('button', 'submit', etc.). 78 | */ 79 | htmlType?: HtmlType; 80 | 81 | /** 82 | * If true, adds a gradient border. 83 | */ 84 | hasBorder?: boolean; 85 | 86 | /** 87 | * Optional className to be added to the inner button element. 88 | */ 89 | innerClassName?: string; 90 | 91 | /** 92 | * Function to call on button click. 93 | */ 94 | onClick?: (event: React.MouseEvent) => void; 95 | } 96 | 97 | export const Button = ({ 98 | children, 99 | className, 100 | onClick = () => {}, 101 | size = ButtonSize.base, 102 | variant = Variant.primary, 103 | isDisabled = false, 104 | hasBorder = false, 105 | innerClassName, 106 | htmlType = HtmlType.button, 107 | isFullWidth = false, 108 | ...restOfProps 109 | }: ButtonProps) => { 110 | const classes = { 111 | container: cn( 112 | "relative z-1", 113 | "disabled:opacity-30 disabled:pointer-events-none", 114 | "transition-all duration-300", 115 | Sizes[size], 116 | ...(!hasBorder ? Variants[variant] : []), 117 | { 118 | "w-full": isFullWidth, 119 | "h-fit w-fit rounded-full bg-gradient-to-rb from-primary-600 to-secondary-500 p-px buttonBgTransitionReset": 120 | hasBorder, 121 | }, 122 | className 123 | ), 124 | innerContainer: cn( 125 | Variants[variant], 126 | Sizes[size], 127 | "inline-block transition-all duration-300 ease-in-out w-full h-full", 128 | innerClassName 129 | ), 130 | }; 131 | 132 | return ( 133 | 146 | ); 147 | }; 148 | -------------------------------------------------------------------------------- /app/components/common/Contributors.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from '../config/utils/cn'; 4 | import { type Contributor, useContributors } from './hooks'; 5 | 6 | export const Contributors = () => { 7 | const { contributors, isLoading } = useContributors(); 8 | const columns = contributors.length <= 10 ? contributors.length : 10 9 | const contributorsClasses = 'overflow-x-auto mt-5' 10 | 11 | const renderContributors = (contributorList: Contributor[]) => { 12 | return contributorList.map((contributor, index) => ( 13 | 19 | {isLoading ? ( 20 |
21 | ) : ( 22 | // eslint-disable-next-line @next/next/no-img-element 23 | {`Contribuidor: 24 | )} 25 | 26 | )); 27 | }; 28 | 29 | return ( 30 |
31 |

Contribuidores del desarrollo

32 |
33 | {contributors.length > 0 && ( 34 |
38 | {renderContributors(contributors)} 39 |
40 | )} 41 |
42 |
43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /app/components/common/Countdown.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useLayoutEffect, useState } from "react"; 2 | import { cn } from "../utils"; 3 | import { useTime } from "@/hooks/useTimezone"; 4 | 5 | interface CountDownProps { 6 | className?: string; 7 | } 8 | 9 | export const Countdown = ({ className }: CountDownProps) => { 10 | const time = [ 11 | { key: "days", label: "Días" }, 12 | { key: "hours", label: "Horas" }, 13 | { key: "minutes", label: "Minutos" }, 14 | { key: "seconds", label: "Segundos" }, 15 | ]; 16 | 17 | const startTime = useTime({ timestamp: 1726855200000}); 18 | const [timeLeft, setTimeLeft] = useState({ 19 | days: 0, 20 | hours: 0, 21 | minutes: 0, 22 | seconds: 0, 23 | }); 24 | 25 | const updateCountdown = () => { 26 | // Establecer la fecha y hora de finalización (20 Septiembre 2024, 20:00 en UTC+2) 27 | const countdownDate = new Date("2024-09-20T18:00:00Z"); // UTC+2 es dos horas menos que UTC 28 | 29 | const now = new Date(); 30 | 31 | const timeDifference = countdownDate.getTime() - now.getTime(); 32 | 33 | const days = Math.floor(timeDifference / (1000 * 60 * 60 * 24)); 34 | const hours = Math.floor((timeDifference / (1000 * 60 * 60)) % 24); 35 | const minutes = Math.floor((timeDifference / (1000 * 60)) % 60); 36 | const seconds = Math.floor((timeDifference / 1000) % 60); 37 | 38 | setTimeLeft({ days, hours, minutes, seconds }); 39 | }; 40 | 41 | useLayoutEffect(() => { 42 | updateCountdown(); 43 | }, []); 44 | 45 | useEffect(() => { 46 | const intervalId = setInterval(() => { 47 | updateCountdown(); 48 | }, 1000); 49 | 50 | return () => clearInterval(intervalId); // Limpiar el intervalo al desmontar el componente 51 | }, []); 52 | 53 | const formatNumber = (number: number) => { 54 | return number < 10 ? `0${number}` : number; 55 | }; 56 | 57 | return ( 58 |
59 |
60 | {time.map(({ key, label }, index) => ( 61 |
62 | 63 | {formatNumber(timeLeft[key as keyof typeof timeLeft])} 64 | 65 |

{label}

66 |
67 | ))} 68 |
69 |

20 de Septiembre de 2024 a las {startTime}

70 |
71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /app/components/common/Divider.tsx: -------------------------------------------------------------------------------- 1 | export const Divider = () => { 2 | return ( 3 |
4 | ); 5 | }; 6 | -------------------------------------------------------------------------------- /app/components/common/Link.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes, ReactNode } from "react"; 2 | import { ButtonSize, HtmlType } from "../config/components/button"; 3 | import { cn } from "../config/utils/cn"; 4 | import { Variant } from "../config/constants/general"; 5 | 6 | const Sizes: Record = { 7 | [ButtonSize.xs]: "py-1 px-3 text-xs font-semibold h-6", 8 | [ButtonSize.sm]: "py-1.5 px-4 text-sm font-semibold h-8", 9 | [ButtonSize.base]: "py-2 px-8 text-sm font-semibold h-10", 10 | [ButtonSize.lg]: "py-3 px-6 text-base font-semibold h-12", 11 | [ButtonSize.xl]: "py-3 px-6 text-lg font-semibold h-14", 12 | }; 13 | 14 | const Variants: Record> = { 15 | [Variant.primary]: [ 16 | "rounded-full", 17 | "bg-gradient-to-rb from-primary-600 via-secondary-500 to-white text-cWhite", 18 | "hover:text-cBlack hover:to-100%", 19 | "buttonBgTransition", 20 | ], 21 | [Variant.secondary]: [ 22 | "text-cWhite", 23 | "bg-gradient-to-rb bg-gradient-to-rb from-black via-[#331e22] to-[#2c2130]", 24 | "from-100% hover:from-0%", 25 | "rounded-full", 26 | "buttonBgTransition", 27 | ], 28 | [Variant.ghost]: [ 29 | "bg-black rounded-full relative ", 30 | "before:absolute before:inset-0 before:-z-1", 31 | 'before:content-[""]', 32 | "before:bg-gradient-to-rb before:from-primary-600/10 before:to-secondary-500/10", 33 | "before:opacity-0 before:hover:opacity-100", 34 | "before:rounded-full", 35 | "before:transition-opacity before:duration-300", 36 | ], 37 | [Variant.twitch]: [ 38 | "rounded-full", 39 | "bg-gradient-to-rb from-[#4b2a88] via-[#7b4dda] to-[#2e195c] text-cWhite", 40 | "hover:to-100%", 41 | "buttonBgTransition", 42 | ], 43 | }; 44 | 45 | interface LinkProps extends ButtonHTMLAttributes { 46 | /** 47 | * Text inside the button. 48 | */ 49 | children: ReactNode | Array | string; 50 | 51 | /** 52 | * Specify an optional className to be added to the component 53 | */ 54 | className?: string; 55 | 56 | /** 57 | * Optional size (e.g., 'sm', 'md'), affects padding/font size. 58 | */ 59 | size?: ButtonSize; 60 | 61 | /** 62 | * Style Variant (e.g., 'primary', 'secondary'), defines appearance. 63 | */ 64 | variant?: Variant; 65 | 66 | /** 67 | * If true, button width extends to 100%. 68 | */ 69 | isFullWidth?: boolean; 70 | 71 | /** 72 | * HTML button type attribute ('button', 'submit', etc.). 73 | */ 74 | htmlType?: HtmlType; 75 | 76 | /** 77 | * If true, adds a gradient border. 78 | */ 79 | hasBorder?: boolean; 80 | 81 | /** 82 | * Optional className to be added to the inner button element. 83 | */ 84 | innerClassName?: string; 85 | 86 | /** 87 | * Href 88 | */ 89 | href: string; 90 | } 91 | 92 | export const Link = ({ 93 | children, 94 | className, 95 | href, 96 | size = ButtonSize.base, 97 | variant = Variant.primary, 98 | hasBorder = false, 99 | innerClassName, 100 | htmlType = HtmlType.button, 101 | isFullWidth = false, 102 | ...restOfProps 103 | }: LinkProps) => { 104 | const classes = { 105 | container: cn( 106 | "relative z-1", 107 | "disabled:opacity-30 disabled:pointer-events-none", 108 | "transition-all duration-300", 109 | Sizes[size], 110 | ...(!hasBorder ? Variants[variant] : []), 111 | { 112 | "w-full": isFullWidth, 113 | "h-fit w-fit rounded-full bg-gradient-to-rb from-primary-600 to-secondary-500 p-px buttonBgTransitionReset": 114 | hasBorder, 115 | }, 116 | className 117 | ), 118 | innerContainer: cn( 119 | Variants[variant], 120 | Sizes[size], 121 | "inline-block transition-all duration-300 ease-in-out w-full h-full", 122 | innerClassName 123 | ), 124 | }; 125 | 126 | return ( 127 | 133 | {hasBorder ? ( 134 | {children} 135 | ) : ( 136 | children 137 | )} 138 | 139 | ); 140 | }; 141 | -------------------------------------------------------------------------------- /app/components/common/Summary.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from "react"; 2 | import { ChevronRight } from "lucide-react"; 3 | import { cn } from "../utils"; 4 | 5 | interface Props { 6 | className?: string; 7 | title: string; 8 | } 9 | 10 | export const Summary = ({ 11 | title, 12 | className, 13 | children, 14 | }: React.PropsWithChildren) => { 15 | const [isOpen, setIsOpen] = useState(false); 16 | const contentRef = useRef(null); 17 | 18 | const toggleOpen = () => { 19 | setIsOpen(!isOpen); 20 | }; 21 | 22 | return ( 23 |
24 |
28 | {title} 29 | 30 |
31 |
41 | {children} 42 |
43 |
44 | ); 45 | }; -------------------------------------------------------------------------------- /app/components/common/Table.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from "./Tag"; 2 | import { useTime } from "@/hooks/useTimezone"; 3 | 4 | type TableProps = { 5 | title: string; 6 | authors: string; 7 | timestamp: number; 8 | }; 9 | 10 | export const Table = ({ title, authors, timestamp }: TableProps) => { 11 | const datetime = useTime({ timestamp }); 12 | return ( 13 |
14 |
15 |

16 | {title} 17 |

18 |

19 | {authors} 20 |

21 | {datetime}h 22 |
23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /app/components/common/Tag.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { cn } from "../utils"; 3 | 4 | type TagProps = { 5 | children: ReactNode; 6 | className?: string; 7 | }; 8 | 9 | export const Tag = ({ children, className }: TagProps) => { 10 | return ( 11 |
12 | {children} 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /app/components/common/Talk.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { Tag } from "./Tag"; 3 | import { useTime } from "@/hooks/useTimezone"; 4 | 5 | type TalkProps = { 6 | title: string; 7 | author: string; 8 | timestamp: number; 9 | img: string; 10 | alt: string; 11 | }; 12 | 13 | export const Talk = ({ title, author, timestamp, img, alt }: TalkProps) => { 14 | 15 | const datetime = useTime({ timestamp }); 16 | return ( 17 |
18 | 25 |
26 |

27 | {title} 28 |

29 |

30 | {author} 31 |

32 |
33 | {datetime}h 34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /app/components/common/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useContributors' 2 | -------------------------------------------------------------------------------- /app/components/common/hooks/useContributors.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export interface Contributor { 4 | username: string; 5 | avatarUrl: string; 6 | } 7 | 8 | export const useContributors = () => { 9 | const [error, setError] = useState(null); 10 | const [isLoading, setIsLoading] = useState(true); 11 | const [contributors, setContributors] = useState>([]); 12 | 13 | useEffect(() => { 14 | const fetchContributors = async () => { 15 | try { 16 | const response = await fetch('https://api.github.com/repos/Afordin/aforshow-2024/contributors'); 17 | if (!response.ok) { 18 | throw new Error(`HTTP error! status: ${response.status}`); 19 | } 20 | const data = await response.json(); 21 | 22 | if (!Array.isArray(data) || data.some((item) => typeof item.login !== 'string' || typeof item.avatar_url !== 'string')) { 23 | throw new Error('Invalid data format'); 24 | } 25 | 26 | const contributorsData: Array = data 27 | .map(({ login, avatar_url, contributions }) => ({ 28 | username: login, 29 | avatarUrl: avatar_url, 30 | contributions 31 | })) 32 | .sort((a, b) => b.contributions - a.contributions); 33 | 34 | setContributors(contributorsData); 35 | } catch (error) { 36 | if (error instanceof Error) { 37 | setError(`Error fetching contributors: ${error.message}`); 38 | } else { 39 | setError('An unexpected error occurred'); 40 | } 41 | } finally { 42 | setIsLoading(false); 43 | } 44 | }; 45 | 46 | fetchContributors(); 47 | }, []); 48 | 49 | return { contributors, isLoading, error }; 50 | }; 51 | -------------------------------------------------------------------------------- /app/components/config/components/button.ts: -------------------------------------------------------------------------------- 1 | export enum ButtonSize { 2 | xs = "xs", 3 | sm = "sm", 4 | base = "base", 5 | lg = "lg", 6 | xl = "xl", 7 | } 8 | 9 | export enum HtmlType { 10 | button = "button", 11 | reset = "reset", 12 | submit = "submit", 13 | } 14 | -------------------------------------------------------------------------------- /app/components/config/constants/general.ts: -------------------------------------------------------------------------------- 1 | export enum Variant { 2 | primary = "primary", 3 | secondary = "secondary", 4 | ghost = "ghost", 5 | twitch = "twitch", 6 | } 7 | -------------------------------------------------------------------------------- /app/components/config/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | /** 5 | * Combines and merges Tailwind/UNO 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 | -------------------------------------------------------------------------------- /app/components/icons/Logo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const Logo = (props: SVGProps) => ( 5 | 31 | ); 32 | export default Logo; 33 | -------------------------------------------------------------------------------- /app/components/logos.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | export const Discord = (props: SVGProps) => ( 5 | 14 | 15 | 16 | ); 17 | 18 | export const Github = (props: SVGProps) => ( 19 | 28 | 29 | 30 | ); 31 | 32 | export const Instagram = (props: SVGProps) => ( 33 | 42 | 46 | 47 | ); 48 | 49 | export const X = (props: SVGProps) => ( 50 | 58 | 62 | 63 | ); 64 | 65 | export const Twitch = (props: SVGProps) => ( 66 | 77 | 78 | 79 | 80 | 84 | 88 | 89 | 90 | ); 91 | -------------------------------------------------------------------------------- /app/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { cva, type VariantProps } from "class-variance-authority"; 3 | import { cn } from "../utils"; 4 | 5 | const buttonVariants = cva( 6 | "inline-flex items-center justify-center whitespace-nowrap rounded-full text-sm font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 7 | { 8 | variants: { 9 | variant: { 10 | default: [ 11 | "bg-gradient-to-br from-caPrimary-500 via-caSecondary-500 to-white text-caWhite", 12 | "hover:text-caBlack", 13 | "buttonBgTransition", 14 | ], 15 | secondary: [ 16 | "bg-gradient-to-br from-black via-[#331e22] to-[#2c2130] text-white", 17 | "from-100% hover:from-0%", 18 | "buttonBgTransition", 19 | ], 20 | twitch: [ 21 | "bg-gradient-to-br from-[#4b2a88] via-[#7b4dda] to-[#2e195c] text-white", 22 | "buttonBgTransition", 23 | ] 24 | }, 25 | size: { 26 | default: "py-2 px-8 text-sm h-10", 27 | xs: "py-1 px-3 text-xs h-6", 28 | sm: "py-1.5 px-4 text-sm h-8", 29 | lg: "py-3 px-6 text-base h-12", 30 | xl: "py-3 px-6 text-lg h-14", 31 | }, 32 | }, 33 | defaultVariants: { 34 | variant: "default", 35 | size: "default", 36 | }, 37 | } 38 | ); 39 | 40 | /* Extiende el button html y las variantes de cva (size, variant) */ 41 | export interface ButtonProps 42 | extends React.ButtonHTMLAttributes, 43 | VariantProps {} 44 | 45 | const Button = ({ className, variant, size, ...props }: ButtonProps) => { 46 | return ( 47 |