initial survey renderer

pull/2/head
bvn13 2023-04-29 22:45:52 +03:00
parent 1d1bcceb2d
commit a17b6fa63d
65 changed files with 53367 additions and 0 deletions

30
build.gradle 100644
View File

@ -0,0 +1,30 @@
gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS
buildscript {
ext {
gradleNodePluginVersion = '3.5.1'
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath "com.github.node-gradle:gradle-node-plugin:${gradleNodePluginVersion}"
}
}
subprojects {
if (it.name == 'survey') {
apply plugin: 'java'
apply plugin: 'com.github.node-gradle.node'
}
group = 'me.bvn13.tl-esa-tools'
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
}

View File

@ -0,0 +1,3 @@
org.gradle.console = plain
org.gradle.logging.level = info
org.gradle.jvmargs = -Xmx4G

BIN
gradle/wrapper/gradle-wrapper.jar vendored 100644

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Wed Dec 21 12:15:26 MSK 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

185
gradlew vendored 100755
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored 100644
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

20
q/template.json 100644
View File

@ -0,0 +1,20 @@
{
"title": "",
"intro": "",
"settings": {
"randomizeQuestions": false,
"randomizeOptions": false
},
"questions": [
{
"group" : "",
"question": "",
"options": [
{
"option": "",
"value": 0
}
]
}
]
}

3
settings.gradle 100644
View File

@ -0,0 +1,3 @@
rootProject.name = 'tl-esa-tools'
include 'survey'

8
site/.htaccess 100644
View File

@ -0,0 +1,8 @@
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]
</IfModule>

33
site/app/index.css 100644
View File

@ -0,0 +1,33 @@
/* src/index.css */
body {
margin: 0;
font-family:
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
"Roboto",
"Oxygen",
"Ubuntu",
"Cantarell",
"Fira Sans",
"Droid Sans",
"Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: rgb(250 250 250);
}
code {
font-family:
source-code-pro,
Menlo,
Monaco,
Consolas,
"Courier New",
monospace;
}
/* src/App.css */
/* src/components/survey/Survey.css */
/*# sourceMappingURL=index.css.map */

View File

@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../survey/src/index.css"],
"sourcesContent": ["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n background-color: rgb(250 250 250);\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n}\n"],
"mappings": ";AAAA;AACI;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA;AACA;AACA;AAAA;AAGJ;AACI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
"names": []
}

41373
site/app/index.js 100644

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

BIN
site/favicon.ico 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

25
site/index.html 100644
View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description"
content="TL :: ESA"
/>
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="/app/index.css" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
<title>TL :: ESA</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/app/index.js"></script>
</body>
</html>

BIN
site/logo-big.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
site/logo192.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
site/logo512.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

25
site/manifest.json 100644
View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,295 @@
{
"title": "АGILE-КОМПАС",
"intro": "В этом тесте 15 вопросов, которые помогут определить оптимальную комбинацию целей на спринт по сферам и уровням.\n\nВаша задача — вспомнить, как часто за последний месяц вы испытывали то, что указано в каждом вопросе.\n\nЭтот тест рекомендуется проходить каждый спринт перед постановкой целей.",
"settings": {
"randomizeQuestions": false,
"randomizeOptions": false
},
"questions": [
{
"group" : "1",
"title": "Я чувствую себя уставшим(ей) даже после длительного сна и отдыха",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "2",
"title": "Я могу разозлиться без видимой причины, меня раздражают позитивные и успешные люди",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Мне сложно концентрироваться на задаче больше 5 минут",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я чувствую собственную беспомощность, кажется, что от меня ничего не зависит",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Все вокруг кажется пустым и неинтересным",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я чувствую, что мне не к кому обратиться за помощью и поддержкой в сложной ситуации",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я думаю, что близкие ценят меня за то, что я делаю и как себя веду, а не просто за то, что я есть",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "В кругу близких и друзей я вынужден(а) играть чужую роль, чтобы меня принимали таким(ой), какой(ая) я есть",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я чувствую, что никому не нужен (нужна) и никто меня не любит",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Мне кажется, что никто из близких меня не понимает",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я боюсь потерять работу, потому что это приведет к финансовым трудностям",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Мне не хватает денег на питание, оплату жилья и на то, чтобы покрыть привычные расходы",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я финансово завишу от других (родителей, супруга(и), босса, партнеров по бизнесу)",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я боюсь, что перемены в стране, отрасли, в которой я работаю, или экономике лишат меня средств к существованиюЭто обязательный вопрос",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Мой доход скачет (то густо, то пусто) и находится вне моего контроля",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
}
]
}

View File

@ -0,0 +1,20 @@
{
"title": "",
"intro": "",
"settings": {
"randomizeQuestions": false,
"randomizeOptions": false
},
"questions": [
{
"group" : "",
"question": "",
"options": [
{
"option": "",
"value": 0
}
]
}
]
}

3
site/robots.txt 100644
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -0,0 +1,41 @@
module.exports = {
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended'],
plugins: ['@typescript-eslint', 'react-hooks', 'prettier'],
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
},
env: {
browser: true,
node: true,
es6: true,
jest: true
},
rules: {
"no-control-regex": 0,
'no-undef': 0,
'no-unused-vars': 'off',
'react/prop-types': 0,
'@typescript-eslint/camelcase': 0,
'@typescript-eslint/no-unused-vars': 1,
'@typescript-eslint/no-use-before-define': 0,
'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/ban-ts-ignore': 0,
'@typescript-eslint/explicit-member-accessibility': 0,
'@typescript-eslint/member-delimiter-style': 0,
'@typescript-eslint/no-empty-function': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-non-null-assertion': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
},
settings: {
react: {
version: 'detect'
}
}
}

View File

@ -0,0 +1,8 @@
{
"printWidth": 160,
"tabWidth": 4,
"singleQuote": true,
"semi": false,
"trailingComma": "none",
"bracketSpacing": true
}

140
survey/README.md 100644
View File

@ -0,0 +1,140 @@
<h1 align="center">New React App</h1>
<br />
This is a blank README file that you can customize at your needs.\
Describe your project, how it works and how to contribute to it.
<br />
# 🚀 Available Scripts
In the project directory, you can run:
<br />
## ⚡️ start
```
npm start
```
or
```
yarn start
```
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
<br />
## 🧪 test
```
npm test
```
or
```
yarn test
```
Launches the test runner in the interactive watch mode.
<br />
## 🦾 build
```
npm build
```
or
```
yarn build
```
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
<br />
## 🧶 lint
```
npm lint
```
or
```
yarn lint
```
Creates a `.eslintcache` file in which ESLint cache is stored. Running this command can dramatically improve ESLint's running time by ensuring that only changed files are linted.
<br />
## 🎯 format
```
npm format
```
or
```
yarn format
```
Checks if your files are formatted. This command will output a human-friendly message and a list of unformatted files, if any.
<br />
# 🧬 Project structure
This is the structure of the files in the project:
```sh
├── public # public files (favicon, .htaccess, manifest, ...)
├── src # source files
│ ├── components
│ ├── pages
│ ├── resources # images, constants and other static resources
│ ├── store # Redux store
│ │ ├── actions # store's actions
│ │ └── reducers # store's reducers
│ ├── styles
│ ├── tests # all test files
│ ├── types # data interfaces
│ ├── utility # utilities functions and custom components
│ ├── App.tsx
│ ├── index.tsx
│ ├── react-app-env.d.ts
│ ├── RootComponent.tsx # React component with all the routes
│ ├── serviceWorker.ts
│ └── setupTests.ts
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── package.json
├── README.md
└── tsconfig.json
```
# 📖 Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
#
<p align="center">Bootstrapped with Create React App.</p>

View File

@ -0,0 +1,93 @@
// https://dzone.com/articles/integrating-java-and-npm-builds-using-gradle
// https://github.com/node-gradle/gradle-node-plugin/blob/master/examples/vertx-react/build.gradle
plugins {
id "base"
// You have to specify the plugin version, for instance
// id "com.github.node-gradle.node" version "3.0.0"
// This works as is here because we use the plugin source
id "com.github.node-gradle.node"
}
// https://github.com/node-gradle/gradle-node-plugin/blob/master/examples/simple-node/npm/build.gradle
node {
/* gradle-node-plugin configuration
https://github.com/srs/gradle-node-plugin/blob/master/docs/node.md
Task name pattern:
./gradlew npm_<command> Executes an NPM command.
*/
// Version of node to use.
version = '18.12.1'
// Version of npm to use.
npmVersion = '8.1.4'
// Version of Yarn
yarnVersion = '1.22.19'
// If true, it will download node using above parameters.
// If false, it will try to use globally installed node.
download = true
}
def yarn = tasks.named("yarn")
def copyTask = tasks.register("copyPublic", Copy) {
dependsOn(yarn)
mustRunAfter(yarn)
from "public"
into "${buildDir}/../../site"
println("copied")
}
def rewriteContent = tasks.register("rewriteFileContent") {
dependsOn(copyTask)
mustRunAfter(copyTask)
inputs.dir("public")
doLast {
def publicPath = ""
def path = "${buildDir}/../../site"
ant.replaceregexp(match: '%PUBLIC_URL%', replace: publicPath, flags: 'g', byline: true) {
fileset(dir: path, includes: 'index.html,manifest.json')
}
println("rewritten")
}
}
// https://www.metachris.com/2021/04/starting-a-typescript-project-in-2021/
def buildTask = tasks.register("buildWebapp", NpxTask) {
command = "esbuild"
args = ["src/index.tsx", "--bundle", "--sourcemap", "--outfile=${buildDir}/../../site/app/index.js"]
dependsOn(rewriteContent)
mustRunAfter(rewriteContent)
inputs.dir(fileTree("src").exclude("**/*.test.js").exclude("**/*.spec.js").exclude("**/__tests__/**/*.js"))
inputs.dir("node_modules")
inputs.dir("public")
outputs.dir("${buildDir}/../../site")
environment = ["BUILD_PATH": "${buildDir}/../../site"]
println("built")
}
def testTask = tasks.register("testWebapp", NpxTask) {
command = "react-scripts"
args = ["test"]
dependsOn(yarn)
inputs.dir("node_modules")
inputs.dir("src")
inputs.dir("public")
outputs.upToDateWhen { true }
environment = ['CI': 'true']
}
sourceSets {
java {
main {
resources {
// This makes the processResources task automatically depend on the buildWebapp one
srcDir(buildTask)
}
}
}
}

View File

@ -0,0 +1,58 @@
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@reduxjs/toolkit": "^1.9.5",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"axios": "^0.27.2",
"eslint-plugin-prettier": "^4.2.1",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.11.0",
"react-scripts": "^5.0.1",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.4.2",
"sass": "^1.62.1",
"sass-loader": "^13.2.2",
"@mui/material": "^5.12.0",
"@mui/styles": "^5.12.0",
"notistack": "^3.0.1",
"@emotion/styled": "^11.10.5",
"@emotion/core": "^11.0.0",
"@emotion/react": "^11.10.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"lint": "eslint --cache .",
"format": "prettier --check ."
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
"defaults",
"not IE 11"
],
"devDependencies": {
"@types/jest": "^29.5.1",
"@types/node": "^18.16.3",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@types/react-redux": "^7.1.25",
"typescript": "^4.9.5"
},
"homepage": ".",
"postcss": {
"plugins": {
"autoprefixer": {}
}
}
}

View File

@ -0,0 +1,8 @@
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]
</IfModule>

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description"
content="TL :: ESA"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="%PUBLIC_URL%/app/index.css" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
<title>TL :: ESA</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="%PUBLIC_URL%/app/index.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,295 @@
{
"title": "АGILE-КОМПАС",
"intro": "В этом тесте 15 вопросов, которые помогут определить оптимальную комбинацию целей на спринт по сферам и уровням.\n\nВаша задача — вспомнить, как часто за последний месяц вы испытывали то, что указано в каждом вопросе.\n\nЭтот тест рекомендуется проходить каждый спринт перед постановкой целей.",
"settings": {
"randomizeQuestions": false,
"randomizeOptions": false
},
"questions": [
{
"group" : "1",
"title": "Я чувствую себя уставшим(ей) даже после длительного сна и отдыха",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "2",
"title": "Я могу разозлиться без видимой причины, меня раздражают позитивные и успешные люди",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Мне сложно концентрироваться на задаче больше 5 минут",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я чувствую собственную беспомощность, кажется, что от меня ничего не зависит",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Все вокруг кажется пустым и неинтересным",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я чувствую, что мне не к кому обратиться за помощью и поддержкой в сложной ситуации",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я думаю, что близкие ценят меня за то, что я делаю и как себя веду, а не просто за то, что я есть",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "В кругу близких и друзей я вынужден(а) играть чужую роль, чтобы меня принимали таким(ой), какой(ая) я есть",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я чувствую, что никому не нужен (нужна) и никто меня не любит",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Мне кажется, что никто из близких меня не понимает",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я боюсь потерять работу, потому что это приведет к финансовым трудностям",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Мне не хватает денег на питание, оплату жилья и на то, чтобы покрыть привычные расходы",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я финансово завишу от других (родителей, супруга(и), босса, партнеров по бизнесу)",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Я боюсь, что перемены в стране, отрасли, в которой я работаю, или экономике лишат меня средств к существованиюЭто обязательный вопрос",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
},
{
"group" : "3",
"title": "Мой доход скачет (то густо, то пусто) и находится вне моего контроля",
"question": "Как часто за последний месяц вы испытывали это?",
"options": [
{
"option": "Ни разу",
"value": 2
},
{
"option": "Время от времени",
"value": 1
},
{
"option": "Постоянно",
"value": 0
}
]
}
]
}

View File

@ -0,0 +1,20 @@
{
"title": "",
"intro": "",
"settings": {
"randomizeQuestions": false,
"randomizeOptions": false
},
"questions": [
{
"group" : "",
"question": "",
"options": [
{
"option": "",
"value": 0
}
]
}
]
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

18
survey/src/App.tsx 100644
View File

@ -0,0 +1,18 @@
import './App.css'
import React from 'react'
import {Provider} from 'react-redux'
import {PersistGate} from 'redux-persist/integration/react'
import RootComponent from './RootComponent'
import {persistor, store} from './store/reducers/store'
const App: React.FC = () => {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RootComponent/>
</PersistGate>
</Provider>
)
}
export default App

View File

@ -0,0 +1,19 @@
import React from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
import HomePage from './pages/HomePage'
import NotFoundPage from './pages/NotFoundPage'
import { ROUTES } from './resources/routes-constants'
// import './styles/main.sass'
const RootComponent: React.FC = () => {
return (
<Router>
<Routes>
<Route path="*" element={<NotFoundPage />} />
<Route path={ROUTES.HOMEPAGE_ROUTE} element={<HomePage />} />
</Routes>
</Router>
)
}
export default RootComponent

View File

@ -0,0 +1,37 @@
export interface SettingsDto {
randomizeQuestions: boolean,
randomizeOptions: boolean
}
export interface OptionDto {
option: string,
value: number
}
export interface QuestionDto {
group: string,
title: string,
question: string,
options: OptionDto[]
}
export interface SurveyDto {
title: string,
intro: string,
settings: SettingsDto,
questions: QuestionDto[]
}
const Api = {
loadSurvey: (uri: string,
onSuccess: (survey: SurveyDto) => void,
onError: (failure: string) => void) => {
fetch(uri)
.then(res => res.json())
.then(out => onSuccess(out as SurveyDto))
.catch(err => onError(err))
}
};
export default Api;

View File

@ -0,0 +1,24 @@
import moment from 'moment'
import React, { useEffect, useState } from 'react'
const DateDisplay: React.FC = () => {
const [date, setDate] = useState('')
/**
* On component render sets the date state to current date and time
*/
useEffect(() => {
const interval = setInterval(() => {
setDate(moment().toDate().toString())
}, 1000)
return () => clearInterval(interval)
}, [])
return (
<div style={{ position: 'relative', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column' }}>
<span style={{ color: 'orange' }}>{date}</span>
</div>
)
}
export default DateDisplay

View File

@ -0,0 +1,99 @@
import './Survey.css'
import * as React from "react";
import {SurveyDto} from "./../../api/Api";
import {Box, Button, Paper, Step, StepContent, StepLabel, Stepper, Typography} from "@mui/material";
interface Props {
survey: SurveyDto | undefined
}
const Survey: React.FC<Props> = ({survey}) => {
const [activeStep, setActiveStep] = React.useState(-1);
const handleNext = (group: string | undefined, value: number | undefined) => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
const handleReset = () => {
setActiveStep(-1);
};
const prepare = (text: string | undefined) => {
if (text !== undefined) {
return text
.replaceAll("\n", "<br/>")
.replaceAll("<script", "script")
} else {
return ""
}
}
return (
<div>
<h1 style={{fontSize: '4em'}}>{survey?.title}</h1>
<div dangerouslySetInnerHTML={{__html: prepare(survey?.intro)}}></div>
<br/>
<Box sx={{maxWidth: 400}}>
<Button
variant="contained"
onClick={() => handleNext(undefined, undefined)}
sx={{mt: 1, mr: 1}}
disabled={activeStep !== -1}
>
Start
</Button>
</Box>
<br/>
<Box sx={{maxWidth: 400}}>
<Stepper activeStep={activeStep} orientation={"vertical"}>
{survey?.questions.map((question, index) =>
<Step key={question.question}>
<StepLabel optional={
index === survey?.questions.length - 1 ? (
<Typography variant="caption">Last step</Typography>
) : null
}>
{'Question ' + (index + 1)}
</StepLabel>
<StepContent>
<h4>{question.title}</h4>
<Typography>{question.question}</Typography>
<Box sx={{mb: 2}}>
<div>
{question.options.map((option, index) =>
<div>
<Button
variant="contained"
onClick={() => handleNext(question.group, option.value)}
sx={{mt: 1, mr: 1}}
>
{option.option}
</Button>
<br/>
</div>
)}
</div>
</Box>
</StepContent>
</Step>
)}
</Stepper>
{activeStep === survey?.questions.length && (
<Paper square elevation={0} sx={{p: 3}}>
<Typography>All steps completed - you&apos;re finished</Typography>
<Button onClick={handleReset} sx={{mt: 1, mr: 1}}>
Reset
</Button>
</Paper>
)}
</Box>
</div>
)
}
export default Survey;

View File

@ -0,0 +1,16 @@
import * as React from "react";
interface MyProps {
condition: boolean,
children: any
}
const If = (props: MyProps) => {
return (
<div>
{props.condition === true ? props.children: (<></>)}
</div>
)
}
export default If;

View File

@ -0,0 +1,14 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: rgb(250 250 250);
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -0,0 +1,13 @@
import './index.css'
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import * as serviceWorker from './serviceWorker'
const root = createRoot(document.getElementById('root')!) // createRoot(container!) if you use TypeScript
root.render(<App />)
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister()

View File

@ -0,0 +1,43 @@
import React, {useEffect} from 'react'
import DateDisplay from '../components/DateDisplay'
import {SnackbarOrigin, useSnackbar} from 'notistack';
import Api, {SurveyDto} from "./../api/Api";
import Survey from "./../components/survey/Survey";
import {Container, Grid} from "@mui/material";
const HomePage: React.FC = () => {
const [survey, setSurvey] = React.useState<SurveyDto>();
const queryParameters = new URLSearchParams(window.location.search);
const surveyUri: string = queryParameters.get("s") as string
const {enqueueSnackbar} = useSnackbar();
const snackbarAnchor = {
horizontal: 'center',
vertical: 'top'
} as SnackbarOrigin;
useEffect(() => {
Api.loadSurvey(surveyUri,
survey => {
setSurvey(survey);
console.log("Survey: " + survey)
},
(failure) => {
enqueueSnackbar(failure, {
variant: 'error',
anchorOrigin: snackbarAnchor
})
})
}, [surveyUri]);
return (
<Container>
<Grid container={true} spacing={2}>
<Grid item xs={6} md={8}>
<Survey survey={survey}/>
</Grid>
</Grid>
</Container>
)
}
export default HomePage

View File

@ -0,0 +1,25 @@
import React from 'react'
import { useNavigate } from 'react-router-dom'
import { ROUTES } from '../resources/routes-constants'
const NotFoundPage: React.FC = () => {
const navigate = useNavigate()
/**
* Call this function to redirect the user to the homepage.
*/
const redirectToHomePage = () => {
navigate(ROUTES.HOMEPAGE_ROUTE)
}
return (
<div style={{ position: 'relative', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column' }}>
<h1 style={{ fontSize: '4em' }}>Oops 404!</h1>
<span style={{ cursor: 'pointer' }} onClick={() => redirectToHomePage()}>
Homepage
</span>
</div>
)
}
export default NotFoundPage

1
survey/src/react-app-env.d.ts vendored 100644
View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -0,0 +1,5 @@
const baseUrl = 'http://exampleurl'
export const getData = (userId: number): string => {
return baseUrl + '/data/' + userId
}

View File

@ -0,0 +1,3 @@
export const ROUTES = {
HOMEPAGE_ROUTE: '/'
}

View File

@ -0,0 +1,103 @@
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
window.location.hostname === '[::1]' ||
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
)
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void
onUpdate?: (registration: ServiceWorkerRegistration) => void
}
export const register = (config?: Config): void => {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
if (publicUrl.origin !== window.location.origin) {
return
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) {
checkValidServiceWorker(swUrl, config)
navigator.serviceWorker.ready.then(() => {
console.log('This web app is being served cache-first by a service ' + 'worker. To learn more, visit https://bit.ly/CRA-PWA')
})
} else {
registerValidSW(swUrl, config)
}
})
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing
if (installingWorker == null) {
return
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
console.log('New content is available and will be used when all ' + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.')
if (config && config.onUpdate) {
config.onUpdate(registration)
}
} else {
console.log('Content is cached for offline use.')
if (config && config.onSuccess) {
config.onSuccess(registration)
}
}
}
}
}
})
.catch((error) => {
console.error('Error during service worker registration:', error)
})
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
fetch(swUrl, {
headers: { 'Service-Worker': 'script' }
})
.then((response) => {
const contentType = response.headers.get('content-type')
if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload()
})
})
} else {
registerValidSW(swUrl, config)
}
})
.catch(() => {
console.log('No internet connection found. App is running in offline mode.')
})
}
export const unregister = (): void => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then((registration) => {
registration.unregister()
})
.catch((error) => {
console.error(error.message)
})
}
}

View File

@ -0,0 +1 @@
import '@testing-library/jest-dom'

View File

@ -0,0 +1,3 @@
import { createAction } from '@reduxjs/toolkit'
export const setContents = createAction<string[]>('data/setContents')

View File

@ -0,0 +1,18 @@
import { createReducer } from '@reduxjs/toolkit'
import { setContents } from '../actions/data'
interface DataReducer {
contents: string[]
}
const initialState: DataReducer = {
contents: []
}
const dataReducer = createReducer<DataReducer>(initialState, (builder) => {
builder.addCase(setContents, (state, action) => {
state.contents = action.payload
})
})
export default dataReducer

View File

@ -0,0 +1,36 @@
import { combineReducers, configureStore } from '@reduxjs/toolkit'
import { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage
import data from './data'
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
const rootReducer = combineReducers({
data
})
const persistedReducer = persistReducer(
{
key: 'root',
storage,
whitelist: ['data']
},
rootReducer
)
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
}
})
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const persistor = persistStore(store)

View File

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react'
import DateDisplay from '../../components/DateDisplay'
test('renders current date', () => {
render(<DateDisplay />)
const timeFormat = screen.getByText(/GMT/i)
expect(timeFormat).toBeInTheDocument()
})

View File

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react'
import HomePage from '../../pages/HomePage'
test('renders hello world message', () => {
render(<HomePage />)
const greetings = screen.getByText(/Hello world/i)
expect(greetings).toBeInTheDocument()
})

View File

@ -0,0 +1,13 @@
import { render, screen } from '@testing-library/react'
import { BrowserRouter as Router } from 'react-router-dom'
import NotFoundPage from '../../pages/NotFoundPage'
test('renders error message', () => {
render(
<Router>
<NotFoundPage />
</Router>
)
const errorMessage = screen.getByText(/Oops 404!/i)
expect(errorMessage).toBeInTheDocument()
})

View File

@ -0,0 +1,10 @@
export interface ReducerData {
contents: string[]
}
export type ReduxActionData<T> = {
type: any
payload?: T
}
export type ReduxAction<T> = (data: T) => ReduxActionData<T>

View File

@ -0,0 +1,65 @@
import axios from 'axios'
const CustomAxios = axios.create()
const toCamelCase: any = (object: any) => {
let transformedObject = object
if (typeof object === 'object' && object !== null) {
if (object instanceof Array) {
transformedObject = object.map(toCamelCase)
} else {
transformedObject = {}
for (const key in object) {
if (object[key] !== undefined) {
const newKey = key.replace(/(_\w)|(-\w)/g, (k) => k[1].toUpperCase())
transformedObject[newKey] = toCamelCase(object[key])
}
}
}
}
return transformedObject
}
export const toSnackCase: any = (object: any) => {
let transformedObject = object
if (typeof object === 'object' && object !== null) {
if (object instanceof Array) {
transformedObject = object.map(toSnackCase)
} else {
transformedObject = {}
for (const key in object) {
if (object[key] !== undefined) {
const newKey = key
.replace(/\.?([A-Z]+)/g, function (_, y) {
return '_' + y.toLowerCase()
})
.replace(/^_/, '')
transformedObject[newKey] = toSnackCase(object[key])
}
}
}
}
return transformedObject
}
CustomAxios.interceptors.response.use(
(response) => {
response.data = toCamelCase(response.data)
return response
},
(error) => {
return Promise.reject(error)
}
)
CustomAxios.interceptors.request.use(
(config) => {
config.data = toSnackCase(config.data)
return config
},
(error) => {
return Promise.reject(error)
}
)
export default CustomAxios

View File

@ -0,0 +1,8 @@
/**
* This function can be used anywhere in the app to greet the user
* @param userName The user's first name
* @returns A kind greeting message
*/
export const sayHello = (userName: string): string => {
return 'Welcome ' + userName + '!'
}

View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es6",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

9903
survey/yarn.lock 100644

File diff suppressed because it is too large Load Diff