[
  {
    "path": ".github/workflows/ci-dashboard-e2e.yml",
    "content": "name: Dashboard e2e testing\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ $default-branch ]\n  pull_request:\n    branches: [ $default-branch ]\n\njobs:\n  cypress:\n    defaults:\n      run:\n        working-directory: dashboard\n\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - run: npm install\n\n      - run: npm run build\n        env:\n          NODE_ENV: production\n\n      - name: Run project locally\n        run: |\n          npm install serve\n          $(npm bin)/serve dist -s -p 8080 &\n\n      - name: Run tests\n        uses: cypress-io/github-action@v2\n        with:\t\n          working-directory: dashboard\n          browser: chrome\n          headless: true\n"
  },
  {
    "path": ".github/workflows/ci-dashboard-unit.yml",
    "content": "name: Dashboard unit testing\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ $default-branch ]\n  pull_request:\n    branches: [ $default-branch ]\n\ndefaults:\n  run:\n    working-directory: dashboard\n\njobs:\n  jest:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [13.x]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v1\n      with:\n        node-version: ${{ matrix.node-version }}\n    - run: npm install\n    - run: npm run test:unit --if-present\n"
  },
  {
    "path": ".github/workflows/ci-widget-e2e.yml",
    "content": "name: Widget e2e testing\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ $default-branch ]\n  pull_request:\n    branches: [ $default-branch ]\n\njobs:\n  cypress:\n    defaults:\n      run:\n        working-directory: widget\n\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - run: npm install\n\n      - run: npm run build\n        env:\n          NODE_ENV: production\n\n      - name: Run project locally\n        run: |\n          npm install serve\n          $(npm bin)/serve dist -s -p 8080 &\n\n      - name: Run tests\n        uses: cypress-io/github-action@v2\n        with:\t\n          working-directory: widget\n          browser: chrome\n          headless: true\n"
  },
  {
    "path": ".github/workflows/ci-widget-unit.yml",
    "content": "name: Widget unit testing\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ $default-branch ]\n  pull_request:\n    branches: [ $default-branch ]\n\ndefaults:\n  run:\n    working-directory: widget\n\njobs:\n  jest:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [13.x]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v1\n      with:\n        node-version: ${{ matrix.node-version }}\n    - run: npm install\n    - run: npm run test:unit --if-present\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\n"
  },
  {
    "path": "backend/.eslintrc.js",
    "content": "module.exports = {\n    \"env\": {\n        \"commonjs\": true,\n        \"es6\": true,\n        \"node\": true\n    },\n    \"extends\": [\n        \"standard\"\n    ],\n    \"globals\": {\n        \"Atomics\": \"readonly\",\n        \"SharedArrayBuffer\": \"readonly\"\n    },\n    \"parserOptions\": {\n        \"ecmaVersion\": 2018\n    },\n    \"rules\": {\n    }\n};"
  },
  {
    "path": "backend/.gitignore",
    "content": ".now\n.vercel\n"
  },
  {
    "path": "backend/Dockerfile",
    "content": "FROM node:10-alpine\n\nRUN mkdir -p /src\n\nCOPY package.json src/package.json\n\nWORKDIR /src\n\nRUN npm install --only=production --silent\n\nCOPY . /src\n\nCMD npm start\n"
  },
  {
    "path": "backend/README.md",
    "content": "## Backend do curso treinamento de Vue.js 3\n\nBackend pré-pronto do curso treinamento de Vue.js 3\n\n### Comandos\n\n```\n# Buildar o backend em um container do docker\nnpm run build\n\n# Rodar o container que foi buildado\nnpm run container\n\n# O backend estará disponível na porta 3000\n```\n\n_Este backend existe para auxiliar o curso de front-end com [Vue.js 3 do Vue.js Brasil](https://treinamento.vuejsbrasil.org/)_\n\n"
  },
  {
    "path": "backend/database/index.js",
    "content": "const database = require('./mock')\n\nfunction wait (timeMs) {\n  return new Promise(resolve => {\n    setTimeout(resolve, timeMs)\n  })\n}\n\nasync function update (col, id, data) {\n  if (!database[col]) {\n    return false\n  }\n\n  database[col] = database[col].map(item => {\n    if (item.id === id) {\n      return { ...item, ...data }\n    }\n\n    return item\n  })\n\n  await wait(500)\n  return true\n}\n\nasync function readAll (col) {\n  await wait(2500)\n  if (!database[col]) {\n    return []\n  }\n\n  return database[col].sort((a, b) => b.createdAt - a.createdAt)\n}\n\nasync function insert (col, data) {\n  if (!database[col]) {\n    database[col] = []\n  }\n\n  database[col].push(data)\n  await wait(500)\n  return true\n}\n\nasync function readOneById (col, id) {\n  if (!database[col]) return\n  const res = database[col].find(item => String(item.id) === String(id))\n\n  await wait(500)\n  return res\n}\n\nasync function readOneByEmail (col, email) {\n  if (!database[col]) return\n  const res = database[col].find(item => String(item.email) === String(email))\n\n  await wait(500)\n  return res\n}\n\nmodule.exports = {\n  update,\n  insert,\n  readAll,\n  readOneById,\n  readOneByEmail\n}\n"
  },
  {
    "path": "backend/database/mock.js",
    "content": "module.exports = {\n  users: [\n    {\n      id: 'eab759f8-f238-4ff9-ae91-ee1558982329',\n      name: 'Igor Halfeld',\n      email: 'igor@igor.me',\n      password: '1234',\n      apiKey: ['fcd5015c-10d3-4e9c-b395-ec7ed8850165'],\n      createdAt: new Date('2020-09-05').getTime()\n    }\n  ],\n  feedbacks: [\n    {\n      text: 'Muito bom!',\n      fingerprint: '490135491',\n      id: 'eab759f8-f238-4ff9-ae91-ee1558982329',\n      apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165',\n      type: 'OTHER',\n      device: 'Chrome 85.0, macOS 10.14',\n      page: 'https://feedbacker.com/pricing',\n      createdAt: new Date('2020-11-13').getTime()\n    },\n    {\n      text: 'Muitos erros slkkkkkkk',\n      fingerprint: '490135491',\n      id: 'eab759f8-f238-4ff9-ae91-ee1558982329',\n      apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165',\n      type: 'ISSUE',\n      device: 'Chrome 85.0, macOS 10.14',\n      page: 'https://feedbacker.com/pricing',\n      createdAt: new Date('2020-10-23').getTime()\n    },\n    {\n      text: 'Podia ter um botão de solicitar demo',\n      fingerprint: '490135491',\n      id: 'eab759f8-f238-4ff9-ae91-ee1558982329',\n      apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165',\n      type: 'IDEA',\n      device: 'Chrome 85.0, macOS 10.14',\n      page: 'https://feedbacker.com/pricing',\n      createdAt: new Date('2020-09-23').getTime()\n    },\n    {\n      text: 'Podia ter um botão de solicitar demo 1',\n      fingerprint: '490135491',\n      id: 'eab759f8-f238-4ff9-ae91-ee1558982329',\n      apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165',\n      type: 'IDEA',\n      device: 'Chrome 85.0, macOS 10.14',\n      page: 'https://feedbacker.com/pricing',\n      createdAt: new Date('2020-12-23').getTime()\n    },\n    {\n      text: 'Podia ter um botão de solicitar demo 2',\n      fingerprint: '490135491',\n      id: 'eab759f8-f238-4ff9-ae91-ee1558982329',\n      apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165',\n      type: 'IDEA',\n      device: 'Chrome 85.0, macOS 10.14',\n      page: 'https://feedbacker.com/pricing',\n      createdAt: new Date('2020-08-23').getTime()\n    },\n    {\n      text: 'Muitos erros slkkkkkkk 2',\n      fingerprint: '490135491',\n      id: 'eab759f8-f238-4ff9-ae91-ee1558982329',\n      apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165',\n      type: 'ISSUE',\n      device: 'Chrome 85.0, macOS 10.14',\n      page: 'https://feedbacker.com/pricing',\n      createdAt: new Date('2020-05-23').getTime()\n    },\n    {\n      text: 'Tava bom, agora parece que piorou',\n      fingerprint: '490135491',\n      id: 'eab759f8-f238-4ff9-ae91-ee1558982329',\n      apiKey: 'fcd5015c-10d3-4e9c-b395-ec7ed8850165',\n      type: 'ISSUE',\n      device: 'Chrome 85.0, macOS 10.14',\n      page: 'https://feedbacker.com/pricing',\n      createdAt: new Date('2020-05-23').getTime()\n    }\n  ]\n}\n"
  },
  {
    "path": "backend/handlers/apikey.js",
    "content": "function CreateApiKeyHandler (db) {\n  async function checkIfApiKeyExists (ctx) {\n    const { apikey } = ctx.query\n    if (!apikey) {\n      ctx.status = 400\n      ctx.body = { error: 'apikey query param not provided' }\n      return\n    }\n    const users = await db.readAll('users')\n\n    const apiKeyExists = users.map((user) => {\n      return user.apiKey.includes(apikey)\n    })\n\n    if (apiKeyExists.includes(true)) {\n      ctx.status = 200\n      return\n    }\n\n    ctx.status = 404\n  }\n\n  return {\n    checkIfApiKeyExists\n  }\n}\n\nmodule.exports = CreateApiKeyHandler\n"
  },
  {
    "path": "backend/handlers/auth.js",
    "content": "const jwt = require('jsonwebtoken')\n\nfunction CreateAuthHandler (db) {\n  async function login (ctx) {\n    const { email, password } = ctx.request.body\n    const user = await db.readOneByEmail('users', email)\n\n    if (!user) {\n      ctx.status = 404\n      ctx.body = { error: 'Not found' }\n      return\n    }\n\n    const canLogin = () => (\n      user.email === email &&\n      user.password === password\n    )\n\n    if (!canLogin()) {\n      ctx.status = 401\n      ctx.body = { error: 'Unauthorized' }\n      return\n    }\n\n    const token = jwt.sign({\n      id: user.id,\n      email: user.email,\n      name: user.name\n    }, process.env.JWT_SECRET)\n\n    ctx.status = 200\n    ctx.body = { token }\n  }\n\n  return { login }\n}\n\nmodule.exports = CreateAuthHandler\n"
  },
  {
    "path": "backend/handlers/feedbacks.js",
    "content": "const { v4: uuidv4 } = require('uuid')\n\nconst FEEDBACK_TYPES = {\n  ISSUE: 'ISSUE',\n  IDEA: 'IDEA',\n  OTHER: 'OTHER'\n}\n\nfunction CreateFeedbackHandler (db) {\n  async function create (ctx) {\n    const {\n      type,\n      text,\n      apiKey,\n      fingerprint,\n      device,\n      page\n    } = ctx.request.body\n\n    if (!type) {\n      ctx.status = 400\n      ctx.body = { error: 'type is empty' }\n    }\n    if (!text) {\n      ctx.status = 400\n      ctx.body = { error: 'text is empty' }\n    }\n    if (!fingerprint) {\n      ctx.status = 400\n      ctx.body = { error: 'fingerprint is empty' }\n    }\n    if (!device) {\n      ctx.status = 400\n      ctx.body = { error: 'device is empty' }\n    }\n    if (!page) {\n      ctx.status = 400\n      ctx.body = { error: 'page is empty' }\n    }\n    if (!apiKey) {\n      ctx.status = 400\n      ctx.body = { error: 'apiKey is empty' }\n    }\n\n    if (!FEEDBACK_TYPES[String(type).toUpperCase()]) {\n      ctx.status = 422\n      ctx.body = { error: 'Unknown feedback type' }\n      return\n    }\n\n    // @TODO: for this, I don't validate if apikey is valid.\n    // Just for study purposes.\n\n    const feedback = {\n      text,\n      fingerprint,\n      id: uuidv4(),\n      apiKey,\n      type: String(type).toUpperCase(),\n      device,\n      page,\n      createdAt: new Date().getTime()\n    }\n\n    const inserted = await db.insert('feedbacks', feedback)\n    if (inserted) {\n      ctx.status = 201\n      ctx.body = feedback\n      return\n    }\n\n    ctx.status = 422\n    ctx.body = { error: 'Feedback not created' }\n  }\n\n  async function getFeedbacks (ctx) {\n    const { type } = ctx.query\n    let offset = ctx.query.offset ? Number(ctx.query.offset) : 0\n    let limit = ctx.query.limit ? Number(ctx.query.limit) : 5\n\n    let [\n      user,\n      feedbacks\n    ] = await Promise.all([\n      db.readOneById('users', ctx.state.user.id),\n      db.readAll('feedbacks')\n    ])\n\n    if (!user) {\n      ctx.status = 401\n      ctx.body = { error: 'Unauthorized' }\n      return\n    }\n\n    feedbacks = feedbacks.filter((feedback) => {\n      return user.apiKey.includes(feedback.apiKey)\n    })\n\n    if (type) {\n      feedbacks = feedbacks.filter((feedback) => {\n        return feedback.type === String(type).toUpperCase()\n      })\n    }\n\n    const total = feedbacks.length\n\n    if (limit > 10) {\n      limit = 5\n    }\n    if (offset > limit) {\n      offset = limit\n    }\n\n    feedbacks = feedbacks.slice(offset, feedbacks.length).slice(0, limit)\n\n    ctx.status = 200\n    ctx.body = {\n      results: feedbacks || [],\n      pagination: { offset, limit, total }\n    }\n  }\n\n  async function getSummary (ctx) {\n    const { type } = ctx.query\n    let [\n      user,\n      feedbacks\n    ] = await Promise.all([\n      db.readOneById('users', ctx.state.user.id),\n      db.readAll('feedbacks')\n    ])\n\n    if (!user) {\n      ctx.status = 401\n      ctx.body = { error: 'Unauthorized. User not found with this token' }\n      return\n    }\n\n    feedbacks = feedbacks.filter((feedback) => {\n      return user.apiKey.includes(feedback.apiKey)\n    })\n\n    if (type) {\n      feedbacks = feedbacks.filter((feedback) => {\n        return feedback.type === String(type).toUpperCase()\n      })\n    }\n\n    let all = 0\n    let issue = 0\n    let idea = 0\n    let other = 0\n\n    feedbacks.forEach((feedback) => {\n      all++\n\n      if (feedback.type === 'ISSUE') {\n        issue++\n      }\n      if (feedback.type === 'IDEA') {\n        idea++\n      }\n      if (feedback.type === 'OTHER') {\n        other++\n      }\n    })\n\n    ctx.status = 200\n    ctx.body = { all, issue, idea, other }\n  }\n\n  return {\n    create,\n    getFeedbacks,\n    getSummary\n  }\n}\n\nmodule.exports = CreateFeedbackHandler\n"
  },
  {
    "path": "backend/handlers/users.js",
    "content": "const { v4: uuidv4 } = require('uuid')\n\nfunction CreateUserHandler (db) {\n  async function getLoggerUser (ctx) {\n    const { id } = ctx.state.user\n    const user = await db.readOneById('users', id)\n    if (!user) {\n      ctx.status = 404\n      ctx.body = { error: 'Not found' }\n      return\n    }\n\n    const userResponse = {\n      ...user,\n      apiKey: user.apiKey[user.apiKey.length - 1]\n    }\n\n    delete userResponse.password\n    ctx.status = 200\n    ctx.body = userResponse\n  }\n\n  async function generateApiKey (ctx) {\n    const apiKey = uuidv4()\n    const { id } = ctx.state.user\n\n    const user = await db.readOneById('users', id)\n    const updated = await db.update('users', id, {\n      apiKey: [...user.apiKey, apiKey]\n    })\n    if (updated) {\n      ctx.status = 202\n      ctx.body = { apiKey }\n      return\n    }\n    ctx.status = 422\n    ctx.body = { error: 'User not updated' }\n  }\n\n  async function create (ctx) {\n    const { email, password, name } = ctx.request.body\n\n    if (!email) {\n      ctx.status = 400\n      ctx.body = { error: 'email is empty' }\n      return\n    }\n    if (!password) {\n      ctx.status = 400\n      ctx.body = { error: 'password is empty' }\n      return\n    }\n    if (!name) {\n      ctx.status = 400\n      ctx.body = { error: 'name is empty' }\n      return\n    }\n\n    const user = {\n      id: uuidv4(),\n      name,\n      email,\n      password,\n      apiKey: [uuidv4()],\n      createdAt: new Date().getTime()\n    }\n\n    const inserted = await db.insert('users', user)\n    if (inserted) {\n      ctx.status = 201\n      ctx.body = user\n      return\n    }\n\n    ctx.status = 422\n    ctx.body = { error: 'User not created' }\n  }\n\n  return {\n    create,\n    generateApiKey,\n    getLoggerUser\n  }\n}\n\nmodule.exports = CreateUserHandler\n"
  },
  {
    "path": "backend/index.js",
    "content": "const Koa = require('koa')\nconst Router = require('koa-router')\nconst jwt = require('koa-jwt')\nconst cors = require('@koa/cors')\nconst bodyParser = require('koa-bodyparser')\n\nconst database = require('./database')\nconst CreateUserHandler = require('./handlers/users')\nconst CreateAuthHandler = require('./handlers/auth')\nconst CreateFeedbackHandler = require('./handlers/feedbacks')\nconst CreateApiKeyHandler = require('./handlers/apikey')\n\nconst app = new Koa()\nconst router = new Router()\n\nconst {\n  JWT_SECRET = '',\n  PORT = 3000\n} = process.env\nconst authMiddleware = jwt({ secret: JWT_SECRET })\napp.use(bodyParser())\napp.use(cors())\n\nconst feedbacksHandler = CreateFeedbackHandler(database)\nconst usersHandler = CreateUserHandler(database)\nconst authHandler = CreateAuthHandler(database)\nconst apiKeyHandler = CreateApiKeyHandler(database)\n\nrouter.get('/', (ctx) => {\n  ctx.status = 200\n  ctx.body = { message: new Date() }\n})\nrouter.head('/apikey/exists', apiKeyHandler.checkIfApiKeyExists)\nrouter.post('/auth/register', usersHandler.create)\nrouter.post('/auth/login', authHandler.login)\nrouter.get('/users/me', authMiddleware, usersHandler.getLoggerUser)\nrouter.post('/users/me/apikey', authMiddleware, usersHandler.generateApiKey)\nrouter.get('/feedbacks', authMiddleware, feedbacksHandler.getFeedbacks)\nrouter.post('/feedbacks', feedbacksHandler.create)\nrouter.get('/feedbacks/summary', authMiddleware, feedbacksHandler.getSummary)\napp.use(router.routes())\napp.use(router.allowedMethods())\napp.listen(PORT, () => {\n  console.log(`Server running http://localhost:${PORT}`)\n})\n\nmodule.exports = app\n"
  },
  {
    "path": "backend/package.json",
    "content": "{\n  \"name\": \"backend\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Backend pré-pronto do curso treinamento de Vue.js 3\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"JWT_SECRET=sssshhhh node index.js\",\n    \"build\": \"docker build -t vuejs-treinamento-backend .\",\n    \"container\": \"docker run -d -p 3000:3000 vuejs-treinamento-backend\",\n    \"dev\": \"DEBUG=* JWT_SECRET=sssshhhh nodemon index.js\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"@koa/cors\": \"^3.1.0\",\n    \"@vercel/node\": \"^1.8.5\",\n    \"jsonwebtoken\": \"^8.5.1\",\n    \"koa\": \"^2.13.0\",\n    \"koa-bodyparser\": \"^4.3.0\",\n    \"koa-jwt\": \"^4.0.0\",\n    \"koa-router\": \"^10.0.0\",\n    \"uuid\": \"^8.3.1\"\n  },\n  \"devDependencies\": {\n    \"eslint\": \"^7.13.0\",\n    \"eslint-config-standard\": \"^16.0.1\",\n    \"eslint-plugin-import\": \"^2.22.1\",\n    \"eslint-plugin-node\": \"^11.1.0\",\n    \"eslint-plugin-promise\": \"^4.2.1\",\n    \"nodemon\": \"^2.0.6\"\n  }\n}\n"
  },
  {
    "path": "backend/vercel.json",
    "content": "{\n  \"version\": 2,\n  \"builds\": [\n    {\n      \"src\": \"index.js\",\n      \"use\": \"@vercel/node\"\n    }\n  ],\n  \"routes\": [\n    {\n      \"src\": \"/(.*)\",\n      \"dest\": \"/\"\n    }\n  ]\n}\n"
  },
  {
    "path": "backend/vuejs_brasil_feedbacker.json",
    "content": "{\n\t\"info\": {\n\t\t\"_postman_id\": \"03caccb8-c176-4e3f-ae31-751901560b3c\",\n\t\t\"name\": \"Vue.js Brasil - Feedbacker\",\n\t\t\"schema\": \"https://schema.getpostman.com/json/collection/v2.1.0/collection.json\"\n\t},\n\t\"item\": [\n\t\t{\n\t\t\t\"name\": \"Status\",\n\t\t\t\"item\": [\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Pegar o status\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"\",\n\t\t\t\t\t\t\t\t\"value\": \"\",\n\t\t\t\t\t\t\t\t\"type\": \"text\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/?\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"query\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImVhYjc1OWY4LWYyMzgtNGZmOS1hZTkxLWVlMTU1ODk4MjMyOSIsImVtYWlsIjoiaWd1aW5AaWd1aW4ubWUiLCJuYW1lIjoiSWdvciBIYWxmZWxkIiwiaWF0IjoxNjA4NTA2ODAwfQ.AXBHWYY1hioeBXQfhxpI9uBGDH3shKqGgWE2JuTOsh4\",\n\t\t\t\t\t\t\t\t\t\"disabled\": true\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"protocolProfileBehavior\": {}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Apikey\",\n\t\t\t\"item\": [\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Checar se a apikey existe\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"HEAD\",\n\t\t\t\t\t\t\"header\": [],\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/apikey/exists?apikey=fcd5015c-10d3-4e9c-b395-ec7ed8850165\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"apikey\",\n\t\t\t\t\t\t\t\t\"exists\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"query\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"apikey\",\n\t\t\t\t\t\t\t\t\t\"value\": \"fcd5015c-10d3-4e9c-b395-ec7ed8850165\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"protocolProfileBehavior\": {}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Auth\",\n\t\t\t\"item\": [\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Fazer login\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{\\n    \\\"email\\\": \\\"igor@igor.me\\\",\\n    \\\"password\\\": \\\"1234\\\"\\n}\",\n\t\t\t\t\t\t\t\"options\": {\n\t\t\t\t\t\t\t\t\"raw\": {\n\t\t\t\t\t\t\t\t\t\"language\": \"json\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/auth/login\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"auth\",\n\t\t\t\t\t\t\t\t\"login\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Criar um novo usuário\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Ijc4NzRiNzYwLTIyZTItNDE2NS05NDJhLWE4MDcwODJmNDNhNCIsImVtYWlsIjoiZXZhbi55b3VAZ21haWwuY29tIiwibmFtZSI6IkV2YW4gWW91IiwiaWF0IjoxNjA5ODExOTc4fQ.4M4u5n7n8NMfsNDWNAeLw8q20PBQFFfRHicbwT8r8W8\",\n\t\t\t\t\t\t\t\t\"type\": \"text\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{\\n\\t\\\"name\\\": \\\"Evan You\\\",\\n    \\\"email\\\": \\\"evan.you@gmail.com\\\",\\n    \\\"password\\\": \\\"1234\\\"\\n}\",\n\t\t\t\t\t\t\t\"options\": {\n\t\t\t\t\t\t\t\t\"raw\": {\n\t\t\t\t\t\t\t\t\t\"language\": \"json\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/auth/register\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"auth\",\n\t\t\t\t\t\t\t\t\"register\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"protocolProfileBehavior\": {}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Users\",\n\t\t\t\"item\": [\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Pegar os dados do usuário logado\",\n\t\t\t\t\t\"protocolProfileBehavior\": {\n\t\t\t\t\t\t\"disableBodyPruning\": true\n\t\t\t\t\t},\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Ijc4NzRiNzYwLTIyZTItNDE2NS05NDJhLWE4MDcwODJmNDNhNCIsImVtYWlsIjoiZXZhbi55b3VAZ21haWwuY29tIiwibmFtZSI6IkV2YW4gWW91IiwiaWF0IjoxNjA5ODExOTc4fQ.4M4u5n7n8NMfsNDWNAeLw8q20PBQFFfRHicbwT8r8W8\",\n\t\t\t\t\t\t\t\t\"type\": \"text\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/users/me\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"users\",\n\t\t\t\t\t\t\t\t\"me\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Gerar uma nova apiKey\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Ijc4NzRiNzYwLTIyZTItNDE2NS05NDJhLWE4MDcwODJmNDNhNCIsImVtYWlsIjoiZXZhbi55b3VAZ21haWwuY29tIiwibmFtZSI6IkV2YW4gWW91IiwiaWF0IjoxNjA5ODExOTc4fQ.4M4u5n7n8NMfsNDWNAeLw8q20PBQFFfRHicbwT8r8W8\",\n\t\t\t\t\t\t\t\t\"type\": \"text\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/users/me/apikey\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"users\",\n\t\t\t\t\t\t\t\t\"me\",\n\t\t\t\t\t\t\t\t\"apikey\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"protocolProfileBehavior\": {}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Feedbacks\",\n\t\t\t\"item\": [\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Pegar índice de feedbacks\",\n\t\t\t\t\t\"protocolProfileBehavior\": {\n\t\t\t\t\t\t\"disableBodyPruning\": true\n\t\t\t\t\t},\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImVhYjc1OWY4LWYyMzgtNGZmOS1hZTkxLWVlMTU1ODk4MjMyOSIsImVtYWlsIjoiaWdvckBpZ29yLm1lIiwibmFtZSI6Iklnb3IgSGFsZmVsZCIsImlhdCI6MTYxMDQyNjg4OH0.88S5YLssZhC_TgotUZFDlcw5Cc3xlQTB0mqsQcQu1dY\",\n\t\t\t\t\t\t\t\t\"type\": \"text\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"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}\",\n\t\t\t\t\t\t\t\"options\": {\n\t\t\t\t\t\t\t\t\"raw\": {\n\t\t\t\t\t\t\t\t\t\"language\": \"json\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/feedbacks/summary\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"feedbacks\",\n\t\t\t\t\t\t\t\t\"summary\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Pegar todos os feedbacks\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Ijc4NzRiNzYwLTIyZTItNDE2NS05NDJhLWE4MDcwODJmNDNhNCIsImVtYWlsIjoiZXZhbi55b3VAZ21haWwuY29tIiwibmFtZSI6IkV2YW4gWW91IiwiaWF0IjoxNjA5ODExOTc4fQ.4M4u5n7n8NMfsNDWNAeLw8q20PBQFFfRHicbwT8r8W8\",\n\t\t\t\t\t\t\t\t\"type\": \"text\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/feedbacks\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"feedbacks\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Pegar todos os feedbacks do tipo IDEA\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImVhYjc1OWY4LWYyMzgtNGZmOS1hZTkxLWVlMTU1ODk4MjMyOSIsImVtYWlsIjoiaWdvckBpZ29yLm1lIiwibmFtZSI6Iklnb3IgSGFsZmVsZCIsImlhdCI6MTYxMDc0MzgyNn0.2R-hm8yCSAtpcvniI1R9CNF_ZzguRaMZoU2pTrwijds\",\n\t\t\t\t\t\t\t\t\"type\": \"text\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/feedbacks?type=idea\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"feedbacks\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"query\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"idea\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Pegar todos os feedbacks do tipo ISSUE\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImVhYjc1OWY4LWYyMzgtNGZmOS1hZTkxLWVlMTU1ODk4MjMyOSIsImVtYWlsIjoiaWdvckBpZ29yLm1lIiwibmFtZSI6Iklnb3IgSGFsZmVsZCIsImlhdCI6MTYxMDc0MzgyNn0.2R-hm8yCSAtpcvniI1R9CNF_ZzguRaMZoU2pTrwijds\",\n\t\t\t\t\t\t\t\t\"type\": \"text\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/feedbacks?type=issue\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"feedbacks\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"query\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"issue\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Pegar todos os feedbacks do tipo OTHER\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImVhYjc1OWY4LWYyMzgtNGZmOS1hZTkxLWVlMTU1ODk4MjMyOSIsImVtYWlsIjoiaWdvckBpZ29yLm1lIiwibmFtZSI6Iklnb3IgSGFsZmVsZCIsImlhdCI6MTYxMDc0MzgyNn0.2R-hm8yCSAtpcvniI1R9CNF_ZzguRaMZoU2pTrwijds\",\n\t\t\t\t\t\t\t\t\"type\": \"text\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/feedbacks?type=other\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"feedbacks\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"query\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"other\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Criar um feedback ISSUE\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"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}\",\n\t\t\t\t\t\t\t\"options\": {\n\t\t\t\t\t\t\t\t\"raw\": {\n\t\t\t\t\t\t\t\t\t\"language\": \"json\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/feedbacks\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"feedbacks\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Criar um feedback IDEA\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"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}\",\n\t\t\t\t\t\t\t\"options\": {\n\t\t\t\t\t\t\t\t\"raw\": {\n\t\t\t\t\t\t\t\t\t\"language\": \"json\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/feedbacks\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"feedbacks\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Criar um feedback OTHER\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"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}\",\n\t\t\t\t\t\t\t\"options\": {\n\t\t\t\t\t\t\t\t\"raw\": {\n\t\t\t\t\t\t\t\t\t\"language\": \"json\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"http://localhost:3000/feedbacks\",\n\t\t\t\t\t\t\t\"protocol\": \"http\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"localhost\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"3000\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"feedbacks\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"protocolProfileBehavior\": {}\n\t\t}\n\t],\n\t\"protocolProfileBehavior\": {}\n}"
  },
  {
    "path": "conceitos/data-binding/App.vue",
    "content": "<template>\n  <div>\n    <h1 :style=\"{ textDecoration: decoration }\">Hello {{ name }}!</h1>\n    <input type=\"text\" v-model=\"name\">\n    <br>\n    <a :href=\"link\">Link pro curso!</a>\n  </div>\n</template>\n\n<script>\nexport default {\n  data: () => ({\n    name: 'Igor',\n    link: 'https://treinamento.vuejsbrasil.org',\n    decoration: 'underline'\n  })\n}\n</script>\n\n"
  },
  {
    "path": "conceitos/diretivas/App.vue",
    "content": "<template>\n  <div>\n    <h1>Minha lista de tarefas!</h1>\n    <button @click=\"() => showList = !showList\">\n      Ver a lista!\n    </button>\n    <br>\n    <input type=\"text\" v-focus> \n\n    <ul v-if=\"showList\">\n      <li\n        v-for=\"(task, index) in tasks\"\n        :key=\"`${task}-${index}`\"\n      >\n        {{ task.name }}\n      </li>\n    </ul>\n    <p v-else>Lista de tarefas escondidas</p>\n  </div>\n</template>\n\n<script>\nconst focus = {\n  inserted: (el) => {\n    el.focus()\n  }\n}\n\nexport default {\n  directives: {\n    focus\n  },\n  data: () => ({\n    showList: false,\n    tasks: [\n      { name: 'Fazer o curso', isDone: false }\n    ]\n  })\n}\n</script>\n\n"
  },
  {
    "path": "conceitos/eventos-e-metodos/App.vue",
    "content": "<template>\n  <div>\n    <h1>Minha lista de tarefas!</h1>\n    <button @click=\"handleShowHideList\">\n      Ver a lista!\n    </button>\n    <br>\n    <input\n      type=\"text\"\n      @keyup.enter=\"addTask\"\n      v-focus\n      v-model=\"currentTask\"> \n\n    <ul v-if=\"showList\">\n      <li\n        v-for=\"(task, index) in tasks\"\n        @dblclick=\"complete(task)\"\n        :key=\"`${task}-${index}`\"\n        class=\"task-item\"\n        :class=\"{\n          'line-through': task.isDone\n        }\"\n      >\n        {{ task.name }}\n        <button\n          @click=\"remove(task)\"\n        >&times;</button>\n      </li>\n    </ul>\n    <p v-else>Lista de tarefas escondidas</p>\n  </div>\n</template>\n\n<script>\nconst focus = {\n  inserted: (el) => {\n    el.focus()\n  }\n}\n\nexport default {\n  directives: {\n    focus\n  },\n  data: () => ({\n    currentTask: '',\n    showList: false,\n    tasks: [\n      { name: 'Fazer o curso', isDone: false }\n    ]\n  }),\n  methods: {\n    handleShowHideList () {\n      this.showList = !this.showList\n    },\n    addTask () {\n      this.tasks.push({\n        name: this.currentTask,\n        isDone: false\n      })\n      this.currentTask = ''\n    },\n    complete (task) {\n      this.tasks = this.tasks.map(t => {\n        if (t.name === task.name) {\n          return { ...t, isDone: !t.isDone }\n        }\n        return { ...t }\n      })\n    },\n    remove (task) {\n      this.tasks = this.tasks.filter(t => t.name !== task.name)\n    }\n  }\n}\n</script>\n\n<style scoped>\n.line-through {\n  text-decoration: line-through;\n}\n.task-item {\n  cursor: pointer;\n}\n</style>\n"
  },
  {
    "path": "conceitos/lifecycle-hooks/.gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "conceitos/lifecycle-hooks/README.md",
    "content": "# nova-syntax-e-antiga\n\n## Project setup\n```\nnpm install\n```\n\n### Compiles and hot-reloads for development\n```\nnpm run serve\n```\n\n### Compiles and minifies for production\n```\nnpm run build\n```\n\n### Lints and fixes files\n```\nnpm run lint\n```\n\n### Customize configuration\nSee [Configuration Reference](https://cli.vuejs.org/config/).\n"
  },
  {
    "path": "conceitos/lifecycle-hooks/babel.config.js",
    "content": "module.exports = {\n  presets: [\n    '@vue/cli-plugin-babel/preset'\n  ]\n}\n"
  },
  {
    "path": "conceitos/lifecycle-hooks/package.json",
    "content": "{\n  \"name\": \"nova-syntax-e-antiga\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"serve\": \"vue-cli-service serve\",\n    \"build\": \"vue-cli-service build\",\n    \"lint\": \"vue-cli-service lint\"\n  },\n  \"dependencies\": {\n    \"core-js\": \"^3.6.5\",\n    \"vue\": \"^3.0.0\"\n  },\n  \"devDependencies\": {\n    \"@vue/cli-plugin-babel\": \"~4.5.0\",\n    \"@vue/cli-plugin-eslint\": \"~4.5.0\",\n    \"@vue/cli-service\": \"~4.5.0\",\n    \"@vue/compiler-sfc\": \"^3.0.0\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"eslint\": \"^6.7.2\",\n    \"eslint-plugin-vue\": \"^7.0.0-0\"\n  },\n  \"eslintConfig\": {\n    \"root\": true,\n    \"env\": {\n      \"node\": true\n    },\n    \"extends\": [\n      \"plugin:vue/vue3-essential\",\n      \"eslint:recommended\"\n    ],\n    \"parserOptions\": {\n      \"parser\": \"babel-eslint\"\n    },\n    \"rules\": {}\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not dead\"\n  ]\n}\n"
  },
  {
    "path": "conceitos/lifecycle-hooks/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.ico\">\n    <title><%= htmlWebpackPlugin.options.title %></title>\n  </head>\n  <body>\n    <noscript>\n      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n    </noscript>\n    <div id=\"app\"></div>\n    <!-- built files will be auto injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "conceitos/lifecycle-hooks/src/App.vue",
    "content": "<template>\n  <h1>Lifecycle hooks</h1>\n</template>\n\n<script>\nimport { onMounted } from 'vue'\n\nexport default {\n  setup () {\n    onMounted(() => {\n      console.log('Foi montado composition API')\n    })\n  }\n}\n/*\nexport default {\n  mounted () {\n    console.log('Foi montado options API')\n  }\n}\n*/\n</script>\n"
  },
  {
    "path": "conceitos/lifecycle-hooks/src/main.js",
    "content": "import { createApp } from 'vue'\nimport App from './App.vue'\n\ncreateApp(App).mount('#app')\n"
  },
  {
    "path": "conceitos/nova-syntax-e-antiga/.gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "conceitos/nova-syntax-e-antiga/README.md",
    "content": "# nova-syntax-e-antiga\n\n## Project setup\n```\nnpm install\n```\n\n### Compiles and hot-reloads for development\n```\nnpm run serve\n```\n\n### Compiles and minifies for production\n```\nnpm run build\n```\n\n### Lints and fixes files\n```\nnpm run lint\n```\n\n### Customize configuration\nSee [Configuration Reference](https://cli.vuejs.org/config/).\n"
  },
  {
    "path": "conceitos/nova-syntax-e-antiga/babel.config.js",
    "content": "module.exports = {\n  presets: [\n    '@vue/cli-plugin-babel/preset'\n  ]\n}\n"
  },
  {
    "path": "conceitos/nova-syntax-e-antiga/package.json",
    "content": "{\n  \"name\": \"nova-syntax-e-antiga\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"serve\": \"vue-cli-service serve\",\n    \"build\": \"vue-cli-service build\",\n    \"lint\": \"vue-cli-service lint\"\n  },\n  \"dependencies\": {\n    \"core-js\": \"^3.6.5\",\n    \"vue\": \"^3.0.0\"\n  },\n  \"devDependencies\": {\n    \"@vue/cli-plugin-babel\": \"~4.5.0\",\n    \"@vue/cli-plugin-eslint\": \"~4.5.0\",\n    \"@vue/cli-service\": \"~4.5.0\",\n    \"@vue/compiler-sfc\": \"^3.0.0\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"eslint\": \"^6.7.2\",\n    \"eslint-plugin-vue\": \"^7.0.0-0\"\n  },\n  \"eslintConfig\": {\n    \"root\": true,\n    \"env\": {\n      \"node\": true\n    },\n    \"extends\": [\n      \"plugin:vue/vue3-essential\",\n      \"eslint:recommended\"\n    ],\n    \"parserOptions\": {\n      \"parser\": \"babel-eslint\"\n    },\n    \"rules\": {}\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not dead\"\n  ]\n}\n"
  },
  {
    "path": "conceitos/nova-syntax-e-antiga/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.ico\">\n    <title><%= htmlWebpackPlugin.options.title %></title>\n  </head>\n  <body>\n    <noscript>\n      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n    </noscript>\n    <div id=\"app\"></div>\n    <!-- built files will be auto injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "conceitos/nova-syntax-e-antiga/src/App.vue",
    "content": "<template>\n  <div>\n    <h1>Minha lista de tarefas!</h1>\n    <button @click=\"handleShowHideList\">\n      Ver a lista!\n    </button>\n    <br>\n    <input\n      type=\"text\"\n      @keyup.enter=\"addTask\"\n      v-focus\n      v-model=\"state.currentTask\"> \n\n    <ul v-if=\"state.showList\">\n      <li\n        v-for=\"(task, index) in state.tasks\"\n        @dblclick=\"complete(task)\"\n        :key=\"`${task}-${index}`\"\n        class=\"task-item\"\n        :class=\"{\n          'line-through': task.isDone\n        }\"\n      >\n        {{ task.name }}\n        <button\n          @click=\"remove(task)\"\n        >&times;</button>\n      </li>\n    </ul>\n    <p v-else>Lista de tarefas escondidas</p>\n  </div>\n</template>\n\n<script>\nimport { reactive } from 'vue'\n\nconst focus = {\n  inserted: (el) => {\n    el.focus()\n  }\n}\n\nexport default {\n  directives: {\n    focus\n  },\n  setup () {\n    const state = reactive({\n      currentTask: '',\n      showList: false,\n      tasks: [\n        { name: 'Fazer o curso', isDone: false }\n      ]\n    })\n\n    function handleShowHideList () {\n      state.showList = !state.showList\n    }\n\n    function addTask () {\n      state.tasks.push({\n        name: state.currentTask,\n        isDone: false\n      })\n      state.currentTask = ''\n    }\n\n    function complete (task) {\n      state.tasks = state.tasks.map(t => {\n        if (t.name === task.name) {\n          return { ...t, isDone: !t.isDone }\n        }\n        return { ...t }\n      })\n    }\n\n    function remove (task) {\n      state.tasks = state.tasks.filter(t => t.name !== task.name)\n    }\n\n    return {\n      state,\n      handleShowHideList,\n      addTask,\n      complete,\n      remove\n    }\n  }\n}\n</script>\n\n<style scoped>\n.line-through {\n  text-decoration: line-through;\n}\n.task-item {\n  cursor: pointer;\n}\n</style>\n"
  },
  {
    "path": "conceitos/nova-syntax-e-antiga/src/main.js",
    "content": "import { createApp } from 'vue'\nimport App from './App.vue'\n\ncreateApp(App).mount('#app')\n"
  },
  {
    "path": "conceitos/single-file-components/App.vue",
    "content": "<template>  \n  <h1>Hello World</h1>\n</template>\n\n<script lang=\"ts\">\nexport default {}\n</script>\n\n<style lang=\"scss\">\n</style>\n"
  },
  {
    "path": "dashboard/.browserslistrc",
    "content": "> 1%\nlast 2 versions\nnot dead\n"
  },
  {
    "path": "dashboard/.editorconfig",
    "content": "[*.{js,jsx,ts,tsx,vue}]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": "dashboard/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  env: {\n    node: true\n  },\n  extends: [\n    'plugin:vue/vue3-essential',\n    '@vue/standard'\n  ],\n  parserOptions: {\n    parser: 'babel-eslint'\n  },\n  rules: {\n    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',\n    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'\n  },\n  overrides: [\n    {\n      files: [\n        '**/*.spec.js'\n      ],\n      env: {\n        jest: true\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "dashboard/.gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n\n/tests/e2e/videos/\n/tests/e2e/screenshots/\n\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "dashboard/Dockerfile",
    "content": "FROM node:13-alpine as build\n\nWORKDIR /\n\nCOPY . .\n\nENV NODE_ENV=production\nRUN npm install --production\nRUN npm run build\n\nFROM nginx:1.18.0-alpine as final\n\nWORKDIR /\nCOPY --from=build ./dist /usr/share/nginx/html\n"
  },
  {
    "path": "dashboard/README.md",
    "content": "# dashboard\n\n## Project setup\n```\nnpm install\n```\n\n### Compiles and hot-reloads for development\n```\nnpm run serve\n```\n\n### Compiles and minifies for production\n```\nnpm run build\n```\n\n### Run your unit tests\n```\nnpm run test:unit\n```\n\n### Run your end-to-end tests\n```\nnpm run test:e2e\n```\n\n### Lints and fixes files\n```\nnpm run lint\n```\n\n### Customize configuration\nSee [Configuration Reference](https://cli.vuejs.org/config/).\n"
  },
  {
    "path": "dashboard/babel.config.js",
    "content": "module.exports = {\n  presets: [\n    '@vue/cli-plugin-babel/preset'\n  ]\n}\n"
  },
  {
    "path": "dashboard/cypress.json",
    "content": "{\n  \"pluginsFile\": \"tests/e2e/plugins/index.js\"\n}\n"
  },
  {
    "path": "dashboard/jest.config.js",
    "content": "module.exports = {\n  preset: '@vue/cli-plugin-unit-jest',\n  testMatch: [\n    '**/*.spec.js'\n  ],\n  transform: {\n    '^.+\\\\.vue$': 'vue-jest'\n  }\n}\n"
  },
  {
    "path": "dashboard/package.json",
    "content": "{\n  \"name\": \"dashboard\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"serve\": \"vue-cli-service serve\",\n    \"build\": \"vue-cli-service build\",\n    \"test:unit\": \"vue-cli-service test:unit\",\n    \"test:e2e\": \"vue-cli-service test:e2e\",\n    \"lint\": \"vue-cli-service lint\"\n  },\n  \"dependencies\": {\n    \"@tailwindcss/postcss7-compat\": \"^2.0.2\",\n    \"animate.css\": \"^4.1.1\",\n    \"autoprefixer\": \"^9.8.6\",\n    \"axios\": \"^0.21.1\",\n    \"core-js\": \"^3.6.5\",\n    \"postcss\": \"^7.0.35\",\n    \"tailwindcss\": \"npm:@tailwindcss/postcss7-compat@^2.0.2\",\n    \"tiny-emitter\": \"^2.1.0\",\n    \"vee-validate\": \"^4.1.9\",\n    \"vue\": \"^3.0.0\",\n    \"vue-router\": \"^4.0.0-0\",\n    \"vue-toastification\": \"^2.0.0-beta.9\"\n  },\n  \"devDependencies\": {\n    \"@vue/cli-plugin-babel\": \"~4.5.0\",\n    \"@vue/cli-plugin-e2e-cypress\": \"~4.5.0\",\n    \"@vue/cli-plugin-eslint\": \"~4.5.0\",\n    \"@vue/cli-plugin-router\": \"~4.5.0\",\n    \"@vue/cli-plugin-unit-jest\": \"~4.5.0\",\n    \"@vue/cli-service\": \"~4.5.0\",\n    \"@vue/compiler-sfc\": \"^3.0.0\",\n    \"@vue/eslint-config-standard\": \"^5.1.2\",\n    \"@vue/test-utils\": \"^2.0.0-beta.14\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"eslint\": \"^6.7.2\",\n    \"eslint-plugin-import\": \"^2.20.2\",\n    \"eslint-plugin-node\": \"^11.1.0\",\n    \"eslint-plugin-promise\": \"^4.2.1\",\n    \"eslint-plugin-standard\": \"^4.0.0\",\n    \"eslint-plugin-vue\": \"^7.0.0-0\",\n    \"lint-staged\": \"^9.5.0\",\n    \"typescript\": \"~3.9.3\",\n    \"vue-jest\": \"^5.0.0-0\"\n  },\n  \"gitHooks\": {\n    \"pre-commit\": \"lint-staged\"\n  },\n  \"lint-staged\": {\n    \"*.{js,jsx,vue}\": [\n      \"vue-cli-service lint\",\n      \"git add\"\n    ]\n  }\n}\n"
  },
  {
    "path": "dashboard/palette.js",
    "content": "module.exports = {\n  brand: {\n    main: '#EF4983',\n    gray: '#F9F9F9',\n    info: '#8296FB',\n    graydark: '#C0BCB0',\n    warning: '#E4B52E',\n    danger: '#F88676'\n  },\n  mediumslateblue: {\n    50: '#f6f9fd',\n    100: '#e8f3fd',\n    200: '#cbdefb',\n    300: '#abc4fb',\n    400: '#8296fb',\n    500: '#5667fb',\n    600: '#3d46f7',\n    700: '#3137e5',\n    800: '#272cb9',\n    900: '#202591'\n  },\n  slateblue: {\n    50: '#f5f8fc',\n    100: '#eaf0fc',\n    200: '#d3d8fa',\n    300: '#babcf9',\n    400: '#9c8ff9',\n    500: '#7a61f9',\n    600: '#5d41f4',\n    700: '#4933e1',\n    800: '#382ab5',\n    900: '#2d248f'\n  },\n  mediumorchid: {\n    50: '#f9f7fa',\n    100: '#f7edf9',\n    200: '#efd1f5',\n    300: '#e8aef1',\n    400: '#e47cec',\n    500: '#e04fe7',\n    600: '#c932d7',\n    700: '#9a27b5',\n    800: '#712186',\n    900: '#571c66'\n  },\n  deeppink: {\n    50: '#fcf9f9',\n    100: '#fcf0f3',\n    200: '#fad3e6',\n    300: '#f9acd1',\n    400: '#fa73ab',\n    500: '#fb4783',\n    600: '#f42a5c',\n    700: '#d6214a',\n    800: '#a71b3a',\n    900: '#83172f'\n  },\n  tomato: {\n    50: '#fcf9f6',\n    100: '#fcf0ed',\n    200: '#fadad6',\n    300: '#f8b9b0',\n    400: '#f88676',\n    500: '#f85b47',\n    600: '#ee392e',\n    700: '#cc2b2b',\n    800: '#9e2328',\n    900: '#7c1d23'\n  },\n  chocolate: {\n    50: '#fbf8f2',\n    100: '#fbf1e1',\n    200: '#f8e1bb',\n    300: '#f5c782',\n    400: '#f39d41',\n    500: '#f1741e',\n    600: '#e24f13',\n    700: '#bc3b16',\n    800: '#922e1a',\n    900: '#732619'\n  },\n  goldenrod: {\n    50: '#fbfaf4',\n    100: '#faf6e0',\n    200: '#f5ebb0',\n    300: '#efd86f',\n    400: '#e4b52e',\n    500: '#d69111',\n    600: '#b76b0a',\n    700: '#8b500e',\n    800: '#663d12',\n    900: '#4e3012'\n  },\n  darkgoldenrod: {\n    50: '#fbfaf6',\n    100: '#f9f8e7',\n    200: '#f2efbc',\n    300: '#e8de7f',\n    400: '#d3bd3a',\n    500: '#b79b17',\n    600: '#8e750d',\n    700: '#685910',\n    800: '#4b4213',\n    900: '#393414'\n  },\n  lightseagreen: {\n    50: '#f5fafa',\n    100: '#eaf7f5',\n    200: '#ceeee8',\n    300: '#a7dfd9',\n    400: '#63c3be',\n    500: '#30a29c',\n    600: '#237f79',\n    700: '#236460',\n    800: '#204c4a',\n    900: '#1b3d3c'\n  },\n  cornflowerblue: {\n    50: '#f4fafc',\n    100: '#e2f7fb',\n    200: '#bdeaf7',\n    300: '#8fd8f5',\n    400: '#4fb6f1',\n    500: '#238fed',\n    600: '#1a6bdf',\n    700: '#1b54bd',\n    800: '#19418c',\n    900: '#15346b'\n  }\n}\n"
  },
  {
    "path": "dashboard/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  }\n}\n"
  },
  {
    "path": "dashboard/public/_redirects",
    "content": "/* /index.html 200\n\n"
  },
  {
    "path": "dashboard/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.ico\">\n    <title>Feedbacker</title>\n  </head>\n  <body>\n    <noscript>\n      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n    </noscript>\n    <div id=\"app\"></div>\n    <!-- built files will be auto injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "dashboard/src/App.vue",
    "content": "<template>\n  <modal-factory />\n  <router-view />\n</template>\n\n<script>\nimport { watch } from 'vue'\nimport ModalFactory from './components/ModalFactory'\nimport { useRouter, useRoute } from 'vue-router'\nimport services from './services'\nimport { setCurrentUser } from './store/user'\n\nexport default {\n  components: { ModalFactory },\n  setup () {\n    const router = useRouter()\n    const route = useRoute()\n\n    watch(() => route.path, async () => {\n      if (route.meta.hasAuth) {\n        const token = window.localStorage.getItem('token')\n        if (!token) {\n          router.push({ name: 'Home' })\n          return\n        }\n\n        const { data } = await services.users.getMe()\n        setCurrentUser(data)\n      }\n    })\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/assets/css/fonts.css",
    "content": "@font-face {\n  font-family: \"RobotoRegular\";\n  src: local(\"Roboto Regular\"), local(\"Roboto-Regular\"),\n    url(\"../fonts/Roboto-Regular.ttf\") format(\"truetype\");\n  font-weight: 400;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: \"RobotoMedium\";\n  src: local(\"Roboto Medium\"), local(\"Roboto-Medium\"),\n    url(\"../fonts/Roboto-Medium.ttf\") format(\"truetype\");\n  font-weight: 500;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: \"RobotoBold\";\n  src: local(\"Roboto Bold\"), local(\"Roboto-Bold\"),\n    url(\"../fonts/Roboto-Bold.ttf\") format(\"truetype\");\n  font-weight: 700;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: \"RobotoBlack\";\n  src: local(\"Roboto Black\"), local(\"Roboto-Black\"),\n    url(\"../fonts/Roboto-Black.ttf\") format(\"truetype\");\n  font-weight: 900;\n  font-style: normal;\n}\n"
  },
  {
    "path": "dashboard/src/assets/css/tailwind.css",
    "content": "/*! @import */\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml,\nbody,\n#app {\n  width: 100%;\n  height: 100%;\n}\n"
  },
  {
    "path": "dashboard/src/components/ContentLoader/index.vue",
    "content": "<template>\n  <div\n    :style=\"{\n      width: computedWidth,\n      height\n    }\"\n    class=\"opacity-75 content-loader\"\n  >\n    <span :style=\"{ animationDuration }\" class=\"content-loader--fx\"/>\n    <slot />\n  </div>\n</template>\n\n<script>\nimport { computed } from 'vue'\n\nexport default {\n  props: {\n    maxWidth: {\n      default: 100,\n      type: Number\n    },\n    minWidth: {\n      default: 80,\n      type: Number\n    },\n    animationDuration: {\n      type: String,\n      default: '1.6s'\n    },\n    height: {\n      default: '1rem',\n      type: String\n    },\n    width: {\n      default: '1rem',\n      type: String\n    }\n  },\n  setup (props) {\n    const computedWidth = computed(() => {\n      const value = Math.random() * (props.width - props.minWidth)\n      return props.width ?? `${Math.floor(value + props.minWidth)}%`\n    })\n\n    return { computedWidth }\n  }\n}\n</script>\n\n<style lang=\"postcss\" scoped>\n@keyframes shimmer {\n  100% {\n    transform: translateX(100%);\n  }\n}\n\n.content-loader {\n  position: relative;\n  vertical-align: middle;\n  overflow: hidden;\n  background: #f6f7f8;\n}\n.content-loader--fx {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  transform: translateX(-100%);\n  background-image: linear-gradient(to right, #eeeeee 8%, #dddddd 18%, #eeeeee 33%);\n  background-position: 0 0;\n  background-size: 1000 100;\n  animation: shimmer infinite alternate ease-in-out;\n}\n</style>\n"
  },
  {
    "path": "dashboard/src/components/FeedbackCard/Badge.vue",
    "content": "<template>\n  <span\n    :class=\"`bg-${classColor}`\"\n    class=\"p-2 text-xs font-black text-white uppercase rounded-full\">\n    {{ label }}\n  </span>\n</template>\n\n<script>\nimport { computed } from 'vue'\nexport default {\n  props: {\n    type: { type: String, required: true }\n  },\n  setup (props) {\n    const label = computed(() => {\n      if (props.type === 'ISSUE') {\n        return 'problema'\n      }\n\n      if (props.type === 'IDEA') {\n        return 'ideia'\n      }\n\n      return 'outros'\n    })\n\n    const classColor = computed(() => {\n      if (props.type === 'ISSUE') {\n        return 'brand-danger'\n      }\n\n      if (props.type === 'IDEA') {\n        return 'brand-warning'\n      }\n\n      return 'brand-graydark'\n    })\n\n    return {\n      label,\n      classColor\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/components/FeedbackCard/Loading.vue",
    "content": "<template>\n  <content-loader\n    class=\"flex flex-col items-center rounded\"\n    width=\"100%\"\n    height=\"300px\"\n  >\n    <content-loader\n      class=\"mt-3 rounded\"\n      width=\"90%\"\n      height=\"90px\"\n      animation-duration=\"2s\"\n    />\n\n    <content-loader\n      class=\"mt-3 rounded\"\n      width=\"90%\"\n      height=\"90px\"\n      animation-duration=\"2.3s\"\n    />\n\n    <content-loader\n      class=\"mt-3 rounded\"\n      width=\"90%\"\n      height=\"90px\"\n      animation-duration=\"2.7s\"\n    />\n  </content-loader>\n</template>\n\n<script>\nimport ContentLoader from '../ContentLoader'\n\nexport default {\n  components: { ContentLoader }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/components/FeedbackCard/index.vue",
    "content": "<template>\n  <div\n    @click=\"handleToggle\"\n    class=\"flex flex-col px-8 py-6 rounded cursor-pointer bg-brand-gray\">\n    <div class=\"flex items-center justify-between w-full mb-8\">\n      <badge :type=\"feedback.type\" />\n\n      <span class=\"font-regular text-brand-graydark\">\n        {{ getDiffTimeBetweenCurrentDate(feedback.createdAt) }}\n      </span>\n    </div>\n\n    <div class=\"text-lg font-medium text-gray-800\">\n      {{ feedback.text }}\n    </div>\n\n    <div\n      :class=\"{\n        animate__fadeOutUp: state.isClosing\n      }\"\n      class=\"flex mt-8 animate__animated animate__fadeInUp animate__faster\"\n      v-if=\"state.isOpen\"\n    >\n      <div class=\"flex flex-col w-1/2\">\n        <div class=\"flex flex-col\">\n          <span class=\"font-bold text-gray-400 uppercase select-none\">Página</span>\n          <span class=\"font-medium text-gray-700\">{{ feedback.page }}</span>\n        </div>\n        <div class=\"flex flex-col\">\n          <span class=\"font-bold text-gray-400 uppercase select-none\">Dispositivo</span>\n          <span class=\"font-medium text-gray-700\">{{ feedback.device }}</span>\n        </div>\n      </div>\n\n      <div class=\"flex flex-col w-1/2\">\n        <div class=\"flex flex-col\">\n          <span class=\"font-bold text-gray-400 uppercase select-none\">Usuário</span>\n          <span class=\"font-medium text-gray-700\">{{ feedback.fingerprint }}</span>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"flex justify-end mt-8\" v-if=\"!state.isOpen\">\n      <icon name=\"chevron-down\" size=\"24\" :color=\"brandColors.graydark\" />\n    </div>\n\n  </div>\n</template>\n\n<script>\nimport Icon from '../Icon'\nimport Badge from './Badge'\nimport { getDiffTimeBetweenCurrentDate } from '../../utils/date'\nimport palette from '../../../palette'\nimport { reactive } from 'vue'\nimport { wait } from '../../utils/timeout'\n\nexport default {\n  components: { Badge, Icon },\n  props: {\n    isOpened: { type: Boolean, default: false },\n    feedback: { type: Object, required: true }\n  },\n  setup (props) {\n    const state = reactive({\n      isOpen: props.isOpened,\n      isClosing: !props.isOpened\n    })\n\n    async function handleToggle () {\n      state.isClosing = true\n\n      await wait(250)\n      state.isOpen = !state.isOpen\n      state.isClosing = false\n    }\n\n    return {\n      state,\n      handleToggle,\n      getDiffTimeBetweenCurrentDate,\n      brandColors: palette.brand\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/components/HeaderLogged/HeaderLogged.spec.js",
    "content": "import { shallowMount } from '@vue/test-utils'\nimport HeaderLogged from '.'\nimport { routes } from '../../router'\n\nimport { createRouter, createWebHistory } from 'vue-router'\n\nconst router = createRouter({\n  history: createWebHistory('/'),\n  routes\n})\n\nconst mockStore = { currentUser: {} }\njest.mock('../../hooks/useStore', () => {\n  return () => {\n    return mockStore\n  }\n})\n\ndescribe('<HeaderLogged />', () => {\n  it('should render header logged correctly', async () => {\n    router.push('/')\n    await router.isReady()\n    const wrapper = shallowMount(HeaderLogged, {\n      global: {\n        plugins: [router]\n      }\n    })\n\n    expect(wrapper.html()).toMatchSnapshot()\n  })\n\n  it('should render 3 dots when there\\'s not user logged', async () => {\n    router.push('/')\n    await router.isReady()\n    const wrapper = shallowMount(HeaderLogged, {\n      global: {\n        plugins: [router]\n      }\n    })\n\n    const buttonLogout = wrapper.find('#logout-button')\n    expect(buttonLogout.text()).toBe('...')\n  })\n\n  it('should render user anem when there\\'s user logged', async () => {\n    router.push('/')\n    await router.isReady()\n    mockStore.currentUser.name = 'Igor'\n    const wrapper = shallowMount(HeaderLogged, {\n      global: {\n        plugins: [router]\n      }\n    })\n\n    const buttonLogout = wrapper.find('#logout-button')\n    expect(buttonLogout.text()).toBe('Igor (sair)')\n  })\n})\n"
  },
  {
    "path": "dashboard/src/components/HeaderLogged/__snapshots__/HeaderLogged.spec.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`<HeaderLogged /> should render header logged correctly 1`] = `\n<div class=\"flex items-center justify-between w-4/5 max-w-6xl py-10\">\n  <div class=\"w-28 lg:w-36\"><img class=\"w-full\" alt=\"logo\"></div>\n  <div class=\"flex\">\n    <ul class=\"flex list-none\">\n      <li class=\"px-6 py-2 mr-2 font-bold text-white rounded-full cursor-pointer focus:outline-none\"> Credenciais </li>\n      <li class=\"px-6 py-2 mr-2 font-bold text-white rounded-full cursor-pointer focus:outline-none\"> Feedbacks </li>\n      <li id=\"logout-button\" class=\"px-6 py-2 font-bold bg-white rounded-full cursor-pointer text-brand-main focus:outline-none\">...</li>\n    </ul>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "dashboard/src/components/HeaderLogged/index.vue",
    "content": "<template>\n  <div class=\"flex items-center justify-between w-4/5 max-w-6xl py-10\">\n    <div class=\"w-28 lg:w-36\">\n      <img class=\"w-full\" src=\"../../assets/images/logo_white.png\" alt=\"logo\">\n    </div>\n\n    <div class=\"flex\">\n      <ul class=\"flex list-none\">\n        <li\n          @click=\"() => router.push({ name: 'Credencials' })\"\n          class=\"px-6 py-2 mr-2 font-bold text-white rounded-full cursor-pointer focus:outline-none\"\n        >\n          Credenciais\n        </li>\n        <li\n          @click=\"() => router.push({ name: 'Feedbacks' })\"\n          class=\"px-6 py-2 mr-2 font-bold text-white rounded-full cursor-pointer focus:outline-none\"\n        >\n          Feedbacks\n        </li>\n        <li\n          id=\"logout-button\"\n          @click=\"handleLogout\"\n          class=\"px-6 py-2 font-bold bg-white rounded-full cursor-pointer text-brand-main focus:outline-none\"\n        >\n          {{ logoutLabel }}\n        </li>\n      </ul>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { useRouter } from 'vue-router'\nimport { computed } from 'vue'\nimport useStore from '../../hooks/useStore'\nimport { cleanCurrentUser } from '../../store/user'\n\nexport default {\n  setup () {\n    const router = useRouter()\n    const store = useStore('User')\n\n    const logoutLabel = computed(() => {\n      if (!store.currentUser.name) {\n        return '...'\n      }\n      return `${store.currentUser.name} (sair)`\n    })\n\n    function handleLogout () {\n      window.localStorage.removeItem('token')\n      cleanCurrentUser()\n      router.push({ name: 'Home' })\n    }\n\n    return {\n      router,\n      logoutLabel,\n      handleLogout\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/components/Icon/ChevronDown.vue",
    "content": "<template>\n  <svg :width=\"size\" :height=\"size\" viewBox=\"0 0 17 10\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M1.9975 0L8.5 6.18084L15.0025 0L17 1.90283L8.5 10L0 1.90283L1.9975 0Z\"\n      :fill=\"color\"/>\n  </svg>\n</template>\n\n<script>\nexport default {\n  props: {\n    size: { type: [String, Number], default: 22 },\n    color: { type: String, default: 'white' }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/components/Icon/Copy.vue",
    "content": "<template>\n  <svg\n    :width=\"size\"\n    :height=\"size\" viewBox=\"0 0 19 22\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M14 0H2C0.9 0 0 0.9 0 2V16H2V2H14V0ZM13 4L19 10V20C19 21.1 18.1 22 17 22H5.99C4.89 22 4 21.1 4 20L4.01 6C4.01 4.9 4.9 4 6 4H13ZM12 11H17.5L12 5.5V11Z\"\n      :fill=\"color\"/>\n  </svg>\n</template>\n\n<script>\nexport default {\n  props: {\n    size: { type: [String, Number], default: 22 },\n    color: { type: String, default: 'white' }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/components/Icon/Loading.vue",
    "content": "<template>\n  <svg\n    :width=\"size\"\n    :height=\"size\" viewBox=\"0 0 22 30\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M11 6.81818V10.9091L16.5 5.45455L11 0V4.09091C4.9225 4.09091 0 8.97273 0 15C0 17.1409 0.6325 19.1318 1.705 20.8091L3.7125 18.8182C3.09375 17.6864 2.75 16.3773 2.75 15C2.75 10.4864 6.44875 6.81818 11 6.81818ZM20.295 9.19091L18.2875 11.1818C18.8925 12.3273 19.25 13.6227 19.25 15C19.25 19.5136 15.5512 23.1818 11 23.1818V19.0909L5.5 24.5455L11 30V25.9091C17.0775 25.9091 22 21.0273 22 15C22 12.8591 21.3675 10.8682 20.295 9.19091Z\"\n      :fill=\"color\"/>\n  </svg>\n</template>\n\n<script>\nexport default {\n  props: {\n    size: { type: [String, Number], default: 22 },\n    color: { type: String, default: 'white' }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/components/Icon/index.vue",
    "content": "<template>\n  <component :is=\"name\" v-bind=\"$props\"/>\n</template>\n\n<script>\nimport Loading from './Loading.vue'\nimport Copy from './Copy.vue'\nimport ChevronDown from './ChevronDown.vue'\n\nexport default {\n  components: { Loading, Copy, ChevronDown },\n  props: {\n    name: { type: String, required: true }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/components/ModalCreateAccount/index.vue",
    "content": "<template>\n  <div class=\"flex justify-between\" id=\"modal-create-account\">\n    <h1 class=\"text-4xl font-black text-gray-800\">\n      Crie uma conta\n    </h1>\n\n    <button\n      @click=\"close\"\n      class=\"text-4xl text-gray-600 focus:outline-none\"\n    >\n      &times;\n    </button>\n  </div>\n\n  <div class=\"mt-16\">\n    <form @submit.prevent=\"handleSubmit\">\n      <label class=\"block\">\n        <span class=\"text-lg font-medium text-gray-800\">Nome</span>\n        <input\n          v-model=\"state.name.value\"\n          type=\"text\"\n          :class=\"{\n            'border-brand-danger': !!state.name.errorMessage\n          }\"\n          class=\"block w-full px-4 py-3 mt-1 text-lg bg-gray-100 border-2 border-transparent rounded\"\n          placeholder=\"Jone Doe\"\n        >\n        <span\n          v-if=\"!!state.name.errorMessage\"\n          class=\"block font-medium text-brand-danger\"\n        >\n          {{ state.name.errorMessage }}\n        </span>\n      </label>\n\n      <label class=\"block mt-9\">\n        <span class=\"text-lg font-medium text-gray-800\">E-mail</span>\n        <input\n          v-model=\"state.email.value\"\n          type=\"email\"\n          :class=\"{\n            'border-brand-danger': !!state.email.errorMessage\n          }\"\n          class=\"block w-full px-4 py-3 mt-1 text-lg bg-gray-100 border-2 border-transparent rounded\"\n          placeholder=\"jane.dae@gmail.com\"\n        >\n        <span\n          v-if=\"!!state.email.errorMessage\"\n          class=\"block font-medium text-brand-danger\"\n        >\n          {{ state.email.errorMessage }}\n        </span>\n      </label>\n\n      <label class=\"block mt-9\">\n        <span class=\"text-lg font-medium text-gray-800\">Senha</span>\n        <input\n          v-model=\"state.password.value\"\n          type=\"password\"\n          :class=\"{\n            'border-brand-danger': !!state.password.errorMessage\n          }\"\n          class=\"block w-full px-4 py-3 mt-1 text-lg bg-gray-100 border-2 border-transparent rounded\"\n          placeholder=\"jane.dae@gmail.com\"\n        >\n        <span\n          v-if=\"!!state.password.errorMessage\"\n          class=\"block font-medium text-brand-danger\"\n        >\n          {{ state.password.errorMessage }}\n        </span>\n      </label>\n\n      <button\n        :disabled=\"state.isLoading\"\n        type=\"submit\"\n        :class=\"{\n          'opacity-50': state.isLoading\n        }\"\n        class=\"px-8 py-3 mt-10 text-2xl font-bold text-white rounded-full bg-brand-main focus:outline-none transition-all duration-150\"\n      >\n        <icon v-if=\"state.isLoading\" name=\"loading\" class=\"animate-spin\" />\n        <span v-else>Entrar</span>\n      </button>\n    </form>\n  </div>\n</template>\n\n<script>\nimport { reactive } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { useField } from 'vee-validate'\nimport { useToast } from 'vue-toastification'\nimport useModal from '../../hooks/useModal'\nimport Icon from '../Icon'\nimport { validateEmptyAndLength3, validateEmptyAndEmail } from '../../utils/validators'\nimport services from '../../services'\n\nexport default {\n  components: { Icon },\n  setup () {\n    const router = useRouter()\n    const modal = useModal()\n    const toast = useToast()\n\n    const {\n      value: nameValue,\n      errorMessage: nameErrorMessage\n    } = useField('name', validateEmptyAndLength3)\n\n    const {\n      value: emailValue,\n      errorMessage: emailErrorMessage\n    } = useField('email', validateEmptyAndEmail)\n\n    const {\n      value: passwordValue,\n      errorMessage: passwordErrorMessage\n    } = useField('password', validateEmptyAndLength3)\n\n    const state = reactive({\n      hasErrors: false,\n      isLoading: false,\n      name: {\n        value: nameValue,\n        errorMessage: nameErrorMessage\n      },\n      email: {\n        value: emailValue,\n        errorMessage: emailErrorMessage\n      },\n      password: {\n        value: passwordValue,\n        errorMessage: passwordErrorMessage\n      }\n    })\n\n    async function login ({ email, password }) {\n      const { data, errors } = await services.auth.login({ email, password })\n      if (!errors) {\n        window.localStorage.setItem('token', data.token)\n        router.push({ name: 'Feedbacks' })\n        modal.close()\n      }\n\n      state.isLoading = false\n    }\n\n    async function handleSubmit () {\n      try {\n        toast.clear()\n        state.isLoading = true\n\n        const { errors } = await services.auth.register({\n          name: state.name.value,\n          email: state.email.value,\n          password: state.password.value\n        })\n\n        if (!errors) {\n          await login({\n            email: state.email.value,\n            password: state.password.value\n          })\n          return\n        }\n\n        if (errors.status === 400) {\n          toast.error('Ocorreu um erro ao criar a conta')\n        }\n\n        state.isLoading = false\n      } catch (error) {\n        state.isLoading = false\n        state.hasErrors = !!error\n        toast.error('Ocorreu um erro ao criar a conta')\n      }\n    }\n\n    return {\n      state,\n      close: modal.close,\n      handleSubmit\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/components/ModalFactory/index.vue",
    "content": "<template>\n  <teleport to=\"body\">\n    <div\n      v-if=\"state.isActive\"\n      class=\"fixed top-0 left-0 z-50 flex items-center justify-center w-full h-full bg-black bg-opacity-50\"\n      @click=\"handleModalToogle({ status: false })\"\n    >\n      <div\n        class=\"fixed mx-10\"\n        :class=\"state.width\"\n        @click.stop\n      >\n        <div class=\"flex flex-col overflow-hidden bg-white rounded-lg animate__animated animate__fadeInDown animate__faster\">\n          <div class=\"flex flex-col px-12 py-10 bg-white\">\n            <component :is=\"state.component\" />\n          </div>\n        </div>\n\n      </div>\n    </div>\n  </teleport>\n</template>\n\n<script>\nimport { reactive, onMounted, onBeforeUnmount, defineAsyncComponent } from 'vue'\nimport useModal from '../../hooks/useModal'\n\nconst ModalLogin = defineAsyncComponent(() => import('../ModalLogin'))\nconst ModalAccountCreate = defineAsyncComponent(() => import('../ModalCreateAccount'))\n\nconst DEFAULT_WIDTH = 'w-3/4 lg:w-1/3'\n\nexport default {\n  components: {\n    ModalLogin,\n    ModalAccountCreate\n  },\n  setup () {\n    const modal = useModal()\n    const state = reactive({\n      isActive: false,\n      component: {},\n      props: {},\n      width: DEFAULT_WIDTH\n    })\n\n    onMounted(() => {\n      modal.listen(handleModalToogle)\n    })\n\n    onBeforeUnmount(() => {\n      modal.off(handleModalToogle)\n    })\n\n    function handleModalToogle (payload) {\n      if (payload.status) {\n        state.component = payload.component\n        state.props = payload.props\n        state.width = payload.width ?? DEFAULT_WIDTH\n      } else {\n        state.component = {}\n        state.props = {}\n        state.width = DEFAULT_WIDTH\n      }\n\n      state.isActive = payload.status\n    }\n\n    return {\n      state,\n      handleModalToogle\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/components/ModalLogin/index.vue",
    "content": "<template>\n  <div class=\"flex justify-between\" id=\"modal-login\">\n    <h1 class=\"text-4xl font-black text-gray-800\">\n      Entre na sua conta\n    </h1>\n\n    <button\n      @click=\"close\"\n      class=\"text-4xl text-gray-600 focus:outline-none\"\n    >\n      &times;\n    </button>\n  </div>\n\n  <div class=\"mt-16\">\n    <form @submit.prevent=\"handleSubmit\">\n      <label class=\"block\">\n        <span class=\"text-lg font-medium text-gray-800\">E-mail</span>\n        <input\n          id=\"email-field\"\n          v-model=\"state.email.value\"\n          type=\"email\"\n          :class=\"{\n            'border-brand-danger': !!state.email.errorMessage\n          }\"\n          class=\"block w-full px-4 py-3 mt-1 text-lg bg-gray-100 border-2 border-transparent rounded\"\n          placeholder=\"jane.dae@gmail.com\"\n        >\n        <span\n          id=\"email-error\"\n          v-if=\"!!state.email.errorMessage\"\n          class=\"block font-medium text-brand-danger\"\n        >\n          {{ state.email.errorMessage }}\n        </span>\n      </label>\n\n      <label class=\"block mt-9\">\n        <span class=\"text-lg font-medium text-gray-800\">Senha</span>\n        <input\n          id=\"password-field\"\n          v-model=\"state.password.value\"\n          type=\"password\"\n          :class=\"{\n            'border-brand-danger': !!state.password.errorMessage\n          }\"\n          class=\"block w-full px-4 py-3 mt-1 text-lg bg-gray-100 border-2 border-transparent rounded\"\n          placeholder=\"jane.dae@gmail.com\"\n        >\n        <span\n          v-if=\"!!state.password.errorMessage\"\n          class=\"block font-medium text-brand-danger\"\n        >\n          {{ state.password.errorMessage }}\n        </span>\n      </label>\n\n      <button\n        id=\"submit-button\"\n        :disabled=\"state.isLoading\"\n        type=\"submit\"\n        :class=\"{\n          'opacity-50': state.isLoading\n        }\"\n        class=\"px-8 py-3 mt-10 text-2xl font-bold text-white rounded-full bg-brand-main focus:outline-none transition-all duration-150\"\n      >\n        <icon v-if=\"state.isLoading\" name=\"loading\" class=\"animate-spin\" />\n        <span v-else>Entrar</span>\n      </button>\n    </form>\n  </div>\n</template>\n\n<script>\nimport { reactive } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { useField } from 'vee-validate'\nimport { useToast } from 'vue-toastification'\nimport useModal from '../../hooks/useModal'\nimport Icon from '../Icon'\nimport { validateEmptyAndLength3, validateEmptyAndEmail } from '../../utils/validators'\nimport services from '../../services'\n\nexport default {\n  components: { Icon },\n  setup () {\n    const router = useRouter()\n    const modal = useModal()\n    const toast = useToast()\n\n    const {\n      value: emailValue,\n      errorMessage: emailErrorMessage\n    } = useField('email', validateEmptyAndEmail)\n\n    const {\n      value: passwordValue,\n      errorMessage: passwordErrorMessage\n    } = useField('password', validateEmptyAndLength3)\n\n    const state = reactive({\n      hasErrors: false,\n      isLoading: false,\n      email: {\n        value: emailValue,\n        errorMessage: emailErrorMessage\n      },\n      password: {\n        value: passwordValue,\n        errorMessage: passwordErrorMessage\n      }\n    })\n\n    async function handleSubmit () {\n      try {\n        toast.clear()\n        state.isLoading = true\n        const { data, errors } = await services.auth.login({\n          email: state.email.value,\n          password: state.password.value\n        })\n\n        if (!errors) {\n          window.localStorage.setItem('token', data.token)\n          router.push({ name: 'Feedbacks' })\n          state.isLoading = false\n          modal.close()\n          return\n        }\n\n        if (errors.status === 404) {\n          toast.error('E-mail não encontrado')\n        }\n        if (errors.status === 401) {\n          toast.error('E-mail/senha inválidos')\n        }\n        if (errors.status === 400) {\n          toast.error('Ocorreu um erro ao fazer o login')\n        }\n\n        state.isLoading = false\n      } catch (error) {\n        state.isLoading = false\n        state.hasErrors = !!error\n        toast.error('Ocorreu um erro ao fazer o login')\n      }\n    }\n\n    return {\n      state,\n      close: modal.close,\n      handleSubmit\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/hooks/useModal.js",
    "content": "import bus from '../utils/bus'\n\nconst EVENT_NAME = 'modal:toggle'\n\nexport default function useModal () {\n  function open (payload = {}) {\n    bus.emit(EVENT_NAME, { status: true, ...payload })\n  }\n\n  function close (payload = {}) {\n    bus.emit(EVENT_NAME, { status: false, ...payload })\n  }\n\n  function listen (fn) {\n    bus.on(EVENT_NAME, fn)\n  }\n\n  function off (fn) {\n    bus.off(EVENT_NAME, fn)\n  }\n\n  return { open, close, listen, off }\n}\n"
  },
  {
    "path": "dashboard/src/hooks/useStore.js",
    "content": "import Store from '../store'\n\nexport default function useStore (module) {\n  if (module) {\n    return Store[module]\n  }\n\n  return Store\n}\n"
  },
  {
    "path": "dashboard/src/main.js",
    "content": "import { createApp } from 'vue'\nimport Toast, { POSITION } from 'vue-toastification'\nimport App from './App.vue'\nimport router from './router'\n\nimport 'animate.css'\nimport '@/assets/css/tailwind.css'\nimport '@/assets/css/fonts.css'\nimport 'vue-toastification/dist/index.css'\n\nconst app = createApp(App)\napp.use(router)\napp.use(Toast, { position: POSITION.BOTTOM_RIGHT })\napp.mount('#app')\n"
  },
  {
    "path": "dashboard/src/router/index.js",
    "content": "import { createRouter, createWebHistory } from 'vue-router'\n\nconst Home = () => import('../views/Home/index.vue')\nconst Feedbacks = () => import('../views/Feedbacks/index.vue')\nconst Credencials = () => import('../views/Credencials/index.vue')\n\nexport const routes = [\n  {\n    path: '/',\n    name: 'Home',\n    component: Home\n  },\n  {\n    path: '/feedbacks',\n    name: 'Feedbacks',\n    component: Feedbacks,\n    meta: {\n      hasAuth: true\n    }\n  },\n  {\n    path: '/credencials',\n    name: 'Credencials',\n    component: Credencials,\n    meta: {\n      hasAuth: true\n    }\n  },\n  {\n    path: '/:pathMatch(.*)*',\n    redirect: { name: 'Home' }\n  }\n]\n\nconst router = createRouter({\n  history: createWebHistory('/'),\n  routes\n})\n\nexport default router\n"
  },
  {
    "path": "dashboard/src/services/__snapshots__/auth.spec.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`AuthService should return a token when user login 1`] = `\nObject {\n  \"data\": Object {\n    \"token\": \"123.123.123\",\n  },\n  \"errors\": null,\n}\n`;\n\nexports[`AuthService should return an user when user register 1`] = `\nObject {\n  \"data\": Object {\n    \"email\": \"igor@igor.me\",\n    \"name\": \"Igor\",\n    \"password\": \"123\",\n  },\n  \"errors\": null,\n}\n`;\n"
  },
  {
    "path": "dashboard/src/services/auth.js",
    "content": "export default httpClient => ({\n  register: async ({ name, email, password }) => {\n    const response = await httpClient.post('/auth/register', {\n      name,\n      email,\n      password\n    })\n    let errors = null\n\n    if (!response.data) {\n      errors = {\n        status: response.request.status,\n        statusText: response.request.statusText\n      }\n    }\n\n    return {\n      data: response.data,\n      errors\n    }\n  },\n  login: async ({ email, password }) => {\n    const response = await httpClient.post('/auth/login', {\n      email,\n      password\n    })\n    let errors = null\n\n    if (!response.data) {\n      errors = {\n        status: response.request.status,\n        statusText: response.request.statusText\n      }\n    }\n\n    return {\n      data: response.data,\n      errors\n    }\n  }\n})\n"
  },
  {
    "path": "dashboard/src/services/auth.spec.js",
    "content": "import mockAxios from 'axios'\nimport AuthService from './auth'\n\njest.mock('axios')\n\ndescribe('AuthService', () => {\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('should return a token when user login', async () => {\n    const token = '123.123.123'\n    mockAxios.post.mockImplementationOnce(() => {\n      return Promise.resolve({ data: { token } })\n    })\n\n    const response = await AuthService(mockAxios).login({ email: 'igor@igor.me', password: '123' })\n    expect(response.data).toHaveProperty('token')\n    expect(response).toMatchSnapshot()\n  })\n\n  it('should return an user when user register', async () => {\n    const user = {\n      name: 'Igor',\n      password: '123',\n      email: 'igor@igor.me'\n    }\n    mockAxios.post.mockImplementationOnce(() => {\n      return Promise.resolve({ data: user })\n    })\n\n    const response = await AuthService(mockAxios).register(user)\n    expect(response.data).toHaveProperty('name')\n    expect(response.data).toHaveProperty('password')\n    expect(response.data).toHaveProperty('email')\n    expect(response).toMatchSnapshot()\n  })\n\n  it('should throw an error when not found', async () => {\n    const errors = { status: 404, statusText: 'Not Found' }\n    mockAxios.post.mockImplementationOnce(() => {\n      return Promise.resolve({ request: errors })\n    })\n\n    const response = await AuthService(mockAxios).login({ email: 'igor@igor.me', password: '123' })\n    expect(response.errors).toHaveProperty('status')\n    expect(response.errors).toHaveProperty('statusText')\n  })\n})\n"
  },
  {
    "path": "dashboard/src/services/feedbacks.js",
    "content": "const defaultPagination = {\n  limit: 5,\n  offset: 0\n}\n\nexport default httpClient => ({\n  getAll: async ({ type, limit, offset } = defaultPagination) => {\n    const query = { limit, offset }\n    if (type) {\n      query.type = type\n    }\n    const response = await httpClient.get('/feedbacks', { params: query })\n\n    return { data: response.data }\n  },\n  getSummary: async () => {\n    const response = await httpClient.get('/feedbacks/summary')\n    return { data: response.data }\n  }\n})\n"
  },
  {
    "path": "dashboard/src/services/index.js",
    "content": "import axios from 'axios'\nimport router from '../router'\nimport { setGlobalLoading } from '../store/global'\nimport AuthService from './auth'\nimport UsersService from './users'\nimport FeedbacksService from './feedbacks'\n\nconst API_ENVS = {\n  production: 'https://backend-treinamento-vue3.vercel.app',\n  development: '',\n  local: 'http://localhost:3000'\n}\n\nconst httpClient = axios.create({\n  baseURL: API_ENVS[process.env.NODE_ENV] || API_ENVS.local\n})\n\nhttpClient.interceptors.request.use(config => {\n  setGlobalLoading(true)\n  const token = window.localStorage.getItem('token')\n\n  if (token) {\n    config.headers.common.Authorization = `Bearer ${token}`\n  }\n\n  return config\n})\n\nhttpClient.interceptors.response.use((response) => {\n  setGlobalLoading(false)\n  return response\n}, (error) => {\n  const canThrowAnError = error.request.status === 0 ||\n    error.request.status === 500\n\n  if (canThrowAnError) {\n    setGlobalLoading(false)\n    throw new Error(error.message)\n  }\n\n  if (error.response.status === 401) {\n    router.push({ name: 'Home' })\n  }\n\n  setGlobalLoading(false)\n  return error\n})\n\nexport default {\n  auth: AuthService(httpClient),\n  users: UsersService(httpClient),\n  feedbacks: FeedbacksService(httpClient)\n}\n"
  },
  {
    "path": "dashboard/src/services/users.js",
    "content": "export default httpClient => ({\n  getMe: async () => {\n    const response = await httpClient.get('/users/me')\n\n    return {\n      data: response.data\n    }\n  },\n  generateApikey: async () => {\n    const response = await httpClient.post('/users/me/apikey')\n\n    return {\n      data: response.data\n    }\n  }\n})\n"
  },
  {
    "path": "dashboard/src/store/global.js",
    "content": "import { reactive } from 'vue'\n\nconst state = reactive({\n  isLoading: false\n})\n\nexport default state\n\nexport function setGlobalLoading (status) {\n  state.isLoading = status\n}\n"
  },
  {
    "path": "dashboard/src/store/index.js",
    "content": "import { readonly } from 'vue'\nimport UserModule from './user'\nimport GlobalModule from './global'\n\nexport default readonly({\n  User: UserModule,\n  Global: GlobalModule\n})\n"
  },
  {
    "path": "dashboard/src/store/user.js",
    "content": "import { reactive } from 'vue'\n\nconst userInitialState = {\n  currentUser: {}\n}\n\nlet state = reactive(userInitialState)\n\nexport default state\n\nexport function resetUserStore () {\n  state = reactive(userInitialState)\n}\n\nexport function cleanCurrentUser () {\n  state.currentUser = {}\n}\n\nexport function setCurrentUser (user) {\n  state.currentUser = user\n}\n\nexport function setApiKey (apiKey) {\n  const currentUser = { ...state.currentUser, apiKey }\n  state.currentUser = currentUser\n}\n"
  },
  {
    "path": "dashboard/src/store/user.spec.js",
    "content": "import useStore from '../hooks/useStore'\nimport {\n  resetUserStore,\n  setApiKey,\n  cleanCurrentUser,\n  setCurrentUser\n} from './user'\n\ndescribe('UserStore', () => {\n  afterEach(() => {\n    resetUserStore()\n  })\n\n  it('should set current user', () => {\n    const store = useStore()\n    setCurrentUser({ name: 'Igor' })\n    expect(store.User.currentUser.name).toBe('Igor')\n  })\n\n  it('should set api_key on current user', () => {\n    const store = useStore()\n    setApiKey('123')\n    expect(store.User.currentUser.apiKey).toBe('123')\n  })\n\n  it('should clean current user', () => {\n    const store = useStore()\n    setCurrentUser({ name: 'Igor' })\n    expect(store.User.currentUser.name).toBe('Igor')\n    cleanCurrentUser()\n\n    expect(store.User.currentUser.name).toBeFalsy()\n  })\n})\n"
  },
  {
    "path": "dashboard/src/utils/bus.js",
    "content": "import Emitter from 'tiny-emitter'\n\nexport default new Emitter()\n"
  },
  {
    "path": "dashboard/src/utils/date.js",
    "content": "export function getDiffTimeBetweenCurrentDate (dateString = '', now = new Date()) {\n  const dayInMilliseconds = 86400000\n  if ([null, undefined, false, true].includes(dateString)) {\n    return dateString\n  }\n  const date = new Date(dateString)\n  const isInvalidDate = isNaN(date.getTime())\n\n  if (isInvalidDate) {\n    return dateString\n  }\n\n  const month = date.getMonth()\n  const day = date.getDate()\n  const hour = date.getHours()\n  const minutes = date.getMinutes()\n  const seconds = date.getSeconds()\n\n  const buildMessage = (label, value) => {\n    if (value !== 1) {\n      return `${value} ${label}s atrás`\n    }\n\n    return `${value} ${label} atrás`\n  }\n  const notZero = value => value !== 0\n\n  if (month !== now.getMonth()) {\n    const diff = Math.abs(now - date)\n    const days = Math.ceil(diff / dayInMilliseconds)\n\n    return buildMessage('dia', days)\n  }\n\n  if (day < now.getDate() && notZero(day)) { return buildMessage('dia', now.getDate() - day) }\n  if (hour < now.getHours() && notZero(hour)) { return buildMessage('hora', now.getHours() - hour) }\n  if (minutes < now.getMinutes() && notZero(minutes)) { return buildMessage('minuto', now.getMinutes() - minutes) }\n  if (seconds < now.getSeconds() && notZero(seconds)) { return buildMessage('segundo', now.getSeconds() - seconds) }\n\n  return buildMessage('segundo', 1)\n}\n"
  },
  {
    "path": "dashboard/src/utils/timeout.js",
    "content": "export function wait (timeMs) {\n  return new Promise(resolve => {\n    setTimeout(resolve, timeMs)\n  })\n}\n"
  },
  {
    "path": "dashboard/src/utils/validators.js",
    "content": "export function validateEmptyAndLength3 (value) {\n  if (!value) {\n    return '*Este campo é obrigatório'\n  }\n\n  if (value.length < 3) {\n    return '*Este campo precisa de no mínimo 3 caracteres'\n  }\n\n  return true\n}\n\nexport function validateEmptyAndEmail (value) {\n  if (!value) {\n    return '*Este campo é obrigatório'\n  }\n\n  const isValid = /^[a-z0-9.]+@[a-z0-9]+\\.[a-z]+(\\.[a-z]+)?$/i.test(value)\n\n  if (!isValid) {\n    return '*Este campo precisa ser um e-mail'\n  }\n\n  return true\n}\n"
  },
  {
    "path": "dashboard/src/utils/validators.spec.js",
    "content": "import {\n  validateEmptyAndEmail,\n  validateEmptyAndLength3\n} from './validators'\n\ndescribe('Validators utils', () => {\n  it('should give an error with empty payload', () => {\n    expect(validateEmptyAndLength3()).toBe('*Este campo é obrigatório')\n  })\n\n  it('should give an error with less then 3 character payload', () => {\n    expect(validateEmptyAndLength3('12')).toBe('*Este campo precisa de no mínimo 3 caracteres')\n  })\n\n  it('should returns true when pass a correct param', () => {\n    expect(validateEmptyAndLength3('1234')).toBe(true)\n  })\n\n  it('should give an error with empty payload', () => {\n    expect(validateEmptyAndEmail()).toBe('*Este campo é obrigatório')\n  })\n\n  it('should give an error with a invalid param', () => {\n    expect(validateEmptyAndEmail('myemail@')).toBe('*Este campo precisa ser um e-mail')\n  })\n\n  it('should returns true when pass a correct param', () => {\n    expect(validateEmptyAndEmail('igor@igor.me')).toBe(true)\n  })\n})\n"
  },
  {
    "path": "dashboard/src/views/Credencials/index.vue",
    "content": "<template>\n  <div class=\"flex justify-center w-full h-28 bg-brand-main\">\n    <header-logged />\n  </div>\n\n  <div class=\"flex flex-col items-center justify-center h-64 bg-brand-gray\">\n    <h1 class=\"text-4xl font-black text-center text-gray-800\">\n      Credenciais\n    </h1>\n    <p class=\"text-lg text-center text-gray-800 font-regular\">\n      Guia de instalação e geração de suas credenciais\n    </p>\n  </div>\n\n  <div class=\"flex justify-center w-full h-full\">\n    <div class=\"flex flex-col w-4/5 max-w-6xl py-10\">\n      <h1 class=\"text-3xl font-black text-brand-darkgray\">\n        Instalação e configuração\n      </h1>\n      <p class=\"mt-10 text-lg text-gray-800 font-regular\">\n        Este aqui é a sua chave de api\n      </p>\n\n      <content-loader\n        v-if=\"store.Global.isLoading || state.isLoading\"\n        class=\"rounded\"\n        width=\"600px\"\n        height=\"50px\"\n      />\n      <div\n        v-else\n        class=\"flex py-3 pl-5 mt-2 rounded justify-between items-center bg-brand-gray w-full lg:w-1/2\"\n      >\n        <span v-if=\"state.hasError\">Erro ao carregar a apikey</span>\n        <span v-else id=\"apikey\">{{ store.User.currentUser.apiKey }}</span>\n        <div class=\"flex ml-20 mr-5\" v-if=\"!state.hasError\">\n          <icon\n            @click=\"handleCopy\"\n            name=\"copy\"\n            :color=\"brandColors.graydark\"\n            size=\"24\"\n            class=\"cursor-pointer\"\n          />\n          <icon\n            id=\"generate-apikey\"\n            @click=\"handleGenerateApikey\"\n            name=\"loading\"\n            :color=\"brandColors.graydark\"\n            size=\"24\"\n            class=\"cursor-pointer ml-3\"\n          />\n        </div>\n      </div>\n\n      <p class=\"mt-5 text-lg text-gray-800 font-regular\">\n        Coloque o script abaixo no seu site para começar a receber feedbacks\n      </p>\n\n      <content-loader\n        v-if=\"store.Global.isLoading || state.isLoading\"\n        class=\"rounded\"\n        width=\"600px\"\n        height=\"50px\"\n      />\n      <div\n        v-else\n        class=\"py-3 pl-5 pr-20 mt-2 rounded bg-brand-gray w-full lg:w-2/3 overflow-x-scroll\"\n      >\n        <span v-if=\"state.hasError\">Erro ao carregar o script</span>\n        <pre v-else>\n&lt;script\n  defer\n  async\n  onload=\"init('{{store.User.currentUser.apiKey}}')\"\n  src=\"https://igorhalfeld-feedbacker-widget.netlify.app/init.js\"\n&gt;&lt;/script&gt;\n        </pre>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { reactive, watch } from 'vue'\nimport { useToast } from 'vue-toastification'\nimport HeaderLogged from '../../components/HeaderLogged'\nimport ContentLoader from '../../components/ContentLoader'\nimport Icon from '../../components/Icon'\nimport useStore from '../../hooks/useStore'\nimport palette from '../../../palette'\nimport services from '../../services'\nimport { setApiKey } from '../../store/user'\n\nexport default {\n  components: { ContentLoader, HeaderLogged, Icon },\n  setup () {\n    const store = useStore()\n    const toast = useToast()\n    const state = reactive({\n      hasError: false,\n      isLoading: false\n    })\n\n    watch(() => store.User.currentUser, () => {\n      if (!store.Global.isLoading && !store.User.currentUser.apiKey) {\n        handleError(true)\n      }\n    })\n\n    function handleError (error) {\n      state.isLoading = false\n      state.hasError = !!error\n    }\n\n    async function handleGenerateApikey () {\n      try {\n        state.isLoading = true\n        const { data } = await services.users.generateApikey()\n\n        setApiKey(data.apiKey)\n        state.isLoading = false\n      } catch (error) {\n        handleError(error)\n      }\n    }\n\n    async function handleCopy () {\n      toast.clear()\n\n      try {\n        await navigator.clipboard.writeText(store.User.currentUser.apiKey)\n        toast.success('Copiado!')\n      } catch (error) {\n        handleError(error)\n      }\n    }\n\n    return {\n      state,\n      store,\n      handleGenerateApikey,\n      handleCopy,\n      brandColors: palette.brand\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/views/Feedbacks/Filters.vue",
    "content": "<template>\n  <div class=\"flex flex-col\">\n    <h1 class=\"text-2xl font-regular text-brand-darkgray\">\n      Filtros\n    </h1>\n\n    <ul class=\"flex flex-col mt-3 list-none\">\n      <li\n        v-for=\"filter in state.filters\"\n        :key=\"filter.label\"\n        :class=\"{\n          'bg-gray-200 bg-opacity-50': filter.active\n        }\"\n        @click=\"() => handleSelect(filter)\"\n        class=\"flex items-center justify-between px-4 py-1 rounded cursor-pointer\"\n      >\n        <div class=\"flex items-center\">\n          <span\n            :class=\"filter.color.bg\"\n            class=\"inline-block w-2 h-2 mr-2 rounded-full\"/> {{ filter.label }}\n        </div>\n        <span\n          :class=\"filter.active ? filter.color.text : 'text-brand-graydark'\"\n          class=\"font-bold\"\n        >\n          {{ filter.amount }}\n        </span>\n      </li>\n    </ul>\n  </div>\n</template>\n\n<script>\nimport { reactive } from 'vue'\nimport services from '../../services'\nimport useStore from '../../hooks/useStore'\n\nconst LABELS = {\n  all: 'Todos',\n  issue: 'Problemas',\n  idea: 'Ideias',\n  other: 'Outros'\n}\n\nconst COLORS = {\n  all: { text: 'text-brand-info', bg: 'bg-brand-info' },\n  issue: { text: 'text-brand-danger', bg: 'bg-brand-danger' },\n  idea: { text: 'text-brand-warning', bg: 'bg-brand-warning' },\n  other: { text: 'text-brand-graydark', bg: 'bg-brand-graydark' }\n}\n\nfunction applyFiltersStructure (summary) {\n  return Object.keys(summary).reduce((acc, cur) => {\n    const currentFilter = {\n      label: LABELS[cur],\n      color: COLORS[cur],\n      amount: summary[cur]\n    }\n\n    if (cur === 'all') {\n      currentFilter.active = true\n    } else {\n      currentFilter.type = cur\n    }\n\n    return [...acc, currentFilter]\n  }, [])\n}\n\nexport default {\n  async setup (_, { emit }) {\n    const store = useStore('Global')\n    const state = reactive({\n      hasError: false,\n      filters: [\n        { label: null, amount: null }\n      ]\n    })\n\n    try {\n      const { data } = await services.feedbacks.getSummary()\n      state.filters = applyFiltersStructure(data)\n    } catch (error) {\n      state.hasError = !!error\n      state.filters = applyFiltersStructure({ all: 0, issue: 0, idea: 0, other: 0 })\n    }\n\n    function handleSelect ({ type }) {\n      if (store.isLoading) {\n        return\n      }\n\n      state.filters = state.filters.map((filter) => {\n        if (filter.type === type) {\n          return { ...filter, active: true }\n        }\n        return { ...filter, active: false }\n      })\n\n      emit('select', type)\n    }\n\n    return {\n      state,\n      handleSelect\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/views/Feedbacks/FiltersLoading.vue",
    "content": "<template>\n  <content-loader\n    class=\"flex flex-col items-center rounded\"\n    width=\"100%\"\n    height=\"300px\"\n  >\n\n    <content-loader\n      class=\"mt-3 rounded\"\n      width=\"90%\"\n      height=\"50px\"\n      animation-duration=\"2s\"\n    />\n\n    <content-loader\n      class=\"mt-3 rounded\"\n      width=\"80%\"\n      height=\"50px\"\n      animation-duration=\"2.3s\"\n    />\n\n    <content-loader\n      class=\"mt-3 rounded\"\n      width=\"80%\"\n      height=\"50px\"\n      animation-duration=\"2.7s\"\n    />\n\n    <content-loader\n      class=\"mt-3 rounded\"\n      width=\"80%\"\n      height=\"50px\"\n      animation-duration=\"2.9s\"\n    />\n  </content-loader>\n</template>\n\n<script>\nimport ContentLoader from '../../components/ContentLoader'\n\nexport default {\n  components: { ContentLoader }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/views/Feedbacks/index.vue",
    "content": "<template>\n  <div class=\"flex justify-center w-full h-28 bg-brand-main\">\n    <header-logged />\n  </div>\n\n  <div class=\"flex flex-col items-center justify-center h-64 bg-brand-gray\">\n    <h1 class=\"text-4xl font-black text-center text-gray-800\">\n      Feedbacks\n    </h1>\n    <p class=\"text-lg text-center text-gray-800 font-regular\">\n      Detalhes de todos os feedbacks recebidos.\n    </p>\n  </div>\n\n  <div class=\"flex justify-center w-full pb-20\">\n    <div class=\"w-4/5 max-w-6xl py-10 grid grid-cols-4 gap-2\">\n      <div>\n        <h1 class=\"text-3xl font-black text-brand-darkgray\">\n          Listagem\n        </h1>\n        <suspense>\n          <template #default>\n            <filters\n              @select=\"changeFeedbacksType\"\n              class=\"mt-8 animate__animated animate__fadeIn animate__faster\"\n            />\n          </template>\n          <template #fallback>\n            <filters-loading class=\"mt-8\" />\n          </template>\n        </suspense>\n\n      </div>\n      <div class=\"px-10 pt-20 col-span-3\">\n        <p\n          v-if=\"state.hasError\"\n          class=\"text-lg text-center text-gray-800 font-regular\">\n          Aconteceu um erro ao carregar os feedbacks 🥺\n        </p>\n        <p\n          v-if=\"!state.feedbacks.length && !state.isLoading && !state.isLoadingFeedbacks && !state.hasError\"\n          class=\"text-lg text-center text-gray-800 font-regular\">\n          Ainda nenhum feedback recebido 🤓\n        </p>\n\n        <feedback-card-loading v-if=\"state.isLoading || state.isLoadingFeedbacks\" />\n        <feedback-card\n          v-else\n          v-for=\"(feedback, index) in state.feedbacks\"\n          :key=\"feedback.id\"\n          :is-opened=\"index === 0\"\n          :feedback=\"feedback\"\n          class=\"mb-8\"\n        />\n        <feedback-card-loading v-if=\"state.isLoadingMoreFeedbacks\" />\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { reactive, onMounted, onUnmounted, onErrorCaptured } from 'vue'\nimport Filters from './Filters'\nimport FiltersLoading from './FiltersLoading'\nimport HeaderLogged from '../../components/HeaderLogged'\nimport FeedbackCard from '../../components/FeedbackCard'\nimport FeedbackCardLoading from '../../components/FeedbackCard/Loading'\nimport services from '../../services'\n\nexport default {\n  components: {\n    HeaderLogged,\n    Filters,\n    FiltersLoading,\n    FeedbackCard,\n    FeedbackCardLoading\n  },\n  setup () {\n    const state = reactive({\n      isLoading: false,\n      isLoadingFeedbacks: false,\n      isLoadingMoreFeedbacks: false,\n      feedbacks: [],\n      currentFeedbackType: '',\n      pagination: {\n        limit: 5,\n        offset: 0,\n        total: 0\n      },\n      hasError: false\n    })\n\n    onErrorCaptured(handleErrors)\n\n    onMounted(() => {\n      fetchFeedbacks()\n      window.addEventListener('scroll', handleScroll, false)\n    })\n\n    onUnmounted(() => {\n      window.removeEventListener('scroll', handleScroll, false)\n    })\n\n    function handleErrors (error) {\n      state.isLoading = false\n      state.isLoadingFeedbacks = false\n      state.isLoadingMoreFeedback = false\n      state.hasError = !!error\n    }\n\n    async function handleScroll () {\n      const isBottomOfWindow = Math.ceil(\n        document.documentElement.scrollTop + window.innerHeight\n      ) >= document.documentElement.scrollHeight\n\n      if (state.isLoading || state.isLoadingMoreFeedbacks) return\n      if (!isBottomOfWindow) return\n      if (state.feedbacks.length >= state.pagination.total) return\n\n      try {\n        state.isLoadingMoreFeedbacks = true\n        const { data } = await services.feedbacks.getAll({\n          ...state.pagination,\n          type: state.currentFeedbackType,\n          offset: (state.pagination.offset + 5)\n        })\n\n        if (data.results.length) {\n          state.feedbacks.push(...data.results)\n        }\n\n        state.isLoadingMoreFeedbacks = false\n        state.pagination = data.pagination\n      } catch (error) {\n        state.isLoadingMoreFeedbacks = false\n        handleErrors(error)\n      }\n    }\n\n    async function changeFeedbacksType (type) {\n      try {\n        state.isLoadingFeedbacks = true\n        state.pagination.offset = 0\n        state.pagination.limit = 5\n        state.currentFeedbackType = type\n        const { data } = await services.feedbacks.getAll({\n          type,\n          ...state.pagination\n        })\n\n        state.feedbacks = data.results\n        state.pagination = data.pagination\n        state.isLoadingFeedbacks = false\n      } catch (error) {\n        handleErrors(error)\n      }\n    }\n\n    async function fetchFeedbacks () {\n      try {\n        state.isLoading = true\n        const { data } = await services.feedbacks.getAll({\n          ...state.pagination,\n          type: state.currentFeedbackType\n        })\n\n        state.feedbacks = data.results\n        state.pagination = data.pagination\n        state.isLoading = false\n      } catch (error) {\n        handleErrors(error)\n      }\n    }\n\n    return {\n      state,\n      changeFeedbacksType\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/views/Home/Contact.vue",
    "content": "<template>\n  <div class=\"flex justify-center w-full\">\n    <div class=\"flex flex-col items-center w-4/5 max-w-6xl my-16\">\n      <h1 class=\"text-4xl font-black text-center text-gray-800\">\n        Alguma dúvida?\n      </h1>\n      <p class=\"text-lg text-center text-gray-800 font-regular\">\n        Quer saber melhor como funciona e quais são os preços?\n      </p>\n      <div class=\"mt-10\">\n        <a href=\"mailto:\" class=\"px-6 py-2 mt-10 font-bold text-white rounded-full bg-brand-main focus:outline-none\">\n          Nos mande um e-mail!\n        </a>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n\n}\n</script>\n"
  },
  {
    "path": "dashboard/src/views/Home/CustomHeader.vue",
    "content": "<template>\n  <header class=\"header\">\n    <div class=\"header-group\">\n      <div class=\"flex items-center justify-between py-10\">\n        <div class=\"w-28 lg:w-36\">\n          <img class=\"w-full\" src=\"../../assets/images/logo_white.png\" alt=\"logo\">\n        </div>\n\n        <div class=\"flex\">\n          <button\n            id=\"header-create-account-button\"\n            @click=\"() => emit('create-account')\"\n            class=\"px-6 py-2 font-bold rounded-full text-white focus:outline-none\"\n          >\n            Crie uma conta\n          </button>\n          <button\n            id=\"header-login-button\"\n            @click=\"() => emit('login')\"\n            class=\"px-6 py-2 font-bold bg-white rounded-full text-brand-main focus:outline-none\"\n          >\n            Entrar\n          </button>\n        </div>\n      </div>\n\n      <div class=\"flex flex-col mt-28\">\n        <h1 class=\"text-4xl font-black text-white\">\n          Tenha um feedback. <br>\n          E faça seus clientes mais <br class=\"hidden lg:inline-block\">\n          felizes!\n        </h1>\n        <p class=\"text-lg font-medium text-white\">\n          Receba ideias, reclamações e feedbacks com um <br class=\"hidden lg:inline-block\">\n          simples widget na página.\n        </p>\n        <div>\n          <button\n            @click=\"() => emit('create-account')\"\n            id=\"cta-create-account-button\"\n            class=\"px-6 py-2 mt-10 font-bold bg-white rounded-full text-brand-main focus:outline-none\"\n          >\n            Crie uma conta grátis\n          </button>\n        </div>\n      </div>\n\n    </div>\n  </header>\n</template>\n\n<script>\nexport default {\n  setup (_, { emit }) {\n    return { emit }\n  }\n}\n</script>\n\n<style lang=\"postcss\" scoped>\n.header {\n  @apply bg-brand-main w-full flex justify-center;\n  height: 700px;\n}\n\n.header-group {\n  @apply flex flex-col w-4/5 max-w-6xl;\n}\n\n@media (min-width: 640px) {\n  .header-group {\n    background-image: url(../../assets/images/blue_balloons.png);\n    background-size: 628px;\n    background-position: 90% 100%;\n    background-repeat: no-repeat;\n  }\n}\n</style>\n"
  },
  {
    "path": "dashboard/src/views/Home/Home.spec.js",
    "content": "import Home from '.'\nimport { shallowMount } from '@vue/test-utils'\nimport { routes } from '../../router'\n\nimport { createRouter, createWebHistory } from 'vue-router'\n\nconst router = createRouter({\n  history: createWebHistory('/'),\n  routes\n})\n\ndescribe('<Home />', () => {\n  it('should render home correctly', async () => {\n    router.push('/')\n    await router.isReady()\n    const wrapper = shallowMount(Home, {\n      global: {\n        plugins: [router]\n      }\n    })\n\n    expect(wrapper.html()).toMatchSnapshot()\n  })\n})\n"
  },
  {
    "path": "dashboard/src/views/Home/__snapshots__/Home.spec.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`<Home /> should render home correctly 1`] = `\n<custom-header-stub></custom-header-stub>\n<contact-stub></contact-stub>\n<div class=\"flex justify-center py-10 bg-brand-gray\">\n  <p class=\"font-medium text-center text-gray-800\">feedbacker © 2021</p>\n</div>\n`;\n"
  },
  {
    "path": "dashboard/src/views/Home/index.vue",
    "content": "<template>\n  <custom-header\n    @create-account=\"handleAccountCreate\"\n    @login=\"handleLogin\"\n  />\n  <contact />\n  <div class=\"flex justify-center py-10 bg-brand-gray\">\n    <p class=\"font-medium text-center text-gray-800\">feedbacker © 2021</p>\n  </div>\n</template>\n\n<script>\nimport { onMounted } from 'vue'\nimport { useRouter } from 'vue-router'\nimport CustomHeader from './CustomHeader.vue'\nimport Contact from './Contact.vue'\nimport useModal from '../../hooks/useModal'\n\nexport default {\n  components: { CustomHeader, Contact },\n  setup () {\n    const router = useRouter()\n    const modal = useModal()\n\n    onMounted(() => {\n      const token = window.localStorage.getItem('token')\n      if (token) {\n        router.push({ name: 'Feedbacks' })\n      }\n    })\n\n    function handleLogin () {\n      modal.open({\n        component: 'ModalLogin'\n      })\n    }\n\n    function handleAccountCreate () {\n      modal.open({\n        component: 'ModalAccountCreate'\n      })\n    }\n\n    return {\n      handleLogin,\n      handleAccountCreate\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "dashboard/tailwind.config.js",
    "content": "const colors = require('tailwindcss/colors')\nconst palette = require('./palette')\n\nmodule.exports = {\n  purge: [\n    './src/**/*.html',\n    './src/**/*.vue',\n    './src/**/*.jsx'\n  ],\n  presets: [],\n  darkMode: false, // or 'media' or 'class'\n  theme: {\n    extend: {\n      colors: palette\n    },\n    screens: {\n      sm: '640px',\n      md: '768px',\n      lg: '1024px',\n      xl: '1280px',\n      '2xl': '1536px'\n    },\n    colors: {\n      transparent: 'transparent',\n      current: 'currentColor',\n\n      black: colors.black,\n      white: colors.white,\n      gray: colors.coolGray,\n      red: colors.red,\n      yellow: colors.amber,\n      green: colors.emerald,\n      blue: colors.blue,\n      indigo: colors.indigo,\n      purple: colors.violet,\n      pink: colors.pink\n    },\n    spacing: {\n      px: '1px',\n      0: '0px',\n      0.5: '0.125rem',\n      1: '0.25rem',\n      1.5: '0.375rem',\n      2: '0.5rem',\n      2.5: '0.625rem',\n      3: '0.75rem',\n      3.5: '0.875rem',\n      4: '1rem',\n      5: '1.25rem',\n      6: '1.5rem',\n      7: '1.75rem',\n      8: '2rem',\n      9: '2.25rem',\n      10: '2.5rem',\n      11: '2.75rem',\n      12: '3rem',\n      14: '3.5rem',\n      16: '4rem',\n      20: '5rem',\n      24: '6rem',\n      28: '7rem',\n      32: '8rem',\n      36: '9rem',\n      40: '10rem',\n      44: '11rem',\n      48: '12rem',\n      52: '13rem',\n      56: '14rem',\n      60: '15rem',\n      64: '16rem',\n      72: '18rem',\n      80: '20rem',\n      96: '24rem'\n    },\n    animation: {\n      none: 'none',\n      spin: 'spin 1s linear infinite',\n      ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',\n      pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',\n      bounce: 'bounce 1s infinite'\n    },\n    backgroundColor: (theme) => theme('colors'),\n    backgroundImage: {\n      none: 'none',\n      'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))',\n      'gradient-to-tr': 'linear-gradient(to top right, var(--tw-gradient-stops))',\n      'gradient-to-r': 'linear-gradient(to right, var(--tw-gradient-stops))',\n      'gradient-to-br': 'linear-gradient(to bottom right, var(--tw-gradient-stops))',\n      'gradient-to-b': 'linear-gradient(to bottom, var(--tw-gradient-stops))',\n      'gradient-to-bl': 'linear-gradient(to bottom left, var(--tw-gradient-stops))',\n      'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))',\n      'gradient-to-tl': 'linear-gradient(to top left, var(--tw-gradient-stops))'\n    },\n    backgroundOpacity: (theme) => theme('opacity'),\n    backgroundPosition: {\n      bottom: 'bottom',\n      center: 'center',\n      left: 'left',\n      'left-bottom': 'left bottom',\n      'left-top': 'left top',\n      right: 'right',\n      'right-bottom': 'right bottom',\n      'right-top': 'right top',\n      top: 'top'\n    },\n    backgroundSize: {\n      auto: 'auto',\n      cover: 'cover',\n      contain: 'contain'\n    },\n    borderColor: (theme) => ({\n      ...theme('colors'),\n      DEFAULT: theme('colors.gray.200', 'currentColor')\n    }),\n    borderOpacity: (theme) => theme('opacity'),\n    borderRadius: {\n      none: '0px',\n      sm: '0.125rem',\n      DEFAULT: '0.25rem',\n      md: '0.375rem',\n      lg: '0.5rem',\n      xl: '0.75rem',\n      '2xl': '1rem',\n      '3xl': '1.5rem',\n      full: '9999px'\n    },\n    borderWidth: {\n      DEFAULT: '1px',\n      0: '0px',\n      2: '2px',\n      4: '4px',\n      8: '8px'\n    },\n    boxShadow: {\n      sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',\n      DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',\n      md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',\n      lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',\n      xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',\n      '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',\n      inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',\n      none: 'none'\n    },\n    container: {},\n    cursor: {\n      auto: 'auto',\n      default: 'default',\n      pointer: 'pointer',\n      wait: 'wait',\n      text: 'text',\n      move: 'move',\n      'not-allowed': 'not-allowed'\n    },\n    divideColor: (theme) => theme('borderColor'),\n    divideOpacity: (theme) => theme('borderOpacity'),\n    divideWidth: (theme) => theme('borderWidth'),\n    fill: { current: 'currentColor' },\n    flex: {\n      1: '1 1 0%',\n      auto: '1 1 auto',\n      initial: '0 1 auto',\n      none: 'none'\n    },\n    flexGrow: {\n      0: '0',\n      DEFAULT: '1'\n    },\n    flexShrink: {\n      0: '0',\n      DEFAULT: '1'\n    },\n    fontFamily: {\n      regular: ['RobotoRegular'],\n      medium: ['RobotoMedium'],\n      bold: ['RobotoBold'],\n      black: ['RobotoBlack'],\n      sans: [\n        'ui-sans-serif',\n        'system-ui',\n        '-apple-system',\n        'BlinkMacSystemFont',\n        '\"Segoe UI\"',\n        'Roboto',\n        '\"Helvetica Neue\"',\n        'Arial',\n        '\"Noto Sans\"',\n        'sans-serif',\n        '\"Apple Color Emoji\"',\n        '\"Segoe UI Emoji\"',\n        '\"Segoe UI Symbol\"',\n        '\"Noto Color Emoji\"'\n      ],\n      serif: ['ui-serif', 'Georgia', 'Cambria', '\"Times New Roman\"', 'Times', 'serif'],\n      mono: [\n        'ui-monospace',\n        'SFMono-Regular',\n        'Menlo',\n        'Monaco',\n        'Consolas',\n        '\"Liberation Mono\"',\n        '\"Courier New\"',\n        'monospace'\n      ]\n    },\n    fontSize: {\n      xs: ['0.75rem', { lineHeight: '1rem' }],\n      sm: ['0.875rem', { lineHeight: '1.25rem' }],\n      base: ['1rem', { lineHeight: '1.5rem' }],\n      lg: ['1.125rem', { lineHeight: '1.75rem' }],\n      xl: ['1.25rem', { lineHeight: '1.75rem' }],\n      '2xl': ['1.5rem', { lineHeight: '2rem' }],\n      '3xl': ['1.875rem', { lineHeight: '2.25rem' }],\n      '4xl': ['2.25rem', { lineHeight: '2.5rem' }],\n      '5xl': ['3rem', { lineHeight: '1' }],\n      '6xl': ['3.75rem', { lineHeight: '1' }],\n      '7xl': ['4.5rem', { lineHeight: '1' }],\n      '8xl': ['6rem', { lineHeight: '1' }],\n      '9xl': ['8rem', { lineHeight: '1' }]\n    },\n    fontWeight: {\n      thin: '100',\n      extralight: '200',\n      light: '300',\n      normal: '400',\n      medium: '500',\n      semibold: '600',\n      bold: '700',\n      extrabold: '800',\n      black: '900'\n    },\n    gap: (theme) => theme('spacing'),\n    gradientColorStops: (theme) => theme('colors'),\n    gridAutoColumns: {\n      auto: 'auto',\n      min: 'min-content',\n      max: 'max-content',\n      fr: 'minmax(0, 1fr)'\n    },\n    gridAutoRows: {\n      auto: 'auto',\n      min: 'min-content',\n      max: 'max-content',\n      fr: 'minmax(0, 1fr)'\n    },\n    gridColumn: {\n      auto: 'auto',\n      'span-1': 'span 1 / span 1',\n      'span-2': 'span 2 / span 2',\n      'span-3': 'span 3 / span 3',\n      'span-4': 'span 4 / span 4',\n      'span-5': 'span 5 / span 5',\n      'span-6': 'span 6 / span 6',\n      'span-7': 'span 7 / span 7',\n      'span-8': 'span 8 / span 8',\n      'span-9': 'span 9 / span 9',\n      'span-10': 'span 10 / span 10',\n      'span-11': 'span 11 / span 11',\n      'span-12': 'span 12 / span 12',\n      'span-full': '1 / -1'\n    },\n    gridColumnEnd: {\n      auto: 'auto',\n      1: '1',\n      2: '2',\n      3: '3',\n      4: '4',\n      5: '5',\n      6: '6',\n      7: '7',\n      8: '8',\n      9: '9',\n      10: '10',\n      11: '11',\n      12: '12',\n      13: '13'\n    },\n    gridColumnStart: {\n      auto: 'auto',\n      1: '1',\n      2: '2',\n      3: '3',\n      4: '4',\n      5: '5',\n      6: '6',\n      7: '7',\n      8: '8',\n      9: '9',\n      10: '10',\n      11: '11',\n      12: '12',\n      13: '13'\n    },\n    gridRow: {\n      auto: 'auto',\n      'span-1': 'span 1 / span 1',\n      'span-2': 'span 2 / span 2',\n      'span-3': 'span 3 / span 3',\n      'span-4': 'span 4 / span 4',\n      'span-5': 'span 5 / span 5',\n      'span-6': 'span 6 / span 6',\n      'span-full': '1 / -1'\n    },\n    gridRowStart: {\n      auto: 'auto',\n      1: '1',\n      2: '2',\n      3: '3',\n      4: '4',\n      5: '5',\n      6: '6',\n      7: '7'\n    },\n    gridRowEnd: {\n      auto: 'auto',\n      1: '1',\n      2: '2',\n      3: '3',\n      4: '4',\n      5: '5',\n      6: '6',\n      7: '7'\n    },\n    transformOrigin: {\n      center: 'center',\n      top: 'top',\n      'top-right': 'top right',\n      right: 'right',\n      'bottom-right': 'bottom right',\n      bottom: 'bottom',\n      'bottom-left': 'bottom left',\n      left: 'left',\n      'top-left': 'top left'\n    },\n    gridTemplateColumns: {\n      none: 'none',\n      1: 'repeat(1, minmax(0, 1fr))',\n      2: 'repeat(2, minmax(0, 1fr))',\n      3: 'repeat(3, minmax(0, 1fr))',\n      4: 'repeat(4, minmax(0, 1fr))',\n      5: 'repeat(5, minmax(0, 1fr))',\n      6: 'repeat(6, minmax(0, 1fr))',\n      7: 'repeat(7, minmax(0, 1fr))',\n      8: 'repeat(8, minmax(0, 1fr))',\n      9: 'repeat(9, minmax(0, 1fr))',\n      10: 'repeat(10, minmax(0, 1fr))',\n      11: 'repeat(11, minmax(0, 1fr))',\n      12: 'repeat(12, minmax(0, 1fr))'\n    },\n    gridTemplateRows: {\n      none: 'none',\n      1: 'repeat(1, minmax(0, 1fr))',\n      2: 'repeat(2, minmax(0, 1fr))',\n      3: 'repeat(3, minmax(0, 1fr))',\n      4: 'repeat(4, minmax(0, 1fr))',\n      5: 'repeat(5, minmax(0, 1fr))',\n      6: 'repeat(6, minmax(0, 1fr))'\n    },\n    height: (theme) => ({\n      auto: 'auto',\n      ...theme('spacing'),\n      '1/2': '50%',\n      '1/3': '33.333333%',\n      '2/3': '66.666667%',\n      '1/4': '25%',\n      '2/4': '50%',\n      '3/4': '75%',\n      '1/5': '20%',\n      '2/5': '40%',\n      '3/5': '60%',\n      '4/5': '80%',\n      '1/6': '16.666667%',\n      '2/6': '33.333333%',\n      '3/6': '50%',\n      '4/6': '66.666667%',\n      '5/6': '83.333333%',\n      full: '100%',\n      screen: '100vh'\n    }),\n    inset: (theme, { negative }) => ({\n      auto: 'auto',\n      ...theme('spacing'),\n      ...negative(theme('spacing')),\n      '1/2': '50%',\n      '1/3': '33.333333%',\n      '2/3': '66.666667%',\n      '1/4': '25%',\n      '2/4': '50%',\n      '3/4': '75%',\n      full: '100%',\n      '-1/2': '-50%',\n      '-1/3': '-33.333333%',\n      '-2/3': '-66.666667%',\n      '-1/4': '-25%',\n      '-2/4': '-50%',\n      '-3/4': '-75%',\n      '-full': '-100%'\n    }),\n    keyframes: {\n      spin: {\n        to: {\n          transform: 'rotate(360deg)'\n        }\n      },\n      ping: {\n        '75%, 100%': {\n          transform: 'scale(2)',\n          opacity: '0'\n        }\n      },\n      pulse: {\n        '50%': {\n          opacity: '.5'\n        }\n      },\n      bounce: {\n        '0%, 100%': {\n          transform: 'translateY(-25%)',\n          animationTimingFunction: 'cubic-bezier(0.8,0,1,1)'\n        },\n        '50%': {\n          transform: 'none',\n          animationTimingFunction: 'cubic-bezier(0,0,0.2,1)'\n        }\n      }\n    },\n    letterSpacing: {\n      tighter: '-0.05em',\n      tight: '-0.025em',\n      normal: '0em',\n      wide: '0.025em',\n      wider: '0.05em',\n      widest: '0.1em'\n    },\n    lineHeight: {\n      none: '1',\n      tight: '1.25',\n      snug: '1.375',\n      normal: '1.5',\n      relaxed: '1.625',\n      loose: '2',\n      3: '.75rem',\n      4: '1rem',\n      5: '1.25rem',\n      6: '1.5rem',\n      7: '1.75rem',\n      8: '2rem',\n      9: '2.25rem',\n      10: '2.5rem'\n    },\n    listStyleType: {\n      none: 'none',\n      disc: 'disc',\n      decimal: 'decimal'\n    },\n    margin: (theme, { negative }) => ({\n      auto: 'auto',\n      ...theme('spacing'),\n      ...negative(theme('spacing'))\n    }),\n    maxHeight: (theme) => ({\n      ...theme('spacing'),\n      full: '100%',\n      screen: '100vh'\n    }),\n    maxWidth: (theme, { breakpoints }) => ({\n      none: 'none',\n      0: '0rem',\n      xs: '20rem',\n      sm: '24rem',\n      md: '28rem',\n      lg: '32rem',\n      xl: '36rem',\n      '2xl': '42rem',\n      '3xl': '48rem',\n      '4xl': '56rem',\n      '5xl': '64rem',\n      '6xl': '72rem',\n      '7xl': '80rem',\n      full: '100%',\n      min: 'min-content',\n      max: 'max-content',\n      prose: '65ch',\n      ...breakpoints(theme('screens'))\n    }),\n    minHeight: {\n      0: '0px',\n      full: '100%',\n      screen: '100vh'\n    },\n    minWidth: {\n      0: '0px',\n      full: '100%',\n      min: 'min-content',\n      max: 'max-content'\n    },\n    objectPosition: {\n      bottom: 'bottom',\n      center: 'center',\n      left: 'left',\n      'left-bottom': 'left bottom',\n      'left-top': 'left top',\n      right: 'right',\n      'right-bottom': 'right bottom',\n      'right-top': 'right top',\n      top: 'top'\n    },\n    opacity: {\n      0: '0',\n      5: '0.05',\n      10: '0.1',\n      20: '0.2',\n      25: '0.25',\n      30: '0.3',\n      40: '0.4',\n      50: '0.5',\n      60: '0.6',\n      70: '0.7',\n      75: '0.75',\n      80: '0.8',\n      90: '0.9',\n      95: '0.95',\n      100: '1'\n    },\n    order: {\n      first: '-9999',\n      last: '9999',\n      none: '0',\n      1: '1',\n      2: '2',\n      3: '3',\n      4: '4',\n      5: '5',\n      6: '6',\n      7: '7',\n      8: '8',\n      9: '9',\n      10: '10',\n      11: '11',\n      12: '12'\n    },\n    outline: {\n      none: ['2px solid transparent', '2px'],\n      white: ['2px dotted white', '2px'],\n      black: ['2px dotted black', '2px']\n    },\n    padding: (theme) => theme('spacing'),\n    placeholderColor: (theme) => theme('colors'),\n    placeholderOpacity: (theme) => theme('opacity'),\n    ringColor: (theme) => ({\n      DEFAULT: theme('colors.blue.500', '#3b82f6'),\n      ...theme('colors')\n    }),\n    ringOffsetColor: (theme) => theme('colors'),\n    ringOffsetWidth: {\n      0: '0px',\n      1: '1px',\n      2: '2px',\n      4: '4px',\n      8: '8px'\n    },\n    ringOpacity: (theme) => ({\n      DEFAULT: '0.5',\n      ...theme('opacity')\n    }),\n    ringWidth: {\n      DEFAULT: '3px',\n      0: '0px',\n      1: '1px',\n      2: '2px',\n      4: '4px',\n      8: '8px'\n    },\n    rotate: {\n      '-180': '-180deg',\n      '-90': '-90deg',\n      '-45': '-45deg',\n      '-12': '-12deg',\n      '-6': '-6deg',\n      '-3': '-3deg',\n      '-2': '-2deg',\n      '-1': '-1deg',\n      0: '0deg',\n      1: '1deg',\n      2: '2deg',\n      3: '3deg',\n      6: '6deg',\n      12: '12deg',\n      45: '45deg',\n      90: '90deg',\n      180: '180deg'\n    },\n    scale: {\n      0: '0',\n      50: '.5',\n      75: '.75',\n      90: '.9',\n      95: '.95',\n      100: '1',\n      105: '1.05',\n      110: '1.1',\n      125: '1.25',\n      150: '1.5'\n    },\n    skew: {\n      '-12': '-12deg',\n      '-6': '-6deg',\n      '-3': '-3deg',\n      '-2': '-2deg',\n      '-1': '-1deg',\n      0: '0deg',\n      1: '1deg',\n      2: '2deg',\n      3: '3deg',\n      6: '6deg',\n      12: '12deg'\n    },\n    space: (theme, { negative }) => ({\n      ...theme('spacing'),\n      ...negative(theme('spacing'))\n    }),\n    stroke: {\n      current: 'currentColor'\n    },\n    strokeWidth: {\n      0: '0',\n      1: '1',\n      2: '2'\n    },\n    textColor: (theme) => theme('colors'),\n    textOpacity: (theme) => theme('opacity'),\n    transitionDuration: {\n      DEFAULT: '150ms',\n      75: '75ms',\n      100: '100ms',\n      150: '150ms',\n      200: '200ms',\n      300: '300ms',\n      500: '500ms',\n      700: '700ms',\n      1000: '1000ms'\n    },\n    transitionDelay: {\n      75: '75ms',\n      100: '100ms',\n      150: '150ms',\n      200: '200ms',\n      300: '300ms',\n      500: '500ms',\n      700: '700ms',\n      1000: '1000ms'\n    },\n    transitionProperty: {\n      none: 'none',\n      all: 'all',\n      DEFAULT: 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform',\n      colors: 'background-color, border-color, color, fill, stroke',\n      opacity: 'opacity',\n      shadow: 'box-shadow',\n      transform: 'transform'\n    },\n    transitionTimingFunction: {\n      DEFAULT: 'cubic-bezier(0.4, 0, 0.2, 1)',\n      linear: 'linear',\n      in: 'cubic-bezier(0.4, 0, 1, 1)',\n      out: 'cubic-bezier(0, 0, 0.2, 1)',\n      'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)'\n    },\n    translate: (theme, { negative }) => ({\n      ...theme('spacing'),\n      ...negative(theme('spacing')),\n      '1/2': '50%',\n      '1/3': '33.333333%',\n      '2/3': '66.666667%',\n      '1/4': '25%',\n      '2/4': '50%',\n      '3/4': '75%',\n      full: '100%',\n      '-1/2': '-50%',\n      '-1/3': '-33.333333%',\n      '-2/3': '-66.666667%',\n      '-1/4': '-25%',\n      '-2/4': '-50%',\n      '-3/4': '-75%',\n      '-full': '-100%'\n    }),\n    width: (theme) => ({\n      auto: 'auto',\n      ...theme('spacing'),\n      '1/2': '50%',\n      '1/3': '33.333333%',\n      '2/3': '66.666667%',\n      '1/4': '25%',\n      '2/4': '50%',\n      '3/4': '75%',\n      '1/5': '20%',\n      '2/5': '40%',\n      '3/5': '60%',\n      '4/5': '80%',\n      '1/6': '16.666667%',\n      '2/6': '33.333333%',\n      '3/6': '50%',\n      '4/6': '66.666667%',\n      '5/6': '83.333333%',\n      '1/12': '8.333333%',\n      '2/12': '16.666667%',\n      '3/12': '25%',\n      '4/12': '33.333333%',\n      '5/12': '41.666667%',\n      '6/12': '50%',\n      '7/12': '58.333333%',\n      '8/12': '66.666667%',\n      '9/12': '75%',\n      '10/12': '83.333333%',\n      '11/12': '91.666667%',\n      full: '100%',\n      screen: '100vw',\n      min: 'min-content',\n      max: 'max-content'\n    }),\n    zIndex: {\n      auto: 'auto',\n      0: '0',\n      10: '10',\n      20: '20',\n      30: '30',\n      40: '40',\n      50: '50'\n    }\n  },\n  variantOrder: [\n    'first',\n    'last',\n    'odd',\n    'even',\n    'visited',\n    'checked',\n    'group-hover',\n    'group-focus',\n    'focus-within',\n    'hover',\n    'focus',\n    'focus-visible',\n    'active',\n    'disabled'\n  ],\n  variants: {\n    accessibility: ['responsive', 'focus-within', 'focus'],\n    alignContent: ['responsive'],\n    alignItems: ['responsive'],\n    alignSelf: ['responsive'],\n    animation: ['responsive'],\n    appearance: ['responsive'],\n    backgroundAttachment: ['responsive'],\n    backgroundClip: ['responsive'],\n    backgroundColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],\n    backgroundImage: ['responsive'],\n    backgroundOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    backgroundPosition: ['responsive'],\n    backgroundRepeat: ['responsive'],\n    backgroundSize: ['responsive'],\n    borderCollapse: ['responsive'],\n    borderColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],\n    borderOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    borderRadius: ['responsive'],\n    borderStyle: ['responsive'],\n    borderWidth: ['responsive'],\n    boxShadow: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    boxSizing: ['responsive'],\n    clear: ['responsive'],\n    container: ['responsive'],\n    cursor: ['responsive'],\n    display: ['responsive'],\n    divideColor: ['responsive', 'dark'],\n    divideOpacity: ['responsive'],\n    divideStyle: ['responsive'],\n    divideWidth: ['responsive'],\n    fill: ['responsive'],\n    flex: ['responsive'],\n    flexDirection: ['responsive'],\n    flexGrow: ['responsive'],\n    flexShrink: ['responsive'],\n    flexWrap: ['responsive'],\n    float: ['responsive'],\n    fontFamily: ['responsive'],\n    fontSize: ['responsive'],\n    fontSmoothing: ['responsive'],\n    fontStyle: ['responsive'],\n    fontVariantNumeric: ['responsive'],\n    fontWeight: ['responsive'],\n    gap: ['responsive'],\n    gradientColorStops: ['responsive', 'dark', 'hover', 'focus'],\n    gridAutoColumns: ['responsive'],\n    gridAutoFlow: ['responsive'],\n    gridAutoRows: ['responsive'],\n    gridColumn: ['responsive'],\n    gridColumnEnd: ['responsive'],\n    gridColumnStart: ['responsive'],\n    gridRow: ['responsive'],\n    gridRowEnd: ['responsive'],\n    gridRowStart: ['responsive'],\n    gridTemplateColumns: ['responsive'],\n    gridTemplateRows: ['responsive'],\n    height: ['responsive'],\n    inset: ['responsive'],\n    justifyContent: ['responsive'],\n    justifyItems: ['responsive'],\n    justifySelf: ['responsive'],\n    letterSpacing: ['responsive'],\n    lineHeight: ['responsive'],\n    listStylePosition: ['responsive'],\n    listStyleType: ['responsive'],\n    margin: ['responsive'],\n    maxHeight: ['responsive'],\n    maxWidth: ['responsive'],\n    minHeight: ['responsive'],\n    minWidth: ['responsive'],\n    objectFit: ['responsive'],\n    objectPosition: ['responsive'],\n    opacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    order: ['responsive'],\n    outline: ['responsive', 'focus-within', 'focus'],\n    overflow: ['responsive'],\n    overscrollBehavior: ['responsive'],\n    padding: ['responsive'],\n    placeContent: ['responsive'],\n    placeItems: ['responsive'],\n    placeSelf: ['responsive'],\n    placeholderColor: ['responsive', 'dark', 'focus'],\n    placeholderOpacity: ['responsive', 'focus'],\n    pointerEvents: ['responsive'],\n    position: ['responsive'],\n    resize: ['responsive'],\n    ringColor: ['responsive', 'dark', 'focus-within', 'focus'],\n    ringOffsetColor: ['responsive', 'dark', 'focus-within', 'focus'],\n    ringOffsetWidth: ['responsive', 'focus-within', 'focus'],\n    ringOpacity: ['responsive', 'focus-within', 'focus'],\n    ringWidth: ['responsive', 'focus-within', 'focus'],\n    rotate: ['responsive', 'hover', 'focus'],\n    scale: ['responsive', 'hover', 'focus'],\n    skew: ['responsive', 'hover', 'focus'],\n    space: ['responsive'],\n    stroke: ['responsive'],\n    strokeWidth: ['responsive'],\n    tableLayout: ['responsive'],\n    textAlign: ['responsive'],\n    textColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],\n    textDecoration: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    textOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    textOverflow: ['responsive'],\n    textTransform: ['responsive'],\n    transform: ['responsive'],\n    transformOrigin: ['responsive'],\n    transitionDelay: ['responsive'],\n    transitionDuration: ['responsive'],\n    transitionProperty: ['responsive'],\n    transitionTimingFunction: ['responsive'],\n    translate: ['responsive', 'hover', 'focus'],\n    userSelect: ['responsive'],\n    verticalAlign: ['responsive'],\n    visibility: ['responsive'],\n    whitespace: ['responsive'],\n    width: ['responsive'],\n    wordBreak: ['responsive'],\n    zIndex: ['responsive', 'focus-within', 'focus']\n  },\n  plugins: []\n}\n"
  },
  {
    "path": "dashboard/tests/e2e/.eslintrc.js",
    "content": "module.exports = {\n  plugins: [\n    'cypress'\n  ],\n  env: {\n    mocha: true,\n    'cypress/globals': true\n  },\n  rules: {\n    strict: 'off'\n  }\n}\n"
  },
  {
    "path": "dashboard/tests/e2e/plugins/index.js",
    "content": "/* eslint-disable arrow-body-style */\n// https://docs.cypress.io/guides/guides/plugins-guide.html\n\n// if you need a custom webpack configuration you can uncomment the following import\n// and then use the `file:preprocessor` event\n// as explained in the cypress docs\n// https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples\n\n// /* eslint-disable import/no-extraneous-dependencies, global-require */\n// const webpack = require('@cypress/webpack-preprocessor')\n\nmodule.exports = (on, config) => {\n  // on('file:preprocessor', webpack({\n  //  webpackOptions: require('@vue/cli-service/webpack.config'),\n  //  watchOptions: {}\n  // }))\n\n  return Object.assign({}, config, {\n    fixturesFolder: 'tests/e2e/fixtures',\n    integrationFolder: 'tests/e2e/specs',\n    screenshotsFolder: 'tests/e2e/screenshots',\n    videosFolder: 'tests/e2e/videos',\n    supportFile: 'tests/e2e/support/index.js'\n  })\n}\n"
  },
  {
    "path": "dashboard/tests/e2e/specs/credencials.js",
    "content": "const APP_URL = process.env.APP_URL || 'http://localhost:8080'\n\ndescribe('Credencials', () => {\n  it('should generate an api_key', () => {\n    cy.visit(APP_URL)\n\n    cy.get('#header-login-button').click()\n    cy.get('#modal-login')\n\n    cy.get('#email-field').type('igor@igor.me')\n    cy.get('#password-field').type('1234')\n    cy.get('#submit-button').click()\n\n    cy.wait(4000)\n    cy.visit(`${APP_URL}/credencials`)\n    cy.wait(2000)\n\n    const oldApikey = cy.get('#apikey').invoke('text')\n    cy.get('#generate-apikey').click()\n    cy.wait(2000)\n    const newApikey = cy.get('#apikey').invoke('text')\n\n    expect(oldApikey).to.not.equal(newApikey)\n  })\n})\n"
  },
  {
    "path": "dashboard/tests/e2e/specs/home.js",
    "content": "const APP_URL = process.env.APP_URL || 'http://localhost:8080'\n\ndescribe('Home', () => {\n  it('should render create account modal when click on cta create account button', () => {\n    cy.visit(APP_URL)\n\n    cy.get('#cta-create-account-button').click()\n\n    cy.get('#modal-create-account')\n  })\n\n  it('should render create account modal when click on header create account button', () => {\n    cy.visit(APP_URL)\n\n    cy.get('#header-create-account-button').click()\n\n    cy.get('#modal-create-account')\n  })\n\n  it('should render login modal when click on header login button', () => {\n    cy.visit(APP_URL)\n\n    cy.get('#header-login-button').click()\n\n    cy.get('#modal-login')\n  })\n\n  it('should login with an email and password', () => {\n    cy.visit(APP_URL)\n\n    cy.get('#header-login-button').click()\n    cy.get('#modal-login')\n\n    cy.get('#email-field').type('igor@igor.me')\n    cy.get('#password-field').type('1234')\n    cy.get('#submit-button').click()\n\n    cy.url().should('include', '/feedbacks')\n  })\n\n  it('should show an error with an invalid email', () => {\n    cy.visit(APP_URL)\n\n    cy.get('#header-login-button').click()\n    cy.get('#modal-login')\n\n    cy.get('#email-field').type('igor@')\n    cy.get('#password-field').type('1234')\n    cy.get('#submit-button').click()\n\n    cy.get('#email-error')\n  })\n\n  it('should logout work correctly', () => {\n    cy.visit(APP_URL)\n\n    cy.get('#header-login-button').click()\n    cy.get('#modal-login')\n\n    cy.get('#email-field').type('igor@igor.me')\n    cy.get('#password-field').type('1234')\n    cy.get('#submit-button').click()\n\n    cy.url().should('include', '/feedbacks')\n    cy.get('#logout-button').click()\n    cy.url().should('include', '/')\n  })\n})\n"
  },
  {
    "path": "dashboard/tests/e2e/support/commands.js",
    "content": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n//\n//\n// -- This is a parent command --\n// Cypress.Commands.add(\"login\", (email, password) => { ... })\n//\n//\n// -- This is a child command --\n// Cypress.Commands.add(\"drag\", { prevSubject: 'element'}, (subject, options) => { ... })\n//\n//\n// -- This is a dual command --\n// Cypress.Commands.add(\"dismiss\", { prevSubject: 'optional'}, (subject, options) => { ... })\n//\n//\n// -- This is will overwrite an existing command --\n// Cypress.Commands.overwrite(\"visit\", (originalFn, url, options) => { ... })\n"
  },
  {
    "path": "dashboard/tests/e2e/support/index.js",
    "content": "// ***********************************************************\n// This example support/index.js is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\nimport './commands'\n\n// Alternatively you can use CommonJS syntax:\n// require('./commands')\n"
  },
  {
    "path": "try-widget/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Minha loja de biscoitos</title>\n\n  <style>\n\n    * {\n      box-sizing: border-box;\n      margin: 0;\n      padding: 0;\n    }\n    html,\n    body {\n      width: 100%;\n      height: 100%;\n      overflow-x: hidden;\n    }\n\n    .container {\n      width: 100%;\n      height: 70vh;\n      background-color: #f4f4f4;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n    }\n    .container > h1 {\n      font-family: Roboto;\n      color: #444;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <h1>Meu site de venda de biscoitos</h1>\n  </div>\n\n  <script\n    defer\n    async\n    onload=\"init('fcd5015c-10d3-4e9c-b395-ec7ed8850165')\"\n    src=\"https://igorhalfeld-feedbacker-widget.netlify.app/init.js\"\n  >\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "widget/.browserslistrc",
    "content": "> 1%\nlast 2 versions\nnot dead\n"
  },
  {
    "path": "widget/.editorconfig",
    "content": "[*.{js,jsx,ts,tsx,vue}]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": "widget/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  env: {\n    node: true\n  },\n  extends: [\n    'plugin:vue/vue3-essential',\n    '@vue/standard',\n    '@vue/typescript/recommended'\n  ],\n  parserOptions: {\n    ecmaVersion: 2020\n  },\n  rules: {\n    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',\n    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'\n  },\n  overrides: [\n    {\n      files: [\n        '**/*.{j,t}s?(x)',\n      ],\n      env: {\n        jest: true\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "widget/.gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n\n/tests/e2e/videos/\n/tests/e2e/screenshots/\n\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "widget/Dockerfile",
    "content": "FROM node:13-alpine as build\n\nWORKDIR /\n\nCOPY . .\n\nENV NODE_ENV=production\nRUN npm install --production\nRUN npm run build\n\nFROM nginx:1.18.0-alpine as final\n\nWORKDIR /\nCOPY --from=build ./dist /usr/share/nginx/html\n"
  },
  {
    "path": "widget/README.md",
    "content": "# widget\n\n## Project setup\n```\nnpm install\n```\n\n### Compiles and hot-reloads for development\n```\nnpm run serve\n```\n\n### Compiles and minifies for production\n```\nnpm run build\n```\n\n### Run your unit tests\n```\nnpm run test:unit\n```\n\n### Run your end-to-end tests\n```\nnpm run test:e2e\n```\n\n### Lints and fixes files\n```\nnpm run lint\n```\n\n### Customize configuration\nSee [Configuration Reference](https://cli.vuejs.org/config/).\n"
  },
  {
    "path": "widget/babel.config.js",
    "content": "module.exports = {\n  presets: [\n    '@vue/app'\n  ]\n}\n"
  },
  {
    "path": "widget/cypress.json",
    "content": "{\n  \"pluginsFile\": \"tests/e2e/plugins/index.js\"\n}\n"
  },
  {
    "path": "widget/jest.config.js",
    "content": "module.exports = {\n  preset: '@vue/cli-plugin-unit-jest/presets/typescript',\n  testMatch: [\n    '**/*.spec.js'\n  ],\n  transform: {\n    '^.+\\\\.vue$': 'vue-jest',\n    '^.+\\\\.(ts|tsx)$': 'ts-jest'\n  }\n}\n"
  },
  {
    "path": "widget/package.json",
    "content": "{\n  \"name\": \"widget\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"serve\": \"vue-cli-service serve\",\n    \"build\": \"vue-cli-service build\",\n    \"test:unit\": \"vue-cli-service test:unit\",\n    \"test:e2e\": \"vue-cli-service test:e2e\",\n    \"lint\": \"vue-cli-service lint\"\n  },\n  \"dependencies\": {\n    \"@tailwindcss/postcss7-compat\": \"^2.0.3\",\n    \"animate.css\": \"^4.1.1\",\n    \"autoprefixer\": \"^9.8.6\",\n    \"axios\": \"^0.21.1\",\n    \"postcss\": \"^7.0.35\",\n    \"tailwindcss\": \"npm:@tailwindcss/postcss7-compat@^2.0.3\",\n    \"vue\": \"^3.0.0\"\n  },\n  \"devDependencies\": {\n    \"@types/jest\": \"^24.9.1\",\n    \"@typescript-eslint/eslint-plugin\": \"^2.33.0\",\n    \"@typescript-eslint/parser\": \"^2.33.0\",\n    \"@vue/babel-preset-app\": \"^4.5.11\",\n    \"@vue/cli-plugin-e2e-cypress\": \"~4.5.0\",\n    \"@vue/cli-plugin-eslint\": \"~4.5.0\",\n    \"@vue/cli-plugin-typescript\": \"~4.5.0\",\n    \"@vue/cli-plugin-unit-jest\": \"~4.5.0\",\n    \"@vue/cli-service\": \"~4.5.0\",\n    \"@vue/compiler-sfc\": \"^3.0.0\",\n    \"@vue/eslint-config-standard\": \"^5.1.2\",\n    \"@vue/eslint-config-typescript\": \"^5.0.2\",\n    \"@vue/test-utils\": \"^2.0.0-0\",\n    \"babel-jest\": \"^26.6.3\",\n    \"eslint\": \"^6.7.2\",\n    \"eslint-plugin-import\": \"^2.20.2\",\n    \"eslint-plugin-node\": \"^11.1.0\",\n    \"eslint-plugin-promise\": \"^4.2.1\",\n    \"eslint-plugin-standard\": \"^4.0.0\",\n    \"eslint-plugin-vue\": \"^7.0.0-0\",\n    \"jest\": \"^26.6.3\",\n    \"lint-staged\": \"^9.5.0\",\n    \"ts-jest\": \"^26.5.0\",\n    \"typescript\": \"^3.9.7\",\n    \"vue-jest\": \"^5.0.0-0\"\n  },\n  \"gitHooks\": {\n    \"pre-commit\": \"lint-staged\"\n  },\n  \"lint-staged\": {\n    \"*.{js,jsx,vue,ts,tsx}\": [\n      \"vue-cli-service lint\",\n      \"git add\"\n    ]\n  }\n}\n"
  },
  {
    "path": "widget/palette.js",
    "content": "module.exports = {\n  brand: {\n    main: '#EF4983',\n    gray: '#F9F9F9',\n    info: '#8296FB',\n    success: '#63C3BE',\n    graydark: '#C0BCB0',\n    warning: '#E4B52E',\n    danger: '#F88676'\n  },\n  mediumslateblue: {\n    50: '#f6f9fd',\n    100: '#e8f3fd',\n    200: '#cbdefb',\n    300: '#abc4fb',\n    400: '#8296fb',\n    500: '#5667fb',\n    600: '#3d46f7',\n    700: '#3137e5',\n    800: '#272cb9',\n    900: '#202591'\n  },\n  slateblue: {\n    50: '#f5f8fc',\n    100: '#eaf0fc',\n    200: '#d3d8fa',\n    300: '#babcf9',\n    400: '#9c8ff9',\n    500: '#7a61f9',\n    600: '#5d41f4',\n    700: '#4933e1',\n    800: '#382ab5',\n    900: '#2d248f'\n  },\n  mediumorchid: {\n    50: '#f9f7fa',\n    100: '#f7edf9',\n    200: '#efd1f5',\n    300: '#e8aef1',\n    400: '#e47cec',\n    500: '#e04fe7',\n    600: '#c932d7',\n    700: '#9a27b5',\n    800: '#712186',\n    900: '#571c66'\n  },\n  deeppink: {\n    50: '#fcf9f9',\n    100: '#fcf0f3',\n    200: '#fad3e6',\n    300: '#f9acd1',\n    400: '#fa73ab',\n    500: '#fb4783',\n    600: '#f42a5c',\n    700: '#d6214a',\n    800: '#a71b3a',\n    900: '#83172f'\n  },\n  tomato: {\n    50: '#fcf9f6',\n    100: '#fcf0ed',\n    200: '#fadad6',\n    300: '#f8b9b0',\n    400: '#f88676',\n    500: '#f85b47',\n    600: '#ee392e',\n    700: '#cc2b2b',\n    800: '#9e2328',\n    900: '#7c1d23'\n  },\n  chocolate: {\n    50: '#fbf8f2',\n    100: '#fbf1e1',\n    200: '#f8e1bb',\n    300: '#f5c782',\n    400: '#f39d41',\n    500: '#f1741e',\n    600: '#e24f13',\n    700: '#bc3b16',\n    800: '#922e1a',\n    900: '#732619'\n  },\n  goldenrod: {\n    50: '#fbfaf4',\n    100: '#faf6e0',\n    200: '#f5ebb0',\n    300: '#efd86f',\n    400: '#e4b52e',\n    500: '#d69111',\n    600: '#b76b0a',\n    700: '#8b500e',\n    800: '#663d12',\n    900: '#4e3012'\n  },\n  darkgoldenrod: {\n    50: '#fbfaf6',\n    100: '#f9f8e7',\n    200: '#f2efbc',\n    300: '#e8de7f',\n    400: '#d3bd3a',\n    500: '#b79b17',\n    600: '#8e750d',\n    700: '#685910',\n    800: '#4b4213',\n    900: '#393414'\n  },\n  lightseagreen: {\n    50: '#f5fafa',\n    100: '#eaf7f5',\n    200: '#ceeee8',\n    300: '#a7dfd9',\n    400: '#63c3be',\n    500: '#30a29c',\n    600: '#237f79',\n    700: '#236460',\n    800: '#204c4a',\n    900: '#1b3d3c'\n  },\n  cornflowerblue: {\n    50: '#f4fafc',\n    100: '#e2f7fb',\n    200: '#bdeaf7',\n    300: '#8fd8f5',\n    400: '#4fb6f1',\n    500: '#238fed',\n    600: '#1a6bdf',\n    700: '#1b54bd',\n    800: '#19418c',\n    900: '#15346b'\n  }\n}\n"
  },
  {
    "path": "widget/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {}\n  }\n}\n"
  },
  {
    "path": "widget/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.ico\">\n    <title><%= htmlWebpackPlugin.options.title %></title>\n  </head>\n  <body>\n    <noscript>\n      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n    </noscript>\n    <div id=\"app\"></div>\n    <!-- built files will be auto injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "widget/public/init.js",
    "content": "function init (apiKey) {\n  async function handleLoadWidget () {\n    const page = `${window.location.origin}${window.location.pathname}`\n    const fp = await window.FingerprintJS.load()\n    const fingerprint = await fp.get()\n\n    const WIDGET_URL = `https://igorhalfeld-feedbacker-widget.netlify.app?api_key=${apiKey}&page=${page}&fingerprint=${fingerprint.visitorId}`\n    const config = { method: 'HEAD' }\n    const res = await fetch(`https://backend-treinamento-vue3.vercel.app/apikey/exists?apikey=${apiKey}`, config)\n\n    if (res.status === 200) {\n      const iframe = document.createElement('iframe')\n      iframe.src = WIDGET_URL\n      iframe.id = 'feedbacker-iframe'\n      iframe.style.position = 'fixed'\n      iframe.style.bottom = '0px'\n      iframe.style.right = '0px'\n      iframe.style.overflow = 'hidden'\n      iframe.style.border = '0px'\n      iframe.style.zIndex = '99999'\n      document.body.appendChild(iframe)\n\n      window.addEventListener('message', (event) => {\n        if (!event.data.isWidget) return\n\n        if (event.data.isOpen) {\n          iframe.width = '100%'\n          iframe.height = '100%'\n        } else {\n          iframe.width = '300px'\n          iframe.height = '150px'\n        }\n      })\n      return\n    }\n\n    console.log('* [feedbacker] was not loaded because apikey does not exists')\n  }\n\n  const script = document.createElement('script')\n  script.src = '//cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@3/dist/fp.min.js'\n  script.async = 'async'\n  script.addEventListener('load', handleLoadWidget)\n\n  document.body.appendChild(script)\n}\n\nwindow.init = init\n"
  },
  {
    "path": "widget/src/App.vue",
    "content": "<template>\n  <widget />\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport Widget from './views/Widget/index.vue'\n\nexport default defineComponent({\n  components: { Widget }\n})\n</script>\n"
  },
  {
    "path": "widget/src/assets/css/fonts.css",
    "content": "@font-face {\n  font-family: \"RobotoRegular\";\n  src: local(\"Roboto Regular\"), local(\"Roboto-Regular\"),\n    url(\"../fonts/Roboto-Regular.ttf\") format(\"truetype\");\n  font-weight: 400;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: \"RobotoMedium\";\n  src: local(\"Roboto Medium\"), local(\"Roboto-Medium\"),\n    url(\"../fonts/Roboto-Medium.ttf\") format(\"truetype\");\n  font-weight: 500;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: \"RobotoBold\";\n  src: local(\"Roboto Bold\"), local(\"Roboto-Bold\"),\n    url(\"../fonts/Roboto-Bold.ttf\") format(\"truetype\");\n  font-weight: 700;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: \"RobotoBlack\";\n  src: local(\"Roboto Black\"), local(\"Roboto-Black\"),\n    url(\"../fonts/Roboto-Black.ttf\") format(\"truetype\");\n  font-weight: 900;\n  font-style: normal;\n}\n"
  },
  {
    "path": "widget/src/assets/css/tailwind.css",
    "content": "/*! @import */\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml,\nbody,\n#app {\n  width: 100%;\n  height: 100%;\n}\n"
  },
  {
    "path": "widget/src/components/Icon/ArrowRight.vue",
    "content": "<template>\n  <svg\n    :width=\"size\"\n    :height=\"size\" viewBox=\"0 0 13 5\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M3.07415 3.21819L12.3158 3.23798L12.3183 2.06329L3.07666 2.04351L3.08044 0.281483L0.00512019 2.62427L3.07038 4.98021L3.07415 3.21819Z\"\n      :fill=\"color\"/>\n  </svg>\n</template>\n\n<script>\nexport default {\n  props: {\n    size: { type: [String, Number], default: 22 },\n    color: { type: String, default: 'white' }\n  }\n}\n</script>\n"
  },
  {
    "path": "widget/src/components/Icon/Atention.vue",
    "content": "<template>\n  <svg\n    :width=\"size\"\n    :height=\"size\" viewBox=\"0 0 52 52\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M25.9999 4.3335C14.0399 4.3335 4.33325 14.0402 4.33325 26.0002C4.33325 37.9602 14.0399 47.6668 25.9999 47.6668C37.9599 47.6668 47.6666 37.9602 47.6666 26.0002C47.6666 14.0402 37.9599 4.3335 25.9999 4.3335ZM28.1666 36.8335H23.8333V32.5002H28.1666V36.8335ZM28.1666 28.1668H23.8333V15.1668H28.1666V28.1668Z\"\n      :fill=\"color\"/>\n  </svg>\n</template>\n\n<script>\nexport default {\n  props: {\n    size: { type: [String, Number], default: 22 },\n    color: { type: String, default: 'white' }\n  }\n}\n</script>\n"
  },
  {
    "path": "widget/src/components/Icon/Chat.vue",
    "content": "<template>\n  <svg\n    :width=\"size\"\n    :height=\"size\" viewBox=\"0 0 30 28\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M24.9982 10.6297C24.9982 4.45448 19.0609 0 12.4988 0C5.89808 0 0 4.48874 0 10.6297C0.00581151 12.8132 0.759476 14.9264 2.13112 16.6049C2.18958 18.548 1.06887 21.3402 0.0551531 23.3652C2.76483 22.8677 6.61838 21.7642 8.36453 20.6736C17.9871 23.062 24.9982 17.0564 24.9982 10.6297ZM6.87429 12.3869C6.56533 12.3865 6.2634 12.2929 6.00667 12.1179C5.74993 11.9429 5.54992 11.6943 5.43192 11.4036C5.31391 11.1128 5.2832 10.793 5.34368 10.4845C5.40415 10.176 5.5531 9.89264 5.77168 9.67031C5.99026 9.44798 6.26868 9.29662 6.57172 9.23537C6.87477 9.17411 7.18885 9.20572 7.47426 9.32619C7.75967 9.44665 8.0036 9.65057 8.17522 9.91217C8.34683 10.1738 8.43843 10.4813 8.43843 10.7959C8.43799 11.218 8.27301 11.6227 7.97972 11.921C7.68643 12.2193 7.28884 12.3869 6.87429 12.3869ZM12.4999 12.3869C12.1909 12.3865 11.889 12.2929 11.6323 12.1179C11.3756 11.9429 11.1755 11.6943 11.0575 11.4036C10.9395 11.1128 10.9088 10.793 10.9693 10.4845C11.0298 10.176 11.1787 9.89264 11.3973 9.67031C11.6159 9.44798 11.8943 9.29662 12.1973 9.23537C12.5004 9.17411 12.8145 9.20572 13.0999 9.32619C13.3853 9.44665 13.6292 9.65057 13.8008 9.91217C13.9725 10.1738 14.0641 10.4813 14.0641 10.7959C14.0639 11.005 14.0233 11.2121 13.9446 11.4052C13.8658 11.5983 13.7505 11.7738 13.6051 11.9215C13.4598 12.0693 13.2872 12.1864 13.0974 12.2663C12.9076 12.3461 12.7042 12.3871 12.4988 12.3869H12.4999ZM18.1255 12.3869C17.8165 12.3869 17.5144 12.2935 17.2575 12.1187C17.0005 11.9439 16.8002 11.6954 16.682 11.4047C16.5637 11.114 16.5328 10.7941 16.5931 10.4855C16.6534 10.1769 16.8022 9.8934 17.0207 9.6709C17.2392 9.4484 17.5176 9.29688 17.8207 9.23549C18.1238 9.1741 18.438 9.20561 18.7235 9.32602C19.009 9.44644 19.253 9.65036 19.4247 9.91199C19.5964 10.1736 19.688 10.4812 19.688 10.7959C19.6879 11.005 19.6473 11.212 19.5686 11.4051C19.4899 11.5982 19.3746 11.7736 19.2293 11.9213C19.084 12.0691 18.9115 12.1862 18.7218 12.2661C18.532 12.346 18.3287 12.387 18.1233 12.3869H18.1255ZM26.9202 14.221C26.7193 14.8418 26.4681 15.4446 26.1691 16.0231C28.2649 17.6292 28.9896 20.1288 26.7487 22.8508C26.7228 23.7994 26.6875 24.1857 26.9214 25.2247C25.8062 24.8315 25.6975 24.7254 24.8404 24.1897C22.1815 24.8501 19.5093 25.0758 17.2293 23.1019C16.5152 23.3048 15.7887 23.4596 15.0546 23.5652C16.9458 25.9536 20.4271 27.252 24.5249 26.2378C25.6677 26.9515 28.191 27.672 29.9647 28C29.3029 26.6702 28.5688 24.845 28.6046 23.5725C30.7964 20.9027 30.5311 16.6543 26.918 14.221H26.9202Z\"\n      :fill=\"color\"/>\n  </svg>\n</template>\n\n<script>\nexport default {\n  props: {\n    size: { type: [String, Number], default: 22 },\n    color: { type: String, default: 'white' }\n  }\n}\n</script>\n"
  },
  {
    "path": "widget/src/components/Icon/Check.vue",
    "content": "<template>\n  <svg\n    :width=\"size\"\n    :height=\"size\" viewBox=\"0 0 43 43\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M21.5 0C9.632 0 0 9.632 0 21.5C0 33.368 9.632 43 21.5 43C33.368 43 43 33.368 43 21.5C43 9.632 33.368 0 21.5 0ZM17.2 32.25L6.45 21.5L9.4815 18.4685L17.2 26.1655L33.5185 9.847L36.55 12.9L17.2 32.25Z\"\n      :fill=\"color\"/>\n  </svg>\n</template>\n\n<script>\nexport default {\n  props: {\n    size: { type: [String, Number], default: 22 },\n    color: { type: String, default: 'white' }\n  }\n}\n</script>\n"
  },
  {
    "path": "widget/src/components/Icon/ChevronDown.vue",
    "content": "<template>\n  <svg :width=\"size\" :height=\"size\" viewBox=\"0 0 17 10\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M1.9975 0L8.5 6.18084L15.0025 0L17 1.90283L8.5 10L0 1.90283L1.9975 0Z\"\n      :fill=\"color\"/>\n  </svg>\n</template>\n\n<script>\nexport default {\n  props: {\n    size: { type: [String, Number], default: 22 },\n    color: { type: String, default: 'white' }\n  }\n}\n</script>\n"
  },
  {
    "path": "widget/src/components/Icon/Close.vue",
    "content": "<template>\n  <svg\n    :width=\"size\"\n    :height=\"size\" viewBox=\"0 0 8 8\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M8 0.805714L7.19429 0L4 3.19429L0.805714 0L0 0.805714L3.19429 4L0 7.19429L0.805714 8L4 4.80571L7.19429 8L8 7.19429L4.80571 4L8 0.805714Z\"\n      :fill=\"color\"/>\n  </svg>\n</template>\n\n<script>\nexport default {\n  props: {\n    size: { type: [String, Number], default: 22 },\n    color: { type: String, default: 'white' }\n  }\n}\n</script>\n"
  },
  {
    "path": "widget/src/components/Icon/Copy.vue",
    "content": "<template>\n  <svg\n    :width=\"size\"\n    :height=\"size\" viewBox=\"0 0 19 22\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M14 0H2C0.9 0 0 0.9 0 2V16H2V2H14V0ZM13 4L19 10V20C19 21.1 18.1 22 17 22H5.99C4.89 22 4 21.1 4 20L4.01 6C4.01 4.9 4.9 4 6 4H13ZM12 11H17.5L12 5.5V11Z\"\n      :fill=\"color\"/>\n  </svg>\n</template>\n\n<script>\nexport default {\n  props: {\n    size: { type: [String, Number], default: 22 },\n    color: { type: String, default: 'white' }\n  }\n}\n</script>\n"
  },
  {
    "path": "widget/src/components/Icon/Loading.vue",
    "content": "<template>\n  <svg\n    :width=\"size\"\n    :height=\"size\" viewBox=\"0 0 22 30\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M11 6.81818V10.9091L16.5 5.45455L11 0V4.09091C4.9225 4.09091 0 8.97273 0 15C0 17.1409 0.6325 19.1318 1.705 20.8091L3.7125 18.8182C3.09375 17.6864 2.75 16.3773 2.75 15C2.75 10.4864 6.44875 6.81818 11 6.81818ZM20.295 9.19091L18.2875 11.1818C18.8925 12.3273 19.25 13.6227 19.25 15C19.25 19.5136 15.5512 23.1818 11 23.1818V19.0909L5.5 24.5455L11 30V25.9091C17.0775 25.9091 22 21.0273 22 15C22 12.8591 21.3675 10.8682 20.295 9.19091Z\"\n      :fill=\"color\"/>\n  </svg>\n</template>\n\n<script>\nexport default {\n  props: {\n    size: { type: [String, Number], default: 22 },\n    color: { type: String, default: 'white' }\n  }\n}\n</script>\n"
  },
  {
    "path": "widget/src/components/Icon/index.vue",
    "content": "<template>\n  <component :is=\"name\" v-bind=\"$props\"/>\n</template>\n\n<script>\nimport Loading from './Loading.vue'\nimport Copy from './Copy.vue'\nimport ChevronDown from './ChevronDown.vue'\nimport Chat from './Chat.vue'\nimport Close from './Close.vue'\nimport ArrowRight from './ArrowRight.vue'\nimport Check from './Check.vue'\nimport Atention from './Atention.vue'\n\nexport default {\n  components: {\n    Loading,\n    Copy,\n    ChevronDown,\n    Chat,\n    Close,\n    ArrowRight,\n    Check,\n    Atention\n  },\n  props: {\n    name: { type: String, required: true }\n  }\n}\n</script>\n"
  },
  {
    "path": "widget/src/components/Wizard/Error.vue",
    "content": "<template>\n  <div class=\"flex flex-col items-center justify-between w-full my-5\">\n    <icon\n      name=\"atention\"\n      :color=\"palette.danger\"\n      size=\"70\" />\n\n    <p class=\"text-xl font-black text-center w-full mt-2\">\n      Droga! Aconteceu algum erro.\n    </p>\n\n    <div class=\"flex justify-center items-center w-full mt-2\">\n      <button\n        @click=\"goBack\"\n        class=\"\n          rounded-full font-regular text-sm flex flex-col justify-center bg-brand-gray\n          items-center py-2 px-5 cursor-pointer focus:outline-none\n        \"\n      >\n        Tente novamente\n      </button>\n    </div>\n\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport { resetStore } from '../../store'\nimport Icon from '../Icon/index.vue'\nimport palette from '../../../palette.js'\n\ninterface SetupReturn {\n  goBack(): void;\n  palette: Record<string, string>;\n}\n\nexport default defineComponent({\n  components: { Icon },\n  setup (): SetupReturn {\n    function goBack (): void {\n      resetStore()\n    }\n\n    return {\n      goBack,\n      palette: palette.brand\n    }\n  }\n})\n</script>\n"
  },
  {
    "path": "widget/src/components/Wizard/SelectFeedbackType.vue",
    "content": "<template>\n  <div class=\"flex justify-between w-full my-5\">\n    <button\n      @click=\"() => handleSelect('ISSUE')\"\n      class=\"\n        rounded-xl hover:bg-gray-100 bg-brand-gray flex flex-col\n        justify-center items-center p-5 w-28 cursor-pointer focus:outline-none\n      \">\n      <div class=\"w-12\">\n        <img class=\"w-full\" src=\"../../assets/images/issue.png\" alt=\"problema\">\n      </div>\n      <p class=\"font-medium mt-1 text-gray-800\">Problema</p>\n    </button>\n\n    <button\n      @click=\"() => handleSelect('IDEA')\"\n      class=\"\n        rounded-xl hover:bg-gray-100 bg-brand-gray flex flex-col\n        justify-center items-center p-5 w-28 cursor-pointer focus:outline-none\n      \">\n      <div class=\"w-12\">\n        <img class=\"w-full\" src=\"../../assets/images/lamp.png\" alt=\"problema\">\n      </div>\n      <p class=\"font-medium mt-1 text-gray-800\">Ideia</p>\n    </button>\n\n    <button\n      @click=\"() => handleSelect('OTHER')\"\n      class=\"\n        rounded-xl hover:bg-gray-100 bg-brand-gray flex flex-col\n        justify-center items-center p-5 w-28 cursor-pointer focus:outline-none\n      \">\n      <div class=\"w-12\">\n        <img class=\"w-full\" src=\"../../assets/images/fire.png\" alt=\"problema\">\n      </div>\n      <p class=\"font-medium mt-1 text-gray-800\">Outro</p>\n    </button>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, SetupContext } from 'vue'\n\ninterface SetupReturn {\n  handleSelect(type: string): void;\n}\n\nexport default defineComponent({\n  setup (_, { emit }: SetupContext): SetupReturn {\n    function handleSelect (type: string): void {\n      emit('select-feedback-type', type)\n      emit('next')\n    }\n\n    return {\n      handleSelect\n    }\n  }\n})\n</script>\n"
  },
  {
    "path": "widget/src/components/Wizard/Success.vue",
    "content": "<template>\n  <div class=\"flex flex-col items-center justify-between w-full my-5\">\n    <icon\n      name=\"check\"\n      :color=\"palette.success\"\n      size=\"70\" />\n\n    <p class=\"text-xl font-black text-center w-full mt-2\">\n      Obrigado! Recebemos o seu feedback.\n    </p>\n\n    <div class=\"flex justify-center items-center w-full mt-2\">\n      <button\n        @click=\"goBack\"\n        class=\"\n          rounded-full font-regular text-sm flex flex-col justify-center bg-brand-gray\n          items-center py-2 px-5 cursor-pointer focus:outline-none\n        \"\n      >\n        Envie mais feedbacks\n      </button>\n    </div>\n\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport { resetStore } from '../../store'\nimport Icon from '../Icon/index.vue'\nimport palette from '../../../palette.js'\n\ninterface SetupReturn {\n  goBack(): void;\n  palette: Record<string, string>;\n}\n\nexport default defineComponent({\n  components: { Icon },\n  setup (): SetupReturn {\n    function goBack (): void {\n      resetStore()\n    }\n\n    return {\n      goBack,\n      palette: palette.brand\n    }\n  }\n})\n</script>\n"
  },
  {
    "path": "widget/src/components/Wizard/WriteAFeedback.vue",
    "content": "<template>\n  <div class=\"felx flex-col items-center justify-center w-full my-5\">\n    <textarea\n      v-model=\"state.feedback\"\n      class=\"w-full rounded-lg border-2 border-gray-300 border-solid h-24 p-2 resize-none focus:outline-none\">\n    </textarea>\n    <button\n      :disabled=\"submitButtonIsDisabled\"\n      :class=\"{\n        'opacity-50': state.isLoading,\n        'opacity-50 bg-gray-100 text-gray-500': submitButtonIsDisabled,\n        'bg-brand-main text-white': !submitButtonIsDisabled\n      }\"\n      @click=\"submitAFeedback\"\n      class=\"\n        rounded-lg font-black mt-3 flex flex-col\n        justify-center items-center py-2 w-full cursor-pointer\n        focus:outline-none transition-all duration-200\n      \">\n      <icon v-if=\"state.isLoading\" name=\"loading\" class=\"animate-spin\" color=\"white\" />\n      <template v-else>\n        Enviar feedback\n      </template>\n    </button>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ComputedRef, computed, defineComponent, reactive } from 'vue'\nimport useNavigation from '../../hooks/navigation'\nimport { setMessage } from '../../store'\nimport Icon from '../Icon/index.vue'\nimport useStore from '../../hooks/store'\nimport services from '../../services'\n\ntype State = {\n  feedback: string;\n  isLoading: boolean;\n  hasError: Error | null;\n}\n\ninterface SetupReturn {\n  state: State;\n  submitAFeedback(): Promise<void>;\n  submitButtonIsDisabled: ComputedRef<boolean>;\n}\n\nexport default defineComponent({\n  components: { Icon },\n  setup (): SetupReturn {\n    const store = useStore()\n    const { setSuccessState, setErrorState } = useNavigation()\n    const state = reactive<State>({\n      feedback: '',\n      isLoading: false,\n      hasError: null\n    })\n\n    const submitButtonIsDisabled = computed<boolean>(() => {\n      return !state.feedback.length\n    })\n\n    function handleError (error: Error) {\n      state.hasError = error\n      state.isLoading = false\n      setErrorState()\n    }\n\n    async function submitAFeedback (): Promise<void> {\n      setMessage(state.feedback)\n      state.isLoading = true\n\n      try {\n        const response = await services.feedbacks.create({\n          type: store.feedbackType,\n          text: store.message,\n          page: store.currentPage,\n          apiKey: store.apiKey,\n          device: window.navigator.userAgent,\n          fingerprint: store.fingerprint\n        })\n\n        if (!response.errors) {\n          setSuccessState()\n        } else {\n          setErrorState()\n        }\n\n        state.isLoading = false\n      } catch (error) {\n        handleError(error)\n      }\n    }\n\n    return {\n      state,\n      submitAFeedback,\n      submitButtonIsDisabled\n    }\n  }\n})\n</script>\n"
  },
  {
    "path": "widget/src/components/Wizard/index.vue",
    "content": "<template>\n  <component\n    :is=\"store.currentComponent\"\n    @select-feedback-type=\"handleSelectFeedbackType\"\n    @next=\"next\"\n  />\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport SelectFeedbackType from './SelectFeedbackType.vue'\nimport WriteAFeedback from './WriteAFeedback.vue'\nimport Success from './Success.vue'\nimport ErrorState from './Error.vue'\nimport useStore from '../../hooks/store'\nimport useNavigation, { Navigation } from '../../hooks/navigation'\nimport { StoreState, setFeedbackType } from '../../store'\n\ninterface SetupReturn {\n  store: StoreState;\n  next: Navigation['next'];\n  handleSelectFeedbackType(type: string): void;\n}\n\nexport default defineComponent({\n  components: { SelectFeedbackType, WriteAFeedback, Success, Error: ErrorState },\n  setup (): SetupReturn {\n    const store = useStore()\n    const { next } = useNavigation()\n\n    function handleSelectFeedbackType (type: string): void {\n      setFeedbackType(type)\n    }\n\n    return {\n      store,\n      next,\n      handleSelectFeedbackType\n    }\n  }\n})\n</script>\n"
  },
  {
    "path": "widget/src/hooks/iframe.ts",
    "content": "import {\n  setApiKey,\n  setCurrentPage,\n  setFingerprint\n} from '../store'\n\ninterface IframeControl {\n  updateCoreValuesOnStore(): void;\n  notifyOpen(): void;\n  notifyClose(): void;\n}\n\nexport default function useIframeControl (): IframeControl {\n  function updateCoreValuesOnStore (): void {\n    if (process.env.NODE_ENV === 'production') {\n      const query = new URLSearchParams(window.location.search)\n      const apiKey = query.get('api_key') ?? ''\n      const page = query.get('page') ?? ''\n      const fingerprint = query.get('fingerprint') ?? ''\n\n      setCurrentPage(page)\n      setApiKey(apiKey)\n      setFingerprint(fingerprint)\n      return\n    }\n\n    setCurrentPage('https://playground-url.com')\n    setApiKey('fcd5015c-10d3-4e9c-b395-ec7ed8850165')\n    setFingerprint('123123123123123')\n  }\n\n  function notifyOpen (): void {\n    window.parent.postMessage({\n      isWidget: true,\n      isOpen: true\n    }, '*')\n  }\n\n  function notifyClose (): void {\n    window.parent.postMessage({\n      isWidget: true,\n      isOpen: false\n    }, '*')\n  }\n\n  return {\n    updateCoreValuesOnStore,\n    notifyClose,\n    notifyOpen\n  }\n}\n"
  },
  {
    "path": "widget/src/hooks/navigation.ts",
    "content": "import useStore from './store'\nimport {\n  setCurrentComponent,\n  setFeedbackType\n} from '../store'\n\nexport interface Navigation {\n  next(): void;\n  back(): void;\n  setErrorState(): void;\n  setSuccessState(): void;\n}\n\nexport default function useNavigation (): Navigation {\n  const store = useStore()\n\n  function setErrorState (): void {\n    setCurrentComponent('Error')\n  }\n\n  function setSuccessState (): void {\n    setCurrentComponent('Success')\n  }\n\n  function next (): void {\n    if (store.currentComponent === 'SelectFeedbackType') {\n      setCurrentComponent('WriteAFeedback')\n    }\n  }\n\n  function back (): void {\n    if (store.currentComponent === 'WriteAFeedback') {\n      setCurrentComponent('SelectFeedbackType')\n      setFeedbackType('')\n    }\n  }\n\n  return { next, back, setErrorState, setSuccessState }\n}\n"
  },
  {
    "path": "widget/src/hooks/store.ts",
    "content": "import Store, { StoreState } from '../store'\n\nexport default function useStore (): StoreState {\n  return Store\n}\n"
  },
  {
    "path": "widget/src/main.ts",
    "content": "import { createApp } from 'vue'\nimport Playground from './views/Playground/index.vue'\nimport App from './App.vue'\nimport { setup } from './utils/bootstrap'\n\nimport '@/assets/css/tailwind.css'\nimport '@/assets/css/fonts.css'\nimport 'animate.css'\n\nsetup({\n  onProduction: () => {\n    createApp(App).mount('#app')\n  },\n  onDevelopment: () => {\n    createApp(Playground).mount('#app')\n  }\n})\n"
  },
  {
    "path": "widget/src/services/feedbacks.ts",
    "content": "import { AxiosInstance } from 'axios'\nimport { Feedback } from '../types/feedback'\nimport { RequestError } from '../types/error'\n\ntype Create = {\n  data: Feedback;\n  errors: RequestError | null;\n}\n\ntype CreatePayload = {\n     type: string;\n      text: string;\n      fingerprint: string;\n      device: string;\n      page: string;\n      apiKey: string;\n}\n\nexport interface FeedbackServiceInterface {\n  create(payload: CreatePayload): Promise<Create>;\n}\nfunction FeedbacksService (httpClient: AxiosInstance): FeedbackServiceInterface {\n  async function create (payload: CreatePayload): Promise<Create> {\n    const response = await httpClient.post<Feedback>('/feedbacks', payload)\n    let errors: RequestError | null = null\n\n    if (!response.data) {\n      errors = {\n        status: response.request.status,\n        statusText: response.request.statusText\n      }\n    }\n\n    return {\n      data: response.data,\n      errors\n    }\n  }\n\n  return {\n    create\n  }\n}\n\nexport default FeedbacksService\n"
  },
  {
    "path": "widget/src/services/index.ts",
    "content": "import axios from 'axios'\nimport FeedbacksService from './feedbacks'\n\nconst API_ENVS = {\n  production: 'https://backend-treinamento-vue3.vercel.app',\n  development: '',\n  local: 'http://localhost:3000'\n}\n\nconst httpClient = axios.create({\n  baseURL: API_ENVS[process.env.NODE_ENV] || API_ENVS.local\n})\n\nhttpClient.interceptors.response.use((response) => {\n  return response\n}, (error) => {\n  const canThrowAnError = error.request.status === 0 ||\n    error.request.status === 500\n\n  if (canThrowAnError) {\n    throw new Error(error.message)\n  }\n\n  return error\n})\n\nexport default {\n  feedbacks: FeedbacksService(httpClient)\n}\n"
  },
  {
    "path": "widget/src/shims-vue.d.ts",
    "content": "/* eslint-disable */\ndeclare module '*.vue' {\n  import type { DefineComponent } from 'vue'\n  const component: DefineComponent<{}, {}, any>\n  export default component\n}\n"
  },
  {
    "path": "widget/src/store/index.ts",
    "content": "import { reactive, readonly } from 'vue'\n\nexport type StoreState = {\n  currentComponent: string;\n  feedbackType: string;\n  message: string;\n  apiKey: string;\n  fingerprint: string;\n  currentPage: string;\n}\n\nconst initialState: StoreState = {\n  currentComponent: 'SelectFeedbackType',\n  message: '',\n  feedbackType: '',\n  fingerprint: '',\n  apiKey: '',\n  currentPage: ''\n}\n\nconst state = reactive<StoreState>({ ...initialState })\n\nexport function setCurrentComponent (component: string): void {\n  state.currentComponent = component\n}\n\nexport function setMessage (message: string): void {\n  state.message = message\n}\n\nexport function setFeedbackType (type: string): void {\n  state.feedbackType = type\n}\n\nexport function setCurrentPage (page: string): void {\n  state.currentPage = page\n}\n\nexport function setApiKey (apiKey: string): void {\n  state.apiKey = apiKey\n}\n\nexport function setFingerprint (fingerprint: string): void {\n  state.fingerprint = fingerprint\n}\n\nexport function resetStore (): void {\n  setCurrentComponent(initialState.currentComponent)\n  setMessage(initialState.message)\n  setFeedbackType(initialState.feedbackType)\n  setCurrentPage(initialState.currentPage)\n  setApiKey(initialState.apiKey)\n  setFingerprint(initialState.fingerprint)\n}\n\nexport default readonly(state)\n"
  },
  {
    "path": "widget/src/types/error.ts",
    "content": "export type RequestError = {\n  status: number;\n  statusText: string;\n}\n"
  },
  {
    "path": "widget/src/types/feedback.ts",
    "content": "export type Feedback = {\n  type: string;\n  text: string;\n  fingerprint: string;\n  device: string;\n  page: string;\n  apiKey: string;\n  createdAt: string;\n}\n"
  },
  {
    "path": "widget/src/utils/bootstrap.ts",
    "content": "interface SetupPayload {\n  onProduction: () => void;\n  onDevelopment: () => void;\n}\nexport function setup ({ onProduction, onDevelopment }: SetupPayload) {\n  if (process.env.NODE_ENV !== 'production') {\n    onDevelopment()\n    return\n  }\n\n  onProduction()\n}\n"
  },
  {
    "path": "widget/src/views/Playground/Playground.spec.js",
    "content": "import { shallowMount } from '@vue/test-utils'\nimport Playground from './index.vue'\n\ndescribe('<Playground />', () => {\n  it('should component render correctly', () => {\n    const wrapper = shallowMount(Playground)\n    expect(wrapper).toMatchSnapshot()\n  })\n})\n"
  },
  {
    "path": "widget/src/views/Playground/__snapshots__/Playground.spec.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`<Playground /> should component render correctly 1`] = `\nVueWrapper {\n  \"__app\": Object {\n    \"_component\": Object {\n      \"__emits\": Object {},\n      \"__props\": Array [\n        Object {},\n        Array [],\n      ],\n      \"name\": \"VTU_ROOT\",\n      \"render\": [Function],\n    },\n    \"_container\": <div\n      data-v-app=\"\"\n    >\n      \n      <div\n        class=\"w-full h-3/4 flex flex-col justify-center items-center bg-brand-main\"\n      >\n        <h1\n          class=\"text-6xl font-black text-brand-gray\"\n        >\n          Playground\n        </h1>\n        <p\n          class=\"text-2xl mt-3 font-regular text-brand-gray\"\n        >\n          Este é o playground, use para testar o widget.\n        </p>\n      </div>\n      <div\n        class=\"w-full h-3/4 flex flex-col justify-center items-center bg-brand-gray\"\n      >\n        <h1\n          class=\"text-6xl font-black text-brand-graydark\"\n        >\n          🚀\n        </h1>\n      </div>\n      <div\n        class=\"w-full h-3/4 flex flex-col justify-center items-center bg-brand-main\"\n      >\n        <h1\n          class=\"text-6xl font-black text-brand-gray\"\n        >\n          👀\n        </h1>\n      </div>\n      <div\n        class=\"w-full h-3/4 flex flex-col justify-center items-center bg-brand-gray\"\n      >\n        <h1\n          class=\"text-6xl font-black text-brand-graydark\"\n        >\n          🚨\n        </h1>\n      </div>\n      <widget-stub />\n      \n    </div>,\n    \"_context\": Object {\n      \"app\": [Circular],\n      \"components\": Object {\n        \"transition\": Object {\n          \"name\": \"transition\",\n          \"props\": undefined,\n          \"render\": [Function],\n        },\n        \"transition-group\": Object {\n          \"name\": \"transition-group\",\n          \"props\": undefined,\n          \"render\": [Function],\n        },\n      },\n      \"config\": Object {\n        \"errorHandler\": undefined,\n        \"globalProperties\": Object {},\n        \"isCustomElement\": [Function],\n        \"isNativeTag\": [Function],\n        \"optionMergeStrategies\": Object {},\n        \"performance\": false,\n        \"warnHandler\": undefined,\n      },\n      \"directives\": Object {},\n      \"mixins\": Array [\n        Object {\n          \"__emits\": null,\n          \"__props\": Array [],\n          \"beforeCreate\": [Function],\n        },\n      ],\n      \"provides\": Object {},\n      \"reload\": [Function],\n    },\n    \"_props\": null,\n    \"_uid\": 0,\n    \"component\": [Function],\n    \"config\": Object {\n      \"errorHandler\": undefined,\n      \"globalProperties\": Object {},\n      \"isCustomElement\": [Function],\n      \"isNativeTag\": [Function],\n      \"optionMergeStrategies\": Object {},\n      \"performance\": false,\n      \"warnHandler\": undefined,\n    },\n    \"directive\": [Function],\n    \"mixin\": [Function],\n    \"mount\": [Function],\n    \"provide\": [Function],\n    \"unmount\": [Function],\n    \"use\": [Function],\n    \"version\": \"3.0.5\",\n  },\n  \"__setProps\": [Function],\n  \"componentVM\": Object {},\n  \"rootVM\": Object {},\n}\n`;\n"
  },
  {
    "path": "widget/src/views/Playground/index.vue",
    "content": "<template>\n  <div class=\"w-full h-3/4 flex flex-col justify-center items-center bg-brand-main\">\n    <h1 class=\"text-6xl font-black text-brand-gray\">Playground</h1>\n    <p class=\"text-2xl mt-3 font-regular text-brand-gray\">Este é o playground, use para testar o widget.</p>\n  </div>\n  <div class=\"w-full h-3/4 flex flex-col justify-center items-center bg-brand-gray\">\n    <h1 class=\"text-6xl font-black text-brand-graydark\">🚀</h1>\n  </div>\n  <div class=\"w-full h-3/4 flex flex-col justify-center items-center bg-brand-main\">\n    <h1 class=\"text-6xl font-black text-brand-gray\">👀</h1>\n  </div>\n  <div class=\"w-full h-3/4 flex flex-col justify-center items-center bg-brand-gray\">\n    <h1 class=\"text-6xl font-black text-brand-graydark\">🚨</h1>\n  </div>\n  <widget />\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport Widget from '../Widget/index.vue'\n\nexport default defineComponent({\n  components: { Widget }\n})\n</script>\n"
  },
  {
    "path": "widget/src/views/Widget/Box.vue",
    "content": "<template>\n  <div class=\"box animate__animated animate__fadeInUp animate__faster\">\n    <div\n      :class=\"{\n        'justify-between': canShowAdditionalControlAndInfo,\n        'justify-end': !canShowAdditionalControlAndInfo\n      }\"\n      class=\"relative w-full flex\">\n      <button\n        v-if=\"canShowAdditionalControlAndInfo\"\n        @click=\"back\"\n        :disabled=\"canGoBack\"\n        :class=\"{ invisible: canGoBack }\"\n        class=\"text-xl text-gray-800 focus:outline-none\"\n      >\n        <icon name=\"arrow-right\" :color=\"colors.gray['800']\" />\n      </button>\n\n      <p\n        v-if=\"canShowAdditionalControlAndInfo\"\n        class=\"text-xl font-black text-center text-brand-main\"\n        >\n        {{ label }}\n      </p>\n\n      <button\n        @click=\"() => emit('close-box')\"\n        class=\"text-xl text-gray-800 focus:outline-none\"\n      >\n        <icon size=\"14\" name=\"close\" :color=\"colors.gray['800']\" />\n      </button>\n    </div>\n\n    <wizard />\n\n    <div class=\"text-gray-800 text-sm flex\" v-if=\"canShowAdditionalControlAndInfo\">\n      <icon name=\"chat\" class=\"mr-1\" :color=\"brandColors.graydark\" />\n      widget by\n      <span class=\"ml-1 font-bold\">feedbacker</span>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, computed, ComputedRef, SetupContext } from 'vue'\nimport { brand } from '../../../palette'\nimport Icon from '../../components/Icon/index.vue'\nimport Wizard from '../../components/Wizard/index.vue'\nimport colors from 'tailwindcss/colors'\nimport useStore from '../../hooks/store'\nimport useNavigation, { Navigation } from '../../hooks/navigation'\n\ninterface SetupReturn {\n  emit: SetupContext['emit'];\n  back: Navigation['back'];\n  canGoBack: ComputedRef<boolean>;\n  label: ComputedRef<string>;\n  canShowAdditionalControlAndInfo: ComputedRef<boolean>;\n  brandColors: Record<string, string>;\n  colors: Record<string, string>;\n}\n\nexport default defineComponent({\n  emits: ['close-box'],\n  components: { Icon, Wizard },\n  setup (_, { emit }: SetupContext): SetupReturn {\n    const store = useStore()\n    const { back } = useNavigation()\n\n    const label = computed<string>(() => {\n      if (store.feedbackType === 'ISSUE') {\n        return 'Reporte um problema'\n      }\n\n      if (store.feedbackType === 'IDEA') {\n        return 'Nos fale a sua ideia'\n      }\n\n      if (store.feedbackType === 'OTHER') {\n        return 'Abra sua mente'\n      }\n\n      return 'O que você tem em mente?'\n    })\n\n    const canGoBack = computed<boolean>(() => {\n      return store.currentComponent === 'SelectFeedbackType'\n    })\n\n    const canShowAdditionalControlAndInfo = computed<boolean>(() => {\n      return store.currentComponent !== 'Success' && store.currentComponent !== 'Error'\n    })\n\n    return {\n      emit,\n      colors,\n      label,\n      back,\n      brandColors: brand,\n      canGoBack,\n      canShowAdditionalControlAndInfo\n    }\n  }\n})\n</script>\n\n<style lang=\"postcss\" scoped>\n.box {\n  @apply fixed z-50 bottom-0 right-0 mb-5 mr-5 bg-white rounded-xl\n    py-5 px-5 flex flex-col items-center shadow-xl select-none;\n  width: 400px;\n}\n</style>\n"
  },
  {
    "path": "widget/src/views/Widget/Standby.vue",
    "content": "<template>\n  <div\n    @click=\"() => emit('open-box')\"\n    id=\"widget-open-button\"\n    class=\"\n      fixed z-50 bottom-0 right-0 mb-5 mr-5 bg-brand-main rounded-full\n      py-3 px-5 flex items-center shadow-xl cursor-pointer select-none\n      animate__animated animate__fadeInUp animate__faster\n    \">\n\n    <icon\n      name=\"Chat\"\n      color=\"white\"\n      size=\"27\"\n      class=\"mr-3\"\n    />\n    <span class=\"font-black text-white text-xl\">\n      Deixe um feedback\n    </span>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, SetupContext } from 'vue'\nimport Icon from '../../components/Icon/index.vue'\n\ninterface SetupReturn {\n  emit: SetupContext['emit'];\n}\n\nexport default defineComponent({\n  components: { Icon },\n  emits: ['open-box'],\n  setup (_, { emit }: SetupContext): SetupReturn {\n    return { emit }\n  }\n})\n</script>\n"
  },
  {
    "path": "widget/src/views/Widget/index.vue",
    "content": "<template>\n  <teleport to=\"body\">\n    <component\n      @open-box=\"handleOpenBox\"\n      @close-box=\"handleCloseBox\"\n      :is=\"state.component\"\n    />\n  </teleport>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, reactive, watch } from 'vue'\nimport Standby from './Standby.vue'\nimport Box from './Box.vue'\nimport useIframeControl from '../../hooks/iframe'\nimport useStore from '../../hooks/store'\n\ntype State = {\n  component: string;\n}\n\ninterface SetupReturn {\n  state: State;\n  handleOpenBox(): void;\n  handleCloseBox(): void;\n}\n\nexport default defineComponent({\n  components: { Standby, Box },\n  setup (): SetupReturn {\n    const store = useStore()\n    const iframe = useIframeControl()\n    const state = reactive<State>({\n      component: 'Standby'\n    })\n\n    watch(() => store.currentComponent, () => {\n      iframe.updateCoreValuesOnStore()\n    })\n\n    function handleOpenBox (): void {\n      iframe.notifyOpen()\n      state.component = 'Box'\n    }\n\n    function handleCloseBox (): void {\n      iframe.notifyClose()\n      state.component = 'Standby'\n    }\n\n    return {\n      state,\n      handleOpenBox,\n      handleCloseBox\n    }\n  }\n})\n</script>\n"
  },
  {
    "path": "widget/tailwind.config.js",
    "content": "const colors = require('tailwindcss/colors')\nconst palette = require('./palette')\n\nmodule.exports = {\n  purge: [\n    './src/**/*.html',\n    './src/**/*.vue',\n    './src/**/*.jsx'\n  ],\n  presets: [],\n  darkMode: false, // or 'media' or 'class'\n  theme: {\n    extend: {\n      colors: palette\n    },\n    screens: {\n      sm: '640px',\n      md: '768px',\n      lg: '1024px',\n      xl: '1280px',\n      '2xl': '1536px'\n    },\n    colors: {\n      transparent: 'transparent',\n      current: 'currentColor',\n\n      black: colors.black,\n      white: colors.white,\n      gray: colors.coolGray,\n      red: colors.red,\n      yellow: colors.amber,\n      green: colors.emerald,\n      blue: colors.blue,\n      indigo: colors.indigo,\n      purple: colors.violet,\n      pink: colors.pink\n    },\n    spacing: {\n      px: '1px',\n      0: '0px',\n      0.5: '0.125rem',\n      1: '0.25rem',\n      1.5: '0.375rem',\n      2: '0.5rem',\n      2.5: '0.625rem',\n      3: '0.75rem',\n      3.5: '0.875rem',\n      4: '1rem',\n      5: '1.25rem',\n      6: '1.5rem',\n      7: '1.75rem',\n      8: '2rem',\n      9: '2.25rem',\n      10: '2.5rem',\n      11: '2.75rem',\n      12: '3rem',\n      14: '3.5rem',\n      16: '4rem',\n      20: '5rem',\n      24: '6rem',\n      28: '7rem',\n      32: '8rem',\n      36: '9rem',\n      40: '10rem',\n      44: '11rem',\n      48: '12rem',\n      52: '13rem',\n      56: '14rem',\n      60: '15rem',\n      64: '16rem',\n      72: '18rem',\n      80: '20rem',\n      96: '24rem'\n    },\n    animation: {\n      none: 'none',\n      spin: 'spin 1s linear infinite',\n      ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',\n      pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',\n      bounce: 'bounce 1s infinite'\n    },\n    backgroundColor: (theme) => theme('colors'),\n    backgroundImage: {\n      none: 'none',\n      'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))',\n      'gradient-to-tr': 'linear-gradient(to top right, var(--tw-gradient-stops))',\n      'gradient-to-r': 'linear-gradient(to right, var(--tw-gradient-stops))',\n      'gradient-to-br': 'linear-gradient(to bottom right, var(--tw-gradient-stops))',\n      'gradient-to-b': 'linear-gradient(to bottom, var(--tw-gradient-stops))',\n      'gradient-to-bl': 'linear-gradient(to bottom left, var(--tw-gradient-stops))',\n      'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))',\n      'gradient-to-tl': 'linear-gradient(to top left, var(--tw-gradient-stops))'\n    },\n    backgroundOpacity: (theme) => theme('opacity'),\n    backgroundPosition: {\n      bottom: 'bottom',\n      center: 'center',\n      left: 'left',\n      'left-bottom': 'left bottom',\n      'left-top': 'left top',\n      right: 'right',\n      'right-bottom': 'right bottom',\n      'right-top': 'right top',\n      top: 'top'\n    },\n    backgroundSize: {\n      auto: 'auto',\n      cover: 'cover',\n      contain: 'contain'\n    },\n    borderColor: (theme) => ({\n      ...theme('colors'),\n      DEFAULT: theme('colors.gray.200', 'currentColor')\n    }),\n    borderOpacity: (theme) => theme('opacity'),\n    borderRadius: {\n      none: '0px',\n      sm: '0.125rem',\n      DEFAULT: '0.25rem',\n      md: '0.375rem',\n      lg: '0.5rem',\n      xl: '0.75rem',\n      '2xl': '1rem',\n      '3xl': '1.5rem',\n      full: '9999px'\n    },\n    borderWidth: {\n      DEFAULT: '1px',\n      0: '0px',\n      2: '2px',\n      4: '4px',\n      8: '8px'\n    },\n    boxShadow: {\n      sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',\n      DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',\n      md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',\n      lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',\n      xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',\n      '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',\n      inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',\n      none: 'none'\n    },\n    container: {},\n    cursor: {\n      auto: 'auto',\n      default: 'default',\n      pointer: 'pointer',\n      wait: 'wait',\n      text: 'text',\n      move: 'move',\n      'not-allowed': 'not-allowed'\n    },\n    divideColor: (theme) => theme('borderColor'),\n    divideOpacity: (theme) => theme('borderOpacity'),\n    divideWidth: (theme) => theme('borderWidth'),\n    fill: { current: 'currentColor' },\n    flex: {\n      1: '1 1 0%',\n      auto: '1 1 auto',\n      initial: '0 1 auto',\n      none: 'none'\n    },\n    flexGrow: {\n      0: '0',\n      DEFAULT: '1'\n    },\n    flexShrink: {\n      0: '0',\n      DEFAULT: '1'\n    },\n    fontFamily: {\n      regular: ['RobotoRegular'],\n      medium: ['RobotoMedium'],\n      bold: ['RobotoBold'],\n      black: ['RobotoBlack'],\n      sans: [\n        'ui-sans-serif',\n        'system-ui',\n        '-apple-system',\n        'BlinkMacSystemFont',\n        '\"Segoe UI\"',\n        'Roboto',\n        '\"Helvetica Neue\"',\n        'Arial',\n        '\"Noto Sans\"',\n        'sans-serif',\n        '\"Apple Color Emoji\"',\n        '\"Segoe UI Emoji\"',\n        '\"Segoe UI Symbol\"',\n        '\"Noto Color Emoji\"'\n      ],\n      serif: ['ui-serif', 'Georgia', 'Cambria', '\"Times New Roman\"', 'Times', 'serif'],\n      mono: [\n        'ui-monospace',\n        'SFMono-Regular',\n        'Menlo',\n        'Monaco',\n        'Consolas',\n        '\"Liberation Mono\"',\n        '\"Courier New\"',\n        'monospace'\n      ]\n    },\n    fontSize: {\n      xs: ['0.75rem', { lineHeight: '1rem' }],\n      sm: ['0.875rem', { lineHeight: '1.25rem' }],\n      base: ['1rem', { lineHeight: '1.5rem' }],\n      lg: ['1.125rem', { lineHeight: '1.75rem' }],\n      xl: ['1.25rem', { lineHeight: '1.75rem' }],\n      '2xl': ['1.5rem', { lineHeight: '2rem' }],\n      '3xl': ['1.875rem', { lineHeight: '2.25rem' }],\n      '4xl': ['2.25rem', { lineHeight: '2.5rem' }],\n      '5xl': ['3rem', { lineHeight: '1' }],\n      '6xl': ['3.75rem', { lineHeight: '1' }],\n      '7xl': ['4.5rem', { lineHeight: '1' }],\n      '8xl': ['6rem', { lineHeight: '1' }],\n      '9xl': ['8rem', { lineHeight: '1' }]\n    },\n    fontWeight: {\n      thin: '100',\n      extralight: '200',\n      light: '300',\n      normal: '400',\n      medium: '500',\n      semibold: '600',\n      bold: '700',\n      extrabold: '800',\n      black: '900'\n    },\n    gap: (theme) => theme('spacing'),\n    gradientColorStops: (theme) => theme('colors'),\n    gridAutoColumns: {\n      auto: 'auto',\n      min: 'min-content',\n      max: 'max-content',\n      fr: 'minmax(0, 1fr)'\n    },\n    gridAutoRows: {\n      auto: 'auto',\n      min: 'min-content',\n      max: 'max-content',\n      fr: 'minmax(0, 1fr)'\n    },\n    gridColumn: {\n      auto: 'auto',\n      'span-1': 'span 1 / span 1',\n      'span-2': 'span 2 / span 2',\n      'span-3': 'span 3 / span 3',\n      'span-4': 'span 4 / span 4',\n      'span-5': 'span 5 / span 5',\n      'span-6': 'span 6 / span 6',\n      'span-7': 'span 7 / span 7',\n      'span-8': 'span 8 / span 8',\n      'span-9': 'span 9 / span 9',\n      'span-10': 'span 10 / span 10',\n      'span-11': 'span 11 / span 11',\n      'span-12': 'span 12 / span 12',\n      'span-full': '1 / -1'\n    },\n    gridColumnEnd: {\n      auto: 'auto',\n      1: '1',\n      2: '2',\n      3: '3',\n      4: '4',\n      5: '5',\n      6: '6',\n      7: '7',\n      8: '8',\n      9: '9',\n      10: '10',\n      11: '11',\n      12: '12',\n      13: '13'\n    },\n    gridColumnStart: {\n      auto: 'auto',\n      1: '1',\n      2: '2',\n      3: '3',\n      4: '4',\n      5: '5',\n      6: '6',\n      7: '7',\n      8: '8',\n      9: '9',\n      10: '10',\n      11: '11',\n      12: '12',\n      13: '13'\n    },\n    gridRow: {\n      auto: 'auto',\n      'span-1': 'span 1 / span 1',\n      'span-2': 'span 2 / span 2',\n      'span-3': 'span 3 / span 3',\n      'span-4': 'span 4 / span 4',\n      'span-5': 'span 5 / span 5',\n      'span-6': 'span 6 / span 6',\n      'span-full': '1 / -1'\n    },\n    gridRowStart: {\n      auto: 'auto',\n      1: '1',\n      2: '2',\n      3: '3',\n      4: '4',\n      5: '5',\n      6: '6',\n      7: '7'\n    },\n    gridRowEnd: {\n      auto: 'auto',\n      1: '1',\n      2: '2',\n      3: '3',\n      4: '4',\n      5: '5',\n      6: '6',\n      7: '7'\n    },\n    transformOrigin: {\n      center: 'center',\n      top: 'top',\n      'top-right': 'top right',\n      right: 'right',\n      'bottom-right': 'bottom right',\n      bottom: 'bottom',\n      'bottom-left': 'bottom left',\n      left: 'left',\n      'top-left': 'top left'\n    },\n    gridTemplateColumns: {\n      none: 'none',\n      1: 'repeat(1, minmax(0, 1fr))',\n      2: 'repeat(2, minmax(0, 1fr))',\n      3: 'repeat(3, minmax(0, 1fr))',\n      4: 'repeat(4, minmax(0, 1fr))',\n      5: 'repeat(5, minmax(0, 1fr))',\n      6: 'repeat(6, minmax(0, 1fr))',\n      7: 'repeat(7, minmax(0, 1fr))',\n      8: 'repeat(8, minmax(0, 1fr))',\n      9: 'repeat(9, minmax(0, 1fr))',\n      10: 'repeat(10, minmax(0, 1fr))',\n      11: 'repeat(11, minmax(0, 1fr))',\n      12: 'repeat(12, minmax(0, 1fr))'\n    },\n    gridTemplateRows: {\n      none: 'none',\n      1: 'repeat(1, minmax(0, 1fr))',\n      2: 'repeat(2, minmax(0, 1fr))',\n      3: 'repeat(3, minmax(0, 1fr))',\n      4: 'repeat(4, minmax(0, 1fr))',\n      5: 'repeat(5, minmax(0, 1fr))',\n      6: 'repeat(6, minmax(0, 1fr))'\n    },\n    height: (theme) => ({\n      auto: 'auto',\n      ...theme('spacing'),\n      '1/2': '50%',\n      '1/3': '33.333333%',\n      '2/3': '66.666667%',\n      '1/4': '25%',\n      '2/4': '50%',\n      '3/4': '75%',\n      '1/5': '20%',\n      '2/5': '40%',\n      '3/5': '60%',\n      '4/5': '80%',\n      '1/6': '16.666667%',\n      '2/6': '33.333333%',\n      '3/6': '50%',\n      '4/6': '66.666667%',\n      '5/6': '83.333333%',\n      full: '100%',\n      screen: '100vh'\n    }),\n    inset: (theme, { negative }) => ({\n      auto: 'auto',\n      ...theme('spacing'),\n      ...negative(theme('spacing')),\n      '1/2': '50%',\n      '1/3': '33.333333%',\n      '2/3': '66.666667%',\n      '1/4': '25%',\n      '2/4': '50%',\n      '3/4': '75%',\n      full: '100%',\n      '-1/2': '-50%',\n      '-1/3': '-33.333333%',\n      '-2/3': '-66.666667%',\n      '-1/4': '-25%',\n      '-2/4': '-50%',\n      '-3/4': '-75%',\n      '-full': '-100%'\n    }),\n    keyframes: {\n      spin: {\n        to: {\n          transform: 'rotate(360deg)'\n        }\n      },\n      ping: {\n        '75%, 100%': {\n          transform: 'scale(2)',\n          opacity: '0'\n        }\n      },\n      pulse: {\n        '50%': {\n          opacity: '.5'\n        }\n      },\n      bounce: {\n        '0%, 100%': {\n          transform: 'translateY(-25%)',\n          animationTimingFunction: 'cubic-bezier(0.8,0,1,1)'\n        },\n        '50%': {\n          transform: 'none',\n          animationTimingFunction: 'cubic-bezier(0,0,0.2,1)'\n        }\n      }\n    },\n    letterSpacing: {\n      tighter: '-0.05em',\n      tight: '-0.025em',\n      normal: '0em',\n      wide: '0.025em',\n      wider: '0.05em',\n      widest: '0.1em'\n    },\n    lineHeight: {\n      none: '1',\n      tight: '1.25',\n      snug: '1.375',\n      normal: '1.5',\n      relaxed: '1.625',\n      loose: '2',\n      3: '.75rem',\n      4: '1rem',\n      5: '1.25rem',\n      6: '1.5rem',\n      7: '1.75rem',\n      8: '2rem',\n      9: '2.25rem',\n      10: '2.5rem'\n    },\n    listStyleType: {\n      none: 'none',\n      disc: 'disc',\n      decimal: 'decimal'\n    },\n    margin: (theme, { negative }) => ({\n      auto: 'auto',\n      ...theme('spacing'),\n      ...negative(theme('spacing'))\n    }),\n    maxHeight: (theme) => ({\n      ...theme('spacing'),\n      full: '100%',\n      screen: '100vh'\n    }),\n    maxWidth: (theme, { breakpoints }) => ({\n      none: 'none',\n      0: '0rem',\n      xs: '20rem',\n      sm: '24rem',\n      md: '28rem',\n      lg: '32rem',\n      xl: '36rem',\n      '2xl': '42rem',\n      '3xl': '48rem',\n      '4xl': '56rem',\n      '5xl': '64rem',\n      '6xl': '72rem',\n      '7xl': '80rem',\n      full: '100%',\n      min: 'min-content',\n      max: 'max-content',\n      prose: '65ch',\n      ...breakpoints(theme('screens'))\n    }),\n    minHeight: {\n      0: '0px',\n      full: '100%',\n      screen: '100vh'\n    },\n    minWidth: {\n      0: '0px',\n      full: '100%',\n      min: 'min-content',\n      max: 'max-content'\n    },\n    objectPosition: {\n      bottom: 'bottom',\n      center: 'center',\n      left: 'left',\n      'left-bottom': 'left bottom',\n      'left-top': 'left top',\n      right: 'right',\n      'right-bottom': 'right bottom',\n      'right-top': 'right top',\n      top: 'top'\n    },\n    opacity: {\n      0: '0',\n      5: '0.05',\n      10: '0.1',\n      20: '0.2',\n      25: '0.25',\n      30: '0.3',\n      40: '0.4',\n      50: '0.5',\n      60: '0.6',\n      70: '0.7',\n      75: '0.75',\n      80: '0.8',\n      90: '0.9',\n      95: '0.95',\n      100: '1'\n    },\n    order: {\n      first: '-9999',\n      last: '9999',\n      none: '0',\n      1: '1',\n      2: '2',\n      3: '3',\n      4: '4',\n      5: '5',\n      6: '6',\n      7: '7',\n      8: '8',\n      9: '9',\n      10: '10',\n      11: '11',\n      12: '12'\n    },\n    outline: {\n      none: ['2px solid transparent', '2px'],\n      white: ['2px dotted white', '2px'],\n      black: ['2px dotted black', '2px']\n    },\n    padding: (theme) => theme('spacing'),\n    placeholderColor: (theme) => theme('colors'),\n    placeholderOpacity: (theme) => theme('opacity'),\n    ringColor: (theme) => ({\n      DEFAULT: theme('colors.blue.500', '#3b82f6'),\n      ...theme('colors')\n    }),\n    ringOffsetColor: (theme) => theme('colors'),\n    ringOffsetWidth: {\n      0: '0px',\n      1: '1px',\n      2: '2px',\n      4: '4px',\n      8: '8px'\n    },\n    ringOpacity: (theme) => ({\n      DEFAULT: '0.5',\n      ...theme('opacity')\n    }),\n    ringWidth: {\n      DEFAULT: '3px',\n      0: '0px',\n      1: '1px',\n      2: '2px',\n      4: '4px',\n      8: '8px'\n    },\n    rotate: {\n      '-180': '-180deg',\n      '-90': '-90deg',\n      '-45': '-45deg',\n      '-12': '-12deg',\n      '-6': '-6deg',\n      '-3': '-3deg',\n      '-2': '-2deg',\n      '-1': '-1deg',\n      0: '0deg',\n      1: '1deg',\n      2: '2deg',\n      3: '3deg',\n      6: '6deg',\n      12: '12deg',\n      45: '45deg',\n      90: '90deg',\n      180: '180deg'\n    },\n    scale: {\n      0: '0',\n      50: '.5',\n      75: '.75',\n      90: '.9',\n      95: '.95',\n      100: '1',\n      105: '1.05',\n      110: '1.1',\n      125: '1.25',\n      150: '1.5'\n    },\n    skew: {\n      '-12': '-12deg',\n      '-6': '-6deg',\n      '-3': '-3deg',\n      '-2': '-2deg',\n      '-1': '-1deg',\n      0: '0deg',\n      1: '1deg',\n      2: '2deg',\n      3: '3deg',\n      6: '6deg',\n      12: '12deg'\n    },\n    space: (theme, { negative }) => ({\n      ...theme('spacing'),\n      ...negative(theme('spacing'))\n    }),\n    stroke: {\n      current: 'currentColor'\n    },\n    strokeWidth: {\n      0: '0',\n      1: '1',\n      2: '2'\n    },\n    textColor: (theme) => theme('colors'),\n    textOpacity: (theme) => theme('opacity'),\n    transitionDuration: {\n      DEFAULT: '150ms',\n      75: '75ms',\n      100: '100ms',\n      150: '150ms',\n      200: '200ms',\n      300: '300ms',\n      500: '500ms',\n      700: '700ms',\n      1000: '1000ms'\n    },\n    transitionDelay: {\n      75: '75ms',\n      100: '100ms',\n      150: '150ms',\n      200: '200ms',\n      300: '300ms',\n      500: '500ms',\n      700: '700ms',\n      1000: '1000ms'\n    },\n    transitionProperty: {\n      none: 'none',\n      all: 'all',\n      DEFAULT: 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform',\n      colors: 'background-color, border-color, color, fill, stroke',\n      opacity: 'opacity',\n      shadow: 'box-shadow',\n      transform: 'transform'\n    },\n    transitionTimingFunction: {\n      DEFAULT: 'cubic-bezier(0.4, 0, 0.2, 1)',\n      linear: 'linear',\n      in: 'cubic-bezier(0.4, 0, 1, 1)',\n      out: 'cubic-bezier(0, 0, 0.2, 1)',\n      'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)'\n    },\n    translate: (theme, { negative }) => ({\n      ...theme('spacing'),\n      ...negative(theme('spacing')),\n      '1/2': '50%',\n      '1/3': '33.333333%',\n      '2/3': '66.666667%',\n      '1/4': '25%',\n      '2/4': '50%',\n      '3/4': '75%',\n      full: '100%',\n      '-1/2': '-50%',\n      '-1/3': '-33.333333%',\n      '-2/3': '-66.666667%',\n      '-1/4': '-25%',\n      '-2/4': '-50%',\n      '-3/4': '-75%',\n      '-full': '-100%'\n    }),\n    width: (theme) => ({\n      auto: 'auto',\n      ...theme('spacing'),\n      '1/2': '50%',\n      '1/3': '33.333333%',\n      '2/3': '66.666667%',\n      '1/4': '25%',\n      '2/4': '50%',\n      '3/4': '75%',\n      '1/5': '20%',\n      '2/5': '40%',\n      '3/5': '60%',\n      '4/5': '80%',\n      '1/6': '16.666667%',\n      '2/6': '33.333333%',\n      '3/6': '50%',\n      '4/6': '66.666667%',\n      '5/6': '83.333333%',\n      '1/12': '8.333333%',\n      '2/12': '16.666667%',\n      '3/12': '25%',\n      '4/12': '33.333333%',\n      '5/12': '41.666667%',\n      '6/12': '50%',\n      '7/12': '58.333333%',\n      '8/12': '66.666667%',\n      '9/12': '75%',\n      '10/12': '83.333333%',\n      '11/12': '91.666667%',\n      full: '100%',\n      screen: '100vw',\n      min: 'min-content',\n      max: 'max-content'\n    }),\n    zIndex: {\n      auto: 'auto',\n      0: '0',\n      10: '10',\n      20: '20',\n      30: '30',\n      40: '40',\n      50: '50'\n    }\n  },\n  variantOrder: [\n    'first',\n    'last',\n    'odd',\n    'even',\n    'visited',\n    'checked',\n    'group-hover',\n    'group-focus',\n    'focus-within',\n    'hover',\n    'focus',\n    'focus-visible',\n    'active',\n    'disabled'\n  ],\n  variants: {\n    accessibility: ['responsive', 'focus-within', 'focus'],\n    alignContent: ['responsive'],\n    alignItems: ['responsive'],\n    alignSelf: ['responsive'],\n    animation: ['responsive'],\n    appearance: ['responsive'],\n    backgroundAttachment: ['responsive'],\n    backgroundClip: ['responsive'],\n    backgroundColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],\n    backgroundImage: ['responsive'],\n    backgroundOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    backgroundPosition: ['responsive'],\n    backgroundRepeat: ['responsive'],\n    backgroundSize: ['responsive'],\n    borderCollapse: ['responsive'],\n    borderColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],\n    borderOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    borderRadius: ['responsive'],\n    borderStyle: ['responsive'],\n    borderWidth: ['responsive'],\n    boxShadow: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    boxSizing: ['responsive'],\n    clear: ['responsive'],\n    container: ['responsive'],\n    cursor: ['responsive'],\n    display: ['responsive'],\n    divideColor: ['responsive', 'dark'],\n    divideOpacity: ['responsive'],\n    divideStyle: ['responsive'],\n    divideWidth: ['responsive'],\n    fill: ['responsive'],\n    flex: ['responsive'],\n    flexDirection: ['responsive'],\n    flexGrow: ['responsive'],\n    flexShrink: ['responsive'],\n    flexWrap: ['responsive'],\n    float: ['responsive'],\n    fontFamily: ['responsive'],\n    fontSize: ['responsive'],\n    fontSmoothing: ['responsive'],\n    fontStyle: ['responsive'],\n    fontVariantNumeric: ['responsive'],\n    fontWeight: ['responsive'],\n    gap: ['responsive'],\n    gradientColorStops: ['responsive', 'dark', 'hover', 'focus'],\n    gridAutoColumns: ['responsive'],\n    gridAutoFlow: ['responsive'],\n    gridAutoRows: ['responsive'],\n    gridColumn: ['responsive'],\n    gridColumnEnd: ['responsive'],\n    gridColumnStart: ['responsive'],\n    gridRow: ['responsive'],\n    gridRowEnd: ['responsive'],\n    gridRowStart: ['responsive'],\n    gridTemplateColumns: ['responsive'],\n    gridTemplateRows: ['responsive'],\n    height: ['responsive'],\n    inset: ['responsive'],\n    justifyContent: ['responsive'],\n    justifyItems: ['responsive'],\n    justifySelf: ['responsive'],\n    letterSpacing: ['responsive'],\n    lineHeight: ['responsive'],\n    listStylePosition: ['responsive'],\n    listStyleType: ['responsive'],\n    margin: ['responsive'],\n    maxHeight: ['responsive'],\n    maxWidth: ['responsive'],\n    minHeight: ['responsive'],\n    minWidth: ['responsive'],\n    objectFit: ['responsive'],\n    objectPosition: ['responsive'],\n    opacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    order: ['responsive'],\n    outline: ['responsive', 'focus-within', 'focus'],\n    overflow: ['responsive'],\n    overscrollBehavior: ['responsive'],\n    padding: ['responsive'],\n    placeContent: ['responsive'],\n    placeItems: ['responsive'],\n    placeSelf: ['responsive'],\n    placeholderColor: ['responsive', 'dark', 'focus'],\n    placeholderOpacity: ['responsive', 'focus'],\n    pointerEvents: ['responsive'],\n    position: ['responsive'],\n    resize: ['responsive'],\n    ringColor: ['responsive', 'dark', 'focus-within', 'focus'],\n    ringOffsetColor: ['responsive', 'dark', 'focus-within', 'focus'],\n    ringOffsetWidth: ['responsive', 'focus-within', 'focus'],\n    ringOpacity: ['responsive', 'focus-within', 'focus'],\n    ringWidth: ['responsive', 'focus-within', 'focus'],\n    rotate: ['responsive', 'hover', 'focus'],\n    scale: ['responsive', 'hover', 'focus'],\n    skew: ['responsive', 'hover', 'focus'],\n    space: ['responsive'],\n    stroke: ['responsive'],\n    strokeWidth: ['responsive'],\n    tableLayout: ['responsive'],\n    textAlign: ['responsive'],\n    textColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],\n    textDecoration: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    textOpacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],\n    textOverflow: ['responsive'],\n    textTransform: ['responsive'],\n    transform: ['responsive'],\n    transformOrigin: ['responsive'],\n    transitionDelay: ['responsive'],\n    transitionDuration: ['responsive'],\n    transitionProperty: ['responsive'],\n    transitionTimingFunction: ['responsive'],\n    translate: ['responsive', 'hover', 'focus'],\n    userSelect: ['responsive'],\n    verticalAlign: ['responsive'],\n    visibility: ['responsive'],\n    whitespace: ['responsive'],\n    width: ['responsive'],\n    wordBreak: ['responsive'],\n    zIndex: ['responsive', 'focus-within', 'focus']\n  },\n  plugins: []\n}\n"
  },
  {
    "path": "widget/tests/e2e/.eslintrc.js",
    "content": "module.exports = {\n  plugins: [\n    'cypress'\n  ],\n  env: {\n    mocha: true,\n    'cypress/globals': true\n  },\n  rules: {\n    strict: 'off'\n  }\n}\n"
  },
  {
    "path": "widget/tests/e2e/plugins/index.js",
    "content": "/* eslint-disable arrow-body-style */\n// https://docs.cypress.io/guides/guides/plugins-guide.html\n\n// if you need a custom webpack configuration you can uncomment the following import\n// and then use the `file:preprocessor` event\n// as explained in the cypress docs\n// https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples\n\n// /* eslint-disable import/no-extraneous-dependencies, global-require */\n// const webpack = require('@cypress/webpack-preprocessor')\n\nmodule.exports = (on, config) => {\n  // on('file:preprocessor', webpack({\n  //  webpackOptions: require('@vue/cli-service/webpack.config'),\n  //  watchOptions: {}\n  // }))\n\n  return Object.assign({}, config, {\n    fixturesFolder: 'tests/e2e/fixtures',\n    integrationFolder: 'tests/e2e/specs',\n    screenshotsFolder: 'tests/e2e/screenshots',\n    videosFolder: 'tests/e2e/videos',\n    supportFile: 'tests/e2e/support/index.js'\n  })\n}\n"
  },
  {
    "path": "widget/tests/e2e/specs/widget.js",
    "content": "const APP_URL = process.env.APP_URL || 'http://localhost:8080'\n\ndescribe('Widget', () => {\n  it('Check if widget button are shown', () => {\n    cy.visit(APP_URL)\n    cy.wait(2000)\n    cy.get('#widget-open-button')\n  })\n})\n"
  },
  {
    "path": "widget/tests/e2e/support/commands.js",
    "content": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n//\n//\n// -- This is a parent command --\n// Cypress.Commands.add(\"login\", (email, password) => { ... })\n//\n//\n// -- This is a child command --\n// Cypress.Commands.add(\"drag\", { prevSubject: 'element'}, (subject, options) => { ... })\n//\n//\n// -- This is a dual command --\n// Cypress.Commands.add(\"dismiss\", { prevSubject: 'optional'}, (subject, options) => { ... })\n//\n//\n// -- This is will overwrite an existing command --\n// Cypress.Commands.overwrite(\"visit\", (originalFn, url, options) => { ... })\n"
  },
  {
    "path": "widget/tests/e2e/support/index.js",
    "content": "// ***********************************************************\n// This example support/index.js is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\nimport './commands'\n\n// Alternatively you can use CommonJS syntax:\n// require('./commands')\n"
  },
  {
    "path": "widget/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"module\": \"esnext\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"importHelpers\": true,\n    \"moduleResolution\": \"node\",\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"noImplicitAny\": false,\n    \"sourceMap\": true,\n    \"baseUrl\": \".\",\n    \"types\": [\n      \"webpack-env\",\n      \"jest\"\n    ],\n    \"paths\": {\n      \"@/*\": [\n        \"src/*\"\n      ]\n    },\n    \"lib\": [\n      \"esnext\",\n      \"dom\",\n      \"dom.iterable\",\n      \"scripthost\"\n    ]\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.tsx\",\n    \"src/**/*.vue\",\n    \"tests/**/*.ts\",\n    \"tests/**/*.tsx\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  }
]