Dashboard
+Dashboard
+Servers
{servers.length}
- {servers.length} connected + {serversWithAppsCount} connected
diff --git a/README.md b/README.md index f99cd06..1978622 100644 --- a/README.md +++ b/README.md @@ -4,5 +4,4 @@ This is the frontend of a web wrapper for Coop Clouds abra CLI, letting users se ## Still a work in progess! -## This is built with react, typescript, scss, and vite - +## This is built with react, typescript, scss, and vite \ No newline at end of file diff --git a/src/assets/scss/_global.scss b/src/assets/scss/_global.scss index 9ee2cf2..9fbde7a 100644 --- a/src/assets/scss/_global.scss +++ b/src/assets/scss/_global.scss @@ -1,3 +1,6 @@ +@use './variables' as *; +@use './mixins' as *; + body { margin: 0; padding: 0; @@ -9,3 +12,262 @@ body { width: 100%; min-height: 100vh; } + +// global page layout styles +.page-wrapper { + min-height: 100vh; + background-color: $bg-secondary; +} + +.page-content { + max-width: 1600px; + margin: 0 auto; + padding: $spacing-2xl $spacing-xl; + + @media (max-width: 768px) { + padding: $spacing-xl $spacing-md; + } +} + +.page-header { + margin-bottom: $spacing-2xl; + + h1 { + font-size: $font-size-3xl; + margin: 0 0 $spacing-sm; + color: $text-primary; + } + + .subtitle { + font-size: $font-size-lg; + color: $text-secondary; + margin: 0; + } +} + +// global state styles +.loading, +.error { + text-align: center; + padding: $spacing-3xl; + font-size: $font-size-lg; +} + +.loading { + color: $text-secondary; +} + +.error { + color: $error; +} + +// Stats grid and cards +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: $spacing-lg; + margin-bottom: $spacing-2xl; +} + +.stat-card { + @include card; + display: flex; + align-items: center; + gap: $spacing-lg; + padding: $spacing-xl; + transition: transform $transition-base, box-shadow $transition-base; + + &:hover { + transform: translateY(-2px); + box-shadow: $shadow-lg; + } + + // Modifier classes for colored borders + &.upgrade { + border-left: 4px solid $primary-light; + } + + &.chaos { + border-left: 4px solid $primary-dark; + } + + &.primary { + border-left: 4px solid $primary; + } + + .stat-icon { + font-size: 2rem; + } + + .stat-info { + flex: 1; + + .stat-number { + font-size: $font-size-3xl; + font-weight: $font-weight-bold; + color: $text-primary; + margin: 0; + line-height: 1; + } + + .stat-label { + font-size: $font-size-sm; + color: $text-secondary; + margin: $spacing-xs 0 0; + text-transform: uppercase; + letter-spacing: 0.5px; + } + } +} + +// Filters component +.filters { + @include card; + display: flex; + gap: $spacing-md; + flex-wrap: wrap; + margin-bottom: $spacing-xl; + + @media (max-width: 768px) { + flex-direction: column; + } + + .search-input { + background-color: $bg-primary; + flex: 1; + min-width: 250px; + padding: $spacing-sm $spacing-md; + border: 2px solid $border-color; + border-radius: $radius-md; + font-size: $font-size-base; + transition: border-color $transition-base; + color: $text-primary; + + &::placeholder { + color: $text-muted; + } + + &:focus { + outline: none; + border-color: $primary; + } + } + + select { + padding: $spacing-sm $spacing-md; + border: 2px solid $border-color; + border-radius: $radius-md; + font-size: $font-size-base; + background-color: $bg-primary; + color: $text-primary; + cursor: pointer; + transition: border-color $transition-base; + + &:focus { + outline: none; + border-color: $primary; + } + } + + .checkbox-filter { + display: flex; + align-items: center; + gap: $spacing-sm; + cursor: pointer; + user-select: none; + + input[type="checkbox"] { + cursor: pointer; + width: 18px; + height: 18px; + } + + span { + font-size: $font-size-sm; + color: $text-primary; + } + } +} + +// Status badges +.status-badge { + display: inline-block; + padding: $spacing-xs $spacing-sm; + border-radius: $radius-full; + font-size: $font-size-xs; + font-weight: $font-weight-semibold; + text-transform: capitalize; + + &.status-deployed { + background-color: rgba($success, 0.1); + color: $success; + } + + &.status-stopped { + background-color: rgba($text-muted, 0.1); + color: $text-muted; + } + + &.status-error { + background-color: rgba($error, 0.1); + color: $error; + } +} + +// global badge styles +.recipe-badge, +.server-badge { + display: inline-block; + padding: $spacing-xs $spacing-sm; + border-radius: $radius-sm; + font-size: $font-size-sm; + font-weight: $font-weight-medium; +} + +.recipe-badge { + background-color: rgba($info, 0.1); + color: $info; +} + +.server-badge { + background-color: rgba($text-secondary, 0.1); + color: $text-secondary; +} + +// Results count +.results-count { + text-align: center; + color: $text-secondary; + font-size: $font-size-sm; + padding: $spacing-md; +} + +// No results message +.no-results { + @include card; + text-align: center; + padding: $spacing-3xl; + color: $text-secondary; + grid-column: 1 / -1; +} + +// Navigation link button (for clickable cards) +.nav-link { + all: unset; + cursor: pointer; + width: 100%; + display: block; + + &:focus-visible { + outline: 2px solid $primary; + outline-offset: 2px; + border-radius: $radius-md; + } +} + +.bland-button { + background: none; + border: none; + padding: 0; + margin: 0; +} diff --git a/src/assets/scss/_mixins.scss b/src/assets/scss/_mixins.scss index a280288..ab73615 100644 --- a/src/assets/scss/_mixins.scss +++ b/src/assets/scss/_mixins.scss @@ -45,7 +45,7 @@ // Gradient background @mixin gradient-primary { - background: linear-gradient(135deg, $primary 0%, $primary-dark 100%); + background: linear-gradient(135deg, $primary 0%, $primary-light 100%); } // Truncate text diff --git a/src/assets/scss/_variables.scss b/src/assets/scss/_variables.scss index 5902d3c..0201eba 100644 --- a/src/assets/scss/_variables.scss +++ b/src/assets/scss/_variables.scss @@ -1,6 +1,7 @@ // Colors $primary: #EFEFEF; -$primary-dark: #ff4f88; +$primary-dark: #6A9CFF; +$primary-light: #ff4f88; $secondary: #363636; $success: #10b981; diff --git a/src/components/Header/_Header.scss b/src/components/Header/_Header.scss index 4de3fe8..9b8f02b 100644 --- a/src/components/Header/_Header.scss +++ b/src/components/Header/_Header.scss @@ -2,7 +2,7 @@ @use '../../assets/scss/mixins' as *; .layout-header { - background-color: $primary-dark; + background-color: $primary-light; color: $text-primary; padding: $spacing-lg 0; box-shadow: $shadow-lg; diff --git a/src/index.scss b/src/index.scss index 940ad45..f13b8e4 100644 --- a/src/index.scss +++ b/src/index.scss @@ -1,15 +1,18 @@ @use './assets/scss/variables' as *; @use './assets/scss/mixins' as *; +@use './assets/scss/global' as *; + +// Global root styles :root { line-height: 1.5; font-weight: 400; font-family: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', - 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', - 'Helvetica Neue', sans-serif; + 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', + 'Helvetica Neue', sans-serif; color-scheme: light dark; color: $text-primary; - background-color: $primary; + background-color: $bg-primary; font-synthesis: none; text-rendering: optimizeLegibility; @@ -17,13 +20,9 @@ -moz-osx-font-smoothing: grayscale; } -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; +// Global element resets +* { + box-sizing: border-box; } body { @@ -32,51 +31,63 @@ body { min-height: 100vh; } -h1 { - font-size: 3.2em; - line-height: 1.1; - font-family: 'Lora', serif; +// Global link styles +a { + font-weight: 500; + color: $primary; + text-decoration: none; + transition: color $transition-base; + + &:hover { + color: $primary-light; + } +} + +// Global heading styles +h1, h2, h3, h4, h5, h6 { color: $text-primary; + margin: 0; } +h1 { + font-size: $font-size-3xl; + line-height: 1.1; +} + +h2 { + font-size: $font-size-2xl; + line-height: 1.2; +} + +h3 { + font-size: $font-size-xl; + line-height: 1.3; +} + +// Global button styles button { - border-radius: 8px; + border-radius: $radius-md; border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; + padding: $spacing-sm $spacing-lg; + font-size: $font-size-base; + font-weight: $font-weight-medium; font-family: inherit; - background-color: $primary-dark; + background-color: $primary; + color: white; cursor: pointer; - transition: border-color 0.25s; -} -.bland-button{ - border-radius: 8px; - border: 0px; - padding: 0em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} + transition: all $transition-base; -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: $primary; + &:hover { + background-color: $primary-light; } - a:hover { - color: #747bff; + + &:focus-visible { + outline: 2px solid $primary; + outline-offset: 2px; } - button { - background-color: $primary; + + &:disabled { + opacity: 0.5; + cursor: not-allowed; } } diff --git a/src/routes/Authenticated/Apps/App.scss b/src/routes/Authenticated/Apps/App.scss index a43aa8a..17a4c7a 100644 --- a/src/routes/Authenticated/Apps/App.scss +++ b/src/routes/Authenticated/Apps/App.scss @@ -253,7 +253,7 @@ } .domain-link { - color: #0066cc; + color: $primary-dark; text-decoration: none; font-size: 14px; diff --git a/src/routes/Authenticated/Apps/Apps.scss b/src/routes/Authenticated/Apps/Apps.scss index c32afa4..b1fdd1c 100644 --- a/src/routes/Authenticated/Apps/Apps.scss +++ b/src/routes/Authenticated/Apps/Apps.scss @@ -1,149 +1,17 @@ @use '../../../assets/scss/variables' as *; @use '../../../assets/scss/mixins' as *; +@use '../../../assets/scss/global' as *; +// Extend global page wrapper .apps-page { - min-height: 100vh; - background-color: $bg-secondary; + @extend .page-wrapper; } .apps-content { - max-width: 1600px; - margin: 0 auto; - padding: $spacing-2xl $spacing-xl; - - @media (max-width: 768px) { - padding: $spacing-xl $spacing-md; - } -} - -.page-header { - margin-bottom: $spacing-2xl; - - h1 { - font-size: $font-size-3xl; - margin: 0 0 $spacing-sm; - color: $text-primary; - } - - .subtitle { - font-size: $font-size-lg; - color: $text-secondary; - margin: 0; - } -} - -.stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: $spacing-lg; - margin-bottom: $spacing-2xl; -} - -.stat-card { - @include card; - display: flex; - align-items: center; - gap: $spacing-lg; - padding: $spacing-xl; - transition: transform $transition-base, box-shadow $transition-base; - - &:hover { - transform: translateY(-2px); - box-shadow: $shadow-lg; - } - - &.upgrade { - border-left: 4px solid $warning; - } - - &.chaos { - border-left: 4px solid $info; - } - - .stat-icon { - font-size: 2rem; - } - - .stat-info { - .stat-number { - font-size: $font-size-3xl; - font-weight: $font-weight-bold; - color: $text-primary; - margin: 0; - line-height: 1; - } - - .stat-label { - font-size: $font-size-sm; - color: $text-secondary; - margin: $spacing-xs 0 0; - text-transform: uppercase; - letter-spacing: 0.5px; - } - } -} - -.filters { - @include card; - display: flex; - gap: $spacing-md; - flex-wrap: wrap; - margin-bottom: $spacing-xl; - - @media (max-width: 768px) { - flex-direction: column; - } - - .search-input { - flex: 1; - min-width: 250px; - padding: $spacing-sm $spacing-md; - border: 2px solid $border-color; - border-radius: $radius-md; - font-size: $font-size-base; - transition: border-color $transition-base; - - &:focus { - outline: none; - border-color: $primary; - } - } - - select { - padding: $spacing-sm $spacing-md; - border: 2px solid $border-color; - border-radius: $radius-md; - font-size: $font-size-base; - background-color: white; - cursor: pointer; - transition: border-color $transition-base; - - &:focus { - outline: none; - border-color: $primary; - } - } - - .checkbox-filter { - display: flex; - align-items: center; - gap: $spacing-sm; - cursor: pointer; - user-select: none; - - input[type="checkbox"] { - cursor: pointer; - width: 18px; - height: 18px; - } - - span { - font-size: $font-size-sm; - color: $text-primary; - } - } + @extend .page-content; } +// Apps table specific styles .apps-table-container { @include card; overflow-x: auto; @@ -197,6 +65,7 @@ } } +// App table cell styles .app-name-cell { .app-name { font-weight: $font-weight-medium; @@ -204,39 +73,20 @@ } } -.recipe-badge { - display: inline-block; - padding: $spacing-xs $spacing-sm; - background-color: rgba($info, 0.1); - // color: darken($info, 20%); - border-radius: $radius-sm; - font-size: $font-size-sm; - font-weight: $font-weight-medium; -} - .domain-link { color: $primary; text-decoration: none; transition: color $transition-base; &:hover { - color: $primary-dark; + color: $primary-light; text-decoration: underline; } } .no-domain { color: $text-muted; -} - -.server-badge { - display: inline-block; - padding: $spacing-xs $spacing-sm; - background-color: rgba($text-secondary, 0.1); - color: $text-secondary; - border-radius: $radius-sm; - font-size: $font-size-sm; - font-weight: $font-weight-medium; + font-style: italic; } .version-cell { @@ -247,6 +97,7 @@ .version { font-family: monospace; font-size: $font-size-sm; + color: $text-primary; } .chaos-badge, @@ -255,40 +106,19 @@ } } -.status-badge { - display: inline-block; - padding: $spacing-xs $spacing-sm; - border-radius: $radius-full; - font-size: $font-size-xs; - font-weight: $font-weight-semibold; - text-transform: capitalize; - - &.status-deployed { - background-color: rgba($success, 0.1); - // color: darken($success, 20%); - } - - &.status-stopped { - background-color: rgba($text-muted, 0.1); - color: $text-muted; - } - - &.status-error { - background-color: rgba($error, 0.1); - // color: darken($error, 10%); - } -} - +// Action buttons .actions { display: flex; gap: $spacing-sm; .action-btn { + background: none; border: 1px solid $border-color; padding: $spacing-xs $spacing-sm; border-radius: $radius-sm; cursor: pointer; font-size: $font-size-base; + color: $text-primary; transition: all $transition-base; &:hover { @@ -298,28 +128,7 @@ &.upgrade { border-color: $warning; + color: $warning; } } } - -.results-count { - text-align: center; - color: $text-secondary; - font-size: $font-size-sm; - padding: $spacing-md; -} - -.loading, -.error { - text-align: center; - padding: $spacing-3xl; - font-size: $font-size-lg; -} - -.loading { - color: $text-secondary; -} - -.error { - color: $error; -} \ No newline at end of file diff --git a/src/routes/Authenticated/Dashboard/Dashboard.tsx b/src/routes/Authenticated/Dashboard/Dashboard.tsx index 96b3317..3788542 100644 --- a/src/routes/Authenticated/Dashboard/Dashboard.tsx +++ b/src/routes/Authenticated/Dashboard/Dashboard.tsx @@ -51,6 +51,10 @@ export const Dashboard: React.FC = () => { fetchData(); }, [isMockMode]); + // Calculate stats + const deployedAppsCount = apps.filter(a => a.status === 'deployed').length; + const serversWithAppsCount = new Set(apps.map(a => a.server)).size; + if (loading) { return (
{servers.length}
- {servers.length} connected + {serversWithAppsCount} connected
{app.domain || 'No domain'}
@@ -121,4 +131,4 @@ export const Dashboard: React.FC = () => {