Skip to content

Commit 158d716

Browse files
committed
init
0 parents  commit 158d716

34 files changed

+12676
-0
lines changed

.env.dist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
REACT_APP_QUIZ_SERVER_URL=https://lis-dev.github.io/big5-data
2+
REACT_APP_DEFAULT_LANG=en

.gitignore

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# dependencies
2+
/node_modules
3+
/.pnp
4+
.pnp.js
5+
6+
# testing
7+
/coverage
8+
9+
# production
10+
/build
11+
12+
# misc
13+
.DS_Store
14+
.env
15+
.env.local
16+
.env.development.local
17+
.env.test.local
18+
.env.production.local
19+
20+
npm-debug.log*
21+
yarn-debug.log*
22+
yarn-error.log*

.prettierrc.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"trailingComma": "es5",
3+
"tabWidth": 2,
4+
"semi": false,
5+
"singleQuote": true
6+
}

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Big Five personality traits
2+
3+
The project implements a website with a quiz: 120 questions of [The Big Five Test](https://en.wikipedia.org/wiki/Big_Five_personality_traits) and summary.
4+
5+
## Sources
6+
- [Questions](https://github.com/Alheimsins/b5-johnson-120-ipip-neo-pi-r)
7+
- [Result Texts](https://github.com/Alheimsins/b5-result-text)
8+
- [Calculation](https://github.com/Alheimsins/bigfive-calculate-score)
9+
10+
## Database
11+
The application gets data info from https://github.com/lis-dev/big5-data repository. This behavior can be changed via `REACT_APP_QUIZ_SERVER_URL` environment variable
12+
13+
## Localization
14+
All supported languages can be found on https://github.com/lis-dev/big5-data/tree/master/data/result . The default language is `en` (can be changed via `REACT_APP_DEFAULT_LANG` environment variable)
15+
16+
## Stack
17+
- TypeScript
18+
- NodeJs
19+
20+
## Main dependencies
21+
- [React](https://reactjs.org/), [React Create App](https://github.com/facebook/create-react-app)
22+
- [Tailwind](https://tailwindcss.com)
23+
- [Kutty](https://kutty.netlify.app/docs/)
24+
- [Vatio](https://github.com/pmndrs/valtio/)

craco.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
style: {
3+
postcss: {
4+
plugins: [
5+
require('tailwindcss'),
6+
require('autoprefixer'),
7+
],
8+
},
9+
},
10+
}
11+

package.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "big5",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"@craco/craco": "^6.4.0",
7+
"@testing-library/jest-dom": "^5.11.4",
8+
"@testing-library/react": "^11.1.0",
9+
"@testing-library/user-event": "^12.1.10",
10+
"@types/jest": "^26.0.15",
11+
"@types/node": "^12.0.0",
12+
"@types/react": "^17.0.0",
13+
"@types/react-dom": "^17.0.11",
14+
"kutty": "^0.5.2",
15+
"react": "^17.0.2",
16+
"react-dom": "^17.0.2",
17+
"react-scripts": "^4.0.3",
18+
"typescript": "^4.1.2",
19+
"valtio": "^1.2.6",
20+
"web-vitals": "^1.0.1"
21+
},
22+
"scripts": {
23+
"start": "craco start",
24+
"build": "craco build",
25+
"test": "craco test",
26+
"eject": "react-scripts eject"
27+
},
28+
"eslintConfig": {
29+
"extends": [
30+
"react-app",
31+
"react-app/jest"
32+
]
33+
},
34+
"browserslist": {
35+
"production": [
36+
">0.2%",
37+
"not dead",
38+
"not op_mini all"
39+
],
40+
"development": [
41+
"last 1 chrome version",
42+
"last 1 firefox version",
43+
"last 1 safari version"
44+
]
45+
},
46+
"devDependencies": {
47+
"autoprefixer": "^9.8.8",
48+
"postcss": "^7.0.39",
49+
"prettier": "^2.4.1",
50+
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17"
51+
}
52+
}

public/favicon.svg

Lines changed: 10 additions & 0 deletions
Loading

public/index.html

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<meta name="theme-color" content="#000000" />
8+
<meta
9+
name="description"
10+
content="The Big Five Test Questions Repository and open website sources"
11+
/>
12+
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13+
<!--
14+
manifest.json provides metadata used when your web app is installed on a
15+
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16+
-->
17+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18+
<!--
19+
Notice the use of %PUBLIC_URL% in the tags above.
20+
It will be replaced with the URL of the `public` folder during the build.
21+
Only files inside the `public` folder can be referenced from the HTML.
22+
23+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
24+
work correctly both with client-side routing and a non-root public URL.
25+
Learn how to configure a non-root public URL by running `npm run build`.
26+
-->
27+
<title>The Big Five Test</title>
28+
</head>
29+
<body>
30+
<noscript>You need to enable JavaScript to run this app.</noscript>
31+
<div id="root"></div>
32+
<!--
33+
This HTML file is a template.
34+
If you open it directly in the browser, you will see an empty page.
35+
36+
You can add webfonts, meta tags, or analytics to this file.
37+
The build step will place the bundled scripts into the <body> tag.
38+
39+
To begin the development, run `npm start` or `yarn start`.
40+
To create a production bundle, use `npm run build` or `yarn build`.
41+
-->
42+
</body>
43+
</html>

public/logo192.png

8.92 KB
Loading

public/manifest.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"short_name": "Big 5",
3+
"name": "Big Five Test",
4+
"icons": [
5+
{
6+
"src": "favicon.png",
7+
"sizes": "64x64 32x32 24x24 16x16",
8+
"type": "image/x-icon"
9+
},
10+
{
11+
"src": "logo192.png",
12+
"type": "image/png",
13+
"sizes": "192x192"
14+
},
15+
{
16+
"src": "logo512.png",
17+
"type": "image/png",
18+
"sizes": "512x512"
19+
}
20+
],
21+
"start_url": ".",
22+
"display": "standalone",
23+
"theme_color": "#000000",
24+
"background_color": "#ffffff"
25+
}

public/robots.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# https://www.robotstxt.org/robotstxt.html
2+
User-agent: *
3+
Disallow:

src/App.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.logo {
2+
display: flex;
3+
height: 10vmin;
4+
background: url('./logo.png') no-repeat;
5+
background-size: auto 100%;
6+
padding-left: 55px;
7+
}

src/App.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useSnapshot } from 'valtio'
2+
import './App.css'
3+
import { Quiz } from './components/Quiz'
4+
import { state } from './state'
5+
6+
type Props = {
7+
lang?: string
8+
}
9+
10+
export function App(props: Props) {
11+
const snap = useSnapshot(state)
12+
13+
return <Quiz lang={snap.lang}></Quiz>
14+
}
15+
16+
export default App

src/api/Api.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// TODO Use transport
2+
import { Choices, Question, ResultText } from '../types'
3+
4+
const server = process.env.REACT_APP_QUIZ_SERVER_URL || process.env.PUBLIC_URL
5+
6+
export function getQuestions(lang: string): Promise<Question[]> {
7+
return getData('test', 'questions.json', lang)
8+
}
9+
10+
export function getChoices(lang: string): Promise<Choices> {
11+
return getData('test', 'choices.json', lang)
12+
}
13+
14+
export function getResultTexts(lang: string): Promise<ResultText[]> {
15+
return getData('result', 'index.json', lang)
16+
}
17+
18+
const getData = async (
19+
dir: 'test' | 'result',
20+
filename: string,
21+
lang: string
22+
) => {
23+
return (await fetch(server + `/data/${dir}/${lang}/` + filename)).json()
24+
}

src/b5/CalculationFunction.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Result } from '../types';
2+
3+
export const calculationFunction = (score: number, count: number): Result => {
4+
const average = score / count;
5+
let result: Result = 'neutral';
6+
if (average > 3) {
7+
result = 'high';
8+
} else if (average < 3) {
9+
result = 'low';
10+
}
11+
return result;
12+
};

src/b5/ResultCalculator.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {
2+
Answer,
3+
DomainResult,
4+
Result,
5+
ResultObject,
6+
domains,
7+
DomainResultObject,
8+
} from '../types';
9+
10+
export class ResultCalculator {
11+
constructor(private calculationFunction: (s: number, c: number) => Result) {}
12+
13+
calculate(answers: Answer[]): DomainResult {
14+
let domainResult: DomainResult = this.initDomainResult();
15+
16+
for (let answer of answers) {
17+
const result = domainResult[answer.domain];
18+
result.score += answer.score;
19+
result.count++;
20+
result.result = this.calculationFunction(result.score, result.count);
21+
this.calculateFacet(result, answer);
22+
}
23+
return domainResult;
24+
}
25+
26+
private calculateFacet(result: DomainResultObject, answer: Answer): void {
27+
const facets = result.facets;
28+
if (typeof facets[answer.facet] === 'undefined') {
29+
facets[answer.facet] = new ResultObject(0, 0, 'neutral');
30+
}
31+
const facet = facets[answer.facet];
32+
facet.count++;
33+
facet.score += answer.score;
34+
facet.result = this.calculationFunction(facet.score, facet.count);
35+
}
36+
37+
private initDomainResult(): DomainResult {
38+
let domainResult: Partial<DomainResult> = {};
39+
for (let domain of domains) {
40+
domainResult[domain] = {
41+
score: 0,
42+
count: 0,
43+
result: 'neutral',
44+
facets: [],
45+
};
46+
}
47+
return domainResult as DomainResult;
48+
}
49+
}

src/components/Base.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { SelectLanguage } from './SelectLanguage'
2+
3+
export function Base(props: { children: any }) {
4+
return (
5+
<section className="min-h-screen bg-gray-50 ">
6+
<header className="sticky top-0 bg-white shadow z-10">
7+
<div className="flex flex-row items-center justify-between p-4 mx-auto max-w-7xl">
8+
<div className="justify-center logo items-center">
9+
<a href="/">The Big Five Personality Test</a>
10+
</div>
11+
<div className="flex flex-row">
12+
<SelectLanguage />
13+
</div>
14+
</div>
15+
</header>
16+
<main className="p-4 mx-auto max-w-7xl">{props.children}</main>
17+
</section>
18+
)
19+
}

src/components/ChoiceButton.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Choice } from '../types';
2+
3+
interface Props {
4+
choice: Choice;
5+
onClick: (choice: Choice) => void;
6+
}
7+
8+
export function ChoiceButton(props: Props) {
9+
return (
10+
<button className='w-full md:w-auto btn btn-light' onClick={(e) => {props.onClick(props.choice)}}>
11+
{props.choice.text}
12+
</button>
13+
);
14+
}

src/components/Loading.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function Loading() {
2+
return (
3+
<div className='spinner text-primary' role='status'>
4+
<span className='sr-only'>Loading...</span>
5+
</div>
6+
);
7+
}

src/components/QuestionCard.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Choice, Question } from '../types';
2+
import { ChoiceButton } from './ChoiceButton';
3+
4+
interface Props {
5+
question: Question;
6+
choices: Choice[];
7+
number: number;
8+
totalNumber?: number;
9+
onAnswer: (c: Choice) => void;
10+
}
11+
12+
export function QuestionCard(props: Props) {
13+
let choices = props.choices.map((choice, i) => (
14+
<ChoiceButton
15+
key={choice.score + '_' + i}
16+
choice={choice}
17+
onClick={props.onAnswer}
18+
/>
19+
));
20+
return (
21+
<div className='card w-2/3 mx-auto mb-8 rounded-r-xl sm:rounded-xl overflow-hidden flex p-8'>
22+
<div className='grid grid-cols-4'>
23+
<div className='col-span-1 p-5 text-4xl'>
24+
# {props.number}
25+
{props.totalNumber ? <sup className="text-sm"> of {props.totalNumber}</sup> : ''}
26+
</div>
27+
<div className='col-span-3 p-5 pb-10 text-2xl'>
28+
<div className='card-body'>{props.question.text}</div>
29+
</div>
30+
</div>
31+
<div className='justify-end card-footer'>{choices}</div>
32+
</div>
33+
);
34+
}

0 commit comments

Comments
 (0)