astro-component-kit 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -36
- package/package.json +17 -4
- package/src/components/index.ts +18 -202
- package/src/components/lib/Galaxy3D.astro +249 -137
- package/src/components/lib/app/AppSidebar.astro +249 -0
- package/src/components/lib/app/AppTopbar.astro +284 -0
- package/src/components/lib/app/ArrivalsBoard.astro +176 -0
- package/src/components/lib/app/AuthCard.astro +213 -0
- package/src/components/lib/app/DataTable.astro +346 -0
- package/src/components/lib/app/GuestProfileCard.astro +133 -0
- package/src/components/lib/app/HousekeepingBoard.astro +189 -0
- package/src/components/lib/app/PageHeader.astro +157 -0
- package/src/components/lib/app/PlanUsageCard.astro +112 -0
- package/src/components/lib/app/RoomRack.astro +207 -0
- package/src/components/lib/app/SettingsSection.astro +96 -0
- package/src/components/lib/app/SwitchRow.astro +71 -0
- package/src/components/lib/app/TeamMembers.astro +190 -0
- package/src/components/lib/app/app-icons.ts +52 -0
- package/src/components/lib/app/index.ts +19 -0
- package/src/components/lib/buttons/NeonBorderButton.astro +162 -0
- package/src/components/lib/buttons/RippleButton.astro +199 -0
- package/src/components/lib/buttons/SlideRevealButton.astro +159 -0
- package/src/components/lib/buttons/index.ts +4 -0
- package/src/components/lib/cards/GithubRepoCard.astro +176 -0
- package/src/components/lib/cards/PricingCompareMiniCard.astro +196 -0
- package/src/components/lib/cards/StatsTrendCard.astro +149 -0
- package/src/components/lib/cards/index.ts +3 -0
- package/src/components/lib/charts/ActivityRings.astro +105 -0
- package/src/components/lib/charts/AreaChart.astro +4 -4
- package/src/components/lib/charts/BarChart.astro +3 -3
- package/src/components/lib/charts/BoxPlotChart.astro +109 -0
- package/src/components/lib/charts/BubbleChart.astro +101 -0
- package/src/components/lib/charts/BulletChart.astro +160 -0
- package/src/components/lib/charts/BumpChart.astro +92 -0
- package/src/components/lib/charts/CandlestickChart.astro +105 -0
- package/src/components/lib/charts/ChartFrame.astro +373 -0
- package/src/components/lib/charts/DivergingBarChart.astro +98 -0
- package/src/components/lib/charts/DonutChart.astro +2 -2
- package/src/components/lib/charts/DumbbellChart.astro +92 -0
- package/src/components/lib/charts/ForecastLineChart.astro +125 -0
- package/src/components/lib/charts/FunnelChart.astro +122 -0
- package/src/components/lib/charts/GaugeChart.astro +111 -0
- package/src/components/lib/charts/GoalProgressChart.astro +97 -0
- package/src/components/lib/charts/GroupedBarChart.astro +125 -0
- package/src/components/lib/charts/HeatmapChart.astro +131 -0
- package/src/components/lib/charts/HistogramChart.astro +111 -0
- package/src/components/lib/charts/HorizontalBarChart.astro +155 -0
- package/src/components/lib/charts/KpiStatTile.astro +116 -0
- package/src/components/lib/charts/KpiTrendGrid.astro +60 -0
- package/src/components/lib/charts/LineChart.astro +160 -0
- package/src/components/lib/charts/LinearProgressGauge.astro +136 -0
- package/src/components/lib/charts/LollipopChart.astro +100 -0
- package/src/components/lib/charts/MeterGauge.astro +122 -0
- package/src/components/lib/charts/MetricDeltaTable.astro +125 -0
- package/src/components/lib/charts/PieChart.astro +135 -0
- package/src/components/lib/charts/PolarAreaChart.astro +85 -0
- package/src/components/lib/charts/PopulationPyramid.astro +122 -0
- package/src/components/lib/charts/PunchCardChart.astro +89 -0
- package/src/components/lib/charts/RadarChart.astro +94 -0
- package/src/components/lib/charts/RadarMetricChart.astro +168 -0
- package/src/components/lib/charts/RadialBarChart.astro +124 -0
- package/src/components/lib/charts/RangeAreaChart.astro +113 -0
- package/src/components/lib/charts/RetentionCohortTable.astro +126 -0
- package/src/components/lib/charts/SankeyChart.astro +128 -0
- package/src/components/lib/charts/ScatterPlot.astro +139 -0
- package/src/components/lib/charts/SegmentedBarChart.astro +119 -0
- package/src/components/lib/charts/SlopeChart.astro +105 -0
- package/src/components/lib/charts/SparklineChart.astro +87 -0
- package/src/components/lib/charts/StackedAreaChart.astro +130 -0
- package/src/components/lib/charts/StackedBarChart.astro +136 -0
- package/src/components/lib/charts/SunburstChart.astro +103 -0
- package/src/components/lib/charts/TreemapChart.astro +94 -0
- package/src/components/lib/charts/WaffleChart.astro +121 -0
- package/src/components/lib/charts/WaterfallChart.astro +118 -0
- package/src/components/lib/charts/chart-tooltip.ts +75 -0
- package/src/components/lib/charts/chart-utils.ts +271 -0
- package/src/components/lib/charts/index.ts +46 -0
- package/src/components/lib/data/ActivityHeatmapGrid.astro +157 -0
- package/src/components/lib/data/MetricComparisonRow.astro +109 -0
- package/src/components/lib/data/StatusTimelineFeed.astro +168 -0
- package/src/components/lib/data/index.ts +3 -0
- package/src/components/lib/ecommerce/CartDrawer.astro +483 -0
- package/src/components/lib/ecommerce/CartPageList.astro +290 -0
- package/src/components/lib/ecommerce/CartToast.astro +276 -0
- package/src/components/lib/ecommerce/CategoryTiles.astro +143 -0
- package/src/components/lib/ecommerce/CheckoutForm.astro +423 -0
- package/src/components/lib/ecommerce/CountdownBanner.astro +230 -0
- package/src/components/lib/ecommerce/FilterBar.astro +278 -0
- package/src/components/lib/ecommerce/FilterSidebar.astro +405 -0
- package/src/components/lib/ecommerce/LiveActivityToast.astro +213 -0
- package/src/components/lib/ecommerce/OrderSummary.astro +295 -0
- package/src/components/lib/ecommerce/ProductBuyBox.astro +247 -0
- package/src/components/lib/ecommerce/ProductCardMinimal.astro +229 -0
- package/src/components/lib/ecommerce/ProductGallery.astro +145 -0
- package/src/components/lib/ecommerce/ProductGrid.astro +118 -0
- package/src/components/lib/ecommerce/ProductMedia.astro +113 -0
- package/src/components/lib/ecommerce/ProductRating.astro +78 -0
- package/src/components/lib/ecommerce/ProductToolbar.astro +93 -0
- package/src/components/lib/ecommerce/PromoBar.astro +145 -0
- package/src/components/lib/ecommerce/StockIndicator.astro +127 -0
- package/src/components/lib/ecommerce/StoreHeader.astro +446 -0
- package/src/components/lib/ecommerce/StoreHero.astro +260 -0
- package/src/components/lib/ecommerce/StoreProductCard.astro +307 -0
- package/src/components/lib/ecommerce/TrustBadges.astro +101 -0
- package/src/components/lib/ecommerce/VariantPicker.astro +145 -0
- package/src/components/lib/ecommerce/cart-store.ts +151 -0
- package/src/components/lib/ecommerce/catalog-filter.ts +62 -0
- package/src/components/lib/ecommerce/index.ts +34 -0
- package/src/components/lib/ecommerce/product-icons.ts +28 -0
- package/src/components/lib/ecommerce/types.ts +70 -0
- package/src/components/lib/feedback/ProgressRingCounter.astro +118 -0
- package/src/components/lib/feedback/ToastNotificationGroup.astro +173 -0
- package/src/components/lib/feedback/index.ts +2 -0
- package/src/components/lib/forms/AnimatedInput.astro +4 -2
- package/src/components/lib/forms/CustomRadioGroup.astro +3 -1
- package/src/components/lib/forms/GlassInlineToggleGroup.astro +161 -0
- package/src/components/lib/forms/PasswordStrengthInput.astro +242 -0
- package/src/components/lib/forms/PhoneCountryInput.astro +145 -0
- package/src/components/lib/forms/TagsAutocompleteInput.astro +249 -0
- package/src/components/lib/forms/index.ts +5 -0
- package/src/components/lib/index.ts +2 -0
- package/src/components/lib/layout/BentoGrid3.astro +6 -4
- package/src/components/lib/layout/CenterFixedBox.astro +2 -2
- package/src/components/lib/layout/CenteredContainer.astro +4 -2
- package/src/components/lib/layout/ClusterHorizontal.astro +2 -2
- package/src/components/lib/layout/DiagonalSectionDivider.astro +69 -0
- package/src/components/lib/layout/FeatureHero.astro +4 -2
- package/src/components/lib/layout/FooterCenteredMinimal.astro +4 -2
- package/src/components/lib/layout/FullBleedSection.astro +2 -2
- package/src/components/lib/layout/GalleryGrid4.astro +2 -2
- package/src/components/lib/layout/HolyGrailGrid.astro +127 -0
- package/src/components/lib/layout/LandingSectionSplit.astro +5 -3
- package/src/components/lib/layout/MasonryGrid.astro +2 -2
- package/src/components/lib/layout/MinimalFooterGrid.astro +1 -1
- package/src/components/lib/layout/ScrollSnapSection.astro +1 -1
- package/src/components/lib/layout/SidebarLayoutWrapper.astro +4 -2
- package/src/components/lib/layout/SplitScreenLayout.astro +3 -3
- package/src/components/lib/layout/StepLayoutVertical.astro +1 -1
- package/src/components/lib/layout/StickySidebarWrapper.astro +4 -2
- package/src/components/lib/layout/index.ts +3 -1
- package/src/components/lib/navigation/QuickJumpDock.astro +125 -0
- package/src/components/lib/navigation/StepBreadcrumbBar.astro +151 -0
- package/src/components/lib/navigation/SubNavbarPills.astro +102 -0
- package/src/components/lib/navigation/index.ts +3 -0
- package/src/components/lib/overlays/CookiePreferencesDrawer.astro +226 -0
- package/src/components/lib/overlays/FloatingFeedbackWidget.astro +203 -0
- package/src/components/lib/overlays/ImageCompareSlider.astro +172 -0
- package/src/components/lib/overlays/index.ts +3 -0
- package/src/components/lib/sections/AgencyContactBrief.astro +194 -0
- package/src/components/lib/sections/AgencyFooterCta.astro +94 -0
- package/src/components/lib/sections/AgencyHeader.astro +161 -0
- package/src/components/lib/sections/AgencyHeroKinetic.astro +172 -0
- package/src/components/lib/sections/AgencyServicesList.astro +113 -0
- package/src/components/lib/sections/AgencyTeamGrid.astro +111 -0
- package/src/components/lib/sections/AgencyWorkGrid.astro +147 -0
- package/src/components/lib/sections/BlogGridSection.astro +133 -0
- package/src/components/lib/sections/ButtonsShowcase.astro +20 -18
- package/src/components/lib/sections/CardsShowcase.astro +28 -26
- package/src/components/lib/sections/CareersOpenings.astro +141 -0
- package/src/components/lib/sections/ChangelogSection.astro +151 -0
- package/src/components/lib/sections/ContactGlassForm.astro +10 -8
- package/src/components/lib/sections/ContactSplitMap.astro +141 -0
- package/src/components/lib/sections/CtaAppDownload.astro +13 -11
- package/src/components/lib/sections/CtaGlowBanner.astro +12 -10
- package/src/components/lib/sections/CtaNewsletterSection.astro +165 -0
- package/src/components/lib/sections/CtaSplitImage.astro +149 -0
- package/src/components/lib/sections/DataShowcase.astro +23 -21
- package/src/components/lib/sections/FaqAccordionSection.astro +8 -8
- package/src/components/lib/sections/FaqTwoColumn.astro +121 -0
- package/src/components/lib/sections/FeatureAlternatingRows.astro +192 -0
- package/src/components/lib/sections/FeatureBentoGrid.astro +13 -11
- package/src/components/lib/sections/FeatureIconGrid.astro +155 -0
- package/src/components/lib/sections/FeatureTabsShowcase.astro +182 -0
- package/src/components/lib/sections/FeatureTimelineFlow.astro +8 -6
- package/src/components/lib/sections/FeedbackShowcase.astro +15 -13
- package/src/components/lib/sections/FooterMegaSection.astro +157 -0
- package/src/components/lib/sections/FormsShowcase.astro +22 -20
- package/src/components/lib/sections/HeroDashboardPreview.astro +182 -0
- package/src/components/lib/sections/HeroGlass.astro +22 -20
- package/src/components/lib/sections/HeroGradient.astro +15 -13
- package/src/components/lib/sections/HeroMinimalCentered.astro +175 -0
- package/src/components/lib/sections/HeroShowcaseSplit.astro +306 -0
- package/src/components/lib/sections/HeroSplit.astro +13 -11
- package/src/components/lib/sections/HeroTerminal.astro +253 -0
- package/src/components/lib/sections/HeroWaitlist.astro +231 -0
- package/src/components/lib/sections/HowItWorksSteps.astro +180 -0
- package/src/components/lib/sections/IntegrationsGrid.astro +167 -0
- package/src/components/lib/sections/LayoutShowcase.astro +21 -19
- package/src/components/lib/sections/LogoCloudSection.astro +96 -0
- package/src/components/lib/sections/LogoWallGrid.astro +93 -0
- package/src/components/lib/sections/NavigationShowcase.astro +15 -13
- package/src/components/lib/sections/OverlaysShowcase.astro +21 -19
- package/src/components/lib/sections/PricingComparisonMatrix.astro +15 -13
- package/src/components/lib/sections/PricingSinglePlan.astro +159 -0
- package/src/components/lib/sections/PricingTierCards.astro +11 -9
- package/src/components/lib/sections/PricingToggleCards.astro +171 -0
- package/src/components/lib/sections/ShopCategoryHero.astro +103 -0
- package/src/components/lib/sections/ShopCollectionMosaic.astro +131 -0
- package/src/components/lib/sections/ShopFooterWordmark.astro +100 -0
- package/src/components/lib/sections/ShopHeaderEditorial.astro +396 -0
- package/src/components/lib/sections/ShopHeroEditorial.astro +151 -0
- package/src/components/lib/sections/ShopLookbook.astro +250 -0
- package/src/components/lib/sections/ShopNewsletterEditorial.astro +128 -0
- package/src/components/lib/sections/ShopProductRail.astro +150 -0
- package/src/components/lib/sections/ShopReviewsEditorial.astro +133 -0
- package/src/components/lib/sections/ShopValuesMarquee.astro +91 -0
- package/src/components/lib/sections/StatsCounterBand.astro +124 -0
- package/src/components/lib/sections/StatsDashboardSection.astro +85 -0
- package/src/components/lib/sections/StatsMetricShowcase.astro +9 -7
- package/src/components/lib/sections/TeamGridSection.astro +12 -10
- package/src/components/lib/sections/TestimonialCarousel.astro +167 -0
- package/src/components/lib/sections/TestimonialFeatured.astro +122 -0
- package/src/components/lib/sections/TestimonialGridSection.astro +188 -0
- package/src/components/lib/sections/TestimonialWallGlass.astro +11 -9
- package/src/components/lib/sections/TypographyShowcase.astro +10 -8
- package/src/components/lib/sections/index.ts +39 -0
- package/src/components/lib/templates/ecommerce/StoreCategoryPage.astro +160 -0
- package/src/components/lib/templates/ecommerce/StoreCheckoutPage.astro +98 -0
- package/src/components/lib/templates/ecommerce/StoreHomePage.astro +191 -0
- package/src/components/lib/templates/ecommerce/StoreLayout.astro +169 -0
- package/src/components/lib/templates/ecommerce/StoreProductPage.astro +165 -0
- package/src/components/lib/templates/ecommerce/data.ts +187 -0
- package/src/components/lib/templates/ecommerce/index.ts +14 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialBagPage.astro +52 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialCheckoutPage.astro +123 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialHomePage.astro +102 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialLayout.astro +216 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialProductPage.astro +205 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialShopPage.astro +112 -0
- package/src/components/lib/templates/ecommerce-editorial/data.ts +125 -0
- package/src/components/lib/templates/ecommerce-editorial/index.ts +16 -0
- package/src/components/lib/templates/saas-hotel/AppLayout.astro +83 -0
- package/src/components/lib/templates/saas-hotel/DashboardPage.astro +66 -0
- package/src/components/lib/templates/saas-hotel/GuestsPage.astro +65 -0
- package/src/components/lib/templates/saas-hotel/LandingPage.astro +213 -0
- package/src/components/lib/templates/saas-hotel/ReportsPage.astro +63 -0
- package/src/components/lib/templates/saas-hotel/ReservationsPage.astro +49 -0
- package/src/components/lib/templates/saas-hotel/RoomsPage.astro +48 -0
- package/src/components/lib/templates/saas-hotel/SettingsPage.astro +115 -0
- package/src/components/lib/templates/saas-hotel/SignInPage.astro +50 -0
- package/src/components/lib/templates/saas-hotel/data.ts +229 -0
- package/src/components/lib/templates/saas-hotel/index.ts +19 -0
- package/src/components/lib/templates/saas-hotel/lobby-theme.css +90 -0
- package/src/components/lib/typography/AnimatedCounterText.astro +102 -0
- package/src/components/lib/typography/CurvedPathHeading.astro +86 -0
- package/src/components/lib/typography/GradientTextMarquee.astro +105 -0
- package/src/components/lib/typography/HackerDecodeText.astro +85 -0
- package/src/components/lib/typography/index.ts +4 -0
- package/src/styles/global.css +113 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* SettingsPage — Account & workspace settings: profile, property details, notification switches,
|
|
4
|
+
* team management with roles and invites, and plan usage / billing.
|
|
5
|
+
*
|
|
6
|
+
* Usage (src/pages/app/settings.astro):
|
|
7
|
+
* import { SettingsPage } from 'astro-component-kit/templates/saas-hotel';
|
|
8
|
+
* <SettingsPage />
|
|
9
|
+
*
|
|
10
|
+
* @param {LobbyData} data - Workspace data.
|
|
11
|
+
* @param {SaasRoutes} routes - Page URLs.
|
|
12
|
+
* @param {string} theme - Initial theme (light|dark|auto). Default 'light'.
|
|
13
|
+
*/
|
|
14
|
+
import AppLayout from './AppLayout.astro';
|
|
15
|
+
import PageHeader from '../../app/PageHeader.astro';
|
|
16
|
+
import SettingsSection from '../../app/SettingsSection.astro';
|
|
17
|
+
import SwitchRow from '../../app/SwitchRow.astro';
|
|
18
|
+
import TeamMembers from '../../app/TeamMembers.astro';
|
|
19
|
+
import PlanUsageCard from '../../app/PlanUsageCard.astro';
|
|
20
|
+
import AnimatedInput from '../../forms/AnimatedInput.astro';
|
|
21
|
+
import ModernSelect from '../../forms/ModernSelect.astro';
|
|
22
|
+
import { lobbyData, saasRoutes, type LobbyData, type SaasRoutes } from './data';
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
data?: LobbyData;
|
|
26
|
+
routes?: SaasRoutes;
|
|
27
|
+
theme?: 'light' | 'dark' | 'auto';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { data = lobbyData, routes = saasRoutes, theme = 'light' } = Astro.props;
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
<AppLayout title="Settings" current="settings" data={data} routes={routes} theme={theme}>
|
|
34
|
+
<PageHeader
|
|
35
|
+
title="Settings"
|
|
36
|
+
description="Manage your profile, property, team and billing."
|
|
37
|
+
breadcrumb={[{ label: data.workspace.name, href: routes.dashboard }, { label: 'Settings' }]}
|
|
38
|
+
actions={[]}
|
|
39
|
+
periods={[]}
|
|
40
|
+
tabs={[
|
|
41
|
+
{ label: 'Profile', href: '#profile' },
|
|
42
|
+
{ label: 'Property', href: '#property' },
|
|
43
|
+
{ label: 'Notifications', href: '#notifications' },
|
|
44
|
+
{ label: 'Team', href: '#team', count: data.team.length },
|
|
45
|
+
{ label: 'Billing', href: '#billing' },
|
|
46
|
+
]}
|
|
47
|
+
currentTab="#profile"
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
<div class="settings">
|
|
51
|
+
<div id="profile">
|
|
52
|
+
<SettingsSection id="set-profile" title="Your profile" description="How you appear to your team and in guest messages.">
|
|
53
|
+
<div class="settings__row">
|
|
54
|
+
<AnimatedInput label="Full name" id="set-name" name="name" required autocomplete="name" placeholder=" " />
|
|
55
|
+
<AnimatedInput label="Email" id="set-email" name="email" type="email" required autocomplete="email" />
|
|
56
|
+
</div>
|
|
57
|
+
<div class="settings__row">
|
|
58
|
+
<AnimatedInput label="Job title" id="set-role" name="role" autocomplete="organization-title" />
|
|
59
|
+
<ModernSelect label="Language" id="set-lang" name="lang" options={[{ label: 'English', value: 'en' }, { label: 'Español', value: 'es' }, { label: 'Русский', value: 'ru' }]} />
|
|
60
|
+
</div>
|
|
61
|
+
</SettingsSection>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div id="property">
|
|
65
|
+
<SettingsSection id="set-property" title="Property" description="Details used on invoices, confirmations and your booking engine.">
|
|
66
|
+
<div class="settings__row">
|
|
67
|
+
<AnimatedInput label="Property name" id="set-prop" name="property" required />
|
|
68
|
+
<ModernSelect label="Currency" id="set-cur" name="currency" options={[{ label: 'Euro (€)', value: 'EUR' }, { label: 'US dollar ($)', value: 'USD' }, { label: 'Pound (£)', value: 'GBP' }]} />
|
|
69
|
+
</div>
|
|
70
|
+
<div class="settings__row">
|
|
71
|
+
<ModernSelect label="Check-in time" id="set-in" name="checkin" options={[{ label: '14:00', value: '14' }, { label: '15:00', value: '15' }, { label: '16:00', value: '16' }]} />
|
|
72
|
+
<ModernSelect label="Check-out time" id="set-out" name="checkout" options={[{ label: '11:00', value: '11' }, { label: '12:00', value: '12' }]} />
|
|
73
|
+
</div>
|
|
74
|
+
</SettingsSection>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div id="notifications">
|
|
78
|
+
<SettingsSection id="set-notes" title="Notifications" description="Choose what reaches your inbox and phone.">
|
|
79
|
+
<SwitchRow name="n-bookings" label="New bookings & cancellations" description="Instant email for every change from any channel." checked />
|
|
80
|
+
<SwitchRow name="n-house" label="Housekeeping alerts" description="Push notification when a room is out of order." checked />
|
|
81
|
+
<SwitchRow name="n-reviews" label="New guest reviews" description="Daily digest at 9:00." checked={false} />
|
|
82
|
+
<SwitchRow name="n-report" label="Weekly performance report" description="Occupancy, ADR and RevPAR every Monday." checked />
|
|
83
|
+
</SettingsSection>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div id="team" class="settings__block">
|
|
87
|
+
<div class="settings__intro">
|
|
88
|
+
<h2>Team</h2>
|
|
89
|
+
<p>Invite colleagues and choose what each role can see and do.</p>
|
|
90
|
+
</div>
|
|
91
|
+
<TeamMembers members={data.team} seats={10} />
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div id="billing" class="settings__block">
|
|
95
|
+
<div class="settings__intro">
|
|
96
|
+
<h2>Plan & billing</h2>
|
|
97
|
+
<p>Your subscription renews automatically. Change or cancel any time.</p>
|
|
98
|
+
</div>
|
|
99
|
+
<PlanUsageCard variant="accent" upgrade={{ label: 'Upgrade to Business', href: routes.landing + '#pricing' }} />
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</AppLayout>
|
|
103
|
+
|
|
104
|
+
<style>
|
|
105
|
+
.settings { display: grid; max-width: 1040px; }
|
|
106
|
+
.settings > div { scroll-margin-top: 84px; }
|
|
107
|
+
.settings__row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
|
|
108
|
+
.settings__block { display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 2fr); gap: var(--sp-6); padding-block: var(--sp-6); border-bottom: 1px solid var(--c-border); }
|
|
109
|
+
.settings__intro h2 { margin: 0; font-size: 1rem; font-weight: 700; color: var(--c-text-1); }
|
|
110
|
+
.settings__intro p { margin: 4px 0 0; font-size: 0.85rem; line-height: 1.55; }
|
|
111
|
+
@media (max-width: 900px) {
|
|
112
|
+
.settings__block { grid-template-columns: 1fr; }
|
|
113
|
+
.settings__row { grid-template-columns: 1fr; }
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* SignInPage — Split-screen sign-in (or sign-up) for the hotel SaaS using AuthCard.
|
|
4
|
+
*
|
|
5
|
+
* Usage (src/pages/sign-in.astro):
|
|
6
|
+
* import { SignInPage } from 'astro-component-kit/templates/saas-hotel';
|
|
7
|
+
* <SignInPage />
|
|
8
|
+
*
|
|
9
|
+
* @param {string} mode - 'signin' or 'signup'. Default 'signin'.
|
|
10
|
+
* @param {SaasRoutes} routes - Page URLs (demo sign-in goes to `routes.dashboard`).
|
|
11
|
+
* @param {string} theme - Initial theme (light|dark|auto). Default 'light'.
|
|
12
|
+
* @param {boolean} noindex - Add robots noindex. Default true.
|
|
13
|
+
*/
|
|
14
|
+
import '../../../../styles/global.css';
|
|
15
|
+
import './lobby-theme.css';
|
|
16
|
+
import AuthCard from '../../app/AuthCard.astro';
|
|
17
|
+
import { saasRoutes, type SaasRoutes } from './data';
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
mode?: 'signin' | 'signup';
|
|
21
|
+
routes?: SaasRoutes;
|
|
22
|
+
theme?: 'light' | 'dark' | 'auto';
|
|
23
|
+
noindex?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { mode = 'signin', routes = saasRoutes, theme = 'light', noindex = true } = Astro.props;
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<!doctype html>
|
|
30
|
+
<html lang="en" class="theme-lobby" data-theme={theme === 'auto' ? 'light' : theme}>
|
|
31
|
+
<head>
|
|
32
|
+
<meta charset="utf-8" />
|
|
33
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
34
|
+
<title>{mode === 'signin' ? 'Sign in' : 'Start your free trial'} — Lobby</title>
|
|
35
|
+
{noindex && <meta name="robots" content="noindex, follow" />}
|
|
36
|
+
<script is:inline define:vars={{ initial: theme }}>
|
|
37
|
+
(function () {
|
|
38
|
+
var t = initial;
|
|
39
|
+
try { t = localStorage.getItem('ack-theme') || t; } catch (e) {}
|
|
40
|
+
if (t === 'auto') t = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
41
|
+
document.documentElement.dataset.theme = t;
|
|
42
|
+
})();
|
|
43
|
+
</script>
|
|
44
|
+
</head>
|
|
45
|
+
<body class="lobby">
|
|
46
|
+
<main>
|
|
47
|
+
<AuthCard mode={mode} brand="Lobby" successHref={routes.dashboard} switchHref={routes.landing} />
|
|
48
|
+
</main>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// src/components/lib/templates/saas-hotel/data.ts
|
|
2
|
+
// Demo data for the hotel SaaS template ("Lobby · Hotel OS" with the Hotel Mirador workspace).
|
|
3
|
+
// Replace with your API data; every page reads from this shape.
|
|
4
|
+
|
|
5
|
+
export interface SaasRoutes {
|
|
6
|
+
landing: string;
|
|
7
|
+
signIn: string;
|
|
8
|
+
dashboard: string;
|
|
9
|
+
reservations: string;
|
|
10
|
+
rooms: string;
|
|
11
|
+
guests: string;
|
|
12
|
+
reports: string;
|
|
13
|
+
settings: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const saasRoutes: SaasRoutes = {
|
|
17
|
+
landing: '/',
|
|
18
|
+
signIn: '/sign-in/',
|
|
19
|
+
dashboard: '/app/',
|
|
20
|
+
reservations: '/app/reservations/',
|
|
21
|
+
rooms: '/app/rooms/',
|
|
22
|
+
guests: '/app/guests/',
|
|
23
|
+
reports: '/app/reports/',
|
|
24
|
+
settings: '/app/settings/',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type Status = 'Confirmed' | 'Checked in' | 'Checked out' | 'Pending' | 'Cancelled';
|
|
28
|
+
type RoomStatus = 'clean' | 'dirty' | 'inspecting' | 'occupied' | 'ooo';
|
|
29
|
+
|
|
30
|
+
const GUESTS = [
|
|
31
|
+
['Elena Duarte', 'Spain'], ['James Whitfield', 'UK'], ['Aiko Tanaka', 'Japan'], ['Marco Bellini', 'Italy'],
|
|
32
|
+
['Sofía Ramos', 'Mexico'], ['Hans Müller', 'Germany'], ['Priya Nair', 'India'], ['The Okafor family', 'Nigeria'],
|
|
33
|
+
['Chloé Martin', 'France'], ['Tom Becker', 'Austria'], ['Ana Costa', 'Portugal'], ['Liam O’Connor', 'Ireland'],
|
|
34
|
+
['Mei Chen', 'Singapore'], ['Lucas Silva', 'Brazil'], ['Nora Lindqvist', 'Sweden'], ['David Cohen', 'USA'],
|
|
35
|
+
] as const;
|
|
36
|
+
const CHANNELS = ['Direct', 'Booking.com', 'Expedia', 'Airbnb', 'Direct', 'Booking.com'];
|
|
37
|
+
const PRICES: Record<string, number> = { Standard: 118, Deluxe: 176, Suite: 312 };
|
|
38
|
+
|
|
39
|
+
const rooms = [
|
|
40
|
+
...['101', '102', '103', '104', '105', '106'].map((n) => ({ id: n, name: n, type: 'Standard', floor: 'Floor 1' })),
|
|
41
|
+
...['201', '202', '203', '204', '205', '206'].map((n) => ({ id: n, name: n, type: 'Deluxe', floor: 'Floor 2' })),
|
|
42
|
+
...['301', '302', '303', '304'].map((n) => ({ id: n, name: n, type: 'Suite', floor: 'Floor 3' })),
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/** Day labels starting yesterday (built at build time; demo data only). */
|
|
46
|
+
function buildDays(count: number, locale: string): { labels: string[]; iso: string[] } {
|
|
47
|
+
const start = new Date();
|
|
48
|
+
start.setUTCHours(0, 0, 0, 0);
|
|
49
|
+
start.setUTCDate(start.getUTCDate() - 1);
|
|
50
|
+
const fmt = new Intl.DateTimeFormat(locale, { weekday: 'short', day: 'numeric', timeZone: 'UTC' });
|
|
51
|
+
const labels: string[] = [];
|
|
52
|
+
const iso: string[] = [];
|
|
53
|
+
for (let i = 0; i < count; i++) {
|
|
54
|
+
const d = new Date(start);
|
|
55
|
+
d.setUTCDate(start.getUTCDate() + i);
|
|
56
|
+
labels.push(fmt.format(d).replace(',', ''));
|
|
57
|
+
iso.push(d.toISOString().slice(0, 10));
|
|
58
|
+
}
|
|
59
|
+
return { labels, iso };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const locale = 'en-GB';
|
|
63
|
+
const days = buildDays(14, locale);
|
|
64
|
+
|
|
65
|
+
// Deterministic bookings across the rack.
|
|
66
|
+
const rackBookings = [
|
|
67
|
+
['101', 0, 3, 'checked-in'], ['101', 4, 2, 'confirmed'], ['101', 8, 4, 'pending'],
|
|
68
|
+
['102', 1, 5, 'confirmed'], ['102', 7, 3, 'confirmed'],
|
|
69
|
+
['103', 0, 2, 'checked-in'], ['103', 3, 6, 'confirmed'],
|
|
70
|
+
['104', 2, 2, 'blocked'], ['104', 5, 4, 'confirmed'],
|
|
71
|
+
['105', 0, 4, 'checked-in'], ['105', 6, 3, 'pending'], ['105', 10, 3, 'confirmed'],
|
|
72
|
+
['106', 1, 2, 'confirmed'], ['106', 4, 5, 'confirmed'],
|
|
73
|
+
['201', 0, 6, 'checked-in'], ['201', 7, 2, 'confirmed'],
|
|
74
|
+
['202', 2, 3, 'confirmed'], ['202', 6, 5, 'pending'],
|
|
75
|
+
['203', 0, 1, 'checked-in'], ['203', 3, 4, 'confirmed'], ['203', 9, 3, 'confirmed'],
|
|
76
|
+
['204', 1, 3, 'confirmed'], ['204', 5, 2, 'blocked'],
|
|
77
|
+
['205', 0, 3, 'checked-in'], ['205', 4, 4, 'confirmed'],
|
|
78
|
+
['206', 2, 6, 'confirmed'],
|
|
79
|
+
['301', 1, 6, 'confirmed'], ['301', 9, 3, 'pending'],
|
|
80
|
+
['302', 0, 2, 'checked-in'], ['302', 4, 4, 'confirmed'],
|
|
81
|
+
['303', 3, 3, 'confirmed'],
|
|
82
|
+
['304', 0, 5, 'checked-in'], ['304', 8, 4, 'confirmed'],
|
|
83
|
+
] as const;
|
|
84
|
+
|
|
85
|
+
const bookings = rackBookings.map(([room, start, nights, status], i) => {
|
|
86
|
+
const [guest] = GUESTS[i % GUESTS.length];
|
|
87
|
+
return { room, guest: status === 'blocked' ? 'Maintenance' : guest, start, nights, status, ref: status === 'blocked' ? undefined : `MR-${2810 + i}`, channel: status === 'blocked' ? undefined : CHANNELS[i % CHANNELS.length] };
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const tableStatus = (s: string, i: number): Status =>
|
|
91
|
+
s === 'checked-in' ? 'Checked in' : s === 'pending' ? 'Pending' : i % 9 === 4 ? 'Cancelled' : 'Confirmed';
|
|
92
|
+
|
|
93
|
+
const reservations = bookings
|
|
94
|
+
.filter((b) => b.status !== 'blocked')
|
|
95
|
+
.map((b, i) => {
|
|
96
|
+
const room = rooms.find((r) => r.id === b.room)!;
|
|
97
|
+
const st = tableStatus(b.status, i);
|
|
98
|
+
return {
|
|
99
|
+
ref: b.ref!,
|
|
100
|
+
guest: { name: b.guest, sub: b.channel },
|
|
101
|
+
room: `${room.name} · ${room.type}`,
|
|
102
|
+
checkIn: days.iso[b.start],
|
|
103
|
+
nights: b.nights,
|
|
104
|
+
status: st,
|
|
105
|
+
total: st === 'Cancelled' ? 0 : b.nights * PRICES[room.type],
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const housekeeping: Array<{ number: string; floor: string; type: string; status: RoomStatus; guest?: string; note?: string; assignee?: string }> = rooms.map((r, i) => {
|
|
110
|
+
const occ = bookings.find((b) => b.room === r.id && b.status === 'checked-in');
|
|
111
|
+
const cycle: RoomStatus[] = ['clean', 'dirty', 'inspecting', 'clean', 'dirty'];
|
|
112
|
+
if (r.id === '104') return { number: r.name, floor: r.floor, type: r.type, status: 'ooo', note: 'Plumbing repair' };
|
|
113
|
+
if (occ) return { number: r.name, floor: r.floor, type: r.type, status: 'occupied', guest: occ.guest.split(' ').slice(-1)[0] };
|
|
114
|
+
const status = cycle[i % cycle.length];
|
|
115
|
+
return { number: r.name, floor: r.floor, type: r.type, status, assignee: status !== 'clean' ? ['Rosa', 'Luis', 'Inés'][i % 3] : undefined };
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
119
|
+
|
|
120
|
+
export const lobbyData = {
|
|
121
|
+
product: 'Lobby',
|
|
122
|
+
workspace: { name: 'Hotel Mirador', plan: 'Pro plan', initials: 'HM' },
|
|
123
|
+
user: { name: 'Lucía Ortega', role: 'General manager', email: 'lucia@mirador.com' },
|
|
124
|
+
currency: 'EUR',
|
|
125
|
+
locale,
|
|
126
|
+
days: days.labels,
|
|
127
|
+
rooms,
|
|
128
|
+
bookings,
|
|
129
|
+
reservations,
|
|
130
|
+
housekeeping,
|
|
131
|
+
kpis: [
|
|
132
|
+
{ label: 'Occupancy', value: '84%', change: 6.2, trend: [62, 66, 70, 68, 74, 78, 80, 84] },
|
|
133
|
+
{ label: 'ADR', value: '€168', change: 3.4, trend: [150, 152, 158, 155, 160, 162, 165, 168] },
|
|
134
|
+
{ label: 'RevPAR', value: '€141', change: 9.1, trend: [98, 104, 110, 106, 118, 124, 132, 141] },
|
|
135
|
+
{ label: 'Cancellations', value: '3.1%', change: -1.2, invert: true, trend: [5, 4.6, 4.8, 4.1, 3.9, 3.6, 3.4, 3.1] },
|
|
136
|
+
],
|
|
137
|
+
revenue: {
|
|
138
|
+
categories: ['W1', 'W2', 'W3', 'W4', 'W5', 'W6', 'W7', 'W8', 'W9', 'W10', 'W11', 'W12'],
|
|
139
|
+
series: [
|
|
140
|
+
{ name: 'Rooms', data: [38, 41, 39, 44, 47, 52, 49, 55, 58, 61, 57, 64] },
|
|
141
|
+
{ name: 'F&B', data: [9, 10, 9, 11, 12, 13, 12, 14, 15, 16, 15, 17] },
|
|
142
|
+
{ name: 'Spa & extras', data: [4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9] },
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
occupancyByType: {
|
|
146
|
+
categories: months.slice(4, 11),
|
|
147
|
+
series: [
|
|
148
|
+
{ name: 'Standard', data: [72, 78, 88, 91, 84, 76, 81] },
|
|
149
|
+
{ name: 'Deluxe', data: [64, 71, 85, 89, 79, 70, 77] },
|
|
150
|
+
{ name: 'Suite', data: [48, 55, 74, 80, 66, 58, 63] },
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
channels: [
|
|
154
|
+
{ label: 'Direct', value: 38 },
|
|
155
|
+
{ label: 'Booking.com', value: 31 },
|
|
156
|
+
{ label: 'Expedia', value: 17 },
|
|
157
|
+
{ label: 'Airbnb', value: 9 },
|
|
158
|
+
{ label: 'Corporate', value: 5 },
|
|
159
|
+
],
|
|
160
|
+
forecast: {
|
|
161
|
+
categories: ['Oct 28', 'Oct 31', 'Nov 3', 'Nov 6', 'Nov 9', 'Nov 12', 'Nov 15', 'Nov 18', 'Nov 21', 'Nov 24'],
|
|
162
|
+
actual: [71, 74, 80, 84],
|
|
163
|
+
forecast: [84, 82, 79, 86, 91, 88, 76, 72],
|
|
164
|
+
},
|
|
165
|
+
pace: {
|
|
166
|
+
rows: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
|
167
|
+
columns: ['0–7d', '8–14d', '15–30d', '31–60d', '61–90d', '90d+'],
|
|
168
|
+
values: [
|
|
169
|
+
[12, 9, 14, 8, 4, 2], [10, 8, 12, 7, 3, 2], [11, 10, 13, 9, 4, 3], [14, 12, 15, 10, 6, 3],
|
|
170
|
+
[22, 18, 20, 14, 9, 5], [26, 21, 22, 16, 10, 6], [15, 11, 12, 8, 5, 3],
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
funnel: [
|
|
174
|
+
{ label: 'Website visits', value: 18400 },
|
|
175
|
+
{ label: 'Searched dates', value: 7350 },
|
|
176
|
+
{ label: 'Viewed rooms', value: 3920 },
|
|
177
|
+
{ label: 'Started booking', value: 1180 },
|
|
178
|
+
{ label: 'Booked', value: 612 },
|
|
179
|
+
],
|
|
180
|
+
segments: [
|
|
181
|
+
{ label: 'Leisure', value: 46 },
|
|
182
|
+
{ label: 'Business', value: 28 },
|
|
183
|
+
{ label: 'Groups', value: 14 },
|
|
184
|
+
{ label: 'Long stay', value: 12 },
|
|
185
|
+
],
|
|
186
|
+
arrivals: [
|
|
187
|
+
{ guest: 'Elena Duarte', room: '204', time: '14:00', nights: 3, vip: true, note: 'Anniversary, late arrival' },
|
|
188
|
+
{ guest: 'Aiko Tanaka', room: '102', time: '15:30', nights: 5 },
|
|
189
|
+
{ guest: 'The Okafor family', room: '301', time: '16:00', nights: 6, note: 'Baby cot', done: true },
|
|
190
|
+
{ guest: 'David Cohen', room: '106', time: '18:45', nights: 2 },
|
|
191
|
+
],
|
|
192
|
+
departures: [
|
|
193
|
+
{ guest: 'Hans Müller', room: '203', time: '11:00', balance: '€0', done: true },
|
|
194
|
+
{ guest: 'Priya Nair', room: '302', time: '12:00', balance: '€48' },
|
|
195
|
+
{ guest: 'Lucas Silva', room: '105', time: '12:00', balance: '€0' },
|
|
196
|
+
],
|
|
197
|
+
inHouse: [
|
|
198
|
+
{ guest: 'James Whitfield', room: '201', until: days.labels[6] },
|
|
199
|
+
{ guest: 'Mei Chen', room: '304', until: days.labels[5] },
|
|
200
|
+
{ guest: 'Nora Lindqvist', room: '205', until: days.labels[3] },
|
|
201
|
+
],
|
|
202
|
+
activity: [
|
|
203
|
+
{ title: 'Check-in completed', desc: 'The Okafor family · Suite 301', time: '09:42', status: 'done' as const },
|
|
204
|
+
{ title: 'Payment captured', desc: 'MR-2840 · €890 · Visa', time: '09:15', status: 'done' as const },
|
|
205
|
+
{ title: 'Room 104 out of order', desc: 'Plumbing — technician on site', time: '08:50', status: 'active' as const },
|
|
206
|
+
{ title: 'Rate update pushed', desc: 'Weekend +8% on all channels', time: '08:05', status: 'pending' as const },
|
|
207
|
+
],
|
|
208
|
+
notifications: [
|
|
209
|
+
{ title: 'New booking from Booking.com', text: 'Deluxe King · 3 nights · €528', time: '2 min ago', unread: true, tone: 'success' as const },
|
|
210
|
+
{ title: 'Room 104 out of order', text: 'Plumbing repair scheduled', time: '1 h ago', unread: true, tone: 'warning' as const },
|
|
211
|
+
{ title: 'Weekly report ready', text: 'Occupancy up 6.2% vs last week', time: 'Yesterday', tone: 'info' as const },
|
|
212
|
+
],
|
|
213
|
+
guests: GUESTS.slice(0, 12).map(([name, country], i) => ({
|
|
214
|
+
guest: { name, sub: `${name.toLowerCase().replace(/[^a-z]+/g, '.').replace(/^\.|\.$/g, '')}@mail.com` },
|
|
215
|
+
country,
|
|
216
|
+
stays: [7, 3, 1, 2, 4, 9, 1, 2, 5, 1, 3, 6][i],
|
|
217
|
+
spend: [4820, 2140, 476, 980, 1730, 6120, 290, 1880, 2410, 340, 1290, 3980][i],
|
|
218
|
+
tier: ['Gold', 'Silver', 'Member', 'Member', 'Silver', 'Platinum', 'Member', 'Silver', 'Gold', 'Member', 'Silver', 'Gold'][i],
|
|
219
|
+
tags: [['VIP', 'Anniversary'], ['Business'], ['First stay'], ['Repeat'], ['Spa'], ['VIP', 'Long stay'], [], ['Family'], ['Business'], [], ['Repeat'], ['VIP']][i],
|
|
220
|
+
})),
|
|
221
|
+
team: [
|
|
222
|
+
{ name: 'Lucía Ortega', email: 'lucia@mirador.com', role: 'Owner', status: 'active' as const, you: true },
|
|
223
|
+
{ name: 'Daniel Kim', email: 'daniel@mirador.com', role: 'Manager', status: 'active' as const },
|
|
224
|
+
{ name: 'Rosa Jiménez', email: 'rosa@mirador.com', role: 'Housekeeping', status: 'active' as const },
|
|
225
|
+
{ name: 'Omar Haddad', email: 'omar@mirador.com', role: 'Front desk', status: 'invited' as const },
|
|
226
|
+
],
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export type LobbyData = typeof lobbyData;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/components/lib/templates/saas-hotel/index.ts
|
|
2
|
+
// Hotel SaaS template ("Lobby · Hotel OS"): marketing landing, sign-in and a complete app
|
|
3
|
+
// (dashboard, reservations, rooms, guests, reports, settings) built from library components.
|
|
4
|
+
//
|
|
5
|
+
// import { DashboardPage } from 'astro-component-kit/templates/saas-hotel';
|
|
6
|
+
// <DashboardPage theme="light" />
|
|
7
|
+
|
|
8
|
+
export { default as AppLayout } from './AppLayout.astro';
|
|
9
|
+
export { default as LandingPage } from './LandingPage.astro';
|
|
10
|
+
export { default as SignInPage } from './SignInPage.astro';
|
|
11
|
+
export { default as DashboardPage } from './DashboardPage.astro';
|
|
12
|
+
export { default as ReservationsPage } from './ReservationsPage.astro';
|
|
13
|
+
export { default as RoomsPage } from './RoomsPage.astro';
|
|
14
|
+
export { default as GuestsPage } from './GuestsPage.astro';
|
|
15
|
+
export { default as ReportsPage } from './ReportsPage.astro';
|
|
16
|
+
export { default as SettingsPage } from './SettingsPage.astro';
|
|
17
|
+
|
|
18
|
+
export { lobbyData, saasRoutes } from './data';
|
|
19
|
+
export type { LobbyData, SaasRoutes } from './data';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* Lobby · Hotel OS — theme tokens shared by the landing, sign-in and app pages. */
|
|
2
|
+
.theme-lobby {
|
|
3
|
+
--r-sm: 8px;
|
|
4
|
+
--r-md: 10px;
|
|
5
|
+
--r-lg: 14px;
|
|
6
|
+
--r-xl: 18px;
|
|
7
|
+
}
|
|
8
|
+
.theme-lobby[data-theme='light'] {
|
|
9
|
+
--c-bg: #f4f6fa;
|
|
10
|
+
--c-bg-1: #ffffff;
|
|
11
|
+
--c-bg-2: #eef1f6;
|
|
12
|
+
--c-surface: rgba(15, 23, 42, 0.025);
|
|
13
|
+
--c-border: rgba(15, 23, 42, 0.1);
|
|
14
|
+
--c-text-1: #0f172a;
|
|
15
|
+
--c-text-2: #475569;
|
|
16
|
+
--c-text-3: #64748b;
|
|
17
|
+
--c-primary: #2557d6;
|
|
18
|
+
--c-primary-light: #2557d6;
|
|
19
|
+
--c-primary-glow: rgba(37, 87, 214, 0.14);
|
|
20
|
+
--c-accent: #1e40af;
|
|
21
|
+
--app-accent: #2557d6;
|
|
22
|
+
--c-on-primary: #ffffff;
|
|
23
|
+
--ink-rgb: 15 23 42;
|
|
24
|
+
--paper-rgb: 255 255 255;
|
|
25
|
+
--c-success: #047857;
|
|
26
|
+
--c-warning: #a16207;
|
|
27
|
+
--c-danger: #b91c1c;
|
|
28
|
+
--chart-surface: #ffffff;
|
|
29
|
+
--chart-grid: rgba(15, 23, 42, 0.08);
|
|
30
|
+
--chart-axis: rgba(15, 23, 42, 0.22);
|
|
31
|
+
color-scheme: light;
|
|
32
|
+
}
|
|
33
|
+
.theme-lobby[data-theme='dark'] {
|
|
34
|
+
--c-bg: #0b0f17;
|
|
35
|
+
--c-bg-1: #111827;
|
|
36
|
+
--c-bg-2: #1a2232;
|
|
37
|
+
--c-surface: rgba(148, 163, 184, 0.04);
|
|
38
|
+
--c-border: rgba(148, 163, 184, 0.16);
|
|
39
|
+
--c-text-1: #f1f5f9;
|
|
40
|
+
--c-text-2: #a9b3c4;
|
|
41
|
+
--c-text-3: #8a96aa;
|
|
42
|
+
--c-primary: #7aa2ff;
|
|
43
|
+
--c-primary-light: #7aa2ff;
|
|
44
|
+
--c-primary-glow: rgba(122, 162, 255, 0.16);
|
|
45
|
+
--c-accent: #c7d7ff;
|
|
46
|
+
--app-accent: #7aa2ff;
|
|
47
|
+
--c-on-primary: #0b1020;
|
|
48
|
+
--ink-rgb: 241 245 249;
|
|
49
|
+
--paper-rgb: 11 15 23;
|
|
50
|
+
--c-success: #4ade80;
|
|
51
|
+
--c-warning: #fbbf24;
|
|
52
|
+
--c-danger: #f87171;
|
|
53
|
+
--chart-surface: #111827;
|
|
54
|
+
--chart-grid: rgba(148, 163, 184, 0.12);
|
|
55
|
+
--chart-axis: rgba(148, 163, 184, 0.3);
|
|
56
|
+
color-scheme: dark;
|
|
57
|
+
}
|
|
58
|
+
body.lobby { background: var(--c-bg); color: var(--c-text-2); }
|
|
59
|
+
body.lobby--site { overflow-x: clip; }
|
|
60
|
+
body.lobby::before { display: none; }
|
|
61
|
+
.lobby__skip { position: fixed; top: -60px; left: 12px; z-index: 2000; padding: 0.6rem 1rem; border-radius: 10px; background: var(--app-accent); color: var(--c-on-primary); font-weight: 700; }
|
|
62
|
+
.lobby__skip:focus { top: 12px; color: var(--c-on-primary); }
|
|
63
|
+
.lobby__shell { display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 100dvh; }
|
|
64
|
+
.lobby__main { min-width: 0; display: flex; flex-direction: column; }
|
|
65
|
+
.lobby__content { flex: 1; display: grid; grid-template-columns: minmax(0, 1fr); align-content: start; gap: var(--sp-5); padding: var(--sp-6) clamp(1rem, 3vw, 2rem) var(--sp-12); outline: none; min-width: 0; }
|
|
66
|
+
.lobby-grid { display: grid; gap: var(--sp-5); min-width: 0; }
|
|
67
|
+
.lobby-grid--kpis { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
68
|
+
.lobby-grid--2-1 { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
|
|
69
|
+
.lobby-grid--1-1 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
70
|
+
.lobby-grid--1-2 { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
|
|
71
|
+
.lobby-grid > * { min-width: 0; }
|
|
72
|
+
/* Charts sit on solid app cards */
|
|
73
|
+
.lobby .cf--glass { backdrop-filter: none; background: var(--c-bg-1); }
|
|
74
|
+
.lobby .cf, .lobby .kpi { border-radius: var(--r-lg); }
|
|
75
|
+
@media (max-width: 1200px) {
|
|
76
|
+
.lobby-grid--kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
77
|
+
.lobby-grid--2-1, .lobby-grid--1-2 { grid-template-columns: 1fr; }
|
|
78
|
+
}
|
|
79
|
+
@media (max-width: 720px) {
|
|
80
|
+
.lobby-grid--kpis, .lobby-grid--1-1 { grid-template-columns: 1fr; }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* App cards used by template pages */
|
|
84
|
+
.lobby-card { padding: var(--sp-5); border-radius: var(--r-lg); border: 1px solid var(--c-border); background: var(--c-bg-1); min-width: 0; }
|
|
85
|
+
.lobby-card__title { margin: 0 0 var(--sp-4); font-size: 1rem; font-weight: 700; color: var(--c-text-1); }
|
|
86
|
+
.lobby .status-feed { max-width: none; }
|
|
87
|
+
.lobby .status-feed__line { background: var(--c-border); }
|
|
88
|
+
.lobby .anim-input, .lobby .custom-select { background: var(--c-bg); border-color: var(--c-border); border-radius: var(--r-md); }
|
|
89
|
+
.lobby .anim-input:focus, .lobby .custom-select:focus { border-color: var(--app-accent); box-shadow: 0 0 0 3px var(--c-primary-glow); background: var(--c-bg); }
|
|
90
|
+
.lobby .anim-input:focus ~ .anim-input-label, .lobby .anim-input:not(:placeholder-shown) ~ .anim-input-label { color: var(--app-accent); }
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* AnimatedCounterText — Kinetic statistic counter display with animated number progression and unit suffixes.
|
|
4
|
+
*
|
|
5
|
+
* @param {number} target - Numerical target value (e.g. 99, 120, 2400). Default is 120.
|
|
6
|
+
* @param {string} prefix - Optional string prefix (e.g. '$', '+').
|
|
7
|
+
* @param {string} suffix - Optional string suffix (e.g. '%', 'k', '+'). Default is '+'.
|
|
8
|
+
* @param {string} label - Descriptive text below the counter.
|
|
9
|
+
* @param {string} color - Text accent color. Default is '#d4a017'.
|
|
10
|
+
*/
|
|
11
|
+
interface Props {
|
|
12
|
+
target?: number;
|
|
13
|
+
prefix?: string;
|
|
14
|
+
suffix?: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
color?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
target = 120,
|
|
21
|
+
prefix = '',
|
|
22
|
+
suffix = '+',
|
|
23
|
+
label = 'Components Ready to Ship',
|
|
24
|
+
color = '#d4a017',
|
|
25
|
+
} = Astro.props;
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<div class="stat-counter" data-counter-wrap style={`--counter-color: ${color};`}>
|
|
29
|
+
<div class="stat-counter__number">
|
|
30
|
+
{prefix && <span class="affix">{prefix}</span>}
|
|
31
|
+
<span class="value" data-counter-target={target}>0</span>
|
|
32
|
+
{suffix && <span class="affix">{suffix}</span>}
|
|
33
|
+
</div>
|
|
34
|
+
{label && <p class="stat-counter__label">{label}</p>}
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<style>
|
|
38
|
+
.stat-counter {
|
|
39
|
+
display: inline-flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
align-items: center;
|
|
42
|
+
font-family: var(--font-sans, system-ui, sans-serif);
|
|
43
|
+
user-select: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.stat-counter__number {
|
|
47
|
+
display: inline-flex;
|
|
48
|
+
align-items: baseline;
|
|
49
|
+
font-size: clamp(2.5rem, 6vw, 4.2rem);
|
|
50
|
+
font-weight: 900;
|
|
51
|
+
line-height: 1;
|
|
52
|
+
letter-spacing: -0.04em;
|
|
53
|
+
color: var(--counter-color);
|
|
54
|
+
text-shadow: 0 0 24px rgba(212, 160, 23, 0.35);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.stat-counter__label {
|
|
58
|
+
font-size: 0.85rem;
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
color: var(--c-text-2, #a3a3a3);
|
|
61
|
+
margin-top: 6px;
|
|
62
|
+
text-transform: uppercase;
|
|
63
|
+
letter-spacing: 0.05em;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
66
|
+
|
|
67
|
+
<script>
|
|
68
|
+
function setupCounters() {
|
|
69
|
+
const observer = new IntersectionObserver((entries) => {
|
|
70
|
+
entries.forEach((entry) => {
|
|
71
|
+
if (!entry.isIntersecting) return;
|
|
72
|
+
const targetEl = entry.target.querySelector('[data-counter-target]') as HTMLElement;
|
|
73
|
+
if (!targetEl || targetEl.getAttribute('data-counted')) return;
|
|
74
|
+
|
|
75
|
+
targetEl.setAttribute('data-counted', 'true');
|
|
76
|
+
const finalVal = parseInt(targetEl.getAttribute('data-counter-target') || '0', 10);
|
|
77
|
+
const duration = 1500;
|
|
78
|
+
const start = performance.now();
|
|
79
|
+
|
|
80
|
+
function step(now: number) {
|
|
81
|
+
const progress = Math.min((now - start) / duration, 1);
|
|
82
|
+
// Ease out cubic
|
|
83
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
84
|
+
targetEl.textContent = Math.floor(eased * finalVal).toLocaleString();
|
|
85
|
+
|
|
86
|
+
if (progress < 1) {
|
|
87
|
+
requestAnimationFrame(step);
|
|
88
|
+
} else {
|
|
89
|
+
targetEl.textContent = finalVal.toLocaleString();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
requestAnimationFrame(step);
|
|
94
|
+
});
|
|
95
|
+
}, { threshold: 0.2 });
|
|
96
|
+
|
|
97
|
+
document.querySelectorAll('[data-counter-wrap]').forEach((el) => observer.observe(el));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setupCounters();
|
|
101
|
+
document.addEventListener('astro:page-load', setupCounters);
|
|
102
|
+
</script>
|