Skip to content

Commit ad7a0df

Browse files
authored
Merge pull request #31 from connorabbas/develop
Layouts, User Settings, Improvements
2 parents 54b29ec + 8ba0b28 commit ad7a0df

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2189
-1487
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# Vue SPA w/ PrimeVue & Laravel Breeze API Starter Kit
1+
# PrimeVue SPA & Laravel API Starter Kit
22

33
![Static Badge](<https://img.shields.io/badge/Vue.js%20-%20v3.5%20-%20rgb(66%20184%20131)>) ![Static Badge](<https://img.shields.io/badge/PrimeVue%20-%20v4%20-%20rgb(16%20185%20129)>) ![Static Badge](https://img.shields.io/badge/Tailwind%20CSS%20-%20v4%20-%20%230284c7)
44

55
A [PrimeVue](https://primevue.org/) SPA starter kit meant for use with a [Laravel Breeze](https://github.com/laravel/breeze) API stack backend.
66

7-
An alternative to using the [Laravel, Inertia.js, & PrimeVue Starter Kit](https://github.com/connorabbas/laravel-inertia-primevue).
7+
An alternative to using the [Laravel & PrimeVue (Inertia.js) Starter Kit](https://github.com/connorabbas/laravel-primevue-starter-kit).
88

99
## Setup
1010

@@ -21,7 +21,7 @@ An alternative to using the [Laravel, Inertia.js, & PrimeVue Starter Kit](https:
2121
SANCTUM_STATEFUL_DOMAINS="vue-spa.localhost"
2222
SESSION_DOMAIN="vue-spa.localhost"
2323
```
24-
6. Setup additional routes and controllers for profile page in the Laravel API project:
24+
6. Setup additional routes and controllers for profile settings in the Laravel API project:
2525
2626
```
2727
php artisan make:controller ProfileController

jsconfig.json

-14
This file was deleted.

package-lock.json

+795-597
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+21-19
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,40 @@
11
{
22
"name": "primevue-breeze-spa",
3-
"private": true,
43
"version": "0.0.0",
4+
"private": true,
55
"type": "module",
66
"scripts": {
7-
"dev": "vite",
87
"build": "vite build",
9-
"preview": "vite preview",
10-
"lint": "eslint ."
8+
"dev": "vite",
9+
"lint": "eslint .",
10+
"preview": "vite preview"
1111
},
12-
"devDependencies": {
13-
"@eslint/js": "^9.18.0",
12+
"dependencies": {
1413
"@primeuix/themes": "^1.0.0",
15-
"@primevue/auto-import-resolver": "^4.2.5",
16-
"@tailwindcss/vite": "^4.0.0",
17-
"@typescript-eslint/eslint-plugin": "^8.19.1",
18-
"@typescript-eslint/parser": "^8.19.1",
19-
"@vitejs/plugin-vue": "^5.0.5",
2014
"@vueuse/core": "^12.2.0",
2115
"axios": "^1.7.2",
22-
"eslint": "^9.18.0",
23-
"eslint-config-prettier": "^9.1.0",
24-
"eslint-plugin-vue": "^9.32.0",
16+
"lucide-vue-next": "^0.487.0",
2517
"nprogress": "^0.2.0",
2618
"pinia": "^2.1.7",
27-
"primeicons": "^7.0.0",
2819
"primevue": "^4.3.0",
29-
"tailwindcss": "^4.0.0",
20+
"tailwindcss": "^4.1.0",
3021
"tailwindcss-primeui": "^0.5.0",
31-
"typescript": "^5.7.3",
3222
"typescript-eslint": "^8.19.1",
3323
"unplugin-vue-components": "^0.27.4",
34-
"vite": "^6.0",
3524
"vue": "^3.5.0",
3625
"vue-router": "^4.4.0"
26+
},
27+
"devDependencies": {
28+
"@eslint/js": "^9.18.0",
29+
"@primevue/auto-import-resolver": "^4.2.5",
30+
"@tailwindcss/vite": "^4.0.0",
31+
"@typescript-eslint/eslint-plugin": "^8.19.1",
32+
"@typescript-eslint/parser": "^8.19.1",
33+
"@vitejs/plugin-vue": "^5.0.5",
34+
"eslint": "^9.18.0",
35+
"eslint-config-prettier": "^9.1.0",
36+
"eslint-plugin-vue": "^9.32.0",
37+
"typescript": "^5.7.3",
38+
"vite": "^6.2"
3739
}
38-
}
40+
}

src/App.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { RouterView } from 'vue-router';
55
<template>
66
<!-- Use key to re-generate the page view component on each navigation :key="$route.path" -->
77
<!-- Alteratively, use a watch() on each component to re-render dynamic data as needed -->
8-
<Toast position="top-center" />
8+
<Toast position="bottom-right" />
99
<RouterView v-slot="{ Component }">
1010
<Suspense timeout="0">
1111
<template #default>

src/app.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import './assets/css/app.css';
22
import './assets/css/tailwind.css';
33
import 'nprogress/nprogress.css';
4-
import 'primeicons/primeicons.css';
54

6-
import { useDark } from '@vueuse/core';
5+
import { useColorMode } from '@vueuse/core';
76
import customThemePreset from './theme/noir-preset';
87

98
import { createApp } from 'vue';
@@ -20,9 +19,9 @@ import PageTitleSection from '@/components/PageTitleSection.vue';
2019

2120
const app = createApp(App);
2221
const pinia = createPinia();
23-
const darkMode = useDark(); // set Light/Dark Mode
22+
const colorMode = useColorMode({ emitAuto: true });
2423

25-
app.provide('darkMode', darkMode)
24+
app.provide('colorMode', colorMode)
2625
.use(pinia)
2726
.use(router)
2827
.use(PrimeVue, {

src/assets/css/app.css

+5
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,9 @@ body {
1111
#nprogress .bar {
1212
background: var(--p-primary-500) !important;
1313
z-index: 9999999 !important;
14+
}
15+
16+
.lucide {
17+
width: 16px;
18+
height: 16px;
1419
}

src/components/DeleteUserModal.vue

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<script setup>
2+
import { useTemplateRef } from 'vue';
3+
import { useRouter } from 'vue-router';
4+
import { useAuthStore } from '@/stores/auth';
5+
import { useAxiosForm } from '@/composables/useAxiosForm';
6+
import { useFlashMessage } from '@/composables/useFlashMessage.js';
7+
import InputErrors from '@/components/InputErrors.vue';
8+
9+
const modalOpen = defineModel(false, {
10+
type: Boolean,
11+
});
12+
13+
const authStore = useAuthStore();
14+
const router = useRouter();
15+
const { setFlashMessage } = useFlashMessage();
16+
17+
const passwordInput = useTemplateRef('password-input');
18+
19+
const {
20+
data: formData,
21+
validationErrors,
22+
processing: deleting,
23+
del: submitForm,
24+
reset: resetFormFields,
25+
} = useAxiosForm({
26+
password: '',
27+
});
28+
const deleteAccount = () => {
29+
submitForm('/profile', {
30+
onSuccess: () => {
31+
modalOpen.value = false;
32+
authStore.user = null;
33+
router.push({ name: 'login' }).then(() => {
34+
setFlashMessage('success', 'Your account has been deleted.');
35+
});
36+
},
37+
onError: () => passwordInput.value.$el.focus(),
38+
onFinish: () => resetFormFields(),
39+
});
40+
};
41+
42+
function focusPasswordInput() {
43+
passwordInput.value.$el.focus();
44+
}
45+
</script>
46+
47+
<template>
48+
<Dialog
49+
v-model:visible="modalOpen"
50+
position="center"
51+
header="Are you sure you want to delete your account?"
52+
:style="{ width: '40rem' }"
53+
:draggable="false"
54+
dismissableMask
55+
modal
56+
@show="focusPasswordInput"
57+
>
58+
<div class="mb-6">
59+
<p class="m-0 text-muted-color">
60+
Once your account is deleted, all of its resources and data
61+
will be permanently deleted. Please enter your password to
62+
confirm you would like to permanently delete your account.
63+
</p>
64+
</div>
65+
66+
<div class="flex flex-col gap-2">
67+
<InputText
68+
id="password"
69+
ref="password-input"
70+
v-model="formData.password"
71+
:invalid="Boolean(validationErrors?.password)"
72+
type="password"
73+
placeholder="Password"
74+
autocomplete="current-password"
75+
autofocus
76+
required
77+
fluid
78+
@keyup.enter="deleteAccount"
79+
/>
80+
<InputErrors :errors="validationErrors?.password" />
81+
</div>
82+
83+
<template #footer>
84+
<Button
85+
class="mr-2"
86+
label="Cancel"
87+
plain
88+
text
89+
@click="modalOpen = false"
90+
/>
91+
<Button
92+
:loading="deleting"
93+
label="Delete Account"
94+
severity="danger"
95+
@click="deleteAccount"
96+
/>
97+
</template>
98+
</Dialog>
99+
</template>

src/components/PageTitleSection.vue

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
<template>
22
<section>
33
<div class="flex items-end justify-between flex-wrap gap-2 md:gap-4">
4-
<div>
5-
<h1 class="font-bold text-2xl md:text-3xl leading-tight">
4+
<div class="flex flex-col gap-2">
5+
<h1 class="font-bold text-2xl leading-tight">
66
<slot name="title" />
77
</h1>
8+
<div
9+
v-if="$slots.subTitle"
10+
class="text-muted-color"
11+
>
12+
<slot name="subTitle" />
13+
</div>
814
</div>
915
<div>
1016
<div v-if="$slots.end">

src/components/PrimeVue/LinksPanelMenu.vue

-60
This file was deleted.
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup>
2+
import { ref, watchEffect, inject } from 'vue';
3+
import { Sun, Moon, Monitor } from 'lucide-vue-next';
4+
5+
const colorMode = inject('colorMode');
6+
const selectedColorMode = ref(colorMode.value);
7+
8+
const options = [
9+
{ label: 'Light', value: 'light', icon: Sun },
10+
{ label: 'Dark', value: 'dark', icon: Moon },
11+
{ label: 'System', value: 'auto', icon: Monitor },
12+
];
13+
14+
watchEffect(() => colorMode.value = selectedColorMode.value)
15+
</script>
16+
17+
<template>
18+
<SelectButton
19+
v-model="selectedColorMode"
20+
:options="options"
21+
:allowEmpty="false"
22+
optionLabel="label"
23+
optionValue="value"
24+
>
25+
<template #option="{ option }">
26+
<component :is="option.icon" /> {{ option.label }}
27+
</template>
28+
</SelectButton>
29+
</template>

src/components/ToggleDarkModeButton.vue

-15
This file was deleted.

0 commit comments

Comments
 (0)