Compare commits

...

39 Commits

Author SHA1 Message Date
110cd52a95 new dynamic title 2025-12-31 12:30:43 +00:00
e54e0c4cf3 changed sel 2025-12-31 00:39:52 +00:00
6ad37399e0 cookies fixed 2025-12-31 00:39:10 +00:00
0bcaf2c499 sidebar color 2025-12-31 00:38:59 +00:00
1a99fc6572 full build now 2025-12-31 00:38:28 +00:00
c0660049f4 new font 2025-12-30 21:13:27 +00:00
0acf354473 no ucontainer 2025-12-30 21:13:01 +00:00
3301b595de colors 2025-12-30 21:12:53 +00:00
b919c22cd7 new ui font 2025-12-30 21:12:44 +00:00
ba7de85d65 color migrate 2025-12-30 21:12:01 +00:00
5a536e34d4 color cookie 2025-12-30 17:11:28 +00:00
22d24e0f7b color uis 2025-12-30 17:11:11 +00:00
45adb0b137 color cookie 2025-12-30 16:02:54 +00:00
14c71a07c0 color mode 2025-12-30 13:24:14 +00:00
5ec214565c undefined to 2025-12-30 13:24:06 +00:00
854e80cddd new colors 2025-12-30 13:23:36 +00:00
eeed7497cb changes to docker deploy 2025-12-22 22:18:42 +00:00
b2b27e06fe use header component 2025-12-22 21:15:37 +00:00
be647b36a4 reusable header 2025-12-22 21:15:19 +00:00
8f25837bf4 added dashboard toggle 2025-12-15 13:20:53 +00:00
7ab88db5e2 added title 2025-12-15 13:17:37 +00:00
suricatingss
68aed538c6 removed duplicate parts 2025-12-09 02:13:03 +00:00
suricatingss
f6ab446a91 settings folder 2025-12-09 02:12:39 +00:00
suricatingss
2063878411 main function given 2025-12-08 22:22:15 +00:00
suricatingss
b857c81624 login complete (its still dummy) 2025-12-08 22:22:07 +00:00
suricatingss
eea47b714a version fix 2025-12-08 22:21:14 +00:00
suricatingss
8483926e19 login fully functional 2025-12-08 22:21:05 +00:00
211fa2982a UHeader added 2025-12-08 18:29:23 +00:00
45d18746d0 UHeader added 2025-12-08 18:29:21 +00:00
7e199c1ce2 UContainer added 2025-12-08 18:29:10 +00:00
0d7f09b807 UAuthPage 2025-12-08 18:28:52 +00:00
a4d97c4ef2 added settings 2025-12-08 18:28:15 +00:00
8f8c7d895c login function 2025-12-08 13:43:52 +00:00
773522b725 added nuxt auth utils 2025-12-08 13:28:05 +00:00
53c13156a2 added login backend 2025-12-08 13:27:42 +00:00
3c041b1d83 added "start" 2025-12-08 12:43:45 +00:00
f89c302f84 scope css cleared 2025-12-07 23:02:16 +00:00
526482c8b5 added giest font 2025-12-07 23:01:47 +00:00
eb8b7e8604 added comment 2025-12-06 19:23:59 +00:00
25 changed files with 1687 additions and 475 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
.idea
.git
.nuxt
.output
node_modules

View File

@@ -1,25 +1,29 @@
# 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
# Build Stage 1
# This project by default uses pnpm.
# If you wish to use npm, delete "pnpm-lock.yaml" first.
FROM node:22-alpine AS build
WORKDIR /app
# 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)
RUN corepack enable
# You should have the image now !
# Now you just need to create the container
# Copy package.json and your lockfile, here we add pnpm-lock.yaml for illustration
COPY package.json pnpm-lock.yaml ./
# Here is an example command for creating a container
# sudo docker run -d --name myappcontainer --network host -e PORT=3000 myapp:latest
# Install dependencies
RUN pnpm i
# Copy the entire project
COPY . ./
# Build the project
RUN pnpm run build
# Build Stage 2
FROM node:22-alpine
WORKDIR /app
# Copy the build's result
COPY .output/ .
# Only `.output` folder is needed from the build stage
COPY --from=build /app/.output/ ./
# Change the port and host
ENV PORT=3000

30
Dockerfile.output.bak Normal file
View 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"]

View File

@@ -1,17 +1,32 @@
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap'); /* geist font */
@import "tailwindcss";
@import "@nuxt/ui";
:root {
--font-sans: 'Public Sans', sans-serif;
--ui-bg: rgb(220,220,220);
--font-sans: 'Space Grotesk', sans-serif;
/*--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 {
/*--ui-bg: var(--ui-color-neutral-950);*/
--ui-bg: rgb(17,17,17);
--ui-bg-accented: rgba(189, 23, 255, 0.3);
--ui-bg-elevated: rgba(189, 23, 255, 0.2);
--ui-bg: oklch(16.7% 0.004 49.25);
--ui-primary: var(--color-neutral-900);
--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 {

View 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%);
}

View 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;
}

View 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>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
const items = [
{
}
]
</script>
<template>
<
</template>

View File

@@ -10,7 +10,7 @@ const items_def: NavigationMenuItem[][] = [
active: true
}, {
label: 'Inbox',
icon: 'i-lucide-inbox',
icon: 'solar:chat-round-unread-outline',
badge: '4'
}, {
label: 'Contacts',

View 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-"); }

View 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>>

View File

@@ -5,29 +5,63 @@ const nav : NavigationMenuItem[][] = [[
{
label: 'Home',
icon: 'i-lucide-house',
active: true,
to: "/dashboard"
}, {
label: 'Inbox',
icon: 'i-lucide-inbox',
label: 'Notifications',
icon: 'solar:chat-round-unread-outline',
badge: '4',
to: "/inbox"
to: "/events"
}, {
label: 'Contacts',
icon: 'i-lucide-users',
to: "/contacts"
}]];
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"
},
],
}]
];
/*[
{
label: "Feedback"
}
]];*/
// set color
/*useHead({
bodyAttrs: {
class: "ui-blue"
}
})*/
onMounted(() => {
ui_color_cookie();
})
</script>
<template>
<UDashboardGroup>
<DashboardSidebar collapsible resizable :items="nav"/>
<DashboardSidebar class="bg-(--sidebar-bg)" collapsible :resizable="undefined" :items="nav"/>
<UDashboardSidebarCollapse />
<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>
</template>

15
app/layouts/default.vue Normal file
View 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>

View File

@@ -122,14 +122,12 @@ import { h } from 'vue'
</script>
<template>
<UContainer class="overflow-auto">
<UDashboardSidebarToggle variant="subtle" class="absolute z-20 top-3 left-2.5"/>
<div class="mt-1 mb-3 flex flex-col w-full relative z-0">
<DashboardHeader title="Dashboard"/>
<UContainer class="max-w-full">
<div class="mt-3 mb-3 flex flex-col w-full relative z-0">
<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>
<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 class="mx-auto max-w-220">-->
@@ -144,21 +142,14 @@ import { h } from 'vue'
/>
</UPageGrid>
<div class="max-w-full flex-shrink-0 overflow-y-visible">
<UTable :data="table_demo" :columns="table_headers" class="flex-1 "/>
</div>
</UContainer>
<UFooter>
<p>footer</p>
</UFooter>
</UContainer>
</template>
<style scoped>
body {
overflow: hidden;
height: 100%;
}
</style>

18
app/pages/events.vue Normal file
View 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
View 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>

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
definePageMeta({
layout: "dash-settings",
title: "Settings"
})
</script>
<template>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
definePageMeta({
layout: "dash-settings"
})
</script>
<template>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
definePageMeta({
layout: "dash-settings"
})
</script>
<template>
</template>
<style scoped>
</style>

View 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>

View File

@@ -1,14 +1,21 @@
import tailwindcss from "@tailwindcss/vite";
// https://nuxt.com/docs/api/configuration/nuxt-config
// @ts-ignore
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: {enabled: true},
css: ['./app/assets/css/global.css' ],
modules: ['@nuxt/image', '@nuxt/ui', '@nuxt/test-utils', '@nuxtjs/mdc'],
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',
'nuxt-auth-utils',
],
vite: {
plugins: [
tailwindcss()
tailwindcss(),
],
},
})

View File

@@ -7,7 +7,8 @@
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
"postinstall": "nuxt prepare",
"start": "node .output/server/index.mjs"
},
"dependencies": {
"@nuxt/image": "^2.0.0",
@@ -15,17 +16,19 @@
"@nuxt/ui": "^4.2.0",
"@nuxtjs/mdc": "0.19.1",
"@tailwindcss/vite": "^4.1.17",
"@vueuse/core": "^14.0.0",
"@vueuse/core": "^10.0.0",
"@vueuse/head": "github:vueuse/head",
"nuxt": "^4.2.1",
"nuxt-auth-utils": "0.5.25",
"nuxt-storm": "^1.1.3",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3",
"vue": "^3.5.24",
"vue-router": "^4.6.3"
"vue-router": "^4.6.3",
"zod": "^4.1.13"
},
"devDependencies": {
"@iconify-json/lucide": "^1.2.77",
"@nuxtjs/tailwindcss": "^6.14.0"
"@nuxtjs/tailwindcss": "^6.14.0",
"nuxt": "^4.2.2"
}
}

1506
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

38
server/api/login.post.ts Normal file
View 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";
}
})

View 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) => {
})