├── .eslintrc.json ├── .github └── ISSUE_TEMPLATE │ ├── ---bug-report.md │ └── --feature-request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── components ├── fade-and-slide.tsx ├── fade.tsx └── nav.tsx ├── lib ├── constants.ts └── prisma.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── account.tsx ├── add-app.tsx ├── api │ ├── auth │ │ └── [...nextauth].ts │ ├── follow │ │ └── [...slug].ts │ ├── getToken.ts │ ├── internal-star │ │ └── [...slug].ts │ └── star │ │ └── [...slug].ts ├── auth-check │ └── [...slug].tsx ├── follow │ └── [...slug].tsx ├── index.tsx ├── no-animate.tsx └── star │ └── [...slug].tsx ├── pnpm-lock.yaml ├── prisma ├── migrations │ ├── 20220520193708_init_nextauth │ │ └── migration.sql │ └── migration_lock.toml └── schema.prisma ├── public ├── badges │ ├── badge-small.png │ ├── badge-small.svg │ ├── badge.png │ └── badge.svg ├── banner.png ├── banner.svg ├── favicon.ico ├── logo.png ├── profile.png ├── social.png ├── supabase-logo-icon.png └── vercel.svg ├── styles ├── Home.module.css └── globals.css └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug, needs-triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Sanity check** 11 | - [ ] After taking screenshots, I've reloaded the page 12 | - [ ] I've googled the error 13 | 14 | **Describe the bug** 15 | 16 | 17 | **To Reproduce** 18 | 19 | 20 | **Expected behavior** 21 | 22 | 23 | **Screenshots** 24 | 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "✨ Feature request" 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement, needs-triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | **Describe alternatives you've considered** 17 | 18 | 19 | **Additional context** 20 | 21 | -------------------------------------------------------------------------------- /.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 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | .env 33 | 34 | # vercel 35 | .vercel 36 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | jacob@omg.lol. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Startrack 2 | First off, well done for getting here, and thank you for taking the time to contribute to my project. You're amazing! 3 | 4 | #### Create an issue first 5 | Please use the feature request template, and tell us you're happy to handle this in a PR. This is usually fine, but wait for the `needs triage` label to be removed first. If it's a minor fix, go ahead and create the PR without creating an issue. 6 | 7 | #### Commit Convention 8 | Before you create a Pull Request, please check whether your commits comply with the commit conventions used in this repository. When you create a commit we kindly ask you to follow the [Gitmoji](https://gitmoji.dev/) convention. You can see the possible emojis on their website, and you can use the [VSCode extension](https://marketplace.visualstudio.com/items?itemName=seatonjiang.gitmoji-vscode) to easily add them to your commit message. Please don't use other emojis. Use this format: 9 | ``` 10 | [EMOJI] [COMMIT MESSAGE] 11 | ``` 12 | **Please only use one emoji per commit message. It should be placed at the start, and seperated from the text with a space.** 13 | 14 | --- 15 | #### Setting up a development environment 16 | - Fork the repo 17 | - Clone your fork 18 | - `cd` into the fork 19 | - Install dependencies by running `yarn` 20 | - Start nextjs in development by running `yarn dev` 21 | 22 | *You will not be able to use authentication, due to the requirement of API keys, but you can make UI or content changes* 23 | 24 | #### License 25 | By contributing your code to the `startrack` GitHub repository, you agree to license your contribution under the LGPL-2.1 license. 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 |

Startrack

6 | 7 |

8 | 9 |

10 | Startrack - Embed github stars into your next app. | Product Hunt 11 |

12 | 13 |
14 | 15 | ## Startrack 16 | **Startrack is launching on ProductHunt!** Please show your support with an upvote. 17 | 18 | 19 | [![Star with Startrack](https://img.shields.io/badge/startrack-star-yellow)](https://startrack.vercel.app/star/jacobhq/startrack) 20 | ![Vercel](https://vercelbadge.vercel.app/api/jacobhq/startrack) 21 | 22 | #### Startrack beta launch 23 | - Startrack was beta launched in a [10 min livestream](https://www.youtube.com/watch?v=_ymQsetuz_Q) by creator [@jhqcat](https://twitter.com/jhqcat) 24 | 25 | #### Public roadmap 26 | Startrack's public roadmap can be viewed [here](https://github.com/users/jacobhq/projects/1). To add to it, simply create an issue, and I'll put it on the roadmap. 27 | 28 | #### Adding your repo 29 | Use the [add app page](https://startrack.vercel.app/add-app) (no signup required) or add a link in the following format: 30 | ``` 31 | https://startrack.vercel.app/star/[OWNER]/[REPO] 32 | ``` 33 | Now, users will easily be able to star your repo in one click. Here's an example, using our shields badge and markdown: 34 | ```md 35 | [![Star with Startrack](https://img.shields.io/badge/startrack-star-yellow)](https://startrack.vercel.app/star/jacobhq/startrack) 36 | ``` 37 | 38 | #### License 39 | We have used the [`GNU Lesser General Public License v2.1`](https://choosealicense.com/licenses/lgpl-2.1/). 40 | 41 | | Permissions | Conditions | Limitations | 42 | |------------------|--------------------------------|------------------------| 43 | | - Commercial use | - Disclose source | - Liability | 44 | | - Distribution | - License and copyright notice | - Warranty | 45 | | - Modification | - Same license (library) | | 46 | | - Private use | - State changes | | 47 | 48 | --- 49 | 50 | #### Support me 51 | If you love Startrack and are able to financially support me, please [buy me a coffee](https://buymeacoffee.com/jem). Otherwise, a Twitter follow ([@jhqcat](https://twitter.com/intent/follow?screen_name=jhqcat)) or a YouTube subscribe ([Jacob Marshall](https://www.youtube.com/channel/UChXCa0OuD-HYr3QAESK434g)) would be nice. You can also [star this repo](https://startrack.vercel.app/star/jacobhq/startrack) or follow me on github ([@jacobhq](https://github.com/jacobhq)). Have a great day. 52 | -------------------------------------------------------------------------------- /components/fade-and-slide.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion" 2 | 3 | type FadeAndSlideProps = { 4 | children: any 5 | delay?: number 6 | } 7 | 8 | export function FadeAndSlide({ children, delay }: FadeAndSlideProps) { 9 | return ( 10 | 16 | {children} 17 | 18 | ) 19 | } -------------------------------------------------------------------------------- /components/fade.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion" 2 | 3 | type FadeAndSlideProps = { 4 | children: any 5 | delay?: number 6 | } 7 | 8 | export function Fade({ children, delay }: FadeAndSlideProps) { 9 | return ( 10 | 16 | {children} 17 | 18 | ) 19 | } -------------------------------------------------------------------------------- /components/nav.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Link from 'next/link' 3 | import router, { Router, useRouter, withRouter } from 'next/router' 4 | import { Center, Heading, VStack, HStack, Text, Button, ButtonGroup, Divider, Input, Textarea, IconButton, useColorMode } from '@chakra-ui/react' 5 | import { ArrowBackIcon, SunIcon, MoonIcon } from '@chakra-ui/icons' 6 | import { useState } from 'react' 7 | import { signIn } from "next-auth/react" 8 | import { Fade } from './fade' 9 | 10 | type NavProps = { 11 | animate?: boolean 12 | delay?: number 13 | } 14 | 15 | function Nav({ animate, delay }: NavProps) { 16 | const { colorMode, toggleColorMode } = useColorMode() 17 | let [loading, setLoading] = useState(false) 18 | const router = useRouter() 19 | 20 | delay ??= 0 21 | animate ??= false 22 | 23 | function Back() { 24 | setLoading(true) 25 | router.push('/') 26 | setLoading(false) 27 | } 28 | 29 | return ( 30 | <> 31 | {animate ? 32 | 33 | {router.pathname != ('/' || '/no-animate') ? 34 | } isLoading={loading} /> 35 | :
} 36 | {router.pathname === '/' ? 37 | 38 | 39 | 40 | :
} 41 | : } onClick={toggleColorMode} /> 42 |
43 |
: 44 | 45 | {router.pathname != ('/' || '/no-animate') ? 46 | } isLoading={loading} /> 47 | :
} 48 | {router.pathname === ('/' || '/no-animate') ? 49 | 50 | 51 | 52 | :
} 53 | : } onClick={toggleColorMode} /> 54 |
55 | } 56 | 57 | ) 58 | } 59 | 60 | export default Nav -------------------------------------------------------------------------------- /lib/constants.ts: -------------------------------------------------------------------------------- 1 | export declare const starEvent = "clientside-star-success" 2 | -------------------------------------------------------------------------------- /lib/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | 3 | const prisma = new PrismaClient() 4 | 5 | export default prisma -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | async rewrites() { 4 | return [ 5 | { 6 | source: "/bee.js", 7 | destination: "https://cdn.splitbee.io/sb.js", 8 | }, 9 | { 10 | source: "/_hive/:slug", 11 | destination: "https://hive.splitbee.io/:slug", 12 | }, 13 | ]; 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "startrack", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@auth0/nextjs-auth0": "^1.6.1", 13 | "@chakra-ui/icons": "^2.0.12", 14 | "@chakra-ui/react": "^2.4.1", 15 | "@emotion/react": "^11", 16 | "@emotion/styled": "^11", 17 | "@fontsource/inter": "^4.5.0", 18 | "@next-auth/prisma-adapter": "^1.0.3", 19 | "@octokit/rest": "^18.12.0", 20 | "@prisma/client": "^4.10.0", 21 | "@splitbee/web": "^0.3.0", 22 | "@vercel/analytics": "^0.1.5", 23 | "axios": "^0.24.0", 24 | "chakra-ui-steps": "^1.5.0", 25 | "framer-motion": "^6.3.10", 26 | "next": "^13.0.3", 27 | "next-auth": "^4.3.4", 28 | "react": "18.2.0", 29 | "react-dom": "18.2.0", 30 | "react-feather": "^2.0.9", 31 | "swr": "^1.0.1" 32 | }, 33 | "devDependencies": { 34 | "@types/react": "^18.0.25", 35 | "eslint": "7.32.0", 36 | "eslint-config-next": "11.1.2", 37 | "prisma": "^4.10.0", 38 | "typescript": "^4.6.4" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { ChakraProvider, extendTheme } from "@chakra-ui/react" 2 | import '../styles/globals.css' 3 | import { StepsStyleConfig as Steps } from 'chakra-ui-steps'; 4 | import { SessionProvider } from "next-auth/react" 5 | import { useEffect } from "react"; 6 | import splitbee from '@splitbee/web'; 7 | import { Analytics } from '@vercel/analytics/react'; 8 | 9 | const theme = extendTheme({ 10 | initialColorMode: "light", 11 | useSystemColorMode: false, 12 | components: { 13 | Steps, 14 | }, 15 | }); 16 | 17 | function MyApp({ Component, pageProps: { session, ...pageProps } }) { 18 | useEffect(() => { 19 | splitbee.init({ 20 | scriptUrl: "/bee.js", 21 | apiUrl: "/_hive", 22 | }) 23 | }, []) 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | ) 32 | } 33 | 34 | export default MyApp -------------------------------------------------------------------------------- /pages/account.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { withRouter } from "next/router"; 3 | import { Center } from '@chakra-ui/react' 4 | 5 | function Account({ router }) { 6 | const [session, setSession] = useState(null) 7 | 8 | return ( 9 |
10 | 11 |
12 | ) 13 | } 14 | 15 | export default withRouter(Account) -------------------------------------------------------------------------------- /pages/add-app.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import { Center, Heading, Text, Divider, Input, Box, Tab, TabList, TabPanel, TabPanels, Tabs, Container, Code } from '@chakra-ui/react' 3 | import Nav from '../components/nav' 4 | import { useState } from 'react' 5 | 6 | export default function Home() { 7 | let [ghUser, setUser] = useState('') 8 | let [ghRepo, setRepo] = useState('') 9 | 10 | return ( 11 |
12 | 13 | Signup | Startrack 14 | 15 | 16 | 17 | 18 |
19 |
55 |
56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /pages/api/auth/[...nextauth].ts: -------------------------------------------------------------------------------- 1 | // pages/api/auth/[...nextauth.js] 2 | import { PrismaAdapter } from "@next-auth/prisma-adapter"; 3 | import NextAuth from "next-auth" 4 | import GithubProvider from "next-auth/providers/github" 5 | import prisma from "lib/prisma"; 6 | 7 | export default NextAuth({ 8 | adapter: PrismaAdapter(prisma), 9 | // Configure one or more authentication providers 10 | debug: false, 11 | jwt: { 12 | secret: process.env.SECRET 13 | }, 14 | secret: process.env.SECRET, 15 | providers: [ 16 | GithubProvider({ 17 | clientId: process.env.GITHUB_ID, 18 | clientSecret: process.env.GITHUB_SECRET, 19 | authorization: { params: { scope: 'public_repo user:follow' } }, 20 | }), 21 | // ...add more providers here 22 | ], 23 | callbacks: { 24 | async jwt({ token, user, account, profile, isNewUser }) { 25 | if (account?.accessToken) { 26 | token.accessToken = account.accessToken; 27 | } 28 | return token; 29 | }, 30 | }, 31 | }) -------------------------------------------------------------------------------- /pages/api/follow/[...slug].ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from "@octokit/rest"; 2 | import axios from 'axios'; 3 | import { useState } from 'react'; 4 | import { getToken } from "next-auth/jwt" 5 | import prisma from "../../../lib/prisma"; 6 | import { getSession } from "next-auth/react"; 7 | 8 | const secret = process.env.SECRET 9 | 10 | export default async (req, res) => { 11 | const { slug } = req.query 12 | const session = await getSession({ req }) 13 | if (!session) return res.status(401).send("Need authentication") 14 | if (!slug[0] || req.method !== "POST") return res.status(400).send("Bad request") 15 | 16 | const user = await prisma.user.findFirst({ 17 | where: { 18 | email: session.user.email 19 | } 20 | }) 21 | 22 | const account = await prisma.account.findFirst({ 23 | where: { 24 | userId: user.id, 25 | provider: "github" 26 | } 27 | }) 28 | 29 | if (!account) return res.status(401).text("Need gh account") 30 | 31 | const octokit = new Octokit({ 32 | auth: account.access_token, 33 | }) 34 | 35 | await octokit.request('PUT /user/following/{username}', { 36 | username: slug[0], 37 | }) 38 | 39 | res.status(201).send("Done") 40 | }; -------------------------------------------------------------------------------- /pages/api/getToken.ts: -------------------------------------------------------------------------------- 1 | // pages/api/getToken.js 2 | // This is an example of how to read a JSON Web Token from an API route 3 | import { getToken } from "next-auth/jwt" 4 | 5 | const secret = process.env.SECRET 6 | 7 | export default async (req, res) => { 8 | const token = await getToken({ req, secret }) 9 | if (token) { 10 | // Signed in 11 | res.json(token) 12 | } else { 13 | // Not Signed in 14 | res.status(401) 15 | } 16 | res.end() 17 | } -------------------------------------------------------------------------------- /pages/api/internal-star/[...slug].ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from "@octokit/rest"; 2 | import axios from 'axios'; 3 | import { useState } from 'react'; 4 | import { getToken } from "next-auth/jwt" 5 | import prisma from "lib/prisma"; 6 | import { getSession } from "next-auth/react"; 7 | 8 | const secret = process.env.SECRET 9 | 10 | export default async (req, res) => { 11 | const { slug } = req.query 12 | const session = await getSession({ req }) 13 | console.log(session) 14 | if (!session) return res.status(401).send("Need authentication") 15 | if (!slug[0] || !slug[1]) return res.status(400).send("Bad request") 16 | 17 | const user = await prisma.user.findFirst({ 18 | where: { 19 | email: session.user.email 20 | } 21 | }) 22 | 23 | const account = await prisma.account.findFirst({ 24 | where: { 25 | userId: user.id, 26 | provider: "github" 27 | } 28 | }) 29 | 30 | if (!account) return res.status(401).text("Need gh account") 31 | 32 | const octokit = new Octokit({ 33 | auth: account.access_token, 34 | }) 35 | 36 | await octokit.request('GET /user/starred/{owner}/{repo}', { 37 | owner: slug[0], 38 | repo: slug[1] 39 | }).then((data) => { 40 | if (data.status === 204) { 41 | if (req.query.returnTo) { 42 | return res.redirect(req.query.returnTo) 43 | } 44 | 45 | return res.status(204).send("Already starred") 46 | } 47 | }).catch((err) => { 48 | console.log(err) 49 | }) 50 | 51 | await octokit.request('PUT /user/starred/{owner}/{repo}', { 52 | owner: slug[0], 53 | repo: slug[1] 54 | }) 55 | 56 | if (req.query.returnTo) { 57 | return res.redirect(req.query.returnTo) 58 | } 59 | 60 | res.status(201).send("Done") 61 | }; -------------------------------------------------------------------------------- /pages/api/star/[...slug].ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next' 2 | 3 | export default function handler(req: NextApiRequest, res: NextApiResponse) { 4 | let slug = (req.query.slug as String[]) || []; 5 | let url = req.query.returnTo 6 | 7 | res.redirect(`/auth-check/${slug.join('/')}?returnTo=${url}`) 8 | } -------------------------------------------------------------------------------- /pages/auth-check/[...slug].tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Center, 4 | Heading, 5 | Spinner, 6 | Text, 7 | VStack, 8 | } from "@chakra-ui/react"; 9 | import { signIn, useSession } from "next-auth/react"; 10 | import Head from "next/head"; 11 | import { useRouter } from "next/router"; 12 | import { useEffect, useState } from "react"; 13 | 14 | export default function AuthCheck() { 15 | const router = useRouter(); 16 | const { data: session, status } = useSession() 17 | let [redirect, setRedirect] = useState(true) 18 | let slug = (router.query.slug as String[]) || []; 19 | let url = router.query.returnTo as string 20 | 21 | useEffect(() => { 22 | setTimeout(() => { 23 | if (redirect) { 24 | if (status !== "unauthenticated") { 25 | router.push(`/api/internal-star/${slug.join('/')}?returnTo=${url}`) 26 | } else { 27 | signIn("github", { callbackUrl: `/auth-check/${slug.join('/')}?returnTo=${url}` }) 28 | } 29 | } 30 | }, 5000) 31 | }) 32 | 33 | return ( 34 | <> 35 | 36 | About to star {slug.join("/")} 37 | 38 |
39 | 40 | 41 | 42 | About to star {slug.join("/")} 43 | Continuing in 5 seconds 44 | 45 | 46 | 47 |
48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /pages/follow/[...slug].tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import { 3 | Modal, 4 | ModalOverlay, 5 | ModalContent, 6 | ModalHeader, 7 | ModalFooter, 8 | ModalBody, 9 | Link, 10 | Button, 11 | useDisclosure, 12 | Code, 13 | ButtonGroup, 14 | Box, 15 | Heading, 16 | Text, 17 | Skeleton, 18 | Alert, 19 | AlertIcon, 20 | AlertTitle, 21 | AlertDescription, 22 | useToast, 23 | Tooltip, 24 | Avatar, 25 | Flex, 26 | Image 27 | } from "@chakra-ui/react" 28 | import useSWR from 'swr' 29 | import { Octokit } from "@octokit/rest"; 30 | import axios from 'axios'; 31 | import { useEffect, useState } from 'react'; 32 | import { getCsrfToken, signIn, useSession } from 'next-auth/react'; 33 | import { CheckIcon } from '@chakra-ui/icons'; 34 | import Head from 'next/head'; 35 | 36 | const octokit = new Octokit(); 37 | // @ts-ignore 38 | const fetcher = (...args) => fetch(...args).then(res => res.json()) 39 | 40 | function UserCard(query) { 41 | const { data, error } = useSWR(query.query[0] ? 'https://api.github.com/users/' + query.query[0] : null, fetcher) 42 | const toast = useToast() 43 | 44 | if (data && data.message === 'Not Found') return 45 | 46 | Repo not found 47 | Please check your URL... 48 | 49 | 50 | if (error) return 51 | 52 | Failed to load repo card 53 | Try again later... 54 | 55 | if (!data) return 56 | 57 | repoName 58 | 59 | 60 | Lorem ipsum dolor sit amet, consectetur adipiscing elit 61 | 62 | 63 | Lorem ipsum dolor sit amet, consectetur adipiscing elit 64 | 65 | 66 | 67 | // render data 68 | return 69 | 78 | 86 | {data.name} 87 | {data.bio ? data.bio : 'No bio provided'} 88 | {data.followers} followers • {data.public_repos} public repos 89 | 90 | } 91 | 92 | async function follow(query, setLoading, setDone) { 93 | setLoading(true) 94 | const data = await axios.post('/api/follow/' + query[0]).then(() => { 95 | setDone(true) 96 | }).catch((err) => { 97 | console.log(err) 98 | }).finally(() => { 99 | setLoading(false) 100 | }) 101 | } 102 | 103 | const Comment = () => { 104 | const router = useRouter() 105 | const slug = router.query.slug as String[] || [] 106 | const { isOpen, onOpen, onClose } = useDisclosure({ 'defaultIsOpen': true }) 107 | let [loading, setLoading] = useState(false) 108 | let [done, setDone] = useState(false) 109 | const toast = useToast() 110 | const { data: session, status } = useSession() 111 | const { data, error } = useSWR(slug[0] ? 'https://api.github.com/users/' + slug[0] : null, fetcher) 112 | 113 | useEffect(() => { 114 | if (done === true) { 115 | toast({ 116 | status: "success", 117 | title: "Starred successfully" 118 | }) 119 | } 120 | }, [done]) 121 | 122 | return ( 123 | <> 124 | 125 | Follow {slug[0]} 126 | 127 | 128 | 129 | {status === "authenticated" && 130 | Follow {slug[0]} 131 | 132 | You are about to follow {slug.join('/')}. 133 |
134 |
135 | 136 |
137 | 138 | 139 | 140 | 141 | 144 | 145 | 146 |
} 147 | {status === "unauthenticated" && 148 | Unauthenticated 149 | 150 | To continue to follow {slug.join('/')}, please signin with GitHub. You'll only need to do this once. 151 |
152 |
153 | 154 |
155 | 156 | 157 | 158 | 159 | 162 | 163 | 164 |
} 165 | {status === "loading" && 166 | 167 | 168 | Follow {slug.join('/')} 169 | 170 | 171 | 172 | 173 | You are about to follow {slug.join('/')}. 174 | 175 |
176 |
177 | 178 | 179 | 180 |
181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 191 | 192 | 193 | 194 |
} 195 |
196 | 197 | ) 198 | } 199 | 200 | export default Comment -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Link from 'next/link' 3 | import { Center, Heading, VStack, HStack, Text, Button, ButtonGroup, Divider, Avatar, Spacer, Container } from '@chakra-ui/react' 4 | import { ArrowForwardIcon } from '@chakra-ui/icons' 5 | import Nav from 'components/nav' 6 | import { useState } from 'react' 7 | import { motion } from 'framer-motion'; 8 | import { FadeAndSlide } from 'components/fade-and-slide' 9 | import { Fade } from 'components/fade' 10 | 11 | export default function Home() { 12 | let [loading, setLoading] = useState(false) 13 | 14 | function startLoading() { 15 | setLoading(false) 16 | } 17 | 18 | return ( 19 |
20 | 21 | Embed github stars into your next app - Startrack 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 |
68 |
69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /pages/no-animate.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Link from 'next/link' 3 | import { Center, Heading, VStack, HStack, Text, Button, ButtonGroup, Divider, Avatar, Spacer, Container } from '@chakra-ui/react' 4 | import { ArrowForwardIcon } from '@chakra-ui/icons' 5 | import Nav from 'components/nav' 6 | import { useState } from 'react' 7 | import { motion } from 'framer-motion'; 8 | import { FadeAndSlide } from 'components/fade-and-slide' 9 | import { Fade } from 'components/fade' 10 | 11 | export default function Home() { 12 | let [loading, setLoading] = useState(false) 13 | 14 | function startLoading() { 15 | setLoading(false) 16 | } 17 | 18 | return ( 19 |
20 | 21 | Embed github stars into your next app - Startrack 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 |
62 |
63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /pages/star/[...slug].tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import { 3 | Modal, 4 | ModalOverlay, 5 | ModalContent, 6 | ModalHeader, 7 | ModalFooter, 8 | ModalBody, 9 | Link, 10 | Button, 11 | useDisclosure, 12 | Code, 13 | ButtonGroup, 14 | Box, 15 | Heading, 16 | Text, 17 | Skeleton, 18 | Alert, 19 | AlertIcon, 20 | AlertTitle, 21 | AlertDescription, 22 | useToast, 23 | } from "@chakra-ui/react" 24 | import useSWR from 'swr' 25 | import { Octokit } from "@octokit/rest"; 26 | import axios from 'axios'; 27 | import { useEffect, useState } from 'react'; 28 | import { signIn, useSession } from 'next-auth/react'; 29 | import { CheckIcon } from '@chakra-ui/icons'; 30 | import Head from 'next/head'; 31 | import splitbee from '@splitbee/web'; 32 | import { starEvent } from 'lib/constants'; 33 | 34 | const octokit = new Octokit(); 35 | // @ts-ignore 36 | const fetcher = (...args) => fetch(...args).then(res => res.json()) 37 | 38 | function Repo(query) { 39 | const { data, error } = useSWR(query.query[0] ? 'https://api.github.com/repos/' + query.query[0] + '/' + query.query[1] : null, fetcher) 40 | const toast = useToast() 41 | 42 | if (data && data.message === 'Not Found') return 43 | 44 | Repo not found 45 | Please check your URL... 46 | 47 | 48 | if (error) return 49 | 50 | Failed to load repo card 51 | Try again later... 52 | 53 | if (!data) return 54 | 55 | repoName 56 | 57 | 58 | Lorem ipsum dolor sit amet, consectetur adipiscing elit 59 | 60 | 61 | Lorem ipsum dolor sit amet, consectetur adipiscing elit 62 | 63 | 64 | 65 | // render data 66 | return 67 | {data.name} 68 | {data.description ? data.description : 'No description provided'} 69 | This repo was created by {data.owner.login} 70 | 71 | } 72 | 73 | async function star(query, setLoading, setDone) { 74 | setLoading(true) 75 | await axios.post('/api/internal-star/' + query[0] + '/' + query[1]).then(() => { 76 | setDone(true) 77 | splitbee.track("clientside-star-success", { 78 | repo: query[0] + '/' + query[1] 79 | }) 80 | }).catch((err) => { 81 | console.log(err) 82 | }).finally(() => { 83 | setLoading(false) 84 | }) 85 | } 86 | 87 | const Comment = () => { 88 | const router = useRouter() 89 | const slug = router.query.slug as String[] || [] 90 | const { isOpen, onOpen, onClose } = useDisclosure({ 'defaultIsOpen': true }) 91 | let [loading, setLoading] = useState(false) 92 | let [done, setDone] = useState(false) 93 | const toast = useToast() 94 | const { data: session, status } = useSession() 95 | const { data, error } = useSWR(slug[0] ? 'https://api.github.com/repos/' + slug[0] + '/' + slug[1] : null, fetcher) 96 | 97 | useEffect(() => { 98 | if (done === true) { 99 | toast({ 100 | status: "success", 101 | title: "Starred successfully" 102 | }) 103 | } 104 | }, [done]) 105 | 106 | return ( 107 | <> 108 | 109 | Star {slug.join('/')} 110 | 111 | 112 | 113 | {status === "authenticated" && 114 | Star {slug.join('/')} 115 | 116 | You are about to star {slug.join('/')}. 117 |
118 |
119 | 120 |
121 | 122 | 123 | 124 | 125 | 128 | 129 | 130 |
} 131 | {status === "unauthenticated" && 132 | Unauthenticated 133 | 134 | To continue to star {slug.join('/')}, please signin with GitHub. You'll only need to do this once. 135 |
136 |
137 | 138 |
139 | 140 | 141 | 142 | 143 | 146 | 147 | 148 |
} 149 | {status === "loading" && 150 | 151 | 152 | Star {slug.join('/')} 153 | 154 | 155 | 156 | 157 | You are about to star {slug.join('/')}. 158 | 159 |
160 |
161 | 162 | 163 | 164 |
165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 175 | 176 | 177 | 178 |
} 179 |
180 | 181 | ) 182 | } 183 | 184 | export default Comment -------------------------------------------------------------------------------- /prisma/migrations/20220520193708_init_nextauth/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Account" ( 3 | "id" TEXT NOT NULL, 4 | "userId" TEXT NOT NULL, 5 | "type" TEXT NOT NULL, 6 | "provider" TEXT NOT NULL, 7 | "providerAccountId" TEXT NOT NULL, 8 | "refresh_token" TEXT, 9 | "access_token" TEXT, 10 | "expires_at" INTEGER, 11 | "token_type" TEXT, 12 | "scope" TEXT, 13 | "id_token" TEXT, 14 | "session_state" TEXT, 15 | 16 | CONSTRAINT "Account_pkey" PRIMARY KEY ("id") 17 | ); 18 | 19 | -- CreateTable 20 | CREATE TABLE "Session" ( 21 | "id" TEXT NOT NULL, 22 | "sessionToken" TEXT NOT NULL, 23 | "userId" TEXT NOT NULL, 24 | "expires" TIMESTAMP(3) NOT NULL, 25 | 26 | CONSTRAINT "Session_pkey" PRIMARY KEY ("id") 27 | ); 28 | 29 | -- CreateTable 30 | CREATE TABLE "User" ( 31 | "id" TEXT NOT NULL, 32 | "name" TEXT, 33 | "email" TEXT, 34 | "emailVerified" TIMESTAMP(3), 35 | "image" TEXT, 36 | 37 | CONSTRAINT "User_pkey" PRIMARY KEY ("id") 38 | ); 39 | 40 | -- CreateTable 41 | CREATE TABLE "VerificationToken" ( 42 | "identifier" TEXT NOT NULL, 43 | "token" TEXT NOT NULL, 44 | "expires" TIMESTAMP(3) NOT NULL 45 | ); 46 | 47 | -- CreateIndex 48 | CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); 49 | 50 | -- CreateIndex 51 | CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); 52 | 53 | -- CreateIndex 54 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 55 | 56 | -- CreateIndex 57 | CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token"); 58 | 59 | -- CreateIndex 60 | CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token"); 61 | 62 | -- AddForeignKey 63 | ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 64 | 65 | -- AddForeignKey 66 | ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 67 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "postgresql" 10 | url = env("DB") 11 | shadowDatabaseUrl = env("DB_SHADOW") 12 | } 13 | 14 | model Account { 15 | id String @id @default(cuid()) 16 | userId String 17 | type String 18 | provider String 19 | providerAccountId String 20 | refresh_token String? @db.Text 21 | access_token String? @db.Text 22 | expires_at Int? 23 | token_type String? 24 | scope String? 25 | id_token String? @db.Text 26 | session_state String? 27 | 28 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 29 | 30 | @@unique([provider, providerAccountId]) 31 | } 32 | 33 | model Session { 34 | id String @id @default(cuid()) 35 | sessionToken String @unique 36 | userId String 37 | expires DateTime 38 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 39 | } 40 | 41 | model User { 42 | id String @id @default(cuid()) 43 | name String? 44 | email String? @unique 45 | emailVerified DateTime? 46 | image String? 47 | accounts Account[] 48 | sessions Session[] 49 | } 50 | 51 | model VerificationToken { 52 | identifier String 53 | token String @unique 54 | expires DateTime 55 | 56 | @@unique([identifier, token]) 57 | } -------------------------------------------------------------------------------- /public/badges/badge-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobhq/startrack/8f1635cebad0effdf96a0be92b987a5acafe9191/public/badges/badge-small.png -------------------------------------------------------------------------------- /public/badges/badge-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/badges/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobhq/startrack/8f1635cebad0effdf96a0be92b987a5acafe9191/public/badges/badge.png -------------------------------------------------------------------------------- /public/badges/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobhq/startrack/8f1635cebad0effdf96a0be92b987a5acafe9191/public/banner.png -------------------------------------------------------------------------------- /public/banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobhq/startrack/8f1635cebad0effdf96a0be92b987a5acafe9191/public/favicon.ico -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobhq/startrack/8f1635cebad0effdf96a0be92b987a5acafe9191/public/logo.png -------------------------------------------------------------------------------- /public/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobhq/startrack/8f1635cebad0effdf96a0be92b987a5acafe9191/public/profile.png -------------------------------------------------------------------------------- /public/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobhq/startrack/8f1635cebad0effdf96a0be92b987a5acafe9191/public/social.png -------------------------------------------------------------------------------- /public/supabase-logo-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobhq/startrack/8f1635cebad0effdf96a0be92b987a5acafe9191/public/supabase-logo-icon.png -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | padding: 0 0.5rem; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | height: 100vh; 9 | } 10 | 11 | .main { 12 | padding: 5rem 0; 13 | flex: 1; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | } 19 | 20 | .footer { 21 | width: 100%; 22 | height: 100px; 23 | border-top: 1px solid #eaeaea; 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | } 28 | 29 | .footer a { 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | flex-grow: 1; 34 | } 35 | 36 | .title a { 37 | color: #0070f3; 38 | text-decoration: none; 39 | } 40 | 41 | .title a:hover, 42 | .title a:focus, 43 | .title a:active { 44 | text-decoration: underline; 45 | } 46 | 47 | .title { 48 | margin: 0; 49 | line-height: 1.15; 50 | font-size: 4rem; 51 | } 52 | 53 | .title, 54 | .description { 55 | text-align: center; 56 | } 57 | 58 | .description { 59 | line-height: 1.5; 60 | font-size: 1.5rem; 61 | } 62 | 63 | .code { 64 | background: #fafafa; 65 | border-radius: 5px; 66 | padding: 0.75rem; 67 | font-size: 1.1rem; 68 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 69 | Bitstream Vera Sans Mono, Courier New, monospace; 70 | } 71 | 72 | .grid { 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | flex-wrap: wrap; 77 | max-width: 800px; 78 | margin-top: 3rem; 79 | } 80 | 81 | .card { 82 | margin: 1rem; 83 | padding: 1.5rem; 84 | text-align: left; 85 | color: inherit; 86 | text-decoration: none; 87 | border: 1px solid #eaeaea; 88 | border-radius: 10px; 89 | transition: color 0.15s ease, border-color 0.15s ease; 90 | width: 45%; 91 | } 92 | 93 | .card:hover, 94 | .card:focus, 95 | .card:active { 96 | color: #0070f3; 97 | border-color: #0070f3; 98 | } 99 | 100 | .card h2 { 101 | margin: 0 0 1rem 0; 102 | font-size: 1.5rem; 103 | } 104 | 105 | .card p { 106 | margin: 0; 107 | font-size: 1.25rem; 108 | line-height: 1.5; 109 | } 110 | 111 | .logo { 112 | height: 1em; 113 | margin-left: 0.5rem; 114 | } 115 | 116 | @media (max-width: 600px) { 117 | .grid { 118 | width: 100%; 119 | flex-direction: column; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --gap: 1rem; 3 | --big-gap: 4rem; 4 | --main-content: 45rem; 5 | } 6 | 7 | html { 8 | overflow-x: hidden; 9 | } 10 | 11 | .container { 12 | max-width: var(--main-content); 13 | margin: 0 auto; 14 | padding: 0 var(--gap); 15 | padding-top: var(--gap); 16 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true, 21 | "baseUrl": "./", 22 | "paths": { 23 | "lib": ["lib"], 24 | "components": ["components"], 25 | "styles": ["styles"] 26 | } 27 | }, 28 | "include": [ 29 | "next-env.d.ts", 30 | "**/*.ts", 31 | "**/*.tsx" 32 | ], 33 | "exclude": [ 34 | "node_modules" 35 | ] 36 | } 37 | --------------------------------------------------------------------------------