Files
website/website/resources/ts/Layouts/MarketingLayout.vue
Claude Dev 89fac519c3 Add notification system, notification bell, admin/account tests, and footer legal links
- 6 notification classes: PaymentSucceeded, PaymentFailed, SubscriptionCreated,
  SubscriptionCancelled, ServiceProvisioned, InvoiceGenerated (mail + database)
- Wire notifications to existing event listeners + new subscription listeners
- NotificationBell component in Account and Admin layouts
- NotificationController with index, markAsRead, markAllAsRead endpoints
- 62 new Pest tests: AdminPanelTest (admin CRUD) + CustomerAccountTest (account features)
- Add Legal links column to marketing footer
- 114 tests passing (623 assertions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 13:45:10 -05:00

354 lines
10 KiB
Vue

<script lang="ts" setup>
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
import { useTheme } from 'vuetify'
import { marketingNavItems } from '@/navigation/marketing'
import ThemeSwitcher from '@/Components/ThemeSwitcher.vue'
import logoWhite from '@images/ezscale_logo_white.png'
const theme = useTheme()
const isDark = computed(() => theme.global.current.value.dark)
interface PageProps {
domains: { marketing: string; account: string; admin: string }
}
const page = usePage()
const props = computed(() => page.props as unknown as PageProps)
const accountUrl = computed(() => `https://${props.value.domains?.account}`)
const footerLinks = {
products: [
{ title: 'VPS Hosting', href: '/vps-hosting' },
{ title: 'Dedicated Servers', href: '/dedicated-servers' },
{ title: 'Web Hosting', href: '/web-hosting' },
{ title: 'Game Servers', href: '/game-servers' },
],
company: [
{ title: 'About', href: '/about' },
{ title: 'Pricing', href: '/pricing' },
{ title: 'Contact', href: '/contact' },
{ title: 'Blog', href: '/blog' },
],
support: [
{ title: 'Help Center', href: 'https://ezscale.support' },
{ title: 'Knowledge Base', href: 'https://ezscale.support/en/knowledgebase' },
{ title: 'Status Page', href: 'https://status.ezscale.cloud' },
],
legal: [
{ title: 'Terms of Service', href: '/terms-of-service' },
{ title: 'Privacy Policy', href: '/privacy-policy' },
{ title: 'Acceptable Use', href: '/acceptable-use' },
{ title: 'SLA', href: '/sla' },
],
}
const socialLinks = [
{ title: 'twitter', icon: 'tabler-brand-twitter-filled', href: '#' },
{ title: 'facebook', icon: 'tabler-brand-facebook-filled', href: '#' },
{ title: 'github', icon: 'tabler-brand-github-filled', href: '#' },
{ title: 'discord', icon: 'tabler-brand-discord-filled', href: '#' },
]
</script>
<template>
<VApp>
<VAppBar flat>
<VContainer class="d-flex align-center">
<a href="/" class="d-inline-flex align-center">
<img
:src="logoWhite"
alt="EZSCALE"
:class="{ 'logo-light': !isDark }"
style="height: 32px; width: auto;"
>
</a>
<VSpacer />
<div class="d-flex align-center ga-1">
<template v-for="item in marketingNavItems" :key="item.title">
<VMenu v-if="item.children" open-on-hover>
<template #activator="{ props: menuProps }">
<VBtn variant="text" size="small" v-bind="menuProps">
{{ item.title }}
<VIcon icon="tabler-chevron-down" end size="small" />
</VBtn>
</template>
<VList>
<VListItem
v-for="child in item.children"
:key="child.href"
:href="child.href"
>
<template #prepend>
<VIcon v-if="child.icon" :icon="child.icon" />
</template>
<VListItemTitle>{{ child.title }}</VListItemTitle>
<VListItemSubtitle v-if="child.description">
{{ child.description }}
</VListItemSubtitle>
</VListItem>
</VList>
</VMenu>
<a v-else :href="item.href" class="text-decoration-none">
<VBtn variant="text" size="small">
{{ item.title }}
</VBtn>
</a>
</template>
</div>
<VSpacer />
<div class="d-flex align-center ga-2">
<ThemeSwitcher />
<a :href="accountUrl + '/login'" class="text-decoration-none">
<VBtn variant="text" size="small">
Login
</VBtn>
</a>
<a :href="accountUrl + '/register'" class="text-decoration-none">
<VBtn variant="flat" size="small" color="primary">
Sign Up
</VBtn>
</a>
</div>
</VContainer>
</VAppBar>
<VMain>
<slot />
</VMain>
<!-- Footer -->
<div class="footer">
<div class="footer-top pt-11">
<VContainer>
<VRow>
<!-- Logo + Description + Newsletter -->
<VCol cols="12" md="4">
<div
class="mb-4"
:class="$vuetify.display.smAndDown ? 'w-100' : 'w-75'"
>
<div class="d-flex align-center mb-6">
<img
:src="logoWhite"
alt="EZSCALE"
style="height: 32px; width: auto;"
>
</div>
<div class="text-white-variant mb-6">
High-performance VPS, dedicated servers, and hosting solutions with 24/7 support and enterprise-grade infrastructure.
</div>
<VForm class="subscribe-form d-flex align-center">
<VTextField
label="Subscribe to newsletter"
placeholder="john@email.com"
variant="outlined"
density="comfortable"
hide-details
/>
<VBtn class="align-self-end rounded-s-0">
Subscribe
</VBtn>
</VForm>
</div>
</VCol>
<!-- Products -->
<VCol md="2" sm="4" xs="6">
<div class="footer-links">
<h6 class="footer-title text-h6 mb-6">
Products
</h6>
<ul style="list-style: none; padding: 0;">
<li
v-for="link in footerLinks.products"
:key="link.href"
class="mb-4"
>
<a
:href="link.href"
class="text-white-variant"
>
{{ link.title }}
</a>
</li>
</ul>
</div>
</VCol>
<!-- Company -->
<VCol md="2" sm="4" xs="6">
<div class="footer-links">
<h6 class="footer-title text-h6 mb-6">
Company
</h6>
<ul style="list-style: none; padding: 0;">
<li
v-for="link in footerLinks.company"
:key="link.href"
class="mb-4"
>
<a
:href="link.href"
class="text-white-variant"
>
{{ link.title }}
</a>
</li>
</ul>
</div>
</VCol>
<!-- Support -->
<VCol md="2" sm="4" xs="6">
<div class="footer-links">
<h6 class="footer-title text-h6 mb-6">
Support
</h6>
<ul style="list-style: none; padding: 0;">
<li
v-for="link in footerLinks.support"
:key="link.href"
class="mb-4"
>
<a
:href="link.href"
class="text-white-variant"
>
{{ link.title }}
</a>
</li>
</ul>
</div>
</VCol>
<!-- Legal -->
<VCol md="2" sm="4" xs="6">
<div class="footer-links">
<h6 class="footer-title text-h6 mb-6">
Legal
</h6>
<ul style="list-style: none; padding: 0;">
<li
v-for="link in footerLinks.legal"
:key="link.href"
class="mb-4"
>
<a
:href="link.href"
class="text-white-variant"
>
{{ link.title }}
</a>
</li>
</ul>
</div>
</VCol>
</VRow>
</VContainer>
</div>
<!-- Footer Line -->
<div class="footer-line w-100">
<VContainer>
<div class="d-flex justify-space-between flex-wrap gap-y-5 align-center">
<div class="text-body-1 text-white-variant text-wrap me-4">
&copy; {{ new Date().getFullYear() }}
<span class="font-weight-bold ms-1 text-white">EZSCALE</span>,
All rights reserved.
</div>
<div class="d-flex gap-x-6">
<a
v-for="item in socialLinks"
:key="item.title"
:href="item.href"
target="_blank"
rel="noopener noreferrer"
>
<VIcon
:icon="item.icon"
size="16"
color="white"
/>
</a>
</div>
</div>
</VContainer>
</div>
</div>
</VApp>
</template>
<style lang="scss" scoped>
.footer-title {
color: rgba(255, 255, 255, 92%);
}
.footer-top {
border-radius: 60px 60px 0 0;
background-color: #2f3349;
background-size: cover;
color: #fff;
}
.footer-links {
a {
text-decoration: none;
&:hover {
color: #fff !important;
}
}
}
.footer-line {
background: #282c3e;
}
.text-white-variant {
color: rgba(255, 255, 255, 70%);
}
</style>
<style lang="scss">
.subscribe-form {
.v-label {
color: rgba(225, 222, 245, 90%) !important;
}
.v-field {
border-end-end-radius: 0;
border-end-start-radius: 10px;
border-start-end-radius: 0;
border-start-start-radius: 10px;
input.v-field__input::placeholder {
color: rgba(225, 222, 245, 40%) !important;
}
input.v-field__input {
color: rgba(255, 255, 255, 78%);
}
}
}
.footer {
@media (min-width: 600px) and (max-width: 960px) {
.v-container {
padding-inline: 2rem !important;
}
}
}
</style>