Repository: vuejs-br/treinamento-vue3-code Branch: master Commit: a54695dea941 Files: 159 Total size: 184.4 KB Directory structure: gitextract_3dqmdq3z/ ├── .github/ │ └── workflows/ │ ├── ci-dashboard-e2e.yml │ ├── ci-dashboard-unit.yml │ ├── ci-widget-e2e.yml │ └── ci-widget-unit.yml ├── .gitignore ├── backend/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── database/ │ │ ├── index.js │ │ └── mock.js │ ├── handlers/ │ │ ├── apikey.js │ │ ├── auth.js │ │ ├── feedbacks.js │ │ └── users.js │ ├── index.js │ ├── package.json │ ├── vercel.json │ └── vuejs_brasil_feedbacker.json ├── conceitos/ │ ├── data-binding/ │ │ └── App.vue │ ├── diretivas/ │ │ └── App.vue │ ├── eventos-e-metodos/ │ │ └── App.vue │ ├── lifecycle-hooks/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── package.json │ │ ├── public/ │ │ │ └── index.html │ │ └── src/ │ │ ├── App.vue │ │ └── main.js │ ├── nova-syntax-e-antiga/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── package.json │ │ ├── public/ │ │ │ └── index.html │ │ └── src/ │ │ ├── App.vue │ │ └── main.js │ └── single-file-components/ │ └── App.vue ├── dashboard/ │ ├── .browserslistrc │ ├── .editorconfig │ ├── .eslintrc.js │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── babel.config.js │ ├── cypress.json │ ├── jest.config.js │ ├── package.json │ ├── palette.js │ ├── postcss.config.js │ ├── public/ │ │ ├── _redirects │ │ └── index.html │ ├── src/ │ │ ├── App.vue │ │ ├── assets/ │ │ │ └── css/ │ │ │ ├── fonts.css │ │ │ └── tailwind.css │ │ ├── components/ │ │ │ ├── ContentLoader/ │ │ │ │ └── index.vue │ │ │ ├── FeedbackCard/ │ │ │ │ ├── Badge.vue │ │ │ │ ├── Loading.vue │ │ │ │ └── index.vue │ │ │ ├── HeaderLogged/ │ │ │ │ ├── HeaderLogged.spec.js │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── HeaderLogged.spec.js.snap │ │ │ │ └── index.vue │ │ │ ├── Icon/ │ │ │ │ ├── ChevronDown.vue │ │ │ │ ├── Copy.vue │ │ │ │ ├── Loading.vue │ │ │ │ └── index.vue │ │ │ ├── ModalCreateAccount/ │ │ │ │ └── index.vue │ │ │ ├── ModalFactory/ │ │ │ │ └── index.vue │ │ │ └── ModalLogin/ │ │ │ └── index.vue │ │ ├── hooks/ │ │ │ ├── useModal.js │ │ │ └── useStore.js │ │ ├── main.js │ │ ├── router/ │ │ │ └── index.js │ │ ├── services/ │ │ │ ├── __snapshots__/ │ │ │ │ └── auth.spec.js.snap │ │ │ ├── auth.js │ │ │ ├── auth.spec.js │ │ │ ├── feedbacks.js │ │ │ ├── index.js │ │ │ └── users.js │ │ ├── store/ │ │ │ ├── global.js │ │ │ ├── index.js │ │ │ ├── user.js │ │ │ └── user.spec.js │ │ ├── utils/ │ │ │ ├── bus.js │ │ │ ├── date.js │ │ │ ├── timeout.js │ │ │ ├── validators.js │ │ │ └── validators.spec.js │ │ └── views/ │ │ ├── Credencials/ │ │ │ └── index.vue │ │ ├── Feedbacks/ │ │ │ ├── Filters.vue │ │ │ ├── FiltersLoading.vue │ │ │ └── index.vue │ │ └── Home/ │ │ ├── Contact.vue │ │ ├── CustomHeader.vue │ │ ├── Home.spec.js │ │ ├── __snapshots__/ │ │ │ └── Home.spec.js.snap │ │ └── index.vue │ ├── tailwind.config.js │ └── tests/ │ └── e2e/ │ ├── .eslintrc.js │ ├── plugins/ │ │ └── index.js │ ├── specs/ │ │ ├── credencials.js │ │ └── home.js │ └── support/ │ ├── commands.js │ └── index.js ├── try-widget/ │ └── index.html └── widget/ ├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── Dockerfile ├── README.md ├── babel.config.js ├── cypress.json ├── jest.config.js ├── package.json ├── palette.js ├── postcss.config.js ├── public/ │ ├── index.html │ └── init.js ├── src/ │ ├── App.vue │ ├── assets/ │ │ └── css/ │ │ ├── fonts.css │ │ └── tailwind.css │ ├── components/ │ │ ├── Icon/ │ │ │ ├── ArrowRight.vue │ │ │ ├── Atention.vue │ │ │ ├── Chat.vue │ │ │ ├── Check.vue │ │ │ ├── ChevronDown.vue │ │ │ ├── Close.vue │ │ │ ├── Copy.vue │ │ │ ├── Loading.vue │ │ │ └── index.vue │ │ └── Wizard/ │ │ ├── Error.vue │ │ ├── SelectFeedbackType.vue │ │ ├── Success.vue │ │ ├── WriteAFeedback.vue │ │ └── index.vue │ ├── hooks/ │ │ ├── iframe.ts │ │ ├── navigation.ts │ │ └── store.ts │ ├── main.ts │ ├── services/ │ │ ├── feedbacks.ts │ │ └── index.ts │ ├── shims-vue.d.ts │ ├── store/ │ │ └── index.ts │ ├── types/ │ │ ├── error.ts │ │ └── feedback.ts │ ├── utils/ │ │ └── bootstrap.ts │ └── views/ │ ├── Playground/ │ │ ├── Playground.spec.js │ │ ├── __snapshots__/ │ │ │ └── Playground.spec.js.snap │ │ └── index.vue │ └── Widget/ │ ├── Box.vue │ ├── Standby.vue │ └── index.vue ├── tailwind.config.js ├── tests/ │ └── e2e/ │ ├── .eslintrc.js │ ├── plugins/ │ │ └── index.js │ ├── specs/ │ │ └── widget.js │ └── support/ │ ├── commands.js │ └── index.js └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/ci-dashboard-e2e.yml ================================================ name: Dashboard e2e testing on: workflow_dispatch: push: branches: [ $default-branch ] pull_request: branches: [ $default-branch ] jobs: cypress: defaults: run: working-directory: dashboard runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v2 - run: npm install - run: npm run build env: NODE_ENV: production - name: Run project locally run: | npm install serve $(npm bin)/serve dist -s -p 8080 & - name: Run tests uses: cypress-io/github-action@v2 with: working-directory: dashboard browser: chrome headless: true ================================================ FILE: .github/workflows/ci-dashboard-unit.yml ================================================ name: Dashboard unit testing on: workflow_dispatch: push: branches: [ $default-branch ] pull_request: branches: [ $default-branch ] defaults: run: working-directory: dashboard jobs: jest: runs-on: ubuntu-latest strategy: matrix: node-version: [13.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm run test:unit --if-present ================================================ FILE: .github/workflows/ci-widget-e2e.yml ================================================ name: Widget e2e testing on: workflow_dispatch: push: branches: [ $default-branch ] pull_request: branches: [ $default-branch ] jobs: cypress: defaults: run: working-directory: widget runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v2 - run: npm install - run: npm run build env: NODE_ENV: production - name: Run project locally run: | npm install serve $(npm bin)/serve dist -s -p 8080 & - name: Run tests uses: cypress-io/github-action@v2 with: working-directory: widget browser: chrome headless: true ================================================ FILE: .github/workflows/ci-widget-unit.yml ================================================ name: Widget unit testing on: workflow_dispatch: push: branches: [ $default-branch ] pull_request: branches: [ $default-branch ] defaults: run: working-directory: widget jobs: jest: runs-on: ubuntu-latest strategy: matrix: node-version: [13.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm run test:unit --if-present ================================================ FILE: .gitignore ================================================ .DS_Store node_modules ================================================ FILE: backend/.eslintrc.js ================================================ module.exports = { "env": { "commonjs": true, "es6": true, "node": true }, "extends": [ "standard" ], "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parserOptions": { "ecmaVersion": 2018 }, "rules": { } }; ================================================ FILE: backend/.gitignore ================================================ .now .vercel ================================================ FILE: backend/Dockerfile ================================================ FROM node:10-alpine RUN mkdir -p /src COPY package.json src/package.json WORKDIR /src RUN npm install --only=production --silent COPY . /src CMD npm start ================================================ FILE: backend/README.md ================================================ ## Backend do curso treinamento de Vue.js 3 Backend pré-pronto do curso treinamento de Vue.js 3 ### Comandos ``` # Buildar o backend em um container do docker npm run build # Rodar o container que foi buildado npm run container # O backend estará disponível na porta 3000 ``` _Este backend existe para auxiliar o curso de front-end com [Vue.js 3 do Vue.js Brasil](https://treinamento.vuejsbrasil.org/)_ ================================================ FILE: backend/database/index.js ================================================ const database = require('./mock') function wait (timeMs) { return new Promise(resolve => { setTimeout(resolve, timeMs) }) } async function update (col, id, data) { if (!database[col]) { return false } database[col] = database[col].map(item => { if (item.id === id) { return { ...item, ...data } } return item }) await wait(500) return true } async function readAll (col) { await wait(2500) if (!database[col]) { return [] } return database[col].sort((a, b) => b.createdAt - a.createdAt) } async function insert (col, data) { if (!database[col]) { database[col] = [] } database[col].push(data) await wait(500) return true } async function readOneById (col, id) { if (!database[col]) return const res = database[col].find(item => String(item.id) === String(id)) await wait(500) return res } async function readOneByEmail (col, email) { if (!database[col]) return const res = database[col].find(item => String(item.email) === String(email)) await wait(500) return res } module.exports = { update, insert, readAll, readOneById, readOneByEmail } ================================================ FILE: backend/database/mock.js ================================================ module.exports = { users: [ { id: 'eab759f8-f238-4ff9-ae91-ee1558982329', name: 'Igor Halfeld', email: 'igor@igor.me', password: '1234', apiKey: ['fcd5015c-10d3-4e9c-b395-ec7ed8850165'], createdAt: new Date('2020-09-05').getTime() } ], feedbacks: [ { text: 'Muito bom!', fingerprint: '490135491', id: 'eab759f8-f238-4ff9-ae91-ee1558982329', apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165', type: 'OTHER', device: 'Chrome 85.0, macOS 10.14', page: 'https://feedbacker.com/pricing', createdAt: new Date('2020-11-13').getTime() }, { text: 'Muitos erros slkkkkkkk', fingerprint: '490135491', id: 'eab759f8-f238-4ff9-ae91-ee1558982329', apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165', type: 'ISSUE', device: 'Chrome 85.0, macOS 10.14', page: 'https://feedbacker.com/pricing', createdAt: new Date('2020-10-23').getTime() }, { text: 'Podia ter um botão de solicitar demo', fingerprint: '490135491', id: 'eab759f8-f238-4ff9-ae91-ee1558982329', apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165', type: 'IDEA', device: 'Chrome 85.0, macOS 10.14', page: 'https://feedbacker.com/pricing', createdAt: new Date('2020-09-23').getTime() }, { text: 'Podia ter um botão de solicitar demo 1', fingerprint: '490135491', id: 'eab759f8-f238-4ff9-ae91-ee1558982329', apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165', type: 'IDEA', device: 'Chrome 85.0, macOS 10.14', page: 'https://feedbacker.com/pricing', createdAt: new Date('2020-12-23').getTime() }, { text: 'Podia ter um botão de solicitar demo 2', fingerprint: '490135491', id: 'eab759f8-f238-4ff9-ae91-ee1558982329', apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165', type: 'IDEA', device: 'Chrome 85.0, macOS 10.14', page: 'https://feedbacker.com/pricing', createdAt: new Date('2020-08-23').getTime() }, { text: 'Muitos erros slkkkkkkk 2', fingerprint: '490135491', id: 'eab759f8-f238-4ff9-ae91-ee1558982329', apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165', type: 'ISSUE', device: 'Chrome 85.0, macOS 10.14', page: 'https://feedbacker.com/pricing', createdAt: new Date('2020-05-23').getTime() }, { text: 'Tava bom, agora parece que piorou', fingerprint: '490135491', id: 'eab759f8-f238-4ff9-ae91-ee1558982329', apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165', type: 'ISSUE', device: 'Chrome 85.0, macOS 10.14', page: 'https://feedbacker.com/pricing', createdAt: new Date('2020-05-23').getTime() } ] } ================================================ FILE: backend/handlers/apikey.js ================================================ function CreateApiKeyHandler (db) { async function checkIfApiKeyExists (ctx) { const { apikey } = ctx.query if (!apikey) { ctx.status = 400 ctx.body = { error: 'apikey query param not provided' } return } const users = await db.readAll('users') const apiKeyExists = users.map((user) => { return user.apiKey.includes(apikey) }) if (apiKeyExists.includes(true)) { ctx.status = 200 return } ctx.status = 404 } return { checkIfApiKeyExists } } module.exports = CreateApiKeyHandler ================================================ FILE: backend/handlers/auth.js ================================================ const jwt = require('jsonwebtoken') function CreateAuthHandler (db) { async function login (ctx) { const { email, password } = ctx.request.body const user = await db.readOneByEmail('users', email) if (!user) { ctx.status = 404 ctx.body = { error: 'Not found' } return } const canLogin = () => ( user.email === email && user.password === password ) if (!canLogin()) { ctx.status = 401 ctx.body = { error: 'Unauthorized' } return } const token = jwt.sign({ id: user.id, email: user.email, name: user.name }, process.env.JWT_SECRET) ctx.status = 200 ctx.body = { token } } return { login } } module.exports = CreateAuthHandler ================================================ FILE: backend/handlers/feedbacks.js ================================================ const { v4: uuidv4 } = require('uuid') const FEEDBACK_TYPES = { ISSUE: 'ISSUE', IDEA: 'IDEA', OTHER: 'OTHER' } function CreateFeedbackHandler (db) { async function create (ctx) { const { type, text, apiKey, fingerprint, device, page } = ctx.request.body if (!type) { ctx.status = 400 ctx.body = { error: 'type is empty' } } if (!text) { ctx.status = 400 ctx.body = { error: 'text is empty' } } if (!fingerprint) { ctx.status = 400 ctx.body = { error: 'fingerprint is empty' } } if (!device) { ctx.status = 400 ctx.body = { error: 'device is empty' } } if (!page) { ctx.status = 400 ctx.body = { error: 'page is empty' } } if (!apiKey) { ctx.status = 400 ctx.body = { error: 'apiKey is empty' } } if (!FEEDBACK_TYPES[String(type).toUpperCase()]) { ctx.status = 422 ctx.body = { error: 'Unknown feedback type' } return } // @TODO: for this, I don't validate if apikey is valid. // Just for study purposes. const feedback = { text, fingerprint, id: uuidv4(), apiKey, type: String(type).toUpperCase(), device, page, createdAt: new Date().getTime() } const inserted = await db.insert('feedbacks', feedback) if (inserted) { ctx.status = 201 ctx.body = feedback return } ctx.status = 422 ctx.body = { error: 'Feedback not created' } } async function getFeedbacks (ctx) { const { type } = ctx.query let offset = ctx.query.offset ? Number(ctx.query.offset) : 0 let limit = ctx.query.limit ? Number(ctx.query.limit) : 5 let [ user, feedbacks ] = await Promise.all([ db.readOneById('users', ctx.state.user.id), db.readAll('feedbacks') ]) if (!user) { ctx.status = 401 ctx.body = { error: 'Unauthorized' } return } feedbacks = feedbacks.filter((feedback) => { return user.apiKey.includes(feedback.apiKey) }) if (type) { feedbacks = feedbacks.filter((feedback) => { return feedback.type === String(type).toUpperCase() }) } const total = feedbacks.length if (limit > 10) { limit = 5 } if (offset > limit) { offset = limit } feedbacks = feedbacks.slice(offset, feedbacks.length).slice(0, limit) ctx.status = 200 ctx.body = { results: feedbacks || [], pagination: { offset, limit, total } } } async function getSummary (ctx) { const { type } = ctx.query let [ user, feedbacks ] = await Promise.all([ db.readOneById('users', ctx.state.user.id), db.readAll('feedbacks') ]) if (!user) { ctx.status = 401 ctx.body = { error: 'Unauthorized. User not found with this token' } return } feedbacks = feedbacks.filter((feedback) => { return user.apiKey.includes(feedback.apiKey) }) if (type) { feedbacks = feedbacks.filter((feedback) => { return feedback.type === String(type).toUpperCase() }) } let all = 0 let issue = 0 let idea = 0 let other = 0 feedbacks.forEach((feedback) => { all++ if (feedback.type === 'ISSUE') { issue++ } if (feedback.type === 'IDEA') { idea++ } if (feedback.type === 'OTHER') { other++ } }) ctx.status = 200 ctx.body = { all, issue, idea, other } } return { create, getFeedbacks, getSummary } } module.exports = CreateFeedbackHandler ================================================ FILE: backend/handlers/users.js ================================================ const { v4: uuidv4 } = require('uuid') function CreateUserHandler (db) { async function getLoggerUser (ctx) { const { id } = ctx.state.user const user = await db.readOneById('users', id) if (!user) { ctx.status = 404 ctx.body = { error: 'Not found' } return } const userResponse = { ...user, apiKey: user.apiKey[user.apiKey.length - 1] } delete userResponse.password ctx.status = 200 ctx.body = userResponse } async function generateApiKey (ctx) { const apiKey = uuidv4() const { id } = ctx.state.user const user = await db.readOneById('users', id) const updated = await db.update('users', id, { apiKey: [...user.apiKey, apiKey] }) if (updated) { ctx.status = 202 ctx.body = { apiKey } return } ctx.status = 422 ctx.body = { error: 'User not updated' } } async function create (ctx) { const { email, password, name } = ctx.request.body if (!email) { ctx.status = 400 ctx.body = { error: 'email is empty' } return } if (!password) { ctx.status = 400 ctx.body = { error: 'password is empty' } return } if (!name) { ctx.status = 400 ctx.body = { error: 'name is empty' } return } const user = { id: uuidv4(), name, email, password, apiKey: [uuidv4()], createdAt: new Date().getTime() } const inserted = await db.insert('users', user) if (inserted) { ctx.status = 201 ctx.body = user return } ctx.status = 422 ctx.body = { error: 'User not created' } } return { create, generateApiKey, getLoggerUser } } module.exports = CreateUserHandler ================================================ FILE: backend/index.js ================================================ const Koa = require('koa') const Router = require('koa-router') const jwt = require('koa-jwt') const cors = require('@koa/cors') const bodyParser = require('koa-bodyparser') const database = require('./database') const CreateUserHandler = require('./handlers/users') const CreateAuthHandler = require('./handlers/auth') const CreateFeedbackHandler = require('./handlers/feedbacks') const CreateApiKeyHandler = require('./handlers/apikey') const app = new Koa() const router = new Router() const { JWT_SECRET = '', PORT = 3000 } = process.env const authMiddleware = jwt({ secret: JWT_SECRET }) app.use(bodyParser()) app.use(cors()) const feedbacksHandler = CreateFeedbackHandler(database) const usersHandler = CreateUserHandler(database) const authHandler = CreateAuthHandler(database) const apiKeyHandler = CreateApiKeyHandler(database) router.get('/', (ctx) => { ctx.status = 200 ctx.body = { message: new Date() } }) router.head('/apikey/exists', apiKeyHandler.checkIfApiKeyExists) router.post('/auth/register', usersHandler.create) router.post('/auth/login', authHandler.login) router.get('/users/me', authMiddleware, usersHandler.getLoggerUser) router.post('/users/me/apikey', authMiddleware, usersHandler.generateApiKey) router.get('/feedbacks', authMiddleware, feedbacksHandler.getFeedbacks) router.post('/feedbacks', feedbacksHandler.create) router.get('/feedbacks/summary', authMiddleware, feedbacksHandler.getSummary) app.use(router.routes()) app.use(router.allowedMethods()) app.listen(PORT, () => { console.log(`Server running http://localhost:${PORT}`) }) module.exports = app ================================================ FILE: backend/package.json ================================================ { "name": "backend", "version": "1.0.0", "description": "Backend pré-pronto do curso treinamento de Vue.js 3", "main": "index.js", "scripts": { "start": "JWT_SECRET=sssshhhh node index.js", "build": "docker build -t vuejs-treinamento-backend .", "container": "docker run -d -p 3000:3000 vuejs-treinamento-backend", "dev": "DEBUG=* JWT_SECRET=sssshhhh nodemon index.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@koa/cors": "^3.1.0", "@vercel/node": "^1.8.5", "jsonwebtoken": "^8.5.1", "koa": "^2.13.0", "koa-bodyparser": "^4.3.0", "koa-jwt": "^4.0.0", "koa-router": "^10.0.0", "uuid": "^8.3.1" }, "devDependencies": { "eslint": "^7.13.0", "eslint-config-standard": "^16.0.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", "nodemon": "^2.0.6" } } ================================================ FILE: backend/vercel.json ================================================ { "version": 2, "builds": [ { "src": "index.js", "use": "@vercel/node" } ], "routes": [ { "src": "/(.*)", "dest": "/" } ] } ================================================ FILE: backend/vuejs_brasil_feedbacker.json ================================================ { "info": { "_postman_id": "03caccb8-c176-4e3f-ae31-751901560b3c", "name": "Vue.js Brasil - Feedbacker", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ { "name": "Status", "item": [ { "name": "Pegar o status", "request": { "method": "GET", "header": [ { "key": "", "value": "", "type": "text" } ], "url": { "raw": "http://localhost:3000/?", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "" ], "query": [ { "key": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImVhYjc1OWY4LWYyMzgtNGZmOS1hZTkxLWVlMTU1ODk4MjMyOSIsImVtYWlsIjoiaWd1aW5AaWd1aW4ubWUiLCJuYW1lIjoiSWdvciBIYWxmZWxkIiwiaWF0IjoxNjA4NTA2ODAwfQ.AXBHWYY1hioeBXQfhxpI9uBGDH3shKqGgWE2JuTOsh4", "disabled": true } ] } }, "response": [] } ], "protocolProfileBehavior": {} }, { "name": "Apikey", "item": [ { "name": "Checar se a apikey existe", "request": { "method": "HEAD", "header": [], "url": { "raw": "http://localhost:3000/apikey/exists?apikey=fcd5015c-10d3-4e9c-b395-ec7ed8850165", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "apikey", "exists" ], "query": [ { "key": "apikey", "value": "fcd5015c-10d3-4e9c-b395-ec7ed8850165" } ] } }, "response": [] } ], "protocolProfileBehavior": {} }, { "name": "Auth", "item": [ { "name": "Fazer login", "request": { "method": "POST", "header": [], "body": { "mode": "raw", "raw": "{\n \"email\": \"igor@igor.me\",\n \"password\": \"1234\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "http://localhost:3000/auth/login", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "auth", "login" ] } }, "response": [] }, { "name": "Criar um novo usuário", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Ijc4NzRiNzYwLTIyZTItNDE2NS05NDJhLWE4MDcwODJmNDNhNCIsImVtYWlsIjoiZXZhbi55b3VAZ21haWwuY29tIiwibmFtZSI6IkV2YW4gWW91IiwiaWF0IjoxNjA5ODExOTc4fQ.4M4u5n7n8NMfsNDWNAeLw8q20PBQFFfRHicbwT8r8W8", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n\t\"name\": \"Evan You\",\n \"email\": \"evan.you@gmail.com\",\n \"password\": \"1234\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "http://localhost:3000/auth/register", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "auth", "register" ] } }, "response": [] } ], "protocolProfileBehavior": {} }, { "name": "Users", "item": [ { "name": "Pegar os dados do usuário logado", "protocolProfileBehavior": { "disableBodyPruning": true }, "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Ijc4NzRiNzYwLTIyZTItNDE2NS05NDJhLWE4MDcwODJmNDNhNCIsImVtYWlsIjoiZXZhbi55b3VAZ21haWwuY29tIiwibmFtZSI6IkV2YW4gWW91IiwiaWF0IjoxNjA5ODExOTc4fQ.4M4u5n7n8NMfsNDWNAeLw8q20PBQFFfRHicbwT8r8W8", "type": "text" } ], "body": { "mode": "raw", "raw": "" }, "url": { "raw": "http://localhost:3000/users/me", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "users", "me" ] } }, "response": [] }, { "name": "Gerar uma nova apiKey", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Ijc4NzRiNzYwLTIyZTItNDE2NS05NDJhLWE4MDcwODJmNDNhNCIsImVtYWlsIjoiZXZhbi55b3VAZ21haWwuY29tIiwibmFtZSI6IkV2YW4gWW91IiwiaWF0IjoxNjA5ODExOTc4fQ.4M4u5n7n8NMfsNDWNAeLw8q20PBQFFfRHicbwT8r8W8", "type": "text" } ], "url": { "raw": "http://localhost:3000/users/me/apikey", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "users", "me", "apikey" ] } }, "response": [] } ], "protocolProfileBehavior": {} }, { "name": "Feedbacks", "item": [ { "name": "Pegar índice de feedbacks", "protocolProfileBehavior": { "disableBodyPruning": true }, "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImVhYjc1OWY4LWYyMzgtNGZmOS1hZTkxLWVlMTU1ODk4MjMyOSIsImVtYWlsIjoiaWdvckBpZ29yLm1lIiwibmFtZSI6Iklnb3IgSGFsZmVsZCIsImlhdCI6MTYxMDQyNjg4OH0.88S5YLssZhC_TgotUZFDlcw5Cc3xlQTB0mqsQcQu1dY", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n \"type\": \"ISSUE\",\n \"text\": \"Tem um problema aqui\",\n \"fingerprint\": \"10wdjas0da93r0jf\",\n \"device\": \"Chrome 34, Mac OS\",\n \"page\": \"https://feedbacker.com.br/ajuda\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "http://localhost:3000/feedbacks/summary", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "feedbacks", "summary" ] } }, "response": [] }, { "name": "Pegar todos os feedbacks", "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Ijc4NzRiNzYwLTIyZTItNDE2NS05NDJhLWE4MDcwODJmNDNhNCIsImVtYWlsIjoiZXZhbi55b3VAZ21haWwuY29tIiwibmFtZSI6IkV2YW4gWW91IiwiaWF0IjoxNjA5ODExOTc4fQ.4M4u5n7n8NMfsNDWNAeLw8q20PBQFFfRHicbwT8r8W8", "type": "text" } ], "url": { "raw": "http://localhost:3000/feedbacks", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "feedbacks" ] } }, "response": [] }, { "name": "Pegar todos os feedbacks do tipo IDEA", "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImVhYjc1OWY4LWYyMzgtNGZmOS1hZTkxLWVlMTU1ODk4MjMyOSIsImVtYWlsIjoiaWdvckBpZ29yLm1lIiwibmFtZSI6Iklnb3IgSGFsZmVsZCIsImlhdCI6MTYxMDc0MzgyNn0.2R-hm8yCSAtpcvniI1R9CNF_ZzguRaMZoU2pTrwijds", "type": "text" } ], "url": { "raw": "http://localhost:3000/feedbacks?type=idea", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "feedbacks" ], "query": [ { "key": "type", "value": "idea" } ] } }, "response": [] }, { "name": "Pegar todos os feedbacks do tipo ISSUE", "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImVhYjc1OWY4LWYyMzgtNGZmOS1hZTkxLWVlMTU1ODk4MjMyOSIsImVtYWlsIjoiaWdvckBpZ29yLm1lIiwibmFtZSI6Iklnb3IgSGFsZmVsZCIsImlhdCI6MTYxMDc0MzgyNn0.2R-hm8yCSAtpcvniI1R9CNF_ZzguRaMZoU2pTrwijds", "type": "text" } ], "url": { "raw": "http://localhost:3000/feedbacks?type=issue", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "feedbacks" ], "query": [ { "key": "type", "value": "issue" } ] } }, "response": [] }, { "name": "Pegar todos os feedbacks do tipo OTHER", "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImVhYjc1OWY4LWYyMzgtNGZmOS1hZTkxLWVlMTU1ODk4MjMyOSIsImVtYWlsIjoiaWdvckBpZ29yLm1lIiwibmFtZSI6Iklnb3IgSGFsZmVsZCIsImlhdCI6MTYxMDc0MzgyNn0.2R-hm8yCSAtpcvniI1R9CNF_ZzguRaMZoU2pTrwijds", "type": "text" } ], "url": { "raw": "http://localhost:3000/feedbacks?type=other", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "feedbacks" ], "query": [ { "key": "type", "value": "other" } ] } }, "response": [] }, { "name": "Criar um feedback ISSUE", "request": { "method": "POST", "header": [], "body": { "mode": "raw", "raw": "{\n \"type\": \"ISSUE\",\n \"text\": \"Tem um problema aqui\",\n \"fingerprint\": \"10wdjas0da93r0jf\",\n \"apiKey\": \"fcd5015c-10d3-4e9c-b395-ec7ed8850165\",\n \"device\": \"Chrome 34, Mac OS\",\n \"page\": \"https://feedbacker.com.br/ajuda\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "http://localhost:3000/feedbacks", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "feedbacks" ] } }, "response": [] }, { "name": "Criar um feedback IDEA", "request": { "method": "POST", "header": [], "body": { "mode": "raw", "raw": "{\n \"type\": \"IDEA\",\n \"text\": \"E se mudar a cor dessa página?\",\n \"fingerprint\": \"10wdjas0da93r0jf\",\n \"apiKey\": \"fcd5015c-10d3-4e9c-b395-ec7ed8850165\",\n \"device\": \"Chrome 34, Mac OS\",\n \"page\": \"https://feedbacker.com.br/ajuda\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "http://localhost:3000/feedbacks", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "feedbacks" ] } }, "response": [] }, { "name": "Criar um feedback OTHER", "request": { "method": "POST", "header": [], "body": { "mode": "raw", "raw": "{\n \"type\": \"OTHER\",\n \"text\": \"Muito show!\",\n \"fingerprint\": \"10wdjas0da93r0jf\",\n \"apiKey\": \"fcd5015c-10d3-4e9c-b395-ec7ed8850165\",\n \"device\": \"Chrome 34, Mac OS\",\n \"page\": \"https://feedbacker.com.br/ajuda\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "http://localhost:3000/feedbacks", "protocol": "http", "host": [ "localhost" ], "port": "3000", "path": [ "feedbacks" ] } }, "response": [] } ], "protocolProfileBehavior": {} } ], "protocolProfileBehavior": {} } ================================================ FILE: conceitos/data-binding/App.vue ================================================ ================================================ FILE: conceitos/diretivas/App.vue ================================================ ================================================ FILE: conceitos/eventos-e-metodos/App.vue ================================================ ================================================ FILE: conceitos/lifecycle-hooks/.gitignore ================================================ .DS_Store node_modules /dist # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: conceitos/lifecycle-hooks/README.md ================================================ # nova-syntax-e-antiga ## Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Lints and fixes files ``` npm run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). ================================================ FILE: conceitos/lifecycle-hooks/babel.config.js ================================================ module.exports = { presets: [ '@vue/cli-plugin-babel/preset' ] } ================================================ FILE: conceitos/lifecycle-hooks/package.json ================================================ { "name": "nova-syntax-e-antiga", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "core-js": "^3.6.5", "vue": "^3.0.0" }, "devDependencies": { "@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-service": "~4.5.0", "@vue/compiler-sfc": "^3.0.0", "babel-eslint": "^10.1.0", "eslint": "^6.7.2", "eslint-plugin-vue": "^7.0.0-0" }, "eslintConfig": { "root": true, "env": { "node": true }, "extends": [ "plugin:vue/vue3-essential", "eslint:recommended" ], "parserOptions": { "parser": "babel-eslint" }, "rules": {} }, "browserslist": [ "> 1%", "last 2 versions", "not dead" ] } ================================================ FILE: conceitos/lifecycle-hooks/public/index.html ================================================ <%= htmlWebpackPlugin.options.title %>
================================================ FILE: conceitos/lifecycle-hooks/src/App.vue ================================================ ================================================ FILE: conceitos/lifecycle-hooks/src/main.js ================================================ import { createApp } from 'vue' import App from './App.vue' createApp(App).mount('#app') ================================================ FILE: conceitos/nova-syntax-e-antiga/.gitignore ================================================ .DS_Store node_modules /dist # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: conceitos/nova-syntax-e-antiga/README.md ================================================ # nova-syntax-e-antiga ## Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Lints and fixes files ``` npm run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). ================================================ FILE: conceitos/nova-syntax-e-antiga/babel.config.js ================================================ module.exports = { presets: [ '@vue/cli-plugin-babel/preset' ] } ================================================ FILE: conceitos/nova-syntax-e-antiga/package.json ================================================ { "name": "nova-syntax-e-antiga", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "core-js": "^3.6.5", "vue": "^3.0.0" }, "devDependencies": { "@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-service": "~4.5.0", "@vue/compiler-sfc": "^3.0.0", "babel-eslint": "^10.1.0", "eslint": "^6.7.2", "eslint-plugin-vue": "^7.0.0-0" }, "eslintConfig": { "root": true, "env": { "node": true }, "extends": [ "plugin:vue/vue3-essential", "eslint:recommended" ], "parserOptions": { "parser": "babel-eslint" }, "rules": {} }, "browserslist": [ "> 1%", "last 2 versions", "not dead" ] } ================================================ FILE: conceitos/nova-syntax-e-antiga/public/index.html ================================================ <%= htmlWebpackPlugin.options.title %>
================================================ FILE: conceitos/nova-syntax-e-antiga/src/App.vue ================================================ ================================================ FILE: conceitos/nova-syntax-e-antiga/src/main.js ================================================ import { createApp } from 'vue' import App from './App.vue' createApp(App).mount('#app') ================================================ FILE: conceitos/single-file-components/App.vue ================================================ ================================================ FILE: dashboard/.browserslistrc ================================================ > 1% last 2 versions not dead ================================================ FILE: dashboard/.editorconfig ================================================ [*.{js,jsx,ts,tsx,vue}] indent_style = space indent_size = 2 trim_trailing_whitespace = true insert_final_newline = true ================================================ FILE: dashboard/.eslintrc.js ================================================ module.exports = { root: true, env: { node: true }, extends: [ 'plugin:vue/vue3-essential', '@vue/standard' ], parserOptions: { parser: 'babel-eslint' }, rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' }, overrides: [ { files: [ '**/*.spec.js' ], env: { jest: true } } ] } ================================================ FILE: dashboard/.gitignore ================================================ .DS_Store node_modules /dist /tests/e2e/videos/ /tests/e2e/screenshots/ # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: dashboard/Dockerfile ================================================ FROM node:13-alpine as build WORKDIR / COPY . . ENV NODE_ENV=production RUN npm install --production RUN npm run build FROM nginx:1.18.0-alpine as final WORKDIR / COPY --from=build ./dist /usr/share/nginx/html ================================================ FILE: dashboard/README.md ================================================ # dashboard ## Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Run your unit tests ``` npm run test:unit ``` ### Run your end-to-end tests ``` npm run test:e2e ``` ### Lints and fixes files ``` npm run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). ================================================ FILE: dashboard/babel.config.js ================================================ module.exports = { presets: [ '@vue/cli-plugin-babel/preset' ] } ================================================ FILE: dashboard/cypress.json ================================================ { "pluginsFile": "tests/e2e/plugins/index.js" } ================================================ FILE: dashboard/jest.config.js ================================================ module.exports = { preset: '@vue/cli-plugin-unit-jest', testMatch: [ '**/*.spec.js' ], transform: { '^.+\\.vue$': 'vue-jest' } } ================================================ FILE: dashboard/package.json ================================================ { "name": "dashboard", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "test:unit": "vue-cli-service test:unit", "test:e2e": "vue-cli-service test:e2e", "lint": "vue-cli-service lint" }, "dependencies": { "@tailwindcss/postcss7-compat": "^2.0.2", "animate.css": "^4.1.1", "autoprefixer": "^9.8.6", "axios": "^0.21.1", "core-js": "^3.6.5", "postcss": "^7.0.35", "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2", "tiny-emitter": "^2.1.0", "vee-validate": "^4.1.9", "vue": "^3.0.0", "vue-router": "^4.0.0-0", "vue-toastification": "^2.0.0-beta.9" }, "devDependencies": { "@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-e2e-cypress": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-plugin-router": "~4.5.0", "@vue/cli-plugin-unit-jest": "~4.5.0", "@vue/cli-service": "~4.5.0", "@vue/compiler-sfc": "^3.0.0", "@vue/eslint-config-standard": "^5.1.2", "@vue/test-utils": "^2.0.0-beta.14", "babel-eslint": "^10.1.0", "eslint": "^6.7.2", "eslint-plugin-import": "^2.20.2", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.0", "eslint-plugin-vue": "^7.0.0-0", "lint-staged": "^9.5.0", "typescript": "~3.9.3", "vue-jest": "^5.0.0-0" }, "gitHooks": { "pre-commit": "lint-staged" }, "lint-staged": { "*.{js,jsx,vue}": [ "vue-cli-service lint", "git add" ] } } ================================================ FILE: dashboard/palette.js ================================================ module.exports = { brand: { main: '#EF4983', gray: '#F9F9F9', info: '#8296FB', graydark: '#C0BCB0', warning: '#E4B52E', danger: '#F88676' }, mediumslateblue: { 50: '#f6f9fd', 100: '#e8f3fd', 200: '#cbdefb', 300: '#abc4fb', 400: '#8296fb', 500: '#5667fb', 600: '#3d46f7', 700: '#3137e5', 800: '#272cb9', 900: '#202591' }, slateblue: { 50: '#f5f8fc', 100: '#eaf0fc', 200: '#d3d8fa', 300: '#babcf9', 400: '#9c8ff9', 500: '#7a61f9', 600: '#5d41f4', 700: '#4933e1', 800: '#382ab5', 900: '#2d248f' }, mediumorchid: { 50: '#f9f7fa', 100: '#f7edf9', 200: '#efd1f5', 300: '#e8aef1', 400: '#e47cec', 500: '#e04fe7', 600: '#c932d7', 700: '#9a27b5', 800: '#712186', 900: '#571c66' }, deeppink: { 50: '#fcf9f9', 100: '#fcf0f3', 200: '#fad3e6', 300: '#f9acd1', 400: '#fa73ab', 500: '#fb4783', 600: '#f42a5c', 700: '#d6214a', 800: '#a71b3a', 900: '#83172f' }, tomato: { 50: '#fcf9f6', 100: '#fcf0ed', 200: '#fadad6', 300: '#f8b9b0', 400: '#f88676', 500: '#f85b47', 600: '#ee392e', 700: '#cc2b2b', 800: '#9e2328', 900: '#7c1d23' }, chocolate: { 50: '#fbf8f2', 100: '#fbf1e1', 200: '#f8e1bb', 300: '#f5c782', 400: '#f39d41', 500: '#f1741e', 600: '#e24f13', 700: '#bc3b16', 800: '#922e1a', 900: '#732619' }, goldenrod: { 50: '#fbfaf4', 100: '#faf6e0', 200: '#f5ebb0', 300: '#efd86f', 400: '#e4b52e', 500: '#d69111', 600: '#b76b0a', 700: '#8b500e', 800: '#663d12', 900: '#4e3012' }, darkgoldenrod: { 50: '#fbfaf6', 100: '#f9f8e7', 200: '#f2efbc', 300: '#e8de7f', 400: '#d3bd3a', 500: '#b79b17', 600: '#8e750d', 700: '#685910', 800: '#4b4213', 900: '#393414' }, lightseagreen: { 50: '#f5fafa', 100: '#eaf7f5', 200: '#ceeee8', 300: '#a7dfd9', 400: '#63c3be', 500: '#30a29c', 600: '#237f79', 700: '#236460', 800: '#204c4a', 900: '#1b3d3c' }, cornflowerblue: { 50: '#f4fafc', 100: '#e2f7fb', 200: '#bdeaf7', 300: '#8fd8f5', 400: '#4fb6f1', 500: '#238fed', 600: '#1a6bdf', 700: '#1b54bd', 800: '#19418c', 900: '#15346b' } } ================================================ FILE: dashboard/postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, } } ================================================ FILE: dashboard/public/_redirects ================================================ /* /index.html 200 ================================================ FILE: dashboard/public/index.html ================================================ Feedbacker
================================================ FILE: dashboard/src/App.vue ================================================ ================================================ FILE: dashboard/src/assets/css/fonts.css ================================================ @font-face { font-family: "RobotoRegular"; src: local("Roboto Regular"), local("Roboto-Regular"), url("../fonts/Roboto-Regular.ttf") format("truetype"); font-weight: 400; font-style: normal; } @font-face { font-family: "RobotoMedium"; src: local("Roboto Medium"), local("Roboto-Medium"), url("../fonts/Roboto-Medium.ttf") format("truetype"); font-weight: 500; font-style: normal; } @font-face { font-family: "RobotoBold"; src: local("Roboto Bold"), local("Roboto-Bold"), url("../fonts/Roboto-Bold.ttf") format("truetype"); font-weight: 700; font-style: normal; } @font-face { font-family: "RobotoBlack"; src: local("Roboto Black"), local("Roboto-Black"), url("../fonts/Roboto-Black.ttf") format("truetype"); font-weight: 900; font-style: normal; } ================================================ FILE: dashboard/src/assets/css/tailwind.css ================================================ /*! @import */ @tailwind base; @tailwind components; @tailwind utilities; html, body, #app { width: 100%; height: 100%; } ================================================ FILE: dashboard/src/components/ContentLoader/index.vue ================================================ ================================================ FILE: dashboard/src/components/FeedbackCard/Badge.vue ================================================ ================================================ FILE: dashboard/src/components/FeedbackCard/Loading.vue ================================================ ================================================ FILE: dashboard/src/components/FeedbackCard/index.vue ================================================ ================================================ FILE: dashboard/src/components/HeaderLogged/HeaderLogged.spec.js ================================================ import { shallowMount } from '@vue/test-utils' import HeaderLogged from '.' import { routes } from '../../router' import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory('/'), routes }) const mockStore = { currentUser: {} } jest.mock('../../hooks/useStore', () => { return () => { return mockStore } }) describe('', () => { it('should render header logged correctly', async () => { router.push('/') await router.isReady() const wrapper = shallowMount(HeaderLogged, { global: { plugins: [router] } }) expect(wrapper.html()).toMatchSnapshot() }) it('should render 3 dots when there\'s not user logged', async () => { router.push('/') await router.isReady() const wrapper = shallowMount(HeaderLogged, { global: { plugins: [router] } }) const buttonLogout = wrapper.find('#logout-button') expect(buttonLogout.text()).toBe('...') }) it('should render user anem when there\'s user logged', async () => { router.push('/') await router.isReady() mockStore.currentUser.name = 'Igor' const wrapper = shallowMount(HeaderLogged, { global: { plugins: [router] } }) const buttonLogout = wrapper.find('#logout-button') expect(buttonLogout.text()).toBe('Igor (sair)') }) }) ================================================ FILE: dashboard/src/components/HeaderLogged/__snapshots__/HeaderLogged.spec.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` should render header logged correctly 1`] = `
logo
  • Credenciais
  • Feedbacks
  • ...
`; ================================================ FILE: dashboard/src/components/HeaderLogged/index.vue ================================================ ================================================ FILE: dashboard/src/components/Icon/ChevronDown.vue ================================================ ================================================ FILE: dashboard/src/components/Icon/Copy.vue ================================================ ================================================ FILE: dashboard/src/components/Icon/Loading.vue ================================================ ================================================ FILE: dashboard/src/components/Icon/index.vue ================================================ ================================================ FILE: dashboard/src/components/ModalCreateAccount/index.vue ================================================ ================================================ FILE: dashboard/src/components/ModalFactory/index.vue ================================================ ================================================ FILE: dashboard/src/components/ModalLogin/index.vue ================================================ ================================================ FILE: dashboard/src/hooks/useModal.js ================================================ import bus from '../utils/bus' const EVENT_NAME = 'modal:toggle' export default function useModal () { function open (payload = {}) { bus.emit(EVENT_NAME, { status: true, ...payload }) } function close (payload = {}) { bus.emit(EVENT_NAME, { status: false, ...payload }) } function listen (fn) { bus.on(EVENT_NAME, fn) } function off (fn) { bus.off(EVENT_NAME, fn) } return { open, close, listen, off } } ================================================ FILE: dashboard/src/hooks/useStore.js ================================================ import Store from '../store' export default function useStore (module) { if (module) { return Store[module] } return Store } ================================================ FILE: dashboard/src/main.js ================================================ import { createApp } from 'vue' import Toast, { POSITION } from 'vue-toastification' import App from './App.vue' import router from './router' import 'animate.css' import '@/assets/css/tailwind.css' import '@/assets/css/fonts.css' import 'vue-toastification/dist/index.css' const app = createApp(App) app.use(router) app.use(Toast, { position: POSITION.BOTTOM_RIGHT }) app.mount('#app') ================================================ FILE: dashboard/src/router/index.js ================================================ import { createRouter, createWebHistory } from 'vue-router' const Home = () => import('../views/Home/index.vue') const Feedbacks = () => import('../views/Feedbacks/index.vue') const Credencials = () => import('../views/Credencials/index.vue') export const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/feedbacks', name: 'Feedbacks', component: Feedbacks, meta: { hasAuth: true } }, { path: '/credencials', name: 'Credencials', component: Credencials, meta: { hasAuth: true } }, { path: '/:pathMatch(.*)*', redirect: { name: 'Home' } } ] const router = createRouter({ history: createWebHistory('/'), routes }) export default router ================================================ FILE: dashboard/src/services/__snapshots__/auth.spec.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`AuthService should return a token when user login 1`] = ` Object { "data": Object { "token": "123.123.123", }, "errors": null, } `; exports[`AuthService should return an user when user register 1`] = ` Object { "data": Object { "email": "igor@igor.me", "name": "Igor", "password": "123", }, "errors": null, } `; ================================================ FILE: dashboard/src/services/auth.js ================================================ export default httpClient => ({ register: async ({ name, email, password }) => { const response = await httpClient.post('/auth/register', { name, email, password }) let errors = null if (!response.data) { errors = { status: response.request.status, statusText: response.request.statusText } } return { data: response.data, errors } }, login: async ({ email, password }) => { const response = await httpClient.post('/auth/login', { email, password }) let errors = null if (!response.data) { errors = { status: response.request.status, statusText: response.request.statusText } } return { data: response.data, errors } } }) ================================================ FILE: dashboard/src/services/auth.spec.js ================================================ import mockAxios from 'axios' import AuthService from './auth' jest.mock('axios') describe('AuthService', () => { afterEach(() => { jest.clearAllMocks() }) it('should return a token when user login', async () => { const token = '123.123.123' mockAxios.post.mockImplementationOnce(() => { return Promise.resolve({ data: { token } }) }) const response = await AuthService(mockAxios).login({ email: 'igor@igor.me', password: '123' }) expect(response.data).toHaveProperty('token') expect(response).toMatchSnapshot() }) it('should return an user when user register', async () => { const user = { name: 'Igor', password: '123', email: 'igor@igor.me' } mockAxios.post.mockImplementationOnce(() => { return Promise.resolve({ data: user }) }) const response = await AuthService(mockAxios).register(user) expect(response.data).toHaveProperty('name') expect(response.data).toHaveProperty('password') expect(response.data).toHaveProperty('email') expect(response).toMatchSnapshot() }) it('should throw an error when not found', async () => { const errors = { status: 404, statusText: 'Not Found' } mockAxios.post.mockImplementationOnce(() => { return Promise.resolve({ request: errors }) }) const response = await AuthService(mockAxios).login({ email: 'igor@igor.me', password: '123' }) expect(response.errors).toHaveProperty('status') expect(response.errors).toHaveProperty('statusText') }) }) ================================================ FILE: dashboard/src/services/feedbacks.js ================================================ const defaultPagination = { limit: 5, offset: 0 } export default httpClient => ({ getAll: async ({ type, limit, offset } = defaultPagination) => { const query = { limit, offset } if (type) { query.type = type } const response = await httpClient.get('/feedbacks', { params: query }) return { data: response.data } }, getSummary: async () => { const response = await httpClient.get('/feedbacks/summary') return { data: response.data } } }) ================================================ FILE: dashboard/src/services/index.js ================================================ import axios from 'axios' import router from '../router' import { setGlobalLoading } from '../store/global' import AuthService from './auth' import UsersService from './users' import FeedbacksService from './feedbacks' const API_ENVS = { production: 'https://backend-treinamento-vue3.vercel.app', development: '', local: 'http://localhost:3000' } const httpClient = axios.create({ baseURL: API_ENVS[process.env.NODE_ENV] || API_ENVS.local }) httpClient.interceptors.request.use(config => { setGlobalLoading(true) const token = window.localStorage.getItem('token') if (token) { config.headers.common.Authorization = `Bearer ${token}` } return config }) httpClient.interceptors.response.use((response) => { setGlobalLoading(false) return response }, (error) => { const canThrowAnError = error.request.status === 0 || error.request.status === 500 if (canThrowAnError) { setGlobalLoading(false) throw new Error(error.message) } if (error.response.status === 401) { router.push({ name: 'Home' }) } setGlobalLoading(false) return error }) export default { auth: AuthService(httpClient), users: UsersService(httpClient), feedbacks: FeedbacksService(httpClient) } ================================================ FILE: dashboard/src/services/users.js ================================================ export default httpClient => ({ getMe: async () => { const response = await httpClient.get('/users/me') return { data: response.data } }, generateApikey: async () => { const response = await httpClient.post('/users/me/apikey') return { data: response.data } } }) ================================================ FILE: dashboard/src/store/global.js ================================================ import { reactive } from 'vue' const state = reactive({ isLoading: false }) export default state export function setGlobalLoading (status) { state.isLoading = status } ================================================ FILE: dashboard/src/store/index.js ================================================ import { readonly } from 'vue' import UserModule from './user' import GlobalModule from './global' export default readonly({ User: UserModule, Global: GlobalModule }) ================================================ FILE: dashboard/src/store/user.js ================================================ import { reactive } from 'vue' const userInitialState = { currentUser: {} } let state = reactive(userInitialState) export default state export function resetUserStore () { state = reactive(userInitialState) } export function cleanCurrentUser () { state.currentUser = {} } export function setCurrentUser (user) { state.currentUser = user } export function setApiKey (apiKey) { const currentUser = { ...state.currentUser, apiKey } state.currentUser = currentUser } ================================================ FILE: dashboard/src/store/user.spec.js ================================================ import useStore from '../hooks/useStore' import { resetUserStore, setApiKey, cleanCurrentUser, setCurrentUser } from './user' describe('UserStore', () => { afterEach(() => { resetUserStore() }) it('should set current user', () => { const store = useStore() setCurrentUser({ name: 'Igor' }) expect(store.User.currentUser.name).toBe('Igor') }) it('should set api_key on current user', () => { const store = useStore() setApiKey('123') expect(store.User.currentUser.apiKey).toBe('123') }) it('should clean current user', () => { const store = useStore() setCurrentUser({ name: 'Igor' }) expect(store.User.currentUser.name).toBe('Igor') cleanCurrentUser() expect(store.User.currentUser.name).toBeFalsy() }) }) ================================================ FILE: dashboard/src/utils/bus.js ================================================ import Emitter from 'tiny-emitter' export default new Emitter() ================================================ FILE: dashboard/src/utils/date.js ================================================ export function getDiffTimeBetweenCurrentDate (dateString = '', now = new Date()) { const dayInMilliseconds = 86400000 if ([null, undefined, false, true].includes(dateString)) { return dateString } const date = new Date(dateString) const isInvalidDate = isNaN(date.getTime()) if (isInvalidDate) { return dateString } const month = date.getMonth() const day = date.getDate() const hour = date.getHours() const minutes = date.getMinutes() const seconds = date.getSeconds() const buildMessage = (label, value) => { if (value !== 1) { return `${value} ${label}s atrás` } return `${value} ${label} atrás` } const notZero = value => value !== 0 if (month !== now.getMonth()) { const diff = Math.abs(now - date) const days = Math.ceil(diff / dayInMilliseconds) return buildMessage('dia', days) } if (day < now.getDate() && notZero(day)) { return buildMessage('dia', now.getDate() - day) } if (hour < now.getHours() && notZero(hour)) { return buildMessage('hora', now.getHours() - hour) } if (minutes < now.getMinutes() && notZero(minutes)) { return buildMessage('minuto', now.getMinutes() - minutes) } if (seconds < now.getSeconds() && notZero(seconds)) { return buildMessage('segundo', now.getSeconds() - seconds) } return buildMessage('segundo', 1) } ================================================ FILE: dashboard/src/utils/timeout.js ================================================ export function wait (timeMs) { return new Promise(resolve => { setTimeout(resolve, timeMs) }) } ================================================ FILE: dashboard/src/utils/validators.js ================================================ export function validateEmptyAndLength3 (value) { if (!value) { return '*Este campo é obrigatório' } if (value.length < 3) { return '*Este campo precisa de no mínimo 3 caracteres' } return true } export function validateEmptyAndEmail (value) { if (!value) { return '*Este campo é obrigatório' } const isValid = /^[a-z0-9.]+@[a-z0-9]+\.[a-z]+(\.[a-z]+)?$/i.test(value) if (!isValid) { return '*Este campo precisa ser um e-mail' } return true } ================================================ FILE: dashboard/src/utils/validators.spec.js ================================================ import { validateEmptyAndEmail, validateEmptyAndLength3 } from './validators' describe('Validators utils', () => { it('should give an error with empty payload', () => { expect(validateEmptyAndLength3()).toBe('*Este campo é obrigatório') }) it('should give an error with less then 3 character payload', () => { expect(validateEmptyAndLength3('12')).toBe('*Este campo precisa de no mínimo 3 caracteres') }) it('should returns true when pass a correct param', () => { expect(validateEmptyAndLength3('1234')).toBe(true) }) it('should give an error with empty payload', () => { expect(validateEmptyAndEmail()).toBe('*Este campo é obrigatório') }) it('should give an error with a invalid param', () => { expect(validateEmptyAndEmail('myemail@')).toBe('*Este campo precisa ser um e-mail') }) it('should returns true when pass a correct param', () => { expect(validateEmptyAndEmail('igor@igor.me')).toBe(true) }) }) ================================================ FILE: dashboard/src/views/Credencials/index.vue ================================================ ================================================ FILE: dashboard/src/views/Feedbacks/Filters.vue ================================================ ================================================ FILE: dashboard/src/views/Feedbacks/FiltersLoading.vue ================================================ ================================================ FILE: dashboard/src/views/Feedbacks/index.vue ================================================ ================================================ FILE: dashboard/src/views/Home/Contact.vue ================================================ ================================================ FILE: dashboard/src/views/Home/CustomHeader.vue ================================================ ================================================ FILE: dashboard/src/views/Home/Home.spec.js ================================================ import Home from '.' import { shallowMount } from '@vue/test-utils' import { routes } from '../../router' import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory('/'), routes }) describe('', () => { it('should render home correctly', async () => { router.push('/') await router.isReady() const wrapper = shallowMount(Home, { global: { plugins: [router] } }) expect(wrapper.html()).toMatchSnapshot() }) }) ================================================ FILE: dashboard/src/views/Home/__snapshots__/Home.spec.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` should render home correctly 1`] = `

feedbacker © 2021

`; ================================================ FILE: dashboard/src/views/Home/index.vue ================================================ ================================================ FILE: dashboard/tailwind.config.js ================================================ const colors = require('tailwindcss/colors') const palette = require('./palette') module.exports = { purge: [ './src/**/*.html', './src/**/*.vue', './src/**/*.jsx' ], presets: [], darkMode: false, // or 'media' or 'class' theme: { extend: { colors: palette }, screens: { sm: '640px', md: '768px', lg: '1024px', xl: '1280px', '2xl': '1536px' }, colors: { transparent: 'transparent', current: 'currentColor', black: colors.black, white: colors.white, gray: colors.coolGray, red: colors.red, yellow: colors.amber, green: colors.emerald, blue: colors.blue, indigo: colors.indigo, purple: colors.violet, pink: colors.pink }, spacing: { px: '1px', 0: '0px', 0.5: '0.125rem', 1: '0.25rem', 1.5: '0.375rem', 2: '0.5rem', 2.5: '0.625rem', 3: '0.75rem', 3.5: '0.875rem', 4: '1rem', 5: '1.25rem', 6: '1.5rem', 7: '1.75rem', 8: '2rem', 9: '2.25rem', 10: '2.5rem', 11: '2.75rem', 12: '3rem', 14: '3.5rem', 16: '4rem', 20: '5rem', 24: '6rem', 28: '7rem', 32: '8rem', 36: '9rem', 40: '10rem', 44: '11rem', 48: '12rem', 52: '13rem', 56: '14rem', 60: '15rem', 64: '16rem', 72: '18rem', 80: '20rem', 96: '24rem' }, animation: { none: 'none', spin: 'spin 1s linear infinite', ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite', pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', bounce: 'bounce 1s infinite' }, backgroundColor: (theme) => theme('colors'), backgroundImage: { none: 'none', 'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))', 'gradient-to-tr': 'linear-gradient(to top right, var(--tw-gradient-stops))', 'gradient-to-r': 'linear-gradient(to right, var(--tw-gradient-stops))', 'gradient-to-br': 'linear-gradient(to bottom right, var(--tw-gradient-stops))', 'gradient-to-b': 'linear-gradient(to bottom, var(--tw-gradient-stops))', 'gradient-to-bl': 'linear-gradient(to bottom left, var(--tw-gradient-stops))', 'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))', 'gradient-to-tl': 'linear-gradient(to top left, var(--tw-gradient-stops))' }, backgroundOpacity: (theme) => theme('opacity'), backgroundPosition: { bottom: 'bottom', center: 'center', left: 'left', 'left-bottom': 'left bottom', 'left-top': 'left top', right: 'right', 'right-bottom': 'right bottom', 'right-top': 'right top', top: 'top' }, backgroundSize: { auto: 'auto', cover: 'cover', contain: 'contain' }, borderColor: (theme) => ({ ...theme('colors'), DEFAULT: theme('colors.gray.200', 'currentColor') }), borderOpacity: (theme) => theme('opacity'), borderRadius: { none: '0px', sm: '0.125rem', DEFAULT: '0.25rem', md: '0.375rem', lg: '0.5rem', xl: '0.75rem', '2xl': '1rem', '3xl': '1.5rem', full: '9999px' }, borderWidth: { DEFAULT: '1px', 0: '0px', 2: '2px', 4: '4px', 8: '8px' }, boxShadow: { sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)', none: 'none' }, container: {}, cursor: { auto: 'auto', default: 'default', pointer: 'pointer', wait: 'wait', text: 'text', move: 'move', 'not-allowed': 'not-allowed' }, divideColor: (theme) => theme('borderColor'), divideOpacity: (theme) => theme('borderOpacity'), divideWidth: (theme) => theme('borderWidth'), fill: { current: 'currentColor' }, flex: { 1: '1 1 0%', auto: '1 1 auto', initial: '0 1 auto', none: 'none' }, flexGrow: { 0: '0', DEFAULT: '1' }, flexShrink: { 0: '0', DEFAULT: '1' }, fontFamily: { regular: ['RobotoRegular'], medium: ['RobotoMedium'], bold: ['RobotoBold'], black: ['RobotoBlack'], sans: [ 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"' ], serif: ['ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'], mono: [ 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace' ] }, fontSize: { xs: ['0.75rem', { lineHeight: '1rem' }], sm: ['0.875rem', { lineHeight: '1.25rem' }], base: ['1rem', { lineHeight: '1.5rem' }], lg: ['1.125rem', { lineHeight: '1.75rem' }], xl: ['1.25rem', { lineHeight: '1.75rem' }], '2xl': ['1.5rem', { lineHeight: '2rem' }], '3xl': ['1.875rem', { lineHeight: '2.25rem' }], '4xl': ['2.25rem', { lineHeight: '2.5rem' }], '5xl': ['3rem', { lineHeight: '1' }], '6xl': ['3.75rem', { lineHeight: '1' }], '7xl': ['4.5rem', { lineHeight: '1' }], '8xl': ['6rem', { lineHeight: '1' }], '9xl': ['8rem', { lineHeight: '1' }] }, fontWeight: { thin: '100', extralight: '200', light: '300', normal: '400', medium: '500', semibold: '600', bold: '700', extrabold: '800', black: '900' }, gap: (theme) => theme('spacing'), gradientColorStops: (theme) => theme('colors'), gridAutoColumns: { auto: 'auto', min: 'min-content', max: 'max-content', fr: 'minmax(0, 1fr)' }, gridAutoRows: { auto: 'auto', min: 'min-content', max: 'max-content', fr: 'minmax(0, 1fr)' }, gridColumn: { auto: 'auto', 'span-1': 'span 1 / span 1', 'span-2': 'span 2 / span 2', 'span-3': 'span 3 / span 3', 'span-4': 'span 4 / span 4', 'span-5': 'span 5 / span 5', 'span-6': 'span 6 / span 6', 'span-7': 'span 7 / span 7', 'span-8': 'span 8 / span 8', 'span-9': 'span 9 / span 9', 'span-10': 'span 10 / span 10', 'span-11': 'span 11 / span 11', 'span-12': 'span 12 / span 12', 'span-full': '1 / -1' }, gridColumnEnd: { auto: 'auto', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12', 13: '13' }, gridColumnStart: { auto: 'auto', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12', 13: '13' }, gridRow: { auto: 'auto', 'span-1': 'span 1 / span 1', 'span-2': 'span 2 / span 2', 'span-3': 'span 3 / span 3', 'span-4': 'span 4 / span 4', 'span-5': 'span 5 / span 5', 'span-6': 'span 6 / span 6', 'span-full': '1 / -1' }, gridRowStart: { auto: 'auto', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7' }, gridRowEnd: { auto: 'auto', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7' }, transformOrigin: { center: 'center', top: 'top', 'top-right': 'top right', right: 'right', 'bottom-right': 'bottom right', bottom: 'bottom', 'bottom-left': 'bottom left', left: 'left', 'top-left': 'top left' }, gridTemplateColumns: { none: 'none', 1: 'repeat(1, minmax(0, 1fr))', 2: 'repeat(2, minmax(0, 1fr))', 3: 'repeat(3, minmax(0, 1fr))', 4: 'repeat(4, minmax(0, 1fr))', 5: 'repeat(5, minmax(0, 1fr))', 6: 'repeat(6, minmax(0, 1fr))', 7: 'repeat(7, minmax(0, 1fr))', 8: 'repeat(8, minmax(0, 1fr))', 9: 'repeat(9, minmax(0, 1fr))', 10: 'repeat(10, minmax(0, 1fr))', 11: 'repeat(11, minmax(0, 1fr))', 12: 'repeat(12, minmax(0, 1fr))' }, gridTemplateRows: { none: 'none', 1: 'repeat(1, minmax(0, 1fr))', 2: 'repeat(2, minmax(0, 1fr))', 3: 'repeat(3, minmax(0, 1fr))', 4: 'repeat(4, minmax(0, 1fr))', 5: 'repeat(5, minmax(0, 1fr))', 6: 'repeat(6, minmax(0, 1fr))' }, height: (theme) => ({ auto: 'auto', ...theme('spacing'), '1/2': '50%', '1/3': '33.333333%', '2/3': '66.666667%', '1/4': '25%', '2/4': '50%', '3/4': '75%', '1/5': '20%', '2/5': '40%', '3/5': '60%', '4/5': '80%', '1/6': '16.666667%', '2/6': '33.333333%', '3/6': '50%', '4/6': '66.666667%', '5/6': '83.333333%', full: '100%', screen: '100vh' }), inset: (theme, { negative }) => ({ auto: 'auto', ...theme('spacing'), ...negative(theme('spacing')), '1/2': '50%', '1/3': '33.333333%', '2/3': '66.666667%', '1/4': '25%', '2/4': '50%', '3/4': '75%', full: '100%', '-1/2': '-50%', '-1/3': '-33.333333%', '-2/3': '-66.666667%', '-1/4': '-25%', '-2/4': '-50%', '-3/4': '-75%', '-full': '-100%' }), keyframes: { spin: { to: { transform: 'rotate(360deg)' } }, ping: { '75%, 100%': { transform: 'scale(2)', opacity: '0' } }, pulse: { '50%': { opacity: '.5' } }, bounce: { '0%, 100%': { transform: 'translateY(-25%)', animationTimingFunction: 'cubic-bezier(0.8,0,1,1)' }, '50%': { transform: 'none', animationTimingFunction: 'cubic-bezier(0,0,0.2,1)' } } }, letterSpacing: { tighter: '-0.05em', tight: '-0.025em', normal: '0em', wide: '0.025em', wider: '0.05em', widest: '0.1em' }, lineHeight: { none: '1', tight: '1.25', snug: '1.375', normal: '1.5', relaxed: '1.625', loose: '2', 3: '.75rem', 4: '1rem', 5: '1.25rem', 6: '1.5rem', 7: '1.75rem', 8: '2rem', 9: '2.25rem', 10: '2.5rem' }, listStyleType: { none: 'none', disc: 'disc', decimal: 'decimal' }, margin: (theme, { negative }) => ({ auto: 'auto', ...theme('spacing'), ...negative(theme('spacing')) }), maxHeight: (theme) => ({ ...theme('spacing'), full: '100%', screen: '100vh' }), maxWidth: (theme, { breakpoints }) => ({ none: 'none', 0: '0rem', xs: '20rem', sm: '24rem', md: '28rem', lg: '32rem', xl: '36rem', '2xl': '42rem', '3xl': '48rem', '4xl': '56rem', '5xl': '64rem', '6xl': '72rem', '7xl': '80rem', full: '100%', min: 'min-content', max: 'max-content', prose: '65ch', ...breakpoints(theme('screens')) }), minHeight: { 0: '0px', full: '100%', screen: '100vh' }, minWidth: { 0: '0px', full: '100%', min: 'min-content', max: 'max-content' }, objectPosition: { bottom: 'bottom', center: 'center', left: 'left', 'left-bottom': 'left bottom', 'left-top': 'left top', right: 'right', 'right-bottom': 'right bottom', 'right-top': 'right top', top: 'top' }, opacity: { 0: '0', 5: '0.05', 10: '0.1', 20: '0.2', 25: '0.25', 30: '0.3', 40: '0.4', 50: '0.5', 60: '0.6', 70: '0.7', 75: '0.75', 80: '0.8', 90: '0.9', 95: '0.95', 100: '1' }, order: { first: '-9999', last: '9999', none: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12' }, outline: { none: ['2px solid transparent', '2px'], white: ['2px dotted white', '2px'], black: ['2px dotted black', '2px'] }, padding: (theme) => theme('spacing'), placeholderColor: (theme) => theme('colors'), placeholderOpacity: (theme) => theme('opacity'), ringColor: (theme) => ({ DEFAULT: theme('colors.blue.500', '#3b82f6'), ...theme('colors') }), ringOffsetColor: (theme) => theme('colors'), ringOffsetWidth: { 0: '0px', 1: '1px', 2: '2px', 4: '4px', 8: '8px' }, ringOpacity: (theme) => ({ DEFAULT: '0.5', ...theme('opacity') }), ringWidth: { DEFAULT: '3px', 0: '0px', 1: '1px', 2: '2px', 4: '4px', 8: '8px' }, rotate: { '-180': '-180deg', '-90': '-90deg', '-45': '-45deg', '-12': '-12deg', '-6': '-6deg', '-3': '-3deg', '-2': '-2deg', '-1': '-1deg', 0: '0deg', 1: '1deg', 2: '2deg', 3: '3deg', 6: '6deg', 12: '12deg', 45: '45deg', 90: '90deg', 180: '180deg' }, scale: { 0: '0', 50: '.5', 75: '.75', 90: '.9', 95: '.95', 100: '1', 105: '1.05', 110: '1.1', 125: '1.25', 150: '1.5' }, skew: { '-12': '-12deg', '-6': '-6deg', '-3': '-3deg', '-2': '-2deg', '-1': '-1deg', 0: '0deg', 1: '1deg', 2: '2deg', 3: '3deg', 6: '6deg', 12: '12deg' }, space: (theme, { negative }) => ({ ...theme('spacing'), ...negative(theme('spacing')) }), stroke: { current: 'currentColor' }, strokeWidth: { 0: '0', 1: '1', 2: '2' }, textColor: (theme) => theme('colors'), textOpacity: (theme) => theme('opacity'), transitionDuration: { DEFAULT: '150ms', 75: '75ms', 100: '100ms', 150: '150ms', 200: '200ms', 300: '300ms', 500: '500ms', 700: '700ms', 1000: '1000ms' }, transitionDelay: { 75: '75ms', 100: '100ms', 150: '150ms', 200: '200ms', 300: '300ms', 500: '500ms', 700: '700ms', 1000: '1000ms' }, transitionProperty: { none: 'none', all: 'all', DEFAULT: 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform', colors: 'background-color, border-color, color, fill, stroke', opacity: 'opacity', shadow: 'box-shadow', transform: 'transform' }, transitionTimingFunction: { DEFAULT: 'cubic-bezier(0.4, 0, 0.2, 1)', linear: 'linear', in: 'cubic-bezier(0.4, 0, 1, 1)', out: 'cubic-bezier(0, 0, 0.2, 1)', 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)' }, translate: (theme, { negative }) => ({ ...theme('spacing'), ...negative(theme('spacing')), '1/2': '50%', '1/3': '33.333333%', '2/3': '66.666667%', '1/4': '25%', '2/4': '50%', '3/4': '75%', full: '100%', '-1/2': '-50%', '-1/3': '-33.333333%', '-2/3': '-66.666667%', '-1/4': '-25%', '-2/4': '-50%', '-3/4': '-75%', '-full': '-100%' }), width: (theme) => ({ auto: 'auto', ...theme('spacing'), '1/2': '50%', '1/3': '33.333333%', '2/3': '66.666667%', '1/4': '25%', '2/4': '50%', '3/4': '75%', '1/5': '20%', '2/5': '40%', '3/5': '60%', '4/5': '80%', '1/6': '16.666667%', '2/6': '33.333333%', '3/6': '50%', '4/6': '66.666667%', '5/6': '83.333333%', '1/12': '8.333333%', '2/12': '16.666667%', '3/12': '25%', '4/12': '33.333333%', '5/12': '41.666667%', '6/12': '50%', '7/12': '58.333333%', '8/12': '66.666667%', '9/12': '75%', '10/12': '83.333333%', '11/12': '91.666667%', full: '100%', screen: '100vw', min: 'min-content', max: 'max-content' }), zIndex: { auto: 'auto', 0: '0', 10: '10', 20: '20', 30: '30', 40: '40', 50: '50' } }, variantOrder: [ 'first', 'last', 'odd', 'even', 'visited', 'checked', 'group-hover', 'group-focus', 'focus-within', 'hover', 'focus', 'focus-visible', 'active', 'disabled' ], variants: { accessibility: ['responsive', 'focus-within', 'focus'], alignContent: ['responsive'], alignItems: ['responsive'], alignSelf: ['responsive'], animation: ['responsive'], appearance: ['responsive'], backgroundAttachment: ['responsive'], backgroundClip: ['responsive'], backgroundColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], backgroundImage: ['responsive'], backgroundOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], backgroundPosition: ['responsive'], backgroundRepeat: ['responsive'], backgroundSize: ['responsive'], borderCollapse: ['responsive'], borderColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], borderOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], borderRadius: ['responsive'], borderStyle: ['responsive'], borderWidth: ['responsive'], boxShadow: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], boxSizing: ['responsive'], clear: ['responsive'], container: ['responsive'], cursor: ['responsive'], display: ['responsive'], divideColor: ['responsive', 'dark'], divideOpacity: ['responsive'], divideStyle: ['responsive'], divideWidth: ['responsive'], fill: ['responsive'], flex: ['responsive'], flexDirection: ['responsive'], flexGrow: ['responsive'], flexShrink: ['responsive'], flexWrap: ['responsive'], float: ['responsive'], fontFamily: ['responsive'], fontSize: ['responsive'], fontSmoothing: ['responsive'], fontStyle: ['responsive'], fontVariantNumeric: ['responsive'], fontWeight: ['responsive'], gap: ['responsive'], gradientColorStops: ['responsive', 'dark', 'hover', 'focus'], gridAutoColumns: ['responsive'], gridAutoFlow: ['responsive'], gridAutoRows: ['responsive'], gridColumn: ['responsive'], gridColumnEnd: ['responsive'], gridColumnStart: ['responsive'], gridRow: ['responsive'], gridRowEnd: ['responsive'], gridRowStart: ['responsive'], gridTemplateColumns: ['responsive'], gridTemplateRows: ['responsive'], height: ['responsive'], inset: ['responsive'], justifyContent: ['responsive'], justifyItems: ['responsive'], justifySelf: ['responsive'], letterSpacing: ['responsive'], lineHeight: ['responsive'], listStylePosition: ['responsive'], listStyleType: ['responsive'], margin: ['responsive'], maxHeight: ['responsive'], maxWidth: ['responsive'], minHeight: ['responsive'], minWidth: ['responsive'], objectFit: ['responsive'], objectPosition: ['responsive'], opacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], order: ['responsive'], outline: ['responsive', 'focus-within', 'focus'], overflow: ['responsive'], overscrollBehavior: ['responsive'], padding: ['responsive'], placeContent: ['responsive'], placeItems: ['responsive'], placeSelf: ['responsive'], placeholderColor: ['responsive', 'dark', 'focus'], placeholderOpacity: ['responsive', 'focus'], pointerEvents: ['responsive'], position: ['responsive'], resize: ['responsive'], ringColor: ['responsive', 'dark', 'focus-within', 'focus'], ringOffsetColor: ['responsive', 'dark', 'focus-within', 'focus'], ringOffsetWidth: ['responsive', 'focus-within', 'focus'], ringOpacity: ['responsive', 'focus-within', 'focus'], ringWidth: ['responsive', 'focus-within', 'focus'], rotate: ['responsive', 'hover', 'focus'], scale: ['responsive', 'hover', 'focus'], skew: ['responsive', 'hover', 'focus'], space: ['responsive'], stroke: ['responsive'], strokeWidth: ['responsive'], tableLayout: ['responsive'], textAlign: ['responsive'], textColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], textDecoration: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], textOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], textOverflow: ['responsive'], textTransform: ['responsive'], transform: ['responsive'], transformOrigin: ['responsive'], transitionDelay: ['responsive'], transitionDuration: ['responsive'], transitionProperty: ['responsive'], transitionTimingFunction: ['responsive'], translate: ['responsive', 'hover', 'focus'], userSelect: ['responsive'], verticalAlign: ['responsive'], visibility: ['responsive'], whitespace: ['responsive'], width: ['responsive'], wordBreak: ['responsive'], zIndex: ['responsive', 'focus-within', 'focus'] }, plugins: [] } ================================================ FILE: dashboard/tests/e2e/.eslintrc.js ================================================ module.exports = { plugins: [ 'cypress' ], env: { mocha: true, 'cypress/globals': true }, rules: { strict: 'off' } } ================================================ FILE: dashboard/tests/e2e/plugins/index.js ================================================ /* eslint-disable arrow-body-style */ // https://docs.cypress.io/guides/guides/plugins-guide.html // if you need a custom webpack configuration you can uncomment the following import // and then use the `file:preprocessor` event // as explained in the cypress docs // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples // /* eslint-disable import/no-extraneous-dependencies, global-require */ // const webpack = require('@cypress/webpack-preprocessor') module.exports = (on, config) => { // on('file:preprocessor', webpack({ // webpackOptions: require('@vue/cli-service/webpack.config'), // watchOptions: {} // })) return Object.assign({}, config, { fixturesFolder: 'tests/e2e/fixtures', integrationFolder: 'tests/e2e/specs', screenshotsFolder: 'tests/e2e/screenshots', videosFolder: 'tests/e2e/videos', supportFile: 'tests/e2e/support/index.js' }) } ================================================ FILE: dashboard/tests/e2e/specs/credencials.js ================================================ const APP_URL = process.env.APP_URL || 'http://localhost:8080' describe('Credencials', () => { it('should generate an api_key', () => { cy.visit(APP_URL) cy.get('#header-login-button').click() cy.get('#modal-login') cy.get('#email-field').type('igor@igor.me') cy.get('#password-field').type('1234') cy.get('#submit-button').click() cy.wait(4000) cy.visit(`${APP_URL}/credencials`) cy.wait(2000) const oldApikey = cy.get('#apikey').invoke('text') cy.get('#generate-apikey').click() cy.wait(2000) const newApikey = cy.get('#apikey').invoke('text') expect(oldApikey).to.not.equal(newApikey) }) }) ================================================ FILE: dashboard/tests/e2e/specs/home.js ================================================ const APP_URL = process.env.APP_URL || 'http://localhost:8080' describe('Home', () => { it('should render create account modal when click on cta create account button', () => { cy.visit(APP_URL) cy.get('#cta-create-account-button').click() cy.get('#modal-create-account') }) it('should render create account modal when click on header create account button', () => { cy.visit(APP_URL) cy.get('#header-create-account-button').click() cy.get('#modal-create-account') }) it('should render login modal when click on header login button', () => { cy.visit(APP_URL) cy.get('#header-login-button').click() cy.get('#modal-login') }) it('should login with an email and password', () => { cy.visit(APP_URL) cy.get('#header-login-button').click() cy.get('#modal-login') cy.get('#email-field').type('igor@igor.me') cy.get('#password-field').type('1234') cy.get('#submit-button').click() cy.url().should('include', '/feedbacks') }) it('should show an error with an invalid email', () => { cy.visit(APP_URL) cy.get('#header-login-button').click() cy.get('#modal-login') cy.get('#email-field').type('igor@') cy.get('#password-field').type('1234') cy.get('#submit-button').click() cy.get('#email-error') }) it('should logout work correctly', () => { cy.visit(APP_URL) cy.get('#header-login-button').click() cy.get('#modal-login') cy.get('#email-field').type('igor@igor.me') cy.get('#password-field').type('1234') cy.get('#submit-button').click() cy.url().should('include', '/feedbacks') cy.get('#logout-button').click() cy.url().should('include', '/') }) }) ================================================ FILE: dashboard/tests/e2e/support/commands.js ================================================ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite // existing commands. // // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** // // // -- This is a parent command -- // Cypress.Commands.add("login", (email, password) => { ... }) // // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // // // -- This is a dual command -- // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // // // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) ================================================ FILE: dashboard/tests/e2e/support/index.js ================================================ // *********************************************************** // This example support/index.js is processed and // loaded automatically before your test files. // // This is a great place to put global configuration and // behavior that modifies Cypress. // // You can change the location of this file or turn off // automatically serving support files with the // 'supportFile' configuration option. // // You can read more here: // https://on.cypress.io/configuration // *********************************************************** // Import commands.js using ES2015 syntax: import './commands' // Alternatively you can use CommonJS syntax: // require('./commands') ================================================ FILE: try-widget/index.html ================================================ Minha loja de biscoitos

Meu site de venda de biscoitos

================================================ FILE: widget/.browserslistrc ================================================ > 1% last 2 versions not dead ================================================ FILE: widget/.editorconfig ================================================ [*.{js,jsx,ts,tsx,vue}] indent_style = space indent_size = 2 trim_trailing_whitespace = true insert_final_newline = true ================================================ FILE: widget/.eslintrc.js ================================================ module.exports = { root: true, env: { node: true }, extends: [ 'plugin:vue/vue3-essential', '@vue/standard', '@vue/typescript/recommended' ], parserOptions: { ecmaVersion: 2020 }, rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' }, overrides: [ { files: [ '**/*.{j,t}s?(x)', ], env: { jest: true } } ] } ================================================ FILE: widget/.gitignore ================================================ .DS_Store node_modules /dist /tests/e2e/videos/ /tests/e2e/screenshots/ # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: widget/Dockerfile ================================================ FROM node:13-alpine as build WORKDIR / COPY . . ENV NODE_ENV=production RUN npm install --production RUN npm run build FROM nginx:1.18.0-alpine as final WORKDIR / COPY --from=build ./dist /usr/share/nginx/html ================================================ FILE: widget/README.md ================================================ # widget ## Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Run your unit tests ``` npm run test:unit ``` ### Run your end-to-end tests ``` npm run test:e2e ``` ### Lints and fixes files ``` npm run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). ================================================ FILE: widget/babel.config.js ================================================ module.exports = { presets: [ '@vue/app' ] } ================================================ FILE: widget/cypress.json ================================================ { "pluginsFile": "tests/e2e/plugins/index.js" } ================================================ FILE: widget/jest.config.js ================================================ module.exports = { preset: '@vue/cli-plugin-unit-jest/presets/typescript', testMatch: [ '**/*.spec.js' ], transform: { '^.+\\.vue$': 'vue-jest', '^.+\\.(ts|tsx)$': 'ts-jest' } } ================================================ FILE: widget/package.json ================================================ { "name": "widget", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "test:unit": "vue-cli-service test:unit", "test:e2e": "vue-cli-service test:e2e", "lint": "vue-cli-service lint" }, "dependencies": { "@tailwindcss/postcss7-compat": "^2.0.3", "animate.css": "^4.1.1", "autoprefixer": "^9.8.6", "axios": "^0.21.1", "postcss": "^7.0.35", "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.3", "vue": "^3.0.0" }, "devDependencies": { "@types/jest": "^24.9.1", "@typescript-eslint/eslint-plugin": "^2.33.0", "@typescript-eslint/parser": "^2.33.0", "@vue/babel-preset-app": "^4.5.11", "@vue/cli-plugin-e2e-cypress": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-plugin-typescript": "~4.5.0", "@vue/cli-plugin-unit-jest": "~4.5.0", "@vue/cli-service": "~4.5.0", "@vue/compiler-sfc": "^3.0.0", "@vue/eslint-config-standard": "^5.1.2", "@vue/eslint-config-typescript": "^5.0.2", "@vue/test-utils": "^2.0.0-0", "babel-jest": "^26.6.3", "eslint": "^6.7.2", "eslint-plugin-import": "^2.20.2", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.0", "eslint-plugin-vue": "^7.0.0-0", "jest": "^26.6.3", "lint-staged": "^9.5.0", "ts-jest": "^26.5.0", "typescript": "^3.9.7", "vue-jest": "^5.0.0-0" }, "gitHooks": { "pre-commit": "lint-staged" }, "lint-staged": { "*.{js,jsx,vue,ts,tsx}": [ "vue-cli-service lint", "git add" ] } } ================================================ FILE: widget/palette.js ================================================ module.exports = { brand: { main: '#EF4983', gray: '#F9F9F9', info: '#8296FB', success: '#63C3BE', graydark: '#C0BCB0', warning: '#E4B52E', danger: '#F88676' }, mediumslateblue: { 50: '#f6f9fd', 100: '#e8f3fd', 200: '#cbdefb', 300: '#abc4fb', 400: '#8296fb', 500: '#5667fb', 600: '#3d46f7', 700: '#3137e5', 800: '#272cb9', 900: '#202591' }, slateblue: { 50: '#f5f8fc', 100: '#eaf0fc', 200: '#d3d8fa', 300: '#babcf9', 400: '#9c8ff9', 500: '#7a61f9', 600: '#5d41f4', 700: '#4933e1', 800: '#382ab5', 900: '#2d248f' }, mediumorchid: { 50: '#f9f7fa', 100: '#f7edf9', 200: '#efd1f5', 300: '#e8aef1', 400: '#e47cec', 500: '#e04fe7', 600: '#c932d7', 700: '#9a27b5', 800: '#712186', 900: '#571c66' }, deeppink: { 50: '#fcf9f9', 100: '#fcf0f3', 200: '#fad3e6', 300: '#f9acd1', 400: '#fa73ab', 500: '#fb4783', 600: '#f42a5c', 700: '#d6214a', 800: '#a71b3a', 900: '#83172f' }, tomato: { 50: '#fcf9f6', 100: '#fcf0ed', 200: '#fadad6', 300: '#f8b9b0', 400: '#f88676', 500: '#f85b47', 600: '#ee392e', 700: '#cc2b2b', 800: '#9e2328', 900: '#7c1d23' }, chocolate: { 50: '#fbf8f2', 100: '#fbf1e1', 200: '#f8e1bb', 300: '#f5c782', 400: '#f39d41', 500: '#f1741e', 600: '#e24f13', 700: '#bc3b16', 800: '#922e1a', 900: '#732619' }, goldenrod: { 50: '#fbfaf4', 100: '#faf6e0', 200: '#f5ebb0', 300: '#efd86f', 400: '#e4b52e', 500: '#d69111', 600: '#b76b0a', 700: '#8b500e', 800: '#663d12', 900: '#4e3012' }, darkgoldenrod: { 50: '#fbfaf6', 100: '#f9f8e7', 200: '#f2efbc', 300: '#e8de7f', 400: '#d3bd3a', 500: '#b79b17', 600: '#8e750d', 700: '#685910', 800: '#4b4213', 900: '#393414' }, lightseagreen: { 50: '#f5fafa', 100: '#eaf7f5', 200: '#ceeee8', 300: '#a7dfd9', 400: '#63c3be', 500: '#30a29c', 600: '#237f79', 700: '#236460', 800: '#204c4a', 900: '#1b3d3c' }, cornflowerblue: { 50: '#f4fafc', 100: '#e2f7fb', 200: '#bdeaf7', 300: '#8fd8f5', 400: '#4fb6f1', 500: '#238fed', 600: '#1a6bdf', 700: '#1b54bd', 800: '#19418c', 900: '#15346b' } } ================================================ FILE: widget/postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {} } } ================================================ FILE: widget/public/index.html ================================================ <%= htmlWebpackPlugin.options.title %>
================================================ FILE: widget/public/init.js ================================================ function init (apiKey) { async function handleLoadWidget () { const page = `${window.location.origin}${window.location.pathname}` const fp = await window.FingerprintJS.load() const fingerprint = await fp.get() const WIDGET_URL = `https://igorhalfeld-feedbacker-widget.netlify.app?api_key=${apiKey}&page=${page}&fingerprint=${fingerprint.visitorId}` const config = { method: 'HEAD' } const res = await fetch(`https://backend-treinamento-vue3.vercel.app/apikey/exists?apikey=${apiKey}`, config) if (res.status === 200) { const iframe = document.createElement('iframe') iframe.src = WIDGET_URL iframe.id = 'feedbacker-iframe' iframe.style.position = 'fixed' iframe.style.bottom = '0px' iframe.style.right = '0px' iframe.style.overflow = 'hidden' iframe.style.border = '0px' iframe.style.zIndex = '99999' document.body.appendChild(iframe) window.addEventListener('message', (event) => { if (!event.data.isWidget) return if (event.data.isOpen) { iframe.width = '100%' iframe.height = '100%' } else { iframe.width = '300px' iframe.height = '150px' } }) return } console.log('* [feedbacker] was not loaded because apikey does not exists') } const script = document.createElement('script') script.src = '//cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@3/dist/fp.min.js' script.async = 'async' script.addEventListener('load', handleLoadWidget) document.body.appendChild(script) } window.init = init ================================================ FILE: widget/src/App.vue ================================================ ================================================ FILE: widget/src/assets/css/fonts.css ================================================ @font-face { font-family: "RobotoRegular"; src: local("Roboto Regular"), local("Roboto-Regular"), url("../fonts/Roboto-Regular.ttf") format("truetype"); font-weight: 400; font-style: normal; } @font-face { font-family: "RobotoMedium"; src: local("Roboto Medium"), local("Roboto-Medium"), url("../fonts/Roboto-Medium.ttf") format("truetype"); font-weight: 500; font-style: normal; } @font-face { font-family: "RobotoBold"; src: local("Roboto Bold"), local("Roboto-Bold"), url("../fonts/Roboto-Bold.ttf") format("truetype"); font-weight: 700; font-style: normal; } @font-face { font-family: "RobotoBlack"; src: local("Roboto Black"), local("Roboto-Black"), url("../fonts/Roboto-Black.ttf") format("truetype"); font-weight: 900; font-style: normal; } ================================================ FILE: widget/src/assets/css/tailwind.css ================================================ /*! @import */ @tailwind base; @tailwind components; @tailwind utilities; html, body, #app { width: 100%; height: 100%; } ================================================ FILE: widget/src/components/Icon/ArrowRight.vue ================================================ ================================================ FILE: widget/src/components/Icon/Atention.vue ================================================ ================================================ FILE: widget/src/components/Icon/Chat.vue ================================================ ================================================ FILE: widget/src/components/Icon/Check.vue ================================================ ================================================ FILE: widget/src/components/Icon/ChevronDown.vue ================================================ ================================================ FILE: widget/src/components/Icon/Close.vue ================================================ ================================================ FILE: widget/src/components/Icon/Copy.vue ================================================ ================================================ FILE: widget/src/components/Icon/Loading.vue ================================================ ================================================ FILE: widget/src/components/Icon/index.vue ================================================ ================================================ FILE: widget/src/components/Wizard/Error.vue ================================================ ================================================ FILE: widget/src/components/Wizard/SelectFeedbackType.vue ================================================ ================================================ FILE: widget/src/components/Wizard/Success.vue ================================================ ================================================ FILE: widget/src/components/Wizard/WriteAFeedback.vue ================================================ ================================================ FILE: widget/src/components/Wizard/index.vue ================================================ ================================================ FILE: widget/src/hooks/iframe.ts ================================================ import { setApiKey, setCurrentPage, setFingerprint } from '../store' interface IframeControl { updateCoreValuesOnStore(): void; notifyOpen(): void; notifyClose(): void; } export default function useIframeControl (): IframeControl { function updateCoreValuesOnStore (): void { if (process.env.NODE_ENV === 'production') { const query = new URLSearchParams(window.location.search) const apiKey = query.get('api_key') ?? '' const page = query.get('page') ?? '' const fingerprint = query.get('fingerprint') ?? '' setCurrentPage(page) setApiKey(apiKey) setFingerprint(fingerprint) return } setCurrentPage('https://playground-url.com') setApiKey('fcd5015c-10d3-4e9c-b395-ec7ed8850165') setFingerprint('123123123123123') } function notifyOpen (): void { window.parent.postMessage({ isWidget: true, isOpen: true }, '*') } function notifyClose (): void { window.parent.postMessage({ isWidget: true, isOpen: false }, '*') } return { updateCoreValuesOnStore, notifyClose, notifyOpen } } ================================================ FILE: widget/src/hooks/navigation.ts ================================================ import useStore from './store' import { setCurrentComponent, setFeedbackType } from '../store' export interface Navigation { next(): void; back(): void; setErrorState(): void; setSuccessState(): void; } export default function useNavigation (): Navigation { const store = useStore() function setErrorState (): void { setCurrentComponent('Error') } function setSuccessState (): void { setCurrentComponent('Success') } function next (): void { if (store.currentComponent === 'SelectFeedbackType') { setCurrentComponent('WriteAFeedback') } } function back (): void { if (store.currentComponent === 'WriteAFeedback') { setCurrentComponent('SelectFeedbackType') setFeedbackType('') } } return { next, back, setErrorState, setSuccessState } } ================================================ FILE: widget/src/hooks/store.ts ================================================ import Store, { StoreState } from '../store' export default function useStore (): StoreState { return Store } ================================================ FILE: widget/src/main.ts ================================================ import { createApp } from 'vue' import Playground from './views/Playground/index.vue' import App from './App.vue' import { setup } from './utils/bootstrap' import '@/assets/css/tailwind.css' import '@/assets/css/fonts.css' import 'animate.css' setup({ onProduction: () => { createApp(App).mount('#app') }, onDevelopment: () => { createApp(Playground).mount('#app') } }) ================================================ FILE: widget/src/services/feedbacks.ts ================================================ import { AxiosInstance } from 'axios' import { Feedback } from '../types/feedback' import { RequestError } from '../types/error' type Create = { data: Feedback; errors: RequestError | null; } type CreatePayload = { type: string; text: string; fingerprint: string; device: string; page: string; apiKey: string; } export interface FeedbackServiceInterface { create(payload: CreatePayload): Promise; } function FeedbacksService (httpClient: AxiosInstance): FeedbackServiceInterface { async function create (payload: CreatePayload): Promise { const response = await httpClient.post('/feedbacks', payload) let errors: RequestError | null = null if (!response.data) { errors = { status: response.request.status, statusText: response.request.statusText } } return { data: response.data, errors } } return { create } } export default FeedbacksService ================================================ FILE: widget/src/services/index.ts ================================================ import axios from 'axios' import FeedbacksService from './feedbacks' const API_ENVS = { production: 'https://backend-treinamento-vue3.vercel.app', development: '', local: 'http://localhost:3000' } const httpClient = axios.create({ baseURL: API_ENVS[process.env.NODE_ENV] || API_ENVS.local }) httpClient.interceptors.response.use((response) => { return response }, (error) => { const canThrowAnError = error.request.status === 0 || error.request.status === 500 if (canThrowAnError) { throw new Error(error.message) } return error }) export default { feedbacks: FeedbacksService(httpClient) } ================================================ FILE: widget/src/shims-vue.d.ts ================================================ /* eslint-disable */ declare module '*.vue' { import type { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component } ================================================ FILE: widget/src/store/index.ts ================================================ import { reactive, readonly } from 'vue' export type StoreState = { currentComponent: string; feedbackType: string; message: string; apiKey: string; fingerprint: string; currentPage: string; } const initialState: StoreState = { currentComponent: 'SelectFeedbackType', message: '', feedbackType: '', fingerprint: '', apiKey: '', currentPage: '' } const state = reactive({ ...initialState }) export function setCurrentComponent (component: string): void { state.currentComponent = component } export function setMessage (message: string): void { state.message = message } export function setFeedbackType (type: string): void { state.feedbackType = type } export function setCurrentPage (page: string): void { state.currentPage = page } export function setApiKey (apiKey: string): void { state.apiKey = apiKey } export function setFingerprint (fingerprint: string): void { state.fingerprint = fingerprint } export function resetStore (): void { setCurrentComponent(initialState.currentComponent) setMessage(initialState.message) setFeedbackType(initialState.feedbackType) setCurrentPage(initialState.currentPage) setApiKey(initialState.apiKey) setFingerprint(initialState.fingerprint) } export default readonly(state) ================================================ FILE: widget/src/types/error.ts ================================================ export type RequestError = { status: number; statusText: string; } ================================================ FILE: widget/src/types/feedback.ts ================================================ export type Feedback = { type: string; text: string; fingerprint: string; device: string; page: string; apiKey: string; createdAt: string; } ================================================ FILE: widget/src/utils/bootstrap.ts ================================================ interface SetupPayload { onProduction: () => void; onDevelopment: () => void; } export function setup ({ onProduction, onDevelopment }: SetupPayload) { if (process.env.NODE_ENV !== 'production') { onDevelopment() return } onProduction() } ================================================ FILE: widget/src/views/Playground/Playground.spec.js ================================================ import { shallowMount } from '@vue/test-utils' import Playground from './index.vue' describe('', () => { it('should component render correctly', () => { const wrapper = shallowMount(Playground) expect(wrapper).toMatchSnapshot() }) }) ================================================ FILE: widget/src/views/Playground/__snapshots__/Playground.spec.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` should component render correctly 1`] = ` VueWrapper { "__app": Object { "_component": Object { "__emits": Object {}, "__props": Array [ Object {}, Array [], ], "name": "VTU_ROOT", "render": [Function], }, "_container":

Playground

Este é o playground, use para testar o widget.

🚀

👀

🚨

, "_context": Object { "app": [Circular], "components": Object { "transition": Object { "name": "transition", "props": undefined, "render": [Function], }, "transition-group": Object { "name": "transition-group", "props": undefined, "render": [Function], }, }, "config": Object { "errorHandler": undefined, "globalProperties": Object {}, "isCustomElement": [Function], "isNativeTag": [Function], "optionMergeStrategies": Object {}, "performance": false, "warnHandler": undefined, }, "directives": Object {}, "mixins": Array [ Object { "__emits": null, "__props": Array [], "beforeCreate": [Function], }, ], "provides": Object {}, "reload": [Function], }, "_props": null, "_uid": 0, "component": [Function], "config": Object { "errorHandler": undefined, "globalProperties": Object {}, "isCustomElement": [Function], "isNativeTag": [Function], "optionMergeStrategies": Object {}, "performance": false, "warnHandler": undefined, }, "directive": [Function], "mixin": [Function], "mount": [Function], "provide": [Function], "unmount": [Function], "use": [Function], "version": "3.0.5", }, "__setProps": [Function], "componentVM": Object {}, "rootVM": Object {}, } `; ================================================ FILE: widget/src/views/Playground/index.vue ================================================ ================================================ FILE: widget/src/views/Widget/Box.vue ================================================ ================================================ FILE: widget/src/views/Widget/Standby.vue ================================================ ================================================ FILE: widget/src/views/Widget/index.vue ================================================ ================================================ FILE: widget/tailwind.config.js ================================================ const colors = require('tailwindcss/colors') const palette = require('./palette') module.exports = { purge: [ './src/**/*.html', './src/**/*.vue', './src/**/*.jsx' ], presets: [], darkMode: false, // or 'media' or 'class' theme: { extend: { colors: palette }, screens: { sm: '640px', md: '768px', lg: '1024px', xl: '1280px', '2xl': '1536px' }, colors: { transparent: 'transparent', current: 'currentColor', black: colors.black, white: colors.white, gray: colors.coolGray, red: colors.red, yellow: colors.amber, green: colors.emerald, blue: colors.blue, indigo: colors.indigo, purple: colors.violet, pink: colors.pink }, spacing: { px: '1px', 0: '0px', 0.5: '0.125rem', 1: '0.25rem', 1.5: '0.375rem', 2: '0.5rem', 2.5: '0.625rem', 3: '0.75rem', 3.5: '0.875rem', 4: '1rem', 5: '1.25rem', 6: '1.5rem', 7: '1.75rem', 8: '2rem', 9: '2.25rem', 10: '2.5rem', 11: '2.75rem', 12: '3rem', 14: '3.5rem', 16: '4rem', 20: '5rem', 24: '6rem', 28: '7rem', 32: '8rem', 36: '9rem', 40: '10rem', 44: '11rem', 48: '12rem', 52: '13rem', 56: '14rem', 60: '15rem', 64: '16rem', 72: '18rem', 80: '20rem', 96: '24rem' }, animation: { none: 'none', spin: 'spin 1s linear infinite', ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite', pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', bounce: 'bounce 1s infinite' }, backgroundColor: (theme) => theme('colors'), backgroundImage: { none: 'none', 'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))', 'gradient-to-tr': 'linear-gradient(to top right, var(--tw-gradient-stops))', 'gradient-to-r': 'linear-gradient(to right, var(--tw-gradient-stops))', 'gradient-to-br': 'linear-gradient(to bottom right, var(--tw-gradient-stops))', 'gradient-to-b': 'linear-gradient(to bottom, var(--tw-gradient-stops))', 'gradient-to-bl': 'linear-gradient(to bottom left, var(--tw-gradient-stops))', 'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))', 'gradient-to-tl': 'linear-gradient(to top left, var(--tw-gradient-stops))' }, backgroundOpacity: (theme) => theme('opacity'), backgroundPosition: { bottom: 'bottom', center: 'center', left: 'left', 'left-bottom': 'left bottom', 'left-top': 'left top', right: 'right', 'right-bottom': 'right bottom', 'right-top': 'right top', top: 'top' }, backgroundSize: { auto: 'auto', cover: 'cover', contain: 'contain' }, borderColor: (theme) => ({ ...theme('colors'), DEFAULT: theme('colors.gray.200', 'currentColor') }), borderOpacity: (theme) => theme('opacity'), borderRadius: { none: '0px', sm: '0.125rem', DEFAULT: '0.25rem', md: '0.375rem', lg: '0.5rem', xl: '0.75rem', '2xl': '1rem', '3xl': '1.5rem', full: '9999px' }, borderWidth: { DEFAULT: '1px', 0: '0px', 2: '2px', 4: '4px', 8: '8px' }, boxShadow: { sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)', none: 'none' }, container: {}, cursor: { auto: 'auto', default: 'default', pointer: 'pointer', wait: 'wait', text: 'text', move: 'move', 'not-allowed': 'not-allowed' }, divideColor: (theme) => theme('borderColor'), divideOpacity: (theme) => theme('borderOpacity'), divideWidth: (theme) => theme('borderWidth'), fill: { current: 'currentColor' }, flex: { 1: '1 1 0%', auto: '1 1 auto', initial: '0 1 auto', none: 'none' }, flexGrow: { 0: '0', DEFAULT: '1' }, flexShrink: { 0: '0', DEFAULT: '1' }, fontFamily: { regular: ['RobotoRegular'], medium: ['RobotoMedium'], bold: ['RobotoBold'], black: ['RobotoBlack'], sans: [ 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"' ], serif: ['ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'], mono: [ 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace' ] }, fontSize: { xs: ['0.75rem', { lineHeight: '1rem' }], sm: ['0.875rem', { lineHeight: '1.25rem' }], base: ['1rem', { lineHeight: '1.5rem' }], lg: ['1.125rem', { lineHeight: '1.75rem' }], xl: ['1.25rem', { lineHeight: '1.75rem' }], '2xl': ['1.5rem', { lineHeight: '2rem' }], '3xl': ['1.875rem', { lineHeight: '2.25rem' }], '4xl': ['2.25rem', { lineHeight: '2.5rem' }], '5xl': ['3rem', { lineHeight: '1' }], '6xl': ['3.75rem', { lineHeight: '1' }], '7xl': ['4.5rem', { lineHeight: '1' }], '8xl': ['6rem', { lineHeight: '1' }], '9xl': ['8rem', { lineHeight: '1' }] }, fontWeight: { thin: '100', extralight: '200', light: '300', normal: '400', medium: '500', semibold: '600', bold: '700', extrabold: '800', black: '900' }, gap: (theme) => theme('spacing'), gradientColorStops: (theme) => theme('colors'), gridAutoColumns: { auto: 'auto', min: 'min-content', max: 'max-content', fr: 'minmax(0, 1fr)' }, gridAutoRows: { auto: 'auto', min: 'min-content', max: 'max-content', fr: 'minmax(0, 1fr)' }, gridColumn: { auto: 'auto', 'span-1': 'span 1 / span 1', 'span-2': 'span 2 / span 2', 'span-3': 'span 3 / span 3', 'span-4': 'span 4 / span 4', 'span-5': 'span 5 / span 5', 'span-6': 'span 6 / span 6', 'span-7': 'span 7 / span 7', 'span-8': 'span 8 / span 8', 'span-9': 'span 9 / span 9', 'span-10': 'span 10 / span 10', 'span-11': 'span 11 / span 11', 'span-12': 'span 12 / span 12', 'span-full': '1 / -1' }, gridColumnEnd: { auto: 'auto', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12', 13: '13' }, gridColumnStart: { auto: 'auto', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12', 13: '13' }, gridRow: { auto: 'auto', 'span-1': 'span 1 / span 1', 'span-2': 'span 2 / span 2', 'span-3': 'span 3 / span 3', 'span-4': 'span 4 / span 4', 'span-5': 'span 5 / span 5', 'span-6': 'span 6 / span 6', 'span-full': '1 / -1' }, gridRowStart: { auto: 'auto', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7' }, gridRowEnd: { auto: 'auto', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7' }, transformOrigin: { center: 'center', top: 'top', 'top-right': 'top right', right: 'right', 'bottom-right': 'bottom right', bottom: 'bottom', 'bottom-left': 'bottom left', left: 'left', 'top-left': 'top left' }, gridTemplateColumns: { none: 'none', 1: 'repeat(1, minmax(0, 1fr))', 2: 'repeat(2, minmax(0, 1fr))', 3: 'repeat(3, minmax(0, 1fr))', 4: 'repeat(4, minmax(0, 1fr))', 5: 'repeat(5, minmax(0, 1fr))', 6: 'repeat(6, minmax(0, 1fr))', 7: 'repeat(7, minmax(0, 1fr))', 8: 'repeat(8, minmax(0, 1fr))', 9: 'repeat(9, minmax(0, 1fr))', 10: 'repeat(10, minmax(0, 1fr))', 11: 'repeat(11, minmax(0, 1fr))', 12: 'repeat(12, minmax(0, 1fr))' }, gridTemplateRows: { none: 'none', 1: 'repeat(1, minmax(0, 1fr))', 2: 'repeat(2, minmax(0, 1fr))', 3: 'repeat(3, minmax(0, 1fr))', 4: 'repeat(4, minmax(0, 1fr))', 5: 'repeat(5, minmax(0, 1fr))', 6: 'repeat(6, minmax(0, 1fr))' }, height: (theme) => ({ auto: 'auto', ...theme('spacing'), '1/2': '50%', '1/3': '33.333333%', '2/3': '66.666667%', '1/4': '25%', '2/4': '50%', '3/4': '75%', '1/5': '20%', '2/5': '40%', '3/5': '60%', '4/5': '80%', '1/6': '16.666667%', '2/6': '33.333333%', '3/6': '50%', '4/6': '66.666667%', '5/6': '83.333333%', full: '100%', screen: '100vh' }), inset: (theme, { negative }) => ({ auto: 'auto', ...theme('spacing'), ...negative(theme('spacing')), '1/2': '50%', '1/3': '33.333333%', '2/3': '66.666667%', '1/4': '25%', '2/4': '50%', '3/4': '75%', full: '100%', '-1/2': '-50%', '-1/3': '-33.333333%', '-2/3': '-66.666667%', '-1/4': '-25%', '-2/4': '-50%', '-3/4': '-75%', '-full': '-100%' }), keyframes: { spin: { to: { transform: 'rotate(360deg)' } }, ping: { '75%, 100%': { transform: 'scale(2)', opacity: '0' } }, pulse: { '50%': { opacity: '.5' } }, bounce: { '0%, 100%': { transform: 'translateY(-25%)', animationTimingFunction: 'cubic-bezier(0.8,0,1,1)' }, '50%': { transform: 'none', animationTimingFunction: 'cubic-bezier(0,0,0.2,1)' } } }, letterSpacing: { tighter: '-0.05em', tight: '-0.025em', normal: '0em', wide: '0.025em', wider: '0.05em', widest: '0.1em' }, lineHeight: { none: '1', tight: '1.25', snug: '1.375', normal: '1.5', relaxed: '1.625', loose: '2', 3: '.75rem', 4: '1rem', 5: '1.25rem', 6: '1.5rem', 7: '1.75rem', 8: '2rem', 9: '2.25rem', 10: '2.5rem' }, listStyleType: { none: 'none', disc: 'disc', decimal: 'decimal' }, margin: (theme, { negative }) => ({ auto: 'auto', ...theme('spacing'), ...negative(theme('spacing')) }), maxHeight: (theme) => ({ ...theme('spacing'), full: '100%', screen: '100vh' }), maxWidth: (theme, { breakpoints }) => ({ none: 'none', 0: '0rem', xs: '20rem', sm: '24rem', md: '28rem', lg: '32rem', xl: '36rem', '2xl': '42rem', '3xl': '48rem', '4xl': '56rem', '5xl': '64rem', '6xl': '72rem', '7xl': '80rem', full: '100%', min: 'min-content', max: 'max-content', prose: '65ch', ...breakpoints(theme('screens')) }), minHeight: { 0: '0px', full: '100%', screen: '100vh' }, minWidth: { 0: '0px', full: '100%', min: 'min-content', max: 'max-content' }, objectPosition: { bottom: 'bottom', center: 'center', left: 'left', 'left-bottom': 'left bottom', 'left-top': 'left top', right: 'right', 'right-bottom': 'right bottom', 'right-top': 'right top', top: 'top' }, opacity: { 0: '0', 5: '0.05', 10: '0.1', 20: '0.2', 25: '0.25', 30: '0.3', 40: '0.4', 50: '0.5', 60: '0.6', 70: '0.7', 75: '0.75', 80: '0.8', 90: '0.9', 95: '0.95', 100: '1' }, order: { first: '-9999', last: '9999', none: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12' }, outline: { none: ['2px solid transparent', '2px'], white: ['2px dotted white', '2px'], black: ['2px dotted black', '2px'] }, padding: (theme) => theme('spacing'), placeholderColor: (theme) => theme('colors'), placeholderOpacity: (theme) => theme('opacity'), ringColor: (theme) => ({ DEFAULT: theme('colors.blue.500', '#3b82f6'), ...theme('colors') }), ringOffsetColor: (theme) => theme('colors'), ringOffsetWidth: { 0: '0px', 1: '1px', 2: '2px', 4: '4px', 8: '8px' }, ringOpacity: (theme) => ({ DEFAULT: '0.5', ...theme('opacity') }), ringWidth: { DEFAULT: '3px', 0: '0px', 1: '1px', 2: '2px', 4: '4px', 8: '8px' }, rotate: { '-180': '-180deg', '-90': '-90deg', '-45': '-45deg', '-12': '-12deg', '-6': '-6deg', '-3': '-3deg', '-2': '-2deg', '-1': '-1deg', 0: '0deg', 1: '1deg', 2: '2deg', 3: '3deg', 6: '6deg', 12: '12deg', 45: '45deg', 90: '90deg', 180: '180deg' }, scale: { 0: '0', 50: '.5', 75: '.75', 90: '.9', 95: '.95', 100: '1', 105: '1.05', 110: '1.1', 125: '1.25', 150: '1.5' }, skew: { '-12': '-12deg', '-6': '-6deg', '-3': '-3deg', '-2': '-2deg', '-1': '-1deg', 0: '0deg', 1: '1deg', 2: '2deg', 3: '3deg', 6: '6deg', 12: '12deg' }, space: (theme, { negative }) => ({ ...theme('spacing'), ...negative(theme('spacing')) }), stroke: { current: 'currentColor' }, strokeWidth: { 0: '0', 1: '1', 2: '2' }, textColor: (theme) => theme('colors'), textOpacity: (theme) => theme('opacity'), transitionDuration: { DEFAULT: '150ms', 75: '75ms', 100: '100ms', 150: '150ms', 200: '200ms', 300: '300ms', 500: '500ms', 700: '700ms', 1000: '1000ms' }, transitionDelay: { 75: '75ms', 100: '100ms', 150: '150ms', 200: '200ms', 300: '300ms', 500: '500ms', 700: '700ms', 1000: '1000ms' }, transitionProperty: { none: 'none', all: 'all', DEFAULT: 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform', colors: 'background-color, border-color, color, fill, stroke', opacity: 'opacity', shadow: 'box-shadow', transform: 'transform' }, transitionTimingFunction: { DEFAULT: 'cubic-bezier(0.4, 0, 0.2, 1)', linear: 'linear', in: 'cubic-bezier(0.4, 0, 1, 1)', out: 'cubic-bezier(0, 0, 0.2, 1)', 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)' }, translate: (theme, { negative }) => ({ ...theme('spacing'), ...negative(theme('spacing')), '1/2': '50%', '1/3': '33.333333%', '2/3': '66.666667%', '1/4': '25%', '2/4': '50%', '3/4': '75%', full: '100%', '-1/2': '-50%', '-1/3': '-33.333333%', '-2/3': '-66.666667%', '-1/4': '-25%', '-2/4': '-50%', '-3/4': '-75%', '-full': '-100%' }), width: (theme) => ({ auto: 'auto', ...theme('spacing'), '1/2': '50%', '1/3': '33.333333%', '2/3': '66.666667%', '1/4': '25%', '2/4': '50%', '3/4': '75%', '1/5': '20%', '2/5': '40%', '3/5': '60%', '4/5': '80%', '1/6': '16.666667%', '2/6': '33.333333%', '3/6': '50%', '4/6': '66.666667%', '5/6': '83.333333%', '1/12': '8.333333%', '2/12': '16.666667%', '3/12': '25%', '4/12': '33.333333%', '5/12': '41.666667%', '6/12': '50%', '7/12': '58.333333%', '8/12': '66.666667%', '9/12': '75%', '10/12': '83.333333%', '11/12': '91.666667%', full: '100%', screen: '100vw', min: 'min-content', max: 'max-content' }), zIndex: { auto: 'auto', 0: '0', 10: '10', 20: '20', 30: '30', 40: '40', 50: '50' } }, variantOrder: [ 'first', 'last', 'odd', 'even', 'visited', 'checked', 'group-hover', 'group-focus', 'focus-within', 'hover', 'focus', 'focus-visible', 'active', 'disabled' ], variants: { accessibility: ['responsive', 'focus-within', 'focus'], alignContent: ['responsive'], alignItems: ['responsive'], alignSelf: ['responsive'], animation: ['responsive'], appearance: ['responsive'], backgroundAttachment: ['responsive'], backgroundClip: ['responsive'], backgroundColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], backgroundImage: ['responsive'], backgroundOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], backgroundPosition: ['responsive'], backgroundRepeat: ['responsive'], backgroundSize: ['responsive'], borderCollapse: ['responsive'], borderColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], borderOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], borderRadius: ['responsive'], borderStyle: ['responsive'], borderWidth: ['responsive'], boxShadow: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], boxSizing: ['responsive'], clear: ['responsive'], container: ['responsive'], cursor: ['responsive'], display: ['responsive'], divideColor: ['responsive', 'dark'], divideOpacity: ['responsive'], divideStyle: ['responsive'], divideWidth: ['responsive'], fill: ['responsive'], flex: ['responsive'], flexDirection: ['responsive'], flexGrow: ['responsive'], flexShrink: ['responsive'], flexWrap: ['responsive'], float: ['responsive'], fontFamily: ['responsive'], fontSize: ['responsive'], fontSmoothing: ['responsive'], fontStyle: ['responsive'], fontVariantNumeric: ['responsive'], fontWeight: ['responsive'], gap: ['responsive'], gradientColorStops: ['responsive', 'dark', 'hover', 'focus'], gridAutoColumns: ['responsive'], gridAutoFlow: ['responsive'], gridAutoRows: ['responsive'], gridColumn: ['responsive'], gridColumnEnd: ['responsive'], gridColumnStart: ['responsive'], gridRow: ['responsive'], gridRowEnd: ['responsive'], gridRowStart: ['responsive'], gridTemplateColumns: ['responsive'], gridTemplateRows: ['responsive'], height: ['responsive'], inset: ['responsive'], justifyContent: ['responsive'], justifyItems: ['responsive'], justifySelf: ['responsive'], letterSpacing: ['responsive'], lineHeight: ['responsive'], listStylePosition: ['responsive'], listStyleType: ['responsive'], margin: ['responsive'], maxHeight: ['responsive'], maxWidth: ['responsive'], minHeight: ['responsive'], minWidth: ['responsive'], objectFit: ['responsive'], objectPosition: ['responsive'], opacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], order: ['responsive'], outline: ['responsive', 'focus-within', 'focus'], overflow: ['responsive'], overscrollBehavior: ['responsive'], padding: ['responsive'], placeContent: ['responsive'], placeItems: ['responsive'], placeSelf: ['responsive'], placeholderColor: ['responsive', 'dark', 'focus'], placeholderOpacity: ['responsive', 'focus'], pointerEvents: ['responsive'], position: ['responsive'], resize: ['responsive'], ringColor: ['responsive', 'dark', 'focus-within', 'focus'], ringOffsetColor: ['responsive', 'dark', 'focus-within', 'focus'], ringOffsetWidth: ['responsive', 'focus-within', 'focus'], ringOpacity: ['responsive', 'focus-within', 'focus'], ringWidth: ['responsive', 'focus-within', 'focus'], rotate: ['responsive', 'hover', 'focus'], scale: ['responsive', 'hover', 'focus'], skew: ['responsive', 'hover', 'focus'], space: ['responsive'], stroke: ['responsive'], strokeWidth: ['responsive'], tableLayout: ['responsive'], textAlign: ['responsive'], textColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], textDecoration: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], textOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], textOverflow: ['responsive'], textTransform: ['responsive'], transform: ['responsive'], transformOrigin: ['responsive'], transitionDelay: ['responsive'], transitionDuration: ['responsive'], transitionProperty: ['responsive'], transitionTimingFunction: ['responsive'], translate: ['responsive', 'hover', 'focus'], userSelect: ['responsive'], verticalAlign: ['responsive'], visibility: ['responsive'], whitespace: ['responsive'], width: ['responsive'], wordBreak: ['responsive'], zIndex: ['responsive', 'focus-within', 'focus'] }, plugins: [] } ================================================ FILE: widget/tests/e2e/.eslintrc.js ================================================ module.exports = { plugins: [ 'cypress' ], env: { mocha: true, 'cypress/globals': true }, rules: { strict: 'off' } } ================================================ FILE: widget/tests/e2e/plugins/index.js ================================================ /* eslint-disable arrow-body-style */ // https://docs.cypress.io/guides/guides/plugins-guide.html // if you need a custom webpack configuration you can uncomment the following import // and then use the `file:preprocessor` event // as explained in the cypress docs // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples // /* eslint-disable import/no-extraneous-dependencies, global-require */ // const webpack = require('@cypress/webpack-preprocessor') module.exports = (on, config) => { // on('file:preprocessor', webpack({ // webpackOptions: require('@vue/cli-service/webpack.config'), // watchOptions: {} // })) return Object.assign({}, config, { fixturesFolder: 'tests/e2e/fixtures', integrationFolder: 'tests/e2e/specs', screenshotsFolder: 'tests/e2e/screenshots', videosFolder: 'tests/e2e/videos', supportFile: 'tests/e2e/support/index.js' }) } ================================================ FILE: widget/tests/e2e/specs/widget.js ================================================ const APP_URL = process.env.APP_URL || 'http://localhost:8080' describe('Widget', () => { it('Check if widget button are shown', () => { cy.visit(APP_URL) cy.wait(2000) cy.get('#widget-open-button') }) }) ================================================ FILE: widget/tests/e2e/support/commands.js ================================================ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite // existing commands. // // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** // // // -- This is a parent command -- // Cypress.Commands.add("login", (email, password) => { ... }) // // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // // // -- This is a dual command -- // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // // // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) ================================================ FILE: widget/tests/e2e/support/index.js ================================================ // *********************************************************** // This example support/index.js is processed and // loaded automatically before your test files. // // This is a great place to put global configuration and // behavior that modifies Cypress. // // You can change the location of this file or turn off // automatically serving support files with the // 'supportFile' configuration option. // // You can read more here: // https://on.cypress.io/configuration // *********************************************************** // Import commands.js using ES2015 syntax: import './commands' // Alternatively you can use CommonJS syntax: // require('./commands') ================================================ FILE: widget/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "module": "esnext", "strict": true, "jsx": "preserve", "importHelpers": true, "moduleResolution": "node", "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "noImplicitAny": false, "sourceMap": true, "baseUrl": ".", "types": [ "webpack-env", "jest" ], "paths": { "@/*": [ "src/*" ] }, "lib": [ "esnext", "dom", "dom.iterable", "scripthost" ] }, "include": [ "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx" ], "exclude": [ "node_modules" ] }