Merge pull request #25 from LucidKobold/potty-chart-code

Potty chart code
This commit is contained in:
Lucid Kobold
2024-03-16 18:43:02 -04:00
committed by GitHub
42 changed files with 1184 additions and 2317 deletions

View File

@@ -4,7 +4,7 @@
<a href="https://github.com/LucidCreationsMedia/LucidCreationsWebsite/actions/workflows/njsscan-analysis.yml"><img alt="CodeQL Analysis" src="https://github.com/LucidCreationsMedia/LucidCreationsWebsite/actions/workflows/njsscan-analysis.yml/badge.svg?branch=main" /></a>
</p>
# [Lucid Creations Media Website](https://lucidcreations.media/)
# [Lucid Creations Media Website](https://new.lucidcreations.media/)
## About

17
example.env Normal file
View File

@@ -0,0 +1,17 @@
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
# This is for local/dev. Make sure to update ./prisma/schema.prisma to use the dev config.
DATABASE_URL="postgresql://postgres:randompassword@localhost:5432/dbname?schema=public"
# These are values from Vercel. Make sure to update ./prisma/schema.prisma to use the vercel config.
POSTGRES_DATABASE="dbname"
POSTGRES_PASSWORD="randompassword"
POSTGRES_HOST="region.postgres.vercel-storage.com"
POSTGRES_USER="postgres"
POSTGRES_PRISMA_URL="postgres://postgres:randompassword@region.postgres.vercel-storage.com/dbname?pgbouncer=true&connect_timeout=15"
POSTGRES_URL_NON_POOLING="postgres://postgres:randompassword@region.postgres.vercel-storage.com/dbname"
POSTGRES_URL="postgres://postgres:randompassword@region.postgres.vercel-storage.com/dbname"

0
example.env.development Normal file
View File

14
example.env.local Normal file
View File

@@ -0,0 +1,14 @@
# This section is only applicable during the beta and will be removed when the app is completed.
NEXT_PUBLIC_APP_VERSION="0.2.0"
# Auth Secrets
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=""
# Used when sending emails to users
EMAIL_SERVER_USER=""
EMAIL_SERVER_PASSWORD=""
SMTP_SERVER_HOST=""
SMTP_SERVER_PORT=""
EMAIL_FROM=""
GOOGLE_ID=""
GOOGLE_SECRET=""

View File

@@ -2,7 +2,7 @@
"private": true,
"name": "lucid-creations-website",
"homepage": "https://new.lucidcreations.media/",
"version": "0.0.2",
"version": "0.0.1",
"author": {
"name": "Lucid Creations Media",
"url": "https://lucidcreations.media",
@@ -17,33 +17,38 @@
},
"dependencies": {
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.3",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@fontsource/anonymous-pro": "^5.0.12",
"@fontsource/anybody": "^5.0.19",
"@fontsource/kalam": "^5.0.12",
"@fontsource/montserrat": "^5.0.17",
"@fontsource/tilt-neon": "^5.0.4",
"@iconify/react": "^4.1.1",
"@reduxjs/toolkit": "^2.2.1",
"date-fns": "^3.3.1",
"date-fns": "^3.5.0",
"formik": "^2.4.5",
"framer-motion": "^11.0.6",
"next": "14.1.0",
"framer-motion": "^11.0.14",
"next": "14.1.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^9.1.0",
"sharp": "^0.33.2"
},
"devDependencies": {
"@types/node": "^20.11.20",
"@types/react": "^18.2.58",
"@types/node": "^20.11.28",
"@types/react": "^18.2.66",
"@types/react-redux": "^7.1.33",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"eslint": "^8.57.0",
"eslint-config-next": "<13.4.9",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"prettier": "^3.2.5",
"typescript": "^5.3.3"
"typescript": "^5.4.2"
},
"packageManager": "yarn@4.1.0"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1 +1,212 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144.01 157.2"><defs><style>.cls-1{fill:#61a3d7;}</style></defs><path class="cls-1" d="M90.37,123A3.86,3.86,0,0,0,85,124.63q-7.68,13.26-15.31,26.55a11.53,11.53,0,0,0-.75,2,4.09,4.09,0,0,0,3,3.86,3.78,3.78,0,0,0,4.39-1.93q7.71-13.32,15.38-26.65A3.86,3.86,0,0,0,90.37,123Z"/><path class="cls-1" d="M116.26,123a3.84,3.84,0,0,0-5.34,1.59q-6.39,11-12.73,22.06a14.1,14.1,0,0,0-.8,2.11,4.26,4.26,0,0,0,2.93,3.76,3.86,3.86,0,0,0,4.54-1.89q6.4-11.07,12.79-22.16A3.84,3.84,0,0,0,116.26,123Z"/><path class="cls-1" d="M63.53,122.65a3.79,3.79,0,0,0-4.44,1.84q-6.48,11.17-12.89,22.37a4.49,4.49,0,0,0-.51,2.42,3.71,3.71,0,0,0,2.94,3.3,3.76,3.76,0,0,0,4.25-1.81q6.52-11.21,13-22.46a10,10,0,0,0,.62-1.82A4.18,4.18,0,0,0,63.53,122.65Z"/><path class="cls-1" d="M142.6,84.35C139,74,131.3,68.43,120.54,66.76c-.23-1.85-.35-3.69-.69-5.49A34.64,34.64,0,0,0,111.21,44l-5.49,5.48a27.13,27.13,0,0,1,7,20.22c-.19,3,1.76,4.82,4.68,4.78,7.72-.11,13.62,3.21,16.94,10.2a18,18,0,0,1-16.26,25.91c-10.16,0-20.32,0-30.48,0h-30a23,23,0,0,1-10.51-2.5l-5.68,5.68a30.08,30.08,0,0,0,16.13,4.63q30.14,0,60.28,0a29.86,29.86,0,0,0,4.81-.43C138.1,115.3,147.78,99.14,142.6,84.35Z"/><path class="cls-1" d="M139.83,3.83,136.54.54a1.82,1.82,0,0,0-2.59,0L.54,134a1.82,1.82,0,0,0,0,2.59l3.29,3.29a1.83,1.83,0,0,0,1.3.54,1.81,1.81,0,0,0,1.29-.54L139.83,6.42A1.82,1.82,0,0,0,139.83,3.83Z"/><path class="cls-1" d="M36.62,72.45A2.22,2.22,0,0,1,37.29,74c-.7,4.86-1.44,9.72-2.18,14.57L48.59,75.09h0l25.79-25.8h0L84,39.65a1.94,1.94,0,0,1-1.46-1.18C79.06,31.52,75.49,24.6,72,17.64c-1.34-2.68-3.35-4.34-6.35-4.35s-5,1.64-6.37,4.31c-3.5,7-7.08,14-10.59,20.94a1.84,1.84,0,0,1-1.52,1.14c-5.64.9-11.27,1.86-16.91,2.79-2.16.35-4.33.67-6.48,1a6.68,6.68,0,0,0-5.62,4.79A6.77,6.77,0,0,0,20,55.45Q28.33,63.94,36.62,72.45Z"/></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="708px" height="708px" viewBox="0 0 708 708" enable-background="new 0 0 708 708" xml:space="preserve"> <image id="image0" width="708" height="708" x="0" y="0"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAsQAAALECAQAAAClXO4IAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo
AAB1MAAA6mAAADqYAAAXcJy6UTwAAAACYktHRAD/h4/MvwAAAAlwSFlzAAAuIwAALiMBeKU/dgAA
AAd0SU1FB+gCHRYTFmPgxGwAAC0USURBVHja7d3rfdzGAa7x1/r5u/ZUILgC4VTgTQWmKzBdQZQK
DlNBmApMVxCqAi8r8KqCLCvIsgKfD9SFl70AmMs7l+efD7EtE5iRxMcjYBb47i8BiGzQXnv3IFCP
N+4BAM0ZtdVGK/cwUA9CDMQ1aqO3ek+KMR0hBmJ6zLAkUozpCDEQz7cMS6QYkxFiIJbnGZZIMSYi
xEAcrzMskWJMQoiBGA5nWCLFmIAQA+GOZ1gixTiLEAOhTmdYIsU4gxADYc5nWCLFOIkQAyGmZVgi
xTiBEAPLTc+wRIpxFCEGlpqXYYkU4whCDCwzP8MSKcZBhBhYYlmGJVKMAwgxMN/yDEukGK8QYmCu
sAxLpBgvEGJgnvAMS6QYzxBiYI44GZZIMZ4gxMB08TIskWJ8RYiBqeJmWCLF+IwQA9PEz7BEiiGJ
EAPTpMmwRIohQgxMkS7DEikGIQbOSpthiRR3jxADp6XPsESKO0eIgVPyZFgixV0jxMBx+TIskeKO
EWLgmLwZlkhxtwgxcFj+DEukuFOEGDjEk2GJFHeJEAOv+TIskeIOEWLgJW+GJVLcHUIMPOfPsESK
O0OIgafKyLBEirtCiIFvysmwRIo7QoiBL8rKsESKu0GIgUflZVgixZ0gxIAUO8P3EUdGijtAiIHY
Gf6kUb9GHB0pbh4hBmJneK29bkgxpiPE6F2KDEsixZiOEKNvqTIskWJMRojRs5QZlkgxJiLE6Ffq
DEukGJMQYvQqR4YlUowJCDH6lCvDEinGWYQYPcqZYYkU4wxCjP7kzrBEinESIUZvHBmWSDFOIMTo
iyvDEinGUYQYPXFmWCLFOIIQox/uDEukGAcRYvSihAxLpBgHEGL0oZQMS6QYrxBi9KCkDEukGC8Q
YrSvtAxLpBjPEGK0rsQMS6QYTxBitK3UDEukGF8RYrSs5AxLpBifEWK0q/QMS6QYkggx2lVDhiVS
DBFitKqWDEukGIQYTaopwxIp7h4hRntqy7BEijtHiNGaGjMskeKuEWK0pdYMS6S4Y4QYLak5wxIp
7hYhRjtqz7BEijtFiNGKFjIskeIuEWK0oZUMS6S4Q4QYLWgpwxIp7g4hRv1ay7BEijvz3V/uEQCh
9hEz/KChgAw/utRvEY9Wxn9gcBArYtTvQ8RjvdW1ezpfsSruBiFG/eIG6xfduCeUaGakuFiEGC0g
xdOQ4kIRYrSBFE9DiotEiNEKUjwNKS4QIUY7SPE0pLg4hBgtIcXTkOLCEGK0hRRPQ4qLQojRGlI8
DSkuCCFGe0jxNKS4GIQYLSLF05DiQhBitIkUT0OKi0CI0SpSPA0pLgAhRrtI8TSk2I4Qo2WkeBpS
bEaI0TZSPA0ptiLEaB0pnoYUGxFilGyIkgZSPA0ptiHEKNeobaQ0kOJpSLEJIUapHl8JGisNpHga
UmxBiFGmb29mJsV5Z0aKDQgxSvQtwxIpzj0zUpwdIUZ5nmdYIsW5Z0aKMyPEKM3rDEukOPfMSHFW
hBhlOZxhiRTnnhkpzogQoyTHMyyR4twzI8XZEGKU43SGJVKce2akOBNCjFKcz7BEinPPjBRnQYhR
hmkZlkhx7pmR4gwIMUowPcMSKc49M1KcHCGG37wMS6Q498xIcWKEGG7zMyyR4twzI8VJEWJ4Lcuw
RIpzz4wUJ0SI4bQ8wxIpzj0zUpwMIYZPWIYlUpx7ZqQ4EUIMl/AMS6Q498xIcRKEGB5xMiyR4twz
I8UJEGI4xMuwRIpzz4wUR0eIkV/cDEukOPfMSHFkhBi5xc+wRIpzz4wUR0WIkVeaDEukOPfMSHFE
hBg5pcuwRIpzz4wUR0OIkU/aDEukOPfMSHEkhBi5pM+wRIpzz4wUR0GIkUeeDEukOPfMSHEEhBg5
5MuwRIpzz4wUByPESC9vhiVSnHtmpDgQIUZq+TMskeLcMyPFQQgx0vJkWCLFuWdGigMQYqTky7BE
inPPjBQvRoiRjjfDEinOPTNSvBAhRir+DEukOPfMSPEihBhplJFhiRTnnhkpXoAQI4VyMiyR4twz
I8WzEWLEV1aGJVKce2akeCZCjNjKy7BEinPPjBTPQogRV5kZlkhx7pmR4hkIMWIqN8MSKc49M1I8
GSFGPGVnWCLFuWdGiicixIglboY/6fckoyTFeWdGiichxIgjdobXuiTFJqQ4O0KMGOJneC+RYhtS
nBkhRrg0GZZIsQ8pzooQI1S6DEuk2IcUZ0SIESZthiVS7EOKsyHECJE+wxIp9iHFmRBiLJcnwxIp
9iHFWRBiLJUvwxIp9iHFGRBiLJM3wxIp9iHFyRFiLJE/wxIp9iHFiRFizOfJsESKfUhxUoQYc/ky
LJFiH1KcECHGPN4MS6TYhxQnQ4gxhz/DEin2IcWJEGJMV0aGJVLsQ4qTIMSYqpwMS6TYhxQnQIgx
TVkZlkixDymOjhBjivIyLJFiH1IcGSHGeWVmWCLFPqQ4KkKMc8rNsESKfUhxRIQYp5WdYYkU+5Di
aAgxTik/wxIp9iHFkRBiHFdHhiVS7EOKoyDEOKaeDEuk2IcUR0CIcVhdGZZIsQ8pDkaIcUh9GZZI
sQ8pDkSI8VqdGZZIsQ8pDkKI8VK9GZZIsQ8pDkCI8VzdGZZIsQ8pXowQ46n6MyyRYh9SvBAhxjdt
ZFgixT6keBFCjC/aybBEin1I8QKEGI/ayrBEin1I8WyEGFKLGZZIsQ8pnokQo9UMS6TYhxTPQojR
boYlUuxDimcgxL1rO8MSKfYhxZMR4r61n2GJFPuQ4okIcc/6yLBEin1I8SSEuF/9ZFgixT6keAJC
3Ku+MiyRYh9SfBYh7lN/GZZIsQ8pPoMQ96jPDEuk2IcUn0SI+9NvhiVS7EOKTyDEvek7wxIp9iHF
RxHivpBhiRT7kOIjCHFPyPAXpNiFFB9EiPtBhp8ixS6k+ABC3Asy/BIpdiHFrxDiPpDhQ0ixCyl+
gRD3gAwfQ4pdSPEzhLh9ZPgUUuxCip8gxK0jw+eQYhdS/BUhbhsZnoIUu5Dizwhxy8jwVKTYhRRL
IsQtI8NzkGIXUixC3C4yPBcpdiHFhLhRZHgJUuzSfYoJcYvI8FKk2KXzFBPi9pDhEKTYpesUE+LW
kOFQpNil4xQT4raQ4RhIsUu3KSbELSHDsZBil05TTIjbQYZjIsUuXaaYELeCDMdGil06TDEhbgMZ
ToEUu3SXYkLcAjKcCil26SzFhLh+ZDglUuzSVYoJce3IcGqk2KWjFBPiupHhHEixSzcpJsQ1I8O5
kGKXTlJMiOtFhnMixS5dpJgQ14oM50aKXTpIMSGuExl2IMUuzaeYENeIDLuQYpfGU0yI60OGnUix
S9MpJsS1IcNupNil4RQT4rqQ4RKQYpdmU0yIa0KGS0GKXRpNMSGuBxkuCSl2aTLFhLgWZLg0pNil
wRQT4jqQ4RKRYpfmUkyIa0CGS0WKXRpLMSEuHxkuGSl2aSrFhLh0ZLh0pNiloRQT4rKR4RqQYpdm
UkyIS0aGa0GKXRpJMSEuFxmuCSl2aSLFhLhUZLg2pNilgRQT4jKR4RqRYpfqU0yIS0SGa0WKXSpP
MSEuDxmuGSl2qTrFhLg0ZLh2pNil4hQT4rKQ4RaQYpdqU0yIS0KGW0GKXSpNMSEuBxluCSl2qTLF
hLgUZLg1pNilwhQT4jKQ4RaRYpfqUkyIS0CGW0WKXSpLMSH2I8MtI8UuVaWYELuR4daRYpeKUkyI
vchwD0ixSzUpJsROZLgXpNilkhQTYh8y3BNS7FJFigmxCxnuDSl2qSDFhNijxgxf6Eob7fWX/tJO
N1onP2NrSLFL8Sn+7q+cPx14VFuGB33Q5YER3+mCdfhMN/olyXFj/S641G8RR/W7LpPM1j+zyN91
hDi/2jJ8pf939MceNGqX9OztIcUuBaeYEOdWV4YH3er9mRGMCc/fJlLsUmyKuUacV10ZHrU9k2Hp
fUHfZrXgWrFLsdeKWRHnVFeGB20njZY18RKsil2KXBUT4nzqyrAmrIa/+IHrxAuQYpcCU8yliVxq
y/DV5AyLFfEiXKBwKfACBSHOo7YMr/Rh1uywBCl2KS7FhDiH2jIsXc8a75h4NO0ixS6FpZgQp1df
hoeZVy9XicfTMlLsUlSKCXFq9WVYupr576+Sj6hlpNiloBSzayKtGjM86L+zv+a75KNqGzsoXArZ
QcGKOKUaMzx/PYxwrIpdClkVsyJOp84ML1kPsyKOgVWxSwGrYlbEqdSZYdbDPqyKXQpYFbMiTqPW
DC9bD7MijoVVsYt5VcyKOIVaM7x8PTxmGl/rWBW7mFfFhDi+ejM8d//wN6tMI2wfKXaxppgQx1Zv
hrk+XAZS7GJMMSGOq+YML18PIy5S7GJLMSGOqeYMh62Hx4zj7AEpdjGlmBDHU3eGw9bDq4wj7QMp
drGkmBDHUneGuT5cHlLsYkgxIY6j9gyHXh8eso62F6TYJXuKCXEMtWc4fD08ZB5vL0ixS+YUE+Jw
9WeY/RLlIsUuWVNMiEPVn+EY14d/zD7mfpBil4wp5lkTYVrI8NLnSzzH0yZS4hkULpmeQcGKOEQL
GY61X2IwjLwfrIpdMq2KCfFybWQ41vXhwTD2npBilywpJsRLtZHhePuHB8voe0KKXTKkmBAv00qG
4+2XGCzj7wspdkmeYkK8RCsZjvl5usE0g76QYpfEKSbE87WT4Zj7hwfTHHpDil2SppgQz9VOhuM+
X2K0zaI3pNglYYrZRzxPSxmOs3/4G3YS58O+YpdE+4pZEc/RUoYVfaWxNs6lN6yKXRKtignxdG1l
eB39Y8kr42z6Q4pdkqSYEE/VVoZTPH94tM6nP6TYJUGKCfE0rWU4/nqYEOdHil22eoh4tIEQT9Na
htO8j2Mwz6lHpNghbg8etNaOXRPntZfhtf5Iclz2TTiwgyKv+Bneco34vPYynO79dGv3xLrEqjin
JBkmxOe0mOEU14cfDe6pdYoU55Iow4T4tBYznPJ9zaN7at0ixTkkyzAhPqXNDKdbDxNiJ1KcWsIM
8xHn49rMsLRJ+n45btc5cdsunaQZZkV8TKsZTrkellgTe7EqTiVxhgnxYa1mOOX14Udr9wQ7R4pT
SJ5hQnxIuxlOvR5mRexHimPLkGGuEb/WboZTXx+WpHu2sBWAa8XxZMkwIX6p5Qyn+jzdc/+noBn3
ixTHkSnDXJp4ruUMp78+/GjtnibEBYo4smWYED/VdobTXx/+ch6UgBSHyphhQvxN2xmWrjOdZ+2e
KD4jxSGyZpgQf9F6hi/1PtOZ3vOmjmKQ4qUyZ5gQP2o9w6ts62GJNXFJSPES2TNMiKX2MyxdR5zf
eWv3dPEEKZ7LkGFC3EOGLxNtZTpm7Z4wniHFc1gyzD7i9jMcd4bT/KCde9p4hn3F05gy3PuKmAyn
sXZPGy+wKp7CluG+Q0yGU7lwTxyvkOJzjBnu+dJE+xm+0I0lw9IDW9iKxAWK46wZ7ndF3H6Gr/Uf
U4alt1ycKBKr4mPMGe41xO1n+EZ/t57/wv0TgINI8SH2DPcZ4toyPMz+ilR/BJ3uwnx+HEOKXyog
wz2GuK4MX+sv/Vd73erD5CDn3jd8yDueS1wsUvxUERnu72ZdXRke9eezv/+o27P/hX/5NS7/yPqx
aszDbbtHhWS4txVxXRnWq/XFT/pNe92ceCHRSrdJRzTdpXsAOIFVsVRQhvsKcW0ZPuytftGf2ury
4G/4G70zjOmQ91ycKBopLijDPYW4jQx/8V6/6X+6ebFN7Eo/Gcf00oV7ADip7xQXleF+rhHXmeFB
/z3779zrVjfaatBVATfpnvrEO52L1+u14sIy3EuI68ywJO1tH8qIgYf/lK/HFBeX4T4uTdSbYYX/
AltduAeAs/q7QFFghnsIcc0ZljYZzxXfpXsAmKC3FP9ZXobbD3HdGa49xOycqENfKY4nWoZbv0Zc
R4YHXWglaaftq1/Wlf6X7Gcnh3/rg3sImKSva8UxRMxw2yGuI8PSRj9+/et7Xb3YZrMrZmfwEves
iatBiueImuGWL03UkuHnN+Te6Tftnu0O3s46Vmne8UDManCBYrrIGW43xPVkWK+O/E5/6PbrSnKT
7Lx5XLoHgMlI8TTRM9xqiGvK8GE/afv56uo285lju+BtHRUhxeclyHCbIa4/w5L0Vv/SVmP1K+K3
7CauCik+LUmGpe+tk0qhjQw/eq8/9W/dV327Tvqw8J26krTWqJVGbSXdRvgGWEsatdJWu+r/rJHK
pZTktt17baJ8N91Ittt2iTLc3q6JGjO81h/Jz+H1twXr+gtdav3i1/JOVwv/hHChtdZ6/+yf3etD
MQ8NLQ07KA5JluHWQlxjhnsI8YOuZ6xnR13q4uifAubuTD4U9G/mvem3J6T4pYQZbivEdWZ42jPW
WnGnvbbaav9qZbvSqFHrE9H8Ymo8B33Q5dmj/cyq+AhS/FTSDLcU4lozLEnN/CLM9Onzz/HqxUWD
c86neD35zX185OQ4UvxF4gy3E+KaM1z7p+ccTr0Rb62rJ59VPI+HdR5HiqUMGW5l+1rdGRYhmO3q
yDp20I3+mJVhsSI+gc1sWTLcRohrz3D9n57L7+2BLXErXeu/hb2lpH69pzhLhlsIcf0ZZkW8xI8v
PihypZ3+vuhIO/dUCtdzijNluP5rxC1kWBr1p+Gstft2m+1SV4uvsnOzboo+rxVny3DtK+I2Mlz/
8yQ83ulK0lob/RZws/PGPY0q9LgqzpjhulfErWRYkrYzN3BBkh600U+BRxiMv+p16WtVnDXDNa+I
W8owa+Jl3gZmWLomw5P1tCrOnOF6Q9xWhrlh5HLjHkBVeklx9gzXGuLWMsyK2ON3/gM4Uw8pNmS4
zhC3l2EVMIIeXbkHUKHWU2zJcI0hbjHDfKTDgfXwMi2n2JTh+nZNtJlhqd8H//jwjInl2txBYctw
bSvidjMs3bkH0BnWwyFaXBUbM1xXiFvOMHK7cg+gcq2l2JrhmkLceobLGk3rWA+HaynF5gzXE+LW
M8wGtryu3QNoQisptme4lhC3n2HkdOf+tmtGCykuIMN1hJgMI64r9wAaUnuKi8hwDSEmw4jrjl3b
UdWc4kIyXH6IyTBiu3IPoDm1priYDJceYjKM2FgPp1BjigvKcNkhJsOI78o9gEbVluKiMlxyiMkw
4mM9nE5NKS4sw+WGmAwjhSv3AJpWS4qLy3CpISbDSIH1cGo1pLjADJcZYjKMNK7cA+hA+SlelZfh
EkPca4ZH9wCa94n1cBalp7hIpYW41wyr3d9i6A4pnq2sEPebYaT3XjetfhsXhxTPVFKIyTDS+kU7
fXAPohOkeJZyQkyGkd5b/UtbrsdnQYpnKCXEZBi5vNefunQPogukeLIyQkyGpcE9gK78phv3ELpA
iicq4S3OZFjiLc75/ZvrxVmU/sbnIvhDTIYf2X8hOvSzbt1D6AIpPssdYjL8BSHO754LQpmQ4jO8
14jJMJzecdMuE64Vn+EMMRmG24V7AN0gxSf5QkyGnxrdA+jU2j2AjpDiE1whJsPPrdwD6FS834M4
jxQf5QkxGQZ6RIqPcISYDAO9IsUH5Q8xGQZ6RooPyB1iMoyS3LkH0KVUKR4I8TRk+JjRPYBObd0D
6FSKFD9orZ17YkvlDDEZPm7lHkCnNu4BdOtSH6Mer8hXgk6XL8RkGKV54FkTNmPUPdyVZzhfiMkw
ynPrHkC34vag+gznCjEZRomu3QPoFBl+JUeIyfB5K/cAOvSp/m/fKpHhA9KHmAxPMboH0KEb9wC6
RIYPSh1iMoxS3boH0CEyfETaEJNhlOq+3j2n1SLDR6UMMRmebuUeQHe27gF0hwyfkC7EZHi6ld67
h9CdrXsAnSHDJ6UKMRmeY3QPAEiKDJ+RJsRkeJ4L9wCAhMjwWSlCTIbnWfEKSzSMDE8QP8RkeK4L
XthjMLoH0AkyPMl3f8U9Hhmeb6d37iF06F6DewgdIMMTxV0Rk+H5LsmwxTtCnBwZnixmiMnwElfu
AXRrcA+gcWR4hnghJsNLsB72WbsH0DQyPEusEJPhZS7dAwASIMMzxQkxGV5mrR/dQwCiI8OzxQgx
GV7qyj2Aru3cA2gUGV4gPMRkeKmB9bDVzj2AJpHhRUJDTIaXu3IPoHN79wAaRIYXCvtABxlebqX/
uYfQue/cA2gOGV4sZEVMhkN8cA+gcw/uATSHDAdYviImw2H4YLPXAw/jj4oMB1m6IibDYS7IsNlb
PtARERkOtCzEZDjUpXsA0B+64mPOUZDhYEsuTZDhUIP+6x4CPvuoG97nHIQMRzB/RUyGw126B4Cv
ftJ/tNM1a+OFyHAUc1fEZDgGbtSV6E43uu3y9+NyZDiSeSEmwzGs9Yd7CDjqd91yqWIiMhzNnBCT
4Thu9It7CDjpQbfk+CwyHNH0EJPhWPa8o64KjznedPv79DQyHNXUEJPhWC70H/cQMMtH3WrDI4Ke
IcORTQsxGY6HCxN1+qSNbrVxD6MIZDi6KSEmwzFxYaJmD9poo03X4SDDCZwPMRmOiQsTbeg3yGQ4
iXMhJsNxcWGiNXfaaNvNLT0ynMjpEJPh2Lgw0ap7bT//b+ceSjJkOJlTISbDsfFRjh48fM7xVrum
okyGE/r+6I+Q4fgu3ANABm/145O3Ed5J2mivrVT1ngsynNSxFTEZTmGr9+4hwOyT9p+z/GW9vHEP
aQIynNjhEJPhFHhLHU67+/z/O+2KugFIhpM7FGIynMalfnMPAVX5qOsC1stkOIPXzyMmw6ms3QNA
ZX7SH7o2j4EMZ/FyRUyG0+EpxFjid+OLBMhwJs9XxGQ4nYEMY5FfbKtiMpzN0xCT4ZTW7gGgWn+3
vMiJDGf0LcRkOK3RPQBU7EP2M5LhrL6EmAyntnYPABW7yHw+MpzZ4806MpzezLe0As98l/FcZDi7
NyLDOazdAwAmIsMGbzSQ4QxG9wCASciwxRuJDGcwugcATECGTd5EfFAfGT5ucA8AVbvPchYybPNG
3x41EoYMn/Jj+CHQsU2Gc5BhozdSlHyS4VMG9wBQuU3yM5BhqzdShJ8wMnza4B4Aqvag28RnIMNm
b6Tgq8Rk+JzRPQBU7Trx9xcZtgsPMRk+b+UeACr2kPihP2S4AOGXJj6Q4bPW7gGgYpdJv8PIcBHC
b9YN7ikADfs96fVhMlyIx4f+hGxgG9xTqACb17BM2ofCk+FiPIZ4H3CE0T0FoFH/JMO9eAzxNuAI
K/cUije6B4AK3etvukp4fDJclMcQ7wKOwB+7z1m5B4Dq/FNj0g9xkOHCfC8pdAPbin0TJw3uAaAq
d/qQOGtkuDjhlyb4o/c5g3sAqMaDfk2eNTJcoC836x4CjjG6JwE04Z8adJP4HGS4SF/eWbcNOMbK
PYnCrd0DQAU+6gddJb/IR4YL9SXEu4BjrN2TAKr2SX/TRcQngx9Dhov1/ef/3wUcY+WeROFG9wBQ
sAd9SH454hEZLliMSxPv3ZMoXLzf/GhNjqvCj8hw0b6siPdBRxky/LGqVqN7ACjUR33I9n1Dhgv3
ZUW8CTrK4J5GwVbuAaBAd5muCj8iw8V78/Wv2MCWxugeAApzr5+1zvIWukdkuALfQrwNOMrKPY2C
rdwDQEHu9auG5C8+eooMV+FbiHcBR1m7p1GwtXsAKMSD/qkx0625L8hwJb7/+le7gKOs3NMo2OAe
AArwoOvkb557jQxX41uItwFHYQPbce/cA4CZJ8JkuCpxVsTSyC/SQaN7ALByRZgMVybOipiLE8eM
7gHA5l5Xma8If0OGK/PmyV/fBxxn7Z5IoQb3AGBxp5+zfWbuNTJcne+f/PUu4Hrmyj2RQq3dA0Bm
D7rVtTVcZLhCT0O8DXjt0eieSKEG9wCQ0Sfd6Mb8vhoyXKWnId4HHGdwT6RIK/ZMdOJBN7opIFlk
uFJPrxFvAo5DcA4Z3QNAcg/6XT9rlfw9c1OQ4WrFWhGzge2QtXsASOqjbnVbzKtzyXDFnl8jDrFy
T6VAa/cAkEhZCZbIcOXePPs7NrDFNboHgAQ+6gdd2G/KPUeGK/c8xLuAI63cUynOwLs5GvSPjM8R
nooMV+95iLcBRxrdUynO2j0ARPerrt1DeIUMN+B5iPcBRxrcUynO2j0ARPar7bNyx5HhJjwP8Sbg
SGxge2ntHgCiIsNIJt41YsLz3MB/mppChpFQzBCv3JMpyto9AEREhpHUmxd//yngWKN7MkVZuweA
aMgwEnsZ4n3AsQb3ZIqydg8AkZBhJPcyxJuAYw3uyRRk5ApxI8gwMoi5Ih7dkynI2j0AREGGkcXL
EG8DjsXnyL65cA8AEZBhZPIyxLugo63d0ynEKuAR+ygFGUY2cUO8ck+nEBfuASAYGUZGb179Ezaw
hbtwDwCByDCyeh3ifcDRBvd0CrF2DwBBfifDyOt1iDcBRxvc0ynCBbctERkZbtzrEO8CjsYtKokL
E/W7KOxuBxluXtwQc7tOWhHi6r0t6teQDHfgdYi3Qccb3ROy48JECz64B/AVGe5C3Jt1XCXmwkQb
3hdyw5UMd+LNgX92F3C8wT0hs5V+cg8BUVy6ByAy3JFDId4HHG90T8js0j0ARPKL/X4HGe7IoRBv
A463ck/I7NI9AERzaT07Ge7KoRDvAo7X9wa2Ue/dQ0A0H4znJsOdiR3ivtfEH9wDQETvbDdeyXB3
Yl+a6Psq8YV7AIjq0nJWMtyh2Dfret43wQ7i1vxk+N1Mhrv05uA/ZQPbEmv3ABDdZebzkeFOHQ7x
LuCIa/eUbC7cA0B0H7KejQx3K36IV+4pmQy8LrRBOZ86QYY7djjE24Aj9rqBa3QPAElcZDoPGe7a
4RDvg445uCdlMboHgCQuspyFDHfucIg3Qccc3JOyWLsHgCTeZvj9TIa79+bIP38IOObonpRFn7Pu
wZD4+GQYR0O8DTjmyj0pC/YQ1+lOP+hn/TNoy2YIMgxJ3x/557uAp0as3ZMyGN0DwEI/aq0b3UqS
Ro1av3hiyKfAC3WnkWFIOhXi5VbuSRn0OOdWXOv28+3prbaf39+81qBB0pYMI4djId4GHLPXDWyo
01tdv/oE3SbDeckwvjp2jXgfdNTBPa3sRvcAEOAXw+U0MownjoV4E3TUwT2t7FbuASDITebzkWE8
8+boj7CBDf14p6uMZyPDeOF4iLcBRx3c0wJm+pDtTzVkGK+kCfHonhYw09tMT1ojwzjgeIj3AUcd
3NPKbuMeAIJ9yHAOMoyDjod4E3BUHgiJ+rxN/hh4Mowj0qyIuTiBGl0kPToZxlHf/XX8x/6afphX
/tbdH9ZDfrZw3P3nT3nunn3ac/8kQuOz22xrSdJq4ceKvks2DzKME74/8WP3AZcY1t2FGHHcaaed
ttprO/FPZZujP7LSqMdQDxo0Gh/MRIZx0qkQ7wJCvHJPLLu7gMckQfqoTfQnO+y10fNQjxo0atBw
8FcrZO/8KWQYZ5wK8TYgLaN7YtmF/Gz17EG3uv389LP0ttp+PddjlB/Xy+8kfUr0oQ4yjLNOhXgf
cNzBPbHsdu4BVOjj1wdQOjyNcjpkGBOculm31h8hR3ZPLbNRf7qHUJEHXeumg/94kWFMkmpFLI2d
/ZbZ6oG3dExyr6uvTwBuGxnGRG9O/Ng26MiDe2rZbdwDqMC9ftWgGzI8Gxlu2puTP/op4Mije2rZ
3boHULiP+puG7A+cdCHDmOF0iPcBRx7cU8vu1j2AYj3o3/pBFx39mYEMY5bTId4EHHlwTy27vT66
h1Cgj/pZK33o4MbcN2QYM31/8kf3AUce3VMzuNFP7iEU5JNudNtVgB+RYcz23clHJLCBba6QTyO2
o9cES2QYi5xeEe+Cjt3j8yau9Jt7CFYftek2wRIZxkLfnXloWMgzxX7u8vZVn2vie91qo00X29KO
I8NY6PszP/5p4eMEJWnsMsR9rYnvtdFGm47XwN+QYSx2LsT7gGMP7slZ3Oiy+cf/PGj7+Vlpe/dQ
ikGGEeBciDcBURnckzP50OhTJ+61/fy/nXsoxSHDCJJyRTy6J2ey1T/0L/cgonjQ9utj2jfuwRSM
DCPQuRBvA479VqtO/+h6rVG/uAex0J322pLeGcgwgp3bNbHS/wKO3t+b675YaRNwmzO3T9px0WEh
MowIUl6a6PcqsbTXuvgUf/oc3417IBUjw4jiXIjD3sU2uKdnVGqK77/ueEAoMoxIzod4H3D00T09
q7JSfKctH7mIigwjmvMh3gY8yGblnp7ZXqOu9XfrGO4+f+QCcZFhRHQ+xLuAo7f+wYYpPmijG8NL
lAhwSmQYUaUNsbrdwPbUrYaMD8jkqQ/pkWFEdm77GhvYYrnQddLHAT1o0/mTz3Ihw4jufIjDnsD2
azfvKJvig64SXKR4XAPfuifXCTKMBN5M+HfuAo4/uCdYlGsN+ofuox3vk/6h/6tBH8hwJmQYSUwJ
8T7g+KN7goXZ61qDfg36j5skfdSv+kGjrvlGzogMI5HzN+vYwBbfjW406EIXs/eVcCvOhwwjmSnX
iC+DHnXe45vrpltprVFrjWe+xe8+fxh55x5wt8gwEpoS4rBXiP5APCZZaZS0fvFPN5K2rH/tyDCS
mhLisH0TbGBD7cgwEptys056CDjD6J4iEIQMI7lpId4GnGHlniIQgAwjg2kh3gWcYe2eIrAYGUYW
6UO8ck8RWIgMI5P0lyZKeR4vMA8ZRjbTQrwPOsfgniQwGxlGRtNCvAk6x+CeJDATGUZW00LMBjb0
hAwjs6kh3gacY+WeJDADGUZ2U0O8CzjH2j1JYDIyDIMcIR7ckwQmIsOwmBriTcA5Ur4gCIiHDMNk
aoj3QWcZ3dMEziLDsMlxs47bdSgfGYbR1BAr6E1ra/c0gZPIMKymh3gXcJaVe5rACWQYZtNDvA04
y+ieJnAUGYbd9BDvA84yuKcJHEGGUYDpId4EnIUNbCgTGUYR8qyIuTiBEpFhFCLPNWJu16E8ZBjF
mB5iNrChJWQYBZkT4l3AeQb3RIEnyDCKMifEm4DzDO6JAl+RYRRmToj3AecZ3RMFPiPDKM6cEG8D
zhPvNz4QggyjQLmuEXO7DiUgwyhSvhCv3FNF98gwCjUnxNKngDON7qmic2QYxZoX4n3AmQb3VNE1
MoyCzQvxJuBMg3uq6BgZRtHyrYhH91TRLTKMws0L8TbgTGxggwcZRvHmhXgXdK61e7LoEBlGBXKG
eOWeLLpDhlGFeSFmAxtqQoZRibkh3gWca3RPFl0hw6jG3BBvA861ck8WHSHDqEjOFfGP7smiG2QY
VckZYtbEyIMMozI5L01wlRg5kGFUZ26I90FnG9zTRfPIMCo0N8TSXcDZBvd00TgyjCrND/E+4Gyj
e7poGhlGpeaHeBtwtpV7umgYGUa15od4F3A2NrAhFTKMiuUNMWtipEGGUbW8lya4SowUyDAqt+Rm
3UPA+Ub3hNEcMozqzQ8xt+tQEjKMBiwJ8S7gfGv3hNEUMowm5A7xyj1hNIQMoxG5L028d08YzSDD
aMaSEO+Dzji4p4wmkGE0ZEmIN0FnHNxTRgPIMJqyJMRiAxusyDAasyzE24AzrtxTRuXIMJqzLMS7
gDOu3VNG1cgwGpQ/xCv3lFExMowm5b80wQY2LEWG0aj8K2Ju12EZMoxm5V8Rc3ECS5BhNGxZiKX7
gHOu3ZNGdcgwmrY0xLuAc67ck0ZlyDAatzTE24Bzju5JoypkGM1bGuJ9wDkH96RRETKMDiwN8Sbg
nO/ck0Y1yDC64FgRc3EC05BhdMJxjZjbdZiCDKMbS0PMBjakRYbRkeUh3gWcdeWeNgpHhtGV5SHe
Bpx1dE8bRSPD6MzyEO8Dzjq4p42CkWF0Z3mINwFnZQMbjiHD6JDnGjG363AYGUaXXCFeuSeOApFh
dGp5iKVPAV87uieO4pBhdCskxPuArx3cE0dhyDA6FhLiTcDXDu6JoyhkGF1zrYhH98RREDKMzoWE
eBvwtfG+7VA7MozuhYR4F3TmtXvqKAIZBowhXrmnjgKQYUBhIWYDG8KQYUBSaIj3AV87uKcOMzIM
fBYW4k3A1w7uqcOKDANfhYV4F/C1P7qnDiMyDDzhCzG36/pFhoFnwkK8Dfrq0T15WJBh4AXfzTqu
EveJDAOvhIVYugv42sE9eWRHhoEDQkO8D/ja0T15ZEaGgYNCQ7wN+NqVe/LIigwDR4SGeBfwtWxg
6wkZBo5yhpg1cT/IMHCC89IEV4l7QYaBk5w369g30QcyDJwRGmI2sOE0MgycFR7iXcDXju7pIzEy
DEzwffARdgFfO+pKO+20DbzEgTKRYWCS7/4KPcKF/hNlJHfaa6ut9kEP10Q5yDAwUXiI1/oj+qge
tP26Ut44floQjAwDk4WHWIpwiJPuvyZ5zzdjJcgwMEOMEO8jfsud95jl3ec1M0pEhoFZYoR4Y/yw
8qevK2WyXAoyDMwUI8Q3+sU9jc/uviaZfRguZBiYLXz7WujzJmL6UdJPX/+OfRj5kWFggRgr4lgb
2FJiH0YOZBhYJEaIU2xgS4t9GCmQYWChGCFOv4EtLfZhxECGgcXihDjvBra0vu3D4IbfdGQYCBAn
xM4NbGmxD2MKMgwEiRPia/3dPZEs7rjhdwAZBgLF2L4W+nj4evz4ZOX/bR/GrutwkGEgWJwVcX37
JmLrdR8GGQYiiBPiUX+6J1KUe+26+OA1GQaiiBPi2jewpdXqPgwyDEQSK8Q7vXNPpRKt7MMgw0A0
sULc7ga2tGrdh0GGgYhihbiXDWwp1bMPgwwDUcXZvtbPBraU3j7bHlfuPgwyDEQWa0XMBra0ytmH
QYaB6GKFmA1sOfn2YZBhIIFYIWYDm0++fRhkGEgiXoi3eu+eDJRyHwYZBhKJF+Jy3lyHL77sw4hx
w48MA8nEC7EkjVpprZVGDXzAozgh+zDIMJBQ3BA/95jlQYPGhh4c34pvN/zO78Mgw0BSKUP83JeV
Mlku0WOWD9/wI8NAYvlC/NS3JK/4aHSBnu7DGMgwkJonxM99yfJaK3ZeNI0MAweVEOLnvqyUyXJr
yDBwRHkhfo59GK0gw8BRpYf4OfZh1IoMAyfUFeLn2IdRCzIMnFRziJ9iH0a5yDBwRishfo59GOUg
w8BZbYb4OfZh+JBhYIIeQvwc+zDyIcPAJP2F+Dn2YaRDhoGJeg/xc+zDiIcMA5MR4mPYhxGCDAMz
EOJpVhq54TcZGQZmIcRLfNuHwQ2/18gwMBMhDsc+jKfIMDAbIY5t1NDxDT8yDCxAiNP6tg+jhxt+
ZBhYhBDn0/o+DDIMLESIXVrbh0GGgcUIcRlq34dBhoEAhLhEte3DIMNAEEJcvtL3YZBhIBAhrk1p
+zDIMBCMENfMvw+DDAMREOJ25N+HQYaBKAhxq9LvwyDDQCSEuA/x92GQYSAaQtyj8Afgk2EgIkKM
9dery1Nv+JFhICpCjKem7MMgw0BkhBjHfbvh920fBhkGoiPEmOoxyxsyDMT2/wFT3/nhipNN3wAA
ACV0RVh0ZGF0ZTpjcmVhdGUAMjAyNC0wMi0yOVQyMjoxOToyMiswMDowMETd/qIAAAAldEVYdGRh
dGU6bW9kaWZ5ADIwMjQtMDItMjlUMjI6MTk6MjIrMDA6MDA1gEYeAAAAKHRFWHRkYXRlOnRpbWVz
dGFtcAAyMDI0LTAyLTI5VDIyOjE5OjIyKzAwOjAwYpVnwQAAAABJRU5ErkJggg==" />
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,5 +1,5 @@
import React from "react";
import { Box, Link, Button, BoxProps } from "@chakra-ui/react";
import { Box, Link, Button, BoxProps, Text } from "@chakra-ui/react";
import { motion } from "framer-motion";
interface CustomButtonProps {
@@ -14,7 +14,9 @@ const CustomButton = ({ text, link, type }: CustomButtonProps): JSX.Element => {
return (
<MotionBox whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }}>
<Link href={link} target="_blank" rel="noopener">
<Button variant={type}>{text}</Button>
<Button variant={type}>
<Text>{text}</Text>
</Button>
</Link>
</MotionBox>
);

View File

@@ -1,5 +1,5 @@
import React from "react";
import { Box, Link, Button, BoxProps } from "@chakra-ui/react";
import { Box, Link, Button, BoxProps, Text } from "@chakra-ui/react";
import { Icon } from "@iconify/react";
import { motion } from "framer-motion";
@@ -14,7 +14,7 @@ const KoFi = (): JSX.Element => {
rel="noopener"
>
<Button variant="kofi" leftIcon={<Icon icon="cib:ko-fi" />}>
{"Fund The App"}
<Text>{"Fund The App"}</Text>
</Button>
</Link>
</MotionBox>

View File

@@ -8,18 +8,13 @@ type Links = LinkObj[];
const links: Links = [
{
href: "https://docs.google.com/document/d/1hrerGKHTO3iach8A-CabtfIB4lyZWlgO8EGTyOCrI2Y",
href: "https://docs.google.com/document/d/1y1tbTG6TYoLMEde4XHzInByyHQ0T6Aw2RF6Y4Z7Yabs",
name: "Roadmap and Progress",
type: "secondary"
},
{
href: "https://lucidcreations.media/lcm-potty-chart/",
name: "Official Announcement",
type: "secondary"
},
{
type: "ko-fi"
},
// {
// type: "ko-fi"
// },
{
href: "https://t.me/LucidCreationsMedia",
name: "Dev Updates",

View File

@@ -1,70 +0,0 @@
import React from "react";
import { useAppSelector } from "../../redux/hooks";
import { useRouter } from "next/router";
import { HStack, IconButton } from "@chakra-ui/react";
import { Icon } from "@iconify/react";
import { format, isSameMonth, addMonths, subMonths } from "date-fns";
import findValidDateRange from "../../../lib/findValidDateRange";
import DatePicker from "./DatePicker";
interface CalenderNavProps {
isLoading: boolean;
title: string;
}
/**
* @param {boolean} isLoading is the component loading?
* @param {string} title the title for the current date.
*/
const CalenderNav = ({ title, isLoading }: CalenderNavProps): JSX.Element => {
const selectedDate = useAppSelector(
(state) => state.calender.selectedDateInfo
);
const { date } = selectedDate;
const selectedDateObj = new Date(date);
const validDateRange = findValidDateRange();
const { start: validStart, end: validEnd } = validDateRange;
const router = useRouter();
const handleNavButtons = (direction: "next" | "prev") => {
if (direction === "next") {
const newMonth = addMonths(selectedDateObj, 1);
const year = format(newMonth, "y");
const month = format(newMonth, "L");
router.push(`/calendar/${year}/${month}`);
} else if (direction === "prev") {
const newMonth = subMonths(selectedDateObj, 1);
const year = format(newMonth, "y");
const month = format(newMonth, "L");
router.push(`/calendar/${year}/${month}`);
}
};
return (
<HStack spacing={10} as="nav" w="auto" h="10vh" textAlign="center">
<IconButton
isDisabled={isSameMonth(selectedDateObj, validStart)}
aria-label="Previous Month"
icon={<Icon icon="akar-icons:chevron-left" />}
onClick={() => handleNavButtons("prev")}
/>
<DatePicker isLoading={isLoading} title={title} />
<IconButton
isDisabled={isSameMonth(selectedDateObj, validEnd)}
aria-label="Next Month"
icon={<Icon icon="akar-icons:chevron-right" />}
onClick={() => handleNavButtons("next")}
/>
</HStack>
);
};
export default CalenderNav;

View File

@@ -1,284 +0,0 @@
import React, { useRef, useState } from "react";
import { useRouter } from "next/router";
import {
Button,
FormControl,
FormErrorMessage,
FormLabel,
Heading,
HStack,
Input,
Popover,
PopoverBody,
PopoverCloseButton,
PopoverContent,
PopoverHeader,
PopoverTrigger,
Skeleton,
VStack
} from "@chakra-ui/react";
import {
Formik,
// FormikHelpers,
FormikProps,
Form,
Field,
FieldProps
} from "formik";
import { format } from "date-fns";
import findValidDateRange from "../../../lib/findValidDateRange";
import FormValidateEmoji from "./FormValidateEmoji";
interface DatePickerProps {
isLoading: boolean;
title: string;
}
/**
* @param {boolean} isLoading is the component loading?
* @param {string} title the title for the current date.
*/
const DatePicker = ({ title, isLoading }: DatePickerProps): JSX.Element => {
const router = useRouter();
const [valid, setValid] = useState<boolean>(false);
const validDateRange = findValidDateRange();
const validateDate = (
dateString?: string | undefined
): string | undefined => {
let dateError;
if (dateString) {
const dateArr = dateString.split("-");
if (dateArr.length !== 3) {
dateError = "Please select a date.";
setValid(false);
} else if (dateArr.length === 3) {
const date: UpdateCalenderPropsDateLayout = {
year: parseInt(dateArr[0]),
month: parseInt(dateArr[1]),
day: parseInt(dateArr[2])
};
if (!/^(19|20)\d{2}$/.test(`${date.year}`)) {
dateError = "Please use a year between 1900 and 2099";
setValid(false);
}
if (date.month < 1 || date.month > 12) {
dateError = "Please use a month between 1 and 12";
setValid(false);
}
if (date.day < 1 || date.day > 31) {
dateError = "Please use a day between 1 and 31";
setValid(false);
}
setValid(true);
} else {
setValid(true);
}
} else if (dateString.length === 0) {
dateError = "Please select a date.";
setValid(false);
} else {
setValid(true);
}
return dateError;
};
const handleSubmit = (formInput?: { date?: string }): Promise<unknown> => {
return new Promise((resolve, reject) => {
if (formInput.date) {
if (!validateDate(formInput.date)) {
const dateArr = formInput.date.split("-");
const date: UpdateCalenderPropsDateLayout = {
year: parseInt(dateArr[0]),
month: parseInt(dateArr[1]),
day: parseInt(dateArr[2])
};
return resolve(router.push(`/calendar/${date.year}/${date.month}`));
} else {
return reject("Error validating date.");
}
} else {
return reject("Date not provided.");
}
});
};
// Field theme
const fieldTheme = {
width: "auto",
bg: "gray.900",
borderColor: "white",
_placeholder: {
color: "white"
},
_focus: {
bg: "#000",
color: "#FFF",
borderColor: "#63b3ed",
boxShadow: "0 0 0 1px #63b3ed",
zIndex: "1"
}
};
const initRef = useRef();
return (
<Popover placement="bottom" initialFocusRef={initRef}>
<PopoverTrigger>
<Button border="none" variant="outline">
{isLoading ? (
<Skeleton>
<Heading w="100%" h="auto">
{title}
</Heading>
</Skeleton>
) : (
<Heading w="100%" h="auto">
{title}
</Heading>
)}
</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverHeader py={2} fontWeight="semibold">
<Heading size="md" as="h3">
{"Choose a Date"}
</Heading>
</PopoverHeader>
<PopoverCloseButton />
<PopoverBody textAlign="center">
<Formik
initialValues={{
date: ""
}}
onSubmit={(data, actions) => {
handleSubmit(data)
.then(() => {
actions.setSubmitting(false);
actions.resetForm({
values: {
date: ""
}
});
})
.catch(() => {
actions.setSubmitting(false);
});
}}
>
{(
formProps: FormikProps<{
date: string;
}>
) => (
<Form
style={{
width: "100%",
height: "auto"
}}
>
<VStack
alignItems="center"
alignContent="flex-start"
w="100%"
h="auto"
spacing={6}
py={4}
>
<Heading as="h4" size="sm" fontWeight="semibold">
{"Required fields indicated with"}
<FormValidateEmoji type="Required" />
</Heading>
<Field name="date" validate={validateDate}>
{({ field, form }: FieldProps) => (
<FormControl
isInvalid={
form.errors.date && form.touched.date ? true : false
}
>
<VStack
alignContent="center"
alignItems="center"
spacing={2}
w="100%"
h="auto"
>
<HStack
alignContent="center"
alignItems="center"
pl={4}
w="100%"
h="auto"
spacing={2}
>
<FormLabel fontWeight="semibold" htmlFor="date">
{"Date:"}
</FormLabel>
<Input
required
{...fieldTheme}
type="date"
isDisabled={formProps.isSubmitting}
{...field}
id="date"
textAlign="center"
min={format(validDateRange.start, "yyyy-MM-dd")}
max={format(validDateRange.end, "yyyy-MM-dd")}
{...(!form.errors.date && form.touched.date
? {
borderColor: "brand.valid",
boxShadow: "0 0 0 1px #00c17c",
_hover: {
borderColor: "brand.valid",
boxShadow: "0 0 0 1px #00c17c"
}
}
: "")}
/>
{!form.touched.date && (
<FormValidateEmoji type="Required" />
)}
{form.errors.name && form.touched.date && (
<FormValidateEmoji type="Error" />
)}
{!form.errors.name && form.touched.date && (
<FormValidateEmoji type="Valid" />
)}
</HStack>
<FormErrorMessage>
{typeof form.errors.date === "string" &&
form.errors.date}
</FormErrorMessage>
</VStack>
</FormControl>
)}
</Field>
<Button
isDisabled={!valid}
background={valid ? "brand.valid" : "brand.danger"}
isLoading={formProps.isSubmitting}
type="submit"
>
{"Select this date"}
</Button>
</VStack>
</Form>
)}
</Formik>
</PopoverBody>
</PopoverContent>
</Popover>
);
};
export default DatePicker;

View File

@@ -1,262 +0,0 @@
import React, { useState } from "react";
import { Provider } from "react-redux";
import { store } from "../../redux/store";
import { Box, Skeleton, VStack } from "@chakra-ui/react";
import {
add,
getYear,
getMonth,
sub,
getDate,
isBefore,
endOfDay,
isToday as isTodayFun
} from "date-fns";
import router from "next/router";
import AddUpdateSticker from "./modals/AddUpdateSticker";
import DemoStickers from "./stickers/DemoStickers";
interface DayProps {
isLoading: boolean;
isOverflow?: boolean;
overflowDirection?: "next" | "prev" | null;
currSticker: StickerVal;
date: string;
selectedDate: string;
currDate: Date;
tutorial?: "add" | "edit";
}
/**
* The individual days in the calender component.
* @param {boolean} isLoading is the component loading?
* @param {boolean} isOverflow is the current date being given before or after the current month.
* @param {"next" | "prev" | null} overflowDirection the direction the overflow is. This will navigate the calender forward or backwards 1 month.
* @param {StickerVal} currSticker the sticker for this date.
* @param {date} date the date for this day.
* @param {date} selectedDate the date for the selected month.
* @param {Date} currDate today's date.
*/
const Day = ({
isLoading,
isOverflow,
overflowDirection,
currSticker,
date,
selectedDate,
currDate,
tutorial
}: DayProps): JSX.Element => {
const selectedDateObj = new Date(selectedDate);
const currDateObj = new Date(date);
const isToday = isTodayFun(currDateObj);
const handleNav = (direction: "next" | "prev") => {
if (direction === "next") {
console.log(overflowDirection);
const newMonth = add(selectedDateObj, { months: 1 });
const year = getYear(newMonth);
const month = getMonth(newMonth) + 1;
router.push(`/calendar/${year}/${month}`);
} else if (direction === "prev") {
const newMonth = sub(selectedDateObj, { months: 1 });
const year = getYear(newMonth);
const month = getMonth(newMonth) + 1;
router.push(`/calendar/${year}/${month}`);
}
};
// This handles the modal for the day.
const [isOpen, setIsOpen] = useState<boolean>(false);
// The step the modal is at.
const [step, setStep] = useState<number>(0);
// The current selected sticker. (To be added or updated)
const [selectedSticker, setSelectedSticker] = useState<StickerVal>(null);
/**
* TODO: Add logic to remove the onClick within overflow dates.
* Do not give dates for the next month an onClick.
* Do not give dates in the past an onClick there is nothing before that month.
* (Creation date of a chart)
*/
// TODO: When the valid date range is created, disallow pointer cursor outside of the date range.
return isOverflow ? (
<VStack
w="100%"
h="100%"
bg="transparent"
pt={2}
color="gray.600"
border="1px solid #181d8f"
_hover={{
cursor: isBefore(currDateObj, endOfDay(currDate))
? selectedSticker !== null
? "pointer"
: "default"
: "default",
background: "gray.700",
border: "1px solid #FFF",
color: "whiteAlpha.900"
}}
onClick={() =>
selectedSticker !== null ? handleNav(overflowDirection) : ""
}
spacing="0.5rem"
alignContent="center"
justifyContent="flex-start"
>
<Box w="1.8rem" h="1.8rem" textAlign="center" p={0} m={0}>
{`${getDate(currDateObj)}`}
</Box>
{isLoading ? (
<Skeleton key={currSticker}>
<Box fontSize="1.5rem">
<DemoStickers stickerVal={0} />
</Box>
</Skeleton>
) : (
<Box key={currSticker} fontSize="1.5rem">
<DemoStickers stickerVal={currSticker} />
</Box>
)}
</VStack>
) : (
<VStack
w="100%"
h="100%"
bg={
tutorial
? tutorial === "add" && isToday
? "gray.600"
: tutorial === "edit" &&
!isToday &&
isBefore(currDateObj, endOfDay(currDate))
? "gray.600"
: "transparent"
: "transparent"
}
border={
tutorial
? tutorial === "add" && isToday
? "1px solid #00ff3c"
: tutorial === "edit" &&
!isToday &&
isBefore(currDateObj, endOfDay(currDate))
? "1px solid #00ff3c"
: "1px solid #0068ff"
: "1px solid #0068ff"
}
onClick={() => {
setStep(0);
setSelectedSticker(null);
setIsOpen(true);
}}
alignContent="center"
justifyContent="flex-start"
pt={2}
_hover={{
cursor: isBefore(currDateObj, endOfDay(currDate))
? "pointer"
: "default",
bg: tutorial
? tutorial === "add" && isToday
? "gray.600"
: tutorial === "edit" &&
!isToday &&
isBefore(currDateObj, endOfDay(currDate))
? "gray.600"
: "transparent"
: "transparent",
border: "1px solid #FFF"
}}
>
{isToday ? (
<Box
border="1px solid #0068ff"
borderRadius="50%"
w="1.8rem"
h="1.8rem"
textAlign="center"
p={0}
m={0}
>
{`${getDate(currDateObj)}`}
</Box>
) : (
<Box w="1.8rem" h="1.8rem" textAlign="center" p={0} m={0}>
{`${getDate(currDateObj)}`}
</Box>
)}
{isLoading ? (
<Skeleton key={currSticker}>
<Box fontSize="1.5rem">
<DemoStickers stickerVal={0} />
</Box>
</Skeleton>
) : (
<Box key={currSticker} fontSize="1.5rem">
<DemoStickers stickerVal={currSticker} />
</Box>
)}
{tutorial ? (
<Provider store={store}>
{tutorial.toLowerCase() === "add" && isToday && !isLoading && (
<AddUpdateSticker
stickerDate={date}
isOpen={isOpen}
updateIsOpen={setIsOpen}
currSticker={currSticker}
step={step}
updateStep={setStep}
selectedSticker={selectedSticker}
updateSelectedSticker={setSelectedSticker}
currDate={currDate}
/>
)}
{tutorial.toLowerCase() === "edit" &&
!isToday &&
isBefore(currDateObj, endOfDay(currDate)) &&
!isLoading && (
<AddUpdateSticker
stickerDate={date}
isOpen={isOpen}
updateIsOpen={setIsOpen}
currSticker={currSticker}
step={step}
updateStep={setStep}
selectedSticker={selectedSticker}
updateSelectedSticker={setSelectedSticker}
currDate={currDate}
/>
)}
</Provider>
) : (
<Provider store={store}>
{isBefore(currDateObj, endOfDay(currDate)) && !isLoading && (
<AddUpdateSticker
stickerDate={date}
isOpen={isOpen}
updateIsOpen={setIsOpen}
currSticker={currSticker}
step={step}
updateStep={setStep}
selectedSticker={selectedSticker}
updateSelectedSticker={setSelectedSticker}
currDate={currDate}
/>
)}
</Provider>
)}
</VStack>
);
};
export default Day;

View File

@@ -1,35 +0,0 @@
import React, { FC } from "react";
interface FormValidateEmojiProps {
type: string;
}
const FormValidateEmoji: FC<FormValidateEmojiProps> = ({
type
}: FormValidateEmojiProps) => {
interface Validations {
[key: string]: JSX.Element;
}
const validations: Validations = {
Required: (
<span role="img" aria-label="Explication Mark">
</span>
),
Error: (
<span role="img" aria-label="X">
</span>
),
Valid: (
<span role="img" aria-label="Check">
</span>
)
};
return validations[`${type}`];
};
export default FormValidateEmoji;

View File

@@ -1,166 +0,0 @@
import React, { useEffect } from "react";
import { useAppDispatch, useAppSelector } from "../../redux/hooks";
import { updateCurrDate, updateMonth } from "../../features/calender";
import { Box, HStack, SimpleGrid, Text, VStack } from "@chakra-ui/react";
import { isSameDay, format } from "date-fns";
import CalenderNav from "./CalenderNav";
import Day from "./Day";
const Calender = ({
date: newDate,
isLoading
}: UpdateCalendarProps): JSX.Element => {
const dispatch = useAppDispatch();
// * Month * //
const currDate: string = useAppSelector((state) => state.calender.currDate);
const selectedDate: SelectedDateInfo = useAppSelector(
(state) => state.calender.selectedDateInfo
);
const { layout, title, date: currentSelectedDateStr } = selectedDate;
const currDateObj = new Date(currDate);
// * Stickers * //
const stickersMonth: StickerDays = useAppSelector(
(state) => state.stickers.stickersMonth
);
useEffect(() => {
if (newDate && newDate.year && newDate.month && newDate.day) {
const { year, month, day } = newDate;
if (year > 0 && month > 0 && day > 0) {
const generatedDate: Date = new Date(year, month - 1, day);
const currSelectedDateObj = new Date(currentSelectedDateStr);
const dateString: string = generatedDate.toJSON();
if (!isSameDay(currSelectedDateObj, generatedDate)) {
dispatch(updateMonth(dateString));
}
} else {
console.warn("Invalid date format: ", newDate);
}
}
}, [currentSelectedDateStr, dispatch, newDate]);
useEffect(() => {
// console.info("Check to update date.");
const currDateObj = new Date(currDate);
if (!isSameDay(currDateObj, new Date())) {
// console.info("Updated date.");
dispatch(updateCurrDate());
}
}, [currDate, dispatch]);
// Simulated user settings.
const userSettings = {
theme: "default",
startOfWeek: "Sunday"
};
const currMonth: WeekLayout =
layout[`${userSettings.startOfWeek.toLowerCase()}`];
const { month, weekdays } = currMonth;
// TODO: Move the weekdays into it's own component for responsiveness.
return (
<VStack h="92vh" w="100%" mb="5vh">
<CalenderNav title={title} isLoading={isLoading} />
<VStack h="100%" w="100%" spacing={0}>
<HStack
w="100%"
h="auto"
px={{ base: 1, sm: 2, md: 6 }}
spacing={0}
alignContent="center"
alignItems="center"
>
{weekdays.map((weekDay) => {
return (
<Box
key={weekDay}
display="flex"
w="100%"
h={10}
bg="transparent"
border="1px solid #0068ff"
alignContent="center"
alignItems="center"
>
<Text display={{ base: "none", md: "block" }} w="100%" h="auto">
{weekDay}
</Text>
<Text
display={{ base: "none", sm: "block", md: "none" }}
w="100%"
h="auto"
>
{weekDay.substring(0, 3)}
</Text>
<Text display={{ base: "block", sm: "none" }} w="100%" h="auto">
{weekDay.substring(0, 2)}
</Text>
</Box>
);
})}
</HStack>
<SimpleGrid
w="100%"
h="100%"
px={{ base: 1, sm: 2, md: 6 }}
columns={7}
alignItems="center"
>
{Object.keys(month).map((week) => {
const thisWeek = month[week];
return thisWeek.map((day: MonthDay) => {
const { date, isOverflow, overflowDirection } = day;
const toDateObj: Date = new Date(date);
let sticker = null;
let id = "";
stickersMonth.map((stickerDay) => {
const { date: stickerDate } = stickerDay;
if (isSameDay(new Date(stickerDate), toDateObj)) {
sticker = stickerDay.sticker;
id = stickerDay.id;
}
});
return (
<Day
isLoading={isLoading}
isOverflow={isOverflow}
overflowDirection={overflowDirection}
currSticker={sticker}
date={date}
selectedDate={selectedDate.date}
currDate={currDateObj}
key={
id.length
? id
: format(toDateObj, "yyyyddLL") +
`/${sticker === null ? 0 : sticker}`
}
/>
);
});
})}
</SimpleGrid>
</VStack>
</VStack>
);
};
export default Calender;

View File

@@ -1,271 +0,0 @@
import React, { useState, useRef } from "react";
import { useAppDispatch } from "../../../redux/hooks";
import { addEditSticker } from "../../../features/calender/stickers";
import {
Button,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalFooter,
Heading,
HStack,
Text,
VStack,
SimpleGrid,
Box
} from "@chakra-ui/react";
import { format, isSameDay } from "date-fns";
import { Icon } from "@iconify/react";
import StickerSelector from "./StickerSelector";
import DemoStickers from "../stickers/DemoStickers";
interface AddStickerProps {
isOpen: boolean;
updateIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
stickerDate: string;
currSticker: StickerVal;
step: number;
updateStep: React.Dispatch<React.SetStateAction<number>>;
selectedSticker: StickerVal;
updateSelectedSticker: React.Dispatch<React.SetStateAction<StickerVal>>;
currDate: Date;
}
/**
* Handles adding and modifying the stickers for the given month.
* @param {boolean} isOpen Tells the component when the modal should be open.
* @param {React.Dispatch<React.SetStateAction<boolean>>} updateIsOpen Used to close the modal.
* @param {date} stickerDate The date for which the sticker will be added or modified.
* @param {StickerVal} currSticker The current sticker for the date.
* @param {number} step A numerical variable that represents the page the modal should be at.
* @param {React.Dispatch<React.SetStateAction<number>>} updateStep Used to navigate the pages of the modal by updating the step the modal is on.
* @param {StickerVal} selectedSticker the value of the selected sticker.
* @param {React.Dispatch<React.SetStateAction<StickerVal>>} updateSelectedSticker The react state function to update the selected sticker that will be added or updated.
* @param {Date} currDate the current date.
*/
const AddUpdateSticker = ({
isOpen,
updateIsOpen,
stickerDate,
currSticker,
step,
updateStep,
selectedSticker,
updateSelectedSticker,
currDate
}: AddStickerProps): JSX.Element => {
const dispatch = useAppDispatch();
const stickerDateObj = new Date(stickerDate);
const [modalVariant] = useState<"add" | "edit">(
isSameDay(stickerDateObj, currDate) ? "add" : "edit"
);
const handleClose = () => {
updateIsOpen(false);
};
// TODO: Validate that the provided sticker is not the current sticker. Throw an error if the same sticker is attempted.
const handleSubmit = (sticker: StickerVal) => {
dispatch(addEditSticker({ stickerDate, sticker }));
handleClose();
};
// The first sticker to have focus when the modal opens.
const initialRef = useRef();
// * Double check that the submit button is disabled if the selected sticker is the same as the current sticker.
const variants = {
add: [
{
header: `Which sticker did you earn for ${format(
stickerDateObj,
"LLL d, y"
)}?`,
body: (
<VStack
w="100%"
h="auto"
justifyContent="space-between"
alignContent="center"
spacing="4"
>
<Heading textAlign="center" as="h3" size="md" w="100%" h="auto">
{"Select a sticker"}
</Heading>
<StickerSelector
stickerSet="Demo"
currSticker={currSticker}
selectedSticker={selectedSticker}
updateSelectedSticker={updateSelectedSticker}
initialSticker={initialRef}
/>
</VStack>
),
footer: (
<Button
variant="submit"
isDisabled={
selectedSticker === null || selectedSticker === currSticker
}
onClick={() => handleSubmit(selectedSticker)}
>
{"Submit"}
</Button>
)
}
],
edit: [
{
header: `Which sticker did you want to update for ${format(
stickerDateObj,
"LLL d, y"
)}?`,
body: (
<VStack
w="100%"
h="auto"
justifyContent="space-between"
alignContent="center"
>
<Heading textAlign="center" as="h3" size="md" w="100%" h="auto">
{"Current Sticker"}
</Heading>
<Text fontSize="4rem">
<DemoStickers stickerVal={currSticker} />
</Text>
<Heading textAlign="center" as="h3" size="md" w="100%" h="auto">
{"Select your new sticker"}
</Heading>
<StickerSelector
stickerSet="Demo"
currSticker={currSticker}
selectedSticker={selectedSticker}
updateSelectedSticker={updateSelectedSticker}
initialSticker={initialRef}
/>
</VStack>
),
footer: (
<Button
variant="primary"
isDisabled={
selectedSticker === null || selectedSticker === currSticker
}
onClick={() => updateStep(step + 1)}
>
{"Next"}
</Button>
)
},
{
header: `Are you sure you want to change the sticker for ${format(
stickerDateObj,
"M/d/y"
)}?`,
body: (
<SimpleGrid
my={{ base: "0px", sm: "6" }}
mx={{ base: "0px", sm: "10", md: "16" }}
w="auto"
h="100%"
columns={3}
>
<Heading textAlign="center" as="h3" size="md" w="100%" h="auto">
{"Previous Sticker"}
</Heading>
<Box></Box>
<Heading textAlign="center" as="h3" size="md" w="100%" h="auto">
{"New Sticker"}
</Heading>
<Text textAlign="center" w="100%" fontSize="4rem">
<DemoStickers stickerVal={currSticker} />
</Text>
<Box fontSize="4rem" m="auto">
<Icon fontSize="4rem" icon="bi:arrow-right" />
</Box>
<Text textAlign="center" w="100%" fontSize="4rem">
<DemoStickers stickerVal={selectedSticker} />
</Text>
</SimpleGrid>
),
footer: (
<HStack
w="100%"
h="auto"
justifyContent="space-between"
alignContent="center"
>
<Button variant="primary" onClick={() => updateStep(step - 1)}>
{"Previous"}
</Button>
<HStack w="auto" h="auto" alignContent="center" spacing={6}>
<Button
backgroundColor="transparent"
_hover={{ backgroundColor: "brand.danger" }}
onClick={() => updateIsOpen(!isOpen)}
>
{"Cancel"}
</Button>
<Button
variant="submit"
isDisabled={
selectedSticker === null || selectedSticker === currSticker
}
onClick={() => handleSubmit(selectedSticker)}
>
{"Confirm"}
</Button>
</HStack>
</HStack>
)
}
]
};
return (
<Modal
isCentered
initialFocusRef={initialRef}
isOpen={isOpen}
onClose={() => handleClose()}
motionPreset="slideInBottom"
scrollBehavior="inside"
size={modalVariant === "add" ? "xl" : "2xl"}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack
w="100%"
h="auto"
justifyContent="space-between"
alignContent="center"
>
<Heading textAlign="center" as="h2" size="md" w="100%" h="auto">
{modalVariant && variants[modalVariant][step].header}
</Heading>
<Button
fontSize="2rem"
px="1"
onClick={() => updateIsOpen(!isOpen)}
>
<Icon icon="bi:x" />
</Button>
</HStack>
</ModalHeader>
<ModalBody>
{modalVariant && variants[modalVariant][step].body}
</ModalBody>
<ModalFooter>
{modalVariant && variants[modalVariant][step].footer}
</ModalFooter>
</ModalContent>
</Modal>
);
};
export default AddUpdateSticker;

View File

@@ -1,74 +0,0 @@
import { HStack, Button } from "@chakra-ui/react";
import React from "react";
import DemoStickers from "../stickers/DemoStickers";
interface StickerSelectorProps {
stickerSet: "Demo";
currSticker: StickerVal;
selectedSticker: StickerVal;
updateSelectedSticker: React.Dispatch<React.SetStateAction<StickerVal>>;
initialSticker: React.MutableRefObject<undefined>;
}
/**
* Handles displaying a list of dynamic stickers to be selected.
* @param {string} stickerSet The name of the stickers that should be displayed.
* @param {StickerVal} currSticker The current sticker for the date.
* @param {StickerVal} selectedSticker The selected sticker for the current. date
* @param {React.Dispatch<React.SetStateAction<StickerVal>>} updateSelectedSticker TThe react state function to update the selected sticker that will be added or updated.
* @param {React.MutableRefObject<undefined>} initialSticker the sticker that should have be in focus when the modal opens.
*/
const StickerSelector = ({
stickerSet,
currSticker,
selectedSticker,
updateSelectedSticker,
initialSticker
}: StickerSelectorProps): JSX.Element => {
const stickers = {
Demo: (
<HStack
w="100%"
h="auto"
justifyContent="center"
alignContent="center"
spacing={14}
>
<Button
isDisabled={currSticker >= 1}
ref={currSticker <= 1 ? initialSticker : null}
border={selectedSticker === 1 ? "1px solid #FFF" : "opx"}
bg={selectedSticker === 1 && "gray.800"}
onClick={() => updateSelectedSticker(1)}
variant="stickerButton"
>
<DemoStickers stickerVal={1} />
</Button>
<Button
isDisabled={currSticker === 0}
ref={currSticker >= 1 ? initialSticker : null}
border={selectedSticker === 0 ? "1px solid #FFF" : "opx"}
bg={selectedSticker === 0 && "gray.800"}
onClick={() => updateSelectedSticker(0)}
variant="stickerButton"
>
<DemoStickers stickerVal={0} />
</Button>
<Button
isDisabled={currSticker <= -1}
border={selectedSticker === -1 ? "1px solid #FFF" : "opx"}
bg={selectedSticker === -1 && "gray.800"}
onClick={() => updateSelectedSticker(-1)}
variant="stickerButton"
>
<DemoStickers stickerVal={-1} />
</Button>
</HStack>
)
};
return stickers[stickerSet];
};
export default StickerSelector;

View File

@@ -1,56 +0,0 @@
import React, { FC } from "react";
// TODO: When themes are made import the theme from user settings store. Refactor to use whatever those SVGs are.
interface DemoStickersProps {
stickerVal: StickerVal;
}
const DemoStickers: FC<DemoStickersProps> = ({
stickerVal
}: DemoStickersProps) => {
// If sticker is null return an empty space.
if (stickerVal === null) {
return <span aria-label="spacer">&nbsp;</span>;
}
interface StickerToEmoji {
[key: string]: JSX.Element;
}
/**
* ? Temporarily using values -1 to 1.
* ? In the full app the values will be between -2 and 2.
*/
let key = "0";
if (stickerVal > 0) {
key = "1";
} else if (stickerVal < 0) {
key = "-1";
}
// Link value to an emoji representing a sticker.
const stickerToEmoji: StickerToEmoji = {
"1": (
<span role="img" aria-label="Sun">
</span>
),
"0": (
<span role="img" aria-label="Cloud">
</span>
),
"-1": (
<span role="img" aria-label="Raining Cloud">
🌧
</span>
)
};
// Return the appropriate sticker.
return stickerToEmoji[`${key}`];
};
export default DemoStickers;

View File

@@ -0,0 +1,27 @@
import { Button, Heading, Text, Link, VStack } from "@chakra-ui/react";
import React from "react";
const AboutProject = (): JSX.Element => {
const description = `This project and website is a replacement for the current Lucid Creations Media Website. It is going to being designed to be faster, more user friendly, and better accessible compared to Wordpress. This platform is being built on React and Next.js`;
return (
<VStack
justifyContent="center"
alignContent="center"
w="100%"
my="10"
px={{ base: "5vw", md: "15vw", lg: "20vw", xl: "30vw" }}
spacing="4"
>
<Heading w="100%">{"About This Website"}</Heading>
<Text w="100%">{description}</Text>
<Link href="htps://lucidcreations.media" target="_blank" rel="noopener">
<Button type="button" variant="secondary">
<Text>{"Visit the current website"}</Text>
</Button>
</Link>
</VStack>
);
};
export default AboutProject;

View File

@@ -0,0 +1,82 @@
import React from "react";
import { Flex, HStack, Text, VStack } from "@chakra-ui/react";
import BrandText from "../../theme/components/BrandText";
import { Icon } from "@iconify/react";
const WhatIMakeBanner = (): JSX.Element => {
return (
<VStack
bgColor="brand.cosmic"
w="100%"
alignContent="center"
justifyContent="center"
py="10"
px={{ base: "0", lg: "5vw", "2xl": "10vw" }}
>
<VStack w="100%" alignContent="center" pb={{ base: "10", md: "6" }}>
<BrandText type="Heading" headerLevel="h2" text={"What I Make"} />
</VStack>
<Flex
w="100%"
justifyContent="space-around"
alignContent="center"
direction={{ base: "column", md: "row" }}
gap="5rem"
>
<VStack spacing="4" w="100%">
<BrandText type="Heading" headerLevel="h3" text={"Communities"} />
<VStack spacing="4" alignContent="start" justifyContent="center">
<HStack spacing="4">
<Text fontSize="5xl">
<Icon icon="ic:baseline-discord" />
</Text>
<BrandText type="Text" size="xl" text={"Lucid's Cove"} />
</HStack>
<HStack spacing="4">
<Text fontSize="5xl">
<Icon icon="ic:baseline-telegram" />
</Text>
<BrandText type="Text" size="xl" text={"Lucid's Cove"} />
</HStack>
</VStack>
</VStack>
<VStack spacing="4" w="100%">
<BrandText type="Heading" headerLevel="h3" text={"Content"} />
<VStack spacing="4" alignContent="start" justifyContent="center">
<HStack spacing="4" w="100%">
<Text fontSize="5xl">
<Icon icon="heroicons-solid:pencil" />
</Text>
<BrandText type="Text" size="xl" text={"Erotic Stories"} />
</HStack>
<HStack spacing="4" w="100%">
<Text fontSize="5xl">
<Icon icon="ic:baseline-telegram" />
</Text>
<BrandText type="Text" size="xl" text={"Hypno Audio Files"} />
</HStack>
</VStack>
</VStack>
<VStack spacing="4" w="100%">
<BrandText type="Heading" headerLevel="h3" text={"Streams"} />
<VStack spacing="4" alignContent="start" justifyContent="center">
<HStack spacing="4">
<Text fontSize="5xl">
<Icon icon="mdi:twitch" />
</Text>
<BrandText type="Text" size="xl" text={"LucidKobold"} />
</HStack>
<HStack spacing="4">
<Text fontSize="5xl">
<Icon icon="mdi:youtube" />
</Text>
<BrandText type="Text" size="xl" text={"LucidKobold"} />
</HStack>
</VStack>
</VStack>
</Flex>
</VStack>
);
};
export default WhatIMakeBanner;

View File

@@ -0,0 +1,32 @@
import { Heading, VStack } from "@chakra-ui/react";
import React from "react";
import WhatIMakeBanner from "./WhatIMakeBanner";
import AboutProject from "./AboutProject";
const TempHero = (): JSX.Element => {
return (
<VStack
bg="brand.content"
h="100%"
w="100%"
spacing="0"
justifyContent="center"
alignContent="center"
>
<WhatIMakeBanner />
<AboutProject />
<VStack
w="100%"
h="36.3vh"
justifyContent="space-around"
alignContent="center"
>
<Heading size="3xl" as="h2">
{"Placeholder section"}
</Heading>
</VStack>
</VStack>
);
};
export default TempHero;

View File

@@ -1,210 +0,0 @@
import React, { useEffect } from "react";
import { useAppDispatch, useAppSelector } from "../../redux/hooks";
import { updateMonth } from "../../features/calender";
import { Box, HStack, SimpleGrid, Text, VStack } from "@chakra-ui/react";
import { format, isSameDay, isToday } from "date-fns";
import Day from "../calender/Day";
import { setCurrentWeek } from "../../features/tutorial";
interface CalenderExampleProps {
type: "add" | "edit";
isLoading: boolean;
}
const CalenderExample = ({
type,
isLoading
}: CalenderExampleProps): JSX.Element => {
// TODO: Check if the current date is the start of the user's preferred start of the week and use the previous week for the edit example.
const currDateStr: string = useAppSelector(
(state) => state.calender.currDate
);
const currDateObj: Date = new Date(currDateStr);
const dispatch = useAppDispatch();
// * Current Month * //
const selectedDate: SelectedDateInfo = useAppSelector(
(state) => state.calender.selectedDateInfo
);
const { layout, date: currSelectedDateStr } = selectedDate;
// * Stickers * //
const stickersMonth: StickerDays = useAppSelector(
(state) => state.stickers.stickersMonth
);
// Simulated user settings.
const userSettings = {
theme: "default",
startOfWeek: "Sunday"
};
// * Week Names * //
const currMonth: WeekLayout =
layout[`${userSettings.startOfWeek.toLowerCase()}`];
const { month, weekdays } = currMonth;
useEffect(() => {
const currDateObj: Date = new Date(currDateStr);
const currSelectedDateOj: Date = new Date(currSelectedDateStr);
if (!isSameDay(currDateObj, currSelectedDateOj)) {
dispatch(updateMonth(currDateObj.toJSON()));
}
}, [currDateStr, currSelectedDateStr, dispatch]);
// * The current week * //
const currWeek = useAppSelector((state) => state.tutorial.currWeek);
useEffect(() => {
const getCurrentWeek = (): MonthDay[] => {
let foundWeek: MonthDay[];
for (const week in month) {
const currWeek = month[week];
currWeek.forEach((day: MonthDay) => {
const { date } = day;
if (isToday(new Date(date))) {
foundWeek = currWeek;
}
});
}
return foundWeek || ([] as MonthDay[]);
};
if (currWeek === null) {
dispatch(setCurrentWeek(getCurrentWeek()));
}
}, [currWeek, dispatch, month]);
return (
<VStack
h="auto"
w="100%"
alignContent="center"
alignItems="center"
spacing={2}
>
<VStack
h="8.5rem"
w="100%"
alignContent="center"
alignItems="center"
spacing={0}
>
<HStack
w="100%"
h="auto"
alignContent="center"
alignItems="center"
spacing={0}
px={{ base: 1, sm: 2, md: 6 }}
>
{weekdays.map((weekDay) => {
return (
<Box
key={weekDay}
display="flex"
w="100%"
h={10}
bg="transparent"
border="1px solid #0068ff"
alignContent="center"
alignItems="center"
>
<Text display={{ base: "none", md: "block" }} w="100%" h="auto">
{weekDay}
</Text>
<Text
display={{ base: "none", sm: "block", md: "none" }}
w="100%"
h="auto"
>
{weekDay.substring(0, 3)}
</Text>
<Text display={{ base: "block", sm: "none" }} w="100%" h="auto">
{weekDay.substring(0, 2)}
</Text>
</Box>
);
})}
</HStack>
<SimpleGrid
w="100%"
h="100%"
columns={7}
px={{ base: 1, sm: 2, md: 6 }}
alignItems="center"
>
{currWeek &&
currWeek.map((day: MonthDay) => {
const { date, isOverflow, overflowDirection } = day;
const toDateObj: Date = new Date(date);
let sticker = null;
let id = "";
stickersMonth.map((stickerDay) => {
const { date: stickerDate } = stickerDay;
if (isSameDay(new Date(stickerDate), toDateObj)) {
sticker = stickerDay.sticker;
id = stickerDay.id;
}
});
return (
<Day
isLoading={isLoading}
isOverflow={isOverflow}
overflowDirection={overflowDirection}
currSticker={sticker}
date={date}
selectedDate={selectedDate.date}
currDate={currDateObj}
tutorial={type}
key={
id.length
? id
: format(toDateObj, "yyyyddLL") +
`/${sticker === null ? 0 : sticker}`
}
/>
);
})}
</SimpleGrid>
</VStack>
{type === "edit" && (
<VStack
w="100%"
h="auto"
alignContent="center"
alignItems="center"
spacing={2}
>
<Text fontSize="sm" color="whiteAlpha.800">
{
"Not being able to edit within this tutorial when the current date is the start of the week or month is a known bug."
}
</Text>
<Text fontSize="sm" color="whiteAlpha.800">
{"This bug will be fixed in beta v2."}
</Text>
<Text fontSize="sm" color="whiteAlpha.800">
{"You can skip the tutorial and try again tomorrow."}
</Text>
</VStack>
)}
</VStack>
);
};
export default CalenderExample;

View File

@@ -1,10 +0,0 @@
type AboutApp = string[];
const aboutApp: AboutApp = [
"The Potty Chart is an app that mimics a potty/star chart commonly used while potty training toddler or child.",
"The app can be used to track behavior, habits, diaper training, potty training (good luck), daily chores/tasks, or anything else you might want to track in a fun and visual way with colorful themes, stickers, and even receive encouraging messaged from your big/dom, followers, and friends.",
"The final app will have settings to disable any mentions and references of ABDL to allow a more general audience to use, such as for a master and pet relationship.",
"This is a beta build of the app. Some functionality may not work as intended, is not fully functional, and may be missing entirely."
];
export default aboutApp;

View File

@@ -1,9 +0,0 @@
type AppFunctionality = string[];
const appFunctionality: AppFunctionality = [
"The app will generate stickers to display from the 1st of the month to the day before today. This is to simulate previous and continued use.",
"Ability to add a sticker to the current date.",
"Ability to add edit a sticker from a previous date with a confirmation prompt."
];
export default appFunctionality;

View File

@@ -1,38 +0,0 @@
import React from "react";
import { VStack } from "@chakra-ui/react";
import TutorialCalender from "./sections/TutorialCalender";
import TutorialLinks from "./sections/TutorialLinks";
import TutorialHeading from "./sections/TutorialHeading";
import TutorialAboutApp from "./sections/TutorialAboutApp";
import TutorialSubmitButtons from "./sections/TutorialSubmitButtons";
import TutorialAppFunctionality from "./sections/TutorialAppFunctionality";
interface TutorialProps {
isLoading: boolean;
}
const Tutorial = ({ isLoading }: TutorialProps): JSX.Element => {
return (
<VStack
h="auto"
w="auto"
justifyContent="center"
alignContent="center"
my={8}
mx={{ base: 0, sm: 2, md: 4 }}
py={4}
px={{ base: 0, sm: 2, md: 4 }}
bg="gray.700"
borderRadius={{ base: "", sm: "2xl" }}
>
<TutorialHeading />
<TutorialAboutApp />
<TutorialAppFunctionality />
<TutorialCalender isLoading={isLoading} />
<TutorialLinks />
<TutorialSubmitButtons isLoading={isLoading} />
</VStack>
);
};
export default Tutorial;

View File

@@ -1,33 +0,0 @@
import React from "react";
import { VStack, Heading, Divider, Text } from "@chakra-ui/react";
import aboutApp from "../data/aboutApp";
const TutorialAboutApp = (): JSX.Element => {
return (
<VStack
h="auto"
w="100%"
justifyContent="center"
alignContent="center"
spacing={4}
>
<Heading as="h3" size="lg">
{"About the App"}
</Heading>
<VStack
h="auto"
w="100%"
justifyContent="start"
alignContent="center"
spacing={1}
>
{aboutApp.map((string: string) => {
return <Text key={string.replaceAll(" ", "-")}>{string}</Text>;
})}
</VStack>
<Divider orientation="horizontal" />
</VStack>
);
};
export default TutorialAboutApp;

View File

@@ -1,33 +0,0 @@
import React from "react";
import { VStack, Heading, Divider, Text } from "@chakra-ui/react";
import appFunctionality from "../data/appFunctionality";
const TutorialAppFunctionality = (): JSX.Element => {
return (
<VStack
h="auto"
w="100%"
justifyContent="center"
alignContent="center"
spacing={4}
>
<Heading as="h3" size="lg">
{"App Functionality"}
</Heading>
<VStack
h="auto"
w="100%"
justifyContent="start"
alignContent="center"
spacing={1}
>
{appFunctionality.map((string: string) => {
return <Text key={string.replaceAll(" ", "-")}>{string}</Text>;
})}
</VStack>
<Divider orientation="horizontal" />
</VStack>
);
};
export default TutorialAppFunctionality;

View File

@@ -1,74 +0,0 @@
import React from "react";
import { Divider, Heading, HStack, Text, VStack } from "@chakra-ui/react";
import CalenderExample from "../CalenderExample";
interface CalenderExampleProps {
isLoading: boolean;
}
const TutorialCalender = ({ isLoading }: CalenderExampleProps): JSX.Element => {
return (
<VStack
h="auto"
w="100%"
justifyContent="center"
alignContent="center"
spacing={4}
>
<Heading as="h3" size="lg">
{"How to Use The Calender"}
</Heading>
<VStack
h="auto"
w="100%"
justifyContent="center"
alignItems="center"
alignContent="center"
spacing={4}
>
<Heading as="h4" size="md">
{"Add a Sticker to Today's Date"}
</Heading>
<HStack
w="100%"
h="auto"
alignContent="center"
justifyContent="center"
spacing={1}
>
<Text>{"Select the date with the"}</Text>
<Text color="#00ff3c">{" green "}</Text>
<Text>{"border."}</Text>
</HStack>
<CalenderExample type={"add"} isLoading={isLoading} />
</VStack>
<VStack
h="auto"
w="100%"
justifyContent="center"
alignItems="center"
alignContent="center"
spacing={4}
>
<Heading as="h4" size="md">
{"Add a Sticker to Previous Dates"}
</Heading>
<HStack
w="100%"
h="auto"
alignContent="center"
justifyContent="center"
spacing={1}
>
<Text>{"Select a date with a"}</Text>
<Text color="#00ff3c">{" green "}</Text>
<Text>{"border."}</Text>
</HStack>
<CalenderExample type={"edit"} isLoading={isLoading} />
</VStack>
<Divider orientation="horizontal" />
</VStack>
);
};
export default TutorialCalender;

View File

@@ -1,22 +0,0 @@
import React from "react";
import { VStack, Heading, Divider } from "@chakra-ui/react";
const TutorialHeading = (): JSX.Element => {
return (
<VStack
h="auto"
w="100%"
justifyContent="center"
alignContent="center"
spacing={4}
>
<Heading as="h2">{"Welcome to Code Name: LCM Potty Chart"}</Heading>
<Heading as="h3" size="md">
{"A Lucid Creations Media Project"}
</Heading>
<Divider orientation="horizontal" />
</VStack>
);
};
export default TutorialHeading;

View File

@@ -1,23 +0,0 @@
import React from "react";
import { Divider, Heading, VStack } from "@chakra-ui/react";
import Buttons from "../../buttons";
const TutorialLinks = (): JSX.Element => {
return (
<VStack
h="auto"
w="100%"
justifyContent="center"
alignContent="center"
spacing={4}
>
<Heading as="h3" size="lg">
{"More Info"}
</Heading>
<Buttons />
<Divider orientation="horizontal" />
</VStack>
);
};
export default TutorialLinks;

View File

@@ -1,83 +0,0 @@
import { HStack, Button, VStack, Checkbox } from "@chakra-ui/react";
import React from "react";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
import {
setTutorialCompleted,
setTempTutorialComplete,
toggleRememberCompleted
} from "../../../features/tutorial";
interface TutorialSubmitButtonsProps {
isLoading: boolean;
}
const TutorialSubmitButtons = ({
isLoading
}: TutorialSubmitButtonsProps): JSX.Element => {
const rememberComplete: boolean = useAppSelector(
(state) => state.tutorial.rememberCompleted
);
const dispatch = useAppDispatch();
const handleComplete = (): void => {
if (rememberComplete) {
dispatch(setTutorialCompleted());
}
if (!rememberComplete) {
dispatch(setTempTutorialComplete());
}
};
const handleSkip = (): void => {
dispatch(setTempTutorialComplete());
};
const handleUpdateCheck = (): void => {
dispatch(toggleRememberCompleted());
};
return (
<HStack
h="auto"
w="90%"
justifyContent="space-between"
alignItems="flex-start"
pt={8}
>
<Button
type="button"
isDisabled={isLoading}
onClick={() => handleSkip()}
variant="skip"
>
{"Skip"}
</Button>
<VStack
h="auto"
w="auto"
justifyContent="center"
alignItems="center"
spacing={2}
>
<Button
type="button"
isDisabled={isLoading}
onClick={() => handleComplete()}
variant="primary"
>
{"Complete Tutorial"}
</Button>
<Checkbox
isChecked={rememberComplete}
isDisabled={isLoading}
onChange={() => handleUpdateCheck()}
>
{"Remember completed?"}
</Checkbox>
</VStack>
</HStack>
);
};
export default TutorialSubmitButtons;

View File

@@ -1,3 +1,9 @@
import "@fontsource/montserrat/500.css";
import "@fontsource/tilt-neon/400.css";
import "@fontsource/anonymous-pro/400.css";
import "@fontsource/kalam/400.css";
import "@fontsource/anybody/400.css";
import type { AppProps } from "next/app";
import React from "react";
import { ChakraProvider } from "@chakra-ui/react";
@@ -13,7 +19,7 @@ function LCMPottyChart({ Component, pageProps }: AppProps): JSX.Element {
<ChakraProvider theme={AppTheme}>
<Layout {...pageProps}>
<Head>
<title>{"LCM Potty Chart"}</title>
<title>{"LCM Website"}</title>
<meta
name="viewport"
content="width=device-width, user-scalable=yes, initial-scale=1.0"

View File

@@ -4,8 +4,7 @@ import { ColorModeScript } from "@chakra-ui/react";
import AppTheme from "../theme/AppTheme";
const description =
// "Behavior and progress tracker for ABDLs and babyfurs alike. Track multiple littles and create any trackers you would like.";
"Beta preview of a, calender like, 'star chart' behavior and progress tracker for ABDLs, diaperfurs, and babyfurs.";
"Official website for Lucid Creations Media. Where you can back us, donate to us, and purchase our official content such as hypno audio files.";
const logo = "images/logo.svg";
const logoOG = "/images/logo.png";
@@ -18,31 +17,27 @@ class Document extends NextDocument {
<meta name="theme-color" content="#3138dc" />
<link rel="icon" href={logo} sizes="32x32 192x192" />
<link rel="apple-touch-icon" href={logo} />
<meta property="og:title" content="LCM Potty Chart" />
<meta property="og:title" content="Lucid Creations Media Website" />
<meta name="og:description" content={description} />
<meta property="og:type" content="Progress Tracking" />
<meta property="og:type" content="eCommerce" />
<meta property="og:image" content={logoOG} />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:alt" content="LCM Potty Chart Logo" />
<meta property="og:image:alt" content="Lucid Creations Media Logo" />
<meta property="og:url" content="https://lucidcreations.media" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="title" content="LCM Potty Chart" />
<meta property="title" content="Lucid Creations Media Website" />
<meta name="description" content={description} />
<meta property="type" content="Progress Tracking" />
<meta property="type" content="eCommerce" />
<meta property="image" content={logoOG} />
<meta property="image:type" content="image/png" />
<meta property="image:alt" content="LCM Potty Chart Logo" />
<meta property="image:alt" content="Lucid Creations Media Logo" />
<meta property="url" content="https://https://lucidcreations.media" />
<meta httpEquiv="content-language" content="en_US" />
<meta charSet="UTF-8" />
<meta
name="keywords"
content="ABDL Adult Baby Diaper Lover Furry Babyfur ab/dl AB/DL potty chart training progress behavior tracker habbit"
/>
<meta name="keywords" content={description} />
<meta name="copyright" content="Lucid Creations Media" />
<meta name="page-topic" content="Progress Tracking" />
<meta name="page-type" content="Calender" />
<meta name="audience" content="18+" />
<meta name="page-topic" content="eCommerce" />
<meta name="audience" content="E" />
<meta name="robots" content="index, follow" />
</Head>
<html lang="en" />

View File

@@ -1,209 +0,0 @@
import React, { useEffect, useState } from "react";
import { Provider } from "react-redux";
import { store } from "../../redux/store";
import { Box } from "@chakra-ui/react";
import { useRouter } from "next/router";
import {
endOfMonth,
getDate
// getMonth,
// getYear,
// isAfter,
// isBefore,
// isSameMonth
} from "date-fns";
// import findValidDateRange from "../../lib/findValidDateRange";
import ErrorPage from "next/error";
import Calender from "../../components/calender";
const DateRoute: React.FC<unknown> = () => {
const router = useRouter();
const { date: slug } = router.query;
const [date, setDate] = useState<UpdateCalenderPropsDateLayout | null>(null);
const [error, setError] = useState<boolean>(false);
// const dateRange = useRef(findValidDateRange());
// const validDateRange = Object.assign({}, dateRange.current);
const validateDateInput = (
dateArr: number[]
): UpdateCalenderPropsDateLayout => {
if (!(dateArr.length >= 2) && !(dateArr.length <= 3)) {
return {
year: 0,
month: 0,
day: 0
};
}
const date = {
year: 0,
month: 0,
day: 0
};
if (/^(19|20)\d{2}$/.test(`${dateArr[0]}`)) {
date.year = dateArr[0];
}
if (dateArr[1] > 0 && dateArr[1] <= 12) {
date.month = dateArr[1];
}
if (date.month && date.year) {
const lastDay = getDate(
endOfMonth(new Date(date.year, date.month - 1, 1))
);
if (dateArr[2] && dateArr[2] > 0 && dateArr[2] <= lastDay) {
date.day = dateArr[2];
} else if (!dateArr[2]) {
date.day = 1;
}
}
return date;
};
/**
* ! This function does not work as is. It is causing infinite loops whe used within the useEffect.
*/
// const validateDateRange = (
// slugDate: Date
// ): [Date, "after" | "before" | "valid"] => {
// const { start: validStart, end: validEnd } = validDateRange;
// // Check if the slug date is beyond the valid end date.
// if (isAfter(slugDate, validEnd)) {
// // router.push("/calender/now");
// console.warn(
// "Slug date is after the valid date range for this calendar!!!"
// );
// return [validEnd, "after"];
// // Check if the slug is before the valid start date.
// } else if (isBefore(slugDate, validStart)) {
// console.warn(
// "Slug date is before the valid date range for this calendar!!!"
// );
// return [validStart, "before"];
// // router.push(`/${getYear(validStart)}/${getMonth(validStart) + 1}`);
// } else {
// console.info(
// "Slug date is within the valid date range for this calendar."
// );
// return [slugDate, "valid"];
// }
// };
useEffect(() => {
// Checking if the slug exists and is an array.
if (slug && Array.isArray(slug)) {
console.log(slug);
// Grabbing the slug length
const length = slug.length;
// Parsing the slug to convert it from strings to numbers.
const parsedSlug = slug.map((e) => {
return parseInt(e);
});
// Checking if the slug has 2 to 3 numbers within the array. year/month/day.
if (length >= 2 && length <= 3) {
// Validate that the date is valid.
const newDate = validateDateInput(parsedSlug);
// If anything is invalid the year/day/month would be set to 0. This checks for the invalid condition.
if (newDate.year === 0 || newDate.month === 0 || newDate.day === 0) {
setError(true);
// Set the date to the valid date.
} else {
// TODO: Make sure the date is within the valid range using the validateDateRange function.
// const validDate = new Date(
// newDate.year,
// newDate.month - 1,
// newDate.day
// );
// const validDateWithinRange = validateDateRange(validDate)[0];
// setDate({
// ...{
// year: getYear(validDateWithinRange),
// month: getMonth(validDateWithinRange) + 1,
// day: getDate(validDateWithinRange)
// }
// });
setDate({
...newDate
});
}
} else if (length === 1) {
// Checking if the slug is not "now".
// ! Update this to include a check for "today".
if (slug[0] !== "now") {
setError(true);
return console.warn("improper date input:", slug);
}
}
}
}, [slug]);
/**
* ? Pushing into the router within the use effect does not create the infinite loop.
* ? The way the validate date range or the way it is being used within a useEffect is what is creating the infinite loop.
*/
// useEffect(() => {
// // Check is slug and date are valid.
// if (slug && date && date !== null) {
// // Check if the slug is an array and has a length of 2.
// if (Array.isArray(slug) && slug.length === 2) {
// const dateState = new Date(date.year, date.month - 1, date.day);
// const parsedSlug = slug.map((e) => {
// return parseInt(e);
// });
// const slugDate = new Date(parsedSlug[0], parsedSlug[1] - 1, 1);
// if (!isSameMonth(dateState, slugDate)) {
// const validDateWithinRange = validateDateRange(dateState);
// if (validDateRange[1] === "after") {
// router.push("/now");
// } else {
// router.push(
// `/${getYear(validDateWithinRange[0])}/${getMonth(
// validDateWithinRange[0]
// )}`
// );
// }
// }
// }
// }
// }, [date]);
if (router.isFallback) {
return <ErrorPage statusCode={404} />;
}
/**
* TODO: Update to disallow navigation in the future and too far in the past.
* Update so that a date given in the future take the user to /now to today's date.
* Update so that a date given beyond the last valid date will bring the user to the
* last month that has stickers within it (When filter is enabled) or to the creation date of the chart..
*/
return error ? (
<ErrorPage statusCode={404} />
) : (
<Box textAlign="center" w="100%" h="auto" pt="50px" pb="10vh">
<Provider store={store}>
<Calender date={date} isLoading={false} />
</Provider>
</Box>
);
};
export default DateRoute;

View File

@@ -1,23 +0,0 @@
import React, { useEffect } from "react";
import { useRouter } from "next/router";
import { Box, Heading } from "@chakra-ui/react";
const DateIndex = () => {
const router = useRouter();
useEffect(() => {
if (router) {
router.push("calendar/now");
}
}, [router]);
return (
<Box textAlign="center" w="100%" h="auto" pt="50px" pb="10vh">
<Heading as="h2" size="xl">
Loading
</Heading>
</Box>
);
};
export default DateIndex;

View File

@@ -1,130 +1,14 @@
import React, { Fragment, useEffect, useRef } from "react";
import React from "react";
import { Provider } from "react-redux";
import { store } from "../redux/store";
import { useAppDispatch, useAppSelector } from "../redux/hooks";
import { updateLoading } from "../features/calender";
import {
clearTutorialCompleted,
getAndSetTutorial,
StorageState
} from "../features/tutorial";
import { Box } from "@chakra-ui/react";
import { format, isAfter, isBefore, startOfDay } from "date-fns";
import Calender from "../components/calender";
import Tutorial from "../components/tutorial";
import LoadingOverlay from "../components/loading/LoadingOverlay";
import versionStringToNumber from "../../lib/versionStringToNumber";
import TempHero from "../components/hero";
const IndexPage = (): JSX.Element => {
const currDateStr: string = useAppSelector(
(state) => state.calender.currDate
);
const isLoading: boolean = useAppSelector(
(state) => state.calender.isLoading
);
const currDateObj: Date = new Date(currDateStr);
// * Tutorial * //
const completedTutorial: boolean = useAppSelector(
(state) => state.tutorial.completedTutorial
);
const tutorialCompletionInfo: StorageState = useAppSelector(
(state) => state.tutorial.storageState
);
const dispatch = useAppDispatch();
// Get the completed tutorial cookie or have it set to false.
useEffect(() => {
if (completedTutorial === null && tutorialCompletionInfo === null) {
dispatch(getAndSetTutorial());
}
if (completedTutorial !== null) {
dispatch(updateLoading(false));
}
}, [completedTutorial, dispatch, tutorialCompletionInfo]);
// Checking the exp date of completed tutorial cookie and if the version completed is out of date.
useEffect(() => {
if (tutorialCompletionInfo !== null) {
const { exp, version } = tutorialCompletionInfo;
const currDateObj: Date = new Date(currDateStr);
/**
* Checks if the completed tutorial cookie is expired.
* @param {Date} expDate the date when the completed tutorital cookie expires.
* @returns {boolean} true if the cookie is expired, false is otherwise.
*/
const expDateValidator = (expDate: Date): boolean => {
let flag = false;
const startOfToday = startOfDay(currDateObj);
if (isAfter(startOfToday, expDate)) {
flag = true;
}
return flag;
};
/**
* Checks if the last time the completed tutorial is before an update to the tutorial.
* @param {number} lastVersionCompleted the version number the tutorial was last completed.
* @returns {boolean} true if the version given is before the changes to the tutorial, false otherwise.
*/
const versionValidator = (lastVersionCompleted: number): boolean => {
const lastVersionWithChangeStr: string =
process.env.NEXT_PUBLIC_NEW_TUTORIAL_VERSION;
const lastVersionWithChange: number = versionStringToNumber(
lastVersionWithChangeStr
);
const lastUpdatedDateStr: string =
process.env.NEXT_PUBLIC_LAST_UPDATE_DATE;
const lastUpdatedDate: Date = new Date(lastUpdatedDateStr);
let flag = false;
if (
lastVersionCompleted < lastVersionWithChange ||
(lastVersionCompleted === lastVersionWithChange &&
isBefore(currDateObj, lastUpdatedDate))
) {
flag = true;
console.error("Completed cookie version is out of date.");
}
return flag;
};
if (expDateValidator(new Date(exp)) || versionValidator(version)) {
console.warn("Version outdated or cookie expired.");
dispatch(clearTutorialCompleted());
}
}
}, [currDateStr, dispatch, tutorialCompletionInfo]);
// Current date
const currDate = useRef<UpdateCalenderPropsDateLayout>({
year: parseInt(format(currDateObj, "y")),
month: parseInt(format(currDateObj, "M")),
day: parseInt(format(currDateObj, "d"))
});
return (
<Box textAlign="center" w="100%" h="auto" pt="50px" minWidth="min-content">
<Provider store={store}>
{isLoading === true ? (
<Fragment>
<LoadingOverlay />
<Calender date={currDate.current} isLoading={isLoading} />
</Fragment>
) : completedTutorial ? (
<Calender date={currDate.current} isLoading={isLoading} />
) : (
<Tutorial isLoading={isLoading} />
)}
<TempHero />
</Provider>
</Box>
);

View File

@@ -1,5 +1,10 @@
import { extendTheme, ThemeConfig } from "@chakra-ui/react";
// import { createBreakpoints } from "@chakra-ui/theme-tools";
import "@fontsource/montserrat";
import "@fontsource/tilt-neon";
import "@fontsource/anonymous-pro";
import "@fontsource/kalam";
import "@fontsource/anybody";
import buttons from "./components/buttonStyles";
const config: ThemeConfig = {
@@ -15,6 +20,14 @@ const config: ThemeConfig = {
// "2xl": "100em",
// });
const fonts = {
heading: `'Tilt Neon', system-ui`,
body: `'Montserrat', sans-serif`,
mono: `'Anonymous Pro', monospace`,
brand: `'Kalam', cursive`,
LCM: `'Anybody', system-ui`
};
const AppTheme = extendTheme({
config,
colors: {
@@ -22,6 +35,7 @@ const AppTheme = extendTheme({
main: "#3138dc",
primary: "#0068ff",
secondary: "#0086ff",
cosmic: "#314a9e",
hover: "#00aec1",
warning: "#ffbd48",
danger: "#FC8181",
@@ -29,8 +43,7 @@ const AppTheme = extendTheme({
footer: "#0097a7",
footerText: "black",
content: "#2d3748",
kofi: "#FF5E5B",
twitter: "#1da1f2"
kofi: "#FF5E5B"
},
loading: {
overlayBg: "#171923cb",
@@ -47,8 +60,10 @@ const AppTheme = extendTheme({
},
components: {
Button: buttons
}
},
fonts
// breakpoints,
});
export default AppTheme;
export { fonts };

View File

@@ -0,0 +1,29 @@
import { Heading, Text } from "@chakra-ui/react";
import React from "react";
import { fonts } from "../AppTheme";
interface BrandTextProps {
type: "Heading" | "Text";
text: string;
headerLevel?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
size?: string;
}
const BrandText = ({
type,
text,
headerLevel,
size
}: BrandTextProps): JSX.Element => {
return type === "Heading" ? (
<Heading fontFamily={fonts.brand} fontSize={size} as={headerLevel}>
{text}
</Heading>
) : (
<Text fontFamily={fonts.brand} fontSize={size}>
{text}
</Text>
);
};
export default BrandText;

View File

@@ -1,5 +1,5 @@
import React from "react";
import { Button, HStack, Link } from "@chakra-ui/react";
import { Button, HStack, Link, Text } from "@chakra-ui/react";
import navItems, { NavItem } from "./navItems";
const DesktopNav = (): JSX.Element => {
@@ -18,7 +18,9 @@ const DesktopNav = (): JSX.Element => {
{navItems.map((navItem: NavItem) => {
return (
<Link id={"dekstop-" + navItem[0]} key={navItem[0]} href={navItem[1]}>
<Button variant="nav">{navItem[0]}</Button>
<Button variant="nav">
<Text>{navItem[0]}</Text>
</Button>
</Link>
);
})}

View File

@@ -12,9 +12,10 @@ import { Icon } from "@iconify/react";
import DesktopNav from "./DesktopNav";
import MobileNav from "./MobileNav";
import appLogo from "../../../public/images/logo.svg";
import { fonts } from "../AppTheme";
const Header = (): JSX.Element => {
const appName = "LCM Potty Chart";
const appName = "Lucid Creations Media";
const appVersion = process.env.NEXT_PUBLIC_APP_VERSION_HEADER || "";
// Add transparency while not at the top of the page.
@@ -117,7 +118,7 @@ const Header = (): JSX.Element => {
>
<Image height="30" width="30" src={appLogo} alt="App Logo" />
<Heading as="h1" size="md">
<Heading as="h1" size="md" fontFamily={fonts.LCM} fontWeight="400">
{appName}
</Heading>
<Heading color="whiteAlpha.500" as="h2" size="sm">
@@ -151,7 +152,12 @@ const Header = (): JSX.Element => {
}}
>
<Image height="30" width="30" src={appLogo} alt="App Logo" />
<Heading as="h1" size="md">
<Heading
as="h1"
size="md"
fontFamily={fonts.LCM}
fontWeight="400"
>
{appName}
</Heading>
<Heading color="whiteAlpha.500" as="h2" size="sm">

View File

@@ -4,7 +4,8 @@ import {
Link,
MenuDivider,
MenuItem,
MenuList
MenuList,
Text
} from "@chakra-ui/react";
import navItems, { NavItem } from "./navItems";
@@ -42,7 +43,7 @@ const MobileNav: FC<MobileNavProps> = ({ updateOpen }: MobileNavProps) => {
<Link onClick={() => updateOpen(false)} href={navItem[1]}>
{index === 0 ? <MenuDivider /> : <Fragment></Fragment>}
<Button w="100vw" variant={"nav"} p={0} m="auto">
{navItem[0]}
<Text>{navItem[0]}</Text>
</Button>
<MenuDivider />
</Link>

851
yarn.lock

File diff suppressed because it is too large Load Diff