Skip to content

feat!(ui): integrate Pinia store and enhance enterprise paywall #4708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ui/admin/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import { createApp } from "vue";
import vuetify from "@ui/plugins/vuetify";
import * as Globals from "@global/components/index";
import { createPinia } from "pinia";
import App from "./App.vue";
import { loadFonts } from "./plugins/webfontloader";
import { key, store } from "./store";
import router from "./router";
import SnackbarComponent from "./components/Snackbar/Snackbar.vue";

const pinia = createPinia();
const app = createApp(App);

loadFonts();

app.use(vuetify);
app.use(router);
app.use(store, key);
app.use(pinia);

Object.entries(Globals).forEach(([name, component]) => {
app.component(name, component);
});

app.component("SnackbarComponent", SnackbarComponent);
app.mount("#app");
54 changes: 53 additions & 1 deletion ui/admin/src/views/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,48 @@
<v-container class="full-height d-flex justify-center align-center" fluid>
<v-row align="center" justify="center">
<v-col cols="12" sm="8" md="4">
<v-alert
v-if="!isEnterprise"
type="success"
variant="tonal"
class="paywall-banner mb-4"
>
<template v-slot:prepend>
<div class="circle-one shadow d-flex justify-center align-center">
<div class="circle-two shadow d-flex justify-center align-center">
<v-icon color="success" class="green-inner-shadow" size="50">
mdi-crown-circle
</v-icon>
</div>
</div>
</template>
<template v-slot:text>
<strong>Unlock Advanced Features with ShellHub Enterprise!</strong>
<p class="mb-0 text-body-2">
Gain access to real-time session recording, role-based access control (RBAC), audit logs,
and priority support. Take your infrastructure to the next level!
</p>
</template>
</v-alert>

<v-btn
v-if="!isEnterprise"
color="success"
block
class="mb-4"
variant="tonal"
href="https://www.shellhub.io/pricing"
rel="noopener noreferrer"
target="_blank"
>
Upgrade to Enterprise Now
</v-btn>
<v-card theme="dark" class="pa-6" rounded="lg">
<v-card-title class="d-flex justify-center align-center mt-4">
<v-img
:src="Logo"
max-width="220"
alt="logo do ShellHub, uma nuvem de com a escrita ShellHub Admin ao lado"
alt="ShellHub Logo"
/>
<span class="mt-6 text-overline">Admin</span>
</v-card-title>
Expand All @@ -26,6 +62,7 @@
label="Username"
variant="underlined"
data-test="username-text"
:disabled="!isEnterprise"
/>

<v-text-field
Expand All @@ -40,6 +77,8 @@
data-test="password-text"
:type="showPassword ? 'text' : 'password'"
@click:append-inner="showPassword = !showPassword"
:disabled="!isEnterprise"

/>
<v-card-actions class="justify-center">
<v-btn
Expand All @@ -49,6 +88,7 @@
variant="tonal"
block
@click="login"
:disabled="!isEnterprise"
>
LOGIN
</v-btn>
Expand All @@ -62,21 +102,33 @@
</v-container>
</v-main>
</v-app>

<PaywallDialog
v-model="isEnterprise"
filter="enterprise"
title="Unlock Full Managing with ShellHub Enterprise!"
subtitle="Upgrade to ShellHub Enterprise (Managed or On-Premises)
and gain full control over your infrastructure with advanced security,
management and priority support."
/>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useField } from "vee-validate";
import * as yup from "yup";
import { useRoute, useRouter } from "vue-router";
import PaywallDialog from "@global/components/User/PaywallDialog.vue";
import { useStore } from "../store";
import Logo from "../assets/logo-inverted.png";
import { createNewClient } from "../api/http";
import { envVariables } from "@/envVariables";

const showPassword = ref(false);
const store = useStore();
const route = useRoute();
const router = useRouter();
const isEnterprise = ref(!envVariables.isEnterprise);

const { value: username, errorMessage: usernameError } = useField<string | undefined>(
"name",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Login > Renders the component 1`] = `"<v-app-stub overlaps="" fullheight="true"></v-app-stub>"`;
exports[`Login > Renders the component 1`] = `
"<v-app-stub overlaps="" fullheight="true"></v-app-stub>
<paywall-dialog-stub filter="enterprise" title="Unlock Full Managing with ShellHub Enterprise!" subtitle="Upgrade to ShellHub Enterprise (Managed or On-Premises)
and gain full control over your infrastructure with advanced security,
management and priority support." modelvalue="true"></paywall-dialog-stub>"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
v-model="dialog"
transition="dialog-bottom-transition"
max-width="650"
height="800"
max-height="800"
@click:outside="close"
@keydown.esc="close"
>
Expand All @@ -23,20 +23,18 @@
<v-row>
<v-col class="pb-0">
<h1 class="d-flex justify-center align-center text-center" data-test="upgrade-heading">
Upgrade to have access to all features!
{{ title }}
</h1>
</v-col>
</v-row>
<v-row>
<v-col class="pt-1 pb-6">
<p class="d-flex justify-center align-center text-grey text-center" data-test="upgrade-description">
To use this feature, upgrade from the ShellHub Community Edition to one of our premium editions.
Each edition of ShellHub offers its own set of features and benefits, making it easy to find the
right solution for your needs.
{{ subtitle }}
</p>
</v-col>
</v-row>
<div v-if="items.length === 0">
<div v-if="filteredItems.length === 0">
<v-row>
<v-col class="d-flex align-center justify-center">
<v-btn
Expand All @@ -52,7 +50,7 @@
</v-row>
</div>
<v-row v-else data-test="items-row">
<v-col v-for="(item, i) in items" :key="i" :data-test="'item-' + i">
<v-col v-for="(item, i) in filteredItems" :key="i" :data-test="'item-' + i">
<v-card class="bg-v-theme-surface border d-flex flex-column justify-space-between" height="100%" :data-test="'item-card-' + i">
<v-card-title class="d-flex justify-center" :data-test="'item-title-' + i">
<b>{{ item.title }}</b>
Expand Down Expand Up @@ -80,34 +78,57 @@
</v-card>
</v-col>
</v-row>
<v-card-actions data-test="card-actions">
<v-spacer />
<v-btn @click="close" class="mt-4" variant="text" data-test="close-btn">
Close
</v-btn>
</v-card-actions>
</v-container>
<v-card-actions data-test="card-actions">
<v-spacer />
<v-btn @click="close" class="mt-4" variant="text" data-test="close-btn">
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { useStore } from "@/store";
import { computed, onMounted } from "vue";
import { storeToRefs } from "pinia";
import useGlobalStore from "@global/store/modules/global";

const props = defineProps<{
filter?: "cloud" | "enterprise" | "all";
title?: string;
subtitle?: string;
}>();

const emit = defineEmits(["close"]);
const dialog = defineModel({ default: false });

const store = useStore();
const dialog = ref(false);
const close = () => {
dialog.value = false;
store.commit("users/setShowPaywall", false);
emit("close");
};
const items = computed(() => store.getters["users/getPremiumContent"]);

const store = useGlobalStore();
const { premiumContent } = storeToRefs(store);

const filteredItems = computed(() => {
const filter = props.filter || "all";

if (filter === "cloud") {
return premiumContent.value.filter((item) => item.title.includes("Cloud"));
}
if (filter === "enterprise") {
return premiumContent.value.filter((item) => item.title.includes("Enterprise"));
}
return premiumContent.value;
});

onMounted(() => {
store.dispatch("users/getPremiumContent");
store.getPaywallPremiumContent();
});

defineExpose({ dialog });

</script>

<style scoped>
Expand Down
3 changes: 3 additions & 0 deletions ui/global/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import PaywallDialog from "./User/PaywallDialog.vue";

export default { PaywallDialog };
7 changes: 7 additions & 0 deletions ui/global/store/api/global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const premiumContent = async () => {
const response = await fetch("https://static.shellhub.io/premium-features.v1.json");
const data = await response.json();
return data;
};

export default premiumContent;
30 changes: 30 additions & 0 deletions ui/global/store/modules/global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { defineStore } from "pinia";
import premiumContent from "../api/global";

type PremiumItem = {
title: string;
features: string[];
button: {
label: string;
link: string;
};
};

const useGlobalStore = defineStore("global", {
state: () => ({
premiumContent: [] as PremiumItem[],
}),
actions: {
async getPaywallPremiumContent() {
try {
const res = await premiumContent();
this.premiumContent = res;
} catch (error) {
console.error(error);
throw error;
}
},
},
});

export default useGlobalStore;
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { createVuetify } from "vuetify";
import MockAdapter from "axios-mock-adapter";
import { expect, describe, it, beforeEach, vi } from "vitest";
import { nextTick } from "vue";
import PaywallDialog from "@global/components/User/PaywallDialog.vue";
import { createPinia, setActivePinia } from "pinia";
import { store, key } from "@/store";
import PaywallDialog from "@/components/User/PaywallDialog.vue";
import { router } from "@/router";
import { namespacesApi, devicesApi } from "@/api/http";
import { SnackbarPlugin } from "@/plugins/snackbar";
Expand Down Expand Up @@ -92,6 +93,7 @@ describe("PaywallDialog", async () => {
})));

beforeEach(async () => {
setActivePinia(createPinia());
const el = document.createElement("div");
document.body.appendChild(el);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`PaywallDialog > Renders the component 1`] = `""`;
43 changes: 36 additions & 7 deletions ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading