diff --git a/.scss-lint.yml b/.scss-lint.yml index 9f5f4fe10..481d94c70 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -177,6 +177,8 @@ linters: allow_element_with_attribute: false allow_element_with_class: false allow_element_with_id: false + exclude: + - 'app/assets/stylesheets/administrate/components/_buttons.scss' SelectorDepth: enabled: true @@ -279,3 +281,4 @@ linters: exclude: - 'app/javascript/widget/assets/scss/_reset.scss' - 'app/javascript/widget/assets/scss/sdk.css' + - 'app/assets/stylesheets/administrate/reset/_normalize.scss' diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index 9b826819b..f5e0f5476 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,3 +1,4 @@ //= link_tree ../images //= link administrate/application.css //= link administrate/application.js +//= link dashboardChart.js diff --git a/app/assets/javascripts/dashboardChart.js b/app/assets/javascripts/dashboardChart.js new file mode 100644 index 000000000..6bfe56bda --- /dev/null +++ b/app/assets/javascripts/dashboardChart.js @@ -0,0 +1,55 @@ +// eslint-disable-next-line +function prepareData(data) { + var labels = []; + var dataSet = []; + data.forEach(item => { + labels.push(item[0]); + dataSet.push(item[1]); + }); + return { labels, dataSet }; +} + +function getChartOptions() { + var fontFamily = + 'Inter,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif'; + return { + responsive: true, + legend: { labels: { fontFamily } }, + scales: { + xAxes: [ + { + barPercentage: 1.26, + ticks: { fontFamily }, + gridLines: { display: false }, + }, + ], + yAxes: [ + { + ticks: { fontFamily }, + gridLines: { display: false }, + }, + ], + }, + }; +} + +// eslint-disable-next-line +function drawSuperAdminDashboard(data) { + var ctx = document.getElementById('dashboard-chart').getContext('2d'); + var chartData = prepareData(data); + // eslint-disable-next-line + new Chart(ctx, { + type: 'bar', + data: { + labels: chartData.labels, + datasets: [ + { + label: 'Conversations', + data: chartData.dataSet, + backgroundColor: '#1f93ff', + }, + ], + }, + options: getChartOptions(), + }); +} diff --git a/app/assets/stylesheets/administrate/application.scss b/app/assets/stylesheets/administrate/application.scss new file mode 100644 index 000000000..79738bbf3 --- /dev/null +++ b/app/assets/stylesheets/administrate/application.scss @@ -0,0 +1,32 @@ +@charset 'utf-8'; + +@import 'reset/normalize'; + +@import 'utilities/variables'; +@import 'utilities/text-color'; + +@import 'selectize'; +@import 'datetime_picker'; + +@import 'library/clearfix'; +@import 'library/data-label'; +@import 'library/variables'; + +@import 'base/forms'; +@import 'base/layout'; +@import 'base/lists'; +@import 'base/tables'; +@import 'base/typography'; + +@import 'components/app-container'; +@import 'components/attributes'; +@import 'components/buttons'; +@import 'components/cells'; +@import 'components/field-unit'; +@import 'components/flashes'; +@import 'components/form-actions'; +@import 'components/main-content'; +@import 'components/navigation'; +@import 'components/pagination'; +@import 'components/search'; +@import 'components/reports'; diff --git a/app/assets/stylesheets/administrate/base/_forms.scss b/app/assets/stylesheets/administrate/base/_forms.scss new file mode 100644 index 000000000..bf014a746 --- /dev/null +++ b/app/assets/stylesheets/administrate/base/_forms.scss @@ -0,0 +1,103 @@ +fieldset { + background-color: transparent; + border: 0; + margin: 0; + padding: 0; +} + +legend { + font-weight: $font-weight-medium; + margin: 0; + padding: 0; +} + +label { + display: block; + font-weight: $font-weight-medium; + margin: 0; +} + +input, +select { + display: block; + font-family: $base-font-family; + font-size: $base-font-size; +} + +input, +select, +textarea { + display: block; + font-family: $base-font-family; + font-size: 16px; +} + +[type="color"], +[type="date"], +[type="datetime-local"], +[type="email"], +[type="month"], +[type="number"], +[type="password"], +[type="search"], +[type="tel"], +[type="text"], +[type="time"], +[type="url"], +[type="week"], +input:not([type]), +textarea { + appearance: none; + background-color: $white; + border: $base-border; + border-radius: $base-border-radius; + padding: 0.5em; + transition: border-color $base-duration $base-timing; + width: 100%; + + &:hover { + border-color: mix($black, $base-border-color, 20%); + } + + &:focus { + border-color: $action-color; + outline: none; + } + + &:disabled { + background-color: mix($black, $white, 5%); + cursor: not-allowed; + + &:hover { + border: $base-border; + } + } +} + +textarea { + resize: vertical; +} + +[type="checkbox"], +[type="radio"] { + display: inline; + margin-right: $small-spacing / 2; +} + +[type="file"] { + width: 100%; +} + +select { + width: 100%; +} + +[type="checkbox"], +[type="radio"], +[type="file"], +select { + &:focus { + outline: $focus-outline; + outline-offset: $focus-outline-offset; + } +} diff --git a/app/assets/stylesheets/administrate/base/_layout.scss b/app/assets/stylesheets/administrate/base/_layout.scss new file mode 100644 index 000000000..c4c081a82 --- /dev/null +++ b/app/assets/stylesheets/administrate/base/_layout.scss @@ -0,0 +1,22 @@ +html { + background-color: $color-white; + box-sizing: border-box; + font-size: 10px; + -webkit-font-smoothing: antialiased; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +figure { + margin: 0; +} + +img, +picture { + margin: 0; + max-width: 100%; +} diff --git a/app/assets/stylesheets/administrate/base/_lists.scss b/app/assets/stylesheets/administrate/base/_lists.scss new file mode 100644 index 000000000..70eae5203 --- /dev/null +++ b/app/assets/stylesheets/administrate/base/_lists.scss @@ -0,0 +1,19 @@ +ul, +ol { + list-style-type: none; + margin: 0; + padding: 0; +} + +dl { + margin-bottom: $small-spacing; + + dt { + font-weight: $font-weight-medium; + margin-top: $small-spacing; + } + + dd { + margin: 0; + } +} diff --git a/app/assets/stylesheets/administrate/base/_tables.scss b/app/assets/stylesheets/administrate/base/_tables.scss new file mode 100644 index 000000000..1772e8cf4 --- /dev/null +++ b/app/assets/stylesheets/administrate/base/_tables.scss @@ -0,0 +1,71 @@ +table { + border-collapse: collapse; + font-size: $font-size-default; + text-align: left; + width: 100%; + + a { + color: inherit; + text-decoration: none; + } +} + +tr { + border-bottom: $base-border; + + th { + font-weight: $font-weight-medium; + + &.cell-label--avatar-field { + a { + display: none; + } + } + } +} + +tbody tr { + &:hover { + background-color: $base-background-color; + cursor: pointer; + } + + &:focus { + outline: $focus-outline; + outline-offset: -($focus-outline-width); + } + + td { + &.cell-data--avatar-field { + line-height: 1; + text-align: center; + + img { + border-radius: 50%; + height: $space-large; + max-height: $space-large; + width: $space-large; + } + } + } +} + +td, +th { + padding: $space-slab; + vertical-align: middle; +} + +td:first-child, +th:first-child { + padding-left: 0; +} + +td:last-child, +th:last-child { + padding-right: 0; +} + +td img { + max-height: 2rem; +} diff --git a/app/assets/stylesheets/administrate/base/_typography.scss b/app/assets/stylesheets/administrate/base/_typography.scss new file mode 100644 index 000000000..bf2c2d9c2 --- /dev/null +++ b/app/assets/stylesheets/administrate/base/_typography.scss @@ -0,0 +1,44 @@ +body { + color: $base-font-color; + font-family: $base-font-family; + font-size: $base-font-size; + line-height: $base-line-height; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: $heading-font-family; + font-size: $base-font-size; + line-height: $heading-line-height; + margin: 0; +} + +p { + margin: 0 0 $small-spacing; +} + +a { + color: $action-color; + transition: color $base-duration $base-timing; + + &:hover { + color: mix($black, $action-color, 25%); + } + + &:focus { + outline: $focus-outline; + outline-offset: $focus-outline-offset; + } +} + +hr { + border-bottom: $base-border; + border-left: 0; + border-right: 0; + border-top: 0; + margin: $base-spacing 0; +} diff --git a/app/assets/stylesheets/administrate/components/_app-container.scss b/app/assets/stylesheets/administrate/components/_app-container.scss new file mode 100644 index 000000000..80873272f --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_app-container.scss @@ -0,0 +1,8 @@ +.app-container { + align-items: stretch; + display: flex; + margin-left: auto; + margin-right: auto; + max-width: 100rem; + min-height: 100vh; +} diff --git a/app/assets/stylesheets/administrate/components/_attributes.scss b/app/assets/stylesheets/administrate/components/_attributes.scss new file mode 100644 index 000000000..713d9f523 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_attributes.scss @@ -0,0 +1,26 @@ +.attribute-label { + @include data-label; + clear: left; + float: left; + margin-bottom: $base-spacing; + margin-top: 0.25em; + text-align: right; + width: calc(15% - 1rem); +} + +.preserve-whitespace { + white-space: pre-wrap; + word-wrap: break-word; +} + +.attribute-data { + float: left; + margin-bottom: $base-spacing; + margin-left: 2rem; + width: calc(85% - 1rem); +} + +.attribute--nested { + border: $base-border; + padding: $small-spacing; +} diff --git a/app/assets/stylesheets/administrate/components/_buttons.scss b/app/assets/stylesheets/administrate/components/_buttons.scss new file mode 100644 index 000000000..3e021e658 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_buttons.scss @@ -0,0 +1,50 @@ +button, +input[type="button"], +input[type="reset"], +input[type="submit"], +.button { + appearance: none; + background-color: $color-woot; + border: 0; + border-radius: $base-border-radius; + color: $white; + cursor: pointer; + display: inline-block; + font-size: $font-size-default; + -webkit-font-smoothing: antialiased; + font-weight: $font-weight-medium; + line-height: 1; + padding: $space-one $space-two; + text-decoration: none; + transition: background-color $base-duration $base-timing; + user-select: none; + vertical-align: middle; + white-space: nowrap; + + &:hover { + background-color: mix($black, $color-woot, 20%); + color: $white; + } + + &:focus { + outline: $focus-outline; + outline-offset: $focus-outline-offset; + } + + &:disabled { + cursor: not-allowed; + opacity: 0.5; + + &:hover { + background-color: $color-woot; + } + } +} + +.button--alt { + background-color: transparent; + border: $base-border; + border-color: $blue; + color: $blue; + margin-bottom: $base-spacing; +} diff --git a/app/assets/stylesheets/administrate/components/_cells.scss b/app/assets/stylesheets/administrate/components/_cells.scss new file mode 100644 index 000000000..2f7e27c4a --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_cells.scss @@ -0,0 +1,45 @@ +.cell-label { + &:hover { + a { + color: $action-color; + } + + svg { + fill: $action-color; + transform: rotate(180deg); + } + } + + a { + color: inherit; + display: inline-block; + transition: color $base-duration $base-timing; + width: 100%; + } +} + +.cell-label--asc, +.cell-label--desc { + font-weight: $font-weight-medium; +} + +.cell-label__sort-indicator { + float: right; + margin-left: 5px; + + svg { + fill: $hint-grey; + height: 13px; + transition: transform $base-duration $base-timing; + width: 13px; + } +} + +.cell-label__sort-indicator--desc { + transform: rotate(180deg); +} + +.cell-data--number, +.cell-label--number { + text-align: right; +} diff --git a/app/assets/stylesheets/administrate/components/_field-unit.scss b/app/assets/stylesheets/administrate/components/_field-unit.scss new file mode 100644 index 000000000..856c1872c --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_field-unit.scss @@ -0,0 +1,54 @@ +.field-unit { + @include administrate-clearfix; + align-items: center; + display: flex; + margin-bottom: $base-spacing; + position: relative; + width: 100%; +} + +.field-unit__label { + float: left; + margin-left: 1rem; + text-align: right; + width: calc(15% - 1rem); +} + +.field-unit__field { + float: left; + margin-left: 2rem; + max-width: 50rem; + width: 100%; +} + +.field-unit--nested { + border: $base-border; + margin-left: 7.5%; + max-width: 60rem; + padding: $small-spacing; + width: 100%; + + .field-unit__field { + width: 100%; + } + + .field-unit__label { + width: 10rem; + } +} + +.field-unit--required { + label::after { + color: $red; + content: ' *'; + } +} + +.attribute-data--avatar-field { + height: $space-larger; + width: $space-larger; + + img { + border-radius: 50%; + } +} diff --git a/app/assets/stylesheets/administrate/components/_flashes.scss b/app/assets/stylesheets/administrate/components/_flashes.scss new file mode 100644 index 000000000..48c3e685c --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_flashes.scss @@ -0,0 +1,28 @@ +$base-spacing: 1.5em !default; +$flashes: ( + "alert": #fff6bf, + "error": #fbe3e4, + "notice": #e5edf8, + "success": #e6efc2, +) !default; + +@each $flash-type, $color in $flashes { + .flash-#{$flash-type} { + background-color: $color; + color: mix($black, $color, 60%); + display: block; + margin-bottom: $base-spacing / 2; + padding: $base-spacing / 2; + text-align: center; + + a { + color: mix($black, $color, 70%); + text-decoration: underline; + + &:focus, + &:hover { + color: mix($black, $color, 90%); + } + } + } +} diff --git a/app/assets/stylesheets/administrate/components/_form-actions.scss b/app/assets/stylesheets/administrate/components/_form-actions.scss new file mode 100644 index 000000000..d87d17435 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_form-actions.scss @@ -0,0 +1,3 @@ +.form-actions { + margin-left: calc(15% + 2rem); +} diff --git a/app/assets/stylesheets/administrate/components/_main-content.scss b/app/assets/stylesheets/administrate/components/_main-content.scss new file mode 100644 index 000000000..d03229828 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_main-content.scss @@ -0,0 +1,26 @@ +.main-content { + font-size: $font-size-default; + left: 23rem; + position: absolute; + right: 0; + top: 0; +} + +.main-content__body { + padding: $space-two; +} + +.main-content__header { + align-items: center; + background-color: $color-white; + border-bottom: 1px solid $color-border; + display: flex; + min-height: 5.6rem; + padding: $space-small $space-normal; +} + +.main-content__page-title { + font-size: $font-size-large; + font-weight: $font-weight-medium; + margin-right: auto; +} diff --git a/app/assets/stylesheets/administrate/components/_navigation.scss b/app/assets/stylesheets/administrate/components/_navigation.scss new file mode 100644 index 000000000..f6b1a641d --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_navigation.scss @@ -0,0 +1,72 @@ +.logo-brand { + margin-bottom: $space-normal; + padding: $space-normal $space-smaller; + text-align: center; +} + +.navigation { + background: $white; + border-right: 1px solid $color-border; + display: flex; + flex-direction: column; + font-size: $font-size-default; + font-weight: $font-weight-medium; + height: 100%; + justify-content: flex-start; + left: 0; + margin: 0; + overflow: auto; + padding: $space-normal; + position: fixed; + top: 0; + width: 23rem; + z-index: 1023; + + li { + align-items: center; + display: flex; + + a { + color: $color-gray; + text-decoration: none; + } + + i { + min-width: $space-medium; + } + } +} + +.navigation__link { + background-color: transparent; + color: $color-gray; + display: block; + line-height: 1; + margin-bottom: $space-smaller; + padding: $space-one; + + &:hover { + color: $blue; + + a { + color: $blue; + } + } + + + &.navigation__link--active { + background-color: $color-background; + border-radius: $base-border-radius; + color: $blue; + + a { + color: $blue; + } + } +} + +.logout { + bottom: $space-normal; + left: $space-normal; + position: fixed; +} diff --git a/app/assets/stylesheets/administrate/components/_pagination.scss b/app/assets/stylesheets/administrate/components/_pagination.scss new file mode 100644 index 000000000..cb3e12f21 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_pagination.scss @@ -0,0 +1,19 @@ +.pagination { + font-size: $font-size-default; + margin-top: $base-spacing; + padding-left: $base-spacing; + padding-right: $base-spacing; + text-align: center; + + .first, + .prev, + .page, + .next, + .last { + margin: $small-spacing; + } + + .current { + font-weight: $font-weight-medium; + } +} diff --git a/app/assets/stylesheets/administrate/components/_reports.scss b/app/assets/stylesheets/administrate/components/_reports.scss new file mode 100644 index 000000000..0c8c133b7 --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_reports.scss @@ -0,0 +1,15 @@ +.report--list { + display: flex; + padding: 0 $space-two $space-larger; +} + +.report-card { + flex: 1; + font-size: $font-size-small; + text-align: center; + + .metric { + font-size: $font-size-bigger; + font-weight: 200; + } +} diff --git a/app/assets/stylesheets/administrate/components/_search.scss b/app/assets/stylesheets/administrate/components/_search.scss new file mode 100644 index 000000000..9b61a696a --- /dev/null +++ b/app/assets/stylesheets/administrate/components/_search.scss @@ -0,0 +1,44 @@ +.search { + margin-left: auto; + margin-right: 2rem; + max-width: 24rem; + position: relative; + width: 100%; +} + +.search__input { + background: $grey-1; + padding-left: $space-normal * 2.5; + padding-right: $space-normal * 2.5; +} + +.search__eyeglass-icon { + fill: $grey-7; + height: $space-normal; + left: $space-normal; + position: absolute; + top: 50%; + transform: translateY(-50%); + width: $space-normal; +} + +.search__clear-link { + height: $space-normal; + position: absolute; + right: $space-normal * 0.75; + top: 50%; + transform: translateY(-50%); + width: $space-normal; +} + +.search__clear-icon { + fill: $grey-5; + height: $space-normal; + position: absolute; + transition: fill $base-duration $base-timing; + width: $space-normal; + + &:hover { + fill: $action-color; + } +} diff --git a/app/assets/stylesheets/administrate/library/_clearfix.scss b/app/assets/stylesheets/administrate/library/_clearfix.scss new file mode 100644 index 000000000..ea852351f --- /dev/null +++ b/app/assets/stylesheets/administrate/library/_clearfix.scss @@ -0,0 +1,7 @@ +@mixin administrate-clearfix { + &::after { + clear: both; + content: ''; + display: block; + } +} diff --git a/app/assets/stylesheets/administrate/library/_data-label.scss b/app/assets/stylesheets/administrate/library/_data-label.scss new file mode 100644 index 000000000..2efcd2836 --- /dev/null +++ b/app/assets/stylesheets/administrate/library/_data-label.scss @@ -0,0 +1,8 @@ +@mixin data-label { + color: $hint-grey; + font-size: 0.8em; + font-weight: 400; + letter-spacing: 0.0357em; + position: relative; + text-transform: uppercase; +} diff --git a/app/assets/stylesheets/administrate/library/_variables.scss b/app/assets/stylesheets/administrate/library/_variables.scss new file mode 100644 index 000000000..3fdfcfd8d --- /dev/null +++ b/app/assets/stylesheets/administrate/library/_variables.scss @@ -0,0 +1,61 @@ +// Typography +$base-font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif !default; +$heading-font-family: $base-font-family !default; + +$base-font-size: 10px !default; + +$base-line-height: 1.5 !default; +$heading-line-height: 1.2 !default; + +// Other Sizes +$base-border-radius: 4px !default; +$base-spacing: $base-line-height * 1em !default; +$small-spacing: $base-spacing / 2 !default; + +// Colors +$white: #fff !default; +$black: #000 !default; + +$blue: #1f93ff !default; +$red: #ff382d !default; +$light-yellow: #ffc532 !default; +$light-green: #44ce4b !default; + +$grey-0: #f6f7f7 !default; +$grey-1: #f0f4f5 !default; +$grey-2: #cfd8dc !default; +$grey-5: #adb5bd !default; +$grey-7: #293f54 !default; + +$hint-grey: #7b808c !default; + +// Font Colors +$base-font-color: $grey-7 !default; +$action-color: $blue !default; + +// Background Colors +$base-background-color: $grey-0 !default; + +// Focus +$focus-outline-color: transparentize($action-color, 0.4); +$focus-outline-width: 3px; +$focus-outline: $focus-outline-width solid $focus-outline-color; +$focus-outline-offset: 1px; + +// Flash Colors +$flash-colors: ( + alert: $light-yellow, + error: $red, + notice: mix($white, $blue, 50%), + success: $light-green +); + +// Border +$base-border-color: $grey-1 !default; +$base-border: 1px solid $base-border-color !default; + +// Transitions +$base-duration: 250ms !default; +$base-timing: ease-in-out !default; diff --git a/app/assets/stylesheets/administrate/reset/_normalize.scss b/app/assets/stylesheets/administrate/reset/_normalize.scss new file mode 100644 index 000000000..fa4e73dd4 --- /dev/null +++ b/app/assets/stylesheets/administrate/reset/_normalize.scss @@ -0,0 +1,447 @@ +/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in + * IE on Windows Phone and in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers (opinionated). + */ + +body { + margin: 0; +} + +/** + * Add the correct display in IE 9-. + */ + +article, +aside, +footer, +header, +nav, +section { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + * 1. Add the correct display in IE. + */ + +figcaption, +figure, +main { /* 1 */ + display: block; +} + +/** + * Add the correct margin in IE 8. + */ + +figure { + margin: 1em 40px; +} + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * 1. Remove the gray background on active links in IE 10. + * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. + */ + +a { + background-color: transparent; /* 1 */ + -webkit-text-decoration-skip: objects; /* 2 */ +} + +/** + * 1. Remove the bottom border in Chrome 57- and Firefox 39-. + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Prevent the duplicate application of `bolder` by the next rule in Safari 6. + */ + +b, +strong { + font-weight: inherit; +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font style in Android 4.3-. + */ + +dfn { + font-style: italic; +} + +/** + * Add the correct background and color in IE 9-. + */ + +mark { + background-color: #ff0; + color: #000; +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +audio, +video { + display: inline-block; +} + +/** + * Add the correct display in iOS 4-7. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Remove the border on images inside links in IE 10-. + */ + +img { + border-style: none; +} + +/** + * Hide the overflow in IE. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers (opinionated). + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: sans-serif; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` + * controls in Android 4. + * 2. Correct the inability to style clickable types in iOS and Safari. + */ + +button, +html [type="button"], /* 1 */ +[type="reset"], +[type="submit"] { + -webkit-appearance: button; /* 2 */ +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * 1. Add the correct display in IE 9-. + * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Remove the default vertical scrollbar in IE. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10-. + * 2. Remove the padding in IE 10-. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in IE 9-. + * 1. Add the correct display in Edge, IE, and Firefox. + */ + +details, /* 1 */ +menu { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Scripting + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +canvas { + display: inline-block; +} + +/** + * Add the correct display in IE. + */ + +template { + display: none; +} + +/* Hidden + ========================================================================== */ + +/** + * Add the correct display in IE 10-. + */ + +[hidden] { + display: none; +} diff --git a/app/assets/stylesheets/administrate/utilities/_text-color.scss b/app/assets/stylesheets/administrate/utilities/_text-color.scss new file mode 100644 index 000000000..afa3bcca3 --- /dev/null +++ b/app/assets/stylesheets/administrate/utilities/_text-color.scss @@ -0,0 +1,3 @@ +.text-color-red { + color: $alert-color; +} diff --git a/app/assets/stylesheets/administrate/utilities/_variables.scss b/app/assets/stylesheets/administrate/utilities/_variables.scss new file mode 100644 index 000000000..1798d918b --- /dev/null +++ b/app/assets/stylesheets/administrate/utilities/_variables.scss @@ -0,0 +1,98 @@ +// Font sizes +$font-size-nano: 0.8rem; +$font-size-micro: 1.0rem; +$font-size-mini: 1.2rem; +$font-size-small: 1.4rem; +$font-size-default: 1.6rem; +$font-size-medium: 1.8rem; +$font-size-large: 2.2rem; +$font-size-big: 2.4rem; +$font-size-bigger: 3.0rem; +$font-size-mega: 3.4rem; +$font-size-giga: 4.0rem; + +// spaces +$zero: 0; +$space-micro: 0.2rem; +$space-smaller: 0.4rem; +$space-small: 0.8rem; +$space-one: 1rem; +$space-slab: 1.2rem; +$space-normal: 1.6rem; +$space-two: 2.0rem; +$space-medium: 2.4rem; +$space-large: 3.2rem; +$space-larger: 4.8rem; +$space-jumbo: 6.4rem; +$space-mega: 10.0rem; + +// font-weight +$font-weight-feather: 100; +$font-weight-light: 300; +$font-weight-normal: 400; +$font-weight-medium: 500; +$font-weight-bold: 600; +$font-weight-black: 700; + +//Navbar +$nav-bar-width: 23rem; +$header-height: 5.6rem; + +$woot-logo-padding: $space-large $space-two; + +// Colors +$color-woot: #1f93ff; +$color-gray: #6e6f73; +$color-light-gray: #999a9b; +$color-border: #e0e6ed; +$color-border-light: #f0f4f5; +$color-background: #f4f6fb; +$color-border-dark: #cad0d4; +$color-background-light: #f9fafc; +$color-white: #fff; +$color-body: #3c4858; +$color-heading: #1f2d3d; +$color-extra-light-blue: #f5f7f9; + +$primary-color: $color-woot; +$secondary-color: #35c5ff; +$success-color: #44ce4b; +$warning-color: #ffc532; +$alert-color: #ff382d; + +$masked-bg: rgba(0, 0, 0, .4); + +// Color-palettes + +$color-primary-light: #c7e3ff; +$color-primary-dark: darken($color-woot, 20%); + +// Thumbnail +$thumbnail-radius: 4rem; + +// chat-header +$conv-header-height: 4rem; + +// Inbox List + +$inbox-thumb-size: 4.8rem; + + +// Spinner +$spinkit-spinner-color: $color-white !default; +$spinkit-spinner-margin: 0 0 0 1.6rem !default; +$spinkit-size: 1.6rem !default; + +// Snackbar default +$woot-snackbar-bg: #323232; +$woot-snackbar-button: #ffeb3b; + +$swift-ease-out-duration: .4s !default; +$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default; +$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default; + +// Ionicons +$ionicons-font-path: '~ionicons/fonts'; + +// Transitions +$transition-ease-in: all 0.250s ease-in; diff --git a/app/controllers/super_admin/account_users_controller.rb b/app/controllers/super_admin/account_users_controller.rb index 5b834c232..bf86a5af6 100644 --- a/app/controllers/super_admin/account_users_controller.rb +++ b/app/controllers/super_admin/account_users_controller.rb @@ -2,10 +2,32 @@ class SuperAdmin::AccountUsersController < SuperAdmin::ApplicationController # Overwrite any of the RESTful controller actions to implement custom behavior # For example, you may want to send an email after a foo is updated. # - # def update - # super - # send_foo_updated_email(requested_resource) - # end + def create + resource = resource_class.new(resource_params) + authorize_resource(resource) + + redirect_resource = params[:redirect_to] == 'user' ? resource.user : resource.account + if resource.save + redirect_to( + [namespace, redirect_resource], + notice: translate_with_resource('create.success') + ) + else + redirect_to( + [namespace, redirect_resource], + notice: resource.errors.full_messages.first + ) + end + end + + def destroy + if requested_resource.destroy + flash[:notice] = translate_with_resource('destroy.success') + else + flash[:error] = requested_resource.errors.full_messages.join('
') + end + redirect_to([namespace, requested_resource.account]) + end # Override this method to specify custom lookup behavior. # This will be used to set the resource for the `show`, `edit`, and `update` diff --git a/app/controllers/super_admin/application_controller.rb b/app/controllers/super_admin/application_controller.rb index 463ad30e6..69b61b913 100644 --- a/app/controllers/super_admin/application_controller.rb +++ b/app/controllers/super_admin/application_controller.rb @@ -13,4 +13,11 @@ class SuperAdmin::ApplicationController < Administrate::ApplicationController # def records_per_page # params[:per_page] || 20 # end + + def order + @order ||= Administrate::Order.new( + params.fetch(resource_name, {}).fetch(:order, 'id'), + params.fetch(resource_name, {}).fetch(:direction, 'desc') + ) + end end diff --git a/app/controllers/super_admin/dashboard_controller.rb b/app/controllers/super_admin/dashboard_controller.rb new file mode 100644 index 000000000..b5f3d34eb --- /dev/null +++ b/app/controllers/super_admin/dashboard_controller.rb @@ -0,0 +1,12 @@ +class SuperAdmin::DashboardController < SuperAdmin::ApplicationController + include ActionView::Helpers::NumberHelper + + def index + @data = Conversation.unscoped.group_by_day(:created_at, range: 30.days.ago..2.seconds.ago).count.to_a + @accounts_count = number_with_delimiter(Account.all.length) + @users_count = number_with_delimiter(User.all.length) + @inboxes_count = number_with_delimiter(Inbox.all.length) + @conversations_count = number_with_delimiter(Conversation.all.length) + @messages_count = number_with_delimiter(Message.all.length) + end +end diff --git a/app/dashboards/account_dashboard.rb b/app/dashboards/account_dashboard.rb index d80abc199..3cc35d0d3 100644 --- a/app/dashboards/account_dashboard.rb +++ b/app/dashboards/account_dashboard.rb @@ -12,7 +12,10 @@ class AccountDashboard < Administrate::BaseDashboard name: Field::String, created_at: Field::DateTime, updated_at: Field::DateTime, - locale: Field::String.with_options(searchable: false) + users: CountField, + conversations: CountField, + locale: Field::Select.with_options(collection: LANGUAGES_CONFIG.map { |_x, y| y[:iso_639_1_code] }), + account_users: Field::HasMany }.freeze # COLLECTION_ATTRIBUTES @@ -21,8 +24,11 @@ class AccountDashboard < Administrate::BaseDashboard # By default, it's limited to four items to reduce clutter on index pages. # Feel free to add, remove, or rearrange items. COLLECTION_ATTRIBUTES = %i[ + id name locale + users + conversations ].freeze # SHOW_PAGE_ATTRIBUTES @@ -33,6 +39,8 @@ class AccountDashboard < Administrate::BaseDashboard created_at updated_at locale + conversations + account_users ].freeze # FORM_ATTRIBUTES @@ -58,7 +66,7 @@ class AccountDashboard < Administrate::BaseDashboard # Overwrite this method to customize how accounts are displayed # across all pages of the admin dashboard. # - # def display_resource(account) - # "Account ##{account.id}" - # end + def display_resource(account) + "##{account.id} #{account.name}" + end end diff --git a/app/dashboards/account_user_dashboard.rb b/app/dashboards/account_user_dashboard.rb index f0a96af7f..d2a2e7f27 100644 --- a/app/dashboards/account_user_dashboard.rb +++ b/app/dashboards/account_user_dashboard.rb @@ -8,12 +8,11 @@ class AccountUserDashboard < Administrate::BaseDashboard # which determines how the attribute is displayed # on pages throughout the dashboard. ATTRIBUTE_TYPES = { - account: Field::BelongsTo, - user: Field::BelongsTo, - inviter: Field::BelongsTo.with_options(class_name: 'User'), + account: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name'), + user: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name'), + inviter: Field::BelongsTo.with_options(class_name: 'User', searchable: true, searchable_field: 'name'), id: Field::Number, - role: Field::String.with_options(searchable: false), - inviter_id: Field::Number, + role: Field::Select.with_options(collection: AccountUser.roles.keys), created_at: Field::DateTime, updated_at: Field::DateTime }.freeze @@ -27,7 +26,7 @@ class AccountUserDashboard < Administrate::BaseDashboard account user inviter - id + role ].freeze # SHOW_PAGE_ATTRIBUTES @@ -38,7 +37,6 @@ class AccountUserDashboard < Administrate::BaseDashboard inviter id role - inviter_id created_at updated_at ].freeze @@ -49,9 +47,7 @@ class AccountUserDashboard < Administrate::BaseDashboard FORM_ATTRIBUTES = %i[ account user - inviter role - inviter_id ].freeze # COLLECTION_FILTERS @@ -69,7 +65,7 @@ class AccountUserDashboard < Administrate::BaseDashboard # Overwrite this method to customize how account users are displayed # across all pages of the admin dashboard. # - # def display_resource(account_user) - # "AccountUser ##{account_user.id}" - # end + def display_resource(account_user) + "AccountUser ##{account_user.id}" + end end diff --git a/app/dashboards/super_admin_dashboard.rb b/app/dashboards/super_admin_dashboard.rb index 4ceab3a17..ab467a255 100644 --- a/app/dashboards/super_admin_dashboard.rb +++ b/app/dashboards/super_admin_dashboard.rb @@ -10,6 +10,7 @@ class SuperAdminDashboard < Administrate::BaseDashboard ATTRIBUTE_TYPES = { id: Field::Number, email: Field::String, + password: Field::Password, access_token: Field::HasOne, remember_created_at: Field::DateTime, sign_in_count: Field::Number, @@ -52,12 +53,7 @@ class SuperAdminDashboard < Administrate::BaseDashboard # on the model's form (`new` and `edit`) pages. FORM_ATTRIBUTES = %i[ email - remember_created_at - sign_in_count - current_sign_in_at - last_sign_in_at - current_sign_in_ip - last_sign_in_ip + password ].freeze # COLLECTION_FILTERS diff --git a/app/dashboards/user_dashboard.rb b/app/dashboards/user_dashboard.rb index e8d24eae2..8ed132c61 100644 --- a/app/dashboards/user_dashboard.rb +++ b/app/dashboards/user_dashboard.rb @@ -9,14 +9,11 @@ class UserDashboard < Administrate::BaseDashboard # on pages throughout the dashboard. ATTRIBUTE_TYPES = { account_users: Field::HasMany, - accounts: Field::HasMany, - invitees: Field::HasMany.with_options(class_name: 'User'), id: Field::Number, + avatar_url: AvatarField, provider: Field::String, uid: Field::String, - reset_password_token: Field::String, - reset_password_sent_at: Field::DateTime, - remember_created_at: Field::DateTime, + password: Field::Password, sign_in_count: Field::Number, current_sign_in_at: Field::DateTime, last_sign_in_at: Field::DateTime, @@ -32,7 +29,8 @@ class UserDashboard < Administrate::BaseDashboard tokens: Field::String.with_options(searchable: false), created_at: Field::DateTime, updated_at: Field::DateTime, - pubsub_token: Field::String + pubsub_token: Field::String, + accounts: CountField }.freeze # COLLECTION_ATTRIBUTES @@ -41,21 +39,25 @@ class UserDashboard < Administrate::BaseDashboard # By default, it's limited to four items to reduce clutter on index pages. # Feel free to add, remove, or rearrange items. COLLECTION_ATTRIBUTES = %i[ + id + avatar_url name email + accounts ].freeze # SHOW_PAGE_ATTRIBUTES # an array of attributes that will be displayed on the model's show page. SHOW_PAGE_ATTRIBUTES = %i[ - accounts id + avatar_url unconfirmed_email name nickname email created_at updated_at + account_users ].freeze # FORM_ATTRIBUTES @@ -65,6 +67,7 @@ class UserDashboard < Administrate::BaseDashboard name nickname email + password ].freeze # COLLECTION_FILTERS @@ -82,7 +85,7 @@ class UserDashboard < Administrate::BaseDashboard # Overwrite this method to customize how users are displayed # across all pages of the admin dashboard. # - # def display_resource(user) - # "User ##{user.id}" - # end + def display_resource(user) + "##{user.id} #{user.name}" + end end diff --git a/app/fields/avatar_field.rb b/app/fields/avatar_field.rb new file mode 100644 index 000000000..50633ccd2 --- /dev/null +++ b/app/fields/avatar_field.rb @@ -0,0 +1,7 @@ +require 'administrate/field/base' + +class AvatarField < Administrate::Field::Base + def avatar_url + data.presence || '/admin/avatar.png' + end +end diff --git a/app/fields/count_field.rb b/app/fields/count_field.rb new file mode 100644 index 000000000..de5c4ae42 --- /dev/null +++ b/app/fields/count_field.rb @@ -0,0 +1,7 @@ +require 'administrate/field/base' + +class CountField < Administrate::Field::Base + def to_s + data.count + end +end diff --git a/app/javascript/dashboard/assets/scss/super_admin/pages.scss b/app/javascript/dashboard/assets/scss/super_admin/pages.scss index 91b62d671..2bc31db1c 100644 --- a/app/javascript/dashboard/assets/scss/super_admin/pages.scss +++ b/app/javascript/dashboard/assets/scss/super_admin/pages.scss @@ -1,13 +1,3 @@ @import 'shared/assets/fonts/inter'; @import '../variables'; - -body { - background-color: $color-background; - font-family: Inter; -} - -.button { - background-color: $color-woot; - border-radius: 1px solid $color-woot; - color: $color-white; -} +@import '~ionicons/scss/ionicons'; diff --git a/app/javascript/packs/sdk.js b/app/javascript/packs/sdk.js index ba0187d52..a9cecebbe 100755 --- a/app/javascript/packs/sdk.js +++ b/app/javascript/packs/sdk.js @@ -10,7 +10,7 @@ const runSDK = ({ baseUrl, websiteToken }) => { isOpen: false, position: chatwootSettings.position === 'left' ? 'left' : 'right', websiteToken, - locale: chatwootSettings.locale || 'en', + locale: chatwootSettings.locale, toggle() { IFrameHelper.events.toggleBubble(); diff --git a/app/javascript/packs/superadmin_pages.js b/app/javascript/packs/superadmin_pages.js index 4870b6f0f..7a3f33975 100644 --- a/app/javascript/packs/superadmin_pages.js +++ b/app/javascript/packs/superadmin_pages.js @@ -1 +1,2 @@ import '../dashboard/assets/scss/super_admin/pages.scss'; +import 'chart.js'; diff --git a/app/models/account_user.rb b/app/models/account_user.rb index 6fe575f35..c915a5042 100644 --- a/app/models/account_user.rb +++ b/app/models/account_user.rb @@ -43,7 +43,7 @@ class AccountUser < ApplicationRecord end def destroy_notification_setting - setting = user.notification_settings.new(account_id: account.id) + setting = user.notification_settings.find_by(account_id: account.id) setting.destroy! end end diff --git a/app/views/fields/avatar_field/_index.html.erb b/app/views/fields/avatar_field/_index.html.erb new file mode 100644 index 000000000..61f2eff47 --- /dev/null +++ b/app/views/fields/avatar_field/_index.html.erb @@ -0,0 +1 @@ +<%= image_tag field.avatar_url %> diff --git a/app/views/fields/avatar_field/_show.html.erb b/app/views/fields/avatar_field/_show.html.erb new file mode 100644 index 000000000..61f2eff47 --- /dev/null +++ b/app/views/fields/avatar_field/_show.html.erb @@ -0,0 +1 @@ +<%= image_tag field.avatar_url %> diff --git a/app/views/fields/count_field/_index.html.erb b/app/views/fields/count_field/_index.html.erb new file mode 100644 index 000000000..6d9dbc907 --- /dev/null +++ b/app/views/fields/count_field/_index.html.erb @@ -0,0 +1 @@ +<%= field.to_s %> diff --git a/app/views/fields/count_field/_show.html.erb b/app/views/fields/count_field/_show.html.erb new file mode 100644 index 000000000..6d9dbc907 --- /dev/null +++ b/app/views/fields/count_field/_show.html.erb @@ -0,0 +1 @@ +<%= field.to_s %> diff --git a/app/views/layouts/super_admin/application.html.erb b/app/views/layouts/super_admin/application.html.erb new file mode 100644 index 000000000..c52a40b9f --- /dev/null +++ b/app/views/layouts/super_admin/application.html.erb @@ -0,0 +1,41 @@ +<%# +# Application Layout + +This view template is used as the layout +for every page that Administrate generates. + +By default, it renders: +- Navigation +- Content for a search bar + (if provided by a `content_for` block in a nested page) +- Flashes +- Links to stylesheets and JavaScripts +%> + + + + + + + + + <%= content_for(:title) %> - <%= application_title %> + + <%= render "stylesheet" %> + <%= csrf_meta_tags %> + + + <%= render "icons" %> + +
+ <%= render "navigation" -%> + +
+ <%= render "flashes" -%> + <%= yield %> +
+
+ + <%= render "javascript" %> + + diff --git a/app/views/super_admin/accounts/show.html.erb b/app/views/super_admin/accounts/show.html.erb new file mode 100644 index 000000000..ca08929a6 --- /dev/null +++ b/app/views/super_admin/accounts/show.html.erb @@ -0,0 +1,88 @@ +<%# +# Show + +This view is the template for the show page. +It renders the attributes of a resource, +as well as a link to its edit page. + +## Local variables: + +- `page`: + An instance of [Administrate::Page::Show][1]. + Contains methods for accessing the resource to be displayed on the page, + as well as helpers for describing how each attribute of the resource + should be displayed. + +[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Show +%> + +<% content_for(:title) { t("administrate.actions.show_resource", name: page.page_title) } %> + + + +
+
+ <% page.attributes.each do |attribute| %> +
+ <%= t( + "helpers.label.#{resource_name}.#{attribute.name}", + default: attribute.name.titleize, + ) %> +
+ +
<%= render_field attribute, page: page %>
+ <% end %> +
+
+ +
+ <% account_user_page = Administrate::Page::Form.new(AccountUserDashboard.new, AccountUser.new) %> + <%= form_for([namespace, account_user_page.resource], html: { class: "form" }) do |f| %> + <% if account_user_page.resource.errors.any? %> +
+

+ <%= t( + "administrate.form.errors", + pluralized_errors: pluralize(account_user_page.resource.errors.count, t("administrate.form.error")), + resource_name: display_resource_name(account_user_page.resource_name) + ) %> +

+ + +
+ <% end %> + + <% account_user_page.attributes.each do |attribute| -%> + <% if attribute.name == "account" %> + <%= f.hidden_field('account_id', value: page.resource.id) %> + <%= f.hidden_field('redirect_to', value: 'user') %> + <% else %> +
+ <%= render_field attribute, f: f %> +
+ <% end %> + <% end -%> + +
+ <%= f.submit %> +
+<% end %> + +
diff --git a/app/views/super_admin/application/_collection.html.erb b/app/views/super_admin/application/_collection.html.erb new file mode 100644 index 000000000..3f7aa489c --- /dev/null +++ b/app/views/super_admin/application/_collection.html.erb @@ -0,0 +1,95 @@ +<%# +# Collection + +This partial is used on the `index` and `show` pages +to display a collection of resources in an HTML table. + +## Local variables: + +- `collection_presenter`: + An instance of [Administrate::Page::Collection][1]. + The table presenter uses `ResourceDashboard::COLLECTION_ATTRIBUTES` to determine + the columns displayed in the table +- `resources`: + An ActiveModel::Relation collection of resources to be displayed in the table. + By default, the number of resources is limited by pagination + or by a hard limit to prevent excessive page load times + +[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection +%> + + + + + <% collection_presenter.attribute_types.each do |attr_name, attr_type| %> + + <% end %> + <% [valid_action?(:edit, collection_presenter.resource_name), + valid_action?(:destroy, collection_presenter.resource_name)].count(true).times do %> + + <% end %> + + + + + <% resources.each do |resource| %> + + <%= %(role=link data-url=#{polymorphic_path([namespace, resource])}) %> + <% end %> + > + <% collection_presenter.attributes_for(resource).each do |attribute| %> + + <% end %> + + <% if valid_action? :edit, collection_presenter.resource_name %> + + <% end %> + + <% if valid_action? :destroy, collection_presenter.resource_name %> + + <% end %> + + <% end %> + +
+ <%= link_to(sanitized_order_params(page, collection_field_name).merge( + collection_presenter.order_params_for(attr_name, key: collection_field_name) + )) do %> + <%= t( + "helpers.label.#{collection_presenter.resource_name}.#{attr_name}", + default: attr_name.to_s, + ).titleize %> + <% if collection_presenter.ordered_by?(attr_name) %> + + + + <% end %> + <% end %> +
+ <% if show_action? :show, resource -%> + + <%= render_field attribute %> + + <% end -%> + <%= link_to( + t("administrate.actions.edit"), + [:edit, namespace, resource], + class: "action-edit", + ) if show_action? :edit, resource%><%= link_to( + t("administrate.actions.destroy"), + [namespace, resource], + class: "text-color-red", + method: :delete, + data: { confirm: t("administrate.actions.confirm") } + ) if show_action? :destroy, resource %>
diff --git a/app/views/super_admin/application/_flashes.html.erb b/app/views/super_admin/application/_flashes.html.erb new file mode 100644 index 000000000..8033c8d23 --- /dev/null +++ b/app/views/super_admin/application/_flashes.html.erb @@ -0,0 +1,20 @@ +<%# +# Flash Partial + +This partial renders flash messages on every page. + +## Relevant Helpers: + +- `flash`: + Returns a hash, + where the keys are the type of flash (alert, error, notice, etc) + and the values are the message to be displayed. +%> + +<% if flash.any? %> +
+ <% flash.each do |key, value| -%> +
<%= value.to_s.html_safe %>
+ <% end -%> +
+<% end %> diff --git a/app/views/super_admin/application/_icons.html.erb b/app/views/super_admin/application/_icons.html.erb new file mode 100644 index 000000000..1add01cad --- /dev/null +++ b/app/views/super_admin/application/_icons.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/app/views/super_admin/application/_javascript.html.erb b/app/views/super_admin/application/_javascript.html.erb new file mode 100644 index 000000000..5197fe655 --- /dev/null +++ b/app/views/super_admin/application/_javascript.html.erb @@ -0,0 +1,21 @@ +<%# +# Javascript Partial + +This partial imports the necessary javascript on each page. +By default, it includes the application JS, +but each page can define additional JS sources +by providing a `content_for(:javascript)` block. +%> + +<% Administrate::Engine.javascripts.each do |js_path| %> + <%= javascript_include_tag js_path %> +<% end %> + +<%= yield :javascript %> + +<% if Rails.env.test? %> + <%= javascript_tag do %> + $.fx.off = true; + $.ajaxSetup({ async: false }); + <% end %> +<% end %> diff --git a/app/views/super_admin/application/_navigation.html.erb b/app/views/super_admin/application/_navigation.html.erb index ca8adb494..005bd8cb8 100644 --- a/app/views/super_admin/application/_navigation.html.erb +++ b/app/views/super_admin/application/_navigation.html.erb @@ -10,21 +10,43 @@ as defined by the routes in the `admin/` namespace <%= javascript_pack_tag 'superadmin_pages' %> <%= stylesheet_pack_tag 'superadmin_pages' %> +<% + sidebar_icons = { + accounts: 'ion ion-briefcase', + users: 'ion ion-person-stalker', + super_admins: 'ion ion-unlocked', + access_tokens: 'ion-key' + } +%> - + + + + diff --git a/app/views/super_admin/application/_stylesheet.html.erb b/app/views/super_admin/application/_stylesheet.html.erb new file mode 100644 index 000000000..7b7bb7e5b --- /dev/null +++ b/app/views/super_admin/application/_stylesheet.html.erb @@ -0,0 +1,14 @@ +<%# +# Stylesheet Partial + +This partial imports the necessary stylesheets on each page. +By default, it includes the application CSS, +but each page can define additional CSS sources +by providing a `content_for(:stylesheet)` block. +%> + +<% Administrate::Engine.stylesheets.each do |css_path| %> + <%= stylesheet_link_tag css_path %> +<% end %> + +<%= yield :stylesheet %> diff --git a/app/views/super_admin/application/index.html.erb b/app/views/super_admin/application/index.html.erb new file mode 100644 index 000000000..80a5ae24c --- /dev/null +++ b/app/views/super_admin/application/index.html.erb @@ -0,0 +1,66 @@ +<%# +# Index + +This view is the template for the index page. +It is responsible for rendering the search bar, header and pagination. +It renders the `_table` partial to display details about the resources. + +## Local variables: + +- `page`: + An instance of [Administrate::Page::Collection][1]. + Contains helper methods to help display a table, + and knows which attributes should be displayed in the resource's table. +- `resources`: + An instance of `ActiveRecord::Relation` containing the resources + that match the user's search criteria. + By default, these resources are passed to the table partial to be displayed. +- `search_term`: + A string containing the term the user has searched for, if any. +- `show_search_bar`: + A boolean that determines if the search bar should be shown. + +[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection +%> + +<% content_for(:title) do %> + <%= display_resource_name(page.resource_name) %> +<% end %> + + + +
+ <%= render( + "collection", + collection_presenter: page, + collection_field_name: resource_name, + page: page, + resources: resources, + table_title: "page-title" + ) %> + + <%= paginate resources %> +
diff --git a/app/views/super_admin/dashboard/index.html.erb b/app/views/super_admin/dashboard/index.html.erb new file mode 100644 index 000000000..967c00e55 --- /dev/null +++ b/app/views/super_admin/dashboard/index.html.erb @@ -0,0 +1,69 @@ +<%# +# Index + +This view is the template for the index page. +It is responsible for rendering the search bar, header and pagination. +It renders the `_table` partial to display details about the resources. + +## Local variables: + +- `page`: + An instance of [Administrate::Page::Collection][1]. + Contains helper methods to help display a table, + and knows which attributes should be displayed in the resource's table. +- `resources`: + An instance of `ActiveRecord::Relation` containing the resources + that match the user's search criteria. + By default, these resources are passed to the table partial to be displayed. +- `search_term`: + A string containing the term the user has searched for, if any. +- `show_search_bar`: + A boolean that determines if the search bar should be shown. + +[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection +%> +<%= javascript_include_tag "dashboardChart" %> + +<% content_for(:title) do %> + Admin Dashboard +<% end %> + + + +
+ +
+
+
<%= @accounts_count %>
+
Accounts
+
+
+
<%= @users_count %>
+
Users
+
+
+
<%= @inboxes_count %>
+
Inboxes
+
+
+
<%= @conversations_count %>
+
Conversations
+
+
+
<%= @messages_count %>
+
Messages
+
+
+ +
+ +
+<%= javascript_tag do -%> +drawSuperAdminDashboard(<%= @data.to_json.html_safe -%>) +<% end -%> + +
diff --git a/app/views/super_admin/users/_collection.html.erb b/app/views/super_admin/users/_collection.html.erb new file mode 100644 index 000000000..3f7aa489c --- /dev/null +++ b/app/views/super_admin/users/_collection.html.erb @@ -0,0 +1,95 @@ +<%# +# Collection + +This partial is used on the `index` and `show` pages +to display a collection of resources in an HTML table. + +## Local variables: + +- `collection_presenter`: + An instance of [Administrate::Page::Collection][1]. + The table presenter uses `ResourceDashboard::COLLECTION_ATTRIBUTES` to determine + the columns displayed in the table +- `resources`: + An ActiveModel::Relation collection of resources to be displayed in the table. + By default, the number of resources is limited by pagination + or by a hard limit to prevent excessive page load times + +[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection +%> + + + + + <% collection_presenter.attribute_types.each do |attr_name, attr_type| %> + + <% end %> + <% [valid_action?(:edit, collection_presenter.resource_name), + valid_action?(:destroy, collection_presenter.resource_name)].count(true).times do %> + + <% end %> + + + + + <% resources.each do |resource| %> + + <%= %(role=link data-url=#{polymorphic_path([namespace, resource])}) %> + <% end %> + > + <% collection_presenter.attributes_for(resource).each do |attribute| %> + + <% end %> + + <% if valid_action? :edit, collection_presenter.resource_name %> + + <% end %> + + <% if valid_action? :destroy, collection_presenter.resource_name %> + + <% end %> + + <% end %> + +
+ <%= link_to(sanitized_order_params(page, collection_field_name).merge( + collection_presenter.order_params_for(attr_name, key: collection_field_name) + )) do %> + <%= t( + "helpers.label.#{collection_presenter.resource_name}.#{attr_name}", + default: attr_name.to_s, + ).titleize %> + <% if collection_presenter.ordered_by?(attr_name) %> + + + + <% end %> + <% end %> +
+ <% if show_action? :show, resource -%> + + <%= render_field attribute %> + + <% end -%> + <%= link_to( + t("administrate.actions.edit"), + [:edit, namespace, resource], + class: "action-edit", + ) if show_action? :edit, resource%><%= link_to( + t("administrate.actions.destroy"), + [namespace, resource], + class: "text-color-red", + method: :delete, + data: { confirm: t("administrate.actions.confirm") } + ) if show_action? :destroy, resource %>
diff --git a/app/views/super_admin/users/index.html.erb b/app/views/super_admin/users/index.html.erb new file mode 100644 index 000000000..80a5ae24c --- /dev/null +++ b/app/views/super_admin/users/index.html.erb @@ -0,0 +1,66 @@ +<%# +# Index + +This view is the template for the index page. +It is responsible for rendering the search bar, header and pagination. +It renders the `_table` partial to display details about the resources. + +## Local variables: + +- `page`: + An instance of [Administrate::Page::Collection][1]. + Contains helper methods to help display a table, + and knows which attributes should be displayed in the resource's table. +- `resources`: + An instance of `ActiveRecord::Relation` containing the resources + that match the user's search criteria. + By default, these resources are passed to the table partial to be displayed. +- `search_term`: + A string containing the term the user has searched for, if any. +- `show_search_bar`: + A boolean that determines if the search bar should be shown. + +[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection +%> + +<% content_for(:title) do %> + <%= display_resource_name(page.resource_name) %> +<% end %> + + + +
+ <%= render( + "collection", + collection_presenter: page, + collection_field_name: resource_name, + page: page, + resources: resources, + table_title: "page-title" + ) %> + + <%= paginate resources %> +
diff --git a/app/views/super_admin/users/show.html.erb b/app/views/super_admin/users/show.html.erb new file mode 100644 index 000000000..645fdfe19 --- /dev/null +++ b/app/views/super_admin/users/show.html.erb @@ -0,0 +1,88 @@ +<%# +# Show + +This view is the template for the show page. +It renders the attributes of a resource, +as well as a link to its edit page. + +## Local variables: + +- `page`: + An instance of [Administrate::Page::Show][1]. + Contains methods for accessing the resource to be displayed on the page, + as well as helpers for describing how each attribute of the resource + should be displayed. + +[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Show +%> + +<% content_for(:title) { t("administrate.actions.show_resource", name: page.page_title) } %> + + + +
+
+ <% page.attributes.each do |attribute| %> +
+ <%= t( + "helpers.label.#{resource_name}.#{attribute.name}", + default: attribute.name.titleize, + ) %> +
+ +
<%= render_field attribute, page: page %>
+ <% end %> +
+
+ +
+ <% account_user_page = Administrate::Page::Form.new(AccountUserDashboard.new, AccountUser.new) %> + <%= form_for([namespace, account_user_page.resource], html: { class: "form" }) do |f| %> + <% if account_user_page.resource.errors.any? %> +
+

+ <%= t( + "administrate.form.errors", + pluralized_errors: pluralize(account_user_page.resource.errors.count, t("administrate.form.error")), + resource_name: display_resource_name(account_user_page.resource_name) + ) %> +

+ + +
+ <% end %> + + <% account_user_page.attributes.each do |attribute| -%> + <% if attribute.name == "user" %> + <%= f.hidden_field('user_id', value: page.resource.id) %> + <%= f.hidden_field('redirect_to', value: 'user') %> + <% else %> +
+ <%= render_field attribute, f: f %> +
+ <% end %> + <% end -%> + +
+ <%= f.submit %> +
+<% end %> + +
diff --git a/config/routes.rb b/config/routes.rb index 7a0f437e0..efb621ffe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -172,13 +172,13 @@ Rails.application.routes.draw do devise_scope :super_admin do get 'super_admin/logout', to: 'super_admin/devise/sessions#destroy' namespace :super_admin do - resources :users resources :accounts - resources :account_users + resources :account_users, only: [:new, :create, :destroy] + resources :users resources :super_admins - resources :access_tokens + resources :access_tokens, only: [:index, :show] - root to: 'users#index' + root to: 'dashboard#index' end authenticated :super_admin do mount Sidekiq::Web => '/monitoring/sidekiq' diff --git a/db/schema.rb b/db/schema.rb index cbc4c5d87..f467a623a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -13,6 +13,7 @@ ActiveRecord::Schema.define(version: 2020_05_22_115645) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_stat_statements" enable_extension "pgcrypto" enable_extension "plpgsql" diff --git a/public/admin/avatar.png b/public/admin/avatar.png new file mode 100644 index 000000000..a6c119079 Binary files /dev/null and b/public/admin/avatar.png differ diff --git a/spec/controllers/super_admin/access_tokens_controller_spec.rb b/spec/controllers/super_admin/access_tokens_controller_spec.rb index c1f38ea23..bb516bc04 100644 --- a/spec/controllers/super_admin/access_tokens_controller_spec.rb +++ b/spec/controllers/super_admin/access_tokens_controller_spec.rb @@ -16,7 +16,6 @@ RSpec.describe 'Super Admin access tokens API', type: :request do sign_in super_admin get '/super_admin/access_tokens' expect(response).to have_http_status(:success) - expect(response.body).to include('New access token') expect(response.body).to include(super_admin.access_token.token) end end diff --git a/spec/controllers/super_admin/account_users_controller_spec.rb b/spec/controllers/super_admin/account_users_controller_spec.rb index b3df738aa..64b4d0e71 100644 --- a/spec/controllers/super_admin/account_users_controller_spec.rb +++ b/spec/controllers/super_admin/account_users_controller_spec.rb @@ -3,23 +3,19 @@ require 'rails_helper' RSpec.describe 'Super Admin Account Users API', type: :request do let(:super_admin) { create(:super_admin) } - describe 'GET /super_admin/account_users' do + describe 'GET /super_admin/account_users/new' do context 'when it is an unauthenticated super admin' do it 'returns unauthorized' do - get '/super_admin/account_users' + get '/super_admin/account_users/new' expect(response).to have_http_status(:redirect) end end context 'when it is an authenticated super admin' do - let!(:account_user) { create(:account_user) } - - it 'shows the list of account users' do + it 'shows the account user create page' do sign_in super_admin - get '/super_admin/account_users' + get '/super_admin/account_users/new' expect(response).to have_http_status(:success) - expect(response.body).to include(account_user.account.id.to_s) - expect(response.body).to include(account_user.user.id.to_s) end end end diff --git a/spec/controllers/super_admin_controller_spec.rb b/spec/controllers/super_admin_controller_spec.rb index ea0697dd9..8fbece081 100644 --- a/spec/controllers/super_admin_controller_spec.rb +++ b/spec/controllers/super_admin_controller_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Super Admin', type: :request do sign_in super_admin get '/super_admin' expect(response).to have_http_status(:success) - expect(response.body).to include('New user') + expect(response.body).to include('Dashboard') sign_out super_admin get '/super_admin'