- implement actions section

This commit is contained in:
Amruth Pillai
2020-07-10 13:40:48 +05:30
parent 89fa8236e8
commit a8c5d29858
23 changed files with 304 additions and 167 deletions

View File

@ -1,6 +1,7 @@
{ {
"globals": { "globals": {
"document": true, "document": true,
"FileReader": true,
"localStorage": true "localStorage": true
}, },
"extends": ["airbnb", "prettier"], "extends": ["airbnb", "prettier"],

166
package-lock.json generated
View File

@ -1089,14 +1089,14 @@
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
}, },
"@firebase/analytics": { "@firebase/analytics": {
"version": "0.3.8", "version": "0.3.9",
"resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.3.8.tgz", "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.3.9.tgz",
"integrity": "sha512-HpNRBJHnrGq5jtVTNRgA8Ozng2ilt0pkej8D5EvXoaylu80U+ICKLBlIT8TdUSEfkXC/RPjvLXg6vn/sq/CyqA==", "integrity": "sha512-l4dNskm8uQ+UqO6Lw+fuyO1enZBXUV6xNMxeVABEnVrp3wOP90KKb/ZwYgleAxF1It52lorcTtkA1YFpv3iEIQ==",
"requires": { "requires": {
"@firebase/analytics-types": "0.3.1", "@firebase/analytics-types": "0.3.1",
"@firebase/component": "0.1.15", "@firebase/component": "0.1.16",
"@firebase/installations": "0.4.13", "@firebase/installations": "0.4.14",
"@firebase/logger": "0.2.5", "@firebase/logger": "0.2.6",
"@firebase/util": "0.2.50", "@firebase/util": "0.2.50",
"tslib": "^1.11.1" "tslib": "^1.11.1"
} }
@ -1107,13 +1107,13 @@
"integrity": "sha512-63vVJ5NIBh/JF8l9LuPrQYSzFimk7zYHySQB4Dk9rVdJ8kV/vGQoVTvRu1UW05sEc2Ug5PqtEChtTHU+9hvPcA==" "integrity": "sha512-63vVJ5NIBh/JF8l9LuPrQYSzFimk7zYHySQB4Dk9rVdJ8kV/vGQoVTvRu1UW05sEc2Ug5PqtEChtTHU+9hvPcA=="
}, },
"@firebase/app": { "@firebase/app": {
"version": "0.6.7", "version": "0.6.8",
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.7.tgz", "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.8.tgz",
"integrity": "sha512-6NpIZ3iMrCR2XOShK5oi3YYB0GXX5yxVD8p3+2N+X4CF5cERyIrDRf8+YXOFgr+bDHSbVcIyzpWv6ijhg4MJlw==", "integrity": "sha512-Tm7Pi6Dtpx4FFKcpm0jcrZ/qI9oREBxmP3pWlw1jgDW4syRJHmN9/5DYvfFk6FAhj3FrY8E/6F+ngWJfqONotQ==",
"requires": { "requires": {
"@firebase/app-types": "0.6.1", "@firebase/app-types": "0.6.1",
"@firebase/component": "0.1.15", "@firebase/component": "0.1.16",
"@firebase/logger": "0.2.5", "@firebase/logger": "0.2.6",
"@firebase/util": "0.2.50", "@firebase/util": "0.2.50",
"dom-storage": "2.1.0", "dom-storage": "2.1.0",
"tslib": "^1.11.1", "tslib": "^1.11.1",
@ -1126,9 +1126,9 @@
"integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==" "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg=="
}, },
"@firebase/auth": { "@firebase/auth": {
"version": "0.14.7", "version": "0.14.8",
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.14.7.tgz", "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.14.8.tgz",
"integrity": "sha512-NTQY9luV70XUA6zGYOWloDSaOT+l0/R4u3W7ptqVCfZNc4DAt7euUkTbj7SDD14902sHF54j+tk5kmpEmMd0jA==", "integrity": "sha512-LufoWcFpYAoCUkFDKSELH69xI8NdOjNTUFKvWfADZN7ysr4dpPdDs2ZYnH67FqcMb0tX+Jdx6vWrF6VZ37AAJQ==",
"requires": { "requires": {
"@firebase/auth-types": "0.10.1" "@firebase/auth-types": "0.10.1"
} }
@ -1144,23 +1144,23 @@
"integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==" "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw=="
}, },
"@firebase/component": { "@firebase/component": {
"version": "0.1.15", "version": "0.1.16",
"resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.15.tgz", "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.16.tgz",
"integrity": "sha512-HqFb1qQl1vtlUMIzPM15plNz27jqM8DWjuQQuGeDfG+4iRRflwKfgNw1BOyoP4kQ8vOBCL7t/71yPXSomNdJdQ==", "integrity": "sha512-FvffvFN0LWgv1H/FIyruTECOL69Dhy+JfwoTq+mV39V8Mz9lNpo41etonL5AOr7KmXxYJVbNwkx0L9Ei88i7JA==",
"requires": { "requires": {
"@firebase/util": "0.2.50", "@firebase/util": "0.2.50",
"tslib": "^1.11.1" "tslib": "^1.11.1"
} }
}, },
"@firebase/database": { "@firebase/database": {
"version": "0.6.6", "version": "0.6.7",
"resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.6.tgz", "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.7.tgz",
"integrity": "sha512-TqUJOaCATF/h3wpqhPT9Fz1nZI6gBv/M2pHZztUjX4A9o9Bq93NyqUurYiZnGB7zpSkEADFCVT4f0VBrWdHlNw==", "integrity": "sha512-vm0ch2zNSoHfXWnDG6WVjf0p/BdXOMBL1lAfkGu3DYH/Rkl4p97x57w0WNOURNfL4GY2LIqScSYKCidV7jqTog==",
"requires": { "requires": {
"@firebase/auth-interop-types": "0.1.5", "@firebase/auth-interop-types": "0.1.5",
"@firebase/component": "0.1.15", "@firebase/component": "0.1.16",
"@firebase/database-types": "0.5.1", "@firebase/database-types": "0.5.1",
"@firebase/logger": "0.2.5", "@firebase/logger": "0.2.6",
"@firebase/util": "0.2.50", "@firebase/util": "0.2.50",
"faye-websocket": "0.11.3", "faye-websocket": "0.11.3",
"tslib": "^1.11.1" "tslib": "^1.11.1"
@ -1175,13 +1175,13 @@
} }
}, },
"@firebase/firestore": { "@firebase/firestore": {
"version": "1.15.5", "version": "1.16.0",
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.15.5.tgz", "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.16.0.tgz",
"integrity": "sha512-unkRIC2hL2Ge5er/Hj43aUYiEKlW5bpju8TnIaF33avg/wZpSsmtVrMlAQVkBWFhvWeYpJSr2QOzNLa1bQvuCA==", "integrity": "sha512-RsgGIP9e6HW5soEHIuo0CGVFpeTKe0hqjrgOLk92W0mjL6irzBlqmd5HcGMY4F5QiZryc2vMT1/3LvRhkUyf8g==",
"requires": { "requires": {
"@firebase/component": "0.1.15", "@firebase/component": "0.1.16",
"@firebase/firestore-types": "1.11.0", "@firebase/firestore-types": "1.12.0",
"@firebase/logger": "0.2.5", "@firebase/logger": "0.2.6",
"@firebase/util": "0.2.50", "@firebase/util": "0.2.50",
"@firebase/webchannel-wrapper": "0.2.41", "@firebase/webchannel-wrapper": "0.2.41",
"@grpc/grpc-js": "^1.0.0", "@grpc/grpc-js": "^1.0.0",
@ -1190,16 +1190,16 @@
} }
}, },
"@firebase/firestore-types": { "@firebase/firestore-types": {
"version": "1.11.0", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.11.0.tgz", "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.12.0.tgz",
"integrity": "sha512-hD7+cmMUvT5OJeWVrcRkE87PPuj/0/Wic6bntCopJE1WIX/Dm117AUkHgKd3S7Ici6DLp4bdlx1MjjwWL5942w==" "integrity": "sha512-OqNxVb63wPZdUc7YnpacAW1WNIMSKERSewCRi+unCQ0YI0KNfrDSypyGCyel+S3GdOtKMk9KnvDknaGbnaFX4g=="
}, },
"@firebase/functions": { "@firebase/functions": {
"version": "0.4.47", "version": "0.4.48",
"resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.4.47.tgz", "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.4.48.tgz",
"integrity": "sha512-wiyMezW1EYq80Uk15M4poapCG10PjN5UJEY0jJr7DhCnDAoADMGlsIYFYio60+biGreij5/hpOybw5mU9WpXUw==", "integrity": "sha512-BwI/JzO/f/nquKG1IS3VqmwMaKEhvM58/08vTnp46krHBsOYqsdD9T2amz+HXGT9fe2HhDsUhgFE8D00S0vqbg==",
"requires": { "requires": {
"@firebase/component": "0.1.15", "@firebase/component": "0.1.16",
"@firebase/functions-types": "0.3.17", "@firebase/functions-types": "0.3.17",
"@firebase/messaging-types": "0.4.5", "@firebase/messaging-types": "0.4.5",
"isomorphic-fetch": "2.2.1", "isomorphic-fetch": "2.2.1",
@ -1212,11 +1212,11 @@
"integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==" "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ=="
}, },
"@firebase/installations": { "@firebase/installations": {
"version": "0.4.13", "version": "0.4.14",
"resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.13.tgz", "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.14.tgz",
"integrity": "sha512-Sic7BtWgdUwk+Z1C4L49Edkhzaol/ijEIdv0pkHfjedIPirIU2V8CJ5qykx2y4aTiyVbdFqfjIpp1c6A6W3GBA==", "integrity": "sha512-hQPsaU7wdTq3CFMtFQwZy6LgdXZAkXoUToV4O+ekPbjM65QzaGVogJVU8O2H6ADXoq37SarcUXKe86pcUWdFLA==",
"requires": { "requires": {
"@firebase/component": "0.1.15", "@firebase/component": "0.1.16",
"@firebase/installations-types": "0.3.4", "@firebase/installations-types": "0.3.4",
"@firebase/util": "0.2.50", "@firebase/util": "0.2.50",
"idb": "3.0.2", "idb": "3.0.2",
@ -1229,17 +1229,17 @@
"integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==" "integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q=="
}, },
"@firebase/logger": { "@firebase/logger": {
"version": "0.2.5", "version": "0.2.6",
"resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz",
"integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw=="
}, },
"@firebase/messaging": { "@firebase/messaging": {
"version": "0.6.19", "version": "0.6.20",
"resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.6.19.tgz", "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.6.20.tgz",
"integrity": "sha512-PhqK69m70G+GGgvbdnGz2+PyoqfmR5b+nouj1JV+HgyBCjMAhF8rDYQzCWWgy4HaWbLoS/xW6AZUKG20Kv2H1A==", "integrity": "sha512-1MqyljXnbFBeHYhL6QInVM9aO5MW820yhNmOIVxk58wNXq4tOQLzqnKuvlgZ+ttgqlDzrIYiVf3EOHh5DptttQ==",
"requires": { "requires": {
"@firebase/component": "0.1.15", "@firebase/component": "0.1.16",
"@firebase/installations": "0.4.13", "@firebase/installations": "0.4.14",
"@firebase/messaging-types": "0.4.5", "@firebase/messaging-types": "0.4.5",
"@firebase/util": "0.2.50", "@firebase/util": "0.2.50",
"idb": "3.0.2", "idb": "3.0.2",
@ -1252,13 +1252,13 @@
"integrity": "sha512-sux4fgqr/0KyIxqzHlatI04Ajs5rc3WM+WmtCpxrKP1E5Bke8xu/0M+2oy4lK/sQ7nov9z15n3iltAHCgTRU3Q==" "integrity": "sha512-sux4fgqr/0KyIxqzHlatI04Ajs5rc3WM+WmtCpxrKP1E5Bke8xu/0M+2oy4lK/sQ7nov9z15n3iltAHCgTRU3Q=="
}, },
"@firebase/performance": { "@firebase/performance": {
"version": "0.3.8", "version": "0.3.9",
"resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.3.8.tgz", "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.3.9.tgz",
"integrity": "sha512-jODXrtFLyfnRiBehHuMBmsBtMv38U9sTictRxJSz+9JahvWYm1AF0YDzPlfeyYj+kxM6+S5wdQxUaPVdcWAvWg==", "integrity": "sha512-Fj22DZXRhhKv1OSUzDxX7AqpJUcDld6tzXK1yxOC8e3v1DFPQMQdM9FoG1m1b/Vrqa6pCCqnqG6gh6VPnEcAzQ==",
"requires": { "requires": {
"@firebase/component": "0.1.15", "@firebase/component": "0.1.16",
"@firebase/installations": "0.4.13", "@firebase/installations": "0.4.14",
"@firebase/logger": "0.2.5", "@firebase/logger": "0.2.6",
"@firebase/performance-types": "0.0.13", "@firebase/performance-types": "0.0.13",
"@firebase/util": "0.2.50", "@firebase/util": "0.2.50",
"tslib": "^1.11.1" "tslib": "^1.11.1"
@ -1287,13 +1287,13 @@
} }
}, },
"@firebase/remote-config": { "@firebase/remote-config": {
"version": "0.1.24", "version": "0.1.25",
"resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.24.tgz", "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.25.tgz",
"integrity": "sha512-/Kd+I5mNPI2wJJFySOC8Mjj4lRnEwZhU0RteuVlzFCDWWEyTE//r+p2TLAufQ9J+Fd3Ru5fVMFLNyU8k71Viiw==", "integrity": "sha512-8YWefBhy77HMbWXWdbenalx+IDY/XkS+iURQ9qRYvSIFYx6RL04DzlakZNOY9CQAcxTA+cTSt4NNlhjopBjf2Q==",
"requires": { "requires": {
"@firebase/component": "0.1.15", "@firebase/component": "0.1.16",
"@firebase/installations": "0.4.13", "@firebase/installations": "0.4.14",
"@firebase/logger": "0.2.5", "@firebase/logger": "0.2.6",
"@firebase/remote-config-types": "0.1.9", "@firebase/remote-config-types": "0.1.9",
"@firebase/util": "0.2.50", "@firebase/util": "0.2.50",
"tslib": "^1.11.1" "tslib": "^1.11.1"
@ -1305,11 +1305,11 @@
"integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==" "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA=="
}, },
"@firebase/storage": { "@firebase/storage": {
"version": "0.3.37", "version": "0.3.38",
"resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.37.tgz", "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.38.tgz",
"integrity": "sha512-RLbiRQlnvXRP/30OaEiUoRHBxZygqrZyotPPWD2WmD3JMM9qGTVpYNQ092mqL3R8ViyejwlpjlPvrDo7Z9BzgQ==", "integrity": "sha512-gWVQr5xqrU3cfhhwbAE+9iJ0XMvzbxWMvteKurn5cRNaGbmSob/O/ISOAvsQgPnk+K9zPMd2OwyzaTOl9PEMrw==",
"requires": { "requires": {
"@firebase/component": "0.1.15", "@firebase/component": "0.1.16",
"@firebase/storage-types": "0.3.12", "@firebase/storage-types": "0.3.12",
"@firebase/util": "0.2.50", "@firebase/util": "0.2.50",
"tslib": "^1.11.1" "tslib": "^1.11.1"
@ -1369,9 +1369,9 @@
} }
}, },
"@grpc/grpc-js": { "@grpc/grpc-js": {
"version": "1.1.1", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.2.tgz",
"integrity": "sha512-mhZRszS0SKwnWPJaNyrECePZ9U7vaHFGqrzxQbWinWR3WznBIU+nmh2L5J3elF+lp5DEUIzARXkifbs6LQVAHA==", "integrity": "sha512-k2u86Bkm/3xrjUaSWeIyzXScBt/cC8uE7BznR0cpueQi11R33W6qfJdMrkrsmSHirp5likR55JSXUrcWG6ybHA==",
"requires": { "requires": {
"semver": "^6.2.0" "semver": "^6.2.0"
} }
@ -8021,23 +8021,23 @@
} }
}, },
"firebase": { "firebase": {
"version": "7.15.5", "version": "7.16.0",
"resolved": "https://registry.npmjs.org/firebase/-/firebase-7.15.5.tgz", "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.16.0.tgz",
"integrity": "sha512-yeXo3KDp/ZWO0/Uyen99cUvGM76femebmyNOBTHcGSDkBXvIGth6235KhclxLROIKCC5b3YNwmKX11tbaC6RJg==", "integrity": "sha512-fYimLYkY0SS/jv4+ZnSp5u2+QdtwsTtLwPUfmWiKQnjqas1M5mqhQr3QB7vPQuSANhC3UJZZ5KPxLbFomMJLcA==",
"requires": { "requires": {
"@firebase/analytics": "0.3.8", "@firebase/analytics": "0.3.9",
"@firebase/app": "0.6.7", "@firebase/app": "0.6.8",
"@firebase/app-types": "0.6.1", "@firebase/app-types": "0.6.1",
"@firebase/auth": "0.14.7", "@firebase/auth": "0.14.8",
"@firebase/database": "0.6.6", "@firebase/database": "0.6.7",
"@firebase/firestore": "1.15.5", "@firebase/firestore": "1.16.0",
"@firebase/functions": "0.4.47", "@firebase/functions": "0.4.48",
"@firebase/installations": "0.4.13", "@firebase/installations": "0.4.14",
"@firebase/messaging": "0.6.19", "@firebase/messaging": "0.6.20",
"@firebase/performance": "0.3.8", "@firebase/performance": "0.3.9",
"@firebase/polyfill": "0.3.36", "@firebase/polyfill": "0.3.36",
"@firebase/remote-config": "0.1.24", "@firebase/remote-config": "0.1.25",
"@firebase/storage": "0.3.37", "@firebase/storage": "0.3.38",
"@firebase/util": "0.2.50" "@firebase/util": "0.2.50"
} }
}, },
@ -19528,9 +19528,9 @@
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="
}, },
"whatwg-fetch": { "whatwg-fetch": {
"version": "3.1.1", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.1.1.tgz", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.2.0.tgz",
"integrity": "sha512-UlBvc5VApYAwNutfXFeuC9Jp3QCMazcqobfNqSK/RghRr3F8b0+i/QELUlEPsHjDHfijio6H5KPJcZwYXhuZsA==" "integrity": "sha512-SdGPoQMMnzVYThUbSrEvqTlkvC1Ux27NehaJ/GUHBfNrh5Mjg+1/uRyFMwVnxO2MrikMWvWAqUGgQOfVU4hT7w=="
}, },
"which": { "which": {
"version": "1.3.1", "version": "1.3.1",

View File

@ -22,7 +22,7 @@
"array-move": "^2.2.2", "array-move": "^2.2.2",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"firebase": "^7.15.5", "firebase": "^7.16.0",
"formik": "^2.1.4", "formik": "^2.1.4",
"gatsby": "^2.24.1", "gatsby": "^2.24.1",
"gatsby-image": "^2.4.13", "gatsby-image": "^2.4.13",

View File

@ -53,13 +53,9 @@ const List = ({
)} )}
</div> </div>
<Button <Button outline icon={MdAdd} onClick={handleAdd} className="mt-8 ml-auto">
outline Add New
icon={MdAdd} </Button>
title="Add New"
onClick={handleAdd}
className="mt-8 ml-auto"
/>
</div> </div>
); );
}; };

View File

@ -1,25 +1,52 @@
import React, { memo } from 'react'; import React, { memo, useContext } from 'react';
import { MdImportExport } from 'react-icons/md'; import { MdImportExport } from 'react-icons/md';
import { clone } from 'lodash';
import Heading from '../../../shared/Heading'; import Heading from '../../../shared/Heading';
import Button from '../../../shared/Button'; import Button from '../../../shared/Button';
import styles from './Actions.module.css'; import styles from './Actions.module.css';
import Input from '../../../shared/Input'; import Input from '../../../shared/Input';
import ModalContext from '../../../../contexts/ModalContext';
import { useSelector } from '../../../../contexts/ResumeContext';
const Actions = () => { const Actions = () => {
const state = useSelector();
const { emitter, events } = useContext(ModalContext);
const handleImport = () => emitter.emit(events.IMPORT_MODAL);
const handleExportToJson = () => {
const backupObj = clone(state);
delete backupObj.id;
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(backupObj),
)}`;
const dlAnchor = document.getElementById('downloadAnchor');
dlAnchor.setAttribute('href', dataStr);
dlAnchor.setAttribute('download', `RxResume_${state.id}.json`);
dlAnchor.click();
};
const getSharableUrl = () => {
const shareId = state.id.split('-')[0];
return `https://rxresu.me/r/${shareId}`;
};
return ( return (
<section> <section>
<Heading>Actions</Heading> <Heading>Actions</Heading>
<div className={styles.container}> <div className={styles.container}>
<h5>Import from Other Sources</h5> <h5>Import Your Resume</h5>
<p> <p>
You can import your information from various sources like JSON Resume You can import your information from various sources like JSON Resume
or your LinkedIn profile to autofill most of the data for your resume. or your LinkedIn to autofill most of the data for your resume.
</p> </p>
<div className="mt-4 flex"> <div className="mt-4 flex">
<Button icon={MdImportExport} title="Import" /> <Button icon={MdImportExport} onClick={handleImport}>
Import
</Button>
</div> </div>
</div> </div>
@ -32,21 +59,27 @@ const Actions = () => {
</p> </p>
<div className="mt-4 flex"> <div className="mt-4 flex">
<Button title="Save as PDF" /> <Button>Save as PDF</Button>
<Button outline title="Export as JSON" className="ml-6" /> <Button outline className="ml-6" onClick={handleExportToJson}>
Export as JSON
</Button>
</div> </div>
<a id="downloadAnchor" className="hidden">
Download Exported JSON
</a>
</div> </div>
<div className={styles.container}> <div className={styles.container}>
<h5>Share Your Resume</h5> <h5>Share Your Resume</h5>
<p> <p>
The link below will be accessible publicly if you choose, and you can The link below will be accessible publicly if you choose to share it,
share the latest version of your resume to anyone in the world. and viewers would see the latest version of your resume at any time.
</p> </p>
<div> <div>
<Input type="action" value="https://google.com" onClick={() => {}} /> <Input type="action" value={getSharableUrl()} onClick={() => {}} />
</div> </div>
</div> </div>
@ -59,7 +92,7 @@ const Actions = () => {
</p> </p>
<div className="mt-4 flex"> <div className="mt-4 flex">
<Button title="Load Demo Data" /> <Button>Load Demo Data</Button>
</div> </div>
</div> </div>
@ -73,7 +106,7 @@ const Actions = () => {
</p> </p>
<div className="mt-4 flex"> <div className="mt-4 flex">
<Button title="Delete Account" /> <Button isDelete>Delete Account</Button>
</div> </div>
</div> </div>
</section> </section>

View File

@ -1,7 +1,7 @@
/* eslint-disable jsx-a11y/control-has-associated-label */ /* eslint-disable jsx-a11y/control-has-associated-label */
import React, { memo } from 'react'; import React, { memo } from 'react';
import { useDispatch } from '../../../../contexts/ResumeContext'; import { useDispatch } from '../../../../contexts/ResumeContext';
import colors from '../../../../data/colors'; import colorOptions from '../../../../data/colorOptions';
import { handleKeyUp } from '../../../../utils'; import { handleKeyUp } from '../../../../utils';
import Heading from '../../../shared/Heading'; import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input'; import Input from '../../../shared/Input';
@ -25,7 +25,7 @@ const Colors = () => {
<Heading>Colors</Heading> <Heading>Colors</Heading>
<div className="mb-6 grid grid-cols-8 col-gap-2 row-gap-6"> <div className="mb-6 grid grid-cols-8 col-gap-2 row-gap-6">
{colors.map((color) => ( {colorOptions.map((color) => (
<div <div
key={color} key={color}
tabIndex="0" tabIndex="0"

View File

@ -1,7 +1,7 @@
import cx from 'classnames'; import cx from 'classnames';
import React, { memo } from 'react'; import React, { memo } from 'react';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext'; import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import fonts from '../../../../data/fonts'; import fontOptions from '../../../../data/fontOptions';
import { handleKeyUp } from '../../../../utils'; import { handleKeyUp } from '../../../../utils';
import Heading from '../../../shared/Heading'; import Heading from '../../../shared/Heading';
import styles from './Fonts.module.css'; import styles from './Fonts.module.css';
@ -25,7 +25,7 @@ const Fonts = () => {
<Heading>Fonts</Heading> <Heading>Fonts</Heading>
<div className="grid grid-cols-2 gap-8"> <div className="grid grid-cols-2 gap-8">
{fonts.map((x) => ( {fontOptions.map((x) => (
<div <div
key={x} key={x}
tabIndex="0" tabIndex="0"

View File

@ -1,7 +1,7 @@
import cx from 'classnames'; import cx from 'classnames';
import React, { memo, useContext } from 'react'; import React, { memo, useContext } from 'react';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext'; import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import templates from '../../../../data/templates'; import templateOptions from '../../../../data/templateOptions';
import { handleKeyUp } from '../../../../utils'; import { handleKeyUp } from '../../../../utils';
import Heading from '../../../shared/Heading'; import Heading from '../../../shared/Heading';
import styles from './Templates.module.css'; import styles from './Templates.module.css';
@ -28,7 +28,7 @@ const Templates = () => {
<Heading>Templates</Heading> <Heading>Templates</Heading>
<div className="grid grid-cols-2 gap-8"> <div className="grid grid-cols-2 gap-8">
{templates.map((x) => ( {templateOptions.map((x) => (
<div <div
key={x.id} key={x.id}
tabIndex="0" tabIndex="0"

View File

@ -28,21 +28,20 @@ const Hero = () => {
<div className="mt-12 flex"> <div className="mt-12 flex">
{user ? ( {user ? (
<Button <Button onClick={handleGotoApp} isLoading={loading}>
title="Go to App" Go to App
onClick={handleGotoApp} </Button>
isLoading={loading}
/>
) : ( ) : (
<Button title="Login" onClick={handleLogin} isLoading={loading} /> <Button title="Login" onClick={handleLogin} isLoading={loading} />
)} )}
<Button <Button
outline outline
className="ml-8" className="ml-8"
title="GitHub"
icon={FaGithub} icon={FaGithub}
onClick={toggleDarkMode} onClick={toggleDarkMode}
/> >
GitHub
</Button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,7 +3,15 @@ import React, { memo } from 'react';
import { handleKeyUp } from '../../utils'; import { handleKeyUp } from '../../utils';
import styles from './Button.module.css'; import styles from './Button.module.css';
const Button = ({ icon, title, onClick, outline, className, isLoading }) => { const Button = ({
icon,
onClick,
outline,
children,
className,
isLoading,
isDelete,
}) => {
const Icon = icon; const Icon = icon;
return ( return (
@ -12,10 +20,11 @@ const Button = ({ icon, title, onClick, outline, className, isLoading }) => {
onClick={isLoading ? undefined : onClick} onClick={isLoading ? undefined : onClick}
className={cx(styles.container, className, { className={cx(styles.container, className, {
[styles.outline]: outline, [styles.outline]: outline,
[styles.delete]: isDelete,
})} })}
> >
{icon && <Icon size="14" className="mr-3" />} {icon && <Icon size="14" className="mr-3" />}
{isLoading ? 'Loading...' : title} {isLoading ? 'Loading...' : children}
</button> </button>
); );
}; };

View File

@ -29,3 +29,15 @@
.container.outline:focus { .container.outline:focus {
@apply outline-none; @apply outline-none;
} }
.container.delete {
@apply bg-red-600 border-red-600 text-white;
}
.container.delete:hover {
@apply bg-red-700 border-red-700;
}
.container.delete:focus {
@apply outline-none;
}

View File

@ -4,7 +4,6 @@ import React, { memo, useEffect, useState } from 'react';
import { FaAngleDown } from 'react-icons/fa'; import { FaAngleDown } from 'react-icons/fa';
import { MdClose, MdOpenInNew } from 'react-icons/md'; import { MdClose, MdOpenInNew } from 'react-icons/md';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { IoIosCopy } from 'react-icons/io';
import { useDispatch, useSelector } from '../../contexts/ResumeContext'; import { useDispatch, useSelector } from '../../contexts/ResumeContext';
import { handleKeyUp } from '../../utils'; import { handleKeyUp } from '../../utils';
import styles from './Input.module.css'; import styles from './Input.module.css';

View File

@ -10,6 +10,7 @@ const ModalEvents = {
HOBBY_MODAL: 'hobby_modal', HOBBY_MODAL: 'hobby_modal',
LANGUAGE_MODAL: 'language_modal', LANGUAGE_MODAL: 'language_modal',
REFERENCE_MODAL: 'reference_modal', REFERENCE_MODAL: 'reference_modal',
IMPORT_MODAL: 'import_modal',
}; };
export default ModalEvents; export default ModalEvents;

View File

@ -14,18 +14,18 @@ const DEBOUNCE_WAIT_TIME = 4000;
const defaultState = { const defaultState = {
isOffline: false, isOffline: false,
isUpdating: false, isUpdating: false,
createResume: () => {},
deleteResume: () => {},
getResume: async () => {}, getResume: async () => {},
getResumes: async () => {}, getResumes: async () => {},
createResume: () => {},
updateResume: async () => {}, updateResume: async () => {},
debouncedUpdateResume: async () => {}, debouncedUpdateResume: async () => {},
debouncedUpdateMetadata: async () => {},
deleteResume: () => {},
}; };
const DatabaseContext = createContext(defaultState); const DatabaseContext = createContext(defaultState);
const DatabaseProvider = ({ children }) => { const DatabaseProvider = ({ children }) => {
const [resumeId, setResumeId] = useState(false);
const [isOffline, setOffline] = useState(false); const [isOffline, setOffline] = useState(false);
const [isUpdating, setUpdating] = useState(false); const [isUpdating, setUpdating] = useState(false);
const { user } = useContext(UserContext); const { user } = useContext(UserContext);
@ -38,6 +38,7 @@ const DatabaseProvider = ({ children }) => {
}, []); }, []);
const getResume = async (id) => { const getResume = async (id) => {
setResumeId(id);
const snapshot = await firebase const snapshot = await firebase
.database() .database()
.ref(`users/${user.uid}/resumes/${id}`) .ref(`users/${user.uid}/resumes/${id}`)
@ -71,13 +72,11 @@ const DatabaseProvider = ({ children }) => {
}; };
const updateResume = async (resume) => { const updateResume = async (resume) => {
const { id } = resume;
setUpdating(true); setUpdating(true);
await firebase await firebase
.database() .database()
.ref(`users/${user.uid}/resumes/${id}`) .ref(`users/${user.uid}/resumes/${resumeId}`)
.update({ .update({
...resume, ...resume,
updatedAt: firebase.database.ServerValue.TIMESTAMP, updatedAt: firebase.database.ServerValue.TIMESTAMP,
@ -88,22 +87,6 @@ const DatabaseProvider = ({ children }) => {
const debouncedUpdateResume = debounce(updateResume, DEBOUNCE_WAIT_TIME); const debouncedUpdateResume = debounce(updateResume, DEBOUNCE_WAIT_TIME);
const updateMetadata = async (resumeId, metadata) => {
setUpdating(true);
await firebase
.database()
.ref(`users/${user.uid}/resumes/${resumeId}`)
.update({
metadata,
updatedAt: firebase.database.ServerValue.TIMESTAMP,
});
setUpdating(false);
};
const debouncedUpdateMetadata = debounce(updateMetadata, DEBOUNCE_WAIT_TIME);
const deleteResume = (id) => { const deleteResume = (id) => {
firebase.database().ref(`users/${user.uid}/resumes/${id}`).remove(); firebase.database().ref(`users/${user.uid}/resumes/${id}`).remove();
}; };
@ -116,9 +99,8 @@ const DatabaseProvider = ({ children }) => {
getResume, getResume,
createResume, createResume,
updateResume, updateResume,
debouncedUpdateResume,
debouncedUpdateMetadata,
deleteResume, deleteResume,
debouncedUpdateResume,
}} }}
> >
{children} {children}

View File

@ -90,6 +90,11 @@ const ResumeProvider = ({ children }) => {
debouncedUpdateResume(newState); debouncedUpdateResume(newState);
return newState; return newState;
case 'on_import':
newState = { id: state.id, ...payload };
debouncedUpdateResume(newState);
return newState;
case 'set_data': case 'set_data':
return payload; return payload;

View File

@ -1,4 +1,4 @@
const colors = [ const colorOptions = [
'#f44336', '#f44336',
'#E91E63', '#E91E63',
'#9C27B0', '#9C27B0',
@ -17,4 +17,4 @@ const colors = [
'#FF5722', '#FF5722',
]; ];
export default colors; export default colorOptions;

View File

@ -1,4 +1,4 @@
const fonts = [ const fontOptions = [
'Lato', 'Lato',
'Montserrat', 'Montserrat',
'Nunito', 'Nunito',
@ -9,4 +9,4 @@ const fonts = [
'Titillium Web', 'Titillium Web',
]; ];
export default fonts; export default fontOptions;

View File

@ -1,4 +1,4 @@
const templates = [ const templateOptions = [
{ {
id: 'onyx', id: 'onyx',
name: 'Onyx', name: 'Onyx',
@ -11,4 +11,4 @@ const templates = [
}, },
]; ];
export default templates; export default templateOptions;

View File

@ -39,23 +39,25 @@ const AuthModal = () => {
const loggedInAction = ( const loggedInAction = (
<> <>
<Button outline className="mr-8" title="Logout" onClick={logout} /> <Button outline className="mr-8" onClick={logout}>
<Button title="Go to App" onClick={handleGotoApp} /> Logout
</Button>
<Button title="" onClick={handleGotoApp}>
Go to App
</Button>
</> </>
); );
const loggedOutAction = ( const loggedOutAction = (
<Button <Button isLoading={isLoading} onClick={handleSignInWithGoogle}>
isLoading={isLoading} Sign in with Google
title="Sign in with Google" </Button>
onClick={handleSignInWithGoogle}
/>
); );
return ( return (
<BaseModal <BaseModal
state={[open, setOpen]}
title={getTitle()} title={getTitle()}
state={[open, setOpen]}
action={user ? loggedInAction : loggedOutAction} action={user ? loggedInAction : loggedOutAction}
> >
<p>{getMessage()}</p> <p>{getMessage()}</p>

View File

@ -9,7 +9,7 @@ import { handleKeyUp } from '../utils';
import styles from './BaseModal.module.css'; import styles from './BaseModal.module.css';
const BaseModal = forwardRef( const BaseModal = forwardRef(
({ title, state, children, action, onDestroy }, ref) => { ({ title, state, children, action, hideActions = false, onDestroy }, ref) => {
const [open, setOpen] = state; const [open, setOpen] = state;
const handleClose = () => { const handleClose = () => {
@ -44,16 +44,15 @@ const BaseModal = forwardRef(
<div className={styles.body}>{children}</div> <div className={styles.body}>{children}</div>
{!hideActions && (
<div className={styles.actions}> <div className={styles.actions}>
<Button <Button outline className="mr-8" onClick={handleClose}>
outline Cancel
title="Cancel" </Button>
className="mr-8"
onClick={handleClose}
/>
{action} {action}
</div> </div>
)}
</div> </div>
</Fade> </Fade>
</Modal> </Modal>

View File

@ -79,7 +79,9 @@ const DataModal = ({
: title.create; : title.create;
const submitAction = ( const submitAction = (
<Button type="submit" title={getTitle} onClick={() => onSubmit(values)} /> <Button type="submit" onClick={() => onSubmit(values)}>
{getTitle}
</Button>
); );
const onDestroy = () => { const onDestroy = () => {

View File

@ -5,6 +5,7 @@ import AwardModal from './sections/AwardModal';
import CertificateModal from './sections/CertificateModal'; import CertificateModal from './sections/CertificateModal';
import EducationModal from './sections/EducationModal'; import EducationModal from './sections/EducationModal';
import HobbyModal from './sections/HobbyModal'; import HobbyModal from './sections/HobbyModal';
import ImportModal from './sections/ImportModal';
import LanguageModal from './sections/LanguageModal'; import LanguageModal from './sections/LanguageModal';
import ReferenceModal from './sections/ReferenceModal'; import ReferenceModal from './sections/ReferenceModal';
import SkillModal from './sections/SkillModal'; import SkillModal from './sections/SkillModal';
@ -25,6 +26,7 @@ const ModalRegistrar = () => {
<HobbyModal /> <HobbyModal />
<LanguageModal /> <LanguageModal />
<ReferenceModal /> <ReferenceModal />
<ImportModal />
</> </>
); );
}; };

View File

@ -0,0 +1,95 @@
import React, { memo, useContext, useEffect, useState, useRef } from 'react';
import { Tooltip } from '@material-ui/core';
import ModalContext from '../../contexts/ModalContext';
import BaseModal from '../BaseModal';
import Button from '../../components/shared/Button';
import { useDispatch } from '../../contexts/ResumeContext';
const ImportModal = () => {
const fileInputRef = useRef(null);
const [open, setOpen] = useState(false);
const dispatch = useDispatch();
const { emitter, events } = useContext(ModalContext);
useEffect(() => {
const unbind = emitter.on(events.IMPORT_MODAL, () => setOpen(true));
return () => unbind();
}, [emitter, events]);
const importReactiveResumeJson = (event) => {
const fr = new FileReader();
fr.addEventListener('load', () => {
const payload = JSON.parse(fr.result);
dispatch({ type: 'on_import', payload });
setOpen(false);
});
fr.readAsText(event.target.files[0]);
};
return (
<BaseModal hideActions state={[open, setOpen]} title="Import Data">
<div>
<h5 className="text-xl font-semibold mb-4">
Import from Reactive Resume
</h5>
<p>
Reactive Resume has it&apos;s own schema format to make the most of
all the customizable capabilities it has to offer. If you&apos;d like
to import a backup of your resume made with this app, just upload the
file using the button below.
</p>
<Button className="mt-5" onClick={() => fileInputRef.current.click()}>
Select File
</Button>
<input
ref={fileInputRef}
type="file"
className="hidden"
onChange={importReactiveResumeJson}
/>
</div>
<hr className="my-8" />
<div>
<h5 className="text-xl font-semibold mb-4">Import from JSON Resume</h5>
<p>
<a href="https://jsonresume.org/">JSON Resume</a> is an open standard
for resume schema structure. If you are one of the many enthusiasts
who have their resume ready in this format, all it takes it just one
click to get started with Reactive Resume.
</p>
<Tooltip title="Coming Soon" placement="right" arrow>
<div className="mt-5 inline-block">
<Button className="opacity-50">Select File</Button>
</div>
</Tooltip>
</div>
<hr className="my-8" />
<div>
<h5 className="text-xl font-semibold mb-4">Import from LinkedIn</h5>
<p>
You can import a JSON that was exported from Reactive Resume by
clicking on the button below and selecting the appropriate file.
</p>
<Tooltip title="Coming Soon" placement="right" arrow>
<div className="mt-5 inline-block">
<Button className="opacity-50">Select File</Button>
</div>
</Tooltip>
</div>
</BaseModal>
);
};
export default memo(ImportModal);