├── .github └── dependabot.yml ├── .gitignore ├── .npmrc ├── LICENSE.md ├── README.md ├── api ├── sendSms.js └── userGeo.js ├── gridsome.config.js ├── gridsome.server.js ├── package.json ├── patches ├── @kriya__gridsome@0.9.1.patch └── gridsome-plugin-tailwindcss@4.1.1.patch ├── pnpm-lock.yaml ├── src ├── App.vue ├── assets │ └── img │ │ └── cart.png ├── components │ ├── FetchShops.vue │ ├── HeroFeatures.vue │ ├── HeroForm.vue │ ├── HeroMap.vue │ ├── HeroSearch.vue │ ├── ShopCard.vue │ ├── ShopCardAlt.vue │ ├── ShopCollection.vue │ ├── Shops.vue │ ├── ShopsBookTime.vue │ ├── ShopsMap.vue │ ├── UserAuthForm.vue │ ├── UserAuthModal.vue │ ├── base │ │ ├── BaseButton.vue │ │ ├── BaseCard.vue │ │ ├── BaseContainer.vue │ │ ├── BaseForm.vue │ │ ├── BaseFormControl.vue │ │ ├── BaseFormFieldset.vue │ │ ├── BaseInput.vue │ │ ├── BaseModal.vue │ │ ├── BasePager.vue │ │ ├── BaseSection.vue │ │ └── BaseSectionHeader.vue │ ├── icon │ │ ├── LocationPin.vue │ │ └── ShoppingCart.vue │ └── site │ │ ├── SiteFooter.vue │ │ ├── SiteNavigation.vue │ │ ├── SiteNavigationLang.vue │ │ ├── SiteNavigationSearch.vue │ │ └── SiteNavigationUser.vue ├── favicon.png ├── firebase.js ├── layouts │ └── Default.vue ├── locales │ ├── en.json │ └── es.json ├── main.js ├── mixins │ ├── Alert.js │ ├── SiteSEO.js │ └── Transitions.js ├── pages │ ├── About.vue │ ├── Index.vue │ ├── Shops.vue │ └── user │ │ └── Settings.vue ├── store │ └── index.js └── templates │ └── README.md ├── static └── img │ ├── banner.png │ └── touchicon.png └── tailwind.config.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | timezone: Europe/Madrid 9 | open-pull-requests-limit: 10 10 | ignore: 11 | - dependency-name: firebase 12 | versions: 13 | - 8.2.10 14 | - 8.2.5 15 | - 8.3.0 16 | - 8.3.2 17 | - 8.3.3 18 | - dependency-name: y18n 19 | versions: 20 | - 4.0.1 21 | - 4.0.2 22 | - dependency-name: libphonenumber-js 23 | versions: 24 | - 1.9.12 25 | - 1.9.15 26 | - dependency-name: vue-toast-notification 27 | versions: 28 | - 0.6.1 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .cache 3 | .DS_Store 4 | src/.temp 5 | node_modules 6 | dist 7 | .env 8 | .env.* 9 | 10 | .now 11 | now.json 12 | # Local Netlify folder 13 | .netlify 14 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nestor Vera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trovado 2 | A safer way to shop during quarantine 3 | 4 | ## Instructions 5 | 6 | ### 1. Install Gridsome CLI tool if you don't have 7 | 8 | `npm install --global @gridsome/cli` 9 | 10 | ### 2. Create a Gridsome project 11 | 12 | 1. `gridsome create my-gridsome-site` to install default starter 13 | 2. `cd my-gridsome-site` to open the folder 14 | 3. `gridsome develop` to start a local dev server at `http://localhost:8080` 15 | 4. Happy coding 🎉🙌 16 | -------------------------------------------------------------------------------- /api/sendSms.js: -------------------------------------------------------------------------------- 1 | // Download the helper library from https://www.twilio.com/docs/node/install 2 | // Your Account Sid and Auth Token from twilio.com/console 3 | // DANGER! This is insecure. See http://twil.io/secure 4 | 5 | const client = require('twilio')(process.env.TWILIO_SID, process.env.TWILIO_TOKEN) 6 | 7 | module.exports = async (req, res) => { 8 | // const verification = client.verify.services('VA4e40249804661e51caf7b7c608a4b81b') 9 | // .verifications 10 | // .create({to: '+34665886414', channel: 'sms'}) 11 | // .then((verification) => verification) 12 | 13 | try { 14 | // TODO: Prevent usage from outside the website 15 | const lookup = await client.lookups.phoneNumbers(req.query.to).fetch() 16 | const message = await client.messages.create({ 17 | body: req.query.body || 'Placeholder message body', 18 | from: '+12182428999', to: lookup.phoneNumber, 19 | }).then((message) => message) 20 | 21 | return res.json({ lookup, message }) 22 | 23 | } catch { 24 | return res.json('Make sure the number included its country code') 25 | } 26 | 27 | // return res.json({ 28 | // lookup, 29 | // // message, 30 | // // verification, 31 | // }) 32 | } 33 | -------------------------------------------------------------------------------- /api/userGeo.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch') 2 | 3 | module.exports = async (req, res) => { 4 | const clientIp = req.headers['x-forwarded-for'] 5 | const endpoints = (process.env.NODE_ENV === 'development' ? [ 6 | `https://ipapi.co/json`, 7 | `https://geolocation-db.com/json`, 8 | `https://ipinfo.io/json?token=${process.env.IPINFO_TOKEN}`, 9 | `http://api.ipstack.com/check?access_key=${process.env.IPSTACK_KEY}`, 10 | ] : [ 11 | `https://ipapi.co/${clientIp}/json`, 12 | `https://geolocation-db.com/json/${clientIp}`, 13 | `https://ipinfo.io/${clientIp}?token=${process.env.IPINFO_TOKEN}`, 14 | `http://api.ipstack.com/${clientIp}?access_key=${process.env.IPSTACK_KEY}`, 15 | ]).map((endpoint) => fetch(endpoint) 16 | .then((response) => response.json()) 17 | ) 18 | 19 | const relevant =['city', 'postal', 'latitude', 'longitude', 'loc', 'zip'] 20 | const keepRelevant = (res) => Object.entries(res).filter(([key]) => relevant.includes(key)) 21 | const normalize = (res, ...keys) => res.map((entries) => Object.entries(renameKey(Object.fromEntries(entries), ...keys))) 22 | const renameKey = (obj, oldK, newK, newObj = (oldV, newV) => ({ [newK]: oldV || newV })) => { 23 | const yo = { ...obj, ...newObj(obj[oldK], obj[newK]) } 24 | delete yo[oldK] 25 | return yo 26 | } 27 | 28 | const resolved = await Promise.all(endpoints) 29 | .then((response) => Object.values(response).map(keepRelevant)) 30 | .then((response) => normalize(response, 'zip', 'postal')) 31 | .then((response) => normalize(response, 'loc', '', (oldV, newV) => { 32 | const coords = (oldV || newV || '').split(',').map((val) => Number(val)) 33 | return coords.length > 1 ? { latitude: coords[0], longitude: coords[1] } : {} 34 | })) 35 | .then((response) => response.map((entries) => Object.fromEntries(entries.sort()))) 36 | // TODO: Remove redundant info -> Reduce 37 | .catch((error) => console.error(error)) 38 | 39 | return res.json(resolved) 40 | } 41 | -------------------------------------------------------------------------------- /gridsome.config.js: -------------------------------------------------------------------------------- 1 | // This is where project configuration and plugin options are located. 2 | // Learn more: https://gridsome.org/docs/config 3 | 4 | // Changes here require a server restart. 5 | // To restart press CTRL + C in terminal and run `gridsome develop` 6 | 7 | module.exports = { 8 | siteName: 'Trovado', 9 | siteDescription: 'A safer way to shop during the quarantine.', 10 | siteUrl: process.env.NOW_URL || 'https://trovado.now.sh', 11 | 12 | plugins: [ 13 | { 14 | use: "gridsome-plugin-i18n", 15 | options: { 16 | locales: ['en', 'es'], 17 | fallbackLocale: 'en', 18 | messages: process.env.NODE_ENV === 'production' && { 19 | en: require('./src/locales/en.json'), 20 | es: require('./src/locales/es.json'), 21 | }, 22 | }, 23 | }, 24 | 25 | { 26 | use: 'gridsome-plugin-tailwindcss', 27 | options: { 28 | purgeConfig: { 29 | whitelistPatternsChildren: [ 30 | /mapbox/, 31 | /mapboxgl/, 32 | /notices/, 33 | ], 34 | }, 35 | }, 36 | }, 37 | { 38 | use: '@kriya/gridsome-plugin-google-analytics', 39 | options: { id: process.env.GOOGLE_ANALYTICS_ID }, 40 | }, 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /gridsome.server.js: -------------------------------------------------------------------------------- 1 | // Server API makes it possible to hook into various parts of Gridsome 2 | // on server-side and add custom data to the GraphQL data layer. 3 | // Learn more: https://gridsome.org/docs/server-api/ 4 | 5 | // Changes here require a server restart. 6 | // To restart press CTRL + C in terminal and run `gridsome develop` 7 | 8 | module.exports = function (api) { 9 | api.loadSource(({ addCollection }) => { 10 | // Use the Data Store API here: https://gridsome.org/docs/data-store-api/ 11 | }) 12 | 13 | api.createPages(({ createPage }) => { 14 | // Use the Pages API here: https://gridsome.org/docs/pages-api/ 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "charcuterio", 3 | "private": true, 4 | "packageManager": "pnpm@9.15.0", 5 | "scripts": { 6 | "build": "gridsome build", 7 | "develop": "gridsome develop", 8 | "explore": "gridsome explore" 9 | }, 10 | "resolutions": { 11 | "autoprefixer": "10.4.5" 12 | }, 13 | "dependencies": { 14 | "@kriya/gridsome": "0.9.1", 15 | "@kriya/gridsome-plugin-google-analytics": "0.1.2", 16 | "@mapbox/mapbox-gl-geocoder": "5.0.1", 17 | "@studiometa/vue-mapbox-gl": "1.4.7", 18 | "@turf/distance": "6.5.0", 19 | "deepmerge": "4.3.1", 20 | "firebase": "8.10.0", 21 | "graphql": "14.4.2", 22 | "gridsome-plugin-i18n": "1.6.0", 23 | "libphonenumber-js": "1.11.16", 24 | "mapbox-gl": "1.13.3", 25 | "node-fetch": "2.6.7", 26 | "portal-vue": "2.1.7", 27 | "sharp": "0.25.4", 28 | "twilio": "5.4.0", 29 | "v-scroll-lock": "1.3.1", 30 | "vue-click-outside": "1.1.0", 31 | "vue-feather-icons": "5.1.0", 32 | "vue-meeting-selector": "1.1.3", 33 | "vue-router": "3.6.5", 34 | "vue-tippy": "4.16.1", 35 | "vue-toast-notification": "0.6.3", 36 | "vue-typer": "1.2.0", 37 | "vuefire": "2.2.5", 38 | "vuex": "3.6.2", 39 | "vuexfire": "3.2.5" 40 | }, 41 | "devDependencies": { 42 | "@hacknug/tailwindcss-alpha": "1.0.1", 43 | "@tailwindcss/ui": "0.7.2", 44 | "electron": "33.0.2", 45 | "gridsome-plugin-tailwindcss": "4.1.1", 46 | "tailwindcss-aspect-ratio": "3.0.0", 47 | "tailwindcss-children": "2.1.0", 48 | "tailwindcss-filters": "3.0.0", 49 | "tailwindcss-spinner": "1.2.0" 50 | }, 51 | "pnpm": { 52 | "patchedDependencies": { 53 | "gridsome-plugin-tailwindcss@4.1.1": "patches/gridsome-plugin-tailwindcss@4.1.1.patch", 54 | "@kriya/gridsome@0.9.1": "patches/@kriya__gridsome@0.9.1.patch" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /patches/@kriya__gridsome@0.9.1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/.DS_Store b/.DS_Store 2 | new file mode 100644 3 | index 0000000000000000000000000000000000000000..f9f60d0a8f94f19a9f55d4bcbfeaf7866207ada5 4 | --- /dev/null 5 | +++ b/.DS_Store 6 | @@ -0,0 +1 @@ 7 | +Bud1%libdsclboolclbool  @� @� @� @ E%DSDB`� @� @� @ 8 | \ No newline at end of file 9 | diff --git a/lib/.DS_Store b/lib/.DS_Store 10 | new file mode 100644 11 | index 0000000000000000000000000000000000000000..a7b0016a4594b5d087a79d40ca36089aba2f55b5 12 | --- /dev/null 13 | +++ b/lib/.DS_Store 14 | @@ -0,0 +1 @@ 15 | +Bud1�insdsclbopluginsdsclbool  @� @� @� @ E�DSDB `� @� @� @ 16 | \ No newline at end of file 17 | diff --git a/lib/plugins/.DS_Store b/lib/plugins/.DS_Store 18 | new file mode 100644 19 | index 0000000000000000000000000000000000000000..7fb9f8edaaf892012acc6c9c0be0ed3344debf69 20 | --- /dev/null 21 | +++ b/lib/plugins/.DS_Store 22 | @@ -0,0 +1 @@ 23 | +Bud1�componvue-componentsdsclbool  @� @� @� @ E�DSDB `� @� @� @ 24 | \ No newline at end of file 25 | diff --git a/lib/plugins/vue-components/.DS_Store b/lib/plugins/vue-components/.DS_Store 26 | new file mode 100644 27 | index 0000000000000000000000000000000000000000..f9f60d0a8f94f19a9f55d4bcbfeaf7866207ada5 28 | --- /dev/null 29 | +++ b/lib/plugins/vue-components/.DS_Store 30 | @@ -0,0 +1 @@ 31 | +Bud1%libdsclboolclbool  @� @� @� @ E%DSDB`� @� @� @ 32 | \ No newline at end of file 33 | diff --git a/lib/plugins/vue-components/lib/validate.js b/lib/plugins/vue-components/lib/validate.js 34 | index bc3444b1a2623260077a251ae1022ac97bac3490..083e87478f1d06070037f0eb083b94189df3cf10 100644 35 | --- a/lib/plugins/vue-components/lib/validate.js 36 | +++ b/lib/plugins/vue-components/lib/validate.js 37 | @@ -4,5 +4,8 @@ module.exports = (schema, doc, rules = specifiedRules) => { 38 | if (typeof doc === 'string') { 39 | doc = parse(doc) 40 | } 41 | + 42 | + rules = rules.filter(Boolean) 43 | + 44 | return validate(schema, doc, rules) 45 | } 46 | -------------------------------------------------------------------------------- /patches/gridsome-plugin-tailwindcss@4.1.1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/CHANGELOG.md b/CHANGELOG.md 2 | deleted file mode 100644 3 | index 81af4156ad06d6d31979287d779d29c957f05dc6..0000000000000000000000000000000000000000 4 | diff --git a/gridsome.server.js b/gridsome.server.js 5 | index 6fab2456bca61f1ec497b3b7fd75c66c4290e294..0022f16f7f1bd6df590528384ae1a263ac20c40c 100644 6 | --- a/gridsome.server.js 7 | +++ b/gridsome.server.js 8 | @@ -18,15 +18,15 @@ function TailwindPlugin(api, options) { 9 | .use("postcss-loader") 10 | .tap((options) => { 11 | if (shouldTimeTravel) { 12 | - options.plugins.unshift( 13 | + options.postcssOptions.plugins.unshift( 14 | require("postcss-preset-env")(presetEnvConfig) 15 | ); 16 | } 17 | 18 | - options.plugins.unshift(tailwind); 19 | + options.postcssOptions.plugins.unshift(tailwind); 20 | 21 | if (shouldImport) { 22 | - options.plugins.unshift(require("postcss-import")()); 23 | + options.postcssOptions.plugins.unshift(require("postcss-import")()); 24 | } 25 | 26 | return options; 27 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | query { 22 | metadata { 23 | siteName 24 | siteDescription 25 | } 26 | } 27 | 28 | 29 | 69 | -------------------------------------------------------------------------------- /src/assets/img/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacknug/trovado/af9254b57efde86c11ec9f23fd4c755625ae1d7a/src/assets/img/cart.png -------------------------------------------------------------------------------- /src/components/FetchShops.vue: -------------------------------------------------------------------------------- 1 | 77 | -------------------------------------------------------------------------------- /src/components/HeroFeatures.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 82 | -------------------------------------------------------------------------------- /src/components/HeroForm.vue: -------------------------------------------------------------------------------- 1 |