summaryrefslogtreecommitdiff
path: root/front/src
diff options
context:
space:
mode:
Diffstat (limited to 'front/src')
-rw-r--r--front/src/assets/DMMono-Light.ttfbin0 -> 48456 bytes
-rw-r--r--front/src/assets/chessh_lg.svg1
-rw-r--r--front/src/assets/chessh_sm.svg1
-rw-r--r--front/src/context/auth_context.js71
-rw-r--r--front/src/hooks/useLocalStorage.js31
-rw-r--r--front/src/index.css225
-rw-r--r--front/src/index.js47
-rw-r--r--front/src/root.jsx47
-rw-r--r--front/src/routes/auth_successful.jsx47
-rw-r--r--front/src/routes/demo.jsx61
-rw-r--r--front/src/routes/home.jsx62
-rw-r--r--front/src/routes/keys.jsx204
-rw-r--r--front/src/routes/password.jsx144
-rw-r--r--front/src/setupProxy.js21
14 files changed, 962 insertions, 0 deletions
diff --git a/front/src/assets/DMMono-Light.ttf b/front/src/assets/DMMono-Light.ttf
new file mode 100644
index 0000000..2b14cea
--- /dev/null
+++ b/front/src/assets/DMMono-Light.ttf
Binary files differ
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";
+ },
+ })
+ );
+ }
+};