SPA zbudowane w React 19 + Vite 8 z pełnym zestawem funkcjonalności: autentykacja z 2FA, kreator rezerwacji, panel admina, analityka, GraphQL (Apollo Client + SchemaLink), React Query, Storybook, testy jednostkowe (Vitest + RTL) i e2e (Playwright).
478 lines
12 KiB
SCSS
478 lines
12 KiB
SCSS
@use '../styles/variables' as *;
|
|
|
|
.rp {
|
|
min-height: 100vh;
|
|
background: var(--clr-bg);
|
|
|
|
// ── Header ────────────────────────────────────────────────────────────────
|
|
|
|
&__header {
|
|
background: var(--clr-surface);
|
|
border-bottom: 1px solid var(--clr-border);
|
|
box-shadow: $shadow-sm;
|
|
}
|
|
|
|
&__header-inner {
|
|
max-width: $container-max-width;
|
|
margin-inline: auto;
|
|
padding: $spacing-4 $spacing-6;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: $spacing-4;
|
|
}
|
|
|
|
&__header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: $spacing-5;
|
|
}
|
|
|
|
&__back {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: $spacing-1;
|
|
font-size: $font-size-sm;
|
|
font-weight: $font-weight-medium;
|
|
color: var(--clr-text-secondary);
|
|
text-decoration: none;
|
|
padding: $spacing-1 $spacing-2;
|
|
border-radius: $radius-base;
|
|
transition: color $transition-fast, background $transition-fast;
|
|
white-space: nowrap;
|
|
|
|
&:hover {
|
|
color: var(--clr-text);
|
|
background: var(--clr-surface-raised);
|
|
}
|
|
}
|
|
|
|
&__title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: $spacing-2;
|
|
font-size: $font-size-xl;
|
|
font-weight: $font-weight-semibold;
|
|
color: var(--clr-text);
|
|
margin: 0;
|
|
}
|
|
|
|
&__subtitle {
|
|
font-size: $font-size-sm;
|
|
color: var(--clr-text-secondary);
|
|
margin: $spacing-1 0 0;
|
|
}
|
|
|
|
&__header-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: $spacing-3;
|
|
}
|
|
|
|
&__logout {
|
|
padding: $spacing-2 $spacing-4;
|
|
border: 1px solid var(--clr-border);
|
|
border-radius: $radius-base;
|
|
background: transparent;
|
|
font-size: $font-size-sm;
|
|
font-weight: $font-weight-medium;
|
|
font-family: $font-family-base;
|
|
color: var(--clr-text-secondary);
|
|
cursor: pointer;
|
|
transition: background $transition-fast, color $transition-fast;
|
|
|
|
&:hover { background: var(--clr-surface-raised); color: var(--clr-text); }
|
|
}
|
|
|
|
// ── Main ──────────────────────────────────────────────────────────────────
|
|
|
|
&__main {
|
|
max-width: 780px;
|
|
margin-inline: auto;
|
|
padding: $spacing-8 $spacing-6;
|
|
}
|
|
|
|
// ── Filter tabs ───────────────────────────────────────────────────────────
|
|
|
|
&__tabs {
|
|
display: flex;
|
|
gap: $spacing-2;
|
|
margin-bottom: $spacing-6;
|
|
border-bottom: 1px solid var(--clr-border);
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
&__tab {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: $spacing-2;
|
|
padding: $spacing-2 $spacing-4;
|
|
background: none;
|
|
border: none;
|
|
border-bottom: 2px solid transparent;
|
|
margin-bottom: -1px;
|
|
font-size: $font-size-sm;
|
|
font-weight: $font-weight-medium;
|
|
font-family: $font-family-base;
|
|
color: var(--clr-text-secondary);
|
|
cursor: pointer;
|
|
transition: color $transition-fast, border-color $transition-fast;
|
|
|
|
&:hover { color: var(--clr-text); }
|
|
|
|
&--active {
|
|
color: var(--clr-primary, #{$color-primary});
|
|
border-bottom-color: var(--clr-primary, #{$color-primary});
|
|
}
|
|
}
|
|
|
|
&__tab-count {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 18px;
|
|
height: 18px;
|
|
padding: 0 5px;
|
|
background: var(--clr-surface-raised, #{$gray-100});
|
|
border-radius: $radius-full;
|
|
font-size: 11px;
|
|
font-weight: $font-weight-semibold;
|
|
color: var(--clr-text-secondary);
|
|
|
|
.rp__tab--active & {
|
|
background: rgba($primary-500, 0.12);
|
|
color: $primary-600;
|
|
}
|
|
}
|
|
|
|
&__msg {
|
|
text-align: center;
|
|
padding: $spacing-16 0;
|
|
font-size: $font-size-sm;
|
|
color: var(--clr-text-secondary);
|
|
}
|
|
|
|
&__list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $spacing-3;
|
|
}
|
|
}
|
|
|
|
// ── Reservation card ─────────────────────────────────────────────────────────
|
|
|
|
.res-card {
|
|
background: var(--clr-surface);
|
|
border: 1px solid var(--clr-border);
|
|
border-radius: $radius-xl;
|
|
overflow: hidden;
|
|
transition: box-shadow $transition-fast;
|
|
|
|
&:hover { box-shadow: $shadow-md; }
|
|
|
|
&--cancelled {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
&--upcoming {
|
|
border-left: 3px solid $primary-400;
|
|
}
|
|
|
|
&--highlighted {
|
|
box-shadow: 0 0 0 2px $primary-400, $shadow-md;
|
|
animation: res-card-pulse 1.2s ease-out;
|
|
}
|
|
// @keyframes z wieloma klatkami
|
|
// które zmieniają box-shadow z 0 0 0 4px rgba($primary-400, 0.5),
|
|
// $shadow-md na 0 0 0 2px $primary-400, $shadow-md
|
|
// animacja trwa 1.2s i jest ease-out
|
|
// jest to animacja pulsowania karty rezerwacji, która jest wyróżniona
|
|
@keyframes res-card-pulse {
|
|
0% { box-shadow: 0 0 0 4px rgba($primary-400, 0.5), $shadow-md; }
|
|
100% { box-shadow: 0 0 0 2px $primary-400, $shadow-md; }
|
|
}
|
|
|
|
// ── Toggle button (collapsed header) ──────────────────────────────────────
|
|
|
|
&__toggle {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: $spacing-4;
|
|
padding: $spacing-4 $spacing-5;
|
|
background: none;
|
|
border: none;
|
|
font-family: $font-family-base;
|
|
cursor: pointer;
|
|
text-align: left;
|
|
color: var(--clr-text);
|
|
transition: background $transition-fast;
|
|
|
|
&:hover { background: var(--clr-surface-raised, #{$gray-50}); }
|
|
}
|
|
|
|
&__summary {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $spacing-1;
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
&__name {
|
|
font-size: $font-size-sm;
|
|
font-weight: $font-weight-semibold;
|
|
color: var(--clr-text);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
&__meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: $spacing-3;
|
|
}
|
|
|
|
&__meta-item {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: $font-size-xs;
|
|
color: var(--clr-text-secondary);
|
|
}
|
|
|
|
&__right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: $spacing-3;
|
|
flex-shrink: 0;
|
|
color: var(--clr-text-secondary);
|
|
}
|
|
|
|
// ── Expanded body ─────────────────────────────────────────────────────────
|
|
|
|
&__body {
|
|
border-top: 1px solid var(--clr-border);
|
|
padding: $spacing-5;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $spacing-4;
|
|
}
|
|
|
|
&__dl {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 0;
|
|
}
|
|
|
|
&__dl-row {
|
|
display: grid;
|
|
grid-template-columns: 160px 1fr;
|
|
gap: $spacing-3;
|
|
padding: $spacing-2 0;
|
|
border-bottom: 1px solid var(--clr-border);
|
|
|
|
&:last-child { border-bottom: none; }
|
|
|
|
dt {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: $spacing-1;
|
|
font-size: $font-size-xs;
|
|
font-weight: $font-weight-medium;
|
|
color: var(--clr-text-secondary);
|
|
}
|
|
|
|
dd {
|
|
font-size: $font-size-xs;
|
|
color: var(--clr-text);
|
|
word-break: break-all;
|
|
}
|
|
}
|
|
|
|
&__mono {
|
|
font-family: $font-family-mono;
|
|
font-size: 11px;
|
|
}
|
|
|
|
// ── Actions row ───────────────────────────────────────────────────────────
|
|
|
|
&__actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: $spacing-3;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
&__pdf-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: $spacing-1;
|
|
padding: $spacing-2 $spacing-3;
|
|
background: transparent;
|
|
border: 1px solid var(--clr-border);
|
|
border-radius: $radius-full;
|
|
font-size: $font-size-xs;
|
|
font-weight: $font-weight-medium;
|
|
font-family: $font-family-base;
|
|
color: var(--clr-text-secondary);
|
|
cursor: pointer;
|
|
transition: background $transition-fast, color $transition-fast;
|
|
|
|
&:hover {
|
|
background: var(--clr-surface-raised);
|
|
color: var(--clr-text);
|
|
}
|
|
}
|
|
|
|
// ── Google Calendar link ──────────────────────────────────────────────────
|
|
|
|
&__cal-btn {
|
|
align-self: flex-start;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: $spacing-1;
|
|
padding: $spacing-2 $spacing-3;
|
|
background: transparent;
|
|
border: 1px solid $accent-200;
|
|
border-radius: $radius-full;
|
|
font-size: $font-size-xs;
|
|
font-weight: $font-weight-medium;
|
|
color: $accent-700;
|
|
text-decoration: none;
|
|
transition: background $transition-fast;
|
|
|
|
&:hover { background: $accent-50; }
|
|
}
|
|
|
|
// ── Re-book trigger ───────────────────────────────────────────────────────
|
|
|
|
&__rebook-btn {
|
|
align-self: flex-start;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: $spacing-1;
|
|
padding: $spacing-2 $spacing-3;
|
|
background: transparent;
|
|
border: 1px solid $accent-200;
|
|
border-radius: $radius-full;
|
|
font-size: $font-size-xs;
|
|
font-weight: $font-weight-medium;
|
|
font-family: $font-family-base;
|
|
color: $accent-700;
|
|
cursor: pointer;
|
|
transition: background $transition-fast;
|
|
|
|
&:hover { background: $accent-50; }
|
|
}
|
|
|
|
// ── Reschedule trigger ────────────────────────────────────────────────────
|
|
|
|
&__reschedule-btn {
|
|
align-self: flex-start;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: $spacing-1;
|
|
padding: $spacing-2 $spacing-3;
|
|
background: transparent;
|
|
border: 1px solid $primary-200;
|
|
border-radius: $radius-full;
|
|
font-size: $font-size-xs;
|
|
font-weight: $font-weight-medium;
|
|
font-family: $font-family-base;
|
|
color: $primary-600;
|
|
cursor: pointer;
|
|
transition: background $transition-fast;
|
|
|
|
&:hover { background: $primary-50; }
|
|
}
|
|
|
|
// ── Cancel trigger ────────────────────────────────────────────────────────
|
|
|
|
&__cancel-trigger {
|
|
align-self: flex-start;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: $spacing-1;
|
|
padding: $spacing-2 $spacing-3;
|
|
background: transparent;
|
|
border: 1px solid #fecaca;
|
|
border-radius: $radius-full;
|
|
font-size: $font-size-xs;
|
|
font-weight: $font-weight-medium;
|
|
font-family: $font-family-base;
|
|
color: $color-error;
|
|
cursor: pointer;
|
|
transition: background $transition-fast;
|
|
|
|
&:hover { background: #fef2f2; }
|
|
}
|
|
|
|
// ── Cancel confirmation ───────────────────────────────────────────────────
|
|
|
|
&__confirm {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $spacing-3;
|
|
padding: $spacing-4;
|
|
background: #fffbeb;
|
|
border: 1px solid #fde68a;
|
|
border-radius: $radius-lg;
|
|
}
|
|
|
|
&__confirm-icon {
|
|
color: #d97706;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
&__confirm-text {
|
|
font-size: $font-size-sm;
|
|
color: #92400e;
|
|
margin: 0;
|
|
}
|
|
|
|
&__confirm-btns {
|
|
display: flex;
|
|
gap: $spacing-3;
|
|
}
|
|
|
|
&__confirm-yes {
|
|
padding: $spacing-2 $spacing-4;
|
|
background: $color-error;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: $radius-base;
|
|
font-size: $font-size-sm;
|
|
font-weight: $font-weight-semibold;
|
|
font-family: $font-family-base;
|
|
cursor: pointer;
|
|
transition: opacity $transition-fast;
|
|
|
|
&:hover:not(:disabled) { opacity: 0.9; }
|
|
&:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
}
|
|
|
|
&__confirm-no {
|
|
padding: $spacing-2 $spacing-4;
|
|
background: transparent;
|
|
border: 1px solid var(--clr-border);
|
|
border-radius: $radius-base;
|
|
font-size: $font-size-sm;
|
|
font-weight: $font-weight-medium;
|
|
font-family: $font-family-base;
|
|
color: var(--clr-text-secondary);
|
|
cursor: pointer;
|
|
transition: background $transition-fast;
|
|
|
|
&:hover { background: var(--clr-surface-raised); }
|
|
}
|
|
|
|
&__reviews {
|
|
margin-top: $spacing-4;
|
|
padding-top: $spacing-4;
|
|
border-top: 1px solid var(--clr-border);
|
|
}
|
|
}
|