├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml └── workflows │ └── development.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── app ├── components │ ├── About.tsx │ ├── BackupForm.tsx │ ├── BookmarkDetails.tsx │ ├── Card.tsx │ ├── CategoryIcon.tsx │ ├── Feed.tsx │ ├── FlashMessage.tsx │ ├── IconLink.tsx │ ├── InputOption.tsx │ ├── Progress.tsx │ ├── ResourcesDetails.tsx │ ├── ResourcesList.tsx │ ├── SearchList.tsx │ ├── ShowMoreButton.tsx │ ├── SidebarNavigation.tsx │ ├── SuggestedResources.tsx │ └── SvgIcon.tsx ├── config.ts ├── entry.client.tsx ├── entry.server.tsx ├── helpers.ts ├── hooks.ts ├── icons │ ├── back.svg │ ├── badge-check.svg │ ├── bookmark.svg │ ├── box-open.svg │ ├── bullhorn.svg │ ├── calendar-alt.svg │ ├── chalkboard-teacher.svg │ ├── check-circle.svg │ ├── circle.svg │ ├── code.svg │ ├── collapse.svg │ ├── discord.svg │ ├── exclamation-circle.svg │ ├── expand.svg │ ├── fire-alt.svg │ ├── github.svg │ ├── hexagon.svg │ ├── history.svg │ ├── home.svg │ ├── info-circle.svg │ ├── layer-group.svg │ ├── link.svg │ ├── logo.svg │ ├── mail-bulk.svg │ ├── map-pin.svg │ ├── map-signs.svg │ ├── meetup.svg │ ├── menu.svg │ ├── pencil.svg │ ├── plus.svg │ ├── remix.svg │ ├── rss-square.svg │ ├── satellite-dish.svg │ ├── search.svg │ ├── square.svg │ ├── template.svg │ ├── times-circle.svg │ ├── times.svg │ ├── trending.svg │ ├── user.svg │ └── video.svg ├── layout.tsx ├── resources.tsx ├── root.tsx ├── routes │ ├── [rss.xml].tsx │ ├── [sitemap.xml].tsx │ ├── _layout.$list.tsx │ ├── _layout.admin.index.tsx │ ├── _layout.admin.pages.tsx │ ├── _layout.admin.resources.tsx │ ├── _layout.admin.statistics.tsx │ ├── _layout.admin.tsx │ ├── _layout.admin.users.$userId.backup.tsx │ ├── _layout.admin.users.tsx │ ├── _layout.index.tsx │ ├── _layout.resources.$resourceId.tsx │ ├── _layout.resources.index.tsx │ ├── _layout.resources.tsx │ ├── _layout.submit.tsx │ ├── _layout.tsx │ ├── auth.tsx │ ├── discover.$list.tsx │ ├── discover.tsx │ ├── login.tsx │ └── logout.tsx ├── scroll.tsx ├── search.ts └── types.ts ├── package-lock.json ├── package.json ├── playwright.config.ts ├── public ├── favicon.ico ├── favicon.svg └── robots.txt ├── remix.config.js ├── remix.env.d.ts ├── scripts ├── backup.mjs └── build.mjs ├── tailwind.config.js ├── tests ├── authentication.spec.ts ├── index.spec.ts ├── setup.ts ├── submission.spec.ts └── utils.ts ├── tsconfig.json ├── worker ├── adapter.ts ├── cache.ts ├── index.ts ├── logging.ts ├── scraping.ts ├── session.ts ├── store │ ├── PageStore.ts │ ├── ResourcesStore.ts │ ├── UserStore.ts │ └── index.ts ├── types.ts └── utils.ts └── wrangler.toml /.env.example: -------------------------------------------------------------------------------- 1 | # Example: Please rename this file to `.env` for local development 2 | 3 | # [Required] GitHub Authentication (OAuth Apps) 4 | # Please note that a real client credentials is needed if you wanna login locally (You can create one with your own GitHub account) 5 | GITHUB_CLIENT_ID=foobar 6 | GITHUB_CLIENT_SECRET=1234567890 7 | GITHUB_CALLBACK_URL=http://localhost:8787/auth 8 | SESSION_SECRETS=s3cr3ts 9 | 10 | # [Optional] GitHub token for scrapping GitHub Repository 11 | GITHUB_TOKEN= 12 | 13 | # [Optional] Google API Key for the Safe Browsing API 14 | GOOGLE_API_KEY= 15 | 16 | # [Optional] UserAgent used for scrapping webpage (Some requests might fail without a proper user-agent) 17 | USER_AGENT="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36" 18 | 19 | # [Optional] Sentry DSN for error logging, fallback to console log if missing 20 | SENTRY_DSN= 21 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /.cache 2 | /.github 3 | /.husky 4 | /build 5 | /public 6 | /node_modules 7 | /dist 8 | /app/styles/tailwind.css -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@remix-run", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: '🐛 Bug report' 2 | description: Create a report to help us improve 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thank you for reporting an issue :pray:. 8 | 9 | This issue tracker is for reporting bugs found in `remix-guide` (https://github.com/edmundhung/remix-guide). 10 | If you have a question about how to achieve something and are struggling, please post a question 11 | inside of `remix-guide` Discussions tab: https://github.com/edmundhung/remix-guide/discussions 12 | 13 | Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already: 14 | - `remix-guide` Issues tab: https://github.com/edmundhung/remix-guide/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc 15 | - `remix-guide` closed issues tab: https://github.com/edmundhung/remix-guide/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed 16 | - `remix-guide` Discussions tab: https://github.com/edmundhung/remix-guide/discussions 17 | 18 | The more information you fill in, the better the community can help you. 19 | - type: textarea 20 | id: description 21 | attributes: 22 | label: Describe the bug 23 | description: Provide a clear and concise description of the challenge you are running into. 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: steps 28 | attributes: 29 | label: Steps to Reproduce the Bug or Issue 30 | description: Describe the steps we have to take to reproduce the behavior. 31 | placeholder: | 32 | 1. Go to '...' 33 | 2. Click on '....' 34 | 3. Scroll down to '....' 35 | 4. See error 36 | validations: 37 | required: true 38 | - type: textarea 39 | id: expected 40 | attributes: 41 | label: Expected behavior 42 | description: Provide a clear and concise description of what you expected to happen. 43 | placeholder: | 44 | As a user, I expected ___ behavior but i am seeing ___ 45 | validations: 46 | required: true 47 | - type: textarea 48 | id: screenshots_or_videos 49 | attributes: 50 | label: Screenshots or Videos 51 | description: | 52 | If applicable, add screenshots or a video to help explain your problem. 53 | For more information on the supported file image/file types and the file size limits, please refer 54 | to the following link: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files 55 | placeholder: | 56 | You can drag your video or image files inside of this editor ↓ 57 | - type: textarea 58 | id: platform 59 | attributes: 60 | label: Platform 61 | value: | 62 | - OS: [e.g. macOS, Windows, Linux] 63 | - Browser: [e.g. Chrome, Safari, Firefox] 64 | - Version: [e.g. 91.1] 65 | validations: 66 | required: true 67 | - type: textarea 68 | id: additional 69 | attributes: 70 | label: Additional context 71 | description: Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Feature Requests & Questions 4 | url: https://github.com/edmundhung/remix-guide/discussions 5 | about: Please ask and answer questions here. 6 | - name: 💬 Remix Discord Channel 7 | url: https://rmx.as/discord 8 | about: Interact with other people using Remix 📀 9 | - name: 💬 New Updates (Twitter) 10 | url: https://twitter.com/remix_run 11 | about: Stay up to date with Remix news on twitter 12 | - name: 🍿 Remix YouTube Channel 13 | url: https://www.youtube.com/channel/UC_9cztXyAZCli9Cky6NWWwQ 14 | about: Are you a techlead or wanting to learn more about Remix in depth? Checkout the Remix YouTube Channel -------------------------------------------------------------------------------- /.github/workflows/development.yml: -------------------------------------------------------------------------------- 1 | name: 📝 Development Workflow 2 | on: 3 | - push 4 | jobs: 5 | test: 6 | name: 🔍 Testing 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: ⬇️ Checkout repo 10 | uses: actions/checkout@v3 11 | - name: ⎔ Setup node 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: 16 15 | - name: 📥 Download deps 16 | uses: bahmutov/npm-install@v1 17 | - name: 🎭 Install Playwright 18 | run: npx playwright install --with-deps 19 | - name: 📦 Build the worker 20 | run: npm run build 21 | - name: 💣 Run some tests 22 | run: npm run test 23 | 24 | lint: 25 | name: ⬣ Linting 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: ⬇️ Checkout repo 29 | uses: actions/checkout@v3 30 | - name: ⎔ Setup node 31 | uses: actions/setup-node@v3 32 | with: 33 | node-version: 16 34 | - name: 📥 Download deps 35 | uses: bahmutov/npm-install@v1 36 | - name: ✨ Code format check 37 | run: npx prettier --check . 38 | - name: ✅ Code linting 39 | run: npx eslint . --ext .js,.mjs,.ts,.tsx 40 | 41 | deploy: 42 | name: 🛳 Deploying 43 | needs: [test, lint] 44 | if: github.ref == 'refs/heads/main' 45 | runs-on: ubuntu-latest 46 | steps: 47 | - name: 🛑 Cancel Previous Runs 48 | uses: styfle/cancel-workflow-action@0.9.1 49 | - name: ⬇️ Checkout repo 50 | uses: actions/checkout@v3 51 | - name: ⎔ Setup node 52 | uses: actions/setup-node@v3 53 | with: 54 | node-version: 16 55 | - name: 📥 Download deps 56 | uses: bahmutov/npm-install@v1 57 | - name: 🔥 Publish 58 | run: npx wrangler publish 59 | env: 60 | CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Remix 4 | /.cache 5 | /build 6 | /public/build 7 | 8 | # Custom Build 9 | /dist 10 | 11 | # Tailwind 12 | /app/styles/tailwind.css 13 | 14 | # Playwright 15 | /test-results 16 | 17 | # Miniflare 18 | /.mf 19 | /.env 20 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /.cache 2 | /.github 3 | /.husky 4 | /build 5 | /public 6 | /node_modules 7 | /dist 8 | /app/styles/tailwind.css -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | # Prettier Configuration 2 | # All supported options are listed here: https://prettier.io/docs/en/options.html 3 | tabWidth: 2 4 | useTabs: true 5 | semi: true 6 | singleQuote: true 7 | trailingComma: all 8 | bracketSpacing: true 9 | bracketSameLine: false 10 | arrowParens: always 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Edmund Hung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # remix-guide 2 | 3 | Remix Guide is a platform for sharing everything about Remix. It is built with [Remix](https://remix.run) and is deployed to [Cloudflare Workers](https://workers.cloudflare.com/). All contents are saved in Durable Objects and cached with Worker KV. 4 | 5 | ## Roadmap 6 | 7 | The idea behind Remix Guide is to make resources from the community more accessible and making the process as automatic as possible at the same time. Future plans include: 8 | 9 | - Make and share your own list 10 | - Better search ranking / recommendations 11 | - Support searching by language and version (remix and packages) 12 | 13 | ## Submission 14 | 15 | As of v1.0, new resources can only be submitted online. There are some basic validations in place to ensure the submitted URL is relevant to remix. However, in order to minimize the risk of spamming or phishing, additional measures have to be added before it is generally available. 16 | 17 | If you would like to submit new content, feel free to share them on the `#showcase` channel of the [Remix Discord](https://discord.com/invite/remix). We are watching the channel and will publish anything shared there as soon as possible. 18 | 19 | ## Development 20 | 21 | To run Remix Guide locally, please make a `.env` file based on `.env.example` first. You can then start the app in development mode using: 22 | 23 | ```sh 24 | npm run dev 25 | ``` 26 | 27 | This should kickstart a dev server and open the webpage on the browser automatically. 28 | 29 | ## Node Version 30 | 31 | Please make sure the node version is **>= 16.7**. If you are using `nvm`, simply run: 32 | 33 | ```sh 34 | nvm use 35 | ``` 36 | 37 | This allows [miniflare](https://github.com/cloudflare/miniflare) to serve a development environment as close to the actual worker runtime as possibile. 38 | -------------------------------------------------------------------------------- /app/components/About.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactElement } from 'react'; 2 | import { useMatches } from '@remix-run/react'; 3 | import SvgIcon from '~/components/SvgIcon'; 4 | import logo from '~/icons/logo.svg'; 5 | 6 | function About(): ReactElement { 7 | const matches = useMatches(); 8 | const { version } = matches[0]?.data ?? {}; 9 | 10 | return ( 11 |
12 |
13 | 14 |

Remix Guide

15 |
Sharing everything about Remix
16 |
17 | {version ? ( 18 |
19 | Version{' '} 20 | 24 | {version} 25 | 26 |
27 | ) : null} 28 |
29 | ); 30 | } 31 | 32 | export default About; 33 | -------------------------------------------------------------------------------- /app/components/BackupForm.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactElement } from 'react'; 2 | import { Form } from '@remix-run/react'; 3 | 4 | interface BackupFormProps { 5 | data: any; 6 | } 7 | 8 | function BackupForm({ data }: BackupFormProps): ReactElement { 9 | const dataSortedByKeys = !data 10 | ? null 11 | : Object.keys(data) 12 | .sort() 13 | .reduce( 14 | (result, key) => Object.assign(result, { [key]: data[key] }), 15 | {} as Record, 16 | ); 17 | 18 | const handleConfirm = ( 19 | e: React.MouseEvent, 20 | ) => { 21 | if (!confirm('Are you sure?')) { 22 | e.preventDefault(); 23 | } 24 | }; 25 | 26 | return ( 27 |
28 |