&*j;=u%25ay-%>3@81tGe^_z*C7pb9y*Ed^H3t$BIKH2o+olp#$q;)_ zfpjCb_^VFg5fU~K)nf*d*r@BCC>UZ!0&b?AGk_jTPXaSnCuW110wjHPPe^9R^;jo3 zwvzTl)C`Zl5}O2}3lec=hZ*$JnkW#7enKKc)(pM${_$9Hc=Sr_A9Biwe*Y=T?~1CK z6eZ9uPICjy-sMGbZl$yQmpB&`ouS8v{58__t0$JP%i3R&%QR3ianbZqDs<2#5FdN@n5bCn^ZtH992~5k(eA|8|@G9u`wdn7bnpg|@{m z^d6Y`*$Zf2Xr&|g%sai#5}Syvv(>Jnx&EM7-|Jr7!M~zdAyjt*xl;OLhvW-a%H1m0 z*x5*nb=R5u><7lyVpN AR?q@1U59 zO+)QW wL8t zyip?u_nI+K$uh{ y)~}qj?(w0&=SE^8`_WMM zTybjG=999h38Yes7}-4*LJ7H)UE8{mE(6;8voE+TYY%33A>S6`G_95^5QHNTo_;Ao ztIQIZ_}49%{8|=O;isBZ?=7kfdF8_@azfoTd+hEJKWE!)$)N%HIe2cplaK`ry#=pV z0q{9w-`i0h@!R8K3GC{ivt{70IWG`EP |(1g7i_Q<>aEAT{5( yD z=!O?kq61VegV+st@XCw475j6vS)_z@efuqQgHQR1T4;|-#OLZNQJPV4k$AX1Uk8Lm z{N*b*ia=I+MB}kWpupJ~>!C@xEN#Wa7V+7{m4j8c?)ChV=D?o~sjT?0C_AQ 7B-vxqX30s0I_`2$in86#`mAsT-w?j{&AL@B3$;P z31G4(lV|b}uSD CIrjk+M1R!X7s 4Aabn<)zpgT}#gE|mIvV38^ODy@<&yflpCwS#fRf9ZX3lPV_?8@C5)A;T zqmouFLFk;qIs4rA=hh=GL~sCFsXHsqO6_y~*AFt93 9UYVBSx1s(=Kb&5;j7cSowdE;7()CC2|-i9Zz+_BIw8#ll~-tyH?F3{%`QCsYa*b#s*9iCc`1P1oC26?`g<9))EJ3%xz+O!B3 zZ7$j~To)C@PquR>a1+Dh>-a%IvH_Y7^ys|4o?E%3`I&ADXfC8++hAdZfzIT#%C+Jz z1lU~K_vAm0m8Qk}K$F>| >RPK%<1SI0(G+8q~H zAsjezyP+u!Se4q3GW)`h`NPSRlMoBjCzNPesWJwVTY!o@G8=(6I%4XHGaSiS3MEBK zhgGFv6Jc>L$4jVE!I?TQuwvz_%CyO!bLh94nqK11C2W$*aa2ueGopG8DnBICVUORP zgytv#)49fVXDaR$SukloYC3u7#5H)}1K21=?DKj^U)8G;MS)&Op)g^zR2($<>C*zW z;X7`hLxiIO#J`ANdyAOJle4V%ppa*(+0i3w;8i*BA_;u8gOO6)MY`ueq7stBMJTB; z-a0R>hT*}>z|Gg}@^zDL1MrH+2hsR8 zHc}*9IvuQC^Ju)^#Y{fOr(96rQNPNhxc;mH@W*m206>Lo<*SaaH?~8zg&f&%YiOEG zGiz?*CP>Bci}!WiS=zj#K5I}>DtpregpP_tfZtPa(N<%vo^#WCQ5BTv0vr%Z{)0q+ z)RbfHktUm|lg&U3YM%lMUM(f u}i#kjX9h>GYctkx9Mt_8{@s%!K_EI zScgwy6%_fR?CG JQtmgNAj^h9B#zma MDWgH55pGuY1Gv7D z;8Psm(vEPiwn#MgJYu4Ty9D|h!?Rj0ddE|&L3S{IP%H4^N!m`60ZwZw^;eg4sk6K{ ziA^`Sbl_4~f&Oo%n;8Ye(tiAdlZKI!Z=|j$5hS|D$bDJ}p{gh$KN&JZYLUjv4h{NY zBJ>X9z !xfDGY z+oh_Z&_e#Q(-}>ssZfm=j$D&4W4FNy&-kAO1~#3Im;F)Nwe{(*75(p=P^VI?X 0GFakfh+X-px4a%Uw@fSbmp9hM1_~R>?Z8+ ziy|e9>8V*`OP}4x5JjdWp}7eX;lVxp5qS} 0YZek;SNmm7tEeSF*-dI)6U-A%m6YvCgM(}_=k#a6o^%-K4{`B1+}O4x zztDT%hVb;v#?j`lTvlFQ3aV#zkX=7 ;YFLS$uIzb0E3lozs5`Xy zi~vF+%{z9uLjKvKPhP%x5f ~7-Gj+%5N`%^=yk*Qn{`> z;xj&ROY6g`iy2a@{O)V(jk&8#hHACVDXey5a+KDod_Z&}kHM}xt7}Md@pil{2x7E~ zL$k^d2@Ec2XskjrN+IILw;#7((abu;OJii&v3?60x>d_Ma(onIPtcVnX@ELF0aL?T zSmWiL3(dOFkt!x=1O!_0n(cAzZW+3nHJ{2S>tgSK?~cF ha^y(l@-Mr2W$%MN{#af8J;V*>hdq!gx=d0h$T7l}>91Wh07)9CTX zh2_ZdQCyFOQ)l(}gft0UZ G`Sh2`x-w`5vC2UD}lZs*5 zG76$akzn}Xi))L3oGJ75#pcN=cX3!=57$Ha=hQ2^lwdyU#a}4JJOz6ddR%zae%#4& za)bFj)z=YQela(F#Y|Q#dp}PJghITwXouVaMq$BM?K%cXn9^Y@g43$=O)F&ZlOUom zJiad#dea;-eywBA@e&D6Pdso1?2^(pXiN91?jvcaUyYoKUmvl5G9e$W!okWe*@a<^ z8cQQ6cNSf+UPDx%?_G4a IiybZHHagF{ ;IcD(dPO!#=u zWfqLcPc^+7Uu#l(B pxft{*4lv#*u7X9AOzDO z1D9?^jIo}?%iz(_dwLa{ex#T}76ZfN_Z-hwpus9y+4xaUu9cX}&P{XrZVWE{1^0yw zO;YhLEW!pJcbCt3L8~a7>jsaN{V3>tz6_7`&pi%GxZ=V3?3K^ U+*ryLSb)8^IblJ0 zSRLNDvIxt)S}g30?s_3NX>F?NKIGrG_zB9@Z>uSW3k2es_H2kU;Rnn%j5qP)!XHKE zPB2mHP~tLCg4K_vH$xv`HbRsJwbZMUV(t=ez;Ec(vyHH)FbfLg`c61I$W_uBB>i^r z&{_P;369-&>23R%qNIULe=1~T$(DA`ev*EWZ6j(B$(te}x1WvmIll21zvygkS%vwG zzkR6Z#RKA2!z!C%M!O>!=Gr0(J0FP=-MN=5t-Ir)of50y10W}j`GtRCsXBakrKtG& zazmITDJMA0C51&BnLY)SY9r)NVTMs);1<=oosS9g31l{4ztjD3#+2H7u_|66b|_*O z;Qk6nalpqdHOjx|K&vUS_6ITgGll;TdaN*ta=M_YtyC)I9Tmr~VaPrH2q b6sd~=AcIxV+%z{E&0@y=DPArw zdV7z(G1hBx7hd{>(cr43^WF%4Y@PXZ?wPpj{OQ#tvc$pABJbvPGvdR`cAtHn)cSEV zrpu}1tJwQ3y!mSmH*uz*x0o|CS<^w%&KJzsj~DU0cLQUxk5B!hWE>aBkjJle8z~;s z-!A=($+}Jq_BTK5^B!`R>!MulZN)F=iXXeUd0w5lUsE5VP*H*oCy( ;?S$p*TVvTxwAeWFB$jHyb0593)$zqalVlDX=GcCN1gU0 zlgU)I$LcXZ8Oyc2TZYTPu@-;7<4YYB-``Qa;IDcvydIA$%kHhJKV^m*- zxcvU4viy &Kr5GVM{IT>WRywKQ9;>SEiQD*NqplK-KK4YR`p0@JW)n_{TU3bt0 zim%;(m1=#v2}zTps=?fU5w^(*y)xT%1vtQH&}50ZF!9YxW=&7*W($2kgKyz1mUgfs zfV<*XVVIFnohW=|j+@Kfo!#liQR^x>2yQdrG;2o8WZR+XzU_nG=Ed2rK?ntA;K5B{ z>M8+*A4 !Jm^Bg}aW?R?6;@QG@uQ8&oJ{hFixcfEnJ4QH?A4>P=q29oDGW;L;= z9-a0;g%c`C+Ai!UmK$NC*4#;Jp<1=TioL=t^YM)<<%u#hnnfSS`nq63QKGO1L8RzX z@MFDq s1z ztYmxDl@LU)5acvHk)~Z`RW7=aJ_nGD!mOSYD>5Odjn@TK#LY{jf?+piB5AM-CAoT_ z?S-*q7}wyLJzK>N%eMPuFgN)Q_otKP;aqy=D5f! 7<=n(lNkYRXVpkB{TAYLYg{|(jtRqYmg$xH zjmq ?B(RE4 zQx^~Pt}gxC2~l=K$$-sYy_r$CO(d=+b3H1MB*y_5g6WLaWTXn+TKQ|hNY^>Mp6k*$ zwkovomhu776vQATqT4blf~g;TY(MWCrf^^yfWJvSAB$p5l;jm@o#=!lqw+Lqfq>X= z$6~kxfm7`3q4zUEB;u4qa#BdJxO!;xGm)wwuisj{0y2x{R(IGMrsIzDY9LW>m!Y`= z04sx3IjnYvL<4JqxQ8f7qYd0s2Ig%`ytYPEMKI)s(LD}D@EY>x`VFtqvnADNBdeao zC96X+MxnwKmjpg{U&gP3HE}1=s!lv&D{6(g_lzyF3A`7Jn*&d_kL<;dAFx!UZ>hB8 z5A*%LsAn;VLp>3${0>M?PSQ)9s3}|h2e?TG4_F{}{Cs>#3Q*t$(CUc}M)I}8cPF6% z=+h(Kh^8)}gj(0}#e7O^FQ6`~fd1#8#!}LMuo3A0bN`o}PYsm!Y}sdOz$+Tegc=qT z8x`PH$7lvnhJp{kHWb22l;@7B7|4yL4UOOVM0MP_>P%S1Lnid)+k9{+3D+JFa#Pyf zhVc#&df87APl4W9X)F3pGS>@etfl=_E5tBcVoOfrD4hmVeTY-cj((pkn%n@EgN{0f zwb_^Rk0I#i ZuHK!l*lN`ceJn(sI{$Fq6nN& zE<-=0_2WN}m+*ivmIOxB@#~Q-cZ>l136w{#TIJe478`KE7@=a{>SzPHsKLzYAyBQO zAtuuF$-JSDy_S@6GW0MOE~R)b;+0f%_NMrW(+V#c_d&U8Z9+ec4=HmOHw?gdjF(Lu zzra 83M_BoO-1b3;9`%&DHfuUY)6YDV21P$C!Rc?mv&{lx#f8oc6?0?x zK08{WP65?#>(vPfA-c=MCY| %*1_<3D4NX zeVTi-JGl2uP_2@0F{G({pxQOXt_d{g_CV6b?jNpfUG9;8yle-^4KHRvZs-_2siata zt+d_T@U$&t*xaD22(fH(W1r$Mo?3dc%Tncm=C6{V9y{v&VT#^1L04vDrLM9qBoZ4@ z6DBN#m57hX7$C(=#$Y5$bJmwA$T8jKD8+6A!-IJwA{WOfs%s}yxUw^?MRZjF$n_KN z6`_bGXcmE#5e4Ym)aQJ)xg3Pg0@k`iGuHe?f(5LtuzSq=nS^5z>vqU0EuZ&75V%Z{ zYyhRLN^)$c6Ds{f7*FBpE;n5iglx5PkHfWrj3`x^j^t z7ntuV`g!9Xg#^3!x)l*}IW=(Tz3>Y5l4uGaB&lz{GDjm2D5S$CExLT`I1#n^lBH7Y zDgpMag@`iETKAI=p<5E#LTkw zVR@=yY|uBVI1HG|8h+d;G-qfuj}-ZR6fN>EfCCW z9~wRQoAPEa#aO?3h?x{YvV*d+NtPkf&4V0k4|L=uj!U{L+oLa(z#&iuhJr3-PjO3R z5s?=nn_5^*^Rawr>>Nr@K(jwkB#JK-=+HqwfdO<+P5byeim)wvqGlP-P|~Nse8=XF zz`?RYB|D6SwS}C+YQv+;}k6$-%D(@+t14BL@vM z2q%q?f6D-A5s$_WY3{^G0F131bbh|g!}#BKw=HQ7mx;Dzg4Z*bTLQSfo{ed{4}NZW zfrRm^Ca$rlE{Ue~uYv>R9{3s mwATcdM_6+yWIO z*ZRH~uXE@#p$XTbCt5j7j2=86e{9>HIB6xDzV+vAo&B?KUiMP|ttOElepnl%|DPqL b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 00000000..080d6c77 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 00000000..01b0f9a1 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * diff --git a/scripts/build.js b/scripts/build.js new file mode 100644 index 00000000..4fa3737d --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,211 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'production'; +process.env.NODE_ENV = 'production'; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + + +const path = require('path'); +const chalk = require('react-dev-utils/chalk'); +const fs = require('fs-extra'); +const webpack = require('webpack'); +const configFactory = require('../config/webpack.config'); +const paths = require('../config/paths'); +const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); +const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); +const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); +const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); +const printBuildError = require('react-dev-utils/printBuildError'); + +const measureFileSizesBeforeBuild = + FileSizeReporter.measureFileSizesBeforeBuild; +const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; +const useYarn = fs.existsSync(paths.yarnLockFile); + +// These sizes are pretty large. We'll warn for bundles exceeding them. +const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; +const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; + +const isInteractive = process.stdout.isTTY; + +// Warn and crash if required files are missing +if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { + process.exit(1); +} + +// Generate configuration +const config = configFactory('production'); + +// We require that you explicitly set browsers and do not fall back to +// browserslist defaults. +const { checkBrowsers } = require('react-dev-utils/browsersHelper'); +checkBrowsers(paths.appPath, isInteractive) + .then(() => { + // First, read the current file sizes in build directory. + // This lets us display how much they changed later. + return measureFileSizesBeforeBuild(paths.appBuild); + }) + .then(previousFileSizes => { + // Remove all content but keep the directory so that + // if you're in it, you don't end up in Trash + fs.emptyDirSync(paths.appBuild); + // Merge with the public folder + copyPublicFolder(); + // Start the webpack build + return build(previousFileSizes); + }) + .then( + ({ stats, previousFileSizes, warnings }) => { + if (warnings.length) { + console.log(chalk.yellow('Compiled with warnings.\n')); + console.log(warnings.join('\n\n')); + console.log( + '\nSearch for the ' + + chalk.underline(chalk.yellow('keywords')) + + ' to learn more about each warning.' + ); + console.log( + 'To ignore, add ' + + chalk.cyan('// eslint-disable-next-line') + + ' to the line before.\n' + ); + } else { + console.log(chalk.green('Compiled successfully.\n')); + } + + console.log('File sizes after gzip:\n'); + printFileSizesAfterBuild( + stats, + previousFileSizes, + paths.appBuild, + WARN_AFTER_BUNDLE_GZIP_SIZE, + WARN_AFTER_CHUNK_GZIP_SIZE + ); + console.log(); + + const appPackage = require(paths.appPackageJson); + const publicUrl = paths.publicUrl; + const publicPath = config.output.publicPath; + const buildFolder = path.relative(process.cwd(), paths.appBuild); + printHostingInstructions( + appPackage, + publicUrl, + publicPath, + buildFolder, + useYarn + ); + }, + err => { + const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; + if (tscCompileOnError) { + console.log( + chalk.yellow( + 'Compiled with the following type errors (you may want to check these before deploying your app):\n' + ) + ); + printBuildError(err); + } else { + console.log(chalk.red('Failed to compile.\n')); + printBuildError(err); + process.exit(1); + } + } + ) + .catch(err => { + if (err && err.message) { + console.log(err.message); + } + process.exit(1); + }); + +// Create the production build and print the deployment instructions. +function build(previousFileSizes) { + // We used to support resolving modules according to `NODE_PATH`. + // This now has been deprecated in favor of jsconfig/tsconfig.json + // This lets you use absolute paths in imports inside large monorepos: + if (process.env.NODE_PATH) { + console.log( + chalk.yellow( + 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' + ) + ); + console.log(); + } + + console.log('Creating an optimized production build...'); + + const compiler = webpack(config); + return new Promise((resolve, reject) => { + compiler.run((err, stats) => { + let messages; + if (err) { + if (!err.message) { + return reject(err); + } + + let errMessage = err.message; + + // Add additional information for postcss errors + if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) { + errMessage += + '\nCompileError: Begins at CSS selector ' + + err['postcssNode'].selector; + } + + messages = formatWebpackMessages({ + errors: [errMessage], + warnings: [], + }); + } else { + messages = formatWebpackMessages( + stats.toJson({ all: false, warnings: true, errors: true }) + ); + } + if (messages.errors.length) { + // Only keep the first error. Others are often indicative + // of the same problem, but confuse the reader with noise. + if (messages.errors.length > 1) { + messages.errors.length = 1; + } + return reject(new Error(messages.errors.join('\n\n'))); + } + if ( + process.env.CI && + (typeof process.env.CI !== 'string' || + process.env.CI.toLowerCase() !== 'false') && + messages.warnings.length + ) { + console.log( + chalk.yellow( + '\nTreating warnings as errors because process.env.CI = true.\n' + + 'Most CI servers set it automatically.\n' + ) + ); + return reject(new Error(messages.warnings.join('\n\n'))); + } + + return resolve({ + stats, + previousFileSizes, + warnings: messages.warnings, + }); + }); + }); +} + +function copyPublicFolder() { + fs.copySync(paths.appPublic, paths.appBuild, { + dereference: true, + filter: file => file !== paths.appHtml, + }); +} diff --git a/scripts/start.js b/scripts/start.js new file mode 100644 index 00000000..dd89084f --- /dev/null +++ b/scripts/start.js @@ -0,0 +1,147 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'development'; +process.env.NODE_ENV = 'development'; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + + +const fs = require('fs'); +const chalk = require('react-dev-utils/chalk'); +const webpack = require('webpack'); +const WebpackDevServer = require('webpack-dev-server'); +const clearConsole = require('react-dev-utils/clearConsole'); +const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); +const { + choosePort, + createCompiler, + prepareProxy, + prepareUrls, +} = require('react-dev-utils/WebpackDevServerUtils'); +const openBrowser = require('react-dev-utils/openBrowser'); +const paths = require('../config/paths'); +const configFactory = require('../config/webpack.config'); +const createDevServerConfig = require('../config/webpackDevServer.config'); + +const useYarn = fs.existsSync(paths.yarnLockFile); +const isInteractive = process.stdout.isTTY; + +// Warn and crash if required files are missing +if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { + process.exit(1); +} + +// Tools like Cloud9 rely on this. +const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; +const HOST = process.env.HOST || '0.0.0.0'; + +if (process.env.HOST) { + console.log( + chalk.cyan( + `Attempting to bind to HOST environment variable: ${chalk.yellow( + chalk.bold(process.env.HOST) + )}` + ) + ); + console.log( + `If this was unintentional, check that you haven't mistakenly set it in your shell.` + ); + console.log( + `Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}` + ); + console.log(); +} + +// We require that you explicitly set browsers and do not fall back to +// browserslist defaults. +const { checkBrowsers } = require('react-dev-utils/browsersHelper'); +checkBrowsers(paths.appPath, isInteractive) + .then(() => { + // We attempt to use the default port but if it is busy, we offer the user to + // run on a different port. `choosePort()` Promise resolves to the next free port. + return choosePort(HOST, DEFAULT_PORT); + }) + .then(port => { + if (port == null) { + // We have not found a port. + return; + } + const config = configFactory('development'); + const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; + const appName = require(paths.appPackageJson).name; + const useTypeScript = fs.existsSync(paths.appTsConfig); + const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; + const urls = prepareUrls(protocol, HOST, port); + const devSocket = { + warnings: warnings => + devServer.sockWrite(devServer.sockets, 'warnings', warnings), + errors: errors => + devServer.sockWrite(devServer.sockets, 'errors', errors), + }; + // Create a webpack compiler that is configured with custom messages. + const compiler = createCompiler({ + appName, + config, + devSocket, + urls, + useYarn, + useTypeScript, + tscCompileOnError, + webpack, + }); + // Load proxy config + const proxySetting = require(paths.appPackageJson).proxy; + const proxyConfig = prepareProxy(proxySetting, paths.appPublic); + // Serve webpack assets generated by the compiler over a web server. + const serverConfig = createDevServerConfig( + proxyConfig, + urls.lanUrlForConfig + ); + const devServer = new WebpackDevServer(compiler, serverConfig); + // Launch WebpackDevServer. + devServer.listen(port, HOST, err => { + if (err) { + return console.log(err); + } + if (isInteractive) { + clearConsole(); + } + + // We used to support resolving modules according to `NODE_PATH`. + // This now has been deprecated in favor of jsconfig/tsconfig.json + // This lets you use absolute paths in imports inside large monorepos: + if (process.env.NODE_PATH) { + console.log( + chalk.yellow( + 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' + ) + ); + console.log(); + } + + console.log(chalk.cyan('Starting the development server...\n')); + openBrowser(urls.localUrlForBrowser); + }); + + ['SIGINT', 'SIGTERM'].forEach(function(sig) { + process.on(sig, function() { + devServer.close(); + process.exit(); + }); + }); + }) + .catch(err => { + if (err && err.message) { + console.log(err.message); + } + process.exit(1); + }); diff --git a/scripts/test.js b/scripts/test.js new file mode 100644 index 00000000..b57cb383 --- /dev/null +++ b/scripts/test.js @@ -0,0 +1,53 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'test'; +process.env.NODE_ENV = 'test'; +process.env.PUBLIC_URL = ''; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + + +const jest = require('jest'); +const execSync = require('child_process').execSync; +let argv = process.argv.slice(2); + +function isInGitRepository() { + try { + execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); + return true; + } catch (e) { + return false; + } +} + +function isInMercurialRepository() { + try { + execSync('hg --cwd . root', { stdio: 'ignore' }); + return true; + } catch (e) { + return false; + } +} + +// Watch unless on CI or explicitly running all tests +if ( + !process.env.CI && + argv.indexOf('--watchAll') === -1 && + argv.indexOf('--watchAll=false') === -1 +) { + // https://github.com/facebook/create-react-app/issues/5210 + const hasSourceControl = isInGitRepository() || isInMercurialRepository(); + argv.push(hasSourceControl ? '--watch' : '--watchAll'); +} + + +jest.run(argv); diff --git a/src/App.css b/src/App.css new file mode 100644 index 00000000..de82222e --- /dev/null +++ b/src/App.css @@ -0,0 +1,16 @@ +.App { + background-attachment: fixed; + background-position: center; + background-repeat: repeat; + background-color: #fafafa; + padding-top: 4rem; + min-height: 100vh; +} + +.navbar { + background-color: #ef5350 !important; +} + +body { + background-color: transparent !important; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js new file mode 100644 index 00000000..5c93f7f4 --- /dev/null +++ b/src/App.js @@ -0,0 +1,33 @@ +import React, { Component } from 'react'; +import { HashRouter as Router, Route, Switch } from 'react-router-dom'; + +import 'bootstrap/dist/css/bootstrap.min.css'; + +import './App.css'; + +import backgroundImage from './pattern.png'; + +import NavBar from './components/layout/NavBar'; +import Dashboard from './components/layout/Dashboard'; +import SearchBar from './components/search/SearchBar'; +import Pokemon from './components/pokemon/Pokemon'; + +class App extends Component { + render() { + return ( + + + ); + } +} + +export default App; diff --git a/src/App.test.js b/src/App.test.js new file mode 100644 index 00000000..93b64b5f --- /dev/null +++ b/src/App.test.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(+++ +++ ++ + , div); + ReactDOM.unmountComponentAtNode(div); +}); \ No newline at end of file diff --git a/src/components/layout/Dashboard.js b/src/components/layout/Dashboard.js new file mode 100644 index 00000000..35d18f6d --- /dev/null +++ b/src/components/layout/Dashboard.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react'; + +import PokemonList from '../pokemon/PokemonList'; +import SearchBar from '../search/SearchBar'; + +export default class Dashboard extends Component { + render() { + return ( + ++ ); + } +} diff --git a/src/components/layout/Loading.js b/src/components/layout/Loading.js new file mode 100644 index 00000000..2ae6289a --- /dev/null +++ b/src/components/layout/Loading.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react'; +import ReactLoading from 'react-loading'; +import spinner from './spinner.gif'; + +export default class Loading extends Component { + render() { + return ( ++++ + + ); + } +} \ No newline at end of file diff --git a/src/components/layout/NavBar.js b/src/components/layout/NavBar.js new file mode 100644 index 00000000..a07bc9e6 --- /dev/null +++ b/src/components/layout/NavBar.js @@ -0,0 +1,67 @@ +import React, { Component } from 'react'; +import styled from 'styled-components'; +import classnames from 'classnames'; + +const Branding = styled.a` + -moz-user-select: none; + -website-user-select: none; + -ms-user-select: none; + user-select: none; + -o-user-select: none; +`; + +const Logo = styled.img` + height: 30px; + width: 30px; + margin-right: 0.5em; +`; + +const NavBarStyle = styled.nav``; + +export default class NavBar extends Component { + state = { + hoverNavBar: false + }; + + hoverNavBar() { + window.scrollY <= 0 + ? this.setState({ hoverNavBar: false }) + : this.setState({ hoverNavBar: true }); + } + + componentDidMount() { + // Added True To End To Listen to All Events On Page + window.addEventListener('scroll', this.hoverNavBar.bind(this), true); + } + + componentWillUnmount() { + // Added True To End To LIsten to All Events On Page + window.removeEventListener('scroll', this.hoverNavBar.bind(this), true); + } + + render() { + return ( +Getting Pokemon...
++ + ); + } +} \ No newline at end of file diff --git a/src/components/pokemon/Pokemon.js b/src/components/pokemon/Pokemon.js new file mode 100644 index 00000000..774f3230 --- /dev/null +++ b/src/components/pokemon/Pokemon.js @@ -0,0 +1,475 @@ +import React, { Component } from 'react'; +import Axios from 'axios'; + +const TYPE_COLORS = { + bug: 'B1C12E', + dark: '4F3A2D', + dragon: '755EDF', + electric: 'FCBC17', + fairy: 'F4B1F4', + fighting: '823551D', + fire: 'E73B0C', + flying: 'A3B3F7', + ghost: '6060B2', + grass: '74C236', + ground: 'D3B357', + ice: 'A3E7FD', + normal: 'C8C4BC', + poison: '934594', + psychic: 'ED4882', + rock: 'B9A156', + steel: 'B5B5C3', + water: '3295F6' +}; + +export default class Pokemon extends Component { + state = { + name: '', + pokemonIndex: '', + imageUrl: '', + types: [], + description: '', + statTitleWidth: 3, + statBarWidth: 9, + stats: { + hp: '', + attack: '', + defense: '', + speed: '', + specialAttack: '', + specialDefense: '' + }, + height: '', + weight: '', + eggGroups: '', + catchRate: '', + abilities: '', + genderRatioMale: '', + genderRatioFemale: '', + evs: '', + hatchSteps: '', + themeColor: '#EF5350' + }; + + async componentDidMount() { + const { pokemonIndex } = this.props.match.params; + + // Urls for pokemon information + const pokemonUrl = `https://pokeapi.co/api/v2/pokemon/${pokemonIndex}/`; + const pokemonSpeciesUrl = `https://pokeapi.co/api/v2/pokemon-species/${pokemonIndex}/`; + + // Get Pokemon Information + const pokemonRes = await Axios.get(pokemonUrl); + + const name = pokemonRes.data.name; + const imageUrl = pokemonRes.data.sprites.front_default; + + let { hp, attack, defense, speed, specialAttack, specialDefense } = ''; + + pokemonRes.data.stats.map(stat => { + switch (stat.stat.name) { + case 'hp': + hp = stat['base_stat']; + break; + case 'attack': + attack = stat['base_stat']; + break; + case 'defense': + defense = stat['base_stat']; + break; + case 'speed': + speed = stat['base_stat']; + break; + case 'special-attack': + specialAttack = stat['base_stat']; + break; + case 'special-defense': + specialDefense = stat['base_stat']; + break; + default: + break; + } + }); + + // Convert Decimeters to Feet... The + 0.0001 * 100 ) / 100 is for rounding to two decimal places :) + const height = + Math.round((pokemonRes.data.height * 0.328084 + 0.00001) * 100) / 100; + + const weight = + Math.round((pokemonRes.data.weight * 0.220462 + 0.00001) * 100) / 100; + + const types = pokemonRes.data.types.map(type => type.type.name); + + const themeColor = `${TYPE_COLORS[types[types.length - 1]]}`; + + const abilities = pokemonRes.data.abilities + .map(ability => { + return ability.ability.name + .toLowerCase() + .split('-') + .map(s => s.charAt(0).toUpperCase() + s.substring(1)) + .join(' '); + }) + .join(', '); + + const evs = pokemonRes.data.stats + .filter(stat => { + if (stat.effort > 0) { + return true; + } + return false; + }) + .map(stat => { + return `${stat.effort} ${stat.stat.name + .toLowerCase() + .split('-') + .map(s => s.charAt(0).toUpperCase() + s.substring(1)) + .join(' ')}`; + }) + .join(', '); + + // Get Pokemon Description .... Is from a different end point uggh + await Axios.get(pokemonSpeciesUrl).then(res => { + let description = ''; + res.data.flavor_text_entries.some(flavor => { + if (flavor.language.name === 'en') { + description = flavor.flavor_text; + return; + } + }); + const femaleRate = res.data['gender_rate']; + const genderRatioFemale = 12.5 * femaleRate; + const genderRatioMale = 12.5 * (8 - femaleRate); + + const catchRate = Math.round((100 / 255) * res.data['capture_rate']); + + const eggGroups = res.data['egg_groups'] + .map(group => { + return group.name + .toLowerCase() + .split(' ') + .map(s => s.charAt(0).toUpperCase() + s.substring(1)) + .join(' '); + }) + .join(', '); + + const hatchSteps = 255 * (res.data['hatch_counter'] + 1); + + this.setState({ + description, + genderRatioFemale, + genderRatioMale, + catchRate, + eggGroups, + hatchSteps + }); + }); + + this.setState({ + imageUrl, + pokemonIndex, + name, + types, + stats: { + hp, + attack, + defense, + speed, + specialAttack, + specialDefense + }, + themeColor, + height, + weight, + abilities, + evs + }); + } + + render() { + return ( ++ ++ RéactDex + ++ ); + } +} \ No newline at end of file diff --git a/src/components/pokemon/PokemonCard.js b/src/components/pokemon/PokemonCard.js new file mode 100644 index 00000000..3cf4c817 --- /dev/null +++ b/src/components/pokemon/PokemonCard.js @@ -0,0 +1,106 @@ +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; + +import styled from 'styled-components'; + +import spinner from '../layout/spinner.gif'; + +const Sprite = styled.img` + width: 5em; + height: 5em; + display: none; +`; + +const Card = styled.div` + opacity: 0.95; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); + &:hover { + box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22); + } + -moz-user-select: none; + -website-user-select: none; + -ms-user-select: none; + user-select: none; + -o-user-select: none; +`; + +const StyledLink = styled(Link)` + text-decoration: none; + color: black; + &:focus, + &:hover, + &:visited, + &:link, + &:active { + text-decoration: none; + } +`; + +export default class PokemonCard extends Component { + state = { + name: '', + imageUrl: '', + pokemonIndex: '', + imageLoading: true, + toManyRequests: false + }; + + componentDidMount() { + const { name, url } = this.props; + + const pokemonIndex = url.split('/')[url.split('/').length - 2]; + //const imageUrl = `./sprites/pokemon/${pokemonIndex}.png`; + const imageUrl = `https://github.com/PokeAPI/sprites/blob/master/sprites/pokemon/${pokemonIndex}.png?raw=true`; + + this.setState({ name, imageUrl, pokemonIndex }); + } + + render() { + return ( +++++++++{this.state.pokemonIndex}
++++ {this.state.types.map(type => ( + + {type + .toLowerCase() + .split(' ') + .map(s => s.charAt(0).toUpperCase() + s.substring(1)) + .join(' ')} + + ))} +++++++++
+++ {this.state.name + .toLowerCase() + .split(' ') + .map(s => s.charAt(0).toUpperCase() + s.substring(1)) + .join(' ')} +
++++ HP +++++++ {this.state.stats.hp} +++++ Attack +++++++ {this.state.stats.attack} +++++ Defense +++++++ {this.state.stats.defense} +++++ Speed +++++++ {this.state.stats.speed} +++++ Sp Atk +++++++ {this.state.stats.specialAttack} +++++ Sp Def +++++++ {this.state.stats.specialDefense} ++++++{this.state.description}
+
+++ +Profile
+++++++++Height:
+++{this.state.height} ft.
+++Weight:
+++{this.state.weight} lbs
+++Catch Rate:
+++{this.state.catchRate}%
+++Gender Ratio:
++++ + ++++++++Egg Groups:
+++{this.state.eggGroups}
+++Hatch Steps:
+++{this.state.hatchSteps}
+++Abilities:
+++{this.state.abilities}
+++EVs:
+++{this.state.evs}
+++ ); + } +} \ No newline at end of file diff --git a/src/components/pokemon/PokemonList.js b/src/components/pokemon/PokemonList.js new file mode 100644 index 00000000..0da12816 --- /dev/null +++ b/src/components/pokemon/PokemonList.js @@ -0,0 +1,37 @@ +import React, { Component } from 'react'; + +import PokemonCard from './PokemonCard'; +import Loading from '../layout/Loading'; +import axios from 'axios'; + +export default class PokemonList extends Component { + state = { + url: 'https://pokeapi.co/api/v2/pokemon/', + pokemon: null + }; + + async componentDidMount() { + const res = await axios.get(this.state.url); + this.setState({ pokemon: res.data['results'] }); + } + + render() { + return ( ++ ++ +{this.state.pokemonIndex}
+ {this.state.imageLoading ? ( ++ ) : null} +
this.setState({ imageLoading: false })} + onError={() => this.setState({ toManyRequests: true })} + style={ + this.state.toManyRequests + ? { display: 'none' } + : this.state.imageLoading + ? null + : { display: 'block' } + } + /> + {this.state.toManyRequests ? ( + + + To Many Requests + +
+ ) : null} ++++ {this.state.name + .toLowerCase() + .split(' ') + .map(s => s.charAt(0).toUpperCase() + s.substring(1)) + .join(' ')} +
++ {this.state.pokemon ? ( ++ ); + } +} \ No newline at end of file diff --git a/src/components/pokemon/Spinner.gif b/src/components/pokemon/Spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..187b27bc6eb8ee0a9a569e39c6e460f392f90bbf GIT binary patch literal 14917 zcmdVhSx^&pyy)@lWG4X(J0xLCAV5HNP_vPQ#Sjn>5p7sxSEC>zqRj?`)v&05h=7QQ zh@hyrhkyu*8h2FGfVh^nXsuOi=}p@^o}Tv1y>sr+ {this.state.pokemon.map(pokemon => ( ++ ) : ( ++ ))} + + )} + x9;>G>wo|5eeT9wFHf)N*yxI~ zit#Js;i2K(UEQ;9X0tQ1Y0k8!#-_=KljqKy`||lqVL@R~K#-M%Rl@27MX_RdWSGO{ zw0E@6yqHNzO|f^huiI63=iZ(Ell|{MywA _)>s)al{+b zMttc3SXH5DPr;zTVz@tl$&!s5NrF`|0tyaii=%HB*h%fT@iOyyj-{@SRRXfV)R$kB z#iy5+S2pntrB+^boB2MjxPH^F5>F|X=i0|3A9OVz5V#-l=I-Ccru3H{y(Az?oi}lI z@(OHSUGLvIf5aj9K`^Sc)L0kcQGJN@oKru!rwQdy8$q_0QaM+H9mQdFKdLP4X%^`G zw-|&h_UD=}MxN#OO40?MucQ*zo-!EGy5DR+SyfKDfJRUy>*_D0*{(B3Xc;iRii))v z)V_hvpDVp?a)P3Ra6!4?r`|4otuHRaEJJgHrrY8#=bI`D0%hdGS`MO)fZqUauQ*5) zSe@1`Yiosq&CSQu@3LI?M#SgRf9+=vlc;{3{NiE7rHd@bIK>J{{Q^sO#@yN#vg8bj zo*KT>p>5?L^e5Hoi3%tbv>;;bG?5V>T6{G?n9a##b=QMD);-MD$;4)sP!V#$P1S7! zgSx#w4CB<~1dB@>9>Ar~4oQ8L+J^M=>!b#EJlB5 W- J5QPE0r! zVZ7-g$xb_*P>b@d!!_f562vl4s5JGy&E%p)7GU%03;@apOJV_hqp#QpAyeL1rXuL@ z$~u0yP_}bPCcOqvh_(QLHYqcih{dk~a*%sU*-e~!kQ0U)CRGpCr#PAqAzF~tk}iwU znJ#GG8!f8?iMx6?8(f_>Tv1g;XIEeA6|(^r=$bghQJ->6%Y4IXXGpsEiZ=loT7Y|v z_m=$nePE9!bQtR1q+Q>}rgV(j@D+3r7{>sD59~Z?Mn64nR1Jh_J$K_V{eTJ?2w*(; z@|2;<(fRZ>Bnfmx{zeHwIzRnz1m z^&{9-R{aasL*f=>N=KbX4@; vU8I~yol4+c9MNf#bz;rlCX zOix8nrC1`jh6}Y;o*RzvX~I%=v#$o5DGgOO`A&DR pdAj8eW5rT9t*yrG{rN{`E7tm=g0QZiNbucZlZFF$L54N3FWde48aJkBn~N& zc%-1Yu7#l(wX+8e;u @V}lhQb*S}19WIgC+oGnET>s@qiP_R$4zmB#kh zP1y&U>RV%EV=$A_!p>dj5IBccA*3|@1XWt#4(LWDtsZDIw`pXx2dV!qAlxN6h!m5i zFuvcfqCPOYq?laB)tXKB9hri3{ayhF+_6VIh!>lrmoIw>zhAyOn(hi*TEAm2Q?~9D z_%aB+WVt%h *hCFh}B#7-%ZgwEE*%QkehMHfZpPnDyZKN^%YZ%MaPx_5f% zs)9cFfpQZ+8YFB-(^PCM>aHm;2{=tu8~8(1;J~C#^#epYK1Yh;z37$Jz0dI2yEgkS zea6-VCvL9;W=j|;@##XwrlRvgK;D@H#5KDu3jwUf&N{~;Sn2mIp!{s}Vq1bAXO6ii zl6zXcH4rY6!b5DPYz4p3#h|^!(+7hw@Z1Luzy{>aVyI&rE)}nP-0=W)H)KtRiKnvn z#cqFMOEMnSpQ%|NINGc3%lA+j^NX;g$2C*u=k|4&_nsbX1T}4u_7ZxdX8Sxwi_<~* zT``t|`B7^R>7hu0>HuzeUncO|!`5d3tBY+2(o?)qhNYrB3io{X ma}eQYk0Fkbezz^w z8+94}u95K;XZth@vNqs1nhx?VFH+Hme71+sk8f{$da}Eb`12_$eDCr!cSiX5;v@08 z68yrzrB-Q2YfOQseuS;f8(wxIyaH`?s`2eDl-JVWO76F{(q0H7XrYQjaanM^zCyir zjAORv=SLjeUb`*S(Zg_~@GMu _D zO=Y+R_I!9)74m 3i<0aC+_Z zmG*bx^hx(M@AORmg`B?Xz9Odw4~<3tg`B?Xz80Q7=KgX{A9i1z)0?MvPH)@3KWlBt z-K5v-KWnY8-nH>ocV1jFW9Rr0vn$l|i^~pfJo%2{;& gk z1iPY|yLx$61_yw==-4wHe6z2=S^zqpXT&|tbA%kZbVFc$fY8FqTFWCGSy(^GH@{`; z6%yOm3z?-$WpaY+kc2&jfSxE%58YQ5LRkcZh3V-oZJ$5Ef7)cUYJpJeq`lIwH=* zqR7VYr^~6eDwuKn-u!1^0Koh{&B|{BnNjSd3EgCtw3a_FiG{2qW#GZZVE~INou9{X zYB5q^uXvw7o>FDmO!iA;Ouvfc1pCoj9=umTEuba&$8#8h8@7vsuJPm6DwglIw q zd4>+8Sp_G(0xi2K?|3`4GKl{{1bS*{6KFO1B8VrwzkobxoFSBTXg#VQ?0@Qg6duI} zoWOMIWa!YFLvp=si3+OvmJDK++BsEhF<)cKFtja(Pb 2-Dj_dgs__n{#LD~jV5I}WL8LsdidPJP<04wK0g1^n_Y zp>FRrpWdv#AI*t}s^=Z!EGpoB3FSbnXIv@>?A6Pd4RQDRb*P2gbv6uUF6aVu1<&f% zAp>7gA%>R3J^OGv>>#t8J)g$GoUx$-2NM#)z^6GW40jameGaMo^SlUDeIej%8-!{s z%>X_OxCuIjZux~hY{Yh9UgL{ZLn>F)pcE2piqASvQ`}Zpc-$8`6k8cW-w{?3QO-9= zx6O{rKoto@e8Nkgb#mC9t=V^%D9hjBzr=^ErC3db{kS{znK`=OGj6sSIeJXk|7bb6 zZ)0`H$_ZE0H|(HI&*n$r{Q*W+S6Lg`=h0gpao5eRhI(oDbQ@2<`CifE_U20k{KGNb zO`&kfO^wU(=x0OnQLo;mC#(0M5*amTz_(>jxlQPW=F8Vpe^8yzp`Lwl{YIpZFY~{c z^!|HY_wT6tI?n#1>+~A_JL )9hM@6lxlT1JSHxSGbAkTvG!+%CKu;%JPIg|NZFKm$2uByJ%BLt| z;bs_yx;1RO5T=q@)bi;jo9&FYmdEiZSkE1$ZG2RT37Hqu#G|^Yu-_g#>zoIJ4 H_VC&~TSu0XsDDFvq#ag@}F<;bm819(?Z%$Mu4z`wv3M zF3k7E5|1>9JOKUdMSE{G6)E@1W8ub%)?St^SH<%~gwu#JfFV4XxgmYnl~f4FCIY5w z%X~2bxqNX7OM>we*HHk$L-Vkt$HH}*Vg{_;%4tVBg~>`aFy*bD8O9S2DkQx4x;yop zyEuzS2`lnz5{s4{tSL+Hol1Oj4dBDFPE5>TQ_9#Do#30QC#A aeDY%qKdac62#6q^w&`vNoGc8(c1s>T&FvKpu z^cM6T7UpAQ5t1k!D!atWp3$O{yv;G@dF5ClqlbV;UAU+1j<~15`365@=nBKi00Cad z=V)(0AS-1~4~fk@9 8gq2qr5)EO_%w> zR>-PyMqfRvSRm@0sR^I=d1~crLTr*-^!KVkC$B{UAg +B0P9 zpM eo@wTC2P(eJdBNW*o~X>D(i+CWI@2v zD-T>u$m5x-kY(G2t4Fq+)Mn#*UaaV5pi*>m#%bF_Gfv{_majP$krWDU1ZRo6Ox|x7 zx#QtiS3=MQp;p1jC)cIowpE0;-6uJt&iysG%Rzm|oR*ZQpZGGc=cU26#oxchzu)M8 zEYO#D_4!rC!fo%cD3>#y(v_aud^G77&a+1fu(Wbmf#;j aC1WH^`INWrMC8pYR{ErUJ$2pWUD A}+no<17?KELYMam{}Q-Z_~x1@Pzax+1v-bh~@k z06Z8KJDZ2THo+JG^`9|WvZm8=MZn;Lr5oF8UMt7}3+OpnKtG^`jPD(`RA?G#ty(ST zZz{%93o5qpovS5V>bR(qJhQUI2++R3(l(w?u?5hRx3!W-g{cD99p#$4=lPL&<%N96 zxj?&%5r|`crrc9a924w5uj@iW9uVHj%3jXHpR;V3 W#8o@&RHM)E&^$hhevT{ zF0fMP8c9{*#W?f!Q9vZz07-tU6ip1eNb8It&5B-zPGJIkDi)f(+rclxoFj8uaXMys z1~(;+o~U6}q(;avSN+`X&f`G)taL#&Qc0BoAERy@y8|iS&UfSdRG(rTR92mDgYN|& zx^G}432C``2ZTW&c4+AeFjG^*0*S7*_V6o_ZWn*Z_eHX=U37%2X__coY&kwrYm8gS zDnDwNtEA+oaL^s$$rZG=mbH~mev1!LY7#YoQCG&DTe7$A%Pco-p{W=Rd>EdVhkk*V zL1zr?JFEQT?$Ih93Mms|8r?5g%cF!Aire(U1`~IiZ|^egPIlsr0XL!-a>1Ea?9*%f zn0XyCf7AXYqf>91RVJX+9!KLTW=Y8hYmz)zJ}Q!J20H2kZzV;i(+3`$l-t&%8$_C> zD*p())J{3bqXh?6l56~ulum8xxFYL~uaZh^7b|5IUIBjlK_*d2Q{_RL^ywWGP${cQ zK07g8ZS_X2s|oj6!9ni1Mv}vRb**P&jQA1}0S)MtK{FiqK=g{@Udt>%JA#35d2*Zq zBGrYY?p$Hk($R>G`y~u%Ivmmgq1!Xk2vi+la4(=x0*;oa2pPLlKKn<*B3VF(AQ}-V zF}O7D(E%ZGkZeriJIQlIg~f&;cuGem8`XFcw~K&k0=(ImTCkhdV1#xahOP)DHsQ?U zdSxB{?Ij$TdSt~5#DxzCO3d*!A+DV(Vx1Le=RyeuLd+f;!t5w^c7o0yz>?U#=bvmo zERK^kGS==$FJPcY7zJ6F(KV98&3BQ3ii?s^#uGfHu7xUbEG>`?uqudY^;Ct0rq}4# zhMo}kK#QY60pQKywSyWl-SBLDmL 1LaxC0%w7`FZ)H>Dp7?ho-%S#`njIHRdJncXXFuH! zU4Po;{%kL*k77O-e*$-$`CG*$x4_#@4Sp|WYwzbD|L!N^mbBM7(+bp}m-IX0S}&vZ z5zSZkeP4X7YnKo*Ie>#E6#6;Tao@hb6j}E}yY6;R)kj^=jmY )$Kxt84!_N_?GK{}FP3pIR?Qt#4rdF|{7oqTc^8 zwN6C+Iki44o$@@8M$(;TWduI)yE4}9S?A=Zk{O{FIp-8?-YnxsXUFmV9UWng9&$lo z5yV; qhax&>rhM`-ti6ItE+0I{$H|1poDQY4Jl#w<;IiLKn$SaCwEJ9NX?0+BL0N-C3v z!2%b5CRYzu(MhFc_*V55+5EVjb!v7!F -V2Vb5NqfdKN_#6xB40yT*f{b%`83}EMhFjI zU^K*9-e!}xp5*tz8-+RXlp-(!{0sqKH8`OXKX_hsV`V%pZf+~IXrR@3miV2~)?IMp zGgl;i<4<;2(dw@&uRrWV8(2&lziyEndf{66TDW67*NfO>hEa(LQnb%25y6N cq@PtO!WlEtRGsS zO;#EWf?s>q@+32#uRNxX)}tc+)E51vLqa+I|SMo7;~AK>)P=2at9RRc;^e)xv5f zWdfe$);oV50s-u^ vTzmg524X-r8VuOQI-&tFn*%_C?D8%&)I0 zU?5SlHHVZFBSPj`mX_Wce!KM571*;jz;`(RS994-hLh35yBl`Z4>hb2oqco)e ?EgL15w==?6X?Tg8D049;V&)gzsUPX$b`eGYtv zWr$h>$dw#j)$eM_?T4L1`3lc(n?D(XE4Bhg<@v7p-St&HlH2wZLu^MxK-)UE0pT5h z8exSSea&>ZSPi%zZDcw0DrRw?&%3d2s?KixuLjotI8B}j8H+0TYmwg{_s77eYzuZ8 zf01W(VwC`C)?ZNRM)Wym($;zZ-nh^EO)jfugmk2P`f7emQUn?3Tj?l?aNUG-GA{`$ z YhY`X$g&26iw`f~IQ6I$%H~+v)l3ZrAtFUMGmkb#Cjpn# z2>4{rt_-=P@H3G~m(NR5d7e#54~<%Bz)K(gf-?qTay#J9jazftab^{9MPUb8JC}nG zC`}ZIDeyP}R-J!5IU^0e+7M=hAJlX{#`~I)$6ETcZxa}$G{!>jF{4FZaecSKAi>I` z!O0@Ok10|_ed-#Pbq#}AS;>RX9TN_7F|f^*#VrkMSg7%(8Kd7DT}qVz+)C= k6+b4+ zWUCC--y^0_IX@8)u&6t=wn(oAD01TRQwfAH5WEcT*hz$ZHx|i&xH0t0E jLtHf?MX4Z=J-RcT<$I$irFGmPoMJpn1AnS~eN`pZIA4?=^GwC`?MaSY#jCp@A> ztpQri*mF?UGpEzQ)&uaOH7TnzY>{C*OBFz?;Q+uI9}0lAAJd6bw{lZ(kL%Yoh+aT9 z>;eG+qdvr8gT4_986MT!d!R96nxWpO*_L(>vJnFW%dT1NnZ?<8g~@8>SthulU0GLj zn%G@Ed@D@!b>2OLACxG`v9cH1SxiKsj?p;lqobPEpAc#GmAT#3Fl>ou-SIYFR|h!< zis*nrSVCQOgZ*y{_Fedbl*-W80#vua&YhAq86zRBbt_zj+>LnmIJCQwsXcPca;drb z+kxN#Z8!4gD`}t&0muzGc-OcDkpAl6q|+K(;^n7_fyr!*S)l8Nv7Blx4Ge|FKgCB_ z4$3Zd%-1jvD+o7zs-3;Q(@2J!cfNld+$8Gr?mfqhy)QAHQr|>h?eki;p?qmBP5vlZ z4$oRrosXxATQt`v%`BkzQfz@k=t}VEPpTE&*FRe&|MdSb+x??~{(nA920lol{`J)2 zv{KqWL5pNdqfD0``B-Xso?LOza~Co_5`0LFq)qn_%Y`Tx7U0G$#U$C+4l 50{R0MKEEzcs)ohaj#3!L^j9@@{dE5W;O 2?i2X@;L{Z>^Yf5Xk9f|-d3L&jLatrU(^veR z9WO(|L|l*a9(`gH+|L~O0n^_1eTjFvmIqRfH<@(qd^H9S%!@`Ja^ygzt9@JOaX8}T zA+q!J$%f*T32HK{)7}y^z3jLM6#8P6`HVkbnQoSq&WVEWuc$Bhc&UGm3UYx2Zr)WF z&1nfuIsqg5C%)33Mm_3&;3w7+@kmG;Y(fPFhDR;A-w=?wbd6$vXcWcz=2gujl5 |m8>yAy3?T7E+h*-o49NGx``R+hy> z@`gSqDOOO?uf}k#TZZBx?_q~JjlU0uVi3W$YDzCCY^vO4tfd?k=rM8xtz2!!XymQqT=f~#=S$#hB4$4iuF6_mk) zP_UPF`MxdYm?R+Dr(L{q(IfMB%L(2TA`opfu3fzdjb1jnDw(LB*=fWO?H8L`wbd*i zUDFB#QfL_+Ao8pFNzU=R8*A)tTv|AYGhs*^YJHUQOL=klW$eW;fRF`SuWgZo-EkW< z=*^+MnriX}2Wz;g_7@PK9wcnQWBhbfWGXdH-mv@(&XKxFn*>kciB%bhjpwu)u&aVN zjA|}8Llt>ZN*$2K!>8rhwHFx7CUOc=?H)K9diV${Ta^WW@I9RcXm<;d`2-Jd5@OhS zxr`32Phi%OFS*CmvV(4ZZ&}bv$Ff^Yr%rzkwxM8d*YQ`_L}m}I;#4rwto}5}$oRZC z6A#T*y+(89R{nr>=Br$W74`bp9x7xccOY1#dJU$9Byf +s#8P#Fz-E~81aD%gWc>MUoimR>7+r>|j zpQ0RoI&Eys1govVIZ}g$7ZS+r;B)}H$NrAN&}ih)Dg}UA5*-1%V)L8Ng9w$%1^oeQ z#Ok00wQfEYyzp!#64-nG7#^YRK4EP8s_>JK-TN88DTRXd>~8zM#UszU!UhXy|5e@m zr*-`Q=u_myT`7lHe-3pd{Ma8u-RPmePLT`8>?_lToHYUdr~AB)*A48@xkQglYjE?$RK`b+GZ{Y?X|6HeV8X1wzEXZcPy`H7C0X8v?>Z)%MY^L zLn9v+1R-OM%=2Rlc;=^Q8E5$3$I`jciCou1^<(-+7`t!E?kp)hRd;u%%&3wh>fm^n zdC~Sy?MR#GWrxPzX`BTe4ua_-tg2H6jNhU6;=oOD^iKQ%8QKZfi%*K-u}}rsKZu0` zucvAWAbPvV%(dA%MNm)#a-5m8UQiZi_2>YAhpa|d&4^CgU!D)PhdQXdyl7(quu1H$ zfiaUIHR;RrC!<$4;un}B g;p)UYtcCu<8-Rdx@9xJ=Y@Drlf{~t^LC(@ z;~QPKpn98Z*1q5M`2Oi~+lbX+mwtNX;&?ZzwdB`d=AI2jZ1@?s=krIyBR7)Tc!1vl z^G+hk{3gaX){YKJqE}V_eYSxi7e1vh`n;R5y;YQM8+&;Ey|1nWGXIMR`Fy8-9UnZ3 z8_f1n_^yH)000tRvQR7mf3CR9gwRLf64=Iiu|{P8>0RZAR_XTdu&j^{Aq*J)_OK_w zHVr)N3Lk{pC{|0Lkv6ykP_K{PT#Qc&0)X}yUk1dc+O%HBX@jZH5m>%<4HDkDMZ@t) zXkl073+L1ZD=^hec;Z8IX?9dLUEUaAAD{r4wy3DPd5XEggFf|C891tJ3kXYD^iI;~ zs}<{CV aDeWg+XS4c=t+|^fFoh{1R>Ecen*s@vbmICr+|ADn3Ag4D0DNGxA7KQGv&( zbF)YrT%@GOuqF607F&O28lTZ71eiGDB(jc0IMN*QX4Ga$50G(_fnYn+I2hmnUjc1g zO&?6`XO;Kh0tpKnFd%;`$pcYv--G%vjQtIyKI&v*9D zUijtSXD>Wra+cY3+nX*wO|T-(p&?gaGjGmSnX $`ur#}N`wjGdVgv%3u~TR{&O zj?cb5XqQWDHtf5uYg2zmCJkP*#l=?wu14X0R6_%q?++RbzaB&NZJ*<4Wu}R0uy0sT z7|H`DmY&+aB ?smlt~a z{(5=wkAD0A`T%K?lJTc_wZKpPe~8!F@TYhM#wUmsPD7su1I{MDUW3?RJGh*3q`4~M zaM)^owC$z{QJDUntXG;X!dK5-9yh0GXF10r%+(fJQ&h}1j!7$P <4Pj-G`07&?gZVRj#*K*LEK0-WKO`z|<(w^_D-LX~6OzQ70x&_w9h0Y(o)R z$eW81gLY?v%)^t)mA!fE-C9NmbVDaoa?^tf@8TA;=4-numaVaSHjgrKCs3MVTAsOW z|5!GjRSifAA?>brv_3ZS@(R?ac-=$e>kqkPY5+@^YkYMnJbsz|z!{MPvOLnPe&TY< z3l(VX1;XiG7voAaN;Y3utzK7$aJ9OW>jb%8>zOtycx68Ez1r_Xcf&Eqq)d~`_A5@V zQYF4@Gs&sh8`9!i4l|CGw+-kX?3i4Y8_&xi1=x9PV&TANphZAoi*0ViZT0nD!i4!} zpwq#at63wrk7qqy43qrwqi#rkRD~=qGJ1J?|2Ib$pNG7iyHtDMOM3pd4 c-Krz(S8J zonbR6EeG3yaKQ6G;46j$%Ct?C-r8Mb!Gv@o6Qy041UMUBa8d~d9TPI1!?($vgF3Op zbjYCt@h%Xw2`!T$=O+k3*q}FY2XXU`6K4{! ImMjvutSBn>(WPJ !GKV-?I%q#EMW+Kf2Wr<{y_mTY0xYl6N)Ftn)796U zty+&?9o%RR) mh+R zWnD0 LlLzNLTljr50ap4;_vDe3RNnTOFyfBI(fr*C#`bo2k@o6-OFjjs32 z^Y6Yn8U4*SBfEe5hH}2TzKZ@QZ}(Wb+PdqbXn+fCAjeZyrAT5$_4ehkzrj$ OhAV$Py2Tu2ZaIc0Mvd!ka8f$G16$I@O8_>x{bR}$OMoez$2EddXdvT ziw4v6@T-CtB&}lOkOtZ7Ke9oZ)jn>n{H@F3r(EDWg1K|h6$L I54?kS{OHw54 z!5_Y@8y`acsJmEhXtH*;J})OpCfDd6m))7iiB`ccWH_+_hW)x*W~6$C+I1v_ z0WdHePG?wo#)+f|LU>Dk0B9EXY=_~5LovpWkO71xT#95v86%@I%#jfBvpPpDk*Qos z!!c1y-w;6@&ldV4A_?6m!9+jdt4mi-(xup?Cjqk6?z2K^4nIW%hWowu(m=S=X5i!q zzM55Qs1k-?0=*tG8+hRo{agKd`U?aEk}1WEnMVS^${zp+@XoVZ>E3D+5dZ;`5IH~# z9sQ7Mk3zJ++AG=t0ZITJ0%P_*)V;#8?x~#-Ks0`+-GZZi4WhJXNa5ZKTA59LvwSS# zV4kWQH`4Rj)er?Cx?tzc{30drj7F{T!1={0a5G;idV|Hz?RP%hgG~e<2l9O(t_@E* z0Y>gYTHv~fQElfT0uJwXY|lCI4{;@M@qTsCsEBniSfWeE?)v^wCLTdA2k+s3sk?$0 z>Tr46_Y9jT?FJI*9eL>7z@d7_U&?E)s5X``PXz|W3=ex7wyIC#?LR$&{le|VTst}! zLLGW|Q+yQsFz>{hF& tAVcpEthon6YetHnn< z-&DE2$9vUVs!?%nvZ3?npuzXXCZk Y)@^l&r1fLWy!L-<6!mUl6miVh?|joM^!djcw!v z?QHMr^_3gf8tpuu5Eg4aAb`jLG;ZInVC%4*&`S}va?@c>nR-d#evi$!`5vS6;7pHf zZuA8s@st22haX~XQ9CzuVMsL*4({^sl&ieqI(NKlo0oWVyFoDuqV`8f!v}Gp*S60I z>7gtv#rh+kpUwfpOepjU+}rjcDHB|*JoEE@wb3{BVGoSnUeRyeY8jFB`imUU6Zx~^ z#Fo5J3nL4p;z66!LXEVJ>M^NQ9Uql24U8UIwhflKmp-8HmEF6+N1)>R7T0g!06mRD z;7N6i7{0-ttZn#8aw11t>zVWsB9_UG3-?QQ_FfWjfc?UJRWHz_Q$EOYZdVs8F3(hR zEOfP3jvwB&&9}EXh#nt~DzLuO6QkW6js+iEwpi#>TyiZkHMveb$(QUHqU3$Nup)$m zy>&Z4Hz3)>w4zt9y10J|2!@q!K=!`=ZJn}=B?(VnA93-*Z(PH7-#xwY?eEB#vPpaW z)~#z!f-%tlJAe%7`0=Tut~Cd_(Ws|S0s3vt#8Y|YJY*E>bq6t8Tx|d1GKtvNvTUl% z +vp>d{WyOulyBfXzT6gr7$pwzHc-?61IE4kgJ_pgx)kj;d=r2|b54V|cJW1$ znmB>v3BiL-XV*oJ5N(LieAR&(0v~5kCA6-i6 zq25Yz5^9Lf7I&bj{Y7%%qH`bF1|B+ )ZFJ!SnL(F@t_U~!0mnd^PN`Vk zgEo!5o6bJZ>(zQh8)GA%O$)d?gt%UJwWPp_^5w zTW@IY!~}IyaWA=OTCX-X)4syra{!xr*L^%VU9t|@A;5bOEhJg$v4oH1efWjLjUedV z3&yMqK#@=Gc E1MWYoK2(yVCY((ZEX%Tm?j+P1*9-O}X<$$Rn5i+@{nw>Y+26 z!fZ>fUc9X*Ffhj@DK#aN;~~iPTfy8AwSk+oG%Ux{J$QLmSXxAEl$Te*CeO4ep3rke an7=T@XL+tqQj#!gX + + + ); + } +} \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 00000000..cee5f348 --- /dev/null +++ b/src/index.css @@ -0,0 +1,14 @@ +body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..0c5e75da --- /dev/null +++ b/src/index.js @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import * as serviceWorker from './serviceWorker'; + +ReactDOM.render( , document.getElementById('root')); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: http://bit.ly/CRA-PWA +serviceWorker.unregister(); diff --git a/src/logo.svg b/src/logo.svg new file mode 100644 index 00000000..6b60c104 --- /dev/null +++ b/src/logo.svg @@ -0,0 +1,7 @@ + diff --git a/src/serviceWorker.js b/src/serviceWorker.js new file mode 100644 index 00000000..6123daba --- /dev/null +++ b/src/serviceWorker.js @@ -0,0 +1,135 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read http://bit.ly/CRA-PWA + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit http://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} \ No newline at end of file diff --git a/src/setupTests.js b/src/setupTests.js new file mode 100644 index 00000000..74b1a275 --- /dev/null +++ b/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom/extend-expect';