Compare commits
22 Commits
homepage/w
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f3e35f3f6a | |||
| d7b099ac75 | |||
| a6b521a89c | |||
| ad4d28b5dd | |||
| ac22e36202 | |||
| 3180097aa7 | |||
| 4476dd6d74 | |||
| 9ec6d65a4d | |||
| 65f8527cc5 | |||
| bded69345f | |||
| 6f7a0bf3b2 | |||
| 50a94ae86f | |||
| 5b61687cd9 | |||
| 1cf6b35021 | |||
| 18d08af91d | |||
| a95dbe2623 | |||
| 6f3ab2da73 | |||
| 8f397131d4 | |||
| 9139a06f4c | |||
| a7c2ed7dff | |||
| 637968552d | |||
| 9953e2765a |
13
api/package-lock.json
generated
13
api/package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@sentry/node": "^10.27.0",
|
||||
"chalk": "^5.6.2",
|
||||
"connect-sqlite3": "^0.9.16",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
@@ -1314,6 +1315,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/node": "^10.27.0",
|
||||
"chalk": "^5.6.2",
|
||||
"connect-sqlite3": "^0.9.16",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
|
||||
@@ -12,7 +12,7 @@ const pool = mariadb.createPool({
|
||||
connectionLimit: 5,
|
||||
connectTimeout: 10000, // give it more breathing room
|
||||
acquireTimeout: 15000,
|
||||
database: 'ranger_unit_tracker',
|
||||
database: process.env.DB_DATABASE,
|
||||
ssl: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -5,8 +5,26 @@ import express = require('express');
|
||||
import cors = require('cors');
|
||||
import morgan = require('morgan');
|
||||
const app = express()
|
||||
app.use(morgan('dev', {
|
||||
skip: (req) => {
|
||||
import chalk from 'chalk';
|
||||
app.use(morgan((tokens: morgan.TokenIndexer, req: express.Request, res: express.Response) => {
|
||||
const status = Number(tokens.status(req, res));
|
||||
|
||||
// Colorize status code
|
||||
const statusColor = status >= 500 ? chalk.red
|
||||
: status >= 400 ? chalk.yellow
|
||||
: status >= 300 ? chalk.cyan
|
||||
: chalk.green;
|
||||
|
||||
return [
|
||||
chalk.gray(`[${new Date().toISOString()}]`),
|
||||
chalk.blue.bold(tokens.method(req, res)),
|
||||
tokens.url(req, res),
|
||||
statusColor(status),
|
||||
chalk.magenta(tokens['response-time'](req, res) + ' ms'),
|
||||
chalk.yellow(`- User: ${req.user?.name ? `${req.user.name} (${req.user.id})` : 'Unauthenticated'}`),
|
||||
].join(' ');
|
||||
}, {
|
||||
skip: (req: express.Request) => {
|
||||
return req.originalUrl === '/members/me';
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -105,7 +105,6 @@ router.get('/me/:id', [requireLogin], async (req: Request, res: Response) => {
|
||||
const application = await getApplicationByID(appID);
|
||||
if (application === undefined)
|
||||
return res.sendStatus(204);
|
||||
console.log(application.member_id, member)
|
||||
if (application.member_id != member) {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
@@ -191,8 +190,6 @@ router.post('/:id/comment', [requireLogin], async (req: Request, res: Response)
|
||||
const data = req.body.message;
|
||||
const user = req.user;
|
||||
|
||||
console.log(user)
|
||||
|
||||
const sql = `INSERT INTO application_comments(
|
||||
application_id,
|
||||
poster_id,
|
||||
@@ -205,7 +202,6 @@ VALUES(?, ?, ?);`
|
||||
var conn = await pool.getConnection();
|
||||
|
||||
const result = await conn.query(sql, [appID, user.id, data])
|
||||
console.log(result)
|
||||
if (result.affectedRows !== 1) {
|
||||
conn.release();
|
||||
throw new Error("Insert Failure")
|
||||
@@ -237,8 +233,6 @@ router.post('/:id/adminComment', [requireLogin, requireRole("Recruiter")], async
|
||||
const data = req.body.message;
|
||||
const user = req.user;
|
||||
|
||||
console.log(user)
|
||||
|
||||
const sql = `INSERT INTO application_comments(
|
||||
application_id,
|
||||
poster_id,
|
||||
@@ -251,7 +245,6 @@ VALUES(?, ?, ?, 1);`
|
||||
var conn = await pool.getConnection();
|
||||
|
||||
const result = await conn.query(sql, [appID, user.id, data])
|
||||
console.log(result)
|
||||
if (result.affectedRows !== 1) {
|
||||
conn.release();
|
||||
throw new Error("Insert Failure")
|
||||
|
||||
@@ -115,11 +115,24 @@ router.get('/callback', (req, res, next) => {
|
||||
router.get('/logout', [requireLogin], function (req, res, next) {
|
||||
req.logout(function (err) {
|
||||
if (err) { return next(err); }
|
||||
var params = {
|
||||
client_id: process.env.AUTH_CLIENT_ID,
|
||||
returnTo: process.env.CLIENT_URL
|
||||
};
|
||||
res.redirect(process.env.AUTH_END_SESSION_URI + '?' + querystring.stringify(params));
|
||||
|
||||
req.session.destroy((err) => {
|
||||
if (err) { return next(err); }
|
||||
|
||||
res.clearCookie('connect.sid', {
|
||||
path: '/',
|
||||
domain: process.env.CLIENT_DOMAIN,
|
||||
httpOnly: true,
|
||||
sameSite: 'lax'
|
||||
});
|
||||
|
||||
var params = {
|
||||
client_id: process.env.AUTH_CLIENT_ID,
|
||||
returnTo: process.env.CLIENT_URL
|
||||
};
|
||||
res.redirect(process.env.AUTH_END_SESSION_URI + '?' + querystring.stringify(params));
|
||||
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -107,7 +107,6 @@ r.put('/', [requireLogin, requireMemberState(MemberState.Member)], async (req: R
|
||||
let event: CalendarEvent = req.body;
|
||||
event.start = new Date(event.start);
|
||||
event.end = new Date(event.end);
|
||||
console.log(event);
|
||||
updateEvent(event);
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
|
||||
@@ -83,7 +83,6 @@ er.get('/attendees/:id', async (req: Request, res: Response) => {
|
||||
er.post('/', async (req: Request, res: Response) => {
|
||||
const posterID: number = req.user.id;
|
||||
try {
|
||||
console.log();
|
||||
let data: CourseEventDetails = req.body;
|
||||
data.created_by = posterID;
|
||||
data.event_date = new Date(data.event_date);
|
||||
|
||||
@@ -30,9 +30,6 @@ router.post("/admin", [requireRole("17th Administrator")], async (req: Request,
|
||||
let LOARequest = req.body as LOARequest;
|
||||
LOARequest.created_by = req.user.id;
|
||||
LOARequest.filed_date = new Date();
|
||||
|
||||
console.log(LOARequest);
|
||||
|
||||
try {
|
||||
await createNewLOA(LOARequest);
|
||||
res.sendStatus(201);
|
||||
|
||||
@@ -53,7 +53,6 @@ router.get('/me', [requireLogin], async (req, res) => {
|
||||
router.get('/settings', [requireLogin], async (req: Request, res: Response) => {
|
||||
try {
|
||||
let user = req.user.id;
|
||||
console.log(user);
|
||||
let output = await getMemberSettings(user);
|
||||
res.status(200).json(output);
|
||||
} catch (error) {
|
||||
@@ -66,7 +65,6 @@ router.put('/settings', [requireLogin], async (req: Request, res: Response) => {
|
||||
try {
|
||||
let user = req.user.id;
|
||||
let settings: memberSettings = req.body;
|
||||
console.log(settings)
|
||||
await setUserSettings(user, settings);
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
|
||||
@@ -28,7 +28,6 @@ ur.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administ
|
||||
ur.delete('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
|
||||
try {
|
||||
const body = req.body;
|
||||
console.log(body);
|
||||
|
||||
const sql = 'DELETE FROM members_roles WHERE member_id = ? AND role_id = ?'
|
||||
await pool.query(sql, [body.member_id, body.role_id])
|
||||
@@ -86,7 +85,6 @@ r.get('/', [requireMemberState(MemberState.Member)], async (req, res) => {
|
||||
r.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
|
||||
try {
|
||||
const { name, color, description } = req.body;
|
||||
console.log('Creating role:', { name, color, description });
|
||||
if (!name || !color) {
|
||||
return res.status(400).json({ error: 'Name and color are required' });
|
||||
}
|
||||
@@ -113,7 +111,7 @@ r.delete('/:id', [requireMemberState(MemberState.Member), requireRole("17th Admi
|
||||
const res = await pool.query(sql, [id]);
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -73,7 +73,6 @@ export async function approveApplication(id: number, approver: number) {
|
||||
`;
|
||||
|
||||
const result = await pool.execute(sql, [approver, id]);
|
||||
console.log(result);
|
||||
if (result.affectedRows == 1) {
|
||||
return
|
||||
} else {
|
||||
@@ -91,7 +90,6 @@ export async function denyApplication(id: number, approver: number) {
|
||||
`;
|
||||
|
||||
const result = await pool.execute(sql, [approver, id]);
|
||||
console.log(result);
|
||||
if (result.affectedRows == 1) {
|
||||
return
|
||||
} else {
|
||||
|
||||
@@ -126,6 +126,5 @@ export async function getEventAttendance(eventID: number): Promise<CalendarSignu
|
||||
|
||||
const sql = "CALL `sp_GetCalendarEventSignups`(?)"
|
||||
const res = await pool.query(sql, [eventID]);
|
||||
console.log(res[0]);
|
||||
return res[0];
|
||||
}
|
||||
@@ -89,13 +89,11 @@ export async function closeLOA(id: number, closer: number) {
|
||||
ended_at = NOW()
|
||||
WHERE leave_of_absences.id = ?`;
|
||||
let out = await pool.query(sql, [closer, id]);
|
||||
console.log(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
export async function getLOAbyID(id: number): Promise<LOARequest> {
|
||||
let res = await pool.query(`SELECT * FROM leave_of_absences WHERE id = ?`, [id]);
|
||||
console.log(res);
|
||||
if (res.length != 1)
|
||||
throw new Error(`LOA with id ${id} not found`);
|
||||
return res[0];
|
||||
|
||||
@@ -34,7 +34,6 @@ export async function setUserSettings(id: number, settings: memberSettings) {
|
||||
displayName = ?
|
||||
WHERE id = ?;`;
|
||||
let result = await pool.query(sql, [settings.displayName, id])
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
export async function getMembersLite(ids: number[]): Promise<MemberLight[]> {
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function getUserRoles(userID: number): Promise<Role[]> {
|
||||
const sql = `SELECT r.id, r.name
|
||||
FROM members_roles mr
|
||||
INNER JOIN roles r ON mr.role_id = r.id
|
||||
WHERE mr.member_id = 190;`;
|
||||
WHERE mr.member_id = ?;`;
|
||||
|
||||
return await pool.query(sql, [userID]);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
export function toDateTime(date: Date): string {
|
||||
console.log(date);
|
||||
if (typeof date === 'string') {
|
||||
date = new Date(date);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ export type Role = {
|
||||
const addr = import.meta.env.VITE_APIHOST;
|
||||
|
||||
export async function getRoles(): Promise<Role[]> {
|
||||
const res = await fetch(`${addr}/roles`)
|
||||
const res = await fetch(`${addr}/roles`, {
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
return res.json() as Promise<Role[]>;
|
||||
@@ -26,11 +28,12 @@ export async function createRole(name: string, color: string, description: strin
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
color,
|
||||
description
|
||||
})
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
@@ -47,6 +50,7 @@ export async function addMemberToRole(member_id: number, role_id: number): Promi
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
member_id,
|
||||
role_id
|
||||
@@ -64,6 +68,7 @@ export async function addMemberToRole(member_id: number, role_id: number): Promi
|
||||
export async function removeMemberFromRole(member_id: number, role_id: number): Promise<boolean> {
|
||||
const res = await fetch(`${addr}/memberRoles`, {
|
||||
method: "DELETE",
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
@@ -83,7 +88,8 @@ export async function removeMemberFromRole(member_id: number, role_id: number):
|
||||
|
||||
export async function deleteRole(role_id: number): Promise<boolean> {
|
||||
const res = await fetch(`${addr}/roles/${role_id}`, {
|
||||
method: "DELETE"
|
||||
method: "DELETE",
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
|
||||
@@ -197,7 +197,7 @@ defineExpose({ forceReload })
|
||||
<DropdownMenuItem v-if="activeEvent.cancelled" @click="setCancel(false)">
|
||||
Un-Cancel
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem v-else @click="setCancel(true)">
|
||||
<DropdownMenuItem v-else @click="setCancel(true)" class="text-destructive">
|
||||
Cancel
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -74,7 +74,6 @@ const { handleSubmit, values, resetForm } = useForm({
|
||||
const formSubmitted = ref(false);
|
||||
|
||||
const onSubmit = handleSubmit(async (values) => {
|
||||
console.log(values);
|
||||
const out: LOARequest = {
|
||||
member_id: values.member_id,
|
||||
start_date: values.start_date,
|
||||
@@ -122,7 +121,7 @@ const minEndDate = computed(() => {
|
||||
if (values.start_date) {
|
||||
return new CalendarDate(values.start_date.getFullYear(), values.start_date.getMonth() + 1, values.start_date.getDate())
|
||||
} else {
|
||||
return null;
|
||||
return today(getLocalTimeZone());
|
||||
}
|
||||
})
|
||||
|
||||
@@ -135,6 +134,17 @@ const maxEndDate = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const minStartDate = computed(() => {
|
||||
if (values.type && values.end_date) {
|
||||
let endDateObj = new Date(values.end_date.getTime() - values.type.max_length_days * 24 * 60 * 60 * 1000);
|
||||
let td = today(getLocalTimeZone());
|
||||
let start = new CalendarDate(endDateObj.getFullYear(), endDateObj.getMonth() + 1, endDateObj.getDate())
|
||||
return td.compare(start) > 0 ? td : start;
|
||||
} else {
|
||||
return today(getLocalTimeZone());
|
||||
}
|
||||
})
|
||||
|
||||
const memberFilter = ref('');
|
||||
|
||||
const filteredMembers = computed(() => {
|
||||
@@ -227,21 +237,31 @@ const filteredMembers = computed(() => {
|
||||
<FieldContent>
|
||||
<FieldLabel>Start Date</FieldLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" :class="cn(
|
||||
'w-full justify-start text-left font-normal',
|
||||
!field.value && 'text-muted-foreground',
|
||||
)">
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
{{ field.value ? df.format(field.value) : "Pick a date" }}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<div class="relative inline-flex items-center group">
|
||||
<PopoverTrigger as-child>
|
||||
<Button :disabled="!values.type" variant="outline" :class="cn(
|
||||
'w-full justify-start text-left font-normal',
|
||||
!field.value && 'text-muted-foreground',
|
||||
)">
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
{{ field.value ? df.format(field.value) : "Pick a date" }}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<!-- Tooltip bubble -->
|
||||
<div v-if="!values?.type" class="pointer-events-none absolute -top-9 left-1/2 -translate-x-1/2
|
||||
whitespace-nowrap rounded-md bg-popover px-2 py-1 text-xs
|
||||
text-popover-foreground shadow-md border border-border
|
||||
opacity-0 translate-y-1
|
||||
group-hover:opacity-100 group-hover:translate-y-0
|
||||
transition-opacity transition-transform duration-150">
|
||||
Select an LOA type first
|
||||
</div>
|
||||
</div>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<Calendar
|
||||
:model-value="field.value
|
||||
? new CalendarDate(field.value.getFullYear(), field.value.getMonth() + 1, field.value.getDate()) : null"
|
||||
@update:model-value="(val: CalendarDate) => field.onChange(val.toDate(getLocalTimeZone()))"
|
||||
layout="month-and-year" :min-value="today(getLocalTimeZone())" />
|
||||
layout="month-and-year"
|
||||
:min-value="minStartDate || today(getLocalTimeZone())" />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div class="h-4">
|
||||
@@ -255,18 +275,28 @@ const filteredMembers = computed(() => {
|
||||
<FieldContent>
|
||||
<FieldLabel>End Date</FieldLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" :class="cn(
|
||||
'w-full justify-start text-left font-normal',
|
||||
!field.value && 'text-muted-foreground',
|
||||
)">
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
{{ field.value ? df.format(field.value) : "Pick a date" }}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<div class="relative inline-flex items-center group">
|
||||
<PopoverTrigger as-child>
|
||||
<Button :disabled="!values.type" variant="outline" :class="cn(
|
||||
'w-full justify-start text-left font-normal',
|
||||
!field.value && 'text-muted-foreground',
|
||||
)">
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
{{ field.value ? df.format(field.value) : "Pick a date" }}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<!-- Tooltip bubble -->
|
||||
<div v-if="!values?.type" class="pointer-events-none absolute -top-9 left-1/2 -translate-x-1/2
|
||||
whitespace-nowrap rounded-md bg-popover px-2 py-1 text-xs
|
||||
text-popover-foreground shadow-md border border-border
|
||||
opacity-0 translate-y-1
|
||||
group-hover:opacity-100 group-hover:translate-y-0
|
||||
transition-opacity transition-transform duration-150">
|
||||
Select an LOA type first
|
||||
</div>
|
||||
</div>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<Calendar
|
||||
:model-value="field.value ? new CalendarDate(field.value.getFullYear(), field.value.getMonth() + 1, field.value.getDate()) : null"
|
||||
@update:model-value="(val: CalendarDate) => field.onChange(val.toDate(getLocalTimeZone()))"
|
||||
:default-placeholder="defaultPlaceholder" :min-value="minEndDate"
|
||||
:max-value="maxEndDate" layout="month-and-year">
|
||||
|
||||
@@ -34,7 +34,8 @@ import { Plus, X } from 'lucide-vue-next';
|
||||
import Separator from '@/components/ui/separator/Separator.vue';
|
||||
import Input from '@/components/ui/input/Input.vue';
|
||||
import Label from '@/components/ui/label/Label.vue';
|
||||
import { getMembers, Member } from '@/api/member';
|
||||
import { getMembers } from '@/api/member';
|
||||
import { Member } from '@shared/types/member';
|
||||
|
||||
const roles = ref<Role[]>([])
|
||||
const activeRole = ref<Role | null>(null)
|
||||
@@ -116,16 +117,18 @@ async function handleCreateGroup() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleAddMember() {
|
||||
async function handleAddMember() {
|
||||
//guard
|
||||
if (memberToAdd.value == null)
|
||||
return;
|
||||
|
||||
addMemberToRole(memberToAdd.value.member_id, activeRole.value.id);
|
||||
await addMemberToRole(memberToAdd.value.member_id, activeRole.value.id);
|
||||
roles.value = await getRoles();
|
||||
}
|
||||
|
||||
function handleRemoveMember(memberId: number) {
|
||||
async function handleRemoveMember(memberId: number) {
|
||||
removeMemberFromRole(memberId, activeRole.value.id);
|
||||
roles.value = await getRoles();
|
||||
}
|
||||
|
||||
async function handleDeleteRole() {
|
||||
@@ -193,7 +196,7 @@ onMounted(async () => {
|
||||
</ul>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button @click="handleDeleteRole">Delete Group</Button>
|
||||
<!-- <Button @click="handleDeleteRole">Delete Group</Button> -->
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -232,7 +235,7 @@ onMounted(async () => {
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="flex items-center justify-between my-4">
|
||||
<p>Groups</p>
|
||||
<Button @click="showCreateGroupDialog = true">+ Add New Group</Button>
|
||||
<!-- <Button @click="showCreateGroupDialog = true">+ Add New Group</Button> -->
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-5">
|
||||
<Card v-for="value in roles" :key="value.id" @click="activeRole = value; showDialog = true"
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useMemberDirectory } from "@/stores/memberDirectory";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
||||
const saving = ref(false);
|
||||
const saveSuccess = ref(false);
|
||||
const loading = ref(true);
|
||||
const showLoading = ref(false);
|
||||
const form = ref<memberSettings>();
|
||||
@@ -20,13 +21,14 @@ const userStore = useUserStore()
|
||||
|
||||
function saveSettings() {
|
||||
saving.value = true;
|
||||
saveSuccess.value = false;
|
||||
|
||||
setTimeout(async () => {
|
||||
// Replace with your API save call
|
||||
setMemberSettings(form.value);
|
||||
saving.value = false;
|
||||
console.log(userStore.user.id)
|
||||
memberDictionary.invalidateMember(userStore.user.id)
|
||||
memberDictionary.invalidateMember(userStore.user.member.member_id)
|
||||
saveSuccess.value = true;
|
||||
}, 800);
|
||||
}
|
||||
|
||||
@@ -75,7 +77,8 @@ onMounted(async () => {
|
||||
</CardContent>
|
||||
</Transition>
|
||||
|
||||
<CardFooter class="flex justify-end">
|
||||
<CardFooter class="flex justify-end gap-5 items-center">
|
||||
<p v-if="saveSuccess" class="text-green-500">Profile saved</p>
|
||||
<Button @click="saveSettings" :disabled="saving">
|
||||
{{ saving ? "Saving..." : "Save Changes" }}
|
||||
</Button>
|
||||
|
||||
@@ -16,7 +16,7 @@ const router = createRouter({
|
||||
{ path: '/members', component: () => import('@/pages/memberList.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
||||
{ path: '/loa', component: () => import('@/pages/SubmitLOA.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
||||
{ path: '/transfer', component: () => import('@/pages/Transfer.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
||||
{ path: '/profile', component: () => import('@/pages/MyProfile.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
||||
{ path: '/profile', component: () => import('@/pages/MyProfile.vue'), meta: { requiresAuth: true } },
|
||||
|
||||
|
||||
{ path: '/calendar', component: () => import('@/pages/Calendar.vue') },
|
||||
|
||||
Reference in New Issue
Block a user