Added Terms Of Service and Privacy Policies pages. #1

Manually merged
liviu merged 2 commits from GF-14-pages into production 2026-01-15 16:21:03 +01:00
14 changed files with 98 additions and 24 deletions
Showing only changes of commit dc65e01bec - Show all commits

View file

@ -58,8 +58,8 @@ const doLogin = async () => {
</CardContent> </CardContent>
</Card> </Card>
<FieldDescription class="px-6 text-center"> <FieldDescription class="px-6 text-center">
<NuxtLink to="/"><Button variant="link">Terms of Service</Button></NuxtLink> <NuxtLink to="/legal/terms-of-service"><Button variant="link">Terms of Service</Button></NuxtLink>
<NuxtLink to="/"><Button variant="link">Privacy Policy</Button></NuxtLink> <NuxtLink to="/legal/privacy-policy"><Button variant="link">Privacy Policy</Button></NuxtLink>
</FieldDescription> </FieldDescription>
</div> </div>
</template> </template>

View file

@ -74,8 +74,8 @@ const createAccount = async () => {
</Card> </Card>
</ClientOnly> </ClientOnly>
<FieldDescription class="px-6 text-center"> <FieldDescription class="px-6 text-center">
<NuxtLink to="/"><Button variant="link">Terms of Service</Button></NuxtLink> <NuxtLink to="/legal/terms-of-service"><Button variant="link">Terms of Service</Button></NuxtLink>
<NuxtLink to="/"><Button variant="link">Privacy Policy</Button></NuxtLink> <NuxtLink to="/legal/privacy-policy"><Button variant="link">Privacy Policy</Button></NuxtLink>
</FieldDescription> </FieldDescription>
</div> </div>
</template> </template>

View file

@ -8,12 +8,8 @@ import { SidebarInset, SidebarProvider, SidebarTrigger } from "~/components/ui/s
import { Separator } from "~/components/ui/separator"; import { Separator } from "~/components/ui/separator";
import { useRuntimeConfig } from "#app";
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
const config = useRuntimeConfig();
const authStore = useAuthStore(); const authStore = useAuthStore();
await authStore.init(); await authStore.init();
</script> </script>
@ -31,12 +27,8 @@ await authStore.init();
<slot /> <slot />
</main> </main>
<footer class="flex h-12 shrink-0 items-center gap-2 border-b px-4" data-testid="footer"> <footer class="flex h-12 shrink-0 items-center gap-2 border-b px-4" data-testid="footer">
<div v-if="currentYear === 2025" class="bg-muted/50 flex-1 rounded-xl p-2 text-center"> <div v-if="currentYear === 2025" class="bg-muted/50 flex-1 rounded-xl p-2 text-center">Glowing Fiesta 2025</div>
Glowing Fiesta 2025 <span class="text-muted-foreground">({{ config.public.appVersion }})</span> <div v-else class="bg-muted/50 flex-1 rounded-xl p-2 text-center">Glowing Fiesta 2025 - {{ currentYear }}</div>
</div>
<div v-else class="bg-muted/50 flex-1 rounded-xl p-2 text-center">
Glowing Fiesta 2025 - {{ currentYear }} <span class="text-muted-foreground">({{ config.public.appVersion }})</span>
</div>
</footer> </footer>
</SidebarInset> </SidebarInset>
</SidebarProvider> </SidebarProvider>

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue"; import { computed } from "vue";
import { BookOpen, ChevronRight, HandCoins, Settings2, SquareTerminal } from "lucide-vue-next"; import { useRuntimeConfig } from "#app";
import { BookOpen, BookOpenText, ChevronRight, HandCoins, Settings2, SquareTerminal } from "lucide-vue-next";
import { import {
Sidebar, Sidebar,
@ -53,6 +54,16 @@ const data = {
{ title: "Changelog", url: "#" }, { title: "Changelog", url: "#" },
], ],
}, },
{
title: "Legal",
url: "#",
icon: BookOpenText,
isActive: true,
items: [
{ title: "Terms of Service", url: "/legal/terms-of-service" },
{ title: "Privacy Policy", url: "/legal/privacy-policy" },
],
},
{ {
title: "Settings", title: "Settings",
url: "#", url: "#",
@ -85,6 +96,8 @@ const props = withDefaults(defineProps<SidebarLayoutProps>(), {
}); });
const navMain = computed(() => props.navItems || data.navMain); const navMain = computed(() => props.navItems || data.navMain);
const config = useRuntimeConfig();
</script> </script>
<template> <template>
@ -101,7 +114,7 @@ const navMain = computed(() => props.navItems || data.navMain);
</div> </div>
<div class="flex flex-col gap-0.5 leading-none"> <div class="flex flex-col gap-0.5 leading-none">
<span class="font-bold text-primary">Glowing Fiesta</span> <span class="font-bold text-primary">Glowing Fiesta</span>
<span>v1.0.0</span> <span>v{{ config.public.appVersion }}</span>
</div> </div>
</NuxtLink> </NuxtLink>
</SidebarMenuButton> </SidebarMenuButton>

View file

@ -41,8 +41,8 @@ useSeoMeta({
</Card> </Card>
<FieldDescription class="px-6 text-center"> <FieldDescription class="px-6 text-center">
<NuxtLink to="/"><Button variant="link">Home</Button></NuxtLink> <NuxtLink to="/"><Button variant="link">Home</Button></NuxtLink>
<NuxtLink to="/"><Button variant="link">Terms of Service</Button></NuxtLink> <NuxtLink to="/legal/terms-of-service"><Button variant="link">Terms of Service</Button></NuxtLink>
<NuxtLink to="/"><Button variant="link">Privacy Policy</Button></NuxtLink> <NuxtLink to="/legal/privacy-policy"><Button variant="link">Privacy Policy</Button></NuxtLink>
</FieldDescription> </FieldDescription>
</div> </div>
</div> </div>

View file

@ -0,0 +1,22 @@
<script setup lang="ts">
import { useRuntimeConfig, useSeoMeta } from "#app";
import { useBreadcrumbStore } from "~/stores/breadcrumbs";
const breadcrumbStore = useBreadcrumbStore();
breadcrumbStore.setBreadcrumbs([{ label: "Legal" }, { label: "Privacy Policy", to: "/legal/privacy-policy" }]);
const config = useRuntimeConfig();
useSeoMeta({
title: "Privacy Policy",
description: "Privacy Policy page of a very nice all-purpose application",
ogTitle: "Privacy Policy",
ogDescription: "Privacy Policy page of a very nice all-purpose application",
ogImage: config.public.siteUrl + "/images/human.png",
});
</script>
<template>
<div class="bg-muted/50 min-h-screen flex-1 rounded-xl md:min-h-min flex px-4 py-2 gap-2">
<h1 class="text-2xl font-bold text-primary">Privacy Policy</h1>
</div>
</template>

View file

@ -0,0 +1,22 @@
<script setup lang="ts">
import { useRuntimeConfig, useSeoMeta } from "#app";
import { useBreadcrumbStore } from "~/stores/breadcrumbs";
const breadcrumbStore = useBreadcrumbStore();
breadcrumbStore.setBreadcrumbs([{ label: "Legal" }, { label: "Terms of Service", to: "/legal/terms-of-service" }]);
const config = useRuntimeConfig();
useSeoMeta({
title: "Terms of Service",
description: "Terms of Service page of a very nice all-purpose application",
ogTitle: "Terms of Service",
ogDescription: "Terms of Service page of a very nice all-purpose application",
ogImage: config.public.siteUrl + "/images/human.png",
});
</script>
<template>
<div class="bg-muted/50 min-h-screen flex-1 rounded-xl md:min-h-min flex px-4 py-2 gap-2">
<h1 class="text-2xl font-bold text-primary">Terms of Service</h1>
</div>
</template>

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "glowing-fiesta", "name": "glowing-fiesta",
"version": "0.0.0", "version": "0.0.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "glowing-fiesta", "name": "glowing-fiesta",
"version": "0.0.0", "version": "0.0.1",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@pinia/nuxt": "^0.11.3", "@pinia/nuxt": "^0.11.3",

View file

@ -1,6 +1,6 @@
{ {
"name": "glowing-fiesta", "name": "glowing-fiesta",
"version": "0.0.0", "version": "0.0.1",
"type": "module", "type": "module",
"private": true, "private": true,
"scripts": { "scripts": {

View file

@ -63,7 +63,7 @@ describe("Default.vue", () => {
}); });
await flushPromises(); await flushPromises();
const footer = wrapper.find("[data-testid='footer']"); const footer = wrapper.find("[data-testid='footer']");
expect(footer.text()).toBe("Glowing Fiesta 2025 (1.0.1)"); expect(footer.text()).toBe("Glowing Fiesta 2025");
}); });
it("footer shows range when current year is not 2025", async () => { it("footer shows range when current year is not 2025", async () => {
@ -75,7 +75,7 @@ describe("Default.vue", () => {
}); });
await flushPromises(); await flushPromises();
const footer = wrapper.find("[data-testid='footer']"); const footer = wrapper.find("[data-testid='footer']");
expect(footer.text()).toBe("Glowing Fiesta 2025 - 2069 (1.0.1)"); expect(footer.text()).toBe("Glowing Fiesta 2025 - 2069");
}); });
}); });
}); });

View file

@ -3,6 +3,7 @@ import { describe, expect, it, vi, beforeAll, afterAll } from "vitest";
import SidebarLayout from "~/layouts/default/Sidebar.vue"; import SidebarLayout from "~/layouts/default/Sidebar.vue";
import { ref } from "vue"; import { ref } from "vue";
import type * as SidebarUI from "~/components/ui/sidebar"; import type * as SidebarUI from "~/components/ui/sidebar";
import { useRuntimeConfig } from "#app";
const { useSidebarMock } = vi.hoisted(() => ({ const { useSidebarMock } = vi.hoisted(() => ({
useSidebarMock: vi.fn(), useSidebarMock: vi.fn(),
@ -112,6 +113,7 @@ vi.mock("lucide-vue-next", () => {
ChevronsUpDown: MockIcon, ChevronsUpDown: MockIcon,
CreditCard: MockIcon, CreditCard: MockIcon,
BookOpen: MockIcon, BookOpen: MockIcon,
BookOpenText: MockIcon,
HandCoins: MockIcon, HandCoins: MockIcon,
LogIn: MockIcon, LogIn: MockIcon,
LogOut: MockIcon, LogOut: MockIcon,
@ -162,7 +164,7 @@ describe("SidebarLayout", () => {
}); });
await flushPromises(); await flushPromises();
expect(wrapper.text()).toContain("Glowing Fiesta"); expect(wrapper.text()).toContain("Glowing Fiesta");
expect(wrapper.text()).toContain("v1.0.0"); expect(wrapper.text()).toContain(`v${useRuntimeConfig().public.appVersion}`);
}); });
it("renders sidebar content correctly", async () => { it("renders sidebar content correctly", async () => {

View file

@ -0,0 +1,11 @@
import { describe, expect, it } from "vitest";
import { mount, type VueWrapper } from "@vue/test-utils";
import PrivacyPolicyPage from "~/pages/legal/privacy-policy.vue";
describe("pages/legal/privacy-policy.vue", () => {
it("loads without crashing", () => {
const wrapper: VueWrapper = mount(PrivacyPolicyPage);
expect(wrapper.exists()).toBe(true);
expect(wrapper.text()).toContain("Privacy Policy");
});
});

View file

@ -0,0 +1,11 @@
import { describe, expect, it } from "vitest";
import { mount, type VueWrapper } from "@vue/test-utils";
import TermsOfServicePage from "~/pages/legal/terms-of-service.vue";
describe("pages/legal/terms-of-service.vue", () => {
it("loads without crashing", () => {
const wrapper: VueWrapper = mount(TermsOfServicePage);
expect(wrapper.exists()).toBe(true);
expect(wrapper.text()).toContain("Terms of Service");
});
});

View file

@ -12,6 +12,7 @@
"@/*": ["./app/*"], "@/*": ["./app/*"],
"#shared": ["./shared"], "#shared": ["./shared"],
"#shared/*": ["./shared/*"], "#shared/*": ["./shared/*"],
"#app": ["./node_modules/nuxt/dist/app"],
} }
}, },
"include": ["./tests/**/*"] "include": ["./tests/**/*"]