im done for rn gn
This commit is contained in:
commit
79922f09a1
16 changed files with 2283 additions and 0 deletions
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Nuxt dev/build outputs
|
||||||
|
.output
|
||||||
|
.data
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Node dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.fleet
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
75
README.md
Normal file
75
README.md
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Nuxt Minimal Starter
|
||||||
|
|
||||||
|
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Make sure to install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
Start the development server on `http://localhost:3000`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn dev
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Build the application for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Locally preview production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm preview
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn preview
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||||
12
app/app.vue
Normal file
12
app/app.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup>
|
||||||
|
useHead({
|
||||||
|
link: [
|
||||||
|
{ rel: 'stylesheet', href: '/css/main.css' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="app-viewport">
|
||||||
|
<NuxtPage />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
37
app/components/header.vue
Normal file
37
app/components/header.vue
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
userProfile: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
activeTab: {
|
||||||
|
type: String,
|
||||||
|
default: 'welcome'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['logout', 'navigate'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<aside class="settings-sidebar">
|
||||||
|
<div class="sidebar-header archivo-black">
|
||||||
|
MOD PORTAL
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="userProfile" class="user-badge">
|
||||||
|
<span class="user-name">{{ userProfile.username || userProfile.firstName }}</span>
|
||||||
|
<span class="user-email">{{ userProfile.email }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="sidebar-menu">
|
||||||
|
<button type="button" class="nav-item" :class="{ 'active-nav': activeTab === 'welcome' }"
|
||||||
|
@click="emit('navigate', '/')">
|
||||||
|
<span>Welcome</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="nav-item logout-btn" @click="emit('logout')">
|
||||||
|
<span>Sign Out</span>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
</template>
|
||||||
85
app/composables/useKeycloak.js
Normal file
85
app/composables/useKeycloak.js
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const keycloakInstance = ref(null)
|
||||||
|
const isAuthenticated = ref(false)
|
||||||
|
const userProfile = ref(null)
|
||||||
|
const isInitialized = ref(false)
|
||||||
|
|
||||||
|
export const useKeycloak = () => {
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
|
const initKeycloak = async () => {
|
||||||
|
if (isInitialized.value && keycloakInstance.value?.authenticated) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!import.meta.client) return false
|
||||||
|
|
||||||
|
try {
|
||||||
|
const Keycloak = (await import('keycloak-js')).default
|
||||||
|
const keycloak = new Keycloak({
|
||||||
|
url: config.public.keycloakUrl,
|
||||||
|
realm: config.public.keycloakRealm,
|
||||||
|
clientId: config.public.keycloakClientId
|
||||||
|
})
|
||||||
|
|
||||||
|
keycloakInstance.value = keycloak
|
||||||
|
|
||||||
|
const auth = await keycloak.init({
|
||||||
|
onLoad: 'check-sso',
|
||||||
|
pkceMethod: 'S256',
|
||||||
|
responseMode: 'query',
|
||||||
|
checkLoginIframe: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const loggedIn = Boolean(auth || keycloak.authenticated)
|
||||||
|
isAuthenticated.value = loggedIn
|
||||||
|
isInitialized.value = true
|
||||||
|
|
||||||
|
if (loggedIn) {
|
||||||
|
try {
|
||||||
|
userProfile.value = await keycloak.loadUserProfile()
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not load user profile:', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.location.search.includes('code=')) {
|
||||||
|
window.history.replaceState({}, document.title, window.location.pathname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loggedIn
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Keycloak init failed:', error)
|
||||||
|
isInitialized.value = true
|
||||||
|
isAuthenticated.value = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const login = () => {
|
||||||
|
if (keycloakInstance.value) {
|
||||||
|
keycloakInstance.value.login({
|
||||||
|
redirectUri: `${window.location.origin}/`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
if (keycloakInstance.value) {
|
||||||
|
keycloakInstance.value.logout({
|
||||||
|
redirectUri: `${window.location.origin}/oauth`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
keycloakInstance,
|
||||||
|
isAuthenticated,
|
||||||
|
userProfile,
|
||||||
|
isInitialized,
|
||||||
|
initKeycloak,
|
||||||
|
login,
|
||||||
|
logout
|
||||||
|
}
|
||||||
|
}
|
||||||
85
app/pages/index.vue
Normal file
85
app/pages/index.vue
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
<script setup>
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
|
const { isAuthenticated, userProfile, isInitialized, initKeycloak, logout } = useKeycloak()
|
||||||
|
|
||||||
|
//stub
|
||||||
|
const recentLogins = ref([
|
||||||
|
{ user: 'admin_user', time: '2 mins ago', ip: '192.168.1.10' }
|
||||||
|
])
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const auth = await initKeycloak()
|
||||||
|
if (!auth) {
|
||||||
|
navigateTo('/oauth')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
logout()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNavigate = (path) => {
|
||||||
|
navigateTo(path)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="!isInitialized" class="global-loader-wrapper">
|
||||||
|
<div class="spinner lg"></div>
|
||||||
|
<p style="color: #8b949e; margin-top: 1rem; font-size: 0.95rem;">
|
||||||
|
Connecting to Keycloak...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="isAuthenticated" class="settings-container">
|
||||||
|
<Header
|
||||||
|
:user-profile="userProfile"
|
||||||
|
active-tab="welcome"
|
||||||
|
@logout="handleLogout"
|
||||||
|
@navigate="handleNavigate"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<main class="settings-main-content">
|
||||||
|
<section class="settings-panel">
|
||||||
|
<div class="panel-header archivo-black">
|
||||||
|
<span>PORTAL OVERVIEW</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-body grid-layout">
|
||||||
|
<div class="welcome-left-col">
|
||||||
|
<div class="image-wrapper">
|
||||||
|
<img
|
||||||
|
src="/welcome7.png"
|
||||||
|
alt="Welcome to Mod Portal"
|
||||||
|
class="welcome-image"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h2 class="welcome-text">
|
||||||
|
Welcome to the <em>new</em> mod portal!
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="logins-right-col">
|
||||||
|
<div class="logins-card">
|
||||||
|
<div class="logins-header archivo-black">
|
||||||
|
RECENT LOGINS
|
||||||
|
</div>
|
||||||
|
<ul class="logins-list">
|
||||||
|
<li v-for="(log, idx) in recentLogins" :key="idx" class="login-item">
|
||||||
|
<div class="login-user">{{ log.user }}</div>
|
||||||
|
<div class="login-details">
|
||||||
|
<span>{{ log.ip }}</span> •
|
||||||
|
<span class="login-time">{{ log.time }}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
42
app/pages/oauth/index.vue
Normal file
42
app/pages/oauth/index.vue
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const { isAuthenticated, isInitialized, initKeycloak, login } = useKeycloak()
|
||||||
|
const isLoggingIn = ref(false)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const auth = await initKeycloak()
|
||||||
|
if (auth) {
|
||||||
|
navigateTo('/')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleLogin = () => {
|
||||||
|
isLoggingIn.value = true
|
||||||
|
login()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="login-wrapper">
|
||||||
|
<section class="settings-panel">
|
||||||
|
<div class="panel-header archivo-black">
|
||||||
|
MOD PORTAL
|
||||||
|
</div>
|
||||||
|
<div class="panel-body" style="text-align: center;">
|
||||||
|
<p style="color: #8b949e; font-size: 0.95rem; margin-bottom: 1.5rem;">
|
||||||
|
Hai there :3
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn-login-action"
|
||||||
|
:disabled="!isInitialized || isLoggingIn"
|
||||||
|
@click="handleLogin"
|
||||||
|
>
|
||||||
|
<span v-if="isLoggingIn" class="spinner sm btn-spinner"></span>
|
||||||
|
<span>{{ isLoggingIn ? 'Redirecting...' : 'Login with Keycloak' }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
13
nuxt.config.ts
Normal file
13
nuxt.config.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
compatibilityDate: '2025-07-15',
|
||||||
|
devtools: { enabled: true },
|
||||||
|
ssr: true,
|
||||||
|
runtimeConfig: {
|
||||||
|
public: {
|
||||||
|
keycloakUrl: process.env.NUXT_PUBLIC_KEYCLOAK_URL,
|
||||||
|
keycloakRealm: process.env.NUXT_PUBLIC_KEYCLOAK_REALM,
|
||||||
|
keycloakClientId: process.env.NUXT_PUBLIC_KEYCLOAK_CLIENT_ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
19
package.json
Normal file
19
package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "mod-portal-v2",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"keycloak-js": "^26.2.4",
|
||||||
|
"nuxt": "^4.5.0",
|
||||||
|
"vite-tsconfig-paths": "^6.1.1",
|
||||||
|
"vue": "^3.5.40",
|
||||||
|
"vue-router": "^5.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
320
public/css/main.css
Normal file
320
public/css/main.css
Normal file
|
|
@ -0,0 +1,320 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||||
|
|
||||||
|
.archivo-black {
|
||||||
|
font-family: "Archivo Black", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-color: #1d1e1f;
|
||||||
|
color: #c9d1d9;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-viewport {
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-top-color: #00aeef;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner.lg {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
border-width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner.sm {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-spinner {
|
||||||
|
margin-right: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-top-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-loader-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, .btn-login-action {
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
background: #00aeef;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover, .btn-login-action:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled, .btn-login-action:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login-action {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-wrapper {
|
||||||
|
max-width: 420px;
|
||||||
|
margin: 4rem auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-sidebar {
|
||||||
|
background-color: #1a1a1f;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-badge {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-email {
|
||||||
|
color: #8b949e;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||||||
|
color: #8b949e;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
color: #f85149;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn:hover {
|
||||||
|
background: rgba(248, 81, 73, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-nav {
|
||||||
|
background: rgba(0, 174, 239, 0.1);
|
||||||
|
border-left: 3px solid #00aeef;
|
||||||
|
color: #00aeef;
|
||||||
|
font-weight: 600;
|
||||||
|
padding-left: calc(1rem - 3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-main-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-panel {
|
||||||
|
background-color: #1a1a1f;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
padding: 0.85rem 1.25rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-body {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-left-col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-image {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 680px;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text em {
|
||||||
|
color: #00aeef;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logins-right-col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logins-card {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logins-header {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
color: #8b949e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logins-list {
|
||||||
|
list-style: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-item {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-user {
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-details {
|
||||||
|
color: #8b949e;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-time {
|
||||||
|
color: #00aeef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.settings-container {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.settings-sidebar {
|
||||||
|
width: 280px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 900px) {
|
||||||
|
.grid-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 300px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-Agent: *
|
||||||
|
Disallow:
|
||||||
BIN
public/welcome5-1.png
Normal file
BIN
public/welcome5-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
BIN
public/welcome7.png
Normal file
BIN
public/welcome7.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./.nuxt/tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./.nuxt/tsconfig.server.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./.nuxt/tsconfig.shared.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./.nuxt/tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue