mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-12 15:52:56 +10:00
- complete onyx design template
- implement public sharable urls - implement more actions
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
{
|
||||
"globals": {
|
||||
"window": true,
|
||||
"document": true,
|
||||
"FileReader": true,
|
||||
"localStorage": true
|
||||
|
||||
@ -16,3 +16,12 @@ exports.onCreateWebpackConfig = ({ stage, actions, getConfig }) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.onCreatePage = async ({ page, actions }) => {
|
||||
const { createPage } = actions;
|
||||
|
||||
if (page.path.match(/^\/r/)) {
|
||||
page.matchPath = '/r/*';
|
||||
createPage(page);
|
||||
}
|
||||
};
|
||||
|
||||
218
package-lock.json
generated
218
package-lock.json
generated
@ -1377,9 +1377,9 @@
|
||||
}
|
||||
},
|
||||
"@grpc/proto-loader": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.4.tgz",
|
||||
"integrity": "sha512-HTM4QpI9B2XFkPz7pjwMyMgZchJ93TVkL3kWPW8GDMDKYxsMnmf4w2TNMJK7+KNiYHS5cJrCEAFlF+AwtXWVPA==",
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.5.tgz",
|
||||
"integrity": "sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ==",
|
||||
"requires": {
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"protobufjs": "^6.8.6"
|
||||
@ -2012,6 +2012,21 @@
|
||||
"trough": "^1.0.0",
|
||||
"vfile": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"unist-util-is": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz",
|
||||
"integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ=="
|
||||
},
|
||||
"unist-util-visit": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz",
|
||||
"integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0",
|
||||
"unist-util-is": "^4.0.0",
|
||||
"unist-util-visit-parents": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -2295,9 +2310,9 @@
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "13.13.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.13.tgz",
|
||||
"integrity": "sha512-UfvBE9oRCAJVzfR+3eWm/sdLFe/qroAPEXP3GPJ1SehQiEVgZT6NQZWYbPMiJ3UdcKM06v4j+S1lTcdWCmw+3g=="
|
||||
"version": "13.13.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.14.tgz",
|
||||
"integrity": "sha512-Az3QsOt1U/K1pbCQ0TXGELTuTkPLOiFIQf3ILzbOyo0FqgV9SxRnxbxM5QlAveERZMHpZY+7u3Jz2tKyl+yg6g=="
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
@ -2324,9 +2339,9 @@
|
||||
}
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "16.9.41",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.41.tgz",
|
||||
"integrity": "sha512-6cFei7F7L4wwuM+IND/Q2cV1koQUvJ8iSV+Gwn0c3kvABZ691g7sp3hfEQHOUBJtccl1gPi+EyNjMIl9nGA0ug==",
|
||||
"version": "16.9.42",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.42.tgz",
|
||||
"integrity": "sha512-iGy6HwfVfotqJ+PfRZ4eqPHPP5NdPZgQlr0lTs8EfkODRBV9cYy8QMKcC9qPCe1JrESC1Im6SrCFR6tQgg74ag==",
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^2.2.0"
|
||||
@ -3058,12 +3073,12 @@
|
||||
"integrity": "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ=="
|
||||
},
|
||||
"autoprefixer": {
|
||||
"version": "9.8.4",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.4.tgz",
|
||||
"integrity": "sha512-84aYfXlpUe45lvmS+HoAWKCkirI/sw4JK0/bTeeqgHYco3dcsOn0NqdejISjptsYwNji/21dnkDri9PsYKk89A==",
|
||||
"version": "9.8.5",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.5.tgz",
|
||||
"integrity": "sha512-C2p5KkumJlsTHoNv9w31NrBRgXhf6eCMteJuHZi2xhkgC+5Vm40MEtCKPhc0qdgAOhox0YPy1SQHTAky05UoKg==",
|
||||
"requires": {
|
||||
"browserslist": "^4.12.0",
|
||||
"caniuse-lite": "^1.0.30001087",
|
||||
"caniuse-lite": "^1.0.30001097",
|
||||
"colorette": "^1.2.0",
|
||||
"normalize-range": "^0.1.2",
|
||||
"num2fraction": "^1.2.2",
|
||||
@ -3825,6 +3840,14 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
@ -4371,9 +4394,9 @@
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001096",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001096.tgz",
|
||||
"integrity": "sha512-PFTw9UyVfbkcMEFs82q8XVlRayj7HKvnhu5BLcmjGpv+SNyiWasCcWXPGJuO0rK0dhLRDJmtZcJ+LHUfypbw1w=="
|
||||
"version": "1.0.30001097",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001097.tgz",
|
||||
"integrity": "sha512-TeuSleKt/vWXaPkLVFqGDnbweYfq4IaZ6rUugFf3rWY6dlII8StUZ8Ddin0PkADfgYZ4wRqCdO2ORl4Rn5eZIA=="
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.12.0",
|
||||
@ -5647,12 +5670,9 @@
|
||||
}
|
||||
},
|
||||
"decamelize": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-3.2.0.tgz",
|
||||
"integrity": "sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw==",
|
||||
"requires": {
|
||||
"xregexp": "^4.2.4"
|
||||
}
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
@ -6380,9 +6400,9 @@
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.494",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.494.tgz",
|
||||
"integrity": "sha512-EOZuaDT3L1sCIMAVN5J0nGuGWVq5dThrdl0d8XeDYf4MOzbXqZ19OLKesN8TZj0RxtpYjqHpiw/fR6BKWdMwYA=="
|
||||
"version": "1.3.496",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.496.tgz",
|
||||
"integrity": "sha512-TXY4mwoyowwi4Lsrq9vcTUYBThyc1b2hXaTZI13p8/FRhY2CTaq5lK+DVjhYkKiTLsKt569Xes+0J5JsVXFurQ=="
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.3",
|
||||
@ -6421,11 +6441,11 @@
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"encoding": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
||||
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"requires": {
|
||||
"iconv-lite": "~0.4.13"
|
||||
"iconv-lite": "^0.6.2"
|
||||
}
|
||||
},
|
||||
"end-of-stream": {
|
||||
@ -7594,6 +7614,14 @@
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz",
|
||||
@ -7682,6 +7710,14 @@
|
||||
"tmp": "^0.0.33"
|
||||
},
|
||||
"dependencies": {
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
@ -8230,9 +8266,9 @@
|
||||
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc="
|
||||
},
|
||||
"gatsby": {
|
||||
"version": "2.24.1",
|
||||
"resolved": "https://registry.npmjs.org/gatsby/-/gatsby-2.24.1.tgz",
|
||||
"integrity": "sha512-aqFfx+Vj3kBhS17tgL1LrkMzip2Xctd3lCj+pogCGV9GCkg6wOqW2uOEqWeoiCNq09sPuwv3GNB8sbEFoQ/2DA==",
|
||||
"version": "2.24.2",
|
||||
"resolved": "https://registry.npmjs.org/gatsby/-/gatsby-2.24.2.tgz",
|
||||
"integrity": "sha512-2zhCJZBPRJiUGbFRnCogMY3liBoFdb3+cCmIpp5b4BzGUEm+t+QZPSW34xkV5IE1WNywuIMtpZF6G8xTbuepbA==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.3",
|
||||
"@babel/core": "^7.10.3",
|
||||
@ -8536,11 +8572,11 @@
|
||||
}
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.4.tgz",
|
||||
"integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.5.tgz",
|
||||
"integrity": "sha512-i4dpK6xj9BIpVOTboXIlKG9+8HMKggcrMX7WA24xZtKwX0TPelq/rbaS5rCKeNX8sJXZJGdSxpnEGtta+wismQ==",
|
||||
"requires": {
|
||||
"lru-cache": "^5.1.1"
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"ignore": {
|
||||
@ -8549,11 +8585,11 @@
|
||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg=="
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"requires": {
|
||||
"yallist": "^3.0.2"
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
@ -8598,9 +8634,9 @@
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -10223,11 +10259,11 @@
|
||||
"integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ=="
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
},
|
||||
"icss-replace-symbols": {
|
||||
@ -10866,9 +10902,9 @@
|
||||
"integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.0.tgz",
|
||||
"integrity": "sha512-K+LZp6L/6eE5swqIcVXrxl21aGDU4S50gKH0/d96OMQnSBCyGyZl/oZhbkVmdp5sBoINHd4xZvFSARh2dk6DWA==",
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.1.tgz",
|
||||
"integrity": "sha512-/+vOpHQHhoh90Znev8BXiuw1TDQ7IDxWsQnFafUEoK5+4uN5Eoz1p+3GqOj/NtzEi9VzWKQcV9Bm+i8moxedsA==",
|
||||
"requires": {
|
||||
"ansi-escapes": "^4.2.1",
|
||||
"chalk": "^4.1.0",
|
||||
@ -10876,7 +10912,7 @@
|
||||
"cli-width": "^3.0.0",
|
||||
"external-editor": "^3.0.3",
|
||||
"figures": "^3.0.0",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash": "^4.17.16",
|
||||
"mute-stream": "0.0.8",
|
||||
"run-async": "^2.4.0",
|
||||
"rxjs": "^6.6.0",
|
||||
@ -12495,11 +12531,6 @@
|
||||
"trim-newlines": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"find-up": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
|
||||
@ -12976,9 +13007,9 @@
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"neo-async": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
|
||||
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw=="
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"neon-js": {
|
||||
"version": "1.1.2",
|
||||
@ -14472,9 +14503,9 @@
|
||||
}
|
||||
},
|
||||
"postcss-nested": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-4.2.2.tgz",
|
||||
"integrity": "sha512-KivGs+ikQlX8VvR9pbaNA/eVmnCN9WcvD8sO9gPqgy6Q6teOH9NqbHHv+czcVJwbBtIdcq/lCzsVgK9daNrhDQ==",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-4.2.3.tgz",
|
||||
"integrity": "sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"postcss": "^7.0.32",
|
||||
@ -15165,6 +15196,14 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -15325,6 +15364,14 @@
|
||||
"escape-string-regexp": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
|
||||
@ -15691,12 +15738,12 @@
|
||||
"integrity": "sha512-u5l7fhAJXecWUJzVxzMRU2Zvw8m4QmDNHlTrT5uo3KBlYBhmChd7syAakBoay1yIiVhx/8Fi7a6v6kQZfsw81Q=="
|
||||
},
|
||||
"react-scroll": {
|
||||
"version": "1.7.16",
|
||||
"resolved": "https://registry.npmjs.org/react-scroll/-/react-scroll-1.7.16.tgz",
|
||||
"integrity": "sha512-f4M5AdL+3cw3MJ7c/T0hPMY2iHCeQLDXV13lRanAFQ6JIt9xyAdHCpTH9mLUQt9SQh4pRarD+Qc7KhU6qMx3Yg==",
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-scroll/-/react-scroll-1.8.0.tgz",
|
||||
"integrity": "sha512-oZfBXPhcxYPR8elI9tC3ORT6+iqiPPJWslsdR9intbNI5PVSa4XoAfC0I/cB3zk5lxQ/NSexCnT+8RqJL8mSZQ==",
|
||||
"requires": {
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"prop-types": "^15.5.8"
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"react-side-effect": {
|
||||
@ -16801,6 +16848,11 @@
|
||||
"jsonify": "~0.0.0"
|
||||
}
|
||||
},
|
||||
"short-unique-id": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-3.0.3.tgz",
|
||||
"integrity": "sha512-g8StBeiZN4bAtJlZIZQ3C7RNRjtTdJhwgq4WRHC30+z2dbuE/A0Z51CafHsgpwJHYllW4lyH17EKiyBe4W/AeA=="
|
||||
},
|
||||
"side-channel": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz",
|
||||
@ -18671,9 +18723,9 @@
|
||||
}
|
||||
},
|
||||
"unist-util-visit": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz",
|
||||
"integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz",
|
||||
"integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0",
|
||||
"unist-util-is": "^4.0.0",
|
||||
@ -19449,11 +19501,6 @@
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"del": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
|
||||
@ -20132,14 +20179,6 @@
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
|
||||
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
|
||||
},
|
||||
"xregexp": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz",
|
||||
"integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==",
|
||||
"requires": {
|
||||
"@babel/runtime-corejs3": "^7.8.3"
|
||||
}
|
||||
},
|
||||
"xss": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.7.tgz",
|
||||
@ -20184,12 +20223,12 @@
|
||||
}
|
||||
},
|
||||
"yargs": {
|
||||
"version": "15.4.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.0.tgz",
|
||||
"integrity": "sha512-D3fRFnZwLWp8jVAAhPZBsmeIHY8tTsb8ItV9KaAaopmC6wde2u6Yw29JBIZHXw14kgkRnYmDgmQU4FVMDlIsWw==",
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"requires": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^3.2.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
@ -20268,13 +20307,6 @@
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
}
|
||||
}
|
||||
},
|
||||
"yauzl": {
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
"dotenv": "^8.2.0",
|
||||
"firebase": "^7.16.0",
|
||||
"formik": "^2.1.4",
|
||||
"gatsby": "^2.24.1",
|
||||
"gatsby": "^2.24.2",
|
||||
"gatsby-image": "^2.4.13",
|
||||
"gatsby-plugin-create-client-paths": "^2.3.10",
|
||||
"gatsby-plugin-firebase": "^0.2.0-beta.4",
|
||||
@ -48,13 +48,14 @@
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-icons": "^3.10.0",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-scroll": "^1.7.16",
|
||||
"react-scroll": "^1.8.0",
|
||||
"react-toastify": "^6.0.8",
|
||||
"short-unique-id": "^3.0.3",
|
||||
"uuid": "^8.2.0",
|
||||
"yup": "^0.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^9.8.4",
|
||||
"autoprefixer": "^9.8.5",
|
||||
"eslint": "^7.4.0",
|
||||
"eslint-config-airbnb": "^18.2.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
zoom: 0.8;
|
||||
overflow: scroll;
|
||||
box-shadow: var(--shadow);
|
||||
@apply bg-white;
|
||||
@apply bg-white rounded;
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ const LeftNavbar = () => (
|
||||
|
||||
<hr className="my-6" />
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 text-primary-400">
|
||||
<div className="grid grid-cols-1 gap-4 text-primary-500">
|
||||
{sections.map((x) => (
|
||||
<SectionIcon
|
||||
key={x.id}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import React, { Fragment, memo } from 'react';
|
||||
import { Element } from 'react-scroll';
|
||||
import sections from '../../../data/leftSections';
|
||||
import LeftNavbar from './LeftNavbar';
|
||||
import styles from './LeftSidebar.module.css';
|
||||
import Awards from './sections/Awards';
|
||||
import Certifications from './sections/Certifications';
|
||||
import Education from './sections/Education';
|
||||
@ -8,12 +10,11 @@ import Hobbies from './sections/Hobbies';
|
||||
import Languages from './sections/Languages';
|
||||
import Objective from './sections/Objective';
|
||||
import Profile from './sections/Profile';
|
||||
import Projects from './sections/Projects';
|
||||
import References from './sections/References';
|
||||
import Skills from './sections/Skills';
|
||||
import Social from './sections/Social';
|
||||
import Work from './sections/Work';
|
||||
import LeftNavbar from './LeftNavbar';
|
||||
import styles from './LeftSidebar.module.css';
|
||||
|
||||
const getComponent = (id) => {
|
||||
switch (id) {
|
||||
@ -27,6 +28,8 @@ const getComponent = (id) => {
|
||||
return Work;
|
||||
case 'education':
|
||||
return Education;
|
||||
case 'projects':
|
||||
return Projects;
|
||||
case 'awards':
|
||||
return Awards;
|
||||
case 'certifications':
|
||||
|
||||
23
src/components/builder/left/sections/Projects.js
Normal file
23
src/components/builder/left/sections/Projects.js
Normal file
@ -0,0 +1,23 @@
|
||||
import React, { memo } from 'react';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import List from '../../lists/List';
|
||||
|
||||
const Projects = ({ id, name, event }) => {
|
||||
const path = `${id}.items`;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading>{name}</Heading>
|
||||
|
||||
<List
|
||||
path={path}
|
||||
event={event}
|
||||
titlePath="title"
|
||||
subtitlePath="link"
|
||||
textPath="summary"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Projects);
|
||||
@ -42,7 +42,11 @@ const List = ({
|
||||
subtitle={
|
||||
subtitle ||
|
||||
get(x, subtitlePath, '') ||
|
||||
(hasDate && formatDateRange(x))
|
||||
(hasDate &&
|
||||
formatDateRange({
|
||||
startDate: x.startDate,
|
||||
endDate: x.endDate,
|
||||
}))
|
||||
}
|
||||
text={text || get(x, textPath, '')}
|
||||
onEdit={() => handleEdit(x)}
|
||||
|
||||
@ -7,7 +7,7 @@ import SyncIndicator from './SyncIndicator';
|
||||
const RightNavbar = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className="grid grid-cols-1 gap-4 text-primary-400">
|
||||
<div className="grid grid-cols-1 gap-4 text-primary-500">
|
||||
{sections.map((x) => (
|
||||
<SectionIcon
|
||||
key={x.id}
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
import cx from 'classnames';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import { MdSync, MdSyncDisabled } from 'react-icons/md';
|
||||
import { MdSync } from 'react-icons/md';
|
||||
import DatabaseContext from '../../../contexts/DatabaseContext';
|
||||
|
||||
const SyncIndicator = () => {
|
||||
const { isOffline, isUpdating } = useContext(DatabaseContext);
|
||||
const { isUpdating } = useContext(DatabaseContext);
|
||||
|
||||
return (
|
||||
<div className="text-4xl">
|
||||
{isOffline ? (
|
||||
<MdSyncDisabled className="text-red-600" />
|
||||
) : (
|
||||
<MdSync className={cx({ spin: isUpdating })} />
|
||||
)}
|
||||
<MdSync className={cx({ spin: isUpdating })} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,36 +1,88 @@
|
||||
import React, { memo, useContext } from 'react';
|
||||
import { MdImportExport } from 'react-icons/md';
|
||||
import { clone } from 'lodash';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Button from '../../../shared/Button';
|
||||
import styles from './Actions.module.css';
|
||||
import Input from '../../../shared/Input';
|
||||
import React, { memo, useContext, useState } from 'react';
|
||||
import { MdImportExport } from 'react-icons/md';
|
||||
import ModalContext from '../../../../contexts/ModalContext';
|
||||
import { useSelector } from '../../../../contexts/ResumeContext';
|
||||
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
|
||||
import UserContext from '../../../../contexts/UserContext';
|
||||
import Button from '../../../shared/Button';
|
||||
import Heading from '../../../shared/Heading';
|
||||
import Input from '../../../shared/Input';
|
||||
import styles from './Actions.module.css';
|
||||
|
||||
const Actions = () => {
|
||||
const [loadDemoText, setLoadDemoText] = useState('Load Demo Data');
|
||||
const [resetText, setResetText] = useState('Reset Everything');
|
||||
const [deleteText, setDeleteText] = useState('Delete Account');
|
||||
|
||||
const state = useSelector();
|
||||
const dispatch = useDispatch();
|
||||
const { emitter, events } = useContext(ModalContext);
|
||||
const { deleteAccount } = useContext(UserContext);
|
||||
|
||||
const handleImport = () => emitter.emit(events.IMPORT_MODAL);
|
||||
|
||||
const handleExportToJson = () => {
|
||||
const backupObj = clone(state);
|
||||
delete backupObj.id;
|
||||
delete backupObj.user;
|
||||
delete backupObj.name;
|
||||
delete backupObj.createdAt;
|
||||
delete backupObj.updatedAt;
|
||||
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.setAttribute(
|
||||
'download',
|
||||
`RxResume_${state.id}_${Date.now()}.json`,
|
||||
);
|
||||
dlAnchor.click();
|
||||
};
|
||||
|
||||
const getSharableUrl = () => {
|
||||
const shareId = state.id.split('-')[0];
|
||||
const shareId = state.id;
|
||||
return `https://rxresu.me/r/${shareId}`;
|
||||
};
|
||||
|
||||
const handleOpenLink = () => {
|
||||
if (typeof window !== `undefined`) {
|
||||
window && window.open(getSharableUrl());
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadDemo = () => {
|
||||
if (loadDemoText === 'Load Demo Data') {
|
||||
setLoadDemoText('Are you sure?');
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ type: 'load_demo_data' });
|
||||
setLoadDemoText('Load Demo Data');
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (resetText === 'Reset Everything') {
|
||||
setResetText('Are you sure?');
|
||||
return;
|
||||
}
|
||||
|
||||
setResetText('Reset Everything');
|
||||
dispatch({ type: 'reset_data' });
|
||||
};
|
||||
|
||||
const handleDeleteAccount = () => {
|
||||
if (deleteText === 'Delete Account') {
|
||||
setDeleteText('Are you sure?');
|
||||
return;
|
||||
}
|
||||
|
||||
setDeleteText('Buh bye! :(');
|
||||
setTimeout(() => {
|
||||
deleteAccount();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Heading>Actions</Heading>
|
||||
@ -79,7 +131,11 @@ const Actions = () => {
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<Input type="action" value={getSharableUrl()} onClick={() => {}} />
|
||||
<Input
|
||||
type="action"
|
||||
value={getSharableUrl()}
|
||||
onClick={handleOpenLink}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -92,12 +148,25 @@ const Actions = () => {
|
||||
</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<Button>Load Demo Data</Button>
|
||||
<Button onClick={handleLoadDemo}>{loadDemoText}</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>Delete Account</h5>
|
||||
<h5>Reset Everything</h5>
|
||||
|
||||
<p className="leading-loose">
|
||||
Feels like you made too many mistakes? No worries, clear everything
|
||||
with just one click, but be careful if there are no backups.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<Button onClick={handleReset}>{resetText}</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.container}>
|
||||
<h5>Danger Zone</h5>
|
||||
|
||||
<p className="leading-loose">
|
||||
If you would like to delete your account and erase all your resumes,
|
||||
@ -106,7 +175,9 @@ const Actions = () => {
|
||||
</p>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<Button isDelete>Delete Account</Button>
|
||||
<Button isDelete onClick={handleDeleteAccount}>
|
||||
{deleteText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -48,7 +48,7 @@ const Layout = () => {
|
||||
<section>
|
||||
<Heading>Layout</Heading>
|
||||
|
||||
<p>
|
||||
<p className="leading-loose">
|
||||
This template supports {blocks.length} blocks. You can re-order or move
|
||||
sections by dragging/dropping the section names across lists.
|
||||
</p>
|
||||
|
||||
@ -32,7 +32,9 @@ const Hero = () => {
|
||||
Go to App
|
||||
</Button>
|
||||
) : (
|
||||
<Button title="Login" onClick={handleLogin} isLoading={loading} />
|
||||
<Button onClick={handleLogin} isLoading={loading}>
|
||||
Login
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
outline
|
||||
|
||||
@ -3,7 +3,7 @@ import React, { memo, useContext } from 'react';
|
||||
import UserContext from '../../contexts/UserContext';
|
||||
import LoadingScreen from './LoadingScreen';
|
||||
|
||||
const PrivateRoute = ({ component: Component, location, ...props }) => {
|
||||
const PrivateRoute = ({ component: Component, ...props }) => {
|
||||
const { user, loading } = useContext(UserContext);
|
||||
|
||||
if (loading) {
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
.container {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
.container label input,
|
||||
.container label textarea,
|
||||
.container label select {
|
||||
@apply py-3 px-4 rounded text-primary-900 bg-primary-200 border border-transparent appearance-none;
|
||||
@apply w-full py-3 px-4 rounded text-primary-900 bg-primary-200 border border-transparent appearance-none;
|
||||
}
|
||||
|
||||
.container label input::placeholder,
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
import { Field } from 'formik';
|
||||
import React, { memo } from 'react';
|
||||
import { MdAdd } from 'react-icons/md';
|
||||
import { getFieldProps, handleKeyUp } from '../../utils';
|
||||
import Input from './Input';
|
||||
|
||||
const InputArray = ({ formik, schema, helpers, label, path, placeholder }) => {
|
||||
const handleClickAdd = () => {
|
||||
formik.values.temp && helpers.push(formik.values.temp);
|
||||
formik.setFieldValue('temp', '');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="col-span-2">
|
||||
<label>
|
||||
<span>{label}</span>
|
||||
|
||||
{formik.values[path] &&
|
||||
formik.values[path].map((x, i) => (
|
||||
<Field key={i} name={`${path}.${i}`}>
|
||||
{({ field, meta }) => (
|
||||
<Input
|
||||
className="my-1"
|
||||
onClick={() => helpers.remove(i)}
|
||||
{...field}
|
||||
{...meta}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
))}
|
||||
|
||||
<div className="flex items-center">
|
||||
<Input
|
||||
placeholder={placeholder}
|
||||
{...getFieldProps(formik, schema, 'temp')}
|
||||
/>
|
||||
<MdAdd
|
||||
size="18px"
|
||||
tabIndex="0"
|
||||
className="mx-4 cursor-pointer opacity-50 hover:opacity-75"
|
||||
onKeyUp={(e) => handleKeyUp(e, handleClickAdd)}
|
||||
onClick={handleClickAdd}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(InputArray);
|
||||
@ -1,3 +1,4 @@
|
||||
import { Tooltip } from '@material-ui/core';
|
||||
import React, { memo, useContext, useRef } from 'react';
|
||||
import { MdFileUpload } from 'react-icons/md';
|
||||
import StorageContext from '../../contexts/StorageContext';
|
||||
@ -20,27 +21,29 @@ const PhotoUpload = () => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
className={styles.circle}
|
||||
onClick={handleIconClick}
|
||||
onKeyUp={(e) => handleKeyUp(e, handleIconClick)}
|
||||
>
|
||||
<MdFileUpload size="22px" />
|
||||
<input
|
||||
name="file"
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
className="hidden"
|
||||
onChange={handleImageUpload}
|
||||
/>
|
||||
</div>
|
||||
<Tooltip title="Upload Photograph" placement="right-start">
|
||||
<div
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
className={styles.circle}
|
||||
onClick={handleIconClick}
|
||||
onKeyUp={(e) => handleKeyUp(e, handleIconClick)}
|
||||
>
|
||||
<MdFileUpload size="22px" />
|
||||
<input
|
||||
name="file"
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
className="hidden"
|
||||
onChange={handleImageUpload}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<Input
|
||||
name="photograph"
|
||||
label="Photograph"
|
||||
className="pl-6"
|
||||
className="pl-6 w-full"
|
||||
path="profile.photograph"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Tooltip } from '@material-ui/core';
|
||||
import React, { memo, useEffect } from 'react';
|
||||
import { Link, scrollSpy } from 'react-scroll';
|
||||
import styles from './SectionIcon.module.css';
|
||||
|
||||
const SectionIcon = ({ section, containerId, tooltipPlacement }) => {
|
||||
const { id, name, icon: Icon } = section;
|
||||
@ -19,7 +20,7 @@ const SectionIcon = ({ section, containerId, tooltipPlacement }) => {
|
||||
duration={500}
|
||||
containerId={containerId}
|
||||
activeClass="text-primary-900"
|
||||
className="py-2 cursor-pointer focus:outline-none focus:text-primary-900 hover:text-primary-900"
|
||||
className={styles.icon}
|
||||
>
|
||||
<Icon size="18px" />
|
||||
</Link>
|
||||
|
||||
11
src/components/shared/SectionIcon.module.css
Normal file
11
src/components/shared/SectionIcon.module.css
Normal file
@ -0,0 +1,11 @@
|
||||
.icon {
|
||||
@apply py-2 cursor-pointer;
|
||||
}
|
||||
|
||||
.icon:focus {
|
||||
@apply outline-none text-primary-900;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
@apply text-primary-900;
|
||||
}
|
||||
@ -4,6 +4,7 @@ const ModalEvents = {
|
||||
SOCIAL_MODAL: 'social_modal',
|
||||
WORK_MODAL: 'work_modal',
|
||||
EDUCATION_MODAL: 'education_modal',
|
||||
PROJECT_MODAL: 'project_modal',
|
||||
AWARD_MODAL: 'award_modal',
|
||||
CERTIFICATION_MODAL: 'certification_modal',
|
||||
SKILL_MODAL: 'skill_modal',
|
||||
|
||||
@ -1,19 +1,13 @@
|
||||
import firebase from 'gatsby-plugin-firebase';
|
||||
import { debounce } from 'lodash';
|
||||
import React, {
|
||||
createContext,
|
||||
memo,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import UserContext from './UserContext';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import React, { createContext, memo, useContext, useState } from 'react';
|
||||
import initialState from '../data/initialState';
|
||||
import UserContext from './UserContext';
|
||||
|
||||
const DEBOUNCE_WAIT_TIME = 4000;
|
||||
|
||||
const defaultState = {
|
||||
isOffline: false,
|
||||
isUpdating: false,
|
||||
createResume: () => {},
|
||||
deleteResume: () => {},
|
||||
@ -26,28 +20,26 @@ const defaultState = {
|
||||
const DatabaseContext = createContext(defaultState);
|
||||
|
||||
const DatabaseProvider = ({ children }) => {
|
||||
const [resumeId, setResumeId] = useState(false);
|
||||
const [isOffline, setOffline] = useState(false);
|
||||
const dictionary = 'abcdefghijklmnopqrstuvwxyz1234567890'.split('');
|
||||
const uuid = new ShortUniqueId({ dictionary });
|
||||
|
||||
const [isUpdating, setUpdating] = useState(false);
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
useEffect(() => {
|
||||
const connectedRef = firebase.database().ref('.info/connected');
|
||||
connectedRef.on('value', (snapshot) => {
|
||||
snapshot.val() === true ? setOffline(false) : setOffline(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getResume = async (id) => {
|
||||
setResumeId(id);
|
||||
const snapshot = await firebase
|
||||
.database()
|
||||
.ref(`users/${user.uid}/resumes/${id}`)
|
||||
.once('value');
|
||||
return snapshot.val();
|
||||
try {
|
||||
const snapshot = await firebase
|
||||
.database()
|
||||
.ref(`resumes/${id}`)
|
||||
.once('value');
|
||||
return snapshot.val();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const createResume = ({ id, name }) => {
|
||||
const createResume = ({ name }) => {
|
||||
const id = uuid();
|
||||
const createdAt = firebase.database.ServerValue.TIMESTAMP;
|
||||
|
||||
let firstName;
|
||||
@ -57,21 +49,21 @@ const DatabaseProvider = ({ children }) => {
|
||||
[firstName, lastName] = user.displayName.split(' ');
|
||||
}
|
||||
|
||||
firebase
|
||||
.database()
|
||||
.ref(`users/${user.uid}/resumes/${id}`)
|
||||
.set({
|
||||
...initialState,
|
||||
id,
|
||||
name,
|
||||
profile: {
|
||||
...initialState.profile,
|
||||
firstName: firstName || '',
|
||||
lastName: lastName || '',
|
||||
},
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
});
|
||||
const resume = {
|
||||
...initialState,
|
||||
id,
|
||||
name,
|
||||
user: user.uid,
|
||||
profile: {
|
||||
...initialState.profile,
|
||||
firstName: firstName || '',
|
||||
lastName: lastName || '',
|
||||
},
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
};
|
||||
|
||||
firebase.database().ref(`resumes/${id}`).set(resume);
|
||||
};
|
||||
|
||||
const updateResume = async (resume) => {
|
||||
@ -79,7 +71,7 @@ const DatabaseProvider = ({ children }) => {
|
||||
|
||||
await firebase
|
||||
.database()
|
||||
.ref(`users/${user.uid}/resumes/${resumeId}`)
|
||||
.ref(`resumes/${resume.id}`)
|
||||
.update({
|
||||
...resume,
|
||||
updatedAt: firebase.database.ServerValue.TIMESTAMP,
|
||||
@ -90,14 +82,18 @@ const DatabaseProvider = ({ children }) => {
|
||||
|
||||
const debouncedUpdateResume = debounce(updateResume, DEBOUNCE_WAIT_TIME);
|
||||
|
||||
const deleteResume = (id) => {
|
||||
firebase.database().ref(`users/${user.uid}/resumes/${id}`).remove();
|
||||
const deleteResume = async (id) => {
|
||||
await firebase
|
||||
.storage()
|
||||
.ref(`/users/${user.uid}/photographs/${id}`)
|
||||
.delete();
|
||||
|
||||
await firebase.database().ref(`/resumes/${id}`).remove();
|
||||
};
|
||||
|
||||
return (
|
||||
<DatabaseContext.Provider
|
||||
value={{
|
||||
isOffline,
|
||||
isUpdating,
|
||||
getResume,
|
||||
createResume,
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
flatten,
|
||||
concat,
|
||||
times,
|
||||
merge,
|
||||
} from 'lodash';
|
||||
import React, {
|
||||
createContext,
|
||||
@ -18,6 +19,7 @@ import React, {
|
||||
} from 'react';
|
||||
import DatabaseContext from './DatabaseContext';
|
||||
import initialState from '../data/initialState';
|
||||
import demoState from '../data/demoState.json';
|
||||
|
||||
const ResumeContext = createContext({});
|
||||
|
||||
@ -117,7 +119,20 @@ const ResumeProvider = ({ children }) => {
|
||||
return newState;
|
||||
|
||||
case 'set_data':
|
||||
return payload;
|
||||
newState = payload;
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'reset_data':
|
||||
newState = merge(clone(state), initialState);
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
case 'load_demo_data':
|
||||
newState = merge(clone(state), demoState);
|
||||
newState.metadata.layout = demoState.metadata.layout;
|
||||
debouncedUpdateResume(newState);
|
||||
return newState;
|
||||
|
||||
default:
|
||||
throw new Error();
|
||||
|
||||
@ -20,6 +20,10 @@ const StorageProvider = ({ children }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const uploadPhotograph = async (file) => {
|
||||
if (!file) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isFileImage(file)) {
|
||||
toast.error(
|
||||
"You tried to upload a file that was not an image. That won't look good on your resume. Please try again.",
|
||||
@ -27,6 +31,13 @@ const StorageProvider = ({ children }) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (file.size > 2097152) {
|
||||
toast.error(
|
||||
"Your image seems to be bigger than 2 MB. That's way too much. Maybe consider reducing it's size?",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const uploadTask = firebase
|
||||
.storage()
|
||||
.ref(`/users/${user.uid}/photographs/${id}`)
|
||||
|
||||
@ -28,7 +28,7 @@ const COLOR_CONFIG = {
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
darkMode: false,
|
||||
darkMode: true,
|
||||
toggleDarkMode: () => {},
|
||||
};
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { navigate } from '@reach/router';
|
||||
import firebase from 'gatsby-plugin-firebase';
|
||||
import { pick } from 'lodash';
|
||||
import React, { createContext, memo, useEffect, useState } from 'react';
|
||||
@ -16,6 +17,7 @@ const defaultState = {
|
||||
user: defaultUser,
|
||||
logout: async () => {},
|
||||
loginWithGoogle: async () => {},
|
||||
deleteAccount: async () => {},
|
||||
};
|
||||
|
||||
const UserContext = createContext(defaultState);
|
||||
@ -49,16 +51,33 @@ const UserProvider = ({ children }) => {
|
||||
const provider = new firebase.auth.GoogleAuthProvider();
|
||||
|
||||
try {
|
||||
await firebase.auth().signInWithPopup(provider);
|
||||
return await firebase.auth().signInWithPopup(provider);
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
await firebase.auth().signOut();
|
||||
const logout = () => {
|
||||
firebase.auth().signOut();
|
||||
localStorage.removeItem('user');
|
||||
setUser(null);
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
const deleteAccount = async () => {
|
||||
const { currentUser } = firebase.auth();
|
||||
try {
|
||||
await currentUser.delete();
|
||||
} catch (e) {
|
||||
toast.error(e.message);
|
||||
await loginWithGoogle();
|
||||
await currentUser.delete();
|
||||
} finally {
|
||||
logout();
|
||||
toast(
|
||||
"It's sad to see you go, but we respect your privacy. All your data has been deleted successfully. Hope to see you again soon!",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -68,6 +87,7 @@ const UserProvider = ({ children }) => {
|
||||
logout,
|
||||
loading,
|
||||
loginWithGoogle,
|
||||
deleteAccount,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
256
src/data/demoState.json
Normal file
256
src/data/demoState.json
Normal file
@ -0,0 +1,256 @@
|
||||
{
|
||||
"awards": {
|
||||
"heading": "Awards",
|
||||
"items": [
|
||||
{
|
||||
"awarder": "Google",
|
||||
"date": "2019-04-01",
|
||||
"id": "6f857f2b-6312-4a0d-907d-2e17991954eb",
|
||||
"summary": "",
|
||||
"title": "International Flutter Hackathon '19"
|
||||
},
|
||||
{
|
||||
"awarder": "Venturesity",
|
||||
"date": "2016-06-01",
|
||||
"id": "f6efa3f9-9741-4e36-a538-ba0d9779bc61",
|
||||
"summary": "",
|
||||
"title": "Venturesity Banyan Hack '16"
|
||||
}
|
||||
],
|
||||
"visible": true
|
||||
},
|
||||
"certifications": {
|
||||
"heading": "Certifications",
|
||||
"items": [
|
||||
{
|
||||
"date": "2018-02-01",
|
||||
"id": "d2ec12bc-7876-46bc-afd4-11ae06faf3bd",
|
||||
"issuer": "Google",
|
||||
"summary": "",
|
||||
"title": "Applied CS with Android"
|
||||
},
|
||||
{
|
||||
"date": "2019-06-01",
|
||||
"id": "f8312288-53ae-4504-a768-4b67aea95926",
|
||||
"issuer": "Udemy",
|
||||
"summary": "",
|
||||
"title": "Data Science & Machine Learning using Python"
|
||||
}
|
||||
],
|
||||
"visible": true
|
||||
},
|
||||
"education": {
|
||||
"heading": "Education",
|
||||
"items": [
|
||||
{
|
||||
"degree": "Bachelor's Degree",
|
||||
"endDate": "2018-04-01",
|
||||
"field": "Computer Science & Engineering",
|
||||
"gpa": "9.2",
|
||||
"id": "c42e2a5a-3f0d-497e-838b-ac2019dcf045",
|
||||
"institution": "Dayananda Sagar College of Engineering",
|
||||
"startDate": "2015-04-01",
|
||||
"summary": ""
|
||||
},
|
||||
{
|
||||
"degree": "Diploma",
|
||||
"endDate": "2015-04-01",
|
||||
"field": "Computer Science",
|
||||
"gpa": "9.8",
|
||||
"id": "278490a2-c327-4e83-8be8-adf913a9b36c",
|
||||
"institution": "Dayananda Sagar Institute of Technology",
|
||||
"startDate": "2012-04-01",
|
||||
"summary": ""
|
||||
}
|
||||
],
|
||||
"visible": true
|
||||
},
|
||||
"hobbies": {
|
||||
"heading": "Hobbies",
|
||||
"items": [
|
||||
{ "id": "92c35e3b-6cd7-4cea-b505-61347ec61b68", "name": "Photography" },
|
||||
{
|
||||
"id": "d36f2089-93a9-4f30-a425-3dd81c6b89df",
|
||||
"name": "Playing Badminton"
|
||||
},
|
||||
{
|
||||
"id": "d1da41a9-ae83-48fb-8047-d45ebd869a69",
|
||||
"name": "Working on Personal Projects"
|
||||
}
|
||||
],
|
||||
"visible": true
|
||||
},
|
||||
"languages": {
|
||||
"heading": "Languages",
|
||||
"items": [
|
||||
{
|
||||
"fluency": "Very Fluent",
|
||||
"id": "78d8cf32-84c7-431d-969b-fdf277968026",
|
||||
"name": "English"
|
||||
},
|
||||
{
|
||||
"fluency": "Native Tongue",
|
||||
"id": "9e0bd5ed-b88d-4046-8fb9-ecba54d29924",
|
||||
"name": "Tamil"
|
||||
},
|
||||
{
|
||||
"fluency": "Native Tongue",
|
||||
"id": "cb895aa9-c485-4bf3-a9e3-08e8f219451a",
|
||||
"name": "Kannada"
|
||||
},
|
||||
{
|
||||
"fluency": "Learning on Duolingo",
|
||||
"id": "8fff60fc-0cd6-47e2-b64f-fb249d1af0d1",
|
||||
"name": "German"
|
||||
}
|
||||
],
|
||||
"visible": true
|
||||
},
|
||||
"metadata": {
|
||||
"colors": {
|
||||
"background": "#FFFFFF",
|
||||
"primary": "#009688",
|
||||
"text": "#212121"
|
||||
},
|
||||
"font": "Open Sans",
|
||||
"layout": [
|
||||
["objective", "work", "education", "projects"],
|
||||
["hobbies", "languages"],
|
||||
["skills", "certifications", "awards", "references"]
|
||||
],
|
||||
"template": "onyx"
|
||||
},
|
||||
"objective": {
|
||||
"body": "I'm Amruth Pillai, and as you might have already read, I'm a designer, developer, photographer and a writer. This website was made to showcase all of what I can do and plan to do. Don't judge my writing based on this section though, this is by far my shoddiest work yet.\n \nI got into design because I consider myself a pseudo-perfectionist, if that's even a word? As in, I hate to see things 'not look good'. So I set out on a journey to make products that people use that 'look great', and I'm forever on that path.",
|
||||
"heading": "Objective",
|
||||
"visible": true
|
||||
},
|
||||
"profile": {
|
||||
"address": {
|
||||
"city": "Bangalore, India -",
|
||||
"line1": "#5/A, Banashankari Nivas,",
|
||||
"line2": "Brindavan Layout, Subramanyapura,",
|
||||
"pincode": "560061"
|
||||
},
|
||||
"email": "hello@amruthpillai.com",
|
||||
"firstName": "Amruth",
|
||||
"heading": "Profile",
|
||||
"lastName": "Pillai",
|
||||
"phone": "+91 98453 36113",
|
||||
"photograph": "https://firebasestorage.googleapis.com/v0/b/rx-resume.appspot.com/o/users%2FNriQrOfocnfTtoRIpR3qEtHNxYq1%2Fphotographs%2Fx7vvg8?alt=media&token=99df9c05-f5e1-4360-b1e8-5c13ddd8cd84",
|
||||
"profile": "",
|
||||
"subtitle": "Full Stack Web Developer",
|
||||
"website": "amruthpillai.com"
|
||||
},
|
||||
"projects": {
|
||||
"heading": "Projects",
|
||||
"items": [
|
||||
{
|
||||
"date": "2020-07-01",
|
||||
"id": "c768dcca-90f5-4242-a608-6759b4f667fb",
|
||||
"link": "https://github.com/AmruthPillai/Reactive-Resume",
|
||||
"summary": "Reactive Resume, a free and open-source resume builder that works for you. A few of the important features that make it awesome are minimalistic UI/UX, extensive customizability, portability, regularly updated templates, etc.\n\nFor more information, check out [rxresu.me](https://github.com/AmruthPillai/Reactive-Resume)",
|
||||
"title": "Reactive Resume"
|
||||
},
|
||||
{
|
||||
"date": "2020-04-01",
|
||||
"id": "6ca600b1-c21f-4d7b-8431-f7144d537dd3",
|
||||
"link": "https://amruthpillai.com",
|
||||
"summary": "Resume on the Web has been a project that I've been focused on since the early 2014s. I didn't want my information to be displayed on just a sheet of paper that only HRs or Talent Scouts had the privilege of reading, I wanted it to be accessible to everyone. And that's how this project was conceptualized.",
|
||||
"title": "Resume on the Web"
|
||||
}
|
||||
],
|
||||
"visible": true
|
||||
},
|
||||
"public": true,
|
||||
"references": {
|
||||
"heading": "References",
|
||||
"items": [
|
||||
{
|
||||
"email": "willywonka@goldenticket.com",
|
||||
"id": "168339fd-3c4b-4f2f-bd3a-ef184be81700",
|
||||
"name": "Willy Wonka",
|
||||
"phone": "+1 (802) 234-2398",
|
||||
"position": "CEO at Chocolate Factory",
|
||||
"summary": ""
|
||||
},
|
||||
{
|
||||
"email": "elanmusk@nottesla.com",
|
||||
"id": "350465b9-9989-43cc-b97e-4115b8980304",
|
||||
"name": "Elangovan Musk",
|
||||
"phone": "+91 93893 34353",
|
||||
"position": "CEO at Newton Motors",
|
||||
"summary": ""
|
||||
}
|
||||
],
|
||||
"visible": true
|
||||
},
|
||||
"skills": {
|
||||
"heading": "Skills",
|
||||
"items": [
|
||||
{
|
||||
"id": "54e5bceb-d0e9-4f04-98d1-48a34f7cf920",
|
||||
"level": "Advanced",
|
||||
"name": "ReactJS"
|
||||
},
|
||||
{
|
||||
"id": "f0274f62-2252-4cc0-bf12-9e1070942c50",
|
||||
"level": "Advanced",
|
||||
"name": "Angular"
|
||||
},
|
||||
{
|
||||
"id": "689e2852-df1b-4d41-bda8-c41c88196264",
|
||||
"level": "Advanced",
|
||||
"name": "Flutter"
|
||||
},
|
||||
{
|
||||
"id": "3a4f73b1-50c1-4a85-a4b0-2a55dfe5053a",
|
||||
"level": "Novice",
|
||||
"name": "Machine Learning"
|
||||
}
|
||||
],
|
||||
"visible": true
|
||||
},
|
||||
"social": {
|
||||
"heading": "Social",
|
||||
"items": [
|
||||
{
|
||||
"id": "a72107fa-a4a5-407d-9e85-39bdb9c0b11a",
|
||||
"network": "Twitter",
|
||||
"url": "https://pillai.xyz/twitter",
|
||||
"username": "KingOKings"
|
||||
},
|
||||
{
|
||||
"id": "1dd46fdd-b3a3-4786-89ce-2e77c0823aba",
|
||||
"network": "LinkedIn",
|
||||
"url": "https://pillai.xyz/linkedin",
|
||||
"username": "AmruthPillai"
|
||||
}
|
||||
],
|
||||
"visible": true
|
||||
},
|
||||
"work": {
|
||||
"heading": "Work Experience",
|
||||
"items": [
|
||||
{
|
||||
"company": "Postdot Technologies Pvt. Ltd.",
|
||||
"endDate": "",
|
||||
"id": "d7c64937-0cb9-41b1-a3a6-0679c882fe63",
|
||||
"position": "Full Stack Web Developer",
|
||||
"startDate": "2020-06-08",
|
||||
"summary": "Postman is a great tool when trying to dissect RESTful APIs made by others or test ones you have made yourself. It offers a sleek user interface with which to make HTML requests, without the hassle of writing a bunch of code just to test an API's functionality.",
|
||||
"website": "https://postman.com"
|
||||
},
|
||||
{
|
||||
"company": "GoDhiyo Solutions Pvt. Ltd.",
|
||||
"endDate": "2020-04-01",
|
||||
"id": "f5c5dcfe-2a60-4169-a2f1-b305355518ea",
|
||||
"position": "Full Stack Web Developer",
|
||||
"startDate": "2018-07-01",
|
||||
"summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi laoreet volutpat lacus, sed tempor lacus eleifend feugiat. Pellentesque molestie libero ac varius finibus. Fusce convallis, arcu sit amet lacinia vehicula, nisl justo egestas tortor.\n\n- In vestibulum eros a enim rhoncus\n- Phasellus ullamcorper magna quis est sagittis",
|
||||
"website": "https://dhiyo.ai"
|
||||
}
|
||||
],
|
||||
"visible": true
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import leftSections from './leftSections';
|
||||
|
||||
const initialState = {
|
||||
id: '',
|
||||
profile: {
|
||||
heading: 'Profile',
|
||||
photograph: '',
|
||||
@ -38,6 +37,11 @@ const initialState = {
|
||||
visible: true,
|
||||
items: [],
|
||||
},
|
||||
projects: {
|
||||
heading: 'Projects',
|
||||
visible: true,
|
||||
items: [],
|
||||
},
|
||||
awards: {
|
||||
heading: 'Awards',
|
||||
visible: true,
|
||||
@ -68,7 +72,6 @@ const initialState = {
|
||||
visible: true,
|
||||
items: [],
|
||||
},
|
||||
name: '',
|
||||
metadata: {
|
||||
template: 'onyx',
|
||||
font: 'Montserrat',
|
||||
@ -79,7 +82,7 @@ const initialState = {
|
||||
background: '#FFFFFF',
|
||||
},
|
||||
},
|
||||
createdAt: new Date(),
|
||||
public: true,
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { AiFillSafetyCertificate, AiOutlineTwitter } from 'react-icons/ai';
|
||||
import { BsTools } from 'react-icons/bs';
|
||||
import { FaAward, FaUserFriends } from 'react-icons/fa';
|
||||
import { FaAward, FaUserFriends, FaProjectDiagram } from 'react-icons/fa';
|
||||
import {
|
||||
IoLogoGameControllerB,
|
||||
IoMdBriefcase,
|
||||
@ -40,6 +40,12 @@ export default [
|
||||
icon: MdSchool,
|
||||
event: ModalEvents.EDUCATION_MODAL,
|
||||
},
|
||||
{
|
||||
id: 'projects',
|
||||
name: 'Projects',
|
||||
icon: FaProjectDiagram,
|
||||
event: ModalEvents.PROJECT_MODAL,
|
||||
},
|
||||
{
|
||||
id: 'awards',
|
||||
name: 'Awards',
|
||||
|
||||
@ -60,7 +60,7 @@ const AuthModal = () => {
|
||||
state={[open, setOpen]}
|
||||
action={user ? loggedInAction : loggedOutAction}
|
||||
>
|
||||
<p>{getMessage()}</p>
|
||||
<p className="leading-loose">{getMessage()}</p>
|
||||
</BaseModal>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@ import EducationModal from './sections/EducationModal';
|
||||
import HobbyModal from './sections/HobbyModal';
|
||||
import ImportModal from './sections/ImportModal';
|
||||
import LanguageModal from './sections/LanguageModal';
|
||||
import ProjectModal from './sections/ProjectModal';
|
||||
import ReferenceModal from './sections/ReferenceModal';
|
||||
import SkillModal from './sections/SkillModal';
|
||||
import SocialModal from './sections/SocialModal';
|
||||
@ -20,6 +21,7 @@ const ModalRegistrar = () => {
|
||||
<SocialModal />
|
||||
<WorkModal />
|
||||
<EducationModal />
|
||||
<ProjectModal />
|
||||
<AwardModal />
|
||||
<CertificateModal />
|
||||
<SkillModal />
|
||||
|
||||
@ -55,7 +55,7 @@ const ResumeModal = () => {
|
||||
{...getFieldProps(formik, schema, 'name')}
|
||||
/>
|
||||
|
||||
<p>
|
||||
<p className="leading-loose">
|
||||
You are going to be creating a new resume from scratch, but first,
|
||||
let's give it a name. This can be the name of the role you want
|
||||
to apply for, or if you're making a resume for a friend, you
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { FieldArray, Formik } from 'formik';
|
||||
import { Formik } from 'formik';
|
||||
import React, { memo } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import Input from '../../components/shared/Input';
|
||||
import InputArray from '../../components/shared/InputArray';
|
||||
import ModalEvents from '../../constants/ModalEvents';
|
||||
import { getFieldProps } from '../../utils';
|
||||
import DataModal from '../DataModal';
|
||||
@ -14,8 +13,7 @@ const initialValues = {
|
||||
gpa: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
courses: [],
|
||||
temp: '',
|
||||
summary: '',
|
||||
};
|
||||
|
||||
const schema = Yup.object().shape({
|
||||
@ -30,8 +28,7 @@ const schema = Yup.object().shape({
|
||||
startDate &&
|
||||
yupSchema.min(startDate, 'End Date must be later than Start Date'),
|
||||
),
|
||||
courses: Yup.array().of(Yup.string().required('This is a required field.')),
|
||||
temp: Yup.string().ensure(),
|
||||
summary: Yup.string().min(10, 'Please enter at least 10 characters.'),
|
||||
});
|
||||
|
||||
const EducationModal = () => {
|
||||
@ -88,18 +85,11 @@ const EducationModal = () => {
|
||||
{...getFieldProps(formik, schema, 'endDate')}
|
||||
/>
|
||||
|
||||
<FieldArray
|
||||
name="courses"
|
||||
render={(helpers) => (
|
||||
<InputArray
|
||||
formik={formik}
|
||||
schema={schema}
|
||||
helpers={helpers}
|
||||
label="Courses"
|
||||
path="courses"
|
||||
placeholder="Data Structures & Algortihms"
|
||||
/>
|
||||
)}
|
||||
<Input
|
||||
type="textarea"
|
||||
label="Summary"
|
||||
className="col-span-2"
|
||||
{...getFieldProps(formik, schema, 'summary')}
|
||||
/>
|
||||
</div>
|
||||
</DataModal>
|
||||
|
||||
69
src/modals/sections/ProjectModal.js
Normal file
69
src/modals/sections/ProjectModal.js
Normal file
@ -0,0 +1,69 @@
|
||||
import { Formik } from 'formik';
|
||||
import React, { memo } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import Input from '../../components/shared/Input';
|
||||
import ModalEvents from '../../constants/ModalEvents';
|
||||
import { getFieldProps } from '../../utils';
|
||||
import DataModal from '../DataModal';
|
||||
|
||||
const initialValues = {
|
||||
title: '',
|
||||
link: '',
|
||||
date: '',
|
||||
summary: '',
|
||||
};
|
||||
|
||||
const schema = Yup.object().shape({
|
||||
title: Yup.string().required('This is a required field.'),
|
||||
link: Yup.string().url('Must be a valid URL'),
|
||||
date: Yup.date().max(new Date()),
|
||||
summary: Yup.string(),
|
||||
});
|
||||
|
||||
const ProjectModal = () => {
|
||||
return (
|
||||
<Formik
|
||||
validateOnBlur
|
||||
initialValues={initialValues}
|
||||
validationSchema={schema}
|
||||
>
|
||||
{(formik) => (
|
||||
<DataModal
|
||||
name="Project"
|
||||
path="projects.items"
|
||||
event={ModalEvents.PROJECT_MODAL}
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-8">
|
||||
<Input
|
||||
label="Title"
|
||||
className="col-span-2"
|
||||
placeholder="Reactive Resume"
|
||||
{...getFieldProps(formik, schema, 'title')}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Link"
|
||||
placeholder="https://github.com/AmruthPillai/Reactive-Resume"
|
||||
{...getFieldProps(formik, schema, 'link')}
|
||||
/>
|
||||
|
||||
<Input
|
||||
type="date"
|
||||
label="Date"
|
||||
{...getFieldProps(formik, schema, 'date')}
|
||||
/>
|
||||
|
||||
<Input
|
||||
type="textarea"
|
||||
label="Summary"
|
||||
className="col-span-2"
|
||||
{...getFieldProps(formik, schema, 'summary')}
|
||||
/>
|
||||
</div>
|
||||
</DataModal>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ProjectModal);
|
||||
@ -1,8 +1,7 @@
|
||||
import { FieldArray, Formik } from 'formik';
|
||||
import { Formik } from 'formik';
|
||||
import React, { memo } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import Input from '../../components/shared/Input';
|
||||
import InputArray from '../../components/shared/InputArray';
|
||||
import ModalEvents from '../../constants/ModalEvents';
|
||||
import { getFieldProps } from '../../utils';
|
||||
import DataModal from '../DataModal';
|
||||
@ -14,8 +13,6 @@ const initialValues = {
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
summary: '',
|
||||
highlights: [],
|
||||
temp: '',
|
||||
};
|
||||
|
||||
const schema = Yup.object().shape({
|
||||
@ -30,10 +27,6 @@ const schema = Yup.object().shape({
|
||||
yupSchema.min(startDate, 'End Date must be later than Start Date'),
|
||||
),
|
||||
summary: Yup.string().min(10, 'Please enter at least 10 characters.'),
|
||||
highlights: Yup.array().of(
|
||||
Yup.string().required('This is a required field.'),
|
||||
),
|
||||
temp: Yup.string().ensure(),
|
||||
});
|
||||
|
||||
const WorkModal = () => {
|
||||
@ -89,20 +82,6 @@ const WorkModal = () => {
|
||||
className="col-span-2"
|
||||
{...getFieldProps(formik, schema, 'summary')}
|
||||
/>
|
||||
|
||||
<FieldArray
|
||||
name="highlights"
|
||||
render={(helpers) => (
|
||||
<InputArray
|
||||
formik={formik}
|
||||
schema={schema}
|
||||
helpers={helpers}
|
||||
label="Highlights"
|
||||
path="highlights"
|
||||
placeholder="Worked passionately in customer service in a high volume restaurant."
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</DataModal>
|
||||
)}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import firebase from 'gatsby-plugin-firebase';
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import CreateResume from '../../components/dashboard/CreateResume';
|
||||
import ResumePreview from '../../components/dashboard/ResumePreview';
|
||||
@ -11,11 +11,24 @@ const Dashboard = ({ user }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const ref = `users/${user.uid}/resumes`;
|
||||
const resumesRef = 'resumes';
|
||||
const socketRef = '/.info/connected';
|
||||
|
||||
firebase
|
||||
.database()
|
||||
.ref(ref)
|
||||
.ref(socketRef)
|
||||
.on('value', (snapshot) => {
|
||||
if (snapshot.val()) {
|
||||
setLoading(false);
|
||||
firebase.database().ref(socketRef).off();
|
||||
}
|
||||
});
|
||||
|
||||
firebase
|
||||
.database()
|
||||
.ref(resumesRef)
|
||||
.orderByChild('user')
|
||||
.equalTo(user.uid)
|
||||
.on('value', (snapshot) => {
|
||||
if (snapshot.val()) {
|
||||
const resumesArr = [];
|
||||
@ -23,13 +36,13 @@ const Dashboard = ({ user }) => {
|
||||
Object.keys(data).forEach((key) => resumesArr.push(data[key]));
|
||||
setResumes(resumesArr);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
firebase
|
||||
.database()
|
||||
.ref(ref)
|
||||
.ref(resumesRef)
|
||||
.orderByChild('user')
|
||||
.equalTo(user.uid)
|
||||
.on('child_removed', (snapshot) => {
|
||||
if (snapshot.val()) {
|
||||
setResumes(resumes.filter((x) => x.id === snapshot.val().id));
|
||||
@ -37,7 +50,7 @@ const Dashboard = ({ user }) => {
|
||||
});
|
||||
|
||||
return () => {
|
||||
firebase.database().ref(ref).off();
|
||||
firebase.database().ref(resumesRef).off();
|
||||
};
|
||||
}, [user]);
|
||||
|
||||
@ -67,4 +80,4 @@ const Dashboard = ({ user }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Dashboard);
|
||||
export default Dashboard;
|
||||
|
||||
@ -65,7 +65,7 @@ const Feature = ({ title, children }) => {
|
||||
return (
|
||||
<div className="mt-16">
|
||||
<h3 className="text-3xl">{title}</h3>
|
||||
<p className="mt-6 text-lg">{children}</p>
|
||||
<p className="mt-6 text-lg leading-loose">{children}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
17
src/pages/r.js
Normal file
17
src/pages/r.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { Redirect, Router } from '@reach/router';
|
||||
import React, { memo } from 'react';
|
||||
import Wrapper from '../components/shared/Wrapper';
|
||||
import ResumeViewer from './r/view';
|
||||
import NotFound from './404';
|
||||
|
||||
const ResumeRouter = () => (
|
||||
<Wrapper>
|
||||
<Router>
|
||||
<Redirect noThrow from="/r" to="/" exact />
|
||||
<ResumeViewer path="r/:id" />
|
||||
<NotFound default />
|
||||
</Router>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export default memo(ResumeRouter);
|
||||
58
src/pages/r/view.js
Normal file
58
src/pages/r/view.js
Normal file
@ -0,0 +1,58 @@
|
||||
import { navigate, Link } from '@reach/router';
|
||||
import React, { memo, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { toast } from 'react-toastify';
|
||||
import LoadingScreen from '../../components/router/LoadingScreen';
|
||||
import DatabaseContext from '../../contexts/DatabaseContext';
|
||||
import Onyx from '../../templates/Onyx';
|
||||
import styles from './view.module.css';
|
||||
|
||||
const ResumeViewer = ({ id }) => {
|
||||
const [resume, setResume] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { getResume } = useContext(DatabaseContext);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await getResume(id);
|
||||
|
||||
if (!data) {
|
||||
navigate('/');
|
||||
toast.error(
|
||||
`The resume you were looking for does not exist anymore... or maybe it never did?`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
setResume(data);
|
||||
return setLoading(false);
|
||||
})();
|
||||
}, [id]);
|
||||
|
||||
return useMemo(() => {
|
||||
if (loading) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Helmet>
|
||||
<title>{resume.name} | Reactive Resume</title>
|
||||
<link rel="canonical" href={`https://rxresu.me/r/${id}`} />
|
||||
</Helmet>
|
||||
<div
|
||||
className={styles.page}
|
||||
style={{ backgroundColor: resume.metadata.colors.background }}
|
||||
>
|
||||
{resume.metadata.template === 'onyx' && <Onyx data={resume} />}
|
||||
</div>
|
||||
|
||||
<p className={styles.footer}>
|
||||
Built with <Link to="/">Reactive Resume</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default memo(ResumeViewer);
|
||||
13
src/pages/r/view.module.css
Normal file
13
src/pages/r/view.module.css
Normal file
@ -0,0 +1,13 @@
|
||||
.container {
|
||||
background-color: #212121;
|
||||
@apply h-screen overflow-scroll col-span-5 flex flex-col items-center;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: 800px;
|
||||
@apply block my-16 rounded shadow-2xl;
|
||||
}
|
||||
|
||||
.footer {
|
||||
@apply mb-16 text-center opacity-50 leading-loose;
|
||||
}
|
||||
@ -6,6 +6,10 @@ body {
|
||||
@apply transition-colors duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
@apply underline;
|
||||
}
|
||||
@ -27,7 +31,7 @@ label {
|
||||
}
|
||||
|
||||
label > span:first-child {
|
||||
@apply mb-1 text-primary-500 font-semibold tracking-wide text-xs uppercase;
|
||||
@apply mb-1 text-primary-600 font-semibold tracking-wide text-xs uppercase;
|
||||
}
|
||||
|
||||
.MuiTooltip-tooltip {
|
||||
@ -47,3 +51,7 @@ label > span:first-child {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.markdown {
|
||||
@apply leading-relaxed whitespace-pre-wrap;
|
||||
}
|
||||
@ -9,6 +9,7 @@ import Heading from './blocks/Heading/HeadingA';
|
||||
import HobbiesA from './blocks/Hobbies/HobbiesA';
|
||||
import LanguagesA from './blocks/Languages/LanguagesA';
|
||||
import ObjectiveA from './blocks/Objective/ObjectiveA';
|
||||
import ProjectsA from './blocks/Projects/ProjectsA';
|
||||
import ReferencesA from './blocks/References/ReferencesA';
|
||||
import SkillsA from './blocks/Skills/SkillsA';
|
||||
import WorkA from './blocks/Work/WorkA';
|
||||
@ -17,6 +18,7 @@ const Blocks = {
|
||||
objective: ObjectiveA,
|
||||
work: WorkA,
|
||||
education: EducationA,
|
||||
projects: ProjectsA,
|
||||
awards: AwardsA,
|
||||
certifications: CertificationsA,
|
||||
skills: SkillsA,
|
||||
@ -35,8 +37,7 @@ const Onyx = ({ data }) => {
|
||||
return (
|
||||
<PageContext.Provider value={{ data, heading: Heading }}>
|
||||
<div
|
||||
id="page"
|
||||
className="p-10"
|
||||
className="p-10 rounded"
|
||||
style={{
|
||||
fontFamily: data.metadata.font,
|
||||
color: data.metadata.colors.text,
|
||||
@ -45,12 +46,14 @@ const Onyx = ({ data }) => {
|
||||
>
|
||||
<div className="grid grid-cols-4 items-center">
|
||||
<div className="col-span-3 flex items-center">
|
||||
<img
|
||||
className="rounded object-cover mr-4"
|
||||
src={data.profile.photograph}
|
||||
alt="Resume Photograph"
|
||||
style={{ width: '120px', height: '120px' }}
|
||||
/>
|
||||
{data.profile.photograph && (
|
||||
<img
|
||||
className="rounded object-cover mr-4"
|
||||
src={data.profile.photograph}
|
||||
alt="Resume Photograph"
|
||||
style={{ width: '120px', height: '120px' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<h1
|
||||
@ -75,18 +78,18 @@ const Onyx = ({ data }) => {
|
||||
</div>
|
||||
|
||||
<hr
|
||||
className="my-6 opacity-25"
|
||||
className="my-5 opacity-25"
|
||||
style={{ borderColor: data.metadata.colors.text }}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 col-gap-8">
|
||||
<div className="grid gap-4">
|
||||
{data.metadata.layout[0] &&
|
||||
data.metadata.layout[0].map((x) => {
|
||||
const Component = Blocks[x];
|
||||
return Component && <Component key={x} />;
|
||||
})}
|
||||
|
||||
<div className="grid grid-cols-3 col-gap-8">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{data.metadata.layout[1] &&
|
||||
data.metadata.layout[1].map((x) => {
|
||||
const Component = Blocks[x];
|
||||
|
||||
@ -2,30 +2,32 @@ import moment from 'moment';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
import { safetyCheck } from '../../../utils';
|
||||
|
||||
const AwardItem = (x) => (
|
||||
<div key={x.id} className="mb-2">
|
||||
<div key={x.id}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.awarder}</p>
|
||||
<span className="text-xs">{x.awarder}</span>
|
||||
</div>
|
||||
|
||||
<h6 className="text-xs font-medium">
|
||||
{moment(x.date).format('MMMM YYYY')}
|
||||
</h6>
|
||||
{x.date && (
|
||||
<h6 className="text-xs font-medium">
|
||||
{moment(x.date).format('MMMM YYYY')}
|
||||
</h6>
|
||||
)}
|
||||
</div>
|
||||
<ReactMarkdown className="mt-2 text-sm" source={x.summary} />
|
||||
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const AwardsA = () => {
|
||||
const { data, heading: Heading } = useContext(PageContext);
|
||||
|
||||
return data.awards.visible && data.awards.items ? (
|
||||
return safetyCheck(data.awards) ? (
|
||||
<div>
|
||||
<Heading>{data.awards.heading}</Heading>
|
||||
{data.awards.items.map(AwardItem)}
|
||||
<div className="grid gap-4">{data.awards.items.map(AwardItem)}</div>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -2,30 +2,34 @@ import moment from 'moment';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
import { safetyCheck } from '../../../utils';
|
||||
|
||||
const CertificationItem = (x) => (
|
||||
<div key={x.id} className="mb-2">
|
||||
<div key={x.id}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.issuer}</p>
|
||||
<span className="text-xs">{x.issuer}</span>
|
||||
</div>
|
||||
|
||||
<h6 className="text-xs font-medium">
|
||||
{moment(x.date).format('MMMM YYYY')}
|
||||
</h6>
|
||||
{x.date && (
|
||||
<h6 className="text-xs font-medium">
|
||||
{moment(x.date).format('MMMM YYYY')}
|
||||
</h6>
|
||||
)}
|
||||
</div>
|
||||
<ReactMarkdown className="mt-2 text-sm" source={x.summary} />
|
||||
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const CertificationsA = () => {
|
||||
const { data, heading: Heading } = useContext(PageContext);
|
||||
|
||||
return data.certifications.visible && data.certifications.items ? (
|
||||
return safetyCheck(data.certifications) ? (
|
||||
<div>
|
||||
<Heading>{data.certifications.heading}</Heading>
|
||||
{data.certifications.items.map(CertificationItem)}
|
||||
<div className="grid gap-4">
|
||||
{data.certifications.items.map(CertificationItem)}
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import { get } from 'lodash';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import { FaCaretRight } from 'react-icons/fa';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
import { safetyCheck } from '../../../utils';
|
||||
import Icons from '../Icons';
|
||||
|
||||
const ContactItem = ({ value, icon, link }) => {
|
||||
@ -9,9 +10,9 @@ const ContactItem = ({ value, icon, link }) => {
|
||||
const Icon = get(Icons, icon.toLowerCase(), FaCaretRight);
|
||||
|
||||
return value ? (
|
||||
<div className="flex items-center my-3">
|
||||
<div className="flex items-center">
|
||||
<Icon
|
||||
size="14px"
|
||||
size="10px"
|
||||
className="mr-2"
|
||||
style={{ color: data.metadata.colors.primary }}
|
||||
/>
|
||||
@ -30,7 +31,7 @@ const ContactA = () => {
|
||||
const { data } = useContext(PageContext);
|
||||
|
||||
return (
|
||||
<div className="col-span-1 text-xs">
|
||||
<div className="text-xs grid gap-2">
|
||||
<ContactItem
|
||||
icon="phone"
|
||||
value={data.profile.phone}
|
||||
@ -47,18 +48,15 @@ const ContactA = () => {
|
||||
link={`mailto:${data.profile.email}`}
|
||||
/>
|
||||
|
||||
{data.social.visible && data.social.items ? (
|
||||
<div>
|
||||
{data.social.items.map((x) => (
|
||||
<ContactItem
|
||||
key={x.id}
|
||||
value={x.username}
|
||||
icon={x.network}
|
||||
link={x.url}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{safetyCheck(data.social) &&
|
||||
data.social.items.map((x) => (
|
||||
<ContactItem
|
||||
key={x.id}
|
||||
value={x.username}
|
||||
icon={x.network}
|
||||
link={x.url}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,40 +1,39 @@
|
||||
import React, { memo, useContext } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
import { formatDateRange } from '../../../utils';
|
||||
import { formatDateRange, safetyCheck } from '../../../utils';
|
||||
|
||||
const EducationItem = (x) => (
|
||||
<div key={x.id} className="mb-2">
|
||||
<div key={x.id}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<h6 className="font-semibold">{x.institution}</h6>
|
||||
<span className="text-xs">
|
||||
<strong>{x.degree}</strong> {x.field}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<span className="text-xs font-medium">
|
||||
({formatDateRange({ startDate: x.startDate, endDate: x.endDate })})
|
||||
</span>
|
||||
<h6 className="text-sm font-medium">{x.gpa}</h6>
|
||||
{x.startDate && (
|
||||
<h6 className="text-xs font-medium">
|
||||
({formatDateRange({ startDate: x.startDate, endDate: x.endDate })})
|
||||
</h6>
|
||||
)}
|
||||
<span className="text-sm font-medium">{x.gpa}</span>
|
||||
</div>
|
||||
</div>
|
||||
{x.courses && (
|
||||
<ul className="mt-2 text-sm list-disc list-inside">
|
||||
{x.courses.map((y) => (
|
||||
<li key={y}>{y}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const EducationA = () => {
|
||||
const { data, heading: Heading } = useContext(PageContext);
|
||||
|
||||
return data.education.visible && data.education.items ? (
|
||||
return safetyCheck(data.education) ? (
|
||||
<div>
|
||||
<Heading>{data.education.heading}</Heading>
|
||||
{data.education.items.map(EducationItem)}
|
||||
<div className="grid gap-4">
|
||||
{data.education.items.map(EducationItem)}
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useContext, memo } from 'react';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
|
||||
const HeadingA = ({ children }) => {
|
||||
@ -6,7 +6,7 @@ const HeadingA = ({ children }) => {
|
||||
|
||||
return (
|
||||
<h6
|
||||
className="text-xs font-bold uppercase mt-4 mb-1"
|
||||
className="text-xs font-bold uppercase mb-1"
|
||||
style={{ color: data.metadata.colors.primary }}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import React, { memo, useContext } from 'react';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
import { safetyCheck } from '../../../utils';
|
||||
|
||||
const HobbyA = (x) => (
|
||||
<div key={x.id} className="mb-2">
|
||||
<div key={x.id}>
|
||||
<h6 className="font-semibold">{x.name}</h6>
|
||||
</div>
|
||||
);
|
||||
@ -10,10 +11,10 @@ const HobbyA = (x) => (
|
||||
const HobbiesA = () => {
|
||||
const { data, heading: Heading } = useContext(PageContext);
|
||||
|
||||
return data.hobbies.visible && data.hobbies.items ? (
|
||||
return safetyCheck(data.hobbies) ? (
|
||||
<div>
|
||||
<Heading>{data.hobbies.heading}</Heading>
|
||||
{data.hobbies.items.map(HobbyA)}
|
||||
<div className="grid gap-2">{data.hobbies.items.map(HobbyA)}</div>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -1,20 +1,23 @@
|
||||
import React, { memo, useContext } from 'react';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
import { safetyCheck } from '../../../utils';
|
||||
|
||||
const LanguageItem = (x) => (
|
||||
<div key={x.id} className="mb-2">
|
||||
<div key={x.id} className="flex flex-col">
|
||||
<h6 className="font-semibold">{x.name}</h6>
|
||||
<p className="text-xs">{x.fluency}</p>
|
||||
<span className="text-xs">{x.fluency}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const LanguagesA = () => {
|
||||
const { data, heading: Heading } = useContext(PageContext);
|
||||
|
||||
return data.languages.visible && data.languages.items ? (
|
||||
return safetyCheck(data.languages) ? (
|
||||
<div>
|
||||
<Heading>{data.languages.heading}</Heading>
|
||||
{data.languages.items.map(LanguageItem)}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{data.languages.items.map(LanguageItem)}
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
import React, { useContext, memo } from 'react';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
import { safetyCheck } from '../../../utils';
|
||||
|
||||
const ObjectiveA = () => {
|
||||
const { data, heading: Heading } = useContext(PageContext);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Heading>{data.objective.heading}</Heading>
|
||||
<ReactMarkdown className="text-sm" source={data.objective.body} />
|
||||
</div>
|
||||
safetyCheck(data.objective, 'body') && (
|
||||
<div>
|
||||
<Heading>{data.objective.heading}</Heading>
|
||||
<ReactMarkdown
|
||||
className="markdown text-sm"
|
||||
source={data.objective.body}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
39
src/templates/blocks/Projects/ProjectsA.js
Normal file
39
src/templates/blocks/Projects/ProjectsA.js
Normal file
@ -0,0 +1,39 @@
|
||||
import moment from 'moment';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
import { safetyCheck } from '../../../utils';
|
||||
|
||||
const ProjectItem = (x) => (
|
||||
<div key={x.id}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex flex-col">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
{x.link && (
|
||||
<a href={x.link} className="text-xs">
|
||||
{x.link}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
{x.date && (
|
||||
<h6 className="text-xs font-medium">
|
||||
{moment(x.date).format('MMMM YYYY')}
|
||||
</h6>
|
||||
)}
|
||||
</div>
|
||||
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const ProjectsA = () => {
|
||||
const { data, heading: Heading } = useContext(PageContext);
|
||||
|
||||
return safetyCheck(data.projects) ? (
|
||||
<div>
|
||||
<Heading>{data.projects.heading}</Heading>
|
||||
<div className="grid gap-4">{data.projects.items.map(ProjectItem)}</div>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default memo(ProjectsA);
|
||||
@ -1,24 +1,25 @@
|
||||
import React, { memo, useContext } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
import { safetyCheck } from '../../../utils';
|
||||
|
||||
const ReferenceItem = (x) => (
|
||||
<div key={x.id} className="mb-2">
|
||||
<div key={x.id} className="flex flex-col">
|
||||
<h6 className="font-semibold">{x.name}</h6>
|
||||
<p className="text-xs">{x.position}</p>
|
||||
<p className="text-xs">{x.phone}</p>
|
||||
<p className="text-xs">{x.email}</p>
|
||||
<ReactMarkdown className="mt-2 text-sm" source={x.summary} />
|
||||
<span className="text-xs">{x.position}</span>
|
||||
<span className="text-xs">{x.phone}</span>
|
||||
<span className="text-xs">{x.email}</span>
|
||||
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const ReferencesA = () => {
|
||||
const { data, heading: Heading } = useContext(PageContext);
|
||||
|
||||
return data.references.visible && data.references.items ? (
|
||||
return safetyCheck(data.references) ? (
|
||||
<div>
|
||||
<Heading>{data.references.heading}</Heading>
|
||||
<div className="grid grid-cols-3 col-gap-8">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{data.references.items.map(ReferenceItem)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,20 +1,23 @@
|
||||
import React, { memo, useContext } from 'react';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
import { safetyCheck } from '../../../utils';
|
||||
|
||||
const SkillItem = (x) => (
|
||||
<div key={x.id} className="mb-2">
|
||||
<div key={x.id} className="flex flex-col">
|
||||
<h6 className="font-semibold">{x.name}</h6>
|
||||
<p className="text-xs">{x.level}</p>
|
||||
<span className="text-xs">{x.level}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const SkillsA = () => {
|
||||
const { data, heading: Heading } = useContext(PageContext);
|
||||
|
||||
return data.skills.visible && data.skills.items ? (
|
||||
return safetyCheck(data.skills) ? (
|
||||
<div>
|
||||
<Heading>{data.skills.heading}</Heading>
|
||||
{data.skills.items.map(SkillItem)}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{data.skills.items.map(SkillItem)}
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -1,37 +1,32 @@
|
||||
import React, { useContext, memo } from 'react';
|
||||
import React, { memo, useContext } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import PageContext from '../../../contexts/PageContext';
|
||||
import { formatDateRange } from '../../../utils';
|
||||
import { formatDateRange, safetyCheck } from '../../../utils';
|
||||
|
||||
const WorkItem = (x) => (
|
||||
<div key={x.id} className="mb-4 last:mb-0">
|
||||
<div key={x.id}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<h6 className="font-semibold">{x.company}</h6>
|
||||
<span className="text-xs">{x.position}</span>
|
||||
</div>
|
||||
<span className="text-xs font-medium">
|
||||
({formatDateRange({ startDate: x.startDate, endDate: x.endDate })})
|
||||
</span>
|
||||
{x.startDate && (
|
||||
<h6 className="text-xs font-medium">
|
||||
({formatDateRange({ startDate: x.startDate, endDate: x.endDate })})
|
||||
</h6>
|
||||
)}
|
||||
</div>
|
||||
<ReactMarkdown className="mt-2 text-sm" source={x.summary} />
|
||||
{x.highlights && (
|
||||
<ul className="mt-2 text-sm list-disc list-inside">
|
||||
{x.highlights.map((y) => (
|
||||
<li key={y}>{y}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<ReactMarkdown className="markdown mt-2 text-sm" source={x.summary} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const WorkA = () => {
|
||||
const { data, heading: Heading } = useContext(PageContext);
|
||||
|
||||
return data.work.visible && data.work.items ? (
|
||||
return safetyCheck(data.work) ? (
|
||||
<div>
|
||||
<Heading>{data.work.heading}</Heading>
|
||||
<div>{data.work.items.map(WorkItem)}</div>
|
||||
<div className="grid gap-4">{data.work.items.map(WorkItem)}</div>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
import { get } from 'lodash';
|
||||
import { get, isEmpty } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
export const getModalText = (isEditMode, type) => {
|
||||
return isEditMode ? `Edit ${type}` : `Add ${type}`;
|
||||
};
|
||||
|
||||
export const transformCollectionSnapshot = (snapshot, setData) => {
|
||||
const data = [];
|
||||
snapshot.forEach((doc) => data.push(doc.data()));
|
||||
setData(data);
|
||||
export const safetyCheck = (section, path = 'items') => {
|
||||
return !!(section && section.visible === true && !isEmpty(section[path]));
|
||||
};
|
||||
|
||||
export const handleKeyUp = (event, action) => {
|
||||
|
||||
Reference in New Issue
Block a user