mirror of
https://github.com/randyjc/Rahoot.git
synced 2026-03-13 20:15:35 +01:00
First Commit
This commit is contained in:
245
.eslintrc.json
Normal file
245
.eslintrc.json
Normal file
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"prettier",
|
||||
"next/core-web-vitals",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"plugins": ["react", "react-hooks"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"array-callback-return": [
|
||||
"error",
|
||||
{
|
||||
"allowImplicit": false,
|
||||
"checkForEach": true,
|
||||
"allowVoid": true
|
||||
}
|
||||
],
|
||||
"no-await-in-loop": "error",
|
||||
"no-constant-binary-expression": "error",
|
||||
"no-constructor-return": "error",
|
||||
"no-duplicate-imports": ["error", { "includeExports": true }],
|
||||
"no-new-native-nonconstructor": "error",
|
||||
"no-promise-executor-return": ["error", { "allowVoid": true }],
|
||||
"no-self-compare": "error",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unreachable-loop": "error",
|
||||
"no-unused-private-class-members": "error",
|
||||
"arrow-body-style": ["error", "as-needed"],
|
||||
"camelcase": [
|
||||
"error",
|
||||
{
|
||||
"properties": "always",
|
||||
"ignoreDestructuring": true,
|
||||
"ignoreImports": true,
|
||||
"ignoreGlobals": true
|
||||
}
|
||||
],
|
||||
"capitalized-comments": [
|
||||
"error",
|
||||
"always",
|
||||
{ "ignoreConsecutiveComments": true }
|
||||
],
|
||||
"class-methods-use-this": ["error", { "enforceForClassFields": true }],
|
||||
"complexity": ["warn", 40],
|
||||
"consistent-return": "error",
|
||||
"curly": ["error", "all"],
|
||||
"default-param-last": "error",
|
||||
"dot-notation": "error",
|
||||
"eqeqeq": ["error", "always"],
|
||||
"func-name-matching": "error",
|
||||
"func-names": "error",
|
||||
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
|
||||
"grouped-accessor-pairs": ["error", "getBeforeSet"],
|
||||
"guard-for-in": "error",
|
||||
"init-declarations": ["error", "always"],
|
||||
"logical-assignment-operators": [
|
||||
"error",
|
||||
"always",
|
||||
{ "enforceForIfStatements": true }
|
||||
],
|
||||
"max-classes-per-file": ["error", { "ignoreExpressions": true }],
|
||||
"max-depth": ["error", 3],
|
||||
"max-lines": [
|
||||
"error",
|
||||
{ "max": 150, "skipBlankLines": true, "skipComments": true }
|
||||
],
|
||||
"max-lines-per-function": [
|
||||
"warn",
|
||||
{ "max": 50, "skipBlankLines": true, "skipComments": true }
|
||||
],
|
||||
"max-nested-callbacks": ["error", 3],
|
||||
"max-params": ["error", 3],
|
||||
"multiline-comment-style": ["error", "separate-lines"],
|
||||
"new-cap": "error",
|
||||
"no-alert": "error",
|
||||
"no-bitwise": "error",
|
||||
"no-caller": "error",
|
||||
"no-console": "error",
|
||||
"no-else-return": "error",
|
||||
"no-empty-function": "error",
|
||||
"no-empty-static-block": "error",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-label": "error",
|
||||
"no-implicit-coercion": "error",
|
||||
"no-implicit-globals": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-inline-comments": "error",
|
||||
"no-invalid-this": "error",
|
||||
"no-iterator": "error",
|
||||
"no-labels": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-multi-assign": "error",
|
||||
"no-multi-str": "error",
|
||||
"no-nested-ternary": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-object-constructor": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": "error",
|
||||
"no-plusplus": "error",
|
||||
"no-proto": "error",
|
||||
"no-return-assign": ["error", "always"],
|
||||
"no-script-url": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-undef-init": "error",
|
||||
"no-undefined": "error",
|
||||
"no-underscore-dangle": ["error", { "allowFunctionParams": false }],
|
||||
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
||||
"no-unused-expressions": ["error", { "enforceForJSX": true }],
|
||||
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
||||
"no-useless-call": "error",
|
||||
"no-useless-computed-key": ["error", { "enforceForClassMembers": true }],
|
||||
"no-useless-concat": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-useless-rename": "error",
|
||||
"no-useless-return": "error",
|
||||
"no-var": "error",
|
||||
"no-warning-comments": [
|
||||
"error",
|
||||
{
|
||||
"terms": ["todo"]
|
||||
}
|
||||
],
|
||||
"object-shorthand": ["error", "always"],
|
||||
"one-var": ["error", "never"],
|
||||
"operator-assignment": ["error", "always"],
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": [
|
||||
"error",
|
||||
{
|
||||
"destructuring": "any",
|
||||
"ignoreReadBeforeAssign": false
|
||||
}
|
||||
],
|
||||
"prefer-destructuring": "error",
|
||||
"prefer-exponentiation-operator": "error",
|
||||
"prefer-numeric-literals": "error",
|
||||
"prefer-object-has-own": "error",
|
||||
"prefer-object-spread": "error",
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"prefer-regex-literals": ["error", { "disallowRedundantWrapping": true }],
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "error",
|
||||
"radix": "error",
|
||||
"require-await": "error",
|
||||
"require-unicode-regexp": "error",
|
||||
"symbol-description": "error",
|
||||
"yoda": "error",
|
||||
"line-comment-position": ["error", { "position": "above" }],
|
||||
// recommended rules overwrite
|
||||
"indent": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"newline-before-return": "error",
|
||||
"no-undef": "error",
|
||||
"padded-blocks": ["error", "never"],
|
||||
"padding-line-between-statements": [
|
||||
"error",
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": "*",
|
||||
"next": [
|
||||
"break",
|
||||
"case",
|
||||
"cjs-export",
|
||||
"class",
|
||||
"continue",
|
||||
"do",
|
||||
"if",
|
||||
"switch",
|
||||
"try",
|
||||
"while",
|
||||
"return"
|
||||
]
|
||||
},
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": [
|
||||
"break",
|
||||
"case",
|
||||
"cjs-export",
|
||||
"class",
|
||||
"continue",
|
||||
"do",
|
||||
"if",
|
||||
"switch",
|
||||
"try",
|
||||
"while",
|
||||
"return"
|
||||
],
|
||||
"next": "*"
|
||||
},
|
||||
{
|
||||
"blankLine": "never",
|
||||
"prev": ["const", "export"],
|
||||
"next": "const"
|
||||
}
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"double",
|
||||
{
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
],
|
||||
"space-before-blocks": "error",
|
||||
"semi": ["error", "never"],
|
||||
// extra
|
||||
"@next/next/no-html-link-for-pages": ["error", "./src/app"],
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"react/no-unescaped-entities": [
|
||||
"error",
|
||||
{
|
||||
"forbid": [">", "}"]
|
||||
}
|
||||
],
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off"
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
||||
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/socket/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"semi": false
|
||||
}
|
||||
7
jsconfig.json
Normal file
7
jsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
14
next.config.mjs
Normal file
14
next.config.mjs
Normal file
@@ -0,0 +1,14 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
/*async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: "/ws/",
|
||||
destination: `http://localhost:5057/`,
|
||||
},
|
||||
]
|
||||
},*/
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
4920
package-lock.json
generated
Normal file
4920
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
package.json
Normal file
26
package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "rahoot",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.0",
|
||||
"next": "14.1.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"socket.io": "^4.7.4",
|
||||
"socket.io-client": "^4.7.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.0.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.1.0",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.3.0"
|
||||
}
|
||||
}
|
||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
1
public/next.svg
Normal file
1
public/next.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
public/vercel.svg
Normal file
1
public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||
|
After Width: | Height: | Size: 629 B |
234
socket/package-lock.json
generated
Normal file
234
socket/package-lock.json
generated
Normal file
@@ -0,0 +1,234 @@
|
||||
{
|
||||
"name": "socket",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"socket.io": "^4.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||
},
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
||||
"integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.13.tgz",
|
||||
"integrity": "sha512-5G4zQwdiQBSWYTDAH1ctw2eidqdhMJaNsiIDKHFr55ihz5Trl2qqR8fdrT732yPBho5gkNxXm67OxWFBqX9aPg==",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/base64id": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
||||
"engines": {
|
||||
"node": "^4.5.0 || >= 5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io": {
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz",
|
||||
"integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/node": ">=10.0.0",
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "~0.4.1",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
|
||||
"integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.4.tgz",
|
||||
"integrity": "sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.5.2",
|
||||
"socket.io-adapter": "~2.5.2",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-adapter": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
|
||||
"integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
|
||||
"dependencies": {
|
||||
"ws": "~8.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
|
||||
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
socket/package.json
Normal file
8
socket/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"socket.io": "^4.7.4"
|
||||
}
|
||||
}
|
||||
52
socket/src/index.js
Normal file
52
socket/src/index.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Server } from "socket.io"
|
||||
import { GAME_STATE_INIT } from "./quizz.config.js"
|
||||
import Manager from "./roles/manager.js"
|
||||
import Player from "./roles/player.js"
|
||||
|
||||
let gameState = GAME_STATE_INIT
|
||||
|
||||
const io = new Server({
|
||||
cors: {
|
||||
origin: "*",
|
||||
},
|
||||
path: "/ws/",
|
||||
})
|
||||
|
||||
io.listen(5057)
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log(`A user connected ${socket.id}`)
|
||||
|
||||
socket.on("player:join", (player) =>
|
||||
Player.join(gameState, io, socket, player)
|
||||
)
|
||||
|
||||
socket.on("manager:createRoom", () =>
|
||||
Manager.createRoom(gameState, io, socket)
|
||||
)
|
||||
socket.on("manager:kickPlayer", (playerId) =>
|
||||
Manager.kickPlayer(gameState, socket, io, playerId)
|
||||
)
|
||||
|
||||
socket.on("manager:startGame", () => Manager.startGame(gameState, io, socket))
|
||||
|
||||
socket.on("player:selectedAnswer", (answerKey) =>
|
||||
Player.selectedAnswer(gameState, io, socket, answerKey)
|
||||
)
|
||||
|
||||
socket.on("manager:nextQuestion", () =>
|
||||
Manager.nextQuestion(gameState, io, socket)
|
||||
)
|
||||
|
||||
socket.on("manager:showLeaderboard", () =>
|
||||
Manager.showLoaderboard(gameState, io, socket)
|
||||
)
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
console.log(`user disconnected ${socket.id}`)
|
||||
/*if (gameState.manager === socket.id) {
|
||||
console.log("Reset game")
|
||||
gameState = gameStateInit
|
||||
}*/
|
||||
})
|
||||
})
|
||||
83
socket/src/quizz.config.js
Normal file
83
socket/src/quizz.config.js
Normal file
@@ -0,0 +1,83 @@
|
||||
export const GAME_STATE_INIT = {
|
||||
started: false,
|
||||
players: [],
|
||||
playersAnswer: [],
|
||||
manager: null,
|
||||
room: null,
|
||||
currentQuestion: 0,
|
||||
roundStartTime: 0,
|
||||
questions: [
|
||||
{
|
||||
question: "Who are the founders of Adobe?",
|
||||
answers: [
|
||||
"Steve Jobs and Charles Geschke",
|
||||
"Jhon Warnock and Charles Geschke",
|
||||
"Jhon Jonse and Charles Geskie",
|
||||
"Bill Gate",
|
||||
],
|
||||
image: "/question/Adobe-Logo.png",
|
||||
solution: 1,
|
||||
time: 15,
|
||||
},
|
||||
{
|
||||
question: "What is Adobe's most famous software?",
|
||||
answers: ["Encore", "AfterEffect", "Creative Cloud", "Photoshop"],
|
||||
image: "/question/Adobe-Packages.webp",
|
||||
solution: 3,
|
||||
time: 15,
|
||||
},
|
||||
{
|
||||
question: "When was Adobe created?",
|
||||
answers: ["2000", "1982", "2003", "1987"],
|
||||
solution: 1,
|
||||
time: 15,
|
||||
},
|
||||
{
|
||||
question: "Where is headquertes located?",
|
||||
answers: [
|
||||
"San Jose, California",
|
||||
"Bookworm, Cascui",
|
||||
"DowTown, Texas",
|
||||
"Tokyo, Japan",
|
||||
],
|
||||
image: "/question/Adobe_World_Headquarters.jpg",
|
||||
solution: 0,
|
||||
time: 15,
|
||||
},
|
||||
{
|
||||
question: "How many employees at Adobe?",
|
||||
answers: [
|
||||
"15,423 employees",
|
||||
"30,803 employees",
|
||||
"25,988 employees",
|
||||
"5,073 employees",
|
||||
],
|
||||
image: "/question/000012204568_Large.jpg",
|
||||
solution: 2,
|
||||
time: 15,
|
||||
},
|
||||
{
|
||||
question: "Who is the Current CEO?",
|
||||
answers: [
|
||||
"Jhon Warnock",
|
||||
"Victor Newway",
|
||||
"Mark Java",
|
||||
"Shantanu Narayen",
|
||||
],
|
||||
image: "/question/guess-the-person.png",
|
||||
solution: 3,
|
||||
time: 15,
|
||||
},
|
||||
{
|
||||
question: "Adobe's core business is focused on?",
|
||||
answers: [
|
||||
"Creative Software",
|
||||
"Video Game",
|
||||
"Logistics software",
|
||||
"Other",
|
||||
],
|
||||
solution: 0,
|
||||
time: 15,
|
||||
},
|
||||
],
|
||||
}
|
||||
85
socket/src/roles/manager.js
Normal file
85
socket/src/roles/manager.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import { GAME_STATE_INIT } from "../quizz.config.js"
|
||||
import { startRound } from "../utils/round.js"
|
||||
import generateRoomId from "../utils/generateRoomId.js"
|
||||
|
||||
const Manager = {
|
||||
createRoom: (game, io, socket) => {
|
||||
if (game.manager || game.room) {
|
||||
io.to(socket.id).emit("message", "Already manager")
|
||||
return
|
||||
}
|
||||
|
||||
let roomInvite = "207223" //generateRoomId()
|
||||
game.room = roomInvite
|
||||
game.manager = socket.id
|
||||
|
||||
socket.join(roomInvite)
|
||||
io.to(socket.id).emit("manager:inviteCode", roomInvite)
|
||||
|
||||
console.log("New room created: " + roomInvite)
|
||||
},
|
||||
|
||||
kickPlayer: (game, io, socket, playerId) => {
|
||||
if (game.manager !== socket.id) {
|
||||
return
|
||||
}
|
||||
|
||||
const player = game.players.find((p) => p.id === playerId)
|
||||
game.players = game.players.filter((p) => p.id !== playerId)
|
||||
|
||||
io.to(player.id).emit("game:kick")
|
||||
io.to(game.manager).emit("manager:playerKicked", player.id)
|
||||
},
|
||||
|
||||
startGame: (game, io, socket) => {
|
||||
if (game.started || !game.room) {
|
||||
return
|
||||
}
|
||||
|
||||
game.started = true
|
||||
io.to(game.room).emit("startGame", game.room)
|
||||
startRound(game, io, socket)
|
||||
},
|
||||
|
||||
nextQuestion: (game, io, socket) => {
|
||||
if (!game.started) {
|
||||
return
|
||||
}
|
||||
|
||||
if (socket.id !== game.manager) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!game.questions[game.currentQuestion + 1]) {
|
||||
return
|
||||
}
|
||||
|
||||
game.currentQuestion++
|
||||
startRound(game, io, socket)
|
||||
},
|
||||
|
||||
showLoaderboard: (game, io, socket) => {
|
||||
if (!game.questions[game.currentQuestion + 1]) {
|
||||
io.to(game.room).emit("game:status", {
|
||||
name: "FINISH",
|
||||
data: {
|
||||
winners: game.players.slice(0, 3).sort((a, b) => b.points - a.points),
|
||||
},
|
||||
})
|
||||
|
||||
game = GAME_STATE_INIT
|
||||
return
|
||||
}
|
||||
|
||||
io.to(game.room).emit("game:status", {
|
||||
name: "SHOW_LEADERBOARD",
|
||||
data: {
|
||||
leaderboard: game.players
|
||||
.sort((a, b) => b.points - a.points)
|
||||
.slice(0, 5),
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export default Manager
|
||||
57
socket/src/roles/player.js
Normal file
57
socket/src/roles/player.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import convertTimeToPoint from "../utils/convertTimeToPoint.js"
|
||||
import { abortCooldown } from "../utils/round.js"
|
||||
|
||||
const Player = {
|
||||
join: (game, io, socket, player) => {
|
||||
if (!player.room || !player.room || game.started) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log("joined", player)
|
||||
|
||||
socket.join(player.room)
|
||||
|
||||
let playerData = {
|
||||
username: player.username,
|
||||
room: player.room,
|
||||
id: socket.id,
|
||||
points: 0,
|
||||
}
|
||||
socket.to(player.room).emit("manager:newPlayer", playerData)
|
||||
|
||||
game.players.push(playerData)
|
||||
|
||||
io.to(socket.id).emit("game:successJoin")
|
||||
},
|
||||
|
||||
selectedAnswer: (game, io, socket, answerKey) => {
|
||||
const player = game.players.find((player) => player.id === socket.id)
|
||||
const question = game.questions[game.currentQuestion]
|
||||
|
||||
if (!player) {
|
||||
return
|
||||
}
|
||||
|
||||
if (game.playersAnswer.find((p) => p.id === socket.id)) {
|
||||
return
|
||||
}
|
||||
|
||||
game.playersAnswer.push({
|
||||
id: socket.id,
|
||||
answer: answerKey,
|
||||
points: convertTimeToPoint(game.roundStartTime, question.time),
|
||||
})
|
||||
|
||||
io.to(socket.id).emit("game:status", {
|
||||
name: "WAIT",
|
||||
data: { text: "Waiting for the players to answer" },
|
||||
})
|
||||
socket.to(game.room).emit("game:playerAnswer", game.playersAnswer.length)
|
||||
|
||||
if (game.playersAnswer.length === game.players.length) {
|
||||
abortCooldown()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default Player
|
||||
13
socket/src/utils/convertTimeToPoint.js
Normal file
13
socket/src/utils/convertTimeToPoint.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const convertTimeToPoint = (startTime, secondes) => {
|
||||
let points = 1000
|
||||
|
||||
const actualTime = Date.now()
|
||||
const tempsPasseEnSecondes = (actualTime - startTime) / 1000
|
||||
|
||||
points -= (1000 / secondes) * tempsPasseEnSecondes
|
||||
points = Math.max(0, points)
|
||||
|
||||
return points
|
||||
}
|
||||
|
||||
export default convertTimeToPoint
|
||||
14
socket/src/utils/generateRoomId.js
Normal file
14
socket/src/utils/generateRoomId.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const generateRoomId = (length = 6) => {
|
||||
let result = ""
|
||||
const characters = "0123456789"
|
||||
const charactersLength = characters.length
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * charactersLength)
|
||||
result += characters.charAt(randomIndex)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export default generateRoomId
|
||||
109
socket/src/utils/round.js
Normal file
109
socket/src/utils/round.js
Normal file
@@ -0,0 +1,109 @@
|
||||
let cooldownTimeout
|
||||
let cooldownResolve
|
||||
|
||||
export const abortCooldown = () => {
|
||||
clearInterval(cooldownTimeout)
|
||||
cooldownResolve()
|
||||
}
|
||||
|
||||
function cooldown(ms, io, room) {
|
||||
let count = ms - 1
|
||||
|
||||
return new Promise((resolve) => {
|
||||
cooldownResolve = resolve
|
||||
|
||||
cooldownTimeout = setInterval(() => {
|
||||
if (!count) {
|
||||
clearInterval(cooldownTimeout)
|
||||
resolve()
|
||||
}
|
||||
io.to(room).emit("game:cooldown", count)
|
||||
count -= 1
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
const sleep = (sec) => new Promise((r) => setTimeout(r, sec * 1000))
|
||||
|
||||
export const startRound = async (game, io, socket) => {
|
||||
const question = game.questions[game.currentQuestion]
|
||||
|
||||
io.to(game.room).emit("game:status", {
|
||||
name: "SHOW_QUESTION",
|
||||
data: {
|
||||
question: question.question,
|
||||
number: game.currentQuestion + 1,
|
||||
image: question.image,
|
||||
cooldown: 6,
|
||||
},
|
||||
})
|
||||
|
||||
await sleep(6)
|
||||
|
||||
game.roundStartTime = Date.now()
|
||||
|
||||
io.to(game.room).emit("game:status", {
|
||||
name: "SELECT_ANSWER",
|
||||
data: {
|
||||
question: question.question,
|
||||
answers: question.answers,
|
||||
image: question.image,
|
||||
time: question.time,
|
||||
totalPlayer: game.players.length,
|
||||
},
|
||||
})
|
||||
|
||||
await cooldown(question.time, io, game.room)
|
||||
|
||||
game.players.map(async (player) => {
|
||||
let playerAnswer = await game.playersAnswer.find((p) => p.id === player.id)
|
||||
|
||||
let isCorrect = playerAnswer
|
||||
? playerAnswer.answer === question.solution
|
||||
: false
|
||||
|
||||
let points =
|
||||
(isCorrect && Math.round(playerAnswer && playerAnswer.points)) || 0
|
||||
|
||||
game.players.find((p) => p.id === player.id).points += points
|
||||
|
||||
setTimeout(() => {
|
||||
let rank =
|
||||
game.players
|
||||
.sort((a, b) => b.points - a.points)
|
||||
.findIndex((p) => p.id === player.id) + 1
|
||||
|
||||
io.to(player.id).emit("game:status", {
|
||||
name: "SHOW_RESULT",
|
||||
data: {
|
||||
correct: isCorrect,
|
||||
message: isCorrect ? "Nice !" : "Too bad",
|
||||
points: points,
|
||||
myPoints: player.points,
|
||||
totalPlayer: game.players.length,
|
||||
rank: rank,
|
||||
},
|
||||
})
|
||||
}, 200)
|
||||
})
|
||||
|
||||
let totalParType = {}
|
||||
|
||||
game.playersAnswer.forEach(({ answer }) => {
|
||||
totalParType[answer] = (totalParType[answer] || 0) + 1
|
||||
})
|
||||
|
||||
// Manager
|
||||
io.to(game.manager).emit("game:status", {
|
||||
name: "SHOW_RESPONSES",
|
||||
data: {
|
||||
question: game.questions[game.currentQuestion].question,
|
||||
responses: totalParType,
|
||||
correct: game.questions[game.currentQuestion].solution,
|
||||
answers: game.questions[game.currentQuestion].answers,
|
||||
image: game.questions[game.currentQuestion].image,
|
||||
},
|
||||
})
|
||||
|
||||
game.playersAnswer = []
|
||||
}
|
||||
14
socket/web.js
Normal file
14
socket/web.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Server } from "socket.io"
|
||||
|
||||
const io = new Server({
|
||||
cors: {
|
||||
origin: "*",
|
||||
},
|
||||
})
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log("a user connected")
|
||||
io.to(socket.id).emit("message", "Hello from server")
|
||||
})
|
||||
|
||||
io.listen(5057)
|
||||
BIN
src/assets/2238431_1694.jpg
Normal file
BIN
src/assets/2238431_1694.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
6
src/assets/loader.svg
Normal file
6
src/assets/loader.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin:auto;display:block;" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<circle cx="50" cy="50" r="34" stroke="#fff4e4" stroke-width="13" stroke-linecap="square" fill="none">
|
||||
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;180 50 50;720 50 50" keyTimes="0;0.5;1"></animateTransform>
|
||||
<animate attributeName="stroke-dasharray" repeatCount="indefinite" dur="1s" values="21.362830044410593 192.26547039969535;106.81415022205297 106.81415022205297;21.362830044410593 192.26547039969535" keyTimes="0;0.5;1"></animate>
|
||||
</circle>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 704 B |
13
src/assets/logo.svg
Normal file
13
src/assets/logo.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:svgjs="http://svgjs.com/svgjs"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="10.34000015258789 -73.18000030517578 461.989990234375 77.19000244140625"
|
||||
>
|
||||
<g fill="#ff9900">
|
||||
<path d="M32.84 -26.89L61.68 4.01L69.05 -0.13L51.33 -33.88C61.5 -39.74 66.59 -47.11 66.59 -55.99C66.59 -59.18 65.99 -62.67 64.78 -66.46C59.35 -69.82 52.11 -71.5 43.06 -71.5C37.37 -71.5 31.42 -71.01 25.21 -70.02C19.01 -69.02 14.05 -67.93 10.34 -66.72L16.42 1.42L27.02 0.39L29.87 -26.25C30.64 -26.25 31.64 -26.46 32.84 -26.89ZM46.16 -53.01C46.16 -49.31 44.8 -45.6 42.09 -41.89C39.37 -38.19 35.56 -35.21 30.64 -32.97L33.88 -63.49C35.95 -64.26 37.84 -64.65 39.57 -64.65C43.96 -61.89 46.16 -58.01 46.16 -53.01Z M90.69 0L101.16 1.55L104.78 -13.71L132.97 -16.03L140.34 1.55L149.78 0L136.08 -68.92L110.47 -70.47ZM115.13 -57.15L115.9 -57.15L130.65 -21.59L106.72 -21.85Z M222.63 1.68L233.23 0.65L240.73 -70.21L216.68 -68.66L219.78 -32.2L198.84 -32.33L202.85 -70.47L178.67 -68.92L184.74 1.42L195.35 0.39L197.93 -24.18L220.3 -26.12Z M320.04 -21.98C320.04 -34.14 317.31 -44.87 311.83 -54.18C306.36 -63.49 299.49 -69.82 291.21 -73.18C285.35 -69.22 280.22 -62.62 275.82 -53.4C271.43 -44.18 269.23 -33.7 269.23 -21.98C269.23 -16.21 269.83 -10.34 271.04 -4.4C276.64 0.26 284.51 2.56 294.64 2.52C304.77 2.48 311.6 0.43 315.13 -3.62C316.94 -5.86 318.21 -8.4 318.95 -11.25C319.68 -14.09 320.04 -17.67 320.04 -21.98ZM307.11 -26.64C307.11 -21.12 306.51 -15.6 305.3 -10.09C300.56 -8.53 295.76 -7.76 290.89 -7.76C286.02 -7.76 282.37 -8.02 279.96 -8.53C279.96 -16.46 281.9 -24.55 285.78 -32.78C289.66 -41.01 295 -48.27 301.81 -54.56C305.35 -46.81 307.11 -37.5 307.11 -26.64Z M395.61 -21.98C395.61 -34.14 392.87 -44.87 387.4 -54.18C381.92 -63.49 375.05 -69.82 366.77 -73.18C360.91 -69.22 355.78 -62.62 351.39 -53.4C346.99 -44.18 344.79 -33.7 344.79 -21.98C344.79 -16.21 345.4 -10.34 346.6 -4.4C352.21 0.26 360.07 2.56 370.2 2.52C380.33 2.48 387.16 0.43 390.69 -3.62C392.5 -5.86 393.78 -8.4 394.51 -11.25C395.24 -14.09 395.61 -17.67 395.61 -21.98ZM382.68 -26.64C382.68 -21.12 382.07 -15.6 380.87 -10.09C376.13 -8.53 371.32 -7.76 366.45 -7.76C361.58 -7.76 357.94 -8.02 355.52 -8.53C355.52 -16.46 357.46 -24.55 361.34 -32.78C365.22 -41.01 370.57 -48.27 377.38 -54.56C380.91 -46.81 382.68 -37.5 382.68 -26.64Z M418.29 -69.05L418.93 -58.06L433.67 -59.48L439.1 1.42L450.61 0.39L457.21 -61.81L472.08 -63.36L472.33 -70.47Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
22
src/components/AnswerButton.jsx
Normal file
22
src/components/AnswerButton.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import clsx from "clsx"
|
||||
import Triangle from "./icons/Triangle"
|
||||
|
||||
export default function AnswerButton({
|
||||
className,
|
||||
icon: Icon,
|
||||
children,
|
||||
...otherProps
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
"text-left px-4 py-6 rounded shadow-inset flex items-center gap-3",
|
||||
className
|
||||
)}
|
||||
{...otherProps}
|
||||
>
|
||||
<Icon className="h-6 w-6" />
|
||||
<span className="drop-shadow-md">{children}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
15
src/components/Button.jsx
Normal file
15
src/components/Button.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import clsx from "clsx"
|
||||
|
||||
export default function Button({ children, className, ...otherProps }) {
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
"p-2 bg-primary rounded-md text-white font-semibold text-lg btn-shadow",
|
||||
className
|
||||
)}
|
||||
{...otherProps}
|
||||
>
|
||||
<span>{children}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
7
src/components/Form.jsx
Normal file
7
src/components/Form.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function Form({ children }) {
|
||||
return (
|
||||
<div className="bg-white rounded-md shadow-sm p-4 z-10 flex-col flex gap-4 w-full max-w-80">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
13
src/components/Input.jsx
Normal file
13
src/components/Input.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import clsx from "clsx"
|
||||
|
||||
export default function Input({ className, ...otherProps }) {
|
||||
return (
|
||||
<input
|
||||
className={clsx(
|
||||
"outline outline-gray-300 outline-2 rounded-sm p-2 font-semibold text-lg",
|
||||
className
|
||||
)}
|
||||
{...otherProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
6
src/components/Loader.jsx
Normal file
6
src/components/Loader.jsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import loader from "@/assets/loader.svg"
|
||||
import Image from "next/image"
|
||||
|
||||
export default function Loader({ ...otherProps }) {
|
||||
return <Image src={loader} />
|
||||
}
|
||||
35
src/components/game/GameWrapper.jsx
Normal file
35
src/components/game/GameWrapper.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import Image from "next/image"
|
||||
import Button from "@/components/Button"
|
||||
import background from "@/assets/2238431_1694.jpg"
|
||||
import { usePlayerContext } from "@/context/player"
|
||||
|
||||
export default function GameWrapper({ children }) {
|
||||
const { player } = usePlayerContext()
|
||||
|
||||
return (
|
||||
<section className="relative flex justify-between flex-col w-full min-h-screen">
|
||||
<div className="fixed h-full w-full top-0 left-0 bg-orange-600 opacity-70 -z-10">
|
||||
<Image
|
||||
className="object-cover h-full w-full opacity-60 pointer-events-none"
|
||||
src={background}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-4 w-full flex justify-between">
|
||||
<div className="bg-white shadow-inset text-black px-4 font-bold rounded-md flex items-center text-lg">
|
||||
1/10
|
||||
</div>
|
||||
<Button className="bg-white !text-black px-4">Skip</Button>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
|
||||
<div className="bg-white py-2 px-4 flex items-center text-lg justify-between font-bold text-white">
|
||||
<p className="text-gray-800">{!!player && player.username}</p>
|
||||
<div className="bg-gray-800 rounded-sm py-1 px-3 text-lg">
|
||||
{!!player && player.points}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
33
src/components/game/join/Room.jsx
Normal file
33
src/components/game/join/Room.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { usePlayerContext } from "@/context/player"
|
||||
import Form from "@/components/Form"
|
||||
import Button from "@/components/Button"
|
||||
import Input from "@/components/Input"
|
||||
import { useState } from "react"
|
||||
|
||||
export default function Room() {
|
||||
const { player, dispatch } = usePlayerContext()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [roomId, setRoomId] = useState("")
|
||||
|
||||
const handleLogin = () => {
|
||||
dispatch({ type: "JOIN", payload: roomId })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && (
|
||||
<div className="absolute w-full h-full z-30 bg-black/40 flex justify-center items-center">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Form>
|
||||
<Input
|
||||
onChange={(e) => setRoomId(e.target.value)}
|
||||
placeholder="PIN Code here"
|
||||
/>
|
||||
<Button onClick={() => handleLogin()}>Submit</Button>
|
||||
</Form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
42
src/components/game/join/Username.jsx
Normal file
42
src/components/game/join/Username.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { usePlayerContext } from "@/context/player"
|
||||
import Form from "@/components/Form"
|
||||
import Button from "@/components/Button"
|
||||
import Input from "@/components/Input"
|
||||
import { useState } from "react"
|
||||
import { useSocketContext } from "@/context/socket"
|
||||
import { useRouter } from "next/router"
|
||||
|
||||
export default function Username() {
|
||||
const { socket } = useSocketContext()
|
||||
const { player, dispatch } = usePlayerContext()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const router = useRouter()
|
||||
const [username, setUsername] = useState("")
|
||||
|
||||
const handleJoin = () => {
|
||||
dispatch({
|
||||
type: "LOGIN",
|
||||
payload: username,
|
||||
})
|
||||
|
||||
router.push("/game")
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && (
|
||||
<div className="absolute w-full h-full z-30 bg-black/40 flex justify-center items-center">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Form>
|
||||
<Input
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="Usernname here"
|
||||
/>
|
||||
<Button onClick={() => handleJoin()}>Submit</Button>
|
||||
</Form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
76
src/components/game/states/Answers.jsx
Normal file
76
src/components/game/states/Answers.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import Circle from "@/components/icons/Circle"
|
||||
import Triangle from "@/components/icons/Triangle"
|
||||
import Square from "@/components/icons/Square"
|
||||
import Rhombus from "@/components/icons/Rhombus"
|
||||
import AnswerButton from "../../AnswerButton"
|
||||
import { useSocketContext } from "@/context/socket"
|
||||
import Image from "next/image"
|
||||
import { useState } from "react"
|
||||
|
||||
const answersColors = [
|
||||
"bg-red-500",
|
||||
"bg-blue-500",
|
||||
"bg-yellow-500",
|
||||
"bg-green-500",
|
||||
]
|
||||
|
||||
const answersIcons = [Triangle, Rhombus, Circle, Square]
|
||||
|
||||
export default function Answers({ data: { question, answers, image, time } }) {
|
||||
const { socket } = useSocketContext()
|
||||
|
||||
const [cooldown, setCooldown] = useState(time)
|
||||
const [totalAnswer, setTotalAnswer] = useState(0)
|
||||
|
||||
socket.on("game:cooldown", (sec) => {
|
||||
setCooldown(sec)
|
||||
})
|
||||
|
||||
socket.on("game:playerAnswer", (count) => {
|
||||
setTotalAnswer(count)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col justify-between flex-1">
|
||||
<div className="h-full max-w-7xl mx-auto w-full inline-flex flex-col items-center justify-center gap-5 flex-1">
|
||||
<h2 className="text-white text-2xl md:text-4xl font-bold drop-shadow-lg text-center">
|
||||
{question}
|
||||
</h2>
|
||||
|
||||
{/*<Image src={image} className="h-60 w-auto rounded-md" />*/}
|
||||
</div>
|
||||
|
||||
<div className="">
|
||||
<div className="max-w-7xl mx-auto mb-4 rounded-full w-full flex justify-between gap-1 font-bold text-white text-lg md:text-xl px-2">
|
||||
<div className="bg-white shadow-inset text-black px-4 font-bold rounded-md flex flex-col items-center text-lg">
|
||||
<span className="text-sm">Time</span>
|
||||
<span className="-translate-y-1">{cooldown}</span>
|
||||
</div>
|
||||
<div className="bg-white shadow-inset text-black px-4 font-bold rounded-md flex flex-col items-center text-lg">
|
||||
<span className="text-sm">Answers</span>
|
||||
<span className="-translate-y-1">{totalAnswer}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto mb-4 rounded-full w-full grid grid-cols-2 gap-1 font-bold text-white text-lg md:text-xl px-2">
|
||||
{answers.map((answer, key) => (
|
||||
<AnswerButton
|
||||
key={key}
|
||||
className={answersColors[key]}
|
||||
icon={answersIcons[key]}
|
||||
onClick={() => socket.emit("player:selectedAnswer", key)}
|
||||
>
|
||||
{answer}
|
||||
</AnswerButton>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/* OLD Timer
|
||||
<div className="absolute left-8 -translate-y-1/2 top-2/4 text-white font-bold text-6xl rounded-full justify-center items-center bg-orange-400 p-8 aspect-square hidden 2xl:flex">
|
||||
<span </div>className="drop-shadow-md">20</span>
|
||||
</div>
|
||||
*/
|
||||
25
src/components/game/states/Leaderboard.jsx
Normal file
25
src/components/game/states/Leaderboard.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
export default function Leaderboard({ data }) {
|
||||
return (
|
||||
<section className="max-w-7xl mx-auto w-full flex-1 flex-col relative items-center justify-center flex">
|
||||
<h2 className="text-white drop-shadow-md text-5xl font-bold mb-6">
|
||||
Leaderboard
|
||||
</h2>
|
||||
<div className="w-full flex-col flex gap-2">
|
||||
<div className=" bg-orange-500 rounded-md p-3 flex justify-between w-full text-white font-bold text-2xl">
|
||||
<span>Username</span>
|
||||
<span>10000</span>
|
||||
</div>
|
||||
|
||||
<div className="bg-orange-500 rounded-md p-3 flex justify-between w-full text-white font-bold text-2xl">
|
||||
<span>Username</span>
|
||||
<span>10000</span>
|
||||
</div>
|
||||
|
||||
<div className="bg-orange-500 rounded-md p-3 flex justify-between w-full text-white font-bold text-2xl">
|
||||
<span>Username</span>
|
||||
<span>10000</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
27
src/components/game/states/Question.jsx
Normal file
27
src/components/game/states/Question.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useSocketContext } from "@/context/socket"
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
export default function Question({ data: { question } }) {
|
||||
const barRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
barRef.current.style.width = "100%"
|
||||
}, 1)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="max-w-7xl mx-auto w-full flex-1 relative items-center flex flex-col px-4 h-full">
|
||||
<div className="flex items-center flex-1">
|
||||
<h2 className="text-white text-5xl font-bold drop-shadow-lg text-center anim-show w-full justify-self-center">
|
||||
{question}
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
ref={barRef}
|
||||
className="h-6 bg-primary mb-32 rounded-full w-0 self-start justify-self-end"
|
||||
style={{ transition: `width ${6}s linear` }}
|
||||
></div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
35
src/components/game/states/Result.jsx
Normal file
35
src/components/game/states/Result.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import CricleCheck from "@/components/icons/CricleCheck"
|
||||
import CricleXmark from "@/components/icons/CricleXmark"
|
||||
import { usePlayerContext } from "@/context/player"
|
||||
import { useEffect } from "react"
|
||||
|
||||
export default function Result({
|
||||
data: { correct, message, points, myPoints, totalPlayer, rank },
|
||||
}) {
|
||||
const { dispatch } = usePlayerContext()
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: "UPDATE",
|
||||
payload: { points: myPoints },
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="max-w-7xl mx-auto w-full flex-1 relative items-center justify-center flex flex-col">
|
||||
{correct ? (
|
||||
<CricleCheck className="max-h-60 aspect-square w-full" />
|
||||
) : (
|
||||
<CricleXmark className=" max-h-60 aspect-square w-full" />
|
||||
)}
|
||||
<h2 className="text-white font-bold text-4xl mt-1 drop-shadow-lg">
|
||||
{message}
|
||||
</h2>
|
||||
{correct && (
|
||||
<span className="py-2 px-4 text-white font-bold text-2xl drop-shadow-lg bg-black/40 rounded mt-2">
|
||||
+{points}
|
||||
</span>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
12
src/components/game/states/Wait.jsx
Normal file
12
src/components/game/states/Wait.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import Loader from "@/components/Loader"
|
||||
|
||||
export default function Wait({ data: { text } }) {
|
||||
return (
|
||||
<section className="max-w-7xl mx-auto w-full flex-1 relative items-center justify-center flex flex-col">
|
||||
<Loader />
|
||||
<h2 className="text-white font-bold text-4xl mt-5 drop-shadow-lg">
|
||||
{text}
|
||||
</h2>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
25
src/components/icons/Circle.jsx
Normal file
25
src/components/icons/Circle.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
export default function Circle({ className, fill = "#FFF" }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
id="Page-1"
|
||||
stroke="none"
|
||||
strokeWidth="1"
|
||||
fill="none"
|
||||
fillRule="evenodd"
|
||||
>
|
||||
<g id="icon" fill={fill} transform="translate(42.666667, 42.666667)">
|
||||
<path
|
||||
d="M213.333333,3.55271368e-14 C331.15408,3.55271368e-14 426.666667,95.5125867 426.666667,213.333333 C426.666667,331.15408 331.15408,426.666667 213.333333,426.666667 C95.5125867,426.666667 3.55271368e-14,331.15408 3.55271368e-14,213.333333 C3.55271368e-14,95.5125867 95.5125867,3.55271368e-14 213.333333,3.55271368e-14 Z"
|
||||
id="Combined-Shape"
|
||||
></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
37
src/components/icons/CricleCheck.jsx
Normal file
37
src/components/icons/CricleCheck.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
export default function CricleCheck({ className }) {
|
||||
return (
|
||||
<svg
|
||||
fill="#28d524"
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 0 56.00 56.00"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
stroke="#28d524"
|
||||
strokeWidth="0.00056"
|
||||
className={className}
|
||||
>
|
||||
<g strokeWidth="0" transform="translate(11.2,11.2), scale(0.6)">
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="56.00"
|
||||
height="56.00"
|
||||
rx="28"
|
||||
fill="#ffffff"
|
||||
strokewidth="0"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
stroke="#CCCCCC"
|
||||
strokeWidth="0.6719999999999999"
|
||||
/>
|
||||
|
||||
<g>
|
||||
<path d="M 27.9999 51.9063 C 41.0546 51.9063 51.9063 41.0781 51.9063 28 C 51.9063 14.9453 41.0312 4.0937 27.9765 4.0937 C 14.8983 4.0937 4.0937 14.9453 4.0937 28 C 4.0937 41.0781 14.9218 51.9063 27.9999 51.9063 Z M 24.7655 40.0234 C 23.9687 40.0234 23.3593 39.6719 22.6796 38.8750 L 15.9296 30.5312 C 15.5780 30.0859 15.3671 29.5234 15.3671 29.0078 C 15.3671 27.9063 16.2343 27.0625 17.2655 27.0625 C 17.9452 27.0625 18.5077 27.3203 19.0702 28.0469 L 24.6718 35.2890 L 35.5702 17.8281 C 36.0155 17.1016 36.6249 16.75 37.2343 16.75 C 38.2655 16.75 39.2733 17.4297 39.2733 18.5547 C 39.2733 19.0703 38.9687 19.6328 38.6640 20.1016 L 26.7577 38.8750 C 26.2421 39.6484 25.5858 40.0234 24.7655 40.0234 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
31
src/components/icons/CricleXmark.jsx
Normal file
31
src/components/icons/CricleXmark.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
export default function CricleXmark({ className }) {
|
||||
return (
|
||||
<svg
|
||||
fill="#fb1e1e"
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 0 56.00 56.00"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
stroke="#fb1e1e"
|
||||
className={className}
|
||||
>
|
||||
<g strokeWidth="0" transform="translate(12.4,12.4), scale(0.6)">
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="56.00"
|
||||
height="56.00"
|
||||
rx="28"
|
||||
fill="#ffffff"
|
||||
strokewidth="0"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g strokeLinecap="round" strokeLinejoin="round" />
|
||||
|
||||
<g>
|
||||
<path d="M 27.9999 51.9063 C 41.0546 51.9063 51.9063 41.0781 51.9063 28 C 51.9063 14.9453 41.0312 4.0937 27.9765 4.0937 C 14.8983 4.0937 4.0937 14.9453 4.0937 28 C 4.0937 41.0781 14.9218 51.9063 27.9999 51.9063 Z M 19.5858 38.4063 C 18.4843 38.4063 17.5936 37.5156 17.5936 36.4141 C 17.5936 35.8750 17.8280 35.4063 18.2030 35.0547 L 25.1874 28.0234 L 18.2030 20.9922 C 17.8280 20.6641 17.5936 20.1719 17.5936 19.6328 C 17.5936 18.5547 18.4843 17.6875 19.5858 17.6875 C 20.1249 17.6875 20.5936 17.8984 20.9452 18.2734 L 27.9765 25.2812 L 35.0546 18.25 C 35.4530 17.8281 35.8749 17.6406 36.3905 17.6406 C 37.4921 17.6406 38.3827 18.5312 38.3827 19.6094 C 38.3827 20.1484 38.1952 20.5937 37.7968 20.9688 L 30.7655 28.0234 L 37.7733 35.0078 C 38.1249 35.3828 38.3593 35.8516 38.3593 36.4141 C 38.3593 37.5156 37.4687 38.4063 36.3671 38.4063 C 35.8046 38.4063 35.3358 38.1719 34.9843 37.8203 L 27.9765 30.7890 L 20.9921 37.8203 C 20.6405 38.1953 20.1249 38.4063 19.5858 38.4063 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
19
src/components/icons/Rhombus.jsx
Normal file
19
src/components/icons/Rhombus.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
export default function Rhombus({ className, fill = "#FFF" }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
fill={fill}
|
||||
viewBox="-56.32 -56.32 624.64 624.64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
transform="rotate(45)"
|
||||
>
|
||||
<g strokeWidth="0" />
|
||||
|
||||
<g strokeLinecap="round" strokeLinejoin="round" />
|
||||
|
||||
<g>
|
||||
<rect x="48" y="48" width="416" height="416" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
12
src/components/icons/Square.jsx
Normal file
12
src/components/icons/Square.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export default function Square({ className, fill = "#FFF" }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
fill={fill}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="48" y="48" width="416" height="416" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
12
src/components/icons/Triangle.jsx
Normal file
12
src/components/icons/Triangle.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export default function Triangle({ className, fill = "#FFF" }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
fill={fill}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polygon points="256 32 20 464 492 464 256 32" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
1
src/constants.js
Normal file
1
src/constants.js
Normal file
@@ -0,0 +1 @@
|
||||
export const WEBSOCKET_URL = "http://localhost:3000" ///"https://rahoot.ralex.app"
|
||||
51
src/context/player.jsx
Normal file
51
src/context/player.jsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { createContext, useContext, useReducer } from "react"
|
||||
|
||||
export const PlayerContext = createContext()
|
||||
|
||||
export function playerReducer(state, action) {
|
||||
switch (action.type) {
|
||||
case "JOIN":
|
||||
return { player: { ...state.player, room: action.payload } }
|
||||
case "LOGIN":
|
||||
return {
|
||||
player: {
|
||||
...state.player,
|
||||
username: action.payload,
|
||||
points: 0,
|
||||
},
|
||||
}
|
||||
case "UPDATE":
|
||||
return { player: { ...state.player, ...action.payload } }
|
||||
case "LOGOUT":
|
||||
return { player: null }
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export const PlayerContextProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(playerReducer, {
|
||||
player: null,
|
||||
})
|
||||
|
||||
return (
|
||||
<PlayerContext.Provider
|
||||
value={{
|
||||
...state,
|
||||
dispatch,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</PlayerContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function usePlayerContext() {
|
||||
const context = useContext(PlayerContext)
|
||||
|
||||
if (!context) {
|
||||
throw Error("usePlayerContext must be used inside an PlayerContextProvider")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
24
src/context/socket.jsx
Normal file
24
src/context/socket.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { io } from "socket.io-client"
|
||||
import { createContext, useContext, useState } from "react"
|
||||
import { WEBSOCKET_URL } from "@/constants"
|
||||
|
||||
export const socket = io("http://localhost:5057", {
|
||||
path: "/ws/",
|
||||
//addTrailingSlash: false,
|
||||
transports: ["websocket"],
|
||||
})
|
||||
|
||||
export const SocketContext = createContext()
|
||||
|
||||
export const SocketContextProvider = ({ children }) => {
|
||||
return (
|
||||
<SocketContext.Provider value={socket}>{children}</SocketContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useSocketContext() {
|
||||
const context = useContext(SocketContext)
|
||||
const [isConnected, setIsConnected] = useState(false)
|
||||
|
||||
return { socket: context }
|
||||
}
|
||||
24
src/pages/_app.js
Normal file
24
src/pages/_app.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { PlayerContextProvider } from "@/context/player"
|
||||
import { SocketContextProvider } from "@/context/socket"
|
||||
import "@/styles/globals.css"
|
||||
import clsx from "clsx"
|
||||
import { Montserrat, Plaster } from "next/font/google"
|
||||
|
||||
const montserrat = Montserrat({ subsets: ["latin"] })
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<SocketContextProvider>
|
||||
<PlayerContextProvider>
|
||||
<main
|
||||
className={clsx(
|
||||
"flex flex-col text-base-[8px]",
|
||||
montserrat.className
|
||||
)}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
</PlayerContextProvider>
|
||||
</SocketContextProvider>
|
||||
)
|
||||
}
|
||||
13
src/pages/_document.js
Normal file
13
src/pages/_document.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Html, Head, Main, NextScript } from "next/document"
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head />
|
||||
<body className="bg-secondary">
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
5
src/pages/api/hello.js
Normal file
5
src/pages/api/hello.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
|
||||
export default function handler(req, res) {
|
||||
res.status(200).json({ name: "John Doe" });
|
||||
}
|
||||
27
src/pages/api/socket.js
Normal file
27
src/pages/api/socket.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Server } from "socket.io"
|
||||
|
||||
const socketHandler = (req, res) => {
|
||||
if (!res.socket.server.io) {
|
||||
const path = "/api/socket"
|
||||
const httpServer = res.socket.server
|
||||
|
||||
//const io = createIOServer(httpServer, path)
|
||||
const io = new Server(httpServer, {
|
||||
path: path,
|
||||
addTrailingSlash: false,
|
||||
})
|
||||
|
||||
res.socket.server.io = io
|
||||
console.log("SocketIO server started at " + path)
|
||||
}
|
||||
|
||||
res.end()
|
||||
}
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
}
|
||||
|
||||
export default socketHandler
|
||||
64
src/pages/game.jsx
Normal file
64
src/pages/game.jsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import GameWrapper from "@/components/game/GameWrapper"
|
||||
import Answers from "@/components/game/states/Answers"
|
||||
import Leaderboard from "@/components/game/states/Leaderboard"
|
||||
import Question from "@/components/game/states/Question"
|
||||
import Result from "@/components/game/states/Result"
|
||||
import Wait from "@/components/game/states/Wait"
|
||||
import { usePlayerContext } from "@/context/player"
|
||||
import { useSocketContext } from "@/context/socket"
|
||||
import { useRouter } from "next/router"
|
||||
import { createElement, useState } from "react"
|
||||
|
||||
const gameStateComponent = {
|
||||
SELECT_ANSWER: Answers,
|
||||
SHOW_QUESTION: Question,
|
||||
WAIT: Wait,
|
||||
SHOW_RESULT: Result,
|
||||
}
|
||||
|
||||
export default function Game() {
|
||||
const router = useRouter()
|
||||
|
||||
const { socket } = useSocketContext()
|
||||
const { player } = usePlayerContext()
|
||||
|
||||
if (!player) {
|
||||
//router.push("/")
|
||||
return
|
||||
}
|
||||
|
||||
socket.emit("player:join", { username: "Test", room: player.room })
|
||||
|
||||
const [state, setState] = useState({
|
||||
status: {
|
||||
name: "WAIT",
|
||||
data: { text: "Waiting for the players" },
|
||||
},
|
||||
question: {
|
||||
current: 1,
|
||||
total: null,
|
||||
},
|
||||
})
|
||||
|
||||
socket.on("game:status", (status) => {
|
||||
setState({
|
||||
...gameState,
|
||||
status: status,
|
||||
})
|
||||
})
|
||||
|
||||
/*socket.on("game:", (question) => {
|
||||
setState({
|
||||
...state,
|
||||
|
||||
})*/
|
||||
|
||||
return (
|
||||
<GameWrapper>
|
||||
{gameStateComponent[state.status.name] &&
|
||||
createElement(gameStateComponent[state.status.name], {
|
||||
data: state.status.data,
|
||||
})}
|
||||
</GameWrapper>
|
||||
)
|
||||
}
|
||||
36
src/pages/index.js
Normal file
36
src/pages/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import Image from "next/image"
|
||||
import { Montserrat } from "next/font/google"
|
||||
import Form from "@/components/Form"
|
||||
import Button from "@/components/Button"
|
||||
import Input from "@/components/Input"
|
||||
import logo from "@/assets/logo.svg"
|
||||
import { useState } from "react"
|
||||
import Loader from "@/components/Loader"
|
||||
import { usePlayerContext } from "@/context/player"
|
||||
import Room from "@/components/game/join/Room"
|
||||
import Username from "@/components/game/join/Username"
|
||||
|
||||
export default function Home() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const { player } = usePlayerContext()
|
||||
|
||||
return (
|
||||
<section className="min-h-screen relative flex justify-center items-center flex-col">
|
||||
<div className="absolute w-full h-full overflow-hidden">
|
||||
<div className="absolute min-w-[75vmin] min-h-[75vmin] -top-[15vmin] -left-[15vmin] bg-primary/15 rounded-full"></div>
|
||||
<div className="absolute min-w-[75vmin] min-h-[75vmin] -bottom-[15vmin] -right-[15vmin] bg-primary/15 rotate-45"></div>
|
||||
</div>
|
||||
|
||||
{loading && (
|
||||
<div className="absolute w-full h-full z-30 bg-black/40 flex justify-center items-center">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Image src={logo} className="h-10 mb-6" />
|
||||
|
||||
{!player ? <Room /> : <Username />}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
63
src/pages/manager.jsx
Normal file
63
src/pages/manager.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import GameWrapper from "@/components/game/GameWrapper"
|
||||
import Answers from "@/components/game/states/Answers"
|
||||
import Leaderboard from "@/components/game/states/Leaderboard"
|
||||
import Question from "@/components/game/states/Question"
|
||||
import Wait from "@/components/game/states/Wait"
|
||||
import { useSocketContext } from "@/context/socket"
|
||||
import { createElement, useEffect, useMemo, useState } from "react"
|
||||
|
||||
export default function Manager({ children }) {
|
||||
const { socket } = useSocketContext()
|
||||
|
||||
const [gameState, setGameState] = useState({
|
||||
status: {
|
||||
name: "PENDING",
|
||||
},
|
||||
cooldown: 0,
|
||||
answers: [],
|
||||
})
|
||||
|
||||
socket.on("game:status", (status) => {
|
||||
setGameState({
|
||||
...gameState,
|
||||
status: status,
|
||||
cooldown: 0,
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="text-white">{JSON.stringify(gameState)}</p>
|
||||
<hr></hr>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
className="bg-primary p-2"
|
||||
onClick={() => socket.emit("manager:createRoom")}
|
||||
>
|
||||
Create Room
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="bg-primary p-2"
|
||||
onClick={() => socket.emit("manager:startGame")}
|
||||
>
|
||||
Start Game
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="bg-primary p-2"
|
||||
onClick={() => socket.emit("manager:showLeaderboard")}
|
||||
>
|
||||
Leaderboard
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="bg-primary p-2"
|
||||
onClick={() => socket.emit("manager:nextQuestion")}
|
||||
>
|
||||
Next Question
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
43
src/pages/question.jsx
Normal file
43
src/pages/question.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import Image from "next/image"
|
||||
import Form from "@/components/Form"
|
||||
import Button from "@/components/Button"
|
||||
import Input from "@/components/Input"
|
||||
import logo from "@/assets/logo.svg"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import Loader from "@/components/Loader"
|
||||
import { useSocketContext } from "@/context/socket"
|
||||
import background from "@/assets/2238431_1694.jpg"
|
||||
|
||||
export default function Questionasas() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const socket = useSocketContext()
|
||||
const barRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
barRef.current.style.width = "100%"
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="min-h-screen relative flex justify-center items-center flex-col max-w-7xl mx-auto">
|
||||
<div className="fixed h-full w-full top-0 left-0 bg-orange-600 opacity-70 -z-10">
|
||||
<Image
|
||||
className="object-cover h-full w-full opacity-60 pointer-events-none"
|
||||
src={background}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2 className="text-white text-5xl font-bold drop-shadow-lg text-center">
|
||||
Lorem ipsum dolor sit ametorrupti. Alias, recusandae officia.
|
||||
</h2>
|
||||
|
||||
<div
|
||||
ref={barRef}
|
||||
className="absolute h-6 bg-primary bottom-0 mb-32 rounded-full w-0 self-start"
|
||||
style={{ transition: "width 5s linear" }}
|
||||
></div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
52
src/styles/globals.css
Normal file
52
src/styles/globals.css
Normal file
@@ -0,0 +1,52 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.btn-shadow {
|
||||
box-shadow: rgba(0, 0, 0, 0.25) 0px -4px inset;
|
||||
}
|
||||
|
||||
.btn-shadow span {
|
||||
display: block;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-shadow:hover {
|
||||
box-shadow: rgba(0, 0, 0, 0.25) 0px -2px inset;
|
||||
}
|
||||
|
||||
.btn-shadow:hover span {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-shadow:active {
|
||||
transform: translateY(1px);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.text-outline {
|
||||
-webkit-text-stroke: 2px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.shadow-inset {
|
||||
box-shadow: rgba(0, 0, 0, 0.25) 0px -4px inset;
|
||||
}
|
||||
|
||||
.anim-show {
|
||||
animation: show 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes show {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
30% {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
60% {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
80% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
0
src/validator.js
Normal file
0
src/validator.js
Normal file
17
tailwind.config.js
Normal file
17
tailwind.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: "#ff9900",
|
||||
secondary: "#1a140b",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
Reference in New Issue
Block a user