diff options
Diffstat (limited to 'front/src')
-rw-r--r-- | front/src/assets/DMMono-Light.ttf | bin | 0 -> 48456 bytes | |||
-rw-r--r-- | front/src/assets/chessh_lg.svg | 1 | ||||
-rw-r--r-- | front/src/assets/chessh_sm.svg | 1 | ||||
-rw-r--r-- | front/src/context/auth_context.js | 71 | ||||
-rw-r--r-- | front/src/hooks/useLocalStorage.js | 31 | ||||
-rw-r--r-- | front/src/index.css | 225 | ||||
-rw-r--r-- | front/src/index.js | 47 | ||||
-rw-r--r-- | front/src/root.jsx | 47 | ||||
-rw-r--r-- | front/src/routes/auth_successful.jsx | 47 | ||||
-rw-r--r-- | front/src/routes/demo.jsx | 61 | ||||
-rw-r--r-- | front/src/routes/home.jsx | 62 | ||||
-rw-r--r-- | front/src/routes/keys.jsx | 204 | ||||
-rw-r--r-- | front/src/routes/password.jsx | 144 | ||||
-rw-r--r-- | front/src/setupProxy.js | 21 |
14 files changed, 962 insertions, 0 deletions
diff --git a/front/src/assets/DMMono-Light.ttf b/front/src/assets/DMMono-Light.ttf Binary files differnew file mode 100644 index 0000000..2b14cea --- /dev/null +++ b/front/src/assets/DMMono-Light.ttf diff --git a/front/src/assets/chessh_lg.svg b/front/src/assets/chessh_lg.svg new file mode 100644 index 0000000..0838ef2 --- /dev/null +++ b/front/src/assets/chessh_lg.svg @@ -0,0 +1 @@ +<svg version="1.1" viewBox="0.0 0.0 960.0 960.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><clipPath id="p.0"><path d="m0 0l960.0 0l0 960.0l-960.0 0l0 -960.0z" clip-rule="nonzero"/></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l960.0 0l0 960.0l-960.0 0z" fill-rule="evenodd"/><defs><linearGradient id="p.1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.0 30.983922361253416 -30.983922361253416 0.0 0.0 0.0)" spreadMethod="pad" x1="0.06412605277195675" y1="-30.95757731162891" x2="0.06401389269320752" y2="0.02634504942150403"><stop offset="0.0" stop-color="#4d4d4d"/><stop offset="1.0" stop-color="#000000"/></linearGradient></defs><path fill="url(#p.1)" d="m479.18372 1.9868766l0 0c265.09668 0 480.0 214.0925 480.0 478.18896l0 0c0 126.82361 -50.57129 248.45282 -140.58875 338.13068c-90.017456 89.67786 -212.1073 140.05829 -339.41125 140.05829l0 0c-265.09668 0 -480.0 -214.09247 -480.0 -478.18896l0 0c0 -264.09647 214.90332 -478.18896 480.0 -478.18896z" fill-rule="evenodd"/><path fill="#000000" fill-opacity="0.0" d="m159.43431 138.99149l222.5197 0l0 399.43307l-222.5197 0z" fill-rule="evenodd"/><path fill="#8e7cc3" d="m182.02806 262.4121l35.984375 -95.25l9.625 0l-35.984375 95.25l-9.625 0zm83.29689 -76.5l-11.656265 -14.1875l10.14064 0l11.140625 14.1875l-9.625 0zm42.359375 77.515625l0 -9.015625q6.578125 -1.515625 11.34375 -5.71875q4.765625 -4.203125 7.34375 -10.59375q2.59375 -6.390625 2.59375 -14.1875l0 -18.234375q0 -8.015625 -2.640625 -14.390625q-2.640625 -6.390625 -7.40625 -10.6875q-4.75 -4.3125 -11.234375 -5.828125l0 -8.609375q9.421875 2.328125 16.203125 7.90625q6.796875 5.5625 10.484375 13.625q3.703125 8.046875 3.703125 17.984375l0 18.234375q0 15.09375 -8.0 25.53125q-8.0 10.4375 -22.390625 13.984375z" fill-rule="nonzero"/><path fill="#8e7cc3" d="m265.63745 373.88712q-10.343765 0 -15.812515 -5.71875q-5.46875 -5.734375 -5.46875 -15.5625l0 -53.703125l9.109375 0l0 53.703125q0 6.078125 2.9843903 9.625q3.0 3.546875 9.1875 3.546875q6.078125 0 9.109375 -3.546875q3.046875 -3.546875 3.046875 -9.625l0 -53.703125l9.109375 0l0 53.703125q0 9.9375 -5.421875 15.609375q-5.421875 5.671875 -15.84375 5.671875z" fill-rule="nonzero"/><path fill="#8e7cc3" d="m195.19994 505.61212l0 -95.25l24.828125 0l0 8.109375l-15.703125 0l0 79.046875l15.703125 0l0 8.09375l-24.828125 0zm70.437515 -10.125q-3.859375 0 -6.140625 -2.21875q-2.28125 -2.234375 -2.28125 -5.984375q0 -3.953125 2.28125 -6.28125q2.28125 -2.34375 6.140625 -2.34375q3.84375 0 6.125 2.34375q2.28125 2.328125 2.28125 6.28125q0 3.75 -2.28125 5.984375q-2.28125 2.21875 -6.125 2.21875zm45.59375 10.125l0 -8.09375l15.703125 0l0 -79.046875l-15.703125 0l0 -8.109375l24.828125 0l0 95.25l-24.828125 0z" fill-rule="nonzero"/><path fill="#000000" fill-opacity="0.0" d="m159.43431 536.73975l641.13385 0l0 141.35437l-641.13385 0z" fill-rule="evenodd"/><path fill="#45818e" d="m197.10619 643.8323q-5.671875 0 -9.875 -1.765625q-4.203125 -1.78125 -6.6875 -5.015625q-2.484375 -3.25 -2.984375 -7.703125l9.109375 0q0.609375 2.9375 3.296875 4.65625q2.6875 1.71875 7.140625 1.71875l4.265625 0q5.359375 0 8.0 -2.171875q2.640625 -2.1875 2.640625 -5.828125q0 -3.546875 -2.390625 -5.625q-2.375 -2.078125 -7.046875 -2.78125l-7.484375 -1.21875q-8.21875 -1.421875 -12.21875 -5.125q-4.0 -3.703125 -4.0 -10.78125q0 -7.5 4.765625 -11.609375q4.765625 -4.109375 14.28125 -4.109375l3.859375 0q8.0 0 12.859375 3.75q4.859375 3.75 5.875 10.140625l-9.109375 0q-0.609375 -2.640625 -3.046875 -4.203125q-2.4375 -1.578125 -6.578125 -1.578125l-3.859375 0q-5.171875 0 -7.546875 1.9375q-2.375 1.921875 -2.375 5.765625q0 3.453125 2.015625 5.078125q2.03125 1.609375 6.390625 2.3125l7.5 1.21875q9.015625 1.421875 13.109375 5.28125q4.109375 3.84375 4.109375 11.140625q0 7.703125 -4.921875 12.109375q-4.90625 4.40625 -14.828125 4.40625l-4.265625 0zm40.734375 -0.8125l0 -8.3125l19.76564 0l0 -39.109375l-17.23439 0l0 -8.3125l26.343765 0l0 47.421875l18.75 0l0 8.3125l-47.625015 0zm23.312515 -65.765625q-3.359375 0 -5.2812653 -1.71875q-1.921875 -1.71875 -1.921875 -4.65625q0 -3.046875 1.921875 -4.8125q1.9218903 -1.78125 5.2812653 -1.78125q3.34375 0 5.265625 1.78125q1.921875 1.765625 1.921875 4.8125q0 2.9375 -1.921875 4.65625q-1.921875 1.71875 -5.265625 1.71875zm35.5625 65.765625l0 -55.734375l8.0 0l0 6.6875l1.828125 0l-1.421875 2.4375q0 -4.65625 2.484375 -7.390625q2.484375 -2.75 6.734375 -2.75q4.671875 0 7.203125 3.453125q2.53125 3.4375 2.53125 9.71875l-2.734375 -5.46875l4.25 0l-1.921875 2.4375q0 -4.65625 2.578125 -7.390625q2.59375 -2.75 6.84375 -2.75q5.265625 0 8.15625 3.953125q2.890625 3.953125 2.890625 10.4375l0 42.359375l-8.40625 0l0 -42.453125q0 -3.453125 -1.46875 -5.375q-1.46875 -1.921875 -4.3125 -1.921875q-2.734375 0 -4.3125 1.875q-1.5625 1.875 -1.5625 5.3125l0 42.5625l-7.296875 0l0 -42.453125q0 -3.546875 -1.578125 -5.421875q-1.5625 -1.875 -4.40625 -1.875q-2.828125 0 -4.25 1.875q-1.421875 1.875 -1.421875 5.3125l0 42.5625l-8.40625 0zm63.4375 18.234375l0 -73.96875l9.109375 0l0 10.640625l1.828125 0l-1.828125 2.125q0 -6.375 4.203125 -10.078125q4.21875 -3.703125 11.109375 -3.703125q8.40625 0 13.421875 5.53125q5.015625 5.515625 5.015625 15.140625l0 16.3125q0 6.390625 -2.28125 11.0625q-2.28125 4.65625 -6.390625 7.1875q-4.09375 2.53125 -9.765625 2.53125q-6.796875 0 -11.0625 -3.75q-4.25 -3.75 -4.25 -10.03125l1.828125 2.125l-2.03125 0l0.203125 12.671875l0 16.203125l-9.109375 0zm21.375 -25.125q5.78125 0 9.0625 -3.4375q3.296875 -3.453125 3.296875 -9.9375l0 -15.203125q0 -6.484375 -3.296875 -9.921875q-3.28125 -3.453125 -9.0625 -3.453125q-5.578125 0 -8.921875 3.546875q-3.34375 3.546875 -3.34375 9.828125l0 15.203125q0 6.28125 3.34375 9.828125q3.34375 3.546875 8.921875 3.546875zm60.5 7.703125q-6.703125 0 -11.5625 -2.53125q-4.859375 -2.53125 -7.546875 -7.34375q-2.6875 -4.8125 -2.6875 -11.40625l0 -14.796875q0 -6.6875 2.6875 -11.4375q2.6875 -4.765625 7.546875 -7.296875q4.859375 -2.546875 11.5625 -2.546875q6.6875 0 11.546875 2.546875q4.859375 2.53125 7.546875 7.296875q2.6875 4.75 2.6875 11.34375l0 14.890625q0 6.59375 -2.6875 11.40625q-2.6875 4.8125 -7.546875 7.34375q-4.859375 2.53125 -11.546875 2.53125zm0 -8.109375q5.96875 0 9.3125 -3.34375q3.34375 -3.34375 3.34375 -9.828125l0 -14.796875q0 -6.484375 -3.34375 -9.828125q-3.34375 -3.34375 -9.3125 -3.34375q-5.890625 0 -9.28125 3.34375q-3.390625 3.34375 -3.390625 9.828125l0 14.796875q0 6.484375 3.390625 9.828125q3.390625 3.34375 9.28125 3.34375zm39.71875 7.296875l0 -55.734375l9.109375 0l0 10.640625l1.921875 0l-1.921875 2.125q0 -6.578125 3.953125 -10.171875q3.953125 -3.609375 10.953125 -3.609375q8.40625 0 13.359375 5.171875q4.96875 5.171875 4.96875 14.09375l0 37.484375l-9.109375 0l0 -36.484375q0 -5.96875 -3.203125 -9.15625q-3.1875 -3.203125 -8.65625 -3.203125q-5.671875 0 -8.96875 3.453125q-3.296875 3.4375 -3.296875 9.921875l0 35.46875l-9.109375 0zm60.078125 0l0 -8.3125l19.765625 0l0 -39.109375l-17.234375 0l0 -8.3125l26.34375 0l0 47.421875l18.75 0l0 8.3125l-47.625 0zm23.3125 -65.765625q-3.359375 0 -5.28125 -1.71875q-1.921875 -1.71875 -1.921875 -4.65625q0 -3.046875 1.921875 -4.8125q1.921875 -1.78125 5.28125 -1.78125q3.34375 0 5.265625 1.78125q1.921875 1.765625 1.921875 4.8125q0 2.9375 -1.921875 4.65625q-1.921875 1.71875 -5.265625 1.71875zm59.671875 66.78125q-6.6875 0 -11.703125 -2.53125q-5.015625 -2.53125 -7.75 -7.34375q-2.734375 -4.8125 -2.734375 -11.40625l0 -15.203125q0 -6.6875 2.734375 -11.4375q2.734375 -4.765625 7.75 -7.296875q5.015625 -2.546875 11.703125 -2.546875q9.640625 0 15.515625 5.171875q5.875 5.171875 6.171875 14.09375l-9.109375 0q-0.3125 -5.375 -3.609375 -8.265625q-3.28125 -2.890625 -8.96875 -2.890625q-5.96875 0 -9.515625 3.40625q-3.546875 3.390625 -3.546875 9.671875l0 15.296875q0 6.28125 3.546875 9.734375q3.546875 3.4375 9.515625 3.4375q5.6875 0 8.96875 -2.9375q3.296875 -2.9375 3.609375 -8.203125l9.109375 0q-0.296875 8.90625 -6.171875 14.078125q-5.875 5.171875 -15.515625 5.171875zm46.21875 -48.125l8.109375 -31.515625l13.171875 0l-13.171875 31.515625l-8.109375 0zm73.25 47.921875q-5.671875 0 -9.875 -1.765625q-4.203125 -1.78125 -6.6875 -5.015625q-2.484375 -3.25 -2.984375 -7.703125l9.109375 0q0.609375 2.9375 3.296875 4.65625q2.6875 1.71875 7.140625 1.71875l4.265625 0q5.359375 0 8.0 -2.171875q2.640625 -2.1875 2.640625 -5.828125q0 -3.546875 -2.390625 -5.625q-2.375 -2.078125 -7.046875 -2.78125l-7.484375 -1.21875q-8.21875 -1.421875 -12.21875 -5.125q-4.0 -3.703125 -4.0 -10.78125q0 -7.5 4.765625 -11.609375q4.765625 -4.109375 14.28125 -4.109375l3.859375 0q8.0 0 12.859375 3.75q4.859375 3.75 5.875 10.140625l-9.109375 0q-0.609375 -2.640625 -3.046875 -4.203125q-2.4375 -1.578125 -6.578125 -1.578125l-3.859375 0q-5.171875 0 -7.546875 1.9375q-2.375 1.921875 -2.375 5.765625q0 3.453125 2.015625 5.078125q2.03125 1.609375 6.390625 2.3125l7.5 1.21875q9.015625 1.421875 13.109375 5.28125q4.109375 3.84375 4.109375 11.140625q0 7.703125 -4.921875 12.109375q-4.90625 4.40625 -14.828125 4.40625l-4.265625 0z" fill-rule="nonzero"/><path fill="#000000" fill-opacity="0.0" d="m214.6473 679.6524l530.86615 0l0 141.35431l-530.86615 0z" fill-rule="evenodd"/><path fill="#00ff00" d="m232.25667 779.3386l0 -8.8125l31.42189 -15.390625q1.921875 -0.921875 3.59375 -1.578125q1.671875 -0.65625 2.484375 -0.859375q-0.921875 -0.203125 -2.640625 -0.859375q-1.71875 -0.65625 -3.4375 -1.578125l-31.42189 -15.5l0 -9.125l43.57814 21.796875l0 10.125l-43.57814 21.78125z" fill-rule="nonzero"/><path fill="#93c47d" d="m376.03793 786.948q-6.6875 0 -11.609375 -2.53125q-4.90625 -2.53125 -7.59375 -7.34375q-2.671875 -4.8125 -2.671875 -11.40625l0 -33.4375q0 -6.6875 2.671875 -11.453125q2.6875 -4.765625 7.59375 -7.296875q4.921875 -2.53125 11.609375 -2.53125q6.6875 0 11.546875 2.59375q4.875 2.578125 7.5 7.34375q2.640625 4.75 2.640625 11.34375l-9.109375 0q0 -6.28125 -3.296875 -9.671875q-3.296875 -3.40625 -9.28125 -3.40625q-5.96875 0 -9.375 3.34375q-3.390625 3.34375 -3.390625 9.625l0 33.546875q0 6.28125 3.390625 9.6875q3.40625 3.390625 9.375 3.390625q5.984375 0 9.28125 -3.390625q3.296875 -3.40625 3.296875 -9.6875l9.109375 0q0 6.484375 -2.640625 11.296875q-2.625 4.8125 -7.5 7.40625q-4.859375 2.578125 -11.546875 2.578125zm39.328125 -1.015625l0 -73.96875l9.109375 0l0 18.234375l0 10.640625l1.921875 0l-1.921875 2.125q0 -6.578125 3.953125 -10.171875q3.953125 -3.609375 10.953125 -3.609375q8.40625 0 13.359375 5.171875q4.96875 5.171875 4.96875 14.09375l0 37.484375l-9.109375 0l0 -36.484375q0 -5.96875 -3.203125 -9.265625q-3.1875 -3.296875 -8.65625 -3.296875q-5.671875 0 -8.96875 3.546875q-3.296875 3.546875 -3.296875 10.03125l0 35.46875l-9.109375 0zm81.875 1.015625q-6.59375 0 -11.515625 -2.625q-4.90625 -2.640625 -7.59375 -7.453125q-2.6875 -4.8125 -2.6875 -11.203125l0 -15.203125q0 -6.484375 2.6875 -11.234375q2.6875 -4.765625 7.59375 -7.40625q4.921875 -2.640625 11.515625 -2.640625q6.578125 0 11.484375 2.640625q4.9218445 2.640625 7.6093445 7.40625q2.6875 4.75 2.6875 11.234375l0 9.828125l-34.65622 0l0 5.375q0 6.59375 3.34375 10.09375q3.34375 3.484375 9.53125 3.484375q5.265625 0 8.5 -1.875q3.25 -1.875 3.953125 -5.625l9.1249695 0q-0.921875 7.0 -6.8437195 11.109375q-5.921875 4.09375 -14.734375 4.09375zm12.859375 -32.734375l0 -3.75q0 -6.578125 -3.296875 -10.125q-3.28125 -3.546875 -9.5625 -3.546875q-6.1875 0 -9.53125 3.546875q-3.34375 3.546875 -3.34375 10.125l0 2.9375l26.4375 0l-0.703125 0.8125zm48.328094 32.734375q-7.28125 0 -12.5 -2.421875q-5.21875 -2.4375 -8.0625 -7.0q-2.84375 -4.5625 -2.9375 -10.84375l9.125 0q0 5.578125 3.796875 8.828125q3.796875 3.234375 10.578125 3.234375q6.390625 0 9.984375 -3.140625q3.59375 -3.15625 3.59375 -8.71875q0 -4.46875 -2.375 -7.8125q-2.375 -3.34375 -6.84375 -4.65625l-10.03125 -3.140625q-7.59375 -2.328125 -11.703125 -7.796875q-4.09375 -5.46875 -4.09375 -12.875q0 -5.96875 2.671875 -10.375q2.6875 -4.421875 7.546875 -6.890625q4.875 -2.484375 11.453125 -2.484375q9.734375 0 15.609375 5.421875q5.875 5.421875 5.984375 14.4375l-9.125 0q0 -5.484375 -3.296875 -8.5625q-3.28125 -3.09375 -9.265625 -3.09375q-5.875 0 -9.171875 2.84375q-3.296875 2.828125 -3.296875 7.890625q0 4.5625 2.4375 7.90625q2.4375 3.34375 6.984375 4.765625l10.140625 3.234375q7.390625 2.34375 11.4375 7.921875q4.0625 5.5625 4.0625 13.0625q0 6.078125 -2.84375 10.640625q-2.828125 4.5625 -7.953125 7.09375q-5.109375 2.53125 -11.90625 2.53125zm60.796875 0q-7.28125 0 -12.5 -2.421875q-5.21875 -2.4375 -8.0625 -7.0q-2.84375 -4.5625 -2.9375 -10.84375l9.125 0q0 5.578125 3.796875 8.828125q3.796875 3.234375 10.578125 3.234375q6.390625 0 9.984375 -3.140625q3.59375 -3.15625 3.59375 -8.71875q0 -4.46875 -2.375 -7.8125q-2.375 -3.34375 -6.84375 -4.65625l-10.03125 -3.140625q-7.59375 -2.328125 -11.703125 -7.796875q-4.09375 -5.46875 -4.09375 -12.875q0 -5.96875 2.671875 -10.375q2.6875 -4.421875 7.546875 -6.890625q4.875 -2.484375 11.453125 -2.484375q9.734375 0 15.609375 5.421875q5.875 5.421875 5.984375 14.4375l-9.125 0q0 -5.484375 -3.296875 -8.5625q-3.28125 -3.09375 -9.265625 -3.09375q-5.875 0 -9.171875 2.84375q-3.296875 2.828125 -3.296875 7.890625q0 4.5625 2.4375 7.90625q2.4375 3.34375 6.984375 4.765625l10.140625 3.234375q7.390625 2.34375 11.4375 7.921875q4.0625 5.5625 4.0625 13.0625q0 6.078125 -2.84375 10.640625q-2.828125 4.5625 -7.953125 7.09375q-5.109375 2.53125 -11.90625 2.53125zm39.421875 -1.015625l0 -73.96875l9.125 0l0 31.609375l23.703125 0l0 -31.609375l9.125 0l0 73.96875l-9.125 0l0 -34.046875l-23.703125 0l0 34.046875l-9.125 0z" fill-rule="nonzero"/><path fill="#000000" fill-opacity="0.0" d="m595.70844 145.24391l204.85034 0l0 386.96063l-204.85034 0z" fill-rule="evenodd"/><path fill="#e06666" d="m616.3647 251.52391l0 -73.96875l9.109375 0l0 65.65625l33.4375 0l0 8.3125l-42.546875 0zm55.21875 10.14064l0 -7.6093903l48.640625 0l0 7.6093903l-48.640625 0zm80.5625 1.0l0 -95.250015l9.109375 0l0 95.250015l-9.109375 0z" fill-rule="nonzero"/><path fill="#e06666" d="m695.91156 374.13956q-10.34375 0 -15.8125 -5.71875q-5.46875 -5.734375 -5.46875 -15.5625l0 -53.703125l9.109375 0l0 53.703125q0 6.078125 2.984375 9.625q3.0 3.546875 9.1875 3.546875q6.078125 0 9.109375 -3.546875q3.046875 -3.546875 3.046875 -9.625l0 -53.703125l9.109375 0l0 53.703125q0 9.9375 -5.421875 15.609375q-5.421875 5.671875 -15.84375 5.671875z" fill-rule="nonzero"/><path fill="#e06666" d="m625.47406 505.86456l0 -95.25l24.828125 0l0 8.109375l-15.703125 0l0 79.046875l15.703125 0l0 8.09375l-24.828125 0zm48.4375 -11.140625l3.75 -20.265625l-8.609375 0l0 -6.59375l9.828125 0l3.640625 -20.265625l-9.921875 0l0 -6.578125l11.140625 0l3.75 -20.265625l7.09375 0l-3.75 20.265625l16.21875 0l3.75 -20.265625l7.09375 0l-3.75 20.265625l8.609375 0l0 6.578125l-9.828125 0l-3.640625 20.265625l9.921875 0l0 6.59375l-11.140625 0l-3.75 20.265625l-7.09375 0l3.75 -20.265625l-16.21875 0l-3.75 20.265625l-7.09375 0zm12.0625 -26.859375l16.21875 0l3.640625 -20.265625l-16.21875 0l-3.640625 20.265625zm55.53125 38.0l0 -8.09375l15.703125 0l0 -79.046875l-15.703125 0l0 -8.109375l24.828125 0l0 95.25l-24.828125 0z" fill-rule="nonzero"/></g></svg>
\ No newline at end of file diff --git a/front/src/assets/chessh_sm.svg b/front/src/assets/chessh_sm.svg new file mode 100644 index 0000000..d4e22d4 --- /dev/null +++ b/front/src/assets/chessh_sm.svg @@ -0,0 +1 @@ +<svg version="1.1" viewBox="0.0 0.0 960.0 960.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><clipPath id="p.0"><path d="m0 0l960.0 0l0 960.0l-960.0 0l0 -960.0z" clip-rule="nonzero"/></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l960.0 0l0 960.0l-960.0 0z" fill-rule="evenodd"/><defs><linearGradient id="p.1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.0 30.983922361253416 -30.983922361253416 0.0 0.0 0.0)" spreadMethod="pad" x1="0.06412605277195675" y1="-30.95757731162891" x2="0.06401389269320752" y2="0.02634504942150403"><stop offset="0.0" stop-color="#4d4d4d"/><stop offset="1.0" stop-color="#000000"/></linearGradient></defs><path fill="url(#p.1)" d="m479.18372 1.9868766l0 0c265.09668 0 480.0 214.0925 480.0 478.18896l0 0c0 126.82361 -50.57129 248.45282 -140.58875 338.13068c-90.017456 89.67786 -212.1073 140.05829 -339.41125 140.05829l0 0c-265.09668 0 -480.0 -214.09247 -480.0 -478.18896l0 0c0 -264.09647 214.90332 -478.18896 480.0 -478.18896z" fill-rule="evenodd"/><path fill="#000000" fill-opacity="0.0" d="m277.61417 76.85302l460.91342 0l0 806.6456l-460.91342 0z" fill-rule="evenodd"/><path fill="#b4a7d6" d="m305.41104 320.12177l74.671875 -200.53125l27.734375 0l-74.671875 200.53125l-27.734375 0zm174.71564 -161.06252l-24.75 -29.875l29.453125 0l22.8125 29.875l-27.515625 0zm93.01248 163.18752l0 -26.875q12.796875 -2.984375 21.96875 -10.765625q9.171875 -7.796875 14.078125 -19.421875q4.90625 -11.625015 4.90625 -25.92189l0 -38.609375q0 -14.71875 -4.90625 -26.453125q-4.90625 -11.734375 -14.078125 -19.625q-9.171875 -7.890625 -21.96875 -10.875l0 -26.249992q21.125 5.546875 36.265625 17.390617q15.140625 11.84375 23.359375 28.59375q8.21875 16.734375 8.21875 37.21875l0 38.609375q0 31.156265 -17.71875 52.812515q-17.703125 21.640625 -50.125 30.171875z" fill-rule="nonzero"/><path fill="#b4a7d6" d="m484.61105 554.79364q-22.828125 0 -35.734375 -12.484375q-12.90625 -12.484375 -12.90625 -34.03125l0 -111.359375l26.875 0l0 111.15625q0 11.078125 5.546875 17.265625q5.546875 6.1875 16.21875 6.1875q10.453125 0 16.109375 -6.1875q5.65625 -6.1875 5.65625 -17.265625l0 -111.15625l26.87497 0l0 111.359375q0 21.546875 -12.8125 34.03125q-12.7968445 12.484375 -35.828094 12.484375z" fill-rule="nonzero"/><path fill="#b4a7d6" d="m332.08292 832.12177l0 -200.53125l59.734375 0l0 24.53125l-33.078125 0l0 151.46875l33.078125 0l0 24.53125l-59.734375 0zm152.52814 -21.328125q-9.171875 0 -14.609375 -5.328125q-5.4375 -5.34375 -5.4375 -14.515625q0 -9.171875 5.4375 -14.609375q5.4375 -5.453125 14.609375 -5.453125q9.171875 0 14.609375 5.453125q5.4375 5.4375 5.4375 14.609375q0 9.171875 -5.4375 14.515625q-5.4375 5.328125 -14.609375 5.328125zm92.79373 21.328125l0 -24.53125l33.0625 0l0 -151.46875l-33.0625 0l0 -24.53125l59.734375 0l0 200.53125l-59.734375 0z" fill-rule="nonzero"/><path fill="#000000" fill-opacity="0.0" d="m983.22437 552.2332l641.1338 0l0 141.35431l-641.1338 0z" fill-rule="evenodd"/><path fill="#45818e" d="m1020.89624 659.3257q-5.671875 0 -9.875 -1.765625q-4.203125 -1.78125 -6.6875 -5.015625q-2.484375 -3.25 -2.984375 -7.703125l9.109375 0q0.609375 2.9375 3.296875 4.65625q2.6875 1.71875 7.140625 1.71875l4.265625 0q5.359375 0 8.0 -2.171875q2.640625 -2.1875 2.640625 -5.828125q0 -3.546875 -2.390625 -5.625q-2.375 -2.078125 -7.046875 -2.78125l-7.484375 -1.21875q-8.21875 -1.421875 -12.21875 -5.125q-4.0 -3.703125 -4.0 -10.78125q0 -7.5 4.765625 -11.609375q4.765625 -4.109375 14.28125 -4.109375l3.859375 0q8.0 0 12.859375 3.75q4.859375 3.75 5.875 10.140625l-9.109375 0q-0.609375 -2.640625 -3.046875 -4.203125q-2.4375 -1.578125 -6.578125 -1.578125l-3.859375 0q-5.171875 0 -7.546875 1.9375q-2.375 1.921875 -2.375 5.765625q0 3.453125 2.015625 5.078125q2.03125 1.609375 6.390625 2.3125l7.5 1.21875q9.015625 1.421875 13.109375 5.28125q4.109375 3.84375 4.109375 11.140625q0 7.703125 -4.921875 12.109375q-4.90625 4.40625 -14.828125 4.40625l-4.265625 0zm40.734375 -0.8125l0 -8.3125l19.765625 0l0 -39.109375l-17.234375 0l0 -8.3125l26.34375 0l0 47.421875l18.75 0l0 8.3125l-47.625 0zm23.3125 -65.765625q-3.359375 0 -5.28125 -1.71875q-1.921875 -1.71875 -1.921875 -4.65625q0 -3.046875 1.921875 -4.8125q1.921875 -1.78125 5.28125 -1.78125q3.34375 0 5.265625 1.78125q1.921875 1.765625 1.921875 4.8125q0 2.9375 -1.921875 4.65625q-1.921875 1.71875 -5.265625 1.71875zm35.5625 65.765625l0 -55.734375l8.0 0l0 6.6875l1.828125 0l-1.421875 2.4375q0 -4.65625 2.484375 -7.390625q2.484375 -2.75 6.734375 -2.75q4.671875 0 7.203125 3.453125q2.53125 3.4375 2.53125 9.71875l-2.734375 -5.46875l4.25 0l-1.921875 2.4375q0 -4.65625 2.578125 -7.390625q2.59375 -2.75 6.84375 -2.75q5.265625 0 8.15625 3.953125q2.890625 3.953125 2.890625 10.4375l0 42.359375l-8.40625 0l0 -42.453125q0 -3.453125 -1.46875 -5.375q-1.46875 -1.921875 -4.3125 -1.921875q-2.734375 0 -4.3125 1.875q-1.5625 1.875 -1.5625 5.3125l0 42.5625l-7.296875 0l0 -42.453125q0 -3.546875 -1.578125 -5.421875q-1.5625 -1.875 -4.40625 -1.875q-2.828125 0 -4.25 1.875q-1.421875 1.875 -1.421875 5.3125l0 42.5625l-8.40625 0zm63.4375 18.234375l0 -73.96875l9.109375 0l0 10.640625l1.828125 0l-1.828125 2.125q0 -6.375 4.203125 -10.078125q4.21875 -3.703125 11.109375 -3.703125q8.40625 0 13.421875 5.53125q5.015625 5.515625 5.015625 15.140625l0 16.3125q0 6.390625 -2.28125 11.0625q-2.28125 4.65625 -6.390625 7.1875q-4.09375 2.53125 -9.765625 2.53125q-6.796875 0 -11.0625 -3.75q-4.25 -3.75 -4.25 -10.03125l1.828125 2.125l-2.03125 0l0.203125 12.671875l0 16.203125l-9.109375 0zm21.375 -25.125q5.78125 0 9.0625 -3.4375q3.296875 -3.453125 3.296875 -9.9375l0 -15.203125q0 -6.484375 -3.296875 -9.921875q-3.28125 -3.453125 -9.0625 -3.453125q-5.578125 0 -8.921875 3.546875q-3.34375 3.546875 -3.34375 9.828125l0 15.203125q0 6.28125 3.34375 9.828125q3.34375 3.546875 8.921875 3.546875zm60.5 7.703125q-6.703125 0 -11.5625 -2.53125q-4.859375 -2.53125 -7.546875 -7.34375q-2.6875 -4.8125 -2.6875 -11.40625l0 -14.796875q0 -6.6875 2.6875 -11.4375q2.6875 -4.765625 7.546875 -7.296875q4.859375 -2.546875 11.5625 -2.546875q6.6875 0 11.546875 2.546875q4.859375 2.53125 7.546875 7.296875q2.6875 4.75 2.6875 11.34375l0 14.890625q0 6.59375 -2.6875 11.40625q-2.6875 4.8125 -7.546875 7.34375q-4.859375 2.53125 -11.546875 2.53125zm0 -8.109375q5.96875 0 9.3125 -3.34375q3.34375 -3.34375 3.34375 -9.828125l0 -14.796875q0 -6.484375 -3.34375 -9.828125q-3.34375 -3.34375 -9.3125 -3.34375q-5.890625 0 -9.28125 3.34375q-3.390625 3.34375 -3.390625 9.828125l0 14.796875q0 6.484375 3.390625 9.828125q3.390625 3.34375 9.28125 3.34375zm39.71875 7.296875l0 -55.734375l9.109375 0l0 10.640625l1.921875 0l-1.921875 2.125q0 -6.578125 3.953125 -10.171875q3.953125 -3.609375 10.953125 -3.609375q8.40625 0 13.359375 5.171875q4.96875 5.171875 4.96875 14.09375l0 37.484375l-9.109375 0l0 -36.484375q0 -5.96875 -3.203125 -9.15625q-3.1875 -3.203125 -8.65625 -3.203125q-5.671875 0 -8.96875 3.453125q-3.296875 3.4375 -3.296875 9.921875l0 35.46875l-9.109375 0zm60.078125 0l0 -8.3125l19.765625 0l0 -39.109375l-17.234375 0l0 -8.3125l26.34375 0l0 47.421875l18.75 0l0 8.3125l-47.625 0zm23.3125 -65.765625q-3.359375 0 -5.28125 -1.71875q-1.921875 -1.71875 -1.921875 -4.65625q0 -3.046875 1.921875 -4.8125q1.921875 -1.78125 5.28125 -1.78125q3.34375 0 5.265625 1.78125q1.921875 1.765625 1.921875 4.8125q0 2.9375 -1.921875 4.65625q-1.921875 1.71875 -5.265625 1.71875zm59.671875 66.78125q-6.6875 0 -11.703125 -2.53125q-5.015625 -2.53125 -7.75 -7.34375q-2.734375 -4.8125 -2.734375 -11.40625l0 -15.203125q0 -6.6875 2.734375 -11.4375q2.734375 -4.765625 7.75 -7.296875q5.015625 -2.546875 11.703125 -2.546875q9.640625 0 15.515625 5.171875q5.875 5.171875 6.171875 14.09375l-9.109375 0q-0.3125 -5.375 -3.609375 -8.265625q-3.28125 -2.890625 -8.96875 -2.890625q-5.96875 0 -9.515625 3.40625q-3.546875 3.390625 -3.546875 9.671875l0 15.296875q0 6.28125 3.546875 9.734375q3.546875 3.4375 9.515625 3.4375q5.6875 0 8.96875 -2.9375q3.296875 -2.9375 3.609375 -8.203125l9.109375 0q-0.296875 8.90625 -6.171875 14.078125q-5.875 5.171875 -15.515625 5.171875zm46.21875 -48.125l8.109375 -31.515625l13.171875 0l-13.171875 31.515625l-8.109375 0zm73.25 47.921875q-5.671875 0 -9.875 -1.765625q-4.203125 -1.78125 -6.6875 -5.015625q-2.484375 -3.25 -2.984375 -7.703125l9.109375 0q0.609375 2.9375 3.296875 4.65625q2.6875 1.71875 7.140625 1.71875l4.265625 0q5.359375 0 8.0 -2.171875q2.640625 -2.1875 2.640625 -5.828125q0 -3.546875 -2.390625 -5.625q-2.375 -2.078125 -7.046875 -2.78125l-7.484375 -1.21875q-8.21875 -1.421875 -12.21875 -5.125q-4.0 -3.703125 -4.0 -10.78125q0 -7.5 4.765625 -11.609375q4.765625 -4.109375 14.28125 -4.109375l3.859375 0q8.0 0 12.859375 3.75q4.859375 3.75 5.875 10.140625l-9.109375 0q-0.609375 -2.640625 -3.046875 -4.203125q-2.4375 -1.578125 -6.578125 -1.578125l-3.859375 0q-5.171875 0 -7.546875 1.9375q-2.375 1.921875 -2.375 5.765625q0 3.453125 2.015625 5.078125q2.03125 1.609375 6.390625 2.3125l7.5 1.21875q9.015625 1.421875 13.109375 5.28125q4.109375 3.84375 4.109375 11.140625q0 7.703125 -4.921875 12.109375q-4.90625 4.40625 -14.828125 4.40625l-4.265625 0z" fill-rule="nonzero"/><path fill="#000000" fill-opacity="0.0" d="m1038.4374 695.1458l530.8661 0l0 141.35437l-530.8661 0z" fill-rule="evenodd"/><path fill="#00ff00" d="m1056.0468 794.8321l0 -8.8125l31.421875 -15.390625q1.921875 -0.921875 3.59375 -1.578125q1.671875 -0.65625 2.484375 -0.859375q-0.921875 -0.203125 -2.640625 -0.859375q-1.71875 -0.65625 -3.4375 -1.578125l-31.421875 -15.5l0 -9.125l43.578125 21.796875l0 10.125l-43.578125 21.78125z" fill-rule="nonzero"/><path fill="#93c47d" d="m1199.828 802.44147q-6.6875 0 -11.609375 -2.53125q-4.90625 -2.53125 -7.59375 -7.34375q-2.671875 -4.8125 -2.671875 -11.40625l0 -33.4375q0 -6.6875 2.671875 -11.453125q2.6875 -4.765625 7.59375 -7.296875q4.921875 -2.53125 11.609375 -2.53125q6.6875 0 11.546875 2.59375q4.875 2.578125 7.5 7.34375q2.640625 4.75 2.640625 11.34375l-9.109375 0q0 -6.28125 -3.296875 -9.671875q-3.296875 -3.40625 -9.28125 -3.40625q-5.96875 0 -9.375 3.34375q-3.390625 3.34375 -3.390625 9.625l0 33.546875q0 6.28125 3.390625 9.6875q3.40625 3.390625 9.375 3.390625q5.984375 0 9.28125 -3.390625q3.296875 -3.40625 3.296875 -9.6875l9.109375 0q0 6.484375 -2.640625 11.296875q-2.625 4.8125 -7.5 7.40625q-4.859375 2.578125 -11.546875 2.578125zm39.328125 -1.015625l0 -73.96875l9.109375 0l0 18.234375l0 10.640625l1.921875 0l-1.921875 2.125q0 -6.578125 3.953125 -10.171875q3.953125 -3.609375 10.953125 -3.609375q8.40625 0 13.359375 5.171875q4.96875 5.171875 4.96875 14.09375l0 37.484375l-9.109375 0l0 -36.484375q0 -5.96875 -3.203125 -9.265625q-3.1875 -3.296875 -8.65625 -3.296875q-5.671875 0 -8.96875 3.546875q-3.296875 3.546875 -3.296875 10.03125l0 35.46875l-9.109375 0zm81.875 1.015625q-6.59375 0 -11.515625 -2.625q-4.90625 -2.640625 -7.59375 -7.453125q-2.6875 -4.8125 -2.6875 -11.203125l0 -15.203125q0 -6.484375 2.6875 -11.234375q2.6875 -4.765625 7.59375 -7.40625q4.921875 -2.640625 11.515625 -2.640625q6.578125 0 11.484375 2.640625q4.921875 2.640625 7.609375 7.40625q2.6875 4.75 2.6875 11.234375l0 9.828125l-34.65625 0l0 5.375q0 6.59375 3.34375 10.09375q3.34375 3.484375 9.53125 3.484375q5.265625 0 8.5 -1.875q3.25 -1.875 3.953125 -5.625l9.125 0q-0.921875 7.0 -6.84375 11.109375q-5.921875 4.09375 -14.734375 4.09375zm12.859375 -32.734375l0 -3.75q0 -6.578125 -3.296875 -10.125q-3.28125 -3.546875 -9.5625 -3.546875q-6.1875 0 -9.53125 3.546875q-3.34375 3.546875 -3.34375 10.125l0 2.9375l26.4375 0l-0.703125 0.8125zm48.328125 32.734375q-7.28125 0 -12.5 -2.421875q-5.21875 -2.4375 -8.0625 -7.0q-2.84375 -4.5625 -2.9375 -10.84375l9.125 0q0 5.578125 3.796875 8.828125q3.796875 3.234375 10.578125 3.234375q6.390625 0 9.984375 -3.140625q3.59375 -3.15625 3.59375 -8.71875q0 -4.46875 -2.375 -7.8125q-2.375 -3.34375 -6.84375 -4.65625l-10.03125 -3.140625q-7.59375 -2.328125 -11.703125 -7.796875q-4.09375 -5.46875 -4.09375 -12.875q0 -5.96875 2.671875 -10.375q2.6875 -4.421875 7.546875 -6.890625q4.875 -2.484375 11.453125 -2.484375q9.734375 0 15.609375 5.421875q5.875 5.421875 5.984375 14.4375l-9.125 0q0 -5.484375 -3.296875 -8.5625q-3.28125 -3.09375 -9.265625 -3.09375q-5.875 0 -9.171875 2.84375q-3.296875 2.828125 -3.296875 7.890625q0 4.5625 2.4375 7.90625q2.4375 3.34375 6.984375 4.765625l10.140625 3.234375q7.390625 2.34375 11.4375 7.921875q4.0625 5.5625 4.0625 13.0625q0 6.078125 -2.84375 10.640625q-2.828125 4.5625 -7.953125 7.09375q-5.109375 2.53125 -11.90625 2.53125zm60.796875 0q-7.28125 0 -12.5 -2.421875q-5.21875 -2.4375 -8.0625 -7.0q-2.84375 -4.5625 -2.9375 -10.84375l9.125 0q0 5.578125 3.796875 8.828125q3.796875 3.234375 10.578125 3.234375q6.390625 0 9.984375 -3.140625q3.59375 -3.15625 3.59375 -8.71875q0 -4.46875 -2.375 -7.8125q-2.375 -3.34375 -6.84375 -4.65625l-10.03125 -3.140625q-7.59375 -2.328125 -11.703125 -7.796875q-4.09375 -5.46875 -4.09375 -12.875q0 -5.96875 2.671875 -10.375q2.6875 -4.421875 7.546875 -6.890625q4.875 -2.484375 11.453125 -2.484375q9.734375 0 15.609375 5.421875q5.875 5.421875 5.984375 14.4375l-9.125 0q0 -5.484375 -3.296875 -8.5625q-3.28125 -3.09375 -9.265625 -3.09375q-5.875 0 -9.171875 2.84375q-3.296875 2.828125 -3.296875 7.890625q0 4.5625 2.4375 7.90625q2.4375 3.34375 6.984375 4.765625l10.140625 3.234375q7.390625 2.34375 11.4375 7.921875q4.0625 5.5625 4.0625 13.0625q0 6.078125 -2.84375 10.640625q-2.828125 4.5625 -7.953125 7.09375q-5.109375 2.53125 -11.90625 2.53125zm39.421875 -1.015625l0 -73.96875l9.125 0l0 31.609375l23.703125 0l0 -31.609375l9.125 0l0 73.96875l-9.125 0l0 -34.046875l-23.703125 0l0 34.046875l-9.125 0z" fill-rule="nonzero"/><path fill="#000000" fill-opacity="0.0" d="m1419.4984 160.73735l204.85046 0l0 386.96063l-204.85046 0z" fill-rule="evenodd"/><path fill="#e06666" d="m1440.1547 267.01736l0 -73.968765l9.109375 0l0 65.656265l33.4375 0l0 8.3125l-42.546875 0zm55.21875 10.140625l0 -7.609375l48.640625 0l0 7.609375l-48.640625 0zm80.5625 1.0l0 -95.250015l9.109375 0l0 95.250015l-9.109375 0z" fill-rule="nonzero"/><path fill="#e06666" d="m1519.7015 389.633q-10.34375 0 -15.8125 -5.71875q-5.46875 -5.734375 -5.46875 -15.5625l0 -53.703125l9.109375 0l0 53.703125q0 6.078125 2.984375 9.625q3.0 3.546875 9.1875 3.546875q6.078125 0 9.109375 -3.546875q3.046875 -3.546875 3.046875 -9.625l0 -53.703125l9.109375 0l0 53.703125q0 9.9375 -5.421875 15.609375q-5.421875 5.671875 -15.84375 5.671875z" fill-rule="nonzero"/><path fill="#e06666" d="m1449.264 521.358l0 -95.24997l24.828125 0l0 8.109375l-15.703125 0l0 79.046844l15.703125 0l0 8.09375l-24.828125 0zm48.4375 -11.1405945l3.75 -20.265625l-8.609375 0l0 -6.59375l9.828125 0l3.640625 -20.265625l-9.921875 0l0 -6.578125l11.140625 0l3.75 -20.265625l7.09375 0l-3.75 20.265625l16.21875 0l3.75 -20.265625l7.09375 0l-3.75 20.265625l8.609375 0l0 6.578125l-9.828125 0l-3.640625 20.265625l9.921875 0l0 6.59375l-11.140625 0l-3.75 20.265625l-7.09375 0l3.75 -20.265625l-16.21875 0l-3.75 20.265625l-7.09375 0zm12.0625 -26.859375l16.21875 0l3.640625 -20.265625l-16.21875 0l-3.640625 20.265625zm55.53125 37.99997l0 -8.09375l15.703125 0l0 -79.046844l-15.703125 0l0 -8.109375l24.828125 0l0 95.24997l-24.828125 0z" fill-rule="nonzero"/></g></svg>
\ No newline at end of file diff --git a/front/src/context/auth_context.js b/front/src/context/auth_context.js new file mode 100644 index 0000000..19747c6 --- /dev/null +++ b/front/src/context/auth_context.js @@ -0,0 +1,71 @@ +import { useEffect, useContext, createContext } from "react"; +import { useLocalStorage } from "../hooks/useLocalStorage"; + +const AuthContext = createContext({ + signedIn: false, + setSignedIn: () => null, + sessionOver: new Date(), + setSessionOver: () => null, + setPlayer: () => null, + player: null, + signOut: () => null, +}); + +export const useAuthContext = () => useContext(AuthContext); + +export const AuthProvider = ({ children }) => { + const [signedIn, setSignedIn] = useLocalStorage("signedIn", false); + const [sessionOver, setSessionOver] = useLocalStorage( + "sessionOver", + Date.now() + ); + const [player, setPlayer] = useLocalStorage("player", null); + + const setDefaults = () => { + setPlayer(null); + setSessionOver(Date.now()); + setSignedIn(false); + }; + + const signOut = () => + fetch("/api/player/logout", { + method: "GET", + credentials: "same-origin", + }).then(() => setDefaults()); + + useEffect(() => { + setTimeout(() => { + setSessionOver((sessionOver) => { + if (Date.now() >= sessionOver) { + setSignedIn((signedIn) => { + if (signedIn) + alert( + "Session expired. Any further privileged requests will fail until signed in again." + ); + + return false; + }); + setPlayer(null); + } + return sessionOver; + }); + }, sessionOver - Date.now()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sessionOver]); + + return ( + <AuthContext.Provider + value={{ + signedIn, + setSignedIn, + sessionOver, + setSessionOver, + signOut, + setPlayer, + player, + }} + > + {children} + </AuthContext.Provider> + ); +}; diff --git a/front/src/hooks/useLocalStorage.js b/front/src/hooks/useLocalStorage.js new file mode 100644 index 0000000..f00a11b --- /dev/null +++ b/front/src/hooks/useLocalStorage.js @@ -0,0 +1,31 @@ +import { useState, useEffect } from "react"; + +const STORAGE_KEYS_PREFIX = "chessh-"; + +const useStorage = (storage, keyPrefix) => (storageKey, fallbackState) => { + if (!storageKey) + throw new Error( + `"storageKey" must be a nonempty string, but "${storageKey}" was passed.` + ); + + const storedString = storage.getItem(keyPrefix + storageKey); + let parsedObject = null; + + if (storedString !== null) parsedObject = JSON.parse(storedString); + + // eslint-disable-next-line react-hooks/rules-of-hooks + const [value, setValue] = useState(parsedObject ?? fallbackState); + + // eslint-disable-next-line react-hooks/rules-of-hooks + useEffect(() => { + storage.setItem(keyPrefix + storageKey, JSON.stringify(value)); + }, [value, storageKey]); + + return [value, setValue]; +}; + +// eslint-disable-next-line react-hooks/rules-of-hooks +export const useLocalStorage = useStorage( + window.localStorage, + STORAGE_KEYS_PREFIX +); diff --git a/front/src/index.css b/front/src/index.css new file mode 100644 index 0000000..40d45cb --- /dev/null +++ b/front/src/index.css @@ -0,0 +1,225 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +:root { + --main-bg-color: #282828; + --primary-text-color: #ebdbb2; + --success-color: #689d6a; + --success-color-hover: #8ec07c; + --gold-color: #d79921; + --gold-color-hover: #fabd2f; + --blue-color: #458488; + --blue-color-hover: #83a598; + --purple-color: #b16286; + --purple-color-hover: #d3869b; + --red-color: #cc241d; + --red-color-hover: #fb4934; +} + +@font-face { + font-family: "DM Mono"; + src: url("./assets/DMMono-Light.ttf"); +} + +body { + margin: 0; + padding: 0; + background-color: var(--main-bg-color); + font-family: "DM Mono"; + color: var(--primary-text-color); +} + +.button { + cursor: pointer; + flex-shrink: 0; + color: var(--main-bg-color); + text-decoration: none; + border-radius: 8px; + border: var(--primary-text-color) solid 1px; + background-color: var(--success-color); + padding: 0.5rem; + + font-family: "DM Mono"; +} +.button:hover { + background-color: var(--success-color-hover); +} +.gold { + background-color: var(--gold-color); +} +.gold:hover { + background-color: var(--gold-color-hover); +} +.red { + color: var(--primary-text-color); + background-color: var(--red-color); +} +.red:hover { + background-color: var(--red-color-hover); +} + +.logo { + width: 6rem; + height: 6rem; +} + +.navbar { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + margin-bottom: 1rem; + border-radius: 12px; + padding: 0.5rem; + padding-left: 2rem; + padding-right: 2rem; + border: var(--purple-color) solid 1px; +} + +a { + text-decoration: underline; + color: var(--success-color); +} + +a:hover { + background-color: var(--success-color-hover); + text-decoration: none; +} + +.link { + font-size: 1.25rem; +} + +.nav { + display: flex; + flex-direction: row; + justify-content: space-around; + align-items: center; + gap: 2rem; +} + +.flex-row-around { + display: flex; + flex-direction: row; + justify-content: space-around; + align-items: center; + gap: 2rem; +} + +.flex-end-row { + display: flex; + flex-direction: row; + justify-content: flex-end; + gap: 1rem; +} + +.container { + padding-top: 1rem; + max-width: 1200px; + width: 80%; + + margin-left: auto; + margin-right: auto; +} + +.demo-container { + max-width: 900px; + width: 80%; + + border: 1px solid #b16286; + border-radius: 8px; + margin: 0; + padding: 24px; + + background-color: var(--main-bg-color); + + box-shadow: rgb(0, 0, 0, 0.6) 6px 45px 45px -12px; + + position: absolute; + top: 50%; + left: 50%; + -moz-transform: translateX(-50%) translateY(-50%); + -webkit-transform: translateX(-50%) translateY(-50%); + transform: translateX(-50%) translateY(-50%); +} + +.key-card { + display: flex; + justify-content: space-around; + flex-direction: row; + align-items: center; + padding-left: 1rem; + padding-right: 1rem; + border-radius: 12px; + border: solid 1px var(--gold-color); + margin-top: 12px; + gap: 0.5rem; +} + +.key-card-collection { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.5rem; +} + +input, +textarea { + font-family: "DM Mono"; + color: var(--primary-text-color); + background-color: rgba(0, 0, 0, 0.2); + border-radius: 4px; + border: 1px solid var(--primary-text-color); +} +input:focus, +textarea:focus { + border: 1px solid var(--gold-color); +} + +.modal { + display: flex; + flex-direction: column; + gap: 1rem; + + padding: 3rem; + top: 50%; + left: 50%; + -moz-transform: translateX(-50%) translateY(-50%); + -webkit-transform: translateX(-50%) translateY(-50%); + transform: translateX(-50%) translateY(-50%); + position: absolute; + + border-radius: 12px; + border: solid 1px var(--purple-color); + background-color: var(--main-bg-color); +} + +@media screen and (max-width: 680px) { + .container { + width: 95%; + } + .navbar { + flex-direction: column; + } + .key-card { + flex-direction: column; + justify-content: start; + gap: 0; + align-items: start; + padding-bottom: 1rem; + } + .flex-row-around { + flex-direction: column; + gap: 0; + } +} + +@media screen and (max-width: 1200px) { + .key-card-collection { + display: flex; + flex-direction: column; + } +} diff --git a/front/src/index.js b/front/src/index.js new file mode 100644 index 0000000..e827e0a --- /dev/null +++ b/front/src/index.js @@ -0,0 +1,47 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { createBrowserRouter, RouterProvider } from "react-router-dom"; + +import { AuthProvider } from "./context/auth_context"; +import { Root } from "./root"; +import { Demo } from "./routes/demo"; +import { Home } from "./routes/home"; +import { Keys } from "./routes/keys"; +import { Password } from "./routes/password"; +import { AuthSuccessful } from "./routes/auth_successful"; + +import "./index.css"; + +const router = createBrowserRouter([ + { path: "/", element: <Demo /> }, + { + path: "/", + element: <Root />, + errorElement: <> </>, + children: [ + { + path: "home", + element: <Home />, + }, + { + path: "password", + element: <Password />, + }, + { + path: "keys", + element: <Keys />, + }, + { + path: "auth-successful", + element: <AuthSuccessful />, + }, + ], + }, +]); + +const root = ReactDOM.createRoot(document.getElementById("root")); +root.render( + <AuthProvider> + <RouterProvider router={router} /> + </AuthProvider> +); diff --git a/front/src/root.jsx b/front/src/root.jsx new file mode 100644 index 0000000..61f8615 --- /dev/null +++ b/front/src/root.jsx @@ -0,0 +1,47 @@ +import { Link, Outlet } from "react-router-dom"; + +import logo from "./assets/chessh_sm.svg"; + +import { useAuthContext } from "./context/auth_context"; + +export const Root = () => { + const { signedIn, signOut } = useAuthContext(); + + return ( + <> + <div className="container"> + <div className="navbar"> + <div className="flex-row-around"> + <Link to="/home"> + <img src={logo} className="logo" alt="CheSSH Logo" /> + </Link> + </div> + <div className="nav"> + {signedIn ? ( + <> + <Link className="link" to="/password"> + Password + </Link> + <Link className="link" to="/keys"> + Keys + </Link> + <Link className="button" onClick={signOut} to="/"> + Sign Out + </Link> + </> + ) : ( + <> + <a href={process.env.REACT_APP_GITHUB_OAUTH} className="button"> + 🐙 Login w/ GitHub 🐙 + </a> + </> + )} + </div> + </div> + <div className="content"> + <Outlet /> + </div> + </div> + </> + ); +}; diff --git a/front/src/routes/auth_successful.jsx b/front/src/routes/auth_successful.jsx new file mode 100644 index 0000000..7c66587 --- /dev/null +++ b/front/src/routes/auth_successful.jsx @@ -0,0 +1,47 @@ +import { useEffect } from "react"; +import { Link } from "react-router-dom"; + +import { useAuthContext } from "../context/auth_context"; + +export const AuthSuccessful = () => { + const { player, setPlayer, signedIn, setSignedIn, setSessionOver } = + useAuthContext(); + + useEffect(() => { + fetch("/api/player/token/me", { + credentials: "same-origin", + }) + .then((r) => r.json()) + .then(({ player, expiration }) => { + setSignedIn(!!player); + setPlayer(player); + setSessionOver(expiration); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (signedIn) { + return ( + <> + <h3>Hello there, {player?.username || ""}! </h3> + <div> + <span> If you have not already done so: </span> + <Link to="/keys" className="button"> + Add a Public Key + </Link> + </div> + <br /> + <div> + <Link to="/home" className="button"> + Go Home + </Link> + </div> + </> + ); + } + return ( + <> + <p>Loading...</p> + </> + ); +}; diff --git a/front/src/routes/demo.jsx b/front/src/routes/demo.jsx new file mode 100644 index 0000000..b1a2f88 --- /dev/null +++ b/front/src/routes/demo.jsx @@ -0,0 +1,61 @@ +import { useEffect, useRef, useState } from "react"; +import { Link } from "react-router-dom"; + +import * as AsciinemaPlayer from "asciinema-player"; +import "asciinema-player/dist/bundle/asciinema-player.css"; + +const demoProps = { + theme: "tango", + startAt: 12, + autoPlay: true, +}; + +const demoCast = "/chessh.cast"; +const demoCastElementId = "demo"; + +export const Demo = () => { + const player = useRef(null); + const [renderedPlayer, setRenderedPlayer] = useState(false); + + useEffect(() => { + if (!renderedPlayer) { + AsciinemaPlayer.create( + demoCast, + document.getElementById(demoCastElementId), + demoProps + ); + setRenderedPlayer(true); + } + }, [renderedPlayer, player]); + + return ( + <div className="demo-container"> + <h1> + Welcome to <span style={{ color: "green" }}>> CheSSH!</span> + </h1> + <div className="flex-row-around"> + <p> + CheSSH is a multiplayer, scalable, free, open source, and (optionally) + passwordless game of Chess over the SSH protocol, written in Elixir. + </p> + <a + className="button gold" + href="https://github.com/Simponic/chessh" + target="_blank" + rel="noreferrer" + > + 🌟 Star Repo 🌟 + </a> + </div> + <hr /> + <div ref={player} id={demoCastElementId} /> + <hr /> + <div className="flex-row-around"> + <h3>Would you like to play a game?</h3> + <Link className="button" to="/home"> + Yes, Falken ⇒ + </Link> + </div> + </div> + ); +}; diff --git a/front/src/routes/home.jsx b/front/src/routes/home.jsx new file mode 100644 index 0000000..baccb5f --- /dev/null +++ b/front/src/routes/home.jsx @@ -0,0 +1,62 @@ +import { CopyBlock, dracula } from "react-code-blocks"; +import { Link } from "react-router-dom"; + +import { useAuthContext } from "../context/auth_context"; + +export const Home = () => { + const { player, signedIn } = useAuthContext(); + + if (signedIn) { + const sshConfig = `Host chessh + Hostname ${process.env.REACT_APP_SSH_SERVER} + Port ${process.env.REACT_APP_SSH_PORT} + User ${player?.username} + PubkeyAuthentication yes`; + return ( + <> + <h2>Welcome, {player?.username}</h2> + <hr /> + <h3>Getting Started</h3> + <ol> + <div> + <li> + Add a <Link to="/keys">public key</Link>, or{" "} + <Link to="/password">set a password</Link>. + </li> + </div> + <div> + <li> + Insert the following block in your{" "} + <a href="https://linux.die.net/man/5/ssh_config">ssh config</a>: + </li> + + <CopyBlock + theme={dracula} + text={sshConfig} + showLineNumbers={true} + wrapLines + codeBlock + /> + </div> + + <div> + <li>Then, connect with:</li> + <CopyBlock + theme={dracula} + text={"ssh -t chessh"} + language={"shell"} + showLineNumbers={false} + codeBlock + /> + </div> + </ol> + </> + ); + } + + return ( + <> + <p>Looks like you're not signed in 👀. </p> + </> + ); +}; diff --git a/front/src/routes/keys.jsx b/front/src/routes/keys.jsx new file mode 100644 index 0000000..4dee1ce --- /dev/null +++ b/front/src/routes/keys.jsx @@ -0,0 +1,204 @@ +import Modal from "react-modal"; +import { useEffect, useState, useCallback } from "react"; +import { useAuthContext } from "../context/auth_context"; + +Modal.setAppElement("#root"); + +const MINIMIZE_KEY_LEN = 40; +const minimizeKey = (key) => { + const n = key.length; + if (n >= MINIMIZE_KEY_LEN) { + const half = Math.floor(MINIMIZE_KEY_LEN / 2); + return key.substring(0, half) + "..." + key.substring(n - half, n); + } + return key; +}; + +const KeyCard = ({ onDelete, props }) => { + const { id, name, key } = props; + + const deleteThisKey = () => { + if ( + window.confirm( + "Are you sure? This will close all your current ssh sessions." + ) + ) { + fetch(`/api/keys/${id}`, { + credentials: "same-origin", + method: "DELETE", + }) + .then((r) => r.json()) + .then((d) => d.success && onDelete && onDelete()); + } + }; + + return ( + <div className="key-card"> + <h4 style={{ flex: 1 }}>{name}</h4> + <p style={{ flex: 4 }}>{minimizeKey(key)}</p> + + <button + style={{ flex: 0 }} + className="button red" + onClick={deleteThisKey} + > + Delete + </button> + </div> + ); +}; + +const AddKeyButton = ({ onSave }) => { + const [open, setOpen] = useState(false); + const [name, setName] = useState(""); + const [key, setKey] = useState(""); + const [errors, setErrors] = useState(null); + + const setDefaults = () => { + setName(""); + setKey(""); + setErrors(null); + }; + + const close = () => { + setDefaults(); + setOpen(false); + }; + + const createKey = () => { + fetch(`/api/player/keys`, { + credentials: "same-origin", + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + key: key.trim(), + name: name.trim(), + }), + }) + .then((r) => r.json()) + .then((d) => { + if (d.success) { + if (onSave) { + onSave(); + } + close(); + } else if (d.errors) { + if (typeof d.errors === "object") { + setErrors( + Object.keys(d.errors).map( + (field) => `${field}: ${d.errors[field].join(",")}` + ) + ); + } else { + setErrors([d.errors]); + } + } + }); + }; + + return ( + <div> + <button className="button" onClick={() => setOpen(true)}> + + Add Key + </button> + <Modal + isOpen={open} + onRequestClose={close} + className="modal" + contentLabel="Add Key" + > + <div> + <h3>Add SSH Key</h3> + <p> + Not sure about this? Check{" "} + <a + href="https://www.ssh.com/academy/ssh/keygen" + target="_blank" + rel="noreferrer" + > + here + </a>{" "} + for help! + </p> + <hr /> + <p>Key Name *</p> + <input + value={name} + onChange={(e) => setName(e.target.value)} + required + /> + </div> + <div> + <p>SSH Key *</p> + <textarea + cols={40} + rows={5} + value={key} + onChange={(e) => setKey(e.target.value)} + required + /> + </div> + <div> + {errors && ( + <div style={{ color: "red" }}> + {errors.map((error, i) => ( + <p key={i}>{error}</p> + ))} + </div> + )} + </div> + <div className="flex-end-row"> + <button className="button" onClick={createKey}> + Add + </button> + <button className="button red" onClick={close}> + Cancel + </button> + </div> + </Modal> + </div> + ); +}; + +export const Keys = () => { + const { + player: { id: userId }, + } = useAuthContext(); + const [keys, setKeys] = useState(null); + + const refreshKeys = useCallback( + () => + fetch(`/api/player/${userId}/keys`) + .then((r) => r.json()) + .then((keys) => setKeys(keys)), + [userId] + ); + + useEffect(() => { + if (userId) { + refreshKeys(); + } + }, [userId, refreshKeys]); + + if (!keys) return <p>Loading...</p>; + + if (Array.isArray(keys)) { + return ( + <> + <h2>My Keys</h2> + <AddKeyButton onSave={refreshKeys} /> + <div className="key-card-collection"> + {keys.length ? ( + keys.map((key) => ( + <KeyCard key={key.id} onDelete={refreshKeys} props={key} /> + )) + ) : ( + <p>Looks like you've got no keys, try adding some!</p> + )} + </div> + </> + ); + } +}; diff --git a/front/src/routes/password.jsx b/front/src/routes/password.jsx new file mode 100644 index 0000000..11fb775 --- /dev/null +++ b/front/src/routes/password.jsx @@ -0,0 +1,144 @@ +import { useState } from "react"; +import { Link } from "react-router-dom"; + +export const Password = () => { + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [errors, setErrors] = useState(null); + const [success, setSuccess] = useState(false); + + const resetFields = () => { + setErrors(null); + setPassword(""); + setConfirmPassword(""); + }; + + const reset = () => { + resetFields(); + setSuccess(false); + }; + + const deletePassword = () => { + if ( + window.confirm( + "Are you sure? This will close all your current ssh sessions." + ) + ) { + fetch(`/api/player/token/password`, { + method: "DELETE", + credentials: "same-origin", + }) + .then((r) => r.json()) + .then((r) => { + if (r.success) { + resetFields(); + setSuccess(true); + } + }); + } + }; + + const submitPassword = () => { + if ( + window.confirm( + "Are you sure? This will close all your current ssh sessions." + ) + ) { + fetch(`/api/player/token/password`, { + method: "PUT", + credentials: "same-origin", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + password, + password_confirmation: confirmPassword, + }), + }) + .then((r) => r.json()) + .then((p) => { + if (p.success) { + resetFields(); + setSuccess(true); + } else if (p.errors) { + if (typeof p.errors === "object") { + setErrors( + Object.keys(p.errors).map( + (field) => `${field}: ${p.errors[field].join(",")}` + ) + ); + } else { + setErrors([p.errors]); + } + } + }); + } + }; + + return ( + <> + <div> + <h3>Update SSH Password</h3> + <p> + An SSH password allows you to connect from any device. However, it is + inherently less secure than a <Link to="/keys">public key</Link>. + </p> + <p>Use a password at your own risk.</p> + </div> + <hr /> + <div> + <h4> Previously set a password and no longer want it? </h4> + <button className="button red" onClick={deletePassword}> + Delete Password + </button> + </div> + <div> + <h4>Or if you're dead set on it...</h4> + <div> + <p>Password *</p> + <input + value={password} + onChange={(e) => setPassword(e.target.value)} + type="password" + required + /> + </div> + <div> + <p>Confirm Password *</p> + <input + value={confirmPassword} + type="password" + onChange={(e) => setConfirmPassword(e.target.value)} + required + /> + </div> + <div> + {errors && ( + <div style={{ color: "red" }}> + {errors.map((error, i) => ( + <p key={i}>{error}</p> + ))} + </div> + )} + </div> + + <div + className="flex-end-row" + style={{ justifyContent: "start", marginTop: "1rem" }} + > + <button className="button" onClick={submitPassword}> + Submit + </button> + <button className="button gold" onClick={reset}> + Reset Form + </button> + </div> + </div> + + <br /> + <div> + {success && <div style={{ color: "green" }}>Password updated</div>} + </div> + </> + ); +}; diff --git a/front/src/setupProxy.js b/front/src/setupProxy.js new file mode 100644 index 0000000..b23c857 --- /dev/null +++ b/front/src/setupProxy.js @@ -0,0 +1,21 @@ +const { createProxyMiddleware } = require("http-proxy-middleware"); + +module.exports = function (app) { + if (process.env.NODE_ENV != "production") { + app.use( + "/api", + createProxyMiddleware({ + target: "http://localhost:8080", + changeOrigin: true, + pathRewrite: (path, _req) => { + return path.replace("/api", ""); + }, + onProxyRes: function (proxyRes, req, res) { + proxyRes.headers["Access-Control-Allow-Origin"] = "*"; + proxyRes.headers["Access-Control-Allow-Methods"] = + "GET,PUT,POST,DELETE,PATCH,OPTIONS"; + }, + }) + ); + } +}; |