Added Terms Of Service and Privacy Policies pages. #1
14 changed files with 98 additions and 24 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
22
app/pages/legal/privacy-policy.vue
Normal file
22
app/pages/legal/privacy-policy.vue
Normal 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>
|
||||||
22
app/pages/legal/terms-of-service.vue
Normal file
22
app/pages/legal/terms-of-service.vue
Normal 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
4
package-lock.json
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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 () => {
|
||||||
|
|
|
||||||
11
tests/pages/legal/privacy-policy.test.ts
Normal file
11
tests/pages/legal/privacy-policy.test.ts
Normal 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
11
tests/pages/legal/terms-of-service.test.ts
Normal file
11
tests/pages/legal/terms-of-service.test.ts
Normal 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"@/*": ["./app/*"],
|
"@/*": ["./app/*"],
|
||||||
"#shared": ["./shared"],
|
"#shared": ["./shared"],
|
||||||
"#shared/*": ["./shared/*"],
|
"#shared/*": ["./shared/*"],
|
||||||
|
"#app": ["./node_modules/nuxt/dist/app"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["./tests/**/*"]
|
"include": ["./tests/**/*"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue