Skip to content

Commit 2d865c6

Browse files
committed
feat(ui)!: integrate Pinia store and enhance enterprise paywall
- Added Pinia store integration in `main.ts` for state management. - Registered global components dynamically from `@global/components/index`. - Implemented an enterprise paywall banner and upgrade button in `Login.vue`. - Disabled form inputs and login button for non-enterprise users. - Replaced `PaywallDialog.vue` component with a global import. - Upgraded Vue DevTools dependencies to `7.7.2` and added `pinia` to `package.json`. BREAKING CHANGE: `PaywallDialog.vue` has been removed and must be referenced globally.
1 parent ecfe822 commit 2d865c6

File tree

21 files changed

+226
-58
lines changed

21 files changed

+226
-58
lines changed

ui/admin/src/main.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
import { createApp } from "vue";
22
import vuetify from "@ui/plugins/vuetify";
3+
import * as Globals from "@global/components/index";
4+
import { createPinia } from "pinia";
35
import App from "./App.vue";
46
import { loadFonts } from "./plugins/webfontloader";
57
import { key, store } from "./store";
68
import router from "./router";
79
import SnackbarComponent from "./components/Snackbar/Snackbar.vue";
810

11+
const pinia = createPinia();
912
const app = createApp(App);
1013

1114
loadFonts();
1215

1316
app.use(vuetify);
1417
app.use(router);
1518
app.use(store, key);
19+
app.use(pinia);
20+
21+
Object.entries(Globals).forEach(([name, component]) => {
22+
app.component(name, component);
23+
});
24+
1625
app.component("SnackbarComponent", SnackbarComponent);
1726
app.mount("#app");

ui/admin/src/views/Login.vue

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,48 @@
44
<v-container class="full-height d-flex justify-center align-center" fluid>
55
<v-row align="center" justify="center">
66
<v-col cols="12" sm="8" md="4">
7+
<v-alert
8+
v-if="!isEnterprise"
9+
type="success"
10+
variant="tonal"
11+
class="paywall-banner mb-4"
12+
>
13+
<template v-slot:prepend>
14+
<div class="circle-one shadow d-flex justify-center align-center">
15+
<div class="circle-two shadow d-flex justify-center align-center">
16+
<v-icon color="success" class="green-inner-shadow" size="50">
17+
mdi-crown-circle
18+
</v-icon>
19+
</div>
20+
</div>
21+
</template>
22+
<template v-slot:text>
23+
<strong>Unlock Advanced Features with ShellHub Enterprise!</strong>
24+
<p class="mb-0 text-body-2">
25+
Gain access to real-time session recording, role-based access control (RBAC), audit logs,
26+
and priority support. Take your infrastructure to the next level!
27+
</p>
28+
</template>
29+
</v-alert>
30+
31+
<v-btn
32+
v-if="!isEnterprise"
33+
color="success"
34+
block
35+
class="mb-4"
36+
variant="tonal"
37+
href="https://www.shellhub.io/pricing"
38+
rel="noopener noreferrer"
39+
target="_blank"
40+
>
41+
Upgrade to Enterprise Now
42+
</v-btn>
743
<v-card theme="dark" class="pa-6" rounded="lg">
844
<v-card-title class="d-flex justify-center align-center mt-4">
945
<v-img
1046
:src="Logo"
1147
max-width="220"
12-
alt="logo do ShellHub, uma nuvem de com a escrita ShellHub Admin ao lado"
48+
alt="ShellHub Logo"
1349
/>
1450
<span class="mt-6 text-overline">Admin</span>
1551
</v-card-title>
@@ -26,6 +62,7 @@
2662
label="Username"
2763
variant="underlined"
2864
data-test="username-text"
65+
:disabled="!isEnterprise"
2966
/>
3067

3168
<v-text-field
@@ -40,6 +77,8 @@
4077
data-test="password-text"
4178
:type="showPassword ? 'text' : 'password'"
4279
@click:append-inner="showPassword = !showPassword"
80+
:disabled="!isEnterprise"
81+
4382
/>
4483
<v-card-actions class="justify-center">
4584
<v-btn
@@ -49,6 +88,7 @@
4988
variant="tonal"
5089
block
5190
@click="login"
91+
:disabled="!isEnterprise"
5292
>
5393
LOGIN
5494
</v-btn>
@@ -62,21 +102,33 @@
62102
</v-container>
63103
</v-main>
64104
</v-app>
105+
106+
<PaywallDialog
107+
v-model="isEnterprise"
108+
filter="enterprise"
109+
title="Unlock Full Managing with ShellHub Enterprise!"
110+
subtitle="Upgrade to ShellHub Enterprise (Managed or On-Premises)
111+
and gain full control over your infrastructure with advanced security,
112+
management and priority support."
113+
/>
65114
</template>
66115

67116
<script setup lang="ts">
68117
import { ref } from "vue";
69118
import { useField } from "vee-validate";
70119
import * as yup from "yup";
71120
import { useRoute, useRouter } from "vue-router";
121+
import PaywallDialog from "@global/components/User/PaywallDialog.vue";
72122
import { useStore } from "../store";
73123
import Logo from "../assets/logo-inverted.png";
74124
import { createNewClient } from "../api/http";
125+
import { envVariables } from "@/envVariables";
75126
76127
const showPassword = ref(false);
77128
const store = useStore();
78129
const route = useRoute();
79130
const router = useRouter();
131+
const isEnterprise = ref(!envVariables.isEnterprise);
80132
81133
const { value: username, errorMessage: usernameError } = useField<string | undefined>(
82134
"name",
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3-
exports[`Login > Renders the component 1`] = `"<v-app-stub overlaps="" fullheight="true"></v-app-stub>"`;
3+
exports[`Login > Renders the component 1`] = `
4+
"<v-app-stub overlaps="" fullheight="true"></v-app-stub>
5+
<paywall-dialog-stub filter="enterprise" title="Unlock Full Managing with ShellHub Enterprise!" subtitle="Upgrade to ShellHub Enterprise (Managed or On-Premises)
6+
and gain full control over your infrastructure with advanced security,
7+
management and priority support." modelvalue="true"></paywall-dialog-stub>"
8+
`;

ui/src/components/User/PaywallDialog.vue renamed to ui/global/components/User/PaywallDialog.vue

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
v-model="dialog"
44
transition="dialog-bottom-transition"
55
max-width="650"
6-
height="800"
6+
max-height="800"
77
@click:outside="close"
88
@keydown.esc="close"
99
>
@@ -23,20 +23,18 @@
2323
<v-row>
2424
<v-col class="pb-0">
2525
<h1 class="d-flex justify-center align-center text-center" data-test="upgrade-heading">
26-
Upgrade to have access to all features!
26+
{{ title }}
2727
</h1>
2828
</v-col>
2929
</v-row>
3030
<v-row>
3131
<v-col class="pt-1 pb-6">
3232
<p class="d-flex justify-center align-center text-grey text-center" data-test="upgrade-description">
33-
To use this feature, upgrade from the ShellHub Community Edition to one of our premium editions.
34-
Each edition of ShellHub offers its own set of features and benefits, making it easy to find the
35-
right solution for your needs.
33+
{{ subtitle }}
3634
</p>
3735
</v-col>
3836
</v-row>
39-
<div v-if="items.length === 0">
37+
<div v-if="filteredItems.length === 0">
4038
<v-row>
4139
<v-col class="d-flex align-center justify-center">
4240
<v-btn
@@ -52,7 +50,7 @@
5250
</v-row>
5351
</div>
5452
<v-row v-else data-test="items-row">
55-
<v-col v-for="(item, i) in items" :key="i" :data-test="'item-' + i">
53+
<v-col v-for="(item, i) in filteredItems" :key="i" :data-test="'item-' + i">
5654
<v-card class="bg-v-theme-surface border d-flex flex-column justify-space-between" height="100%" :data-test="'item-card-' + i">
5755
<v-card-title class="d-flex justify-center" :data-test="'item-title-' + i">
5856
<b>{{ item.title }}</b>
@@ -80,34 +78,57 @@
8078
</v-card>
8179
</v-col>
8280
</v-row>
83-
<v-card-actions data-test="card-actions">
84-
<v-spacer />
85-
<v-btn @click="close" class="mt-4" variant="text" data-test="close-btn">
86-
Close
87-
</v-btn>
88-
</v-card-actions>
8981
</v-container>
82+
<v-card-actions data-test="card-actions">
83+
<v-spacer />
84+
<v-btn @click="close" class="mt-4" variant="text" data-test="close-btn">
85+
Close
86+
</v-btn>
87+
</v-card-actions>
9088
</v-card>
9189
</v-dialog>
9290
</template>
9391

9492
<script setup lang="ts">
95-
import { computed, onMounted, ref } from "vue";
96-
import { useStore } from "@/store";
93+
import { computed, onMounted } from "vue";
94+
import { storeToRefs } from "pinia";
95+
import useGlobalStore from "@global/store/modules/global";
96+
97+
const props = defineProps<{
98+
filter?: "cloud" | "enterprise" | "all";
99+
title?: string;
100+
subtitle?: string;
101+
}>();
102+
103+
const emit = defineEmits(["close"]);
104+
const dialog = defineModel({ default: false });
97105
98-
const store = useStore();
99-
const dialog = ref(false);
100106
const close = () => {
101107
dialog.value = false;
102-
store.commit("users/setShowPaywall", false);
108+
emit("close");
103109
};
104-
const items = computed(() => store.getters["users/getPremiumContent"]);
110+
111+
const store = useGlobalStore();
112+
const { premiumContent } = storeToRefs(store);
113+
114+
const filteredItems = computed(() => {
115+
const filter = props.filter || "all";
116+
117+
if (filter === "cloud") {
118+
return premiumContent.value.filter((item) => item.title.includes("Cloud"));
119+
}
120+
if (filter === "enterprise") {
121+
return premiumContent.value.filter((item) => item.title.includes("Enterprise"));
122+
}
123+
return premiumContent.value;
124+
});
105125
106126
onMounted(() => {
107-
store.dispatch("users/getPremiumContent");
127+
store.getPaywallPremiumContent();
108128
});
109129
110130
defineExpose({ dialog });
131+
111132
</script>
112133

113134
<style scoped>

ui/global/components/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import PaywallDialog from "./User/PaywallDialog.vue";
2+
3+
export default { PaywallDialog };

ui/global/store/api/global.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const premiumContent = async () => {
2+
const response = await fetch("https://static.shellhub.io/premium-features.v1.json");
3+
const data = await response.json();
4+
return data;
5+
};
6+
7+
export default premiumContent;

ui/global/store/modules/global.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { defineStore } from "pinia";
2+
import premiumContent from "../api/global";
3+
4+
type PremiumItem = {
5+
title: string;
6+
features: string[];
7+
button: {
8+
label: string;
9+
link: string;
10+
};
11+
};
12+
13+
const useGlobalStore = defineStore("global", {
14+
state: () => ({
15+
premiumContent: [] as PremiumItem[],
16+
}),
17+
actions: {
18+
async getPaywallPremiumContent() {
19+
try {
20+
const res = await premiumContent();
21+
this.premiumContent = res;
22+
} catch (error) {
23+
console.error(error);
24+
throw error;
25+
}
26+
},
27+
},
28+
});
29+
30+
export default useGlobalStore;

ui/tests/components/Users/PaywallDialog.spec.ts renamed to ui/global/tests/components/User/PaywallDialog.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { createVuetify } from "vuetify";
33
import MockAdapter from "axios-mock-adapter";
44
import { expect, describe, it, beforeEach, vi } from "vitest";
55
import { nextTick } from "vue";
6+
import PaywallDialog from "@global/components/User/PaywallDialog.vue";
7+
import { createPinia, setActivePinia } from "pinia";
68
import { store, key } from "@/store";
7-
import PaywallDialog from "@/components/User/PaywallDialog.vue";
89
import { router } from "@/router";
910
import { namespacesApi, devicesApi } from "@/api/http";
1011
import { SnackbarPlugin } from "@/plugins/snackbar";
@@ -92,6 +93,7 @@ describe("PaywallDialog", async () => {
9293
})));
9394

9495
beforeEach(async () => {
96+
setActivePinia(createPinia());
9597
const el = document.createElement("div");
9698
document.body.appendChild(el);
9799

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`PaywallDialog > Renders the component 1`] = `""`;

ui/package-lock.json

Lines changed: 36 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)