Compare commits
10 commits
866155e25e
...
b5b082bc5d
| Author | SHA1 | Date | |
|---|---|---|---|
| b5b082bc5d | |||
| defa605e45 | |||
|
|
d1967f718e | ||
|
|
9a4dd4b721 | ||
|
|
82acb354ca | ||
| b27e3655d1 | |||
|
|
3b58a25ccf | ||
| c98879430b | |||
| 7df500cb99 | |||
| 61184f57a5 |
46 changed files with 5518 additions and 1447 deletions
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
## Description
|
||||||
|
|
||||||
|
### Proposed Changes
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
### Checklist before submitting
|
||||||
|
|
||||||
|
- [ ] I followed the guidelines in our [Contributing document](https://github.com/lburcusel/glowing-fiesta/blob/production/CONTRIBUTING.md)
|
||||||
|
- [ ] My submission pass all tests
|
||||||
36
.github/workflows/sonar.yml
vendored
Normal file
36
.github/workflows/sonar.yml
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
name: Sonar
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- production
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- production
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
jobs:
|
||||||
|
sonarqube:
|
||||||
|
name: SonarQube
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup node environment
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "22"
|
||||||
|
cache: "npm"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Run tests and generate coverage
|
||||||
|
run: npm run coverage
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
- name: SonarQube Scan
|
||||||
|
uses: SonarSource/sonarqube-scan-action@v6
|
||||||
|
env:
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
8
.prettierrc.json
Normal file
8
.prettierrc.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"printWidth": 128,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
22
.vscode/settings.json
vendored
Normal file
22
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit"
|
||||||
|
},
|
||||||
|
"[vue]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"eslint.validate": [
|
||||||
|
"javascript",
|
||||||
|
"javascriptreact",
|
||||||
|
"typescript",
|
||||||
|
"typescriptreact",
|
||||||
|
"vue"
|
||||||
|
]
|
||||||
|
}
|
||||||
49
SECURITY.md
Normal file
49
SECURITY.md
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
Glowing Fiesta is unreleased software still in early development, and so bugs and vulnerabilities in its code can be safely
|
||||||
|
disclosed publicly. The preference is to report security issues as
|
||||||
|
[GitHub issues](https://github.com/lburcusel/glowing-fiesta/issues/new?template=bug_report.md).
|
||||||
|
|
||||||
|
However, private vulnerability reporting is also enabled on the repository. If you find a security issue in Glowing Fiesta,
|
||||||
|
or in another package that you believe affects Glowing Fiesta, you may report it privately to the maintainers
|
||||||
|
using the [process outlined in GitHub documentation](https://docs.github.com/en/code-security/security-advisories/working-with-repository-security-advisories/creating-a-repository-security-advisory).
|
||||||
|
|
||||||
|
Issues reported and accepted through the private reporting process will be disclosed publicly once they are resolved,
|
||||||
|
and given a security advisory identifier. The maintainers may include regular contributors in the disposition and resolution
|
||||||
|
process as their expertise requires. Researchers who report security issues privately will be credited in the advisory.
|
||||||
|
|
||||||
|
The maintainers reserve the right to reject reports that are not security issues, or that are not in the scope of Glowing Fiesta.
|
||||||
|
For issues that are determined to not be security issues, please report them as a
|
||||||
|
[GitHub issue](https://github.com/lburcusel/glowing-fiesta/issues/new?template=bug_report.md) instead. If you choose not to
|
||||||
|
re-report the issue as a generic issue, the maintainers may do so themselves.
|
||||||
|
|
||||||
|
Glowing Fiesta does not offer bug bounties for security issues at this time.
|
||||||
|
|
||||||
|
## Scope of Security Issues
|
||||||
|
|
||||||
|
Many security features of the web platform are not yet implemented in Glowing Fiesta. Security reports regarding
|
||||||
|
incomplete features may be redirected to regular issues. The following are examples of issues that are not in scope
|
||||||
|
at this time:
|
||||||
|
|
||||||
|
- Cross-site request forgery
|
||||||
|
- Cross-site scripting
|
||||||
|
- Content Security Policy violations
|
||||||
|
- Cross-origin iframe sandboxing
|
||||||
|
|
||||||
|
The maintainers reserve the right to modify this list as the project matures and as security issues are reported.
|
||||||
|
|
||||||
|
Significant portions of the browser depend on third party libraries. Examples include image decoding, video decoding,
|
||||||
|
internationalization, and 2D graphics. Security issues in these libraries should be reported to the maintainers of the
|
||||||
|
respective libraries. The maintainers of Glowing Fiesta will work with the maintainers of these libraries to resolve the issue.
|
||||||
|
If a security issue relates more to the integration of the library into Glowing Fiesta, it should be reported via the same
|
||||||
|
methods as other security issues.
|
||||||
|
|
||||||
|
## Responsible Disclosure
|
||||||
|
|
||||||
|
The maintainers of Glowing Fiesta will work with security researchers to resolve security issues in a timely manner. A default
|
||||||
|
120-day disclosure timeline is in place for all security issues, but this may be extended if the maintainers and the reporter
|
||||||
|
agree that more time is needed to resolve the issue. The maintainers will keep the reporter informed of progress and
|
||||||
|
resolution steps throughout the process.
|
||||||
|
|
||||||
|
In the case that a security issue is also reported to other package vendors or OSS projects, the maintainers will work
|
||||||
|
with the longest disclosure timeline to ensure that all parties have sufficient time to resolve the issue.
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<NuxtRouteAnnouncer />
|
<NuxtRouteAnnouncer />
|
||||||
<NuxtWelcome />
|
<NuxtLayout>
|
||||||
|
<NuxtPage />
|
||||||
|
</NuxtLayout>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
1
app/assets/css/tailwind.css
Normal file
1
app/assets/css/tailwind.css
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
@import "tailwindcss";
|
||||||
BIN
app/assets/images/logo.png
Normal file
BIN
app/assets/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
24
app/assets/scss/layout/_core.scss
Normal file
24
app/assets/scss/layout/_core.scss
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Ubuntu, Arial, Helvetica, sans-serif;
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--surface-ground);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-wrapper {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
8
app/assets/scss/layout/_footer.scss
Normal file
8
app/assets/scss/layout/_footer.scss
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
.layout-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1rem 0 1rem 0;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border-top: 1px solid var(--surface-border);
|
||||||
|
}
|
||||||
14
app/assets/scss/layout/_main.scss
Normal file
14
app/assets/scss/layout/_main.scss
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
.layout-main-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6rem 2rem 0 2rem;
|
||||||
|
background-color: var(--surface-ground);
|
||||||
|
transition: margin-left var(--layout-section-transition-duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-main {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
}
|
||||||
160
app/assets/scss/layout/_menu.scss
Normal file
160
app/assets/scss/layout/_menu.scss
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
@use "mixins" as *;
|
||||||
|
|
||||||
|
.layout-sidebar {
|
||||||
|
position: fixed;
|
||||||
|
width: 20rem;
|
||||||
|
height: calc(100vh - 8rem);
|
||||||
|
z-index: 999;
|
||||||
|
overflow-y: auto;
|
||||||
|
user-select: none;
|
||||||
|
top: 6rem;
|
||||||
|
left: 2rem;
|
||||||
|
transition:
|
||||||
|
transform var(--layout-section-transition-duration),
|
||||||
|
left var(--layout-section-transition-duration);
|
||||||
|
background-color: var(--surface-overlay);
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-menu {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
.layout-root-menuitem {
|
||||||
|
> .layout-menuitem-root-text {
|
||||||
|
font-size: 0.857rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-color);
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> a {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&.active-menuitem {
|
||||||
|
> .layout-submenu-toggler {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.active-menuitem {
|
||||||
|
> a {
|
||||||
|
.layout-submenu-toggler {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
outline: 0 none;
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
transition:
|
||||||
|
background-color var(--element-transition-duration),
|
||||||
|
box-shadow var(--element-transition-duration);
|
||||||
|
|
||||||
|
.layout-menuitem-icon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-submenu-toggler {
|
||||||
|
font-size: 75%;
|
||||||
|
margin-left: auto;
|
||||||
|
transition: transform var(--element-transition-duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active-route {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
@include focused-inset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 3.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-submenu-enter-from,
|
||||||
|
.layout-submenu-leave-to {
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-submenu-enter-to,
|
||||||
|
.layout-submenu-leave-from {
|
||||||
|
max-height: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-submenu-leave-active {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.45s cubic-bezier(0, 1, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-submenu-enter-active {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 1s ease-in-out;
|
||||||
|
}
|
||||||
15
app/assets/scss/layout/_mixins.scss
Normal file
15
app/assets/scss/layout/_mixins.scss
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
@mixin focused() {
|
||||||
|
outline-width: var(--focus-ring-width);
|
||||||
|
outline-style: var(--focus-ring-style);
|
||||||
|
outline-color: var(--focus-ring-color);
|
||||||
|
outline-offset: var(--focus-ring-offset);
|
||||||
|
box-shadow: var(--focus-ring-shadow);
|
||||||
|
transition:
|
||||||
|
box-shadow var(--transition-duration),
|
||||||
|
outline-color var(--transition-duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin focused-inset() {
|
||||||
|
outline-offset: -1px;
|
||||||
|
box-shadow: inset var(--focus-ring-shadow);
|
||||||
|
}
|
||||||
48
app/assets/scss/layout/_preloading.scss
Normal file
48
app/assets/scss/layout/_preloading.scss
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
.preloader {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 999999;
|
||||||
|
background: #edf1f5;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.preloader-content {
|
||||||
|
border: 0 solid transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50vh - 75px);
|
||||||
|
left: calc(50vw - 75px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preloader-content:before,
|
||||||
|
.preloader-content:after {
|
||||||
|
content: "";
|
||||||
|
border: 1em solid var(--primary-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
animation: loader 2s linear infinite;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preloader-content:before {
|
||||||
|
animation-delay: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loader {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
app/assets/scss/layout/_responsive.scss
Normal file
110
app/assets/scss/layout/_responsive.scss
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
@media screen and (min-width: 1960px) {
|
||||||
|
.layout-main,
|
||||||
|
.landing-wrapper {
|
||||||
|
width: 1504px;
|
||||||
|
margin-left: auto !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.layout-wrapper {
|
||||||
|
&.layout-overlay {
|
||||||
|
.layout-main-container {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-sidebar {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-right: 1px solid var(--surface-border);
|
||||||
|
transition:
|
||||||
|
transform 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99),
|
||||||
|
left 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99);
|
||||||
|
box-shadow:
|
||||||
|
0px 3px 5px rgba(0, 0, 0, 0.02),
|
||||||
|
0px 0px 2px rgba(0, 0, 0, 0.05),
|
||||||
|
0px 1px 4px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-overlay-active {
|
||||||
|
.layout-sidebar {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-static {
|
||||||
|
.layout-main-container {
|
||||||
|
margin-left: 22rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-static-inactive {
|
||||||
|
.layout-sidebar {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-main-container {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mask {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.blocked-scroll {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-wrapper {
|
||||||
|
.layout-main-container {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-sidebar {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
transition:
|
||||||
|
transform 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99),
|
||||||
|
left 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mask {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 998;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--maskbg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-mobile-active {
|
||||||
|
.layout-sidebar {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mask {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
201
app/assets/scss/layout/_topbar.scss
Normal file
201
app/assets/scss/layout/_topbar.scss
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
@use "mixins" as *;
|
||||||
|
|
||||||
|
.layout-topbar {
|
||||||
|
position: fixed;
|
||||||
|
height: 4rem;
|
||||||
|
z-index: 997;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 2rem;
|
||||||
|
background-color: var(--surface-card);
|
||||||
|
transition: left var(--layout-section-transition-duration);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.layout-topbar-logo-container {
|
||||||
|
width: 20rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-logo {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-weight: 500;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
@include focused();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-action {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
transition: background-color var(--element-transition-duration);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
@include focused();
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 1rem;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-topbar-action-highlight {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--primary-contrast-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-menu-button {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-menu-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-actions {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-menu-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-config-menu {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.layout-topbar {
|
||||||
|
padding: 0 2rem;
|
||||||
|
|
||||||
|
.layout-topbar-logo-container {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-menu-button {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-menu-button {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-menu {
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--surface-overlay);
|
||||||
|
transform-origin: top;
|
||||||
|
box-shadow:
|
||||||
|
0px 3px 5px rgba(0, 0, 0, 0.02),
|
||||||
|
0px 0px 2px rgba(0, 0, 0, 0.05),
|
||||||
|
0px 1px 4px rgba(0, 0, 0, 0.08);
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
padding: 1rem;
|
||||||
|
right: 2rem;
|
||||||
|
top: 4rem;
|
||||||
|
min-width: 15rem;
|
||||||
|
border: 1px solid var(--surface-border);
|
||||||
|
|
||||||
|
.layout-topbar-menu-content {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-action {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
justify-content: flex-start;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-weight: medium;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-menu-content {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-panel {
|
||||||
|
.config-panel-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary-color);
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-panel-colors {
|
||||||
|
> div {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
outline-color: transparent;
|
||||||
|
outline-width: 2px;
|
||||||
|
outline-style: solid;
|
||||||
|
outline-offset: 1px;
|
||||||
|
|
||||||
|
&.active-color {
|
||||||
|
outline-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-panel-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
68
app/assets/scss/layout/_typography.scss
Normal file
68
app/assets/scss/layout/_typography.scss
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin: 1.5rem 0 1rem 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
background: #fff8e1;
|
||||||
|
padding: 0.25rem 0.4rem;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 0 2rem;
|
||||||
|
border-left: 4px solid #90a4ae;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border-top: solid var(--surface-border);
|
||||||
|
border-width: 1px 0 0 0;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/assets/scss/layout/_utils.scss
Normal file
25
app/assets/scss/layout/_utils.scss
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
/* Utils */
|
||||||
|
.clearfix:after {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--surface-card);
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-toast {
|
||||||
|
&.p-toast-top-right,
|
||||||
|
&.p-toast-top-left,
|
||||||
|
&.p-toast-top-center {
|
||||||
|
top: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/assets/scss/layout/layout.scss
Normal file
11
app/assets/scss/layout/layout.scss
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
@use "./variables/_common";
|
||||||
|
@use "./_mixins";
|
||||||
|
@use "./_preloading";
|
||||||
|
@use "./_core";
|
||||||
|
@use "./_main";
|
||||||
|
@use "./_topbar";
|
||||||
|
@use "./_menu";
|
||||||
|
@use "./_footer";
|
||||||
|
@use "./_responsive";
|
||||||
|
@use "./_utils";
|
||||||
|
@use "./_typography";
|
||||||
26
app/assets/scss/layout/variables/_common.scss
Normal file
26
app/assets/scss/layout/variables/_common.scss
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
:root {
|
||||||
|
--primary-color: var(--p-primary-color);
|
||||||
|
--primary-contrast-color: var(--p-primary-contrast-color);
|
||||||
|
--text-color: var(--p-text-color);
|
||||||
|
--text-color-secondary: var(--p-text-muted-color);
|
||||||
|
--surface-ground: var(--p-surface-100);
|
||||||
|
--surface-border: var(--p-content-border-color);
|
||||||
|
--surface-card: var(--p-content-background);
|
||||||
|
--surface-hover: var(--p-content-hover-background);
|
||||||
|
--surface-overlay: var(--p-overlay-popover-background);
|
||||||
|
--transition-duration: var(--p-transition-duration);
|
||||||
|
--maskbg: var(--p-mask-background);
|
||||||
|
--content-border-radius: var(--p-content-border-radius);
|
||||||
|
--layout-section-transition-duration: 0.2s;
|
||||||
|
--element-transition-duration: var(--p-transition-duration);
|
||||||
|
--focus-ring-width: var(--p-focus-ring-width);
|
||||||
|
--focus-ring-style: var(--p-focus-ring-style);
|
||||||
|
--focus-ring-color: var(--p-focus-ring-color);
|
||||||
|
--focus-ring-offset: var(--p-focus-ring-offset);
|
||||||
|
--focus-ring-shadow: var(--p-focus-ring-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[class="app-dark"] {
|
||||||
|
--p-text-color: #ffffff;
|
||||||
|
--surface-ground: var(--p-surface-950);
|
||||||
|
}
|
||||||
3
app/assets/scss/styles.scss
Normal file
3
app/assets/scss/styles.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
@use "primeicons/primeicons.css";
|
||||||
|
@use "~/assets/css/tailwind.css";
|
||||||
|
@use "~/assets/scss/layout/layout.scss";
|
||||||
21
app/layouts/Default.vue
Normal file
21
app/layouts/Default.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<div class="layout-wrapper layout-static">
|
||||||
|
<default-topbar />
|
||||||
|
<default-sidebar />
|
||||||
|
<div class="layout-main-container">
|
||||||
|
<div class="layout-main">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<default-footer />
|
||||||
|
</div>
|
||||||
|
<div class="layout-mask animate-fadein"></div>
|
||||||
|
</div>
|
||||||
|
<Toast />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Toast from "primevue/toast";
|
||||||
|
import DefaultTopbar from "~/layouts/default/Topbar.vue";
|
||||||
|
import DefaultSidebar from "~/layouts/default/Sidebar.vue";
|
||||||
|
import DefaultFooter from "~/layouts/default/Footer.vue";
|
||||||
|
</script>
|
||||||
9
app/layouts/default/Footer.vue
Normal file
9
app/layouts/default/Footer.vue
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<footer class="layout-footer font-bold">
|
||||||
|
<div v-if="currentYear === 2025">Glowing Fiesta 2025</div>
|
||||||
|
<div v-else>Glowing Fiesta 2025 - {{ currentYear }}</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
35
app/layouts/default/Sidebar.vue
Normal file
35
app/layouts/default/Sidebar.vue
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<div class="layout-sidebar">
|
||||||
|
<ul class="layout-menu">
|
||||||
|
<li class="layout-root-menuitem">
|
||||||
|
<div class="layout-menuitem-root-text">Home</div>
|
||||||
|
<ul class="layout-submenu">
|
||||||
|
<li>
|
||||||
|
<nuxt-link to="/">
|
||||||
|
<i class="pi pi-home layout-menuitem-icon"></i>
|
||||||
|
<span class="layout-menuitem-text">Dashboard</span>
|
||||||
|
</nuxt-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="layout-root-menuitem">
|
||||||
|
<div class="layout-menuitem-root-text">Configuration</div>
|
||||||
|
<ul class="layout-submenu">
|
||||||
|
<li>
|
||||||
|
<nuxt-link to="/config/account">
|
||||||
|
<i class="pi pi-user layout-menuitem-icon"></i>
|
||||||
|
<span class="layout-menuitem-text">Account</span>
|
||||||
|
</nuxt-link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<nuxt-link to="/config/settings">
|
||||||
|
<i class="pi pi-cog layout-menuitem-icon"></i>
|
||||||
|
<span class="layout-menuitem-text">Settings</span>
|
||||||
|
</nuxt-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
56
app/layouts/default/Topbar.vue
Normal file
56
app/layouts/default/Topbar.vue
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const isDarkTheme = ref(false);
|
||||||
|
|
||||||
|
const toggleDarkMode = () => {
|
||||||
|
isDarkTheme.value = !isDarkTheme.value;
|
||||||
|
document.documentElement.classList.toggle("app-dark");
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleMenu = () => {
|
||||||
|
document.getElementsByClassName("layout-wrapper")[0]?.classList.toggle("layout-static-inactive");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<nav class="layout-topbar">
|
||||||
|
<div class="layout-topbar-logo-container">
|
||||||
|
<button class="layout-menu-button layout-topbar-action" @click="toggleMenu">
|
||||||
|
<i class="pi pi-bars"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<nuxt-link to="/" class="layout-topbar-logo">
|
||||||
|
<img src="@/assets/images/logo.png" alt="Two stick silhouettes admiring fireworks in the sky" />
|
||||||
|
<span>Glowing Fiesta</span>
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-topbar-actions">
|
||||||
|
<div class="layout-config-menu">
|
||||||
|
<button type="button" class="layout-topbar-action layout-topbar-action-highlight" @click="toggleDarkMode">
|
||||||
|
<i :class="['pi', { 'pi-moon': isDarkTheme, 'pi-sun': !isDarkTheme }]"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider layout="vertical" />
|
||||||
|
|
||||||
|
<div class="layout-topbar-menu hidden lg:block">
|
||||||
|
<div class="layout-topbar-menu-content">
|
||||||
|
<button type="button" class="layout-topbar-action">
|
||||||
|
<i class="pi pi-calendar"></i>
|
||||||
|
<span>Calendar</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="layout-topbar-action">
|
||||||
|
<i class="pi pi-inbox"></i>
|
||||||
|
<span>Messages</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="layout-topbar-action">
|
||||||
|
<i class="pi pi-user"></i>
|
||||||
|
<span>Profile</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
5
app/pages/config/Account.vue
Normal file
5
app/pages/config/Account.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<h1>Account</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
5
app/pages/config/Settings.vue
Normal file
5
app/pages/config/Settings.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<h1>Settings</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
5
app/pages/index.vue
Normal file
5
app/pages/index.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<h1>Dashboard</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
import withNuxt from "./.nuxt/eslint.config.mjs";
|
import withNuxt from "./.nuxt/eslint.config.mjs";
|
||||||
|
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
|
||||||
|
|
||||||
export default withNuxt({
|
export default withNuxt(eslintPluginPrettierRecommended, {
|
||||||
files: ["**/*.{ts,js,vue}"],
|
files: ["**/*.{ts,js,vue}"],
|
||||||
rules: {
|
rules: {
|
||||||
|
// Vue rules
|
||||||
|
"vue/no-multiple-template-root": "off",
|
||||||
|
|
||||||
|
// Typescript rules
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/no-require-imports": "off",
|
"@typescript-eslint/no-require-imports": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "warn",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,79 @@
|
||||||
|
import { definePreset } from "@primeuix/themes";
|
||||||
|
import Aura from "@primeuix/themes/aura";
|
||||||
|
|
||||||
|
const defaultPreset = definePreset(Aura, {
|
||||||
|
semantic: {
|
||||||
|
primary: {
|
||||||
|
50: "{teal.50}",
|
||||||
|
100: "{teal.100}",
|
||||||
|
200: "{teal.200}",
|
||||||
|
300: "{teal.300}",
|
||||||
|
400: "{teal.400}",
|
||||||
|
500: "{teal.500}",
|
||||||
|
600: "{teal.600}",
|
||||||
|
700: "{teal.700}",
|
||||||
|
800: "{teal.800}",
|
||||||
|
900: "{teal.900}",
|
||||||
|
950: "{teal.950}",
|
||||||
|
},
|
||||||
|
colorScheme: {
|
||||||
|
light: {
|
||||||
|
surface: {
|
||||||
|
0: "#ffffff",
|
||||||
|
50: "{slate.50}",
|
||||||
|
100: "{slate.100}",
|
||||||
|
200: "{slate.200}",
|
||||||
|
300: "{slate.300}",
|
||||||
|
400: "{slate.400}",
|
||||||
|
500: "{slate.500}",
|
||||||
|
600: "{slate.600}",
|
||||||
|
700: "{slate.700}",
|
||||||
|
800: "{slate.800}",
|
||||||
|
900: "{slate.900}",
|
||||||
|
950: "{slate.950}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
surface: {
|
||||||
|
0: "#000000",
|
||||||
|
50: "{slate.50}",
|
||||||
|
100: "{slate.100}",
|
||||||
|
200: "{slate.200}",
|
||||||
|
300: "{slate.300}",
|
||||||
|
400: "{slate.400}",
|
||||||
|
500: "{slate.500}",
|
||||||
|
600: "{slate.600}",
|
||||||
|
700: "{slate.700}",
|
||||||
|
800: "{slate.800}",
|
||||||
|
900: "{slate.900}",
|
||||||
|
950: "{slate.950}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: '2025-07-15',
|
compatibilityDate: "2025-07-15",
|
||||||
devtools: { enabled: true },
|
devtools: { enabled: true },
|
||||||
modules: ['@nuxt/eslint']
|
router: {
|
||||||
})
|
options: {
|
||||||
|
linkActiveClass: "active-route",
|
||||||
|
linkExactActiveClass: "exact-active-route",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modules: ["@nuxt/eslint", "@nuxtjs/tailwindcss", "@primevue/nuxt-module"],
|
||||||
|
css: ["~/assets/css/tailwind.css", "~/assets/scss/styles.scss"],
|
||||||
|
primevue: {
|
||||||
|
options: {
|
||||||
|
ripple: true,
|
||||||
|
theme: {
|
||||||
|
preset: defaultPreset,
|
||||||
|
options: {
|
||||||
|
darkModeSelector: ".app-dark",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
||||||
5473
package-lock.json
generated
5473
package-lock.json
generated
File diff suppressed because it is too large
Load diff
26
package.json
26
package.json
|
|
@ -9,16 +9,34 @@
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
"postinstall": "nuxt prepare",
|
"postinstall": "nuxt prepare",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint-files": "eslint --ext .js,.ts,.vue"
|
"lint-files": "eslint --ext .js,.ts,.vue",
|
||||||
|
"vitest": "vitest",
|
||||||
|
"test": "vitest run",
|
||||||
|
"coverage": "vitest run --coverage"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/eslint": "^1.9.0",
|
"@nuxtjs/tailwindcss": "^7.0.0-beta.1",
|
||||||
"eslint": "^9.38.0",
|
"@primeuix/themes": "^2.0.1",
|
||||||
"nuxt": "^4.1.3",
|
"nuxt": "^4.1.3",
|
||||||
|
"primeicons": "^7.0.0",
|
||||||
|
"primevue": "^4.4.1",
|
||||||
|
"tailwindcss": "^4.1.17",
|
||||||
"vue": "^3.5.22",
|
"vue": "^3.5.22",
|
||||||
"vue-router": "^4.6.3"
|
"vue-router": "^4.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.9.3"
|
"@nuxt/eslint": "^1.9.0",
|
||||||
|
"@primevue/nuxt-module": "^4.4.1",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"@vitest/coverage-v8": "^4.0.1",
|
||||||
|
"@vue/test-utils": "^2.4.6",
|
||||||
|
"eslint": "^9.38.0",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-prettier": "^5.5.4",
|
||||||
|
"happy-dom": "^20.0.8",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"sass-embedded": "^1.93.3",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vitest": "^4.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
sonar-project.properties
Normal file
12
sonar-project.properties
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
sonar.projectKey=lburcusel_glowing-fiesta
|
||||||
|
sonar.organization=lburcusel
|
||||||
|
|
||||||
|
sonar.projectName=Glowing Fiesta
|
||||||
|
sonar.projectVersion=1.0.0
|
||||||
|
sonar.sourceEncoding=UTF-8
|
||||||
|
sonar.sources=app, tests
|
||||||
|
sonar.inclusions=app/**/*.ts, app/**/*.js, app/**/*.vue, app/**/*.css, app/**/*.scss, tests/**/*.test.ts
|
||||||
|
sonar.exclusions=**/node_modules/**, **/coverage/**, *.config.ts
|
||||||
|
sonar.coverage.exclusions=tests/**, *.config.ts
|
||||||
|
sonar.javascript.lcov.reportPaths=coverage/lcov.info
|
||||||
|
# sonar.testExecutionReportPaths=coverage/sonar-report.xml
|
||||||
0
tests/.keep
Normal file
0
tests/.keep
Normal file
18
tests/app.test.ts
Normal file
18
tests/app.test.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { VueWrapper } from "@vue/test-utils";
|
||||||
|
import { mount } from "@vue/test-utils";
|
||||||
|
import App from "../app/app.vue";
|
||||||
|
|
||||||
|
describe("app.vue", () => {
|
||||||
|
const wrapper: VueWrapper = mount(App, {
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
NuxtRouteAnnouncer: true,
|
||||||
|
NuxtWelcome: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
it("renders without crashing", () => {
|
||||||
|
expect(wrapper.exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
11
tests/layouts/Default.test.ts
Normal file
11
tests/layouts/Default.test.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||||
|
import DefaultLayout from "~/layouts/Default.vue";
|
||||||
|
|
||||||
|
describe("Default.vue", () => {
|
||||||
|
const wrapper: VueWrapper = mount(DefaultLayout, {});
|
||||||
|
|
||||||
|
it("loads without crashing", () => {
|
||||||
|
expect(wrapper.exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
54
tests/layouts/default/Footer.test.ts
Normal file
54
tests/layouts/default/Footer.test.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||||
|
import Footer from "~/layouts/default/Footer.vue";
|
||||||
|
|
||||||
|
describe("Footer.vue", () => {
|
||||||
|
const RealDate = Date;
|
||||||
|
|
||||||
|
const mockDate = (year: number) => {
|
||||||
|
globalThis.Date = class extends RealDate {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
super(args.length === 0 ? `${year}-01-01` : args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static now() {
|
||||||
|
return new RealDate(`${year}-01-01`).getTime();
|
||||||
|
}
|
||||||
|
} as any as DateConstructor;
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
globalThis.Date = RealDate;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads without crashing", () => {
|
||||||
|
const wrapper: VueWrapper = mount(Footer, {});
|
||||||
|
expect(wrapper.exists()).toBe(true);
|
||||||
|
expect(wrapper.find(".layout-footer").exists()).toBe(true);
|
||||||
|
expect(wrapper.find(".layout-footer").classes()).toContain("font-bold");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays only 'Glowing Fiesta 2025' when current year is 2025", () => {
|
||||||
|
mockDate(2025);
|
||||||
|
|
||||||
|
const wrapper = mount(Footer);
|
||||||
|
expect(wrapper.text()).toBe("Glowing Fiesta 2025");
|
||||||
|
expect(wrapper.text()).not.toContain(" - ");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays 'Glowing Fiesta 2025 - 2034' when current year is 2034", () => {
|
||||||
|
mockDate(2034);
|
||||||
|
|
||||||
|
const wrapper = mount(Footer);
|
||||||
|
expect(wrapper.text()).toBe("Glowing Fiesta 2025 - 2034");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has proper structure / content", () => {
|
||||||
|
const wrapper = mount(Footer);
|
||||||
|
const footer = wrapper.find("footer");
|
||||||
|
|
||||||
|
expect(footer.exists()).toBe(true);
|
||||||
|
expect(footer.element.tagName).toBe("FOOTER");
|
||||||
|
expect(wrapper.findAll("div")).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
11
tests/layouts/default/Sidebar.test.ts
Normal file
11
tests/layouts/default/Sidebar.test.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||||
|
import Sidebar from "~/layouts/default/Sidebar.vue";
|
||||||
|
|
||||||
|
describe("Sidebar.vue", () => {
|
||||||
|
it("loads without crashing", () => {
|
||||||
|
const wrapper: VueWrapper = mount(Sidebar, {});
|
||||||
|
expect(wrapper.exists()).toBe(true);
|
||||||
|
expect(wrapper.find(".layout-sidebar").exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
155
tests/layouts/default/Topbar.test.ts
Normal file
155
tests/layouts/default/Topbar.test.ts
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||||
|
import Topbar from "~/layouts/default/Topbar.vue";
|
||||||
|
|
||||||
|
describe("Topbar.vue", () => {
|
||||||
|
let wrapper: VueWrapper;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
document.documentElement.className = "";
|
||||||
|
document.body.innerHTML = '<div class="layout-wrapper"></div>';
|
||||||
|
wrapper = mount(Topbar);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (wrapper) {
|
||||||
|
wrapper.unmount();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Component Rendering", () => {
|
||||||
|
it("loads without crashing", () => {
|
||||||
|
expect(wrapper.exists()).toBe(true);
|
||||||
|
expect(wrapper.find(".layout-topbar").exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the logo image and text", () => {
|
||||||
|
const logo = wrapper.find(".layout-topbar-logo");
|
||||||
|
expect(logo.exists()).toBe(true);
|
||||||
|
expect(logo.find("img").attributes("alt")).toBe("Two stick silhouettes admiring fireworks in the sky");
|
||||||
|
expect(logo.text()).toContain("Glowing Fiesta");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the menu toggle button", () => {
|
||||||
|
const menuButton = wrapper.find(".layout-menu-button");
|
||||||
|
expect(menuButton.exists()).toBe(true);
|
||||||
|
expect(menuButton.find(".pi-bars").exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the dark mode toggle button", () => {
|
||||||
|
const darkModeButton = wrapper.find(".layout-topbar-action-highlight");
|
||||||
|
expect(darkModeButton.exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the topbar menu items", () => {
|
||||||
|
const menuButtons = wrapper.findAll(".layout-topbar-menu button");
|
||||||
|
expect(menuButtons).toHaveLength(3);
|
||||||
|
expect(menuButtons[0].text()).toContain("Calendar");
|
||||||
|
expect(menuButtons[1].text()).toContain("Messages");
|
||||||
|
expect(menuButtons[2].text()).toContain("Profile");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Dark Mode Toggle", () => {
|
||||||
|
it("starts with sun icon (light mode)", () => {
|
||||||
|
const darkModeButton = wrapper.find(".layout-topbar-action-highlight");
|
||||||
|
expect(darkModeButton.find(".pi-sun").exists()).toBe(true);
|
||||||
|
expect(darkModeButton.find(".pi-moon").exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toggles to moon icon when clicked", async () => {
|
||||||
|
const darkModeButton = wrapper.find(".layout-topbar-action-highlight");
|
||||||
|
await darkModeButton.trigger("click");
|
||||||
|
|
||||||
|
expect(darkModeButton.find(".pi-moon").exists()).toBe(true);
|
||||||
|
expect(darkModeButton.find(".pi-sun").exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes app-dark class when toggled off", async () => {
|
||||||
|
expect(document.documentElement.classList.contains("app-dark")).toBe(false);
|
||||||
|
const darkModeButton = wrapper.find(".layout-topbar-action-highlight");
|
||||||
|
|
||||||
|
// Toggle on
|
||||||
|
await darkModeButton.trigger("click");
|
||||||
|
expect(document.documentElement.classList.contains("app-dark")).toBe(true);
|
||||||
|
|
||||||
|
// Toggle off
|
||||||
|
await darkModeButton.trigger("click");
|
||||||
|
expect(document.documentElement.classList.contains("app-dark")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates isDarkTheme ref when toggled", async () => {
|
||||||
|
const darkModeButton = wrapper.find(".layout-topbar-action-highlight");
|
||||||
|
|
||||||
|
expect(wrapper.vm.isDarkTheme).toBe(false);
|
||||||
|
|
||||||
|
await darkModeButton.trigger("click");
|
||||||
|
expect(wrapper.vm.isDarkTheme).toBe(true);
|
||||||
|
|
||||||
|
await darkModeButton.trigger("click");
|
||||||
|
expect(wrapper.vm.isDarkTheme).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Menu Toggle", () => {
|
||||||
|
it("toggles layout-static-inactive class on layout wrapper", async () => {
|
||||||
|
const menuButton = wrapper.find(".layout-menu-button");
|
||||||
|
const layoutWrapper = document.querySelector(".layout-wrapper");
|
||||||
|
|
||||||
|
expect(layoutWrapper?.classList.contains("layout-static-inactive")).toBe(false);
|
||||||
|
|
||||||
|
await menuButton.trigger("click");
|
||||||
|
expect(layoutWrapper?.classList.contains("layout-static-inactive")).toBe(true);
|
||||||
|
|
||||||
|
await menuButton.trigger("click");
|
||||||
|
expect(layoutWrapper?.classList.contains("layout-static-inactive")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles missing layout wrapper gracefully", async () => {
|
||||||
|
document.body.innerHTML = "";
|
||||||
|
|
||||||
|
const menuButton = wrapper.find(".layout-menu-button");
|
||||||
|
|
||||||
|
// Should not throw error
|
||||||
|
expect(() => menuButton.trigger("click")).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Logo Link", () => {
|
||||||
|
it("links to home page", () => {
|
||||||
|
const logoLink = wrapper.find(".layout-topbar-logo");
|
||||||
|
expect(logoLink.exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has correct image source", () => {
|
||||||
|
const img = wrapper.find(".layout-topbar-logo img");
|
||||||
|
expect(img.attributes("src")).toContain("logo.png");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Topbar Actions", () => {
|
||||||
|
it("renders Calendar action button with icon", () => {
|
||||||
|
const buttons = wrapper.findAll(".layout-topbar-menu button");
|
||||||
|
const calendarButton = buttons[0];
|
||||||
|
|
||||||
|
expect(calendarButton.find(".pi-calendar").exists()).toBe(true);
|
||||||
|
expect(calendarButton.text()).toContain("Calendar");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders Messages action button with icon", () => {
|
||||||
|
const buttons = wrapper.findAll(".layout-topbar-menu button");
|
||||||
|
const messagesButton = buttons[1];
|
||||||
|
|
||||||
|
expect(messagesButton.find(".pi-inbox").exists()).toBe(true);
|
||||||
|
expect(messagesButton.text()).toContain("Messages");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders Profile action button with icon", () => {
|
||||||
|
const buttons = wrapper.findAll(".layout-topbar-menu button");
|
||||||
|
const profileButton = buttons[2];
|
||||||
|
|
||||||
|
expect(profileButton.find(".pi-user").exists()).toBe(true);
|
||||||
|
expect(profileButton.text()).toContain("Profile");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
11
tests/pages/config/Account.test.ts
Normal file
11
tests/pages/config/Account.test.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||||
|
import AccountPage from "~/pages/config/Account.vue";
|
||||||
|
|
||||||
|
describe("Account.vue", () => {
|
||||||
|
const wrapper: VueWrapper = mount(AccountPage, {});
|
||||||
|
|
||||||
|
it("loads without crashing", () => {
|
||||||
|
expect(wrapper.exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
11
tests/pages/config/Settings.test.ts
Normal file
11
tests/pages/config/Settings.test.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||||
|
import SettingsPage from "~/pages/config/Settings.vue";
|
||||||
|
|
||||||
|
describe("Settings.vue", () => {
|
||||||
|
const wrapper: VueWrapper = mount(SettingsPage, {});
|
||||||
|
|
||||||
|
it("loads without crashing", () => {
|
||||||
|
expect(wrapper.exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
11
tests/pages/index.test.ts
Normal file
11
tests/pages/index.test.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||||
|
import IndexPage from "~/pages/index.vue";
|
||||||
|
|
||||||
|
describe("pages/index.vue", () => {
|
||||||
|
const wrapper: VueWrapper = mount(IndexPage, {});
|
||||||
|
|
||||||
|
it("loads without crashing", () => {
|
||||||
|
expect(wrapper.exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
24
tests/setup.ts
Normal file
24
tests/setup.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { vi } from "vitest";
|
||||||
|
import { config } from "@vue/test-utils";
|
||||||
|
import PrimeVue from "primevue/config";
|
||||||
|
import Button from "primevue/button";
|
||||||
|
import Ripple from "primevue/ripple";
|
||||||
|
|
||||||
|
Object.defineProperty(global, "import", {
|
||||||
|
value: {
|
||||||
|
meta: {
|
||||||
|
glob: vi.fn(() => ({})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
config.global.plugins = [PrimeVue];
|
||||||
|
config.global.stubs = {
|
||||||
|
NuxtLayout: true,
|
||||||
|
NuxtPage: true,
|
||||||
|
Divider: true,
|
||||||
|
NuxtLink: { template: "<a><slot /></a>" },
|
||||||
|
};
|
||||||
|
config.global.components = { Button };
|
||||||
|
config.global.directives = { Ripple };
|
||||||
|
|
@ -2,23 +2,10 @@
|
||||||
// https://nuxt.com/docs/guide/concepts/typescript
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
"files": [],
|
"files": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{ "path": "./.nuxt/tsconfig.app.json" },
|
||||||
"path": "./.nuxt/tsconfig.app.json"
|
{ "path": "./.nuxt/tsconfig.server.json" },
|
||||||
},
|
{ "path": "./.nuxt/tsconfig.shared.json" },
|
||||||
{
|
{ "path": "./.nuxt/tsconfig.node.json" },
|
||||||
"path": "./.nuxt/tsconfig.server.json"
|
{ "path": "./tsconfig.test.json" }
|
||||||
},
|
]
|
||||||
{
|
|
||||||
"path": "./.nuxt/tsconfig.shared.json"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "./.nuxt/tsconfig.node.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
|
||||||
"paths": {
|
|
||||||
"~/*": ["./src/*"],
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
tsconfig.test.json
Normal file
11
tsconfig.test.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"~": ["./app"],
|
||||||
|
"~/*": ["./app/*"],
|
||||||
|
"@": ["./app"],
|
||||||
|
"@/*": ["./app/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["./tests/**/*"]
|
||||||
|
}
|
||||||
45
vitest.config.ts
Normal file
45
vitest.config.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import { fileURLToPath, URL } from "node:url";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
setupFiles: ["./tests/setup.ts"],
|
||||||
|
environment: "happy-dom",
|
||||||
|
include: ["tests/**/*.test.ts"],
|
||||||
|
coverage: {
|
||||||
|
provider: "v8",
|
||||||
|
reporter: ["text", "html", "lcov"],
|
||||||
|
clean: true,
|
||||||
|
cleanOnRerun: true,
|
||||||
|
include: ["**/*.{js,jsx,ts,tsx,vue}"],
|
||||||
|
exclude: [
|
||||||
|
"node_modules/**",
|
||||||
|
"dist/**",
|
||||||
|
"coverage/**",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"tests/mocks/**",
|
||||||
|
|
||||||
|
// Exclude Nuxt generated files
|
||||||
|
".nuxt/**",
|
||||||
|
".output/**",
|
||||||
|
|
||||||
|
// Exclude TypeScript declaration files
|
||||||
|
"**/*.d.ts",
|
||||||
|
|
||||||
|
// Exclude config files
|
||||||
|
"*.config.*",
|
||||||
|
"assets/icons/**",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
name: "GFiesta",
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"~": fileURLToPath(new URL("./app", import.meta.url)),
|
||||||
|
"@": fileURLToPath(new URL("./app", import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue