Compare commits
41 Commits
3ea91d9058
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 110cd52a95 | |||
| e54e0c4cf3 | |||
| 6ad37399e0 | |||
| 0bcaf2c499 | |||
| 1a99fc6572 | |||
| c0660049f4 | |||
| 0acf354473 | |||
| 3301b595de | |||
| b919c22cd7 | |||
| ba7de85d65 | |||
| 5a536e34d4 | |||
| 22d24e0f7b | |||
| 45adb0b137 | |||
| 14c71a07c0 | |||
| 5ec214565c | |||
| 854e80cddd | |||
| eeed7497cb | |||
| b2b27e06fe | |||
| be647b36a4 | |||
| 8f25837bf4 | |||
| 7ab88db5e2 | |||
|
|
68aed538c6 | ||
|
|
f6ab446a91 | ||
|
|
2063878411 | ||
|
|
b857c81624 | ||
|
|
eea47b714a | ||
|
|
8483926e19 | ||
| 211fa2982a | |||
| 45d18746d0 | |||
| 7e199c1ce2 | |||
| 0d7f09b807 | |||
| a4d97c4ef2 | |||
| 8f8c7d895c | |||
| 773522b725 | |||
| 53c13156a2 | |||
| 3c041b1d83 | |||
| f89c302f84 | |||
| 526482c8b5 | |||
| eb8b7e8604 | |||
|
|
cddfa0b3b9 | ||
|
|
c28c0a41cc |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.idea
|
||||||
|
.git
|
||||||
|
.nuxt
|
||||||
|
.output
|
||||||
|
node_modules
|
||||||
34
Dockerfile
34
Dockerfile
@@ -1,25 +1,29 @@
|
|||||||
# This dockerfile is to turn .output into a docker image
|
# Build Stage 1
|
||||||
# You still need to install the project on your machine and build it.
|
|
||||||
# Only then you run this docker file
|
|
||||||
|
|
||||||
# This project by default uses pnpm.
|
FROM node:22-alpine AS build
|
||||||
# If you wish to use npm, delete "pnpm-lock.yaml" first.
|
WORKDIR /app
|
||||||
|
|
||||||
# 1. pnpm (or npm) i # If you get GNU problems, use pnpm (or npm) i --force
|
RUN corepack enable
|
||||||
# 2. pnpm (or npm) run build # Sit back while it compiles
|
|
||||||
# 3. sudo docker build . -t myapp:latest (replace 'myapp' to whatever tag you wanna give your image)
|
|
||||||
|
|
||||||
# You should have the image now !
|
# Copy package.json and your lockfile, here we add pnpm-lock.yaml for illustration
|
||||||
# Now you just need to create the container
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
|
||||||
# Here is an example command for creating a container
|
# Install dependencies
|
||||||
# sudo docker run -d --name myappcontainer --network host -e PORT=3000 myapp:latest
|
RUN pnpm i
|
||||||
|
|
||||||
|
# Copy the entire project
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
# Build the project
|
||||||
|
RUN pnpm run build
|
||||||
|
|
||||||
|
# Build Stage 2
|
||||||
|
|
||||||
FROM node:22-alpine
|
FROM node:22-alpine
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy the build's result
|
# Only `.output` folder is needed from the build stage
|
||||||
COPY .output/ .
|
COPY --from=build /app/.output/ ./
|
||||||
|
|
||||||
# Change the port and host
|
# Change the port and host
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
@@ -27,4 +31,4 @@ ENV HOST=0.0.0.0
|
|||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["node", "/app/server/index.mjs"]
|
CMD ["node", "/app/server/index.mjs"]
|
||||||
|
|||||||
30
Dockerfile.output.bak
Normal file
30
Dockerfile.output.bak
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# This dockerfile is to turn .output into a docker image
|
||||||
|
# You still need to install the project on your machine and build it.
|
||||||
|
# Only then you run this docker file
|
||||||
|
|
||||||
|
# This project by default uses pnpm.
|
||||||
|
# If you wish to use npm, delete "pnpm-lock.yaml" first.
|
||||||
|
|
||||||
|
# 1. pnpm (or npm) i # If you get GNU problems, use pnpm (or npm) i --force
|
||||||
|
# 2. pnpm (or npm) run build # Sit back while it compiles
|
||||||
|
# 3. sudo docker build . -t myapp:latest (replace 'myapp' to whatever tag you wanna give your image)
|
||||||
|
|
||||||
|
# You should have the image now !
|
||||||
|
# Now you just need to create the container
|
||||||
|
|
||||||
|
# Here is an example command for creating a container
|
||||||
|
# sudo docker run -d --name myappcontainer --network host -e PORT=3000 myapp:latest
|
||||||
|
|
||||||
|
FROM node:22-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the build's result
|
||||||
|
COPY .output/ .
|
||||||
|
|
||||||
|
# Change the port and host
|
||||||
|
ENV PORT=3000
|
||||||
|
ENV HOST=0.0.0.0
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "/app/server/index.mjs"]
|
||||||
@@ -1,17 +1,32 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap'); /* geist font */
|
||||||
|
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "@nuxt/ui";
|
@import "@nuxt/ui";
|
||||||
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-sans: 'Public Sans', sans-serif;
|
--font-sans: 'Space Grotesk', sans-serif;
|
||||||
--ui-bg: rgb(220,220,220);
|
/*--font-sans: 'Public Sans', sans-serif; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.light {
|
||||||
|
--ui-bg: var(--color-stone-100);
|
||||||
|
--ui-primary: var(--color-stone-100);
|
||||||
|
--sidebar-bg: oklch(85% 0.001 106.424);
|
||||||
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
/*--ui-bg: var(--ui-color-neutral-950);*/
|
/*--ui-bg: var(--ui-color-neutral-950);*/
|
||||||
--ui-bg: rgb(17,17,17);
|
--ui-bg: oklch(16.7% 0.004 49.25);
|
||||||
--ui-bg-accented: rgba(189, 23, 255, 0.3);
|
--ui-primary: var(--color-neutral-900);
|
||||||
--ui-bg-elevated: rgba(189, 23, 255, 0.2);
|
--sidebar-bg: oklch(18.7% 0.004 49.25);
|
||||||
|
}
|
||||||
|
|
||||||
--ui-primary: rgb(189, 23, 255);
|
.sidebar-light {
|
||||||
|
--ui-bg: var(--color-stone-300);
|
||||||
|
}
|
||||||
|
.sidebar-dark {
|
||||||
|
background-color: var(--color-stone-800);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-widget-bg {
|
.dark-widget-bg {
|
||||||
|
|||||||
37
app/assets/css/ui_colors.css
Normal file
37
app/assets/css/ui_colors.css
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
.ui-red {
|
||||||
|
--ui-primary: var(--color-red-500);
|
||||||
|
--ui-bg-elevated: oklch(63.7% 0.237 25.331 / 20%);
|
||||||
|
--ui-bg-accented: oklch(63.7% 0.237 25.331 / 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-blue {
|
||||||
|
--ui-primary: var(--color-blue-600);
|
||||||
|
--ui-bg-elevated: oklch(54.6% 0.245 262.881 / 20%);
|
||||||
|
--ui-bg-accented: oklch(54.6% 0.245 262.881 / 40%);
|
||||||
|
}
|
||||||
|
.ui-green {
|
||||||
|
--ui-primary: var(--color-green-500);
|
||||||
|
--ui-bg-elevated: oklch(72.3% 0.219 149.579 / 20%);
|
||||||
|
--ui-bg-accented: oklch(72.3% 0.219 149.579 / 40%);
|
||||||
|
}
|
||||||
|
.ui-violet {
|
||||||
|
--ui-primary: var(--color-violet-500);
|
||||||
|
--ui-bg-elevated: oklch(60.6% 0.25 292.717 / 20%);
|
||||||
|
--ui-bg-accented: oklch(60.6% 0.25 292.717 / 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-indigo {
|
||||||
|
--ui-primary: var(--color-indigo-500);
|
||||||
|
--ui-bg-elevated: oklch(58.5% 0.233 277.117 / 20%);
|
||||||
|
--ui-bg-accented: oklch(58.5% 0.233 277.117 / 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-orange {
|
||||||
|
--ui-primary: var(--color-orange-600);
|
||||||
|
}
|
||||||
|
.ui-cyan {
|
||||||
|
--ui-primary: var(--color-cyan-500);
|
||||||
|
--ui-bg-elevated: oklch(71.5% 0.143 215.221 / 20%);
|
||||||
|
--ui-bg-accented: oklch(71.5% 0.143 215.221 / 40%);
|
||||||
|
}
|
||||||
12
app/assets/css/ui_fonts.css
Normal file
12
app/assets/css/ui_fonts.css
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.font-pubsans {
|
||||||
|
--font-sans: "Public Sans", sans-serif;
|
||||||
|
}
|
||||||
|
.font-geist {
|
||||||
|
--font-sans: "Geist One", sans-serif;
|
||||||
|
}
|
||||||
|
.font-nunito {
|
||||||
|
--font-sans: "Nunito", sans-serif;
|
||||||
|
}
|
||||||
|
.font-grot {
|
||||||
|
--font-sans: "Space Grotesk", sans-serif;
|
||||||
|
}
|
||||||
25
app/components/dashboard/header.vue
Normal file
25
app/components/dashboard/header.vue
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { literal, string } from 'zod';
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: "Title"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UHeader :title="props.title" toggle-side="left" :to="undefined">
|
||||||
|
<template #toggle>
|
||||||
|
<!---<UDashboardSidebarCollapse />-->
|
||||||
|
<UDashboardSidebarToggle />
|
||||||
|
</template>
|
||||||
|
</UHeader>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
11
app/components/dashboard/settings/nav_header.vue
Normal file
11
app/components/dashboard/settings/nav_header.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<
|
||||||
|
</template>
|
||||||
@@ -10,7 +10,7 @@ const items_def: NavigationMenuItem[][] = [
|
|||||||
active: true
|
active: true
|
||||||
}, {
|
}, {
|
||||||
label: 'Inbox',
|
label: 'Inbox',
|
||||||
icon: 'i-lucide-inbox',
|
icon: 'solar:chat-round-unread-outline',
|
||||||
badge: '4'
|
badge: '4'
|
||||||
}, {
|
}, {
|
||||||
label: 'Contacts',
|
label: 'Contacts',
|
||||||
|
|||||||
22
app/composables/ui_cookies.ts
Normal file
22
app/composables/ui_cookies.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type {CookieRef} from "#app";
|
||||||
|
|
||||||
|
function ui_cookie(name:string, def:string,prefix:string): CookieRef<string> {
|
||||||
|
|
||||||
|
const c = useCookie(name,{
|
||||||
|
default: () => def,
|
||||||
|
|
||||||
|
});
|
||||||
|
watch(c, (newVal, oldVal) => {
|
||||||
|
// remove old class (if it's been put)
|
||||||
|
if (oldVal != undefined) document.body.classList.remove(prefix + oldVal.toLowerCase());
|
||||||
|
|
||||||
|
// add new class
|
||||||
|
document.body.classList.add(prefix + newVal.toLowerCase());
|
||||||
|
}, { immediate: true})
|
||||||
|
|
||||||
|
return c;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ui_color_cookie() { return ui_cookie("ui_color","Green","ui-"); }
|
||||||
|
export function ui_font_cookie() { return ui_cookie("ui_font","pubsans","font-"); }
|
||||||
67
app/layouts/dash_settings.vue
Normal file
67
app/layouts/dash_settings.vue
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { NavigationMenuItem } from '@nuxt/ui'
|
||||||
|
|
||||||
|
const nav : NavigationMenuItem[][] = [[
|
||||||
|
{
|
||||||
|
label: 'Home',
|
||||||
|
icon: 'i-lucide-house',
|
||||||
|
to: "/dashboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Notifications',
|
||||||
|
icon: 'solar:chat-round-unread-outline',
|
||||||
|
badge: '4',
|
||||||
|
to: "/events"
|
||||||
|
}, {
|
||||||
|
label: 'Settings',
|
||||||
|
icon: 'i-lucide-settings',
|
||||||
|
//to: '/settings',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: "General",
|
||||||
|
icon: "solar:settings-minimalistic-broken",
|
||||||
|
to: "/settings",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Profile",
|
||||||
|
icon: "solar:user-broken",
|
||||||
|
to: "/settings/profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Theme",
|
||||||
|
icon: "solar:pallete-2-broken",
|
||||||
|
to: "/settings/theme"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const title = ref("...");
|
||||||
|
|
||||||
|
watch(route, (newVal, oldVal) => {
|
||||||
|
title.value = (newVal.meta.title as string) || "Settings";
|
||||||
|
}, {immediate: true});
|
||||||
|
//const title = pageMeta.title || "Settings"
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
ui_color_cookie();
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<UDashboardGroup>
|
||||||
|
<DashboardSidebar class="bg-(--sidebar-bg)" collapsible :resizable="undefined" :items="nav"/>
|
||||||
|
|
||||||
|
<UDashboardSidebarCollapse />
|
||||||
|
|
||||||
|
<UContainer class="max-w-full overflow-auto">
|
||||||
|
<DashboardHeader :title="title"/>
|
||||||
|
<UNavigationMenu :items="nav[0][2].children" />
|
||||||
|
<!-- in my case I want the button to be the same everywhere.
|
||||||
|
if you wanna customize it per page, place it per page inside UContainer -->
|
||||||
|
<!--<UDashboardSidebarToggle variant="subtle" class="absolute z-20 top-3 left-2.5"/>-->
|
||||||
|
<slot />
|
||||||
|
</UContainer>
|
||||||
|
</UDashboardGroup>
|
||||||
|
</template>>
|
||||||
@@ -5,29 +5,63 @@ const nav : NavigationMenuItem[][] = [[
|
|||||||
{
|
{
|
||||||
label: 'Home',
|
label: 'Home',
|
||||||
icon: 'i-lucide-house',
|
icon: 'i-lucide-house',
|
||||||
active: true,
|
|
||||||
to: "/dashboard"
|
to: "/dashboard"
|
||||||
}, {
|
}, {
|
||||||
label: 'Inbox',
|
label: 'Notifications',
|
||||||
icon: 'i-lucide-inbox',
|
icon: 'solar:chat-round-unread-outline',
|
||||||
badge: '4',
|
badge: '4',
|
||||||
to: "/inbox"
|
to: "/events"
|
||||||
}, {
|
}, {
|
||||||
label: 'Contacts',
|
label: 'Settings',
|
||||||
icon: 'i-lucide-users',
|
icon: 'i-lucide-settings',
|
||||||
to: "/contacts"
|
//to: '/settings',
|
||||||
}]];
|
children: [
|
||||||
|
{
|
||||||
|
label: "General",
|
||||||
|
icon: "solar:settings-minimalistic-broken",
|
||||||
|
to: "/settings",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Profile",
|
||||||
|
icon: "solar:user-broken",
|
||||||
|
to: "/settings/profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Theme",
|
||||||
|
icon: "solar:pallete-2-broken",
|
||||||
|
to: "/settings/theme"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
];
|
||||||
/*[
|
/*[
|
||||||
{
|
{
|
||||||
label: "Feedback"
|
label: "Feedback"
|
||||||
}
|
}
|
||||||
]];*/
|
]];*/
|
||||||
|
|
||||||
|
// set color
|
||||||
|
/*useHead({
|
||||||
|
bodyAttrs: {
|
||||||
|
class: "ui-blue"
|
||||||
|
}
|
||||||
|
})*/
|
||||||
|
onMounted(() => {
|
||||||
|
ui_color_cookie();
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<UDashboardGroup>
|
<UDashboardGroup>
|
||||||
<DashboardSidebar collapsible resizable :items="nav"/>
|
<DashboardSidebar class="bg-(--sidebar-bg)" collapsible :resizable="undefined" :items="nav"/>
|
||||||
|
|
||||||
<UDashboardSidebarCollapse />
|
<UDashboardSidebarCollapse />
|
||||||
|
|
||||||
<slot />
|
<UContainer class="max-w-full overflow-auto">
|
||||||
|
<!-- in my case I want the button to be the same everywhere.
|
||||||
|
if you wanna customize it per page, place it per page inside UContainer -->
|
||||||
|
<!--<UDashboardSidebarToggle variant="subtle" class="absolute z-20 top-3 left-2.5"/>-->
|
||||||
|
<slot />
|
||||||
|
</UContainer>
|
||||||
</UDashboardGroup>
|
</UDashboardGroup>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
15
app/layouts/default.vue
Normal file
15
app/layouts/default.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- the whole content must be in a UContainer -->
|
||||||
|
<!-- overflow auto allows scroll -->
|
||||||
|
<!--<UContainer class="overflow-auto">-->
|
||||||
|
<slot />
|
||||||
|
<!--</UContainer>-->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { TableColumn } from "@nuxt/ui"
|
import type { TableColumn } from "@nuxt/ui"
|
||||||
|
//import UBadge from "@nuxt/ui";
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
@@ -114,21 +115,19 @@ import { h } from 'vue'
|
|||||||
{
|
{
|
||||||
accessorKey: 'amount',
|
accessorKey: 'amount',
|
||||||
header: "Ammount",
|
header: "Ammount",
|
||||||
cell: ({ row }) => { return "€" + row.getValue('amount') }
|
cell: ({ row }) => { return h('span', {class: "text-success"},"€" + row.getValue('amount')) }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UContainer>
|
<DashboardHeader title="Dashboard"/>
|
||||||
|
<UContainer class="max-w-full">
|
||||||
<UDashboardSidebarToggle variant="subtle" class="absolute z-20 top-3 left-2.5"/>
|
<div class="mt-3 mb-3 flex flex-col w-full relative z-0">
|
||||||
|
|
||||||
<div class="mt-1 mb-3 flex flex-col w-full relative z-0">
|
|
||||||
<ProseH2 class="text-center m-0.5 w-full">Hey there, user!</ProseH2>
|
<ProseH2 class="text-center m-0.5 w-full">Hey there, user!</ProseH2>
|
||||||
<ProseP class="text-center text-muted m-0.5">Welcome to the UI!</ProseP>
|
<ProseP class="text-center text-muted m-0.5">Welcome to the UI!</ProseP>
|
||||||
<UBadge variant="subtle" class="inline mx-auto">It's cool, isn't it ?</UBadge>
|
<UBadge variant="subtle" color="success" class="inline mx-auto">It's cool, isn't it ?</UBadge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--<div class="mx-auto max-w-220">-->
|
<!--<div class="mx-auto max-w-220">-->
|
||||||
@@ -139,16 +138,18 @@ import { h } from 'vue'
|
|||||||
:description="card.desc"
|
:description="card.desc"
|
||||||
:icon="card.icon"
|
:icon="card.icon"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
class="dark-widget-bg col-lg lg:rounded-none first:lg:rounded-l-2xl last:lg:rounded-r-2xl sm:rounded-2xl"
|
class="dark-widget-bg lg:rounded-none first:lg:rounded-l-2xl last:lg:rounded-r-2xl sm:rounded-2xl"
|
||||||
/>
|
/>
|
||||||
</UPageGrid>
|
</UPageGrid>
|
||||||
|
|
||||||
|
|
||||||
<UTable :data="table_demo" :columns="table_headers"/>
|
<UTable :data="table_demo" :columns="table_headers" class="flex-1 "/>
|
||||||
</UContainer>
|
</UContainer>
|
||||||
|
<UFooter>
|
||||||
|
<p>footer</p>
|
||||||
|
</UFooter>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import "bootstrap/dist/css/bootstrap-grid.css";
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
18
app/pages/events.vue
Normal file
18
app/pages/events.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
layout: "dashboard"
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UHeader title="Notifications & Events" toggle-side="left">
|
||||||
|
<template #toggle>
|
||||||
|
<UDashboardSidebarToggle />
|
||||||
|
</template>
|
||||||
|
</UHeader>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
95
app/pages/login.vue
Normal file
95
app/pages/login.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { loggedIn, user, fetch: refreshSession } = useUserSession();
|
||||||
|
import * as z from 'zod'
|
||||||
|
import type { FormSubmitEvent, AuthFormField } from '@nuxt/ui'
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const providers = [{
|
||||||
|
label: 'Google',
|
||||||
|
icon: 'i-simple-icons-google',
|
||||||
|
onClick: () => {
|
||||||
|
toast.add({ title: 'Google', description: 'Login with Google' })
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: 'GitHub',
|
||||||
|
icon: 'i-simple-icons-github',
|
||||||
|
onClick: () => {
|
||||||
|
toast.add({ title: 'GitHub', description: 'Login with GitHub' })
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
const fields: AuthFormField[] = [{
|
||||||
|
name: 'email',
|
||||||
|
type: 'email',
|
||||||
|
label: 'Email',
|
||||||
|
placeholder: 'Enter your email',
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
name: 'password',
|
||||||
|
label: 'Password',
|
||||||
|
type: 'password',
|
||||||
|
placeholder: 'Enter your password',
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
name: 'remember',
|
||||||
|
label: 'Remember me',
|
||||||
|
type: 'checkbox'
|
||||||
|
}]
|
||||||
|
|
||||||
|
// actual login logic
|
||||||
|
|
||||||
|
const error_popup = ref(false)
|
||||||
|
|
||||||
|
async function sendLogin(u:string,p:string) {
|
||||||
|
try {
|
||||||
|
const r = await $fetch("/api/login", {
|
||||||
|
method: "POST",
|
||||||
|
body: {"email": u, "password": p}
|
||||||
|
})
|
||||||
|
await refreshSession()
|
||||||
|
await navigateTo('/dashboard') // user home page
|
||||||
|
} catch {
|
||||||
|
error_popup.value = true // show popup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
email: z.email('Invalid email'),
|
||||||
|
password: z.string('Password is required').min(8, 'Must be at least 8 characters')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function onSubmit(payload: FormSubmitEvent<schema>) {
|
||||||
|
const data = payload.data.email
|
||||||
|
console.log('Submitted', data)
|
||||||
|
sendLogin(payload.data.email, payload.data.password)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center justify-center gap-4 p-4">
|
||||||
|
<UPageCard class="w-full max-w-md">
|
||||||
|
<UAuthForm
|
||||||
|
:schema="schema"
|
||||||
|
title="Login"
|
||||||
|
description="Enter your credentials to access your account."
|
||||||
|
icon="i-lucide-user"
|
||||||
|
:fields="fields"
|
||||||
|
:providers="providers"
|
||||||
|
@submit="onSubmit"
|
||||||
|
>
|
||||||
|
<template #validation>
|
||||||
|
<UAlert v-if="error_popup" color="error" variant="subtle" icon="i-lucide-info" title="Error signing in" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</UAuthForm>
|
||||||
|
</UPageCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
15
app/pages/settings/index.vue
Normal file
15
app/pages/settings/index.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
layout: "dash-settings",
|
||||||
|
title: "Settings"
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
12
app/pages/settings/profile.vue
Normal file
12
app/pages/settings/profile.vue
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
layout: "dash-settings"
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
14
app/pages/settings/security.vue
Normal file
14
app/pages/settings/security.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
layout: "dash-settings"
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
63
app/pages/settings/theme.vue
Normal file
63
app/pages/settings/theme.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type {CookieRef} from "#app";
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: "dash-settings",
|
||||||
|
title: "Customization"
|
||||||
|
})
|
||||||
|
|
||||||
|
const colors_list = ref(["Cyan","Red","Green","Blue","Purple"])
|
||||||
|
const color = ref("...");
|
||||||
|
|
||||||
|
const fonts_list = ref(["Public Sans","Space Grotesk", "Geist"])
|
||||||
|
const font = ref("...");
|
||||||
|
|
||||||
|
const ready = ref(false);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
color.value = ui_color_cookie().value
|
||||||
|
ready.value = true
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(color, (newVal, oldVal) => {
|
||||||
|
ui_color_cookie().value = newVal;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<UContainer class="mt-4 max-w-full justify-center">
|
||||||
|
<UFormField
|
||||||
|
label="Main Color"
|
||||||
|
orientation="horizontal"
|
||||||
|
description="Select the main color of the UI"
|
||||||
|
size="lg"
|
||||||
|
class="flex justify-between w-full mt-3 mb-3"
|
||||||
|
:disabled="!ready"
|
||||||
|
>
|
||||||
|
<USelect v-model="color" :items="colors_list" />
|
||||||
|
</UFormField>
|
||||||
|
<UFormField
|
||||||
|
label="Mode"
|
||||||
|
orientation="horizontal"
|
||||||
|
description="Select if you want Light or Dark mode"
|
||||||
|
size="lg"
|
||||||
|
class="flex justify-between w-full mt-3 mb-3"
|
||||||
|
>
|
||||||
|
<UColorModeSelect />
|
||||||
|
</UFormField>
|
||||||
|
<UFormField
|
||||||
|
label="Font"
|
||||||
|
orientation="horizontal"
|
||||||
|
description="Select which font you want"
|
||||||
|
size="lg"
|
||||||
|
class="flex justify-between w-full mt-3 mb-3"
|
||||||
|
:disabled="!ready"
|
||||||
|
>
|
||||||
|
<USelect v-model="font" :items="fonts_list" />
|
||||||
|
</UFormField>
|
||||||
|
</UContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
// @ts-ignore
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: '2025-07-15',
|
compatibilityDate: '2025-07-15',
|
||||||
devtools: {enabled: true},
|
devtools: {enabled: true},
|
||||||
css: ['./app/assets/css/global.css' ],
|
css: ['@/assets/css/global.css', '@/assets/css/ui_colors.css', /*'@/assets/css/ui_fonts.css' */],
|
||||||
modules: ['@nuxt/image', '@nuxt/ui', '@nuxt/test-utils', '@nuxtjs/mdc'],
|
modules: [
|
||||||
|
'@nuxt/image',
|
||||||
|
'@nuxt/ui',
|
||||||
|
'@nuxt/test-utils',
|
||||||
|
'@nuxtjs/mdc',
|
||||||
|
'nuxt-auth-utils',
|
||||||
|
],
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [
|
plugins: [
|
||||||
tailwindcss()
|
tailwindcss(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
14
package.json
14
package.json
@@ -7,7 +7,8 @@
|
|||||||
"dev": "nuxt dev",
|
"dev": "nuxt dev",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare",
|
||||||
|
"start": "node .output/server/index.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/image": "^2.0.0",
|
"@nuxt/image": "^2.0.0",
|
||||||
@@ -15,18 +16,19 @@
|
|||||||
"@nuxt/ui": "^4.2.0",
|
"@nuxt/ui": "^4.2.0",
|
||||||
"@nuxtjs/mdc": "0.19.1",
|
"@nuxtjs/mdc": "0.19.1",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@vueuse/core": "^14.0.0",
|
"@vueuse/core": "^10.0.0",
|
||||||
"@vueuse/head": "github:vueuse/head",
|
"@vueuse/head": "github:vueuse/head",
|
||||||
"bootstrap": "^5.3.8",
|
"nuxt-auth-utils": "0.5.25",
|
||||||
"nuxt": "^4.2.1",
|
|
||||||
"nuxt-storm": "^1.1.3",
|
"nuxt-storm": "^1.1.3",
|
||||||
"tailwindcss": "^4.1.17",
|
"tailwindcss": "^4.1.17",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vue": "^3.5.24",
|
"vue": "^3.5.24",
|
||||||
"vue-router": "^4.6.3"
|
"vue-router": "^4.6.3",
|
||||||
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/lucide": "^1.2.77",
|
"@iconify-json/lucide": "^1.2.77",
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0"
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
|
"nuxt": "^4.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1538
pnpm-lock.yaml
generated
1538
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
38
server/api/login.post.ts
Normal file
38
server/api/login.post.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
const bodySchema = z.object({
|
||||||
|
email: z.string().email(),
|
||||||
|
password: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
function check_login(user: string,pwd: string): number {
|
||||||
|
/*const r = $fetch("http://localhost:5000/login", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({user: user, pwd: pwd}),
|
||||||
|
})*/
|
||||||
|
if (user === "tomas@suricatingss.xyz" && pwd === "tomas190905")
|
||||||
|
return 0; // 0 = no errror = success
|
||||||
|
else return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const { email, password } = await readValidatedBody(event, bodySchema.parse)
|
||||||
|
|
||||||
|
const response = (check_login(email, password));
|
||||||
|
switch (response) {
|
||||||
|
case 0:
|
||||||
|
// set the user session in the cookie
|
||||||
|
// this server util is auto-imported by the auth-utils module
|
||||||
|
await setUserSession(event, {
|
||||||
|
user: {
|
||||||
|
name: 'John Doe',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return "";
|
||||||
|
case 1:
|
||||||
|
setResponseStatus(event, 403);
|
||||||
|
return "Incorrect login";
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
13
server/api/register.post.ts
Normal file
13
server/api/register.post.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
const bodyPost = z.object({
|
||||||
|
email: z.string().email(),
|
||||||
|
first_name: z.string(),
|
||||||
|
last_name: z.string(),
|
||||||
|
pwd: z.string()
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
Reference in New Issue
Block a user