feat: add files
This commit is contained in:
commit
fbba5a6814
|
|
@ -0,0 +1,6 @@
|
|||
/.nuxt
|
||||
/.output
|
||||
/.ignore
|
||||
/node_modules
|
||||
.gitignore
|
||||
README.md
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
node_modules
|
||||
logs
|
||||
*.log
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
ARG NODE_VERSION=20.10.0
|
||||
ARG PORT=3000
|
||||
|
||||
FROM node:${NODE_VERSION}-slim as base
|
||||
ENV NODE_ENV=production
|
||||
ENV NODE_OPTIONS=--max-old-space-size=8192
|
||||
WORKDIR /app
|
||||
|
||||
FROM base as build
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
RUN npm install --production=false
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
RUN npm prune
|
||||
|
||||
FROM base as production
|
||||
ENV PORT=$PORT
|
||||
COPY --from=build /app/.output /app/.output
|
||||
CMD ["node", ".output/server/index.mjs"]
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# Client Portal
|
||||
|
||||
## Commands
|
||||
|
||||
- `npm run dev` to start development
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Node.js
|
||||
- Nuxt
|
||||
- Directus
|
||||
- Vuetify
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<template>
|
||||
<NuxtPwaManifest />
|
||||
<NuxtPage />
|
||||
</template>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
export default defineNuxtRouteMiddleware(async () => {
|
||||
const { fetchUser, setUser } = useDirectusAuth();
|
||||
|
||||
const user = useDirectusUser();
|
||||
|
||||
if (!user.value) {
|
||||
const user = await fetchUser();
|
||||
setUser(user.value);
|
||||
}
|
||||
|
||||
if (!user.value) {
|
||||
return navigateTo("/login");
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
export default defineNuxtConfig({
|
||||
compatibilityDate: "2024-11-01",
|
||||
devtools: { enabled: true },
|
||||
modules: ["nuxt-directus", "vuetify-nuxt-module", "@vite-pwa/nuxt"],
|
||||
app: {
|
||||
head: {
|
||||
titleTemplate: "%s • Port Nimara Client Portal",
|
||||
title: "Port Nimara Client Portal",
|
||||
meta: [
|
||||
{ property: "og:title", content: "Port Nimara Client Portal" },
|
||||
{ property: "og:image", content: "/og-image.png" },
|
||||
{ name: "twitter:card", content: "summary_large_image" },
|
||||
],
|
||||
htmlAttrs: {
|
||||
lang: "en",
|
||||
},
|
||||
},
|
||||
},
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
directus: {
|
||||
url: "https://contenthub.portnimara.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
vuetify: {
|
||||
vuetifyOptions: {
|
||||
theme: {
|
||||
defaultTheme: "portnimara",
|
||||
themes: {
|
||||
portnimara: {
|
||||
colors: {
|
||||
primary: "#387bca",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vite-pwa/nuxt": "^0.10.6",
|
||||
"nuxt": "^3.15.4",
|
||||
"nuxt-directus": "^5.7.0",
|
||||
"vue": "latest",
|
||||
"vue-router": "latest",
|
||||
"vuetify-nuxt-module": "^0.18.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<v-app full-height>
|
||||
<v-navigation-drawer>
|
||||
<v-img src="/logo.jpg" height="75" class="my-6" />
|
||||
|
||||
<v-list color="primary" lines="two">
|
||||
<v-list-item
|
||||
to="/dashboard/site"
|
||||
title="Site Analytics"
|
||||
prepend-icon="mdi-view-dashboard"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/dashboard/data"
|
||||
title="Data Analytics"
|
||||
prepend-icon="mdi-finance"
|
||||
/>
|
||||
</v-list>
|
||||
|
||||
<template #append>
|
||||
<v-list lines="two">
|
||||
<v-list-item
|
||||
@click="logOut"
|
||||
title="Log out"
|
||||
prepend-icon="mdi-logout"
|
||||
base-color="error"
|
||||
/>
|
||||
</v-list>
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-app-bar v-if="mdAndDown" elevation="2">
|
||||
<template #prepend>
|
||||
<v-img src="/logo.jpg" width="75" class="ml-3" />
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<v-btn
|
||||
@click="logOut"
|
||||
class="mr-3"
|
||||
variant="tonal"
|
||||
color="error"
|
||||
icon="mdi-logout"
|
||||
/>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
|
||||
<v-main>
|
||||
<router-view />
|
||||
|
||||
<v-bottom-navigation :active="mdAndDown" color="primary" elevation="2">
|
||||
<v-btn to="/dashboard/site">
|
||||
<v-icon icon="mdi-view-dashboard" />
|
||||
<span>Site Analytics</span>
|
||||
</v-btn>
|
||||
<v-btn to="/dashboard/data">
|
||||
<v-icon icon="mdi-finance" />
|
||||
<span>Data Analytics</span>
|
||||
</v-btn>
|
||||
</v-bottom-navigation>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
middleware: ["authentication"],
|
||||
layout: false,
|
||||
});
|
||||
|
||||
const { mdAndDown } = useDisplay();
|
||||
const { logout } = useDirectusAuth();
|
||||
|
||||
const logOut = async () => {
|
||||
await logout();
|
||||
return navigateTo("/login");
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<div class="embed">
|
||||
<iframe
|
||||
src="https://flows.portnimara.com/public/dashboards/ugQabIMShSld7pXWYLwpsV1kpbSILPiui5eIOfhd?org_slug=default"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
useHead({
|
||||
title: "Data Analytics",
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.embed {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.embed iframe {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: calc(100% + 180px);
|
||||
border: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
redirect: "/dashboard/site",
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<div class="embed">
|
||||
<iframe
|
||||
src="https://analytics.portnimara.com/share/56Dc1w6yYGAOjyoj/portnimara.com"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
useHead({
|
||||
title: "Site Analytics",
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.embed {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.embed iframe {
|
||||
width: 100%;
|
||||
height: calc(100% + 120px);
|
||||
border: 0;
|
||||
position: absolute;
|
||||
top: -120px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<script setup>
|
||||
definePageMeta({
|
||||
redirect: "/dashboard",
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<v-app full-height>
|
||||
<v-main class="container">
|
||||
<v-container class="fill-height" fluid>
|
||||
<v-row align="center" justify="center" class="fill-height">
|
||||
<v-card class="pa-6" rounded="xl" max-width="350" elevation="2">
|
||||
<v-form @submit.prevent="submit" v-model="valid">
|
||||
<v-row no-gutters>
|
||||
<v-col cols="12">
|
||||
<v-img src="/logo.jpg" width="200" class="mb-3 mx-auto" />
|
||||
</v-col>
|
||||
<v-scroll-y-transition>
|
||||
<v-col v-if="errorThrown" cols="12" class="my-3">
|
||||
<v-alert
|
||||
text="Invalid email address or password"
|
||||
color="error"
|
||||
variant="tonal"
|
||||
/>
|
||||
</v-col>
|
||||
</v-scroll-y-transition>
|
||||
<v-col cols="12">
|
||||
<v-row dense>
|
||||
<v-col cols="12" class="mt-4">
|
||||
<v-text-field
|
||||
v-model="emailAddress"
|
||||
placeholder="Email address"
|
||||
:disabled="loading"
|
||||
:rules="[
|
||||
(value) => !!value || 'Must not be empty',
|
||||
(value) =>
|
||||
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ||
|
||||
'Invalid email address',
|
||||
]"
|
||||
variant="outlined"
|
||||
type="email"
|
||||
autofocus
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
@click:append-inner="passwordVisible = !passwordVisible"
|
||||
v-model="password"
|
||||
placeholder="Password"
|
||||
:disabled="loading"
|
||||
:type="passwordVisible ? 'text' : 'password'"
|
||||
:append-inner-icon="
|
||||
passwordVisible ? 'mdi-eye' : 'mdi-eye-off'
|
||||
"
|
||||
:rules="[(value) => !!value || 'Must not be empty']"
|
||||
autocomplete="current-password"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-btn
|
||||
text="Log in"
|
||||
:disabled="!valid"
|
||||
:loading="loading"
|
||||
type="submit"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
size="large"
|
||||
block
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { login } = useDirectusAuth();
|
||||
|
||||
const loading = ref(false);
|
||||
const errorThrown = ref(false);
|
||||
|
||||
const emailAddress = ref();
|
||||
|
||||
const password = ref();
|
||||
const passwordVisible = ref(false);
|
||||
|
||||
const valid = ref(false);
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
await login({ email: emailAddress.value, password: password.value });
|
||||
return navigateTo("/dashboard");
|
||||
} catch (error) {
|
||||
errorThrown.value = true;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
useHead({
|
||||
title: "Login",
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
background: url(/background.jpg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 204 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
||||
Loading…
Reference in New Issue