diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment.js b/app/assets/javascripts/visual_review_toolbar/components/comment.js
new file mode 100644
index 0000000000000000000000000000000000000000..3ad99732c4c3bce4a23f0aa4fe44fd040b510fe2
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/comment.js
@@ -0,0 +1,127 @@
+import { BLACK, COMMENT_BOX, MUTED, LOGOUT } from './constants';
+import { clearNote, note, postError } from './note';
+import { buttonClearStyles, selectCommentBox, selectCommentButton, selectNote } from './utils';
+
+const comment = `
+  <div>
+    <textarea id="${COMMENT_BOX}" name="${COMMENT_BOX}" rows="3" placeholder="Enter your feedback or idea" class="gitlab-input" aria-required="true"></textarea>
+    ${note}
+    <p class="gitlab-metadata-note">Additional metadata will be included: browser, OS, current page, user agent, and viewport dimensions.</p>
+  </div>
+  <div class="gitlab-button-wrapper">
+    <button class="gitlab-button gitlab-button-secondary" style="${buttonClearStyles}" type="button" id="${LOGOUT}"> Logout </button>
+    <button class="gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="gitlab-comment-button"> Send feedback </button>
+  </div>
+`;
+
+const resetCommentBox = () => {
+  const commentBox = selectCommentBox();
+  const commentButton = selectCommentButton();
+
+  commentButton.innerText = 'Send feedback';
+  commentButton.classList.replace('gitlab-button-secondary', 'gitlab-button-success');
+  commentButton.style.opacity = 1;
+
+  commentBox.style.pointerEvents = 'auto';
+  commentBox.style.color = BLACK;
+};
+
+const resetCommentButton = () => {
+  const commentBox = selectCommentBox();
+  const currentNote = selectNote();
+
+  commentBox.value = '';
+  currentNote.innerText = '';
+};
+
+const resetComment = () => {
+  resetCommentBox();
+  resetCommentButton();
+};
+
+const confirmAndClear = mergeRequestId => {
+  const commentButton = selectCommentButton();
+  const currentNote = selectNote();
+
+  commentButton.innerText = 'Feedback sent';
+  currentNote.innerText = `Your comment was successfully posted to merge request #${mergeRequestId}`;
+  setTimeout(resetComment, 2000);
+};
+
+const setInProgressState = () => {
+  const commentButton = selectCommentButton();
+  const commentBox = selectCommentBox();
+
+  commentButton.innerText = 'Sending feedback';
+  commentButton.classList.replace('gitlab-button-success', 'gitlab-button-secondary');
+  commentButton.style.opacity = 0.5;
+  commentBox.style.color = MUTED;
+  commentBox.style.pointerEvents = 'none';
+};
+
+const postComment = ({
+  href,
+  platform,
+  browser,
+  userAgent,
+  innerWidth,
+  innerHeight,
+  projectId,
+  mergeRequestId,
+  mrUrl,
+  token,
+}) => {
+  // Clear any old errors
+  clearNote(COMMENT_BOX);
+
+  setInProgressState();
+
+  const commentText = selectCommentBox().value.trim();
+
+  if (!commentText) {
+    postError('Your comment appears to be empty.', COMMENT_BOX);
+    resetCommentBox();
+    return;
+  }
+
+  const detailText = `
+ \n
+<details>
+  <summary>Metadata</summary>
+  Posted from ${href} | ${platform} | ${browser} | ${innerWidth} x ${innerHeight}.
+  <br /><br />
+  <em>User agent: ${userAgent}</em>
+</details>
+  `;
+
+  const url = `
+    ${mrUrl}/api/v4/projects/${projectId}/merge_requests/${mergeRequestId}/discussions`;
+
+  const body = `${commentText} ${detailText}`;
+
+  fetch(url, {
+    method: 'POST',
+    headers: {
+      'PRIVATE-TOKEN': token,
+      'Content-Type': 'application/json',
+    },
+    body: JSON.stringify({ body }),
+  })
+    .then(response => {
+      if (response.ok) {
+        confirmAndClear(mergeRequestId);
+        return;
+      }
+
+      throw new Error(`${response.status}: ${response.statusText}`);
+    })
+    .catch(err => {
+      postError(
+        `Your comment could not be sent. Please try again. Error: ${err.message}`,
+        COMMENT_BOX,
+      );
+      resetCommentBox();
+    });
+};
+
+export { comment, postComment };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/constants.js b/app/assets/javascripts/visual_review_toolbar/components/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..32ed115351573033808567ceb4de28a2e424684a
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/constants.js
@@ -0,0 +1,37 @@
+// component selectors
+const COLLAPSE_BUTTON = 'gitlab-collapse';
+const COMMENT_BOX = 'gitlab-comment';
+const COMMENT_BUTTON = 'gitlab-comment-button';
+const FORM = 'gitlab-form-wrapper';
+const LOGIN = 'gitlab-login';
+const LOGOUT = 'gitlab-logout-button';
+const NOTE = 'gitlab-validation-note';
+const REMEMBER_TOKEN = 'gitlab-remember_token';
+const REVIEW_CONTAINER = 'gitlab-review-container';
+const TOKEN_BOX = 'gitlab-token';
+
+// colors — these are applied programmatically
+// rest of styles belong in ./styles
+const BLACK = 'rgba(46, 46, 46, 1)';
+const CLEAR = 'rgba(255, 255, 255, 0)';
+const MUTED = 'rgba(223, 223, 223, 0.5)';
+const RED = 'rgba(219, 59, 33, 1)';
+const WHITE = 'rgba(255, 255, 255, 1)';
+
+export {
+  COLLAPSE_BUTTON,
+  COMMENT_BOX,
+  COMMENT_BUTTON,
+  FORM,
+  LOGIN,
+  LOGOUT,
+  NOTE,
+  REMEMBER_TOKEN,
+  REVIEW_CONTAINER,
+  TOKEN_BOX,
+  BLACK,
+  CLEAR,
+  MUTED,
+  RED,
+  WHITE,
+};
diff --git a/app/assets/javascripts/visual_review_toolbar/components/index.js b/app/assets/javascripts/visual_review_toolbar/components/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..435818181524e50c9e60aa8d5579a47aa1082018
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/index.js
@@ -0,0 +1,23 @@
+import { comment, postComment } from './comment';
+import { COLLAPSE_BUTTON, COMMENT_BUTTON, LOGIN, LOGOUT, REVIEW_CONTAINER } from './constants';
+import { authorizeUser, login } from './login';
+import { selectContainer } from './utils';
+import { form, logoutUser, toggleForm } from './wrapper';
+import { collapseButton } from './wrapper_icons';
+
+export {
+  authorizeUser,
+  collapseButton,
+  comment,
+  form,
+  login,
+  logoutUser,
+  postComment,
+  selectContainer,
+  toggleForm,
+  COLLAPSE_BUTTON,
+  COMMENT_BUTTON,
+  LOGIN,
+  LOGOUT,
+  REVIEW_CONTAINER,
+};
diff --git a/app/assets/javascripts/visual_review_toolbar/components/login.js b/app/assets/javascripts/visual_review_toolbar/components/login.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2fb905aca7a1313eb61793c71cdb08439582e0b
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/login.js
@@ -0,0 +1,51 @@
+import { LOGIN, REMEMBER_TOKEN, TOKEN_BOX } from './constants';
+import { clearNote, note, postError } from './note';
+import { buttonClearStyles, selectRemember, selectToken } from './utils';
+import { addCommentForm } from './wrapper';
+
+const login = `
+  <div>
+    <label for="${TOKEN_BOX}" class="gitlab-label">Enter your <a class="gitlab-link" href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">personal access token</a></label>
+    <input class="gitlab-input" type="password" id="${TOKEN_BOX}" name="${TOKEN_BOX}" aria-required="true" autocomplete="current-password">
+    ${note}
+  </div>
+  <div class="gitlab-checkbox-wrapper">
+    <input type="checkbox" id="${REMEMBER_TOKEN}" name="${REMEMBER_TOKEN}" value="remember">
+    <label for="${REMEMBER_TOKEN}" class="gitlab-checkbox-label">Remember me</label>
+  </div>
+  <div class="gitlab-button-wrapper">
+    <button class="gitlab-button-wide gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="${LOGIN}"> Submit </button>
+  </div>
+`;
+
+const storeToken = (token, state) => {
+  const { localStorage } = window;
+  const rememberMe = selectRemember().checked;
+
+  // All the browsers we support have localStorage, so let's silently fail
+  // and go on with the rest of the functionality.
+  try {
+    if (rememberMe) {
+      localStorage.setItem('token', token);
+    }
+  } finally {
+    state.token = token;
+  }
+};
+
+const authorizeUser = state => {
+  // Clear any old errors
+  clearNote(TOKEN_BOX);
+
+  const token = selectToken().value;
+
+  if (!token) {
+    postError('Please enter your token.', TOKEN_BOX);
+    return;
+  }
+
+  storeToken(token, state);
+  addCommentForm();
+};
+
+export { authorizeUser, login };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/note.js b/app/assets/javascripts/visual_review_toolbar/components/note.js
new file mode 100644
index 0000000000000000000000000000000000000000..dfebf58fd954a6efe240687e17b74b774c7f50bd
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/note.js
@@ -0,0 +1,27 @@
+import { NOTE, RED } from './constants';
+import { selectById, selectNote } from './utils';
+
+const note = `
+  <p id=${NOTE} class='gitlab-message'></p>
+`;
+
+const clearNote = inputId => {
+  const currentNote = selectNote();
+  currentNote.innerText = '';
+  currentNote.style.color = '';
+
+  if (inputId) {
+    const field = document.getElementById(inputId);
+    field.style.borderColor = '';
+  }
+};
+
+const postError = (message, inputId) => {
+  const currentNote = selectNote();
+  const field = selectById(inputId);
+  field.style.borderColor = RED;
+  currentNote.style.color = RED;
+  currentNote.innerText = message;
+};
+
+export { clearNote, note, postError };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/utils.js b/app/assets/javascripts/visual_review_toolbar/components/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..5938441cd8c261e6a75708f0d78f1f08af77eff8
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/utils.js
@@ -0,0 +1,41 @@
+/* global document */
+
+import {
+  COLLAPSE_BUTTON,
+  COMMENT_BOX,
+  COMMENT_BUTTON,
+  FORM,
+  NOTE,
+  REMEMBER_TOKEN,
+  REVIEW_CONTAINER,
+  TOKEN_BOX,
+} from './constants';
+
+// this style must be applied inline in a handful of components
+const buttonClearStyles = `
+  -webkit-appearance: none;
+`;
+
+// selector functions to abstract out a little
+const selectById = id => document.getElementById(id);
+const selectCollapseButton = () => document.getElementById(COLLAPSE_BUTTON);
+const selectCommentBox = () => document.getElementById(COMMENT_BOX);
+const selectCommentButton = () => document.getElementById(COMMENT_BUTTON);
+const selectContainer = () => document.getElementById(REVIEW_CONTAINER);
+const selectForm = () => document.getElementById(FORM);
+const selectNote = () => document.getElementById(NOTE);
+const selectRemember = () => document.getElementById(REMEMBER_TOKEN);
+const selectToken = () => document.getElementById(TOKEN_BOX);
+
+export {
+  buttonClearStyles,
+  selectById,
+  selectCollapseButton,
+  selectContainer,
+  selectCommentBox,
+  selectCommentButton,
+  selectForm,
+  selectNote,
+  selectRemember,
+  selectToken,
+};
diff --git a/app/assets/javascripts/visual_review_toolbar/components/wrapper.js b/app/assets/javascripts/visual_review_toolbar/components/wrapper.js
new file mode 100644
index 0000000000000000000000000000000000000000..233b7ec496c619f2b82cc6548055d624c6b9b6fd
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/wrapper.js
@@ -0,0 +1,82 @@
+import { comment } from './comment';
+import { CLEAR, FORM, WHITE } from './constants';
+import { login } from './login';
+import { selectCollapseButton, selectContainer, selectForm } from './utils';
+import { commentIcon, compressIcon } from './wrapper_icons';
+
+const form = content => `
+  <form id=${FORM}>
+    ${content}
+  </form>
+`;
+
+const addCommentForm = () => {
+  const formWrapper = selectForm();
+  formWrapper.innerHTML = comment;
+};
+
+const addLoginForm = () => {
+  const formWrapper = selectForm();
+  formWrapper.innerHTML = login;
+};
+
+function logoutUser() {
+  const { localStorage } = window;
+
+  // All the browsers we support have localStorage, so let's silently fail
+  // and go on with the rest of the functionality.
+  try {
+    localStorage.removeItem('token');
+  } catch (err) {
+    return;
+  }
+
+  addLoginForm();
+}
+
+function toggleForm() {
+  const container = selectContainer();
+  const collapseButton = selectCollapseButton();
+  const currentForm = selectForm();
+  const OPEN = 'open';
+  const CLOSED = 'closed';
+
+  /*
+    You may wonder why we spread the arrays before we reverse them.
+    In the immortal words of MDN,
+    Careful: reverse is destructive. It also changes the original array
+  */
+
+  const openButtonClasses = ['gitlab-collapse-closed', 'gitlab-collapse-open'];
+  const closedButtonClasses = [...openButtonClasses].reverse();
+  const openContainerClasses = ['gitlab-closed-wrapper', 'gitlab-open-wrapper'];
+  const closedContainerClasses = [...openContainerClasses].reverse();
+
+  const stateVals = {
+    [OPEN]: {
+      buttonClasses: openButtonClasses,
+      containerClasses: openContainerClasses,
+      icon: compressIcon,
+      display: 'flex',
+      backgroundColor: WHITE,
+    },
+    [CLOSED]: {
+      buttonClasses: closedButtonClasses,
+      containerClasses: closedContainerClasses,
+      icon: commentIcon,
+      display: 'none',
+      backgroundColor: CLEAR,
+    },
+  };
+
+  const nextState = collapseButton.classList.contains('gitlab-collapse-open') ? CLOSED : OPEN;
+  const currentVals = stateVals[nextState];
+
+  container.classList.replace(...currentVals.containerClasses);
+  container.style.backgroundColor = currentVals.backgroundColor;
+  currentForm.style.display = currentVals.display;
+  collapseButton.classList.replace(...currentVals.buttonClasses);
+  collapseButton.innerHTML = currentVals.icon;
+}
+
+export { addCommentForm, addLoginForm, form, logoutUser, toggleForm };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js b/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js
new file mode 100644
index 0000000000000000000000000000000000000000..b686fd4f5c2b3417149685f2ce00c25dfab70894
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js
@@ -0,0 +1,15 @@
+import { buttonClearStyles } from './utils';
+
+const commentIcon = `
+  <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/comment</title><path d="M4 11.132l1.446-.964A1 1 0 0 1 6 10h5a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v6.132zM6.303 12l-2.748 1.832A1 1 0 0 1 2 13V5a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3H6.303z" id="gitlab-comment-icon"/></svg>
+`;
+
+const compressIcon = `
+  <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/compress</title><path d="M5.27 12.182l-1.562 1.561a1 1 0 0 1-1.414 0h-.001a1 1 0 0 1 0-1.415l1.56-1.56L2.44 9.353a.5.5 0 0 1 .353-.854H7.09a.5.5 0 0 1 .5.5v4.294a.5.5 0 0 1-.853.353l-1.467-1.465zm6.911-6.914l1.464 1.464a.5.5 0 0 1-.353.854H8.999a.5.5 0 0 1-.5-.5V2.793a.5.5 0 0 1 .854-.354l1.414 1.415 1.56-1.561a1 1 0 1 1 1.415 1.414l-1.561 1.56z" id="gitlab-compress-icon"/></svg>
+`;
+
+const collapseButton = `
+  <button id='gitlab-collapse' style='${buttonClearStyles}' class='gitlab-button gitlab-button-secondary gitlab-collapse gitlab-collapse-open'>${compressIcon}</button>
+`;
+
+export { commentIcon, compressIcon, collapseButton };
diff --git a/app/assets/javascripts/visual_review_toolbar/index.js b/app/assets/javascripts/visual_review_toolbar/index.js
index 91d0382feacc19adf367c1fe92f93005e4695dd7..941d77e25b400f853daa913a6b248a1ccb1a3d28 100644
--- a/app/assets/javascripts/visual_review_toolbar/index.js
+++ b/app/assets/javascripts/visual_review_toolbar/index.js
@@ -1,2 +1,37 @@
 import './styles/toolbar.css';
-import 'vendor/visual_review_toolbar';
+
+import { form, selectContainer, REVIEW_CONTAINER } from './components';
+import { debounce, eventLookup, getInitialView, initializeState, updateWindowSize } from './store';
+
+/*
+
+  Welcome to the visual review toolbar files. A few useful notes:
+
+  - These files build a static script that is served from our webpack
+    assets folder. (https://gitlab.com/assets/webpack/visual_review_toolbar.js)
+
+  - To compile this file, run `yarn webpack-vrt`.
+
+  - Vue is not used in these files because we do not want to ask users to
+    install another library at this time. It's all pure vanilla javascript.
+
+*/
+
+window.addEventListener('load', () => {
+  initializeState(window, document);
+
+  const { content, toggleButton } = getInitialView(window);
+  const container = document.createElement('div');
+
+  container.setAttribute('id', REVIEW_CONTAINER);
+  container.insertAdjacentHTML('beforeend', toggleButton);
+  container.insertAdjacentHTML('beforeend', form(content));
+
+  document.body.insertBefore(container, document.body.firstChild);
+
+  selectContainer().addEventListener('click', event => {
+    eventLookup(event)();
+  });
+
+  window.addEventListener('resize', debounce(updateWindowSize.bind(null, window), 200));
+});
diff --git a/app/assets/javascripts/visual_review_toolbar/store/events.js b/app/assets/javascripts/visual_review_toolbar/store/events.js
new file mode 100644
index 0000000000000000000000000000000000000000..93996be847334fbe4e20c2b8edffd2f642b13da1
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/store/events.js
@@ -0,0 +1,36 @@
+import {
+  authorizeUser,
+  logoutUser,
+  postComment,
+  toggleForm,
+  COLLAPSE_BUTTON,
+  COMMENT_BUTTON,
+  LOGIN,
+  LOGOUT,
+} from '../components';
+
+import { state } from './state';
+
+const noop = () => {};
+
+const eventLookup = ({ target: { id } }) => {
+  switch (id) {
+    case COLLAPSE_BUTTON:
+      return toggleForm;
+    case COMMENT_BUTTON:
+      return postComment.bind(null, state);
+    case LOGIN:
+      return authorizeUser.bind(null, state);
+    case LOGOUT:
+      return logoutUser;
+    default:
+      return noop;
+  }
+};
+
+const updateWindowSize = wind => {
+  state.innerWidth = wind.innerWidth;
+  state.innerHeight = wind.innerHeight;
+};
+
+export { eventLookup, updateWindowSize };
diff --git a/app/assets/javascripts/visual_review_toolbar/store/index.js b/app/assets/javascripts/visual_review_toolbar/store/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..7143588c0bfc87438a37c593a104f37fa22f3bc8
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/store/index.js
@@ -0,0 +1,5 @@
+import { eventLookup, updateWindowSize } from './events';
+import { getInitialView, initializeState } from './state';
+import debounce from './utils';
+
+export { debounce, eventLookup, getInitialView, initializeState, updateWindowSize };
diff --git a/app/assets/javascripts/visual_review_toolbar/store/state.js b/app/assets/javascripts/visual_review_toolbar/store/state.js
new file mode 100644
index 0000000000000000000000000000000000000000..b4e239a95867161e3ca3419ed81e0519048f71f5
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/store/state.js
@@ -0,0 +1,76 @@
+import { comment, login, collapseButton } from '../components';
+
+const state = {
+  browser: '',
+  href: '',
+  innerWidth: '',
+  innerHeight: '',
+  mergeRequestId: '',
+  mrUrl: '',
+  platform: '',
+  projectId: '',
+  userAgent: '',
+  token: '',
+};
+
+// adapted from https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator#Example_2_Browser_detect_and_return_an_index
+const getBrowserId = sUsrAg => {
+  const aKeys = ['MSIE', 'Edge', 'Firefox', 'Safari', 'Chrome', 'Opera'];
+  let nIdx = aKeys.length - 1;
+
+  for (nIdx; nIdx > -1 && sUsrAg.indexOf(aKeys[nIdx]) === -1; nIdx -= 1);
+  return aKeys[nIdx];
+};
+
+const initializeState = (wind, doc) => {
+  const {
+    innerWidth,
+    innerHeight,
+    location: { href },
+    navigator: { platform, userAgent },
+  } = wind;
+
+  const browser = getBrowserId(userAgent);
+
+  const scriptEl = doc.getElementById('review-app-toolbar-script');
+  const { projectId, mergeRequestId, mrUrl } = scriptEl.dataset;
+
+  // This mutates our default state object above. It's weird but it makes the linter happy.
+  Object.assign(state, {
+    browser,
+    href,
+    innerWidth,
+    innerHeight,
+    mergeRequestId,
+    mrUrl,
+    platform,
+    projectId,
+    userAgent,
+  });
+};
+
+function getInitialView({ localStorage }) {
+  const loginView = {
+    content: login,
+    toggleButton: collapseButton,
+  };
+
+  const commentView = {
+    content: comment,
+    toggleButton: collapseButton,
+  };
+
+  try {
+    const token = localStorage.getItem('token');
+
+    if (token) {
+      state.token = token;
+      return commentView;
+    }
+    return loginView;
+  } catch (err) {
+    return loginView;
+  }
+}
+
+export { initializeState, getInitialView, state };
diff --git a/app/assets/javascripts/visual_review_toolbar/store/utils.js b/app/assets/javascripts/visual_review_toolbar/store/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..5cf145351b3490f16b9fd6beea4e8d7c4739c757
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/store/utils.js
@@ -0,0 +1,15 @@
+const debounce = (fn, time) => {
+  let current;
+
+  const debounced = () => {
+    if (current) {
+      clearTimeout(current);
+    }
+
+    current = setTimeout(fn, time);
+  };
+
+  return debounced;
+};
+
+export default debounce;
diff --git a/vendor/assets/javascripts/visual_review_toolbar.js b/vendor/assets/javascripts/visual_review_toolbar.js
deleted file mode 100644
index 12a3a4c967251b40158af038a72a03e906767ad6..0000000000000000000000000000000000000000
--- a/vendor/assets/javascripts/visual_review_toolbar.js
+++ /dev/null
@@ -1,377 +0,0 @@
-///////////////////////////////////////////////
-/////////////////// STYLES ////////////////////
-///////////////////////////////////////////////
-
-// this style must be applied inline
-const buttonClearStyles = `
-  -webkit-appearance: none;
-`;
-
-///////////////////////////////////////////////
-/////////////////// STATE ////////////////////
-///////////////////////////////////////////////
-const data = {};
-
-///////////////////////////////////////////////
-///////////////// COMPONENTS //////////////////
-///////////////////////////////////////////////
-const note = `
-  <p id='gitlab-validation-note' class='gitlab-message'></p>
-`;
-
-const comment = `
-  <div>
-    <textarea id='gitlab-comment' name='gitlab-comment' rows='3' placeholder='Enter your feedback or idea' class='gitlab-input'></textarea>
-    ${note}
-    <p class='gitlab-metadata-note'>Additional metadata will be included: browser, OS, current page, user agent, and viewport dimensions.</p>
-  </div>
-  <div class='gitlab-button-wrapper''>
-    <button class='gitlab-button gitlab-button-secondary' style='${buttonClearStyles}' type='button' id='gitlab-logout-button'> Logout </button>
-    <button class='gitlab-button gitlab-button-success' style='${buttonClearStyles}' type='button' id='gitlab-comment-button'> Send feedback </button>
-  </div>
-`;
-
-const commentIcon = `
-  <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/comment</title><path d="M4 11.132l1.446-.964A1 1 0 0 1 6 10h5a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v6.132zM6.303 12l-2.748 1.832A1 1 0 0 1 2 13V5a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3H6.303z" id="gitlab-comment-icon"/></svg>
-`;
-
-const compressIcon = `
-  <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/compress</title><path d="M5.27 12.182l-1.562 1.561a1 1 0 0 1-1.414 0h-.001a1 1 0 0 1 0-1.415l1.56-1.56L2.44 9.353a.5.5 0 0 1 .353-.854H7.09a.5.5 0 0 1 .5.5v4.294a.5.5 0 0 1-.853.353l-1.467-1.465zm6.911-6.914l1.464 1.464a.5.5 0 0 1-.353.854H8.999a.5.5 0 0 1-.5-.5V2.793a.5.5 0 0 1 .854-.354l1.414 1.415 1.56-1.561a1 1 0 1 1 1.415 1.414l-1.561 1.56z" id="gitlab-compress-icon"/></svg>
-`;
-
-const collapseButton = `
-  <button id='gitlab-collapse' style='${buttonClearStyles}' class='gitlab-button gitlab-button-secondary gitlab-collapse gitlab-collapse-open'>${compressIcon}</button>
-`;
-
-const form = content => `
-  <div id='gitlab-form-wrapper'>
-    ${content}
-  </div>
-`;
-
-const login = `
-  <div>
-    <label for='gitlab-token' class='gitlab-label'>Enter your <a class='gitlab-link' href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">personal access token</a></label>
-    <input class='gitlab-input' type='password' id='gitlab-token' name='gitlab-token'>
-    ${note}
-  </div>
-  <div class='gitlab-checkbox-wrapper'>
-    <input type="checkbox" id="remember_token" name="remember_token" value="remember">
-    <label for="remember_token" class='gitlab-checkbox-label'>Remember me</label>
-  </div>
-  <div class='gitlab-button-wrapper'>
-    <button class='gitlab-button-wide gitlab-button gitlab-button-success' style='${buttonClearStyles}' type='button' id='gitlab-login'> Submit </button>
-  </div>
-`;
-
-///////////////////////////////////////////////
-//////////////// INTERACTIONS /////////////////
-///////////////////////////////////////////////
-
-// from https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator
-function getBrowserId(sUsrAg) {
-  var aKeys = ['MSIE', 'Edge', 'Firefox', 'Safari', 'Chrome', 'Opera'],
-    nIdx = aKeys.length - 1;
-
-  for (nIdx; nIdx > -1 && sUsrAg.indexOf(aKeys[nIdx]) === -1; nIdx--);
-  return aKeys[nIdx];
-}
-
-function addCommentForm() {
-  const formWrapper = document.getElementById('gitlab-form-wrapper');
-  formWrapper.innerHTML = comment;
-}
-
-function addLoginForm() {
-  const formWrapper = document.getElementById('gitlab-form-wrapper');
-  formWrapper.innerHTML = login;
-}
-
-function authorizeUser() {
-  // Clear any old errors
-  clearNote('gitlab-token');
-
-  const token = document.getElementById('gitlab-token').value;
-  const rememberMe = document.getElementById('remember_token').checked;
-
-  if (!token) {
-    postError('Please enter your token.', 'gitlab-token');
-    return;
-  }
-
-  if (rememberMe) {
-    storeToken(token);
-  }
-
-  authSuccess(token);
-  return;
-}
-
-function authSuccess(token) {
-  data.token = token;
-  addCommentForm();
-}
-
-function clearNote(inputId) {
-  const note = document.getElementById('gitlab-validation-note');
-  note.innerText = '';
-  note.style.color = '';
-
-  if (inputId) {
-    const field = document.getElementById(inputId);
-    field.style.borderColor = '';
-  }
-}
-
-function confirmAndClear(mergeRequestId) {
-  const commentButton = document.getElementById('gitlab-comment-button');
-  const note = document.getElementById('gitlab-validation-note');
-
-  commentButton.innerText = 'Feedback sent';
-  note.innerText = `Your comment was successfully posted to merge request #${mergeRequestId}`;
-
-  setTimeout(resetCommentButton, 1000);
-}
-
-function getInitialState() {
-  const { localStorage } = window;
-
-  try {
-    let token = localStorage.getItem('token');
-
-    if (token) {
-      data.token = token;
-      return comment;
-    }
-
-    return login;
-  } catch (err) {
-    return login;
-  }
-}
-
-function getProjectDetails() {
-  const {
-    innerWidth,
-    innerHeight,
-    location: { href },
-    navigator: { platform, userAgent },
-  } = window;
-  const browser = getBrowserId(userAgent);
-
-  const scriptEl = document.getElementById('review-app-toolbar-script');
-  const { projectId, mergeRequestId, mrUrl } = scriptEl.dataset;
-
-  return {
-    href,
-    platform,
-    browser,
-    userAgent,
-    innerWidth,
-    innerHeight,
-    projectId,
-    mergeRequestId,
-    mrUrl,
-  };
-}
-
-function logoutUser() {
-  const { localStorage } = window;
-
-  // All the browsers we support have localStorage, so let's silently fail
-  // and go on with the rest of the functionality.
-  try {
-    localStorage.removeItem('token');
-  } catch (err) {
-    return;
-  }
-
-  addLoginForm();
-}
-
-function postComment({
-  href,
-  platform,
-  browser,
-  userAgent,
-  innerWidth,
-  innerHeight,
-  projectId,
-  mergeRequestId,
-  mrUrl,
-}) {
-  // Clear any old errors
-  clearNote('gitlab-comment');
-
-  setInProgressState();
-
-  const commentText = document.getElementById('gitlab-comment').value.trim();
-
-  if (!commentText) {
-    postError('Your comment appears to be empty.', 'gitlab-comment');
-    resetCommentBox();
-    return;
-  }
-
-  const detailText = `
- \n
-<details>
-  <summary>Metadata</summary>
-  Posted from ${href} | ${platform} | ${browser} | ${innerWidth} x ${innerHeight}.
-  <br /><br />
-  <em>User agent: ${userAgent}</em>
-</details>
-  `;
-
-  const url = `
-    ${mrUrl}/api/v4/projects/${projectId}/merge_requests/${mergeRequestId}/discussions`;
-
-  const body = `${commentText} ${detailText}`;
-
-  fetch(url, {
-    method: 'POST',
-    headers: {
-      'PRIVATE-TOKEN': data.token,
-      'Content-Type': 'application/json',
-    },
-    body: JSON.stringify({ body }),
-  })
-    .then(response => {
-      if (response.ok) {
-        confirmAndClear(mergeRequestId);
-        return;
-      }
-
-      throw new Error(`${response.status}: ${response.statusText}`);
-    })
-    .catch(err => {
-      postError(
-        `The feedback was not sent successfully. Please try again. Error: ${err.message}`,
-        'gitlab-comment',
-      );
-      resetCommentBox();
-    });
-}
-
-function postError(message, inputId) {
-  const note = document.getElementById('gitlab-validation-note');
-  const field = document.getElementById(inputId);
-  field.style.borderColor = '#db3b21';
-  note.style.color = '#db3b21';
-  note.innerText = message;
-}
-
-function resetCommentBox() {
-  const commentBox = document.getElementById('gitlab-comment');
-  const commentButton = document.getElementById('gitlab-comment-button');
-
-  commentButton.innerText = 'Send feedback';
-  commentButton.classList.replace('gitlab-button-secondary', 'gitlab-button-success');
-  commentButton.style.opacity = 1;
-
-  commentBox.style.pointerEvents = 'auto';
-  commentBox.style.color = 'rgba(0, 0, 0, 1)';
-}
-
-function resetCommentButton() {
-  const commentBox = document.getElementById('gitlab-comment');
-  const note = document.getElementById('gitlab-validation-note');
-
-  commentBox.value = '';
-  note.innerText = '';
-  resetCommentBox();
-}
-
-function setInProgressState() {
-  const commentButton = document.getElementById('gitlab-comment-button');
-  const commentBox = document.getElementById('gitlab-comment');
-
-  commentButton.innerText = 'Sending feedback';
-  commentButton.classList.replace('gitlab-button-success', 'gitlab-button-secondary');
-  commentButton.style.opacity = 0.5;
-  commentBox.style.color = 'rgba(223, 223, 223, 0.5)';
-  commentBox.style.pointerEvents = 'none';
-}
-
-function storeToken(token) {
-  const { localStorage } = window;
-
-  // All the browsers we support have localStorage, so let's silently fail
-  // and go on with the rest of the functionality.
-  try {
-    localStorage.setItem('token', token);
-  } catch (err) {
-    return;
-  }
-}
-
-function toggleForm() {
-  const container = document.getElementById('gitlab-review-container');
-  const collapseButton = document.getElementById('gitlab-collapse');
-  const form = document.getElementById('gitlab-form-wrapper');
-  const OPEN = 'open';
-  const CLOSED = 'closed';
-
-  const stateVals = {
-    [OPEN]: {
-      buttonClasses: ['gitlab-collapse-closed', 'gitlab-collapse-open'],
-      containerClasses: ['gitlab-closed-wrapper', 'gitlab-open-wrapper'],
-      icon: compressIcon,
-      display: 'flex',
-      backgroundColor: 'rgba(255, 255, 255, 1)',
-    },
-    [CLOSED]: {
-      buttonClasses: ['gitlab-collapse-open', 'gitlab-collapse-closed'],
-      containerClasses: ['gitlab-open-wrapper', 'gitlab-closed-wrapper'],
-      icon: commentIcon,
-      display: 'none',
-      backgroundColor: 'rgba(255, 255, 255, 0)',
-    },
-  };
-
-  const nextState = collapseButton.classList.contains('gitlab-collapse-open') ? CLOSED : OPEN;
-
-  container.classList.replace(...stateVals[nextState].containerClasses);
-  container.style.backgroundColor = stateVals[nextState].backgroundColor;
-  form.style.display = stateVals[nextState].display;
-  collapseButton.classList.replace(...stateVals[nextState].buttonClasses);
-  collapseButton.innerHTML = stateVals[nextState].icon;
-}
-
-///////////////////////////////////////////////
-///////////////// INJECTION //////////////////
-///////////////////////////////////////////////
-
-function noop() {}
-
-const eventLookup = ({ target: { id } }) => {
-  switch (id) {
-    case 'gitlab-collapse':
-      return toggleForm;
-    case 'gitlab-comment-button':
-      const projectDetails = getProjectDetails();
-      return postComment.bind(null, projectDetails);
-    case 'gitlab-login':
-      return authorizeUser;
-    case 'gitlab-logout-button':
-      return logoutUser;
-    default:
-      return noop;
-  }
-};
-
-window.addEventListener('load', () => {
-  const content = getInitialState();
-  const container = document.createElement('div');
-
-  container.setAttribute('id', 'gitlab-review-container');
-  container.insertAdjacentHTML('beforeend', collapseButton);
-  container.insertAdjacentHTML('beforeend', form(content));
-
-  document.body.insertBefore(container, document.body.firstChild);
-
-  document.getElementById('gitlab-review-container').addEventListener('click', event => {
-    eventLookup(event)();
-  });
-  
-});