diff --git a/.gitignore b/.gitignore
index 028a97f09..74f2d2ff1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,4 +75,12 @@ yalc.lock
yarn-debug.log*
.yarn-integrity
-/storybook-static
\ No newline at end of file
+/storybook-static
+
+# Vite Ruby
+/public/vite*
+node_modules
+# Vite uses dotenv and suggests to ignore local-only env files. See
+# https://vitejs.dev/guide/env-and-mode.html#env-files
+*.local
+
diff --git a/Gemfile b/Gemfile
index 144240b62..4f972a33d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -62,7 +62,7 @@ gem 'activerecord-import'
gem 'dotenv-rails'
gem 'foreman'
gem 'puma'
-gem 'webpacker'
+gem 'vite_rails'
# metrics on heroku
gem 'barnes'
diff --git a/Gemfile.lock b/Gemfile.lock
index 62c6829ed..54b60dc76 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -216,6 +216,7 @@ GEM
railties (>= 3.2)
down (5.4.0)
addressable (~> 2.8)
+ dry-cli (1.0.0)
ecma-re-validator (0.4.0)
regexp_parser (~> 2.2)
elastic-apm (4.6.2)
@@ -570,7 +571,7 @@ GEM
rack (>= 1.2.0)
rack-protection (3.1.0)
rack (~> 2.2, >= 2.2.4)
- rack-proxy (0.7.6)
+ rack-proxy (0.7.7)
rack
rack-test (2.1.0)
rack (>= 1.3)
@@ -708,7 +709,6 @@ GEM
activerecord (>= 4)
activesupport (>= 4)
selectize-rails (0.12.6)
- semantic_range (3.0.0)
sentry-rails (5.14.0)
railties (>= 5.0)
sentry-ruby (~> 5.14.0)
@@ -795,6 +795,13 @@ GEM
activemodel (>= 3.2)
mail (~> 2.5)
version_gem (1.1.2)
+ vite_rails (3.0.17)
+ railties (>= 5.1, < 8)
+ vite_ruby (~> 3.0, >= 3.2.2)
+ vite_ruby (3.5.0)
+ dry-cli (>= 0.7, < 2)
+ rack-proxy (~> 0.6, >= 0.6.1)
+ zeitwerk (~> 2.2)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1)
@@ -810,11 +817,6 @@ GEM
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
- webpacker (5.4.4)
- activesupport (>= 5.2)
- rack-proxy (>= 0.6.1)
- railties (>= 5.2)
- semantic_range (>= 2.3.0)
webrick (1.8.1)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
@@ -956,10 +958,10 @@ DEPENDENCIES
tzinfo-data
uglifier
valid_email2
+ vite_rails
web-console (>= 4.2.1)
web-push
webmock
- webpacker
wisper (= 2.0.0)
working_hours
diff --git a/Procfile.dev b/Procfile.dev
index 73c2af7e8..df7db5d8c 100644
--- a/Procfile.dev
+++ b/Procfile.dev
@@ -1,4 +1,4 @@
backend: bin/rails s -p 3000
-frontend: export NODE_OPTIONS=--openssl-legacy-provider && bin/webpack-dev-server
# https://github.com/mperham/sidekiq/issues/3090#issuecomment-389748695
worker: dotenv bundle exec sidekiq -C config/sidekiq.yml
+frontend: bin/vite dev
diff --git a/app/javascript/dashboard/assets/scss/_date-picker.scss b/app/javascript/dashboard/assets/scss/_date-picker.scss
index 2132d5fd5..a60785e88 100644
--- a/app/javascript/dashboard/assets/scss/_date-picker.scss
+++ b/app/javascript/dashboard/assets/scss/_date-picker.scss
@@ -1,4 +1,4 @@
-@import '~vue2-datepicker/scss/index';
+@import 'vue2-datepicker/scss/index';
.date-picker {
&.no-margin {
diff --git a/app/javascript/dashboard/assets/scss/_formulate.scss b/app/javascript/dashboard/assets/scss/_formulate.scss
index d683ce3c7..37690a8de 100644
--- a/app/javascript/dashboard/assets/scss/_formulate.scss
+++ b/app/javascript/dashboard/assets/scss/_formulate.scss
@@ -1,4 +1,4 @@
-@import '~dashboard/assets/scss/variables';
+@import 'dashboard/assets/scss/variables';
.formulate-input {
.formulate-input-errors {
diff --git a/app/javascript/dashboard/assets/scss/_foundation-settings.scss b/app/javascript/dashboard/assets/scss/_foundation-settings.scss
index 3fd834cdd..8f6731993 100644
--- a/app/javascript/dashboard/assets/scss/_foundation-settings.scss
+++ b/app/javascript/dashboard/assets/scss/_foundation-settings.scss
@@ -41,7 +41,7 @@
// 36. Tooltip
// 37. Top Bar
-@import '~foundation-sites/scss/util/util';
+@import 'foundation-sites/scss/util/util';
// 1. Global
// ---------
@@ -51,11 +51,13 @@ $contrast-warnings: false;
$global-font-size: 16px;
$global-width: 100%;
$global-lineheight: 1.5;
-$foundation-palette: (primary: $color-woot,
+$foundation-palette: (
+ primary: $color-woot,
secondary: #5d7592,
success: #44ce4b,
warning: #ffc532,
- alert: #ff382d);
+ alert: #ff382d
+);
$light-gray: #c0ccda;
$medium-gray: #8492a6;
$dark-gray: $color-gray;
@@ -64,15 +66,15 @@ $white: #fff;
$body-background: $white;
$body-font-color: $color-body;
$body-font-family: 'PlusJakarta',
--apple-system,
-system-ui,
-BlinkMacSystemFont,
-"Segoe UI",
-Roboto,
-"Helvetica Neue",
-Tahoma,
-Arial,
-sans-serif;
+ -apple-system,
+ system-ui,
+ BlinkMacSystemFont,
+ "Segoe UI",
+ Roboto,
+ "Helvetica Neue",
+ Tahoma,
+ Arial,
+ sans-serif;
$body-antialiased: true;
$global-margin: $space-small;
$global-padding: $space-small;
@@ -88,7 +90,8 @@ $print-transparent-backgrounds: true;
// 2. Breakpoints
// --------------
-$breakpoints: (small: 0,
+$breakpoints: (
+ small: 0,
medium: 640px,
large: 1024px,
xlarge: 1200px,
@@ -96,15 +99,19 @@ $breakpoints: (small: 0,
xxxlarge: 1600px,
);
$print-breakpoint: large;
-$breakpoint-classes: (small medium large);
+$breakpoint-classes: (
+ small medium large
+);
// 3. The Grid
// -----------
$grid-row-width: $global-width;
$grid-column-count: 12;
-$grid-column-gutter: (small: $zero,
- medium: $zero);
+$grid-column-gutter: (
+ small: $zero,
+ medium: $zero
+);
$grid-column-align-edge: true;
$block-grid-max: 8;
@@ -118,7 +125,8 @@ $font-family-monospace: $body-font-family;
$header-color: $color-heading;
$header-lineheight: 1.4;
$header-margin-bottom: 0.3125rem;
-$header-styles: (small: ("h1": ("font-size": 24),
+$header-styles: (
+ small: ("h1": ("font-size": 24),
"h2": ("font-size": 20),
"h3": ("font-size": 19),
"h4": ("font-size": 18),
@@ -129,7 +137,8 @@ $header-styles: (small: ("h1": ("font-size": 24),
"h3": ("font-size": 31),
"h4": ("font-size": 25),
"h5": ("font-size": 20),
- "h6": ("font-size": 16)));
+ "h6": ("font-size": 16))
+);
$header-text-rendering: optimizeLegibility;
$small-font-size: 80%;
$header-small-font-color: $medium-gray;
@@ -249,16 +258,18 @@ $button-background-hover: scale-color($button-background, $lightness: -15%);
$button-color: $white;
$button-color-alt: $white;
$button-radius: var(--border-radius-normal);
-$button-sizes: (tiny: var(--font-size-micro),
+$button-sizes: (
+ tiny: var(--font-size-micro),
small: var(--font-size-mini),
default: var(--font-size-small),
- large: var(--font-size-medium));
+ large: var(--font-size-medium)
+);
$button-palette: $foundation-palette;
$button-opacity-disabled: 0.4;
$button-background-hover-lightness: -20%;
$button-hollow-hover-lightness: -50%;
$button-transition: background-color 0.25s ease-out,
-color 0.25s ease-out;
+ color 0.25s ease-out;
// 12. Button Group
// ----------------
@@ -298,12 +309,18 @@ $card-margin: $global-margin;
// ----------------
$closebutton-position: right top;
-$closebutton-offset-horizontal: (small: 0.66rem,
- medium: 1rem);
-$closebutton-offset-vertical: (small: 0.33em,
- medium: 0.5rem);
-$closebutton-size: (small: 1.5em,
- medium: 2em);
+$closebutton-offset-horizontal: (
+ small: 0.66rem,
+ medium: 1rem
+);
+$closebutton-offset-vertical: (
+ small: 0.33em,
+ medium: 0.5rem
+);
+$closebutton-size: (
+ small: 1.5em,
+ medium: 2em
+);
$closebutton-lineheight: 1;
$closebutton-color: $dark-gray;
$closebutton-color-hover: $black;
@@ -326,9 +343,11 @@ $dropdown-border: 1px solid $medium-gray;
$dropdown-font-size: 0.625rem;
$dropdown-width: 300px;
$dropdown-radius: $global-radius;
-$dropdown-sizes: (tiny: 100px,
+$dropdown-sizes: (
+ tiny: 100px,
small: 200px,
- large: 400px);
+ large: 400px
+);
// 18. Dropdown Menu
// -----------------
@@ -423,10 +442,12 @@ $meter-fill-bad: $alert-color;
// 24. Off-canvas
// --------------
-$offcanvas-sizes: (small: 14.375,
+$offcanvas-sizes: (
+ small: 14.375,
medium: 14.375,
);
-$offcanvas-vertical-sizes: (small: 14.375,
+$offcanvas-vertical-sizes: (
+ small: 14.375,
medium: 14.375,
);
$offcanvas-background: $light-gray;
@@ -486,8 +507,10 @@ $progress-radius: $global-radius;
// --------------------
$responsive-embed-margin-bottom: rem-calc(16);
-$responsive-embed-ratios: (default: 4 by 3,
- widescreen: 16 by 9);
+$responsive-embed-ratios: (
+ default: 4 by 3,
+ widescreen: 16 by 9
+);
// 29. Reveal
// ----------
@@ -541,7 +564,7 @@ $table-padding: rem-calc(8 10 10);
$table-hover-scale: 2%;
$table-row-hover: darken($table-background, $table-hover-scale);
$table-row-stripe-hover: darken($table-background,
- $table-color-scale + $table-hover-scale);
+ $table-color-scale + $table-hover-scale);
$table-is-striped: false;
$table-striped-background: smart-scale($table-background, $table-color-scale);
$table-stripe: even;
diff --git a/app/javascript/dashboard/assets/scss/_mixins.scss b/app/javascript/dashboard/assets/scss/_mixins.scss
index 2fbb9fa7c..9ca87c859 100644
--- a/app/javascript/dashboard/assets/scss/_mixins.scss
+++ b/app/javascript/dashboard/assets/scss/_mixins.scss
@@ -1,5 +1,5 @@
-@import '~dashboard/assets/scss/variables';
-@import '~widget/assets/scss/mixins';
+@import 'dashboard/assets/scss/variables';
+@import 'widget/assets/scss/mixins';
$spinner-before-border-color: rgba(255, 255, 255, 0.7);
diff --git a/app/javascript/dashboard/assets/scss/_woot.scss b/app/javascript/dashboard/assets/scss/_woot.scss
index 2773ced6f..566d79c7a 100644
--- a/app/javascript/dashboard/assets/scss/_woot.scss
+++ b/app/javascript/dashboard/assets/scss/_woot.scss
@@ -69,7 +69,7 @@
@import 'plugins/multiselect';
@import 'plugins/dropdown';
-@import '~shared/assets/stylesheets/ionicons';
+@import 'shared/assets/stylesheets/ionicons';
@import 'utility-helpers';
.tooltip {
diff --git a/app/javascript/dashboard/assets/scss/storybook.scss b/app/javascript/dashboard/assets/scss/storybook.scss
index 457560aa9..a5f9692f2 100644
--- a/app/javascript/dashboard/assets/scss/storybook.scss
+++ b/app/javascript/dashboard/assets/scss/storybook.scss
@@ -10,7 +10,7 @@
@import 'variables';
@import 'vue-multiselect/dist/vue-multiselect.min.css';
-@import '~shared/assets/stylesheets/ionicons';
+@import 'shared/assets/stylesheets/ionicons';
@import 'mixins';
@import 'foundation-settings';
diff --git a/app/javascript/dashboard/components/index.js b/app/javascript/dashboard/components/index.js
index ee06ebc9a..7501306ea 100644
--- a/app/javascript/dashboard/components/index.js
+++ b/app/javascript/dashboard/components/index.js
@@ -1,29 +1,29 @@
/* eslint no-plusplus: 0 */
import AvatarUploader from './widgets/forms/AvatarUploader.vue';
import Bar from './widgets/chart/BarChart';
-import Button from './ui/WootButton';
-import Code from './Code';
-import ColorPicker from './widgets/ColorPicker';
+import Button from './ui/WootButton.vue';
+import Code from './Code.vue';
+import ColorPicker from './widgets/ColorPicker.vue';
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
import ContextMenu from './ui/ContextMenu.vue';
import DeleteModal from './widgets/modal/DeleteModal.vue';
-import DropdownItem from 'shared/components/ui/dropdown/DropdownItem';
-import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
-import FeatureToggle from './widgets/FeatureToggle';
+import DropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
+import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
+import FeatureToggle from './widgets/FeatureToggle.vue';
import HorizontalBar from './widgets/chart/HorizontalBarChart';
import Input from './widgets/forms/Input.vue';
import PhoneInput from './widgets/forms/PhoneInput.vue';
-import Label from './ui/Label';
-import LoadingState from './widgets/LoadingState';
-import Modal from './Modal';
-import ModalHeader from './ModalHeader';
-import ReportStatsCard from './widgets/ReportStatsCard';
-import SidemenuIcon from './SidemenuIcon';
-import Spinner from 'shared/components/Spinner';
-import SubmitButton from './buttons/FormSubmitButton';
-import Tabs from './ui/Tabs/Tabs';
-import TabsItem from './ui/Tabs/TabsItem';
+import Label from './ui/Label.vue';
+import LoadingState from './widgets/LoadingState.vue';
+import Modal from './Modal.vue';
+import ModalHeader from './ModalHeader.vue';
+import ReportStatsCard from './widgets/ReportStatsCard.vue';
+import SidemenuIcon from './SidemenuIcon.vue';
+import Spinner from 'shared/components/Spinner.vue';
+import SubmitButton from './buttons/FormSubmitButton.vue';
+import Tabs from './ui/Tabs/Tabs.jsx';
+import TabsItem from './ui/Tabs/TabsItem.vue';
import Thumbnail from './widgets/Thumbnail.vue';
const WootUIKit = {
diff --git a/app/javascript/dashboard/components/ui/Tabs/Tabs.js b/app/javascript/dashboard/components/ui/Tabs/Tabs.jsx
similarity index 100%
rename from app/javascript/dashboard/components/ui/Tabs/Tabs.js
rename to app/javascript/dashboard/components/ui/Tabs/Tabs.jsx
diff --git a/app/javascript/dashboard/components/widgets/ColorPicker.vue b/app/javascript/dashboard/components/widgets/ColorPicker.vue
index 8e8ac89b5..b5a497173 100644
--- a/app/javascript/dashboard/components/widgets/ColorPicker.vue
+++ b/app/javascript/dashboard/components/widgets/ColorPicker.vue
@@ -53,8 +53,8 @@ export default {