{"id":17513411,"url":"https://github.com/ericting2000/gublight","last_synced_at":"2025-04-23T13:17:19.833Z","repository":{"id":125010141,"uuid":"467927536","full_name":"ericting2000/Gublight","owner":"ericting2000","description":"A website helps you explore fantasies on Github.","archived":false,"fork":false,"pushed_at":"2022-04-27T04:02:47.000Z","size":826,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-23T13:17:11.530Z","etag":null,"topics":["github","nextjs","react","tailwindcss","typescript"],"latest_commit_sha":null,"homepage":"https://gublight-ralqxvcfp-ericting2000.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ericting2000.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-09T12:57:46.000Z","updated_at":"2023-04-05T12:17:10.000Z","dependencies_parsed_at":null,"dependency_job_id":"55a0f617-20ab-47f1-84fb-48a305e9f763","html_url":"https://github.com/ericting2000/Gublight","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericting2000%2FGublight","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericting2000%2FGublight/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericting2000%2FGublight/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericting2000%2FGublight/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ericting2000","download_url":"https://codeload.github.com/ericting2000/Gublight/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250439295,"owners_count":21430824,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["github","nextjs","react","tailwindcss","typescript"],"created_at":"2024-10-20T06:26:17.229Z","updated_at":"2025-04-23T13:17:19.827Z","avatar_url":"https://github.com/ericting2000.png","language":"TypeScript","readme":"\u003cdiv id=\"top\"\u003e\u003c/div\u003e\n\n[![Contributors][contributors-shield]][contributors-url]\n[![Forks][forks-shield]][forks-url]\n[![Stargazers][stars-shield]][stars-url]\n[![Issues][issues-shield]][issues-url]\n[![MIT License][license-shield]][license-url]\n\n\n\u003c!-- PROJECT LOGO --\u003e\n\u003cbr /\u003e\n\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://github.com/ericting2000/Gublight\"\u003e\n    \u003cimg src=\"./public/icon/GubLight.png\" alt=\"Logo\" width=\"80\" height=\"80\"\u003e\n  \u003c/a\u003e\n\n\u003ch1 align=\"center\"\u003eGublight\u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\n  \u003ch3\u003e 📱 Gublight = 🌏 Github + 🔍 Spotlight \u003c/h3\u003e\n    \u003ca href=\"https://gublight.com\"\u003eView Demo\u003c/a\u003e\n    ·\n    \u003ca href=\"https://github.com/ericting2000/Gublight/issues\"\u003eReport Bug\u003c/a\u003e\n    ·\n    \u003ca href=\"https://github.com/ericting2000/Gublight/issues\"\u003eRequest Feature\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n\u003c!-- TABLE OF CONTENTS --\u003e\n\u003cdetails\u003e\n  \u003csummary\u003eOverview\u003c/summary\u003e\n  \u003col\u003e\n    \u003cli\u003e\n      \u003ca href=\"#about-the-project\"\u003eAbout The Project\u003c/a\u003e\n      \u003cul\u003e\n        \u003cli\u003e\u003ca href=\"#built-with\"\u003eBuilt With\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ca href=\"#using\"\u003eUsing\u003c/a\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n      \u003ca href=\"#getting-started\"\u003eGetting Started\u003c/a\u003e\n      \u003cul\u003e\n        \u003cli\u003e\u003ca href=\"#prerequisites\"\u003ePrerequisites\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ca href=\"#installation\"\u003eInstallation\u003c/a\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#usage\"\u003eUsage\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#architecture-design-\u0026-explanation\"\u003eArchitecture Design \u0026 Explanation\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#a-little-more-detail\"\u003eA Little More Detail\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#license\"\u003eLicense\u003c/a\u003e\u003c/li\u003e\n    \n  \u003c/ol\u003e\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n\u003c!-- ABOUT THE PROJECT --\u003e\n\n## 📌 About The Project\n\n![Imgur](https://i.imgur.com/Xf81Hvo.png)\n\nGublight is a [**React**](https://reactjs.org) app paid homage to [**Github**](https://github.com) in design that could：\n\n- browse users/organizations on [**GitHub**](https://github.com)\n- list user's repositories with `Infinite Scroll` from [**GitHub REST API**](https://docs.github.com/en/rest)\n- get repository's detail from [**GitHub REST API**](https://docs.github.com/en/rest)\n- searching user with `Fuzzy Search` feature.\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#top\"\u003eback to top\u003c/a\u003e)\u003c/p\u003e\n\n### Built With\n\n- [Next.js](https://nextjs.org/)\n- [React.js](https://reactjs.org/)\n- [Tailwind CSS](https://tailwindcss.com)\n\n### Using\n\n- [Typescript](https://www.typescriptlang.org)\n- [Sass](https://sass-lang.com)\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#top\"\u003eback to top\u003c/a\u003e)\u003c/p\u003e\n\n\u003c!-- GETTING STARTED --\u003e\n\n## 📌 Getting Started\n\n\u003e This is how you set up your project locally. To preview online, please visit [here](https://www.gublight.com).\n\n### Prerequisites\n\n- yarn\n  ```sh\n  npm install yarn -g\n  ```\n\n### Installation\n\n1. Clone the repo\n   ```sh\n   git clone https://github.com/ericting2000/Gublight.git\n   ```\n2. Install Yarn packages\n\n- yarn\n  ```sh\n  yarn install\n  ```\n\n3. Start the development server\n\n- yarn\n  ```sh\n  yarn dev\n  ```\n\n4. Enjoy Gublight at http://localhost:3000\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#top\"\u003eback to top\u003c/a\u003e)\u003c/p\u003e\n\n\u003c!-- USAGE EXAMPLES --\u003e\n\n## 📌 Usage\n\n- Insert the Github user you would like to look for. Options that match your request may show up under the input box.\n- Click 'Explore' or options showed below to navigate to the designated user page. You're welcomed to use the 'Enter' key to submit.\n- In the user's page, all public repository will be listed. Click whichever repo you would like to explore for more information about it.\n- Fill free to re-search for any user at anytime as you wish !\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#top\"\u003eback to top\u003c/a\u003e)\u003c/p\u003e\n\n\u003c!-- Architecture Design \u0026 Explanation --\u003e\n\n## 📌 Architecture Design \u0026 Explanation\n\n### Routing Architecture Diagram\n\n```bash\npages\n  ├── _app.tsx\n  ├── index.tsx\t# Landing Page\n  ├── 400.tsx # Custom 404 page\n  ├── 500.tsx # Custom 500 page\n  └── users\n        └── [username]\n                ├── repos\n                │     └── [reponame].tsx # detailed information of specific repository\n                └── index.tsx\t# List of repositories\n```\n\n- Dynamic Routing\n  - `/users/{username}/repos` for specific user's repo list.\n  - `/users/{username}/repos/{repo}` for specific repo info of some user.\n- Custom Error Page (404 Not Found, 500 Internal Server Error)\n  - Showed when the route is not exist or encounter internal error.\n\n### Pages\n\n#### Landing Page\n\n![Imgur](https://i.imgur.com/eH80TPu.png)\n\n- `Fuzzy Search`\n  - Feature is accessible across all app.\n  - With Custom Hook _useFuzzySearch()_, users will be able to implement fuzzy search using the input data they gave to generate suggestions.\n\n#### Repository List\n\n![Imgur](https://i.imgur.com/6ilY5lf.png)\n\n- `Infinite Scroll`\n  - Infinite Scroll(Lazy Loading) is essential to this kind of \"Large Data requested\" app, as it optimizes the performance of both API call procedure and render process.\n  - With Infinite Scroll, app only sends a request when the content(data) is about to use. Also, less things for initial render improve the performance and decrease the waiting time which provides a better user experience.\n- `How I Implement` ?\n  - _useCallback_ + _useRef()_ + _IntersectionObserver API_ + _useFetchRepoList_ custom hook.\n- `Core Concept`\n\n  - When ever the last repository is showed on the screen, we send anthoer request to the API.\n  - Below is the example code.\n\n    ```ts\n    const { limit, empty, loading, repos, error, hasMore } = useFetchRepo(\n      user,\n      page\n    ); // Fetch or not depends on the value of state \"user\" or \"page\"\n    const observer = useRef\u003cIntersectionObserver | null\u003e(null);\n    const lastRepo = useCallback(\n      (node) =\u003e {\n        if (loading) {\n          return; // If is in loading state, return. If it didn't return, fetch function will constantly being triggered due to the observer detection.\n        }\n        if (observer.current) {\n          observer.current.disconnect(); // Disconnect the current observer if is existed.(Which is connected to the previous round's last repo, or \"Null\" in the first round)\n        }\n        observer.current = new IntersectionObserver((entries) =\u003e {\n          if (entries[0].isIntersecting \u0026\u0026 hasMore) {\n            setPage((prevPage: number) =\u003e {\n              return prevPage + 1; // Entries[0] will be the last repo in current round, which is the one we want to observe. If it exists and it's on the screen, renew the page and triggered fetch function.\n            });\n          }\n        });\n        if (node) {\n          observer.current.observe(node); // Reconnect observer to the current round's last repo.\n        }\n      },\n      [loading, hasMore]\n    );\n    ```\n\n    ```tsx\n    {\n      repos.map((repo: RepoData, index: number) =\u003e {\n        if (repos.length === index + 1) {\n          return (\n            // We only want to observe the last repo in every round. Please notice the \"ref\" property.\n            \u003cdiv ref={lastRepo} key={repo.name}\u003e\n              # Code of render repo.\n            \u003c/div\u003e\n          );\n        } else {\n          return (\n            \u003cdiv key={repo.name} className={styles.repo}\u003e\n              # Code of render repo.\n            \u003c/div\u003e\n          );\n        }\n      });\n    }\n    ```\n\n  - For demo, i log a message each time when the fetch funtion is triggerd.\n    ![Imgur](https://i.imgur.com/cTK1KHO.gif)\n\n#### Repository Information\n\n![Imgur](https://i.imgur.com/WOPzbek.png)\n\n- Shows the information of the designated repository.\n\n### Components\n\n#### Header\n\n![Imgur](https://i.imgur.com/HpO0VtB.png)\n\n- `Header.tsx`\n  - Used all across the app (except for the landing page only), `Header.tsx` includes the _Search Box_ for users to explore Github.\n  - While displayed in mobile mode, the _Search Box_ will folded into the header. (It is still accessible at anytime by clicking the magnifier icon.)\n\n![Imgur](https://i.imgur.com/tBZHrVF.png)\n\n- `LandingHeader.tsx`\n  - Used only for landing page. No _Search Box_ included.\n\n#### Footer\n\n![Imgur](https://i.imgur.com/Gc6iO8K.png)\n\n- `Footer.tsx`\n  - Giving a Link for navigation to [Gublight's](https;//www.gublight.com) Github repository.\n\n### Custom Hooks\n\n- `useFetchRepoList.ts`\n  - Used for fetching all public repositories that the user has.\n  - cooperate with the **_IntersectionObserver_** WebAPI to decide whether to fetch for new data or not.\n- `useFuzzySearch.ts`\n  - Used for `Fuzzy Search` feature.\n\n\u003c!-- A Liitle More Detail --\u003e\n\n### States Within The Code\n\n- Error Handling\n  - _limit_ for rate limit indication.\n  - _error_ for fetching error indication.\n  - _empty_ for empty users/repo list/repo info indication.\n- Loading\n  - _loading_ for fetching execution.\n\n### Other Stuffs\n\n- Custom Error Page\n  - `404.tsx` for _404 Not Found_\n  - `500.tsx` for _500 Internal Server Error_\n\n## 📌 A Little More Detail\n\n### Rate Limit\n\n- Github restricts API request to prevent DDoS attack and cyberterrorism. For unauthenticated requests, the rate limit allows for up to 60 requests per hour. Since Gublight is a public repository, we're not allowed to submit personal access token for authentication to increse the limit rate.\n- When you encountered such circumstances, some of the functions may thus be temporarily restricted.\n\n### Responsive Web Design\n\n- With responsive design, Gublight is perfect to use in any of the devices.\n  \u003cfigure class=\"third\"\u003e\n    \u003cimg src=\"https://i.imgur.com/smKi9uL.png\" style=\"width:200px\"\u003e\u003cimg src=\"https://imgur.com/4hi813z.jpg \"style=\"width:200px\"\u003e\n  \u003c/figure\u003e\n  \n\n- Toggle the search input by clicking the magnifier icon!\n  \u003cfigure class=\"third\"\u003e\n    \u003cimg src=\"https://i.imgur.com/6yoqjns.png\" style=\"width:200px\"/\u003e\n  \u003c/figure\u003e\n  \n### Fuzzy Search Debounce\n\n- When executing `Fuzzy Search`, **Debounce** is necessary to prevent performance issues. Most of the time, chances are the user already have a clue of their search goal. As a result, calling API immediately as soon as the user insert something is not efficent and a wise solution. Also, **Debounce** largely decrease the probability of hitting the \u003ca href=\"#rate-limit\"\u003eRate Limit\u003c/a\u003e metioned earlier.\n\n  ```ts\n  // In useFuzzySearch()\n\n  const timeout = useRef\u003cNodeJS.Timeout | null\u003e(null);\n\n    useEffect(() =\u003e {\n      setLoading(true);\n\n      function getUserList(queryString: string) {\n        clearTimeout(timeout.current as NodeJS.Timeout);\n\n        timeout.current = setTimeout(async () =\u003e {\n          // Fetching data code\n        }, 600);\n      }\n  ```\n\n### SEO\n\n- SEO(Search Engine Optimization) makes [Gublight](https;//www.gublight.com) being found from anywhere at anytime on anydevice all around the world more easily !\n\n  ```ts\n  \u003cHead\u003e\n    \u003ctitle\u003eGubLight: Explore the software fantasy\u003c/title\u003e\n    \u003cmeta\n      name=\"description\"\n      content=\"Explore the software fantasy. Search the Github Repo as you desire, find the next masterpiece.\"\n    /\u003e\n    \u003cmeta property=\"og:image\" content=\"https://i.imgur.com/Xf81Hvo.png\" /\u003e\n    \u003cmeta name=\"twitter:card\" content=\"summary_large_image\" /\u003e\n    \u003cmeta property=\"og:type\" content=\"website\" /\u003e\n    \u003cmeta property=\"og:url\" content=\"https://gublight.com\" /\u003e\n    \u003cFavicon /\u003e\n  \u003c/Head\u003e\n  ```\n\n  \u003e Adding `\u003cHead\u003e` seciton to include meta tags and information of Gublight.\n\n  \u003c!-- LICENSE--\u003e\n\n## 📌 License\n\nGublight is distributed under the MIT License. See `LICENSE` for more information.\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#top\"\u003eback to top\u003c/a\u003e)\u003c/p\u003e\n\n\u003c!-- MARKDOWN LINKS \u0026 IMAGES --\u003e\n\u003c!-- https://www.markdownguide.org/basic-syntax/#reference-style-links --\u003e\n\n[contributors-shield]: https://img.shields.io/github/contributors/ericting2000/Gublight.svg?style=for-the-badge\n[contributors-url]: https://github.com/ericting2000/Gublight/graphs/contributors\n[forks-shield]: https://img.shields.io/github/forks/ericting2000/Gublight.svg?style=for-the-badge\n[forks-url]: https://github.com/ericting2000/Gublight/network/members\n[stars-shield]: https://img.shields.io/github/stars/ericting2000/Gublight.svg?style=for-the-badge\n[stars-url]: https://github.com/ericting2000/Gublight/stargazers\n[issues-shield]: https://img.shields.io/github/issues/ericting2000/Gublight.svg?style=for-the-badge\n[issues-url]: https://github.com/ericting2000/Gublight/issues\n[license-shield]: https://img.shields.io/github/license/ericting2000/Gublight.svg?style=for-the-badge\n[license-url]: https://github.com/ericting2000/Gublight/blob/master/LICENSE\n[product-screenshot]: images/screenshot.png\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericting2000%2Fgublight","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fericting2000%2Fgublight","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericting2000%2Fgublight/lists"}