diff --git a/api/.env.example b/api/.env.example index 8bfe497..9a1e8da 100644 --- a/api/.env.example +++ b/api/.env.example @@ -21,6 +21,7 @@ CLIENT_URL= # This is whatever URL the client web app is served on CLIENT_DOMAIN= #whatever.com APPLICATION_VERSION= # Should match release tag APPLICATION_ENVIRONMENT= # dev / prod +CONFIG_ID= # configures # Glitchtip GLITCHTIP_DSN= diff --git a/api/src/routes/applications.ts b/api/src/routes/applications.ts index c79514e..e1c9394 100644 --- a/api/src/routes/applications.ts +++ b/api/src/routes/applications.ts @@ -7,9 +7,27 @@ import { MemberState, setUserState } from '../services/memberService'; import { getRankByName, insertMemberRank } from '../services/rankService'; import { ApplicationFull, CommentRow } from "@app/shared/types/application" import { assignUserToStatus } from '../services/statusService'; -import { Request, Response } from 'express'; +import { Request, response, Response } from 'express'; import { getUserRoles } from '../services/rolesService'; +//get CoC +router.get('/coc', async (req: Request, res: Response) => { + const output = await fetch(`${process.env.DOC_HOST}/api/pages/714`, { + headers: { + Authorization: `Token ${process.env.DOC_TOKEN_ID}:${process.env.DOC_TOKEN_SECRET}`, + } + }) + + if (output.ok) { + const out = await output.json(); + res.status(200).json(out.html); + } else { + console.error("Failed to fetch LOA policy from bookstack"); + res.sendStatus(500); + } +}) + + // POST /application router.post('/', async (req, res) => { try { @@ -141,8 +159,9 @@ router.get('/:id', async (req: Request, res: Response) => { }); // POST /application/approve/:id -router.post('/approve/:id', async (req, res) => { - const appID = req.params.id; +router.post('/approve/:id', async (req: Request, res: Response) => { + const appID = Number(req.params.id); + const approved_by = req.user.id; try { const app = await getApplicationByID(appID); @@ -153,14 +172,14 @@ router.post('/approve/:id', async (req, res) => { throw new Error("Something went wrong approving the application"); } - console.log(app.member_id); //update user profile await setUserState(app.member_id, MemberState.Member); - let nextRank = await getRankByName('Recruit') - await insertMemberRank(app.member_id, nextRank.id); - //assign user to "pending basic" - await assignUserToStatus(app.member_id, 1); + await pool.query('CALL sp_accept_new_recruit_validation(?, ?, ?, ?)', [Number(process.env.CONFIG_ID), app.member_id, approved_by, approved_by]) + // let nextRank = await getRankByName('Recruit') + // await insertMemberRank(app.member_id, nextRank.id); + // //assign user to "pending basic" + // await assignUserToStatus(app.member_id, 1); res.sendStatus(200); } catch (err) { console.error('Approve failed:', err); @@ -282,4 +301,5 @@ router.post('/restart', async (req: Request, res: Response) => { } }) + module.exports = router; diff --git a/ui/src/api/application.ts b/ui/src/api/application.ts index 8f60f1e..ec05982 100644 --- a/ui/src/api/application.ts +++ b/ui/src/api/application.ts @@ -89,7 +89,7 @@ export async function getMyApplication(id: number): Promise { } export async function approveApplication(id: Number) { - const res = await fetch(`${addr}/application/approve/${id}`, { method: 'POST' }) + const res = await fetch(`${addr}/application/approve/${id}`, { method: 'POST', credentials: 'include' }) if (!res.ok) { console.error("Something went wrong approving the application") @@ -97,7 +97,7 @@ export async function approveApplication(id: Number) { } export async function denyApplication(id: Number) { - const res = await fetch(`${addr}/application/deny/${id}`, { method: 'POST' }) + const res = await fetch(`${addr}/application/deny/${id}`, { method: 'POST', credentials: 'include' }) if (!res.ok) { console.error("Something went wrong denying the application") @@ -113,4 +113,20 @@ export async function restartApplication() { if (!res.ok) { console.error("Something went wrong restarting your application") } +} + +export async function getCoC(): Promise { + const res = await fetch(`${addr}/application/coc`, { + method: "GET", + credentials: 'include', + }); + if (res.ok) { + const out = res.json(); + if (!out) { + return null; + } + return out; + } else { + return null; + } } \ No newline at end of file diff --git a/ui/src/assets/base.css b/ui/src/assets/base.css index b29a3bb..f06ddf1 100644 --- a/ui/src/assets/base.css +++ b/ui/src/assets/base.css @@ -168,7 +168,7 @@ } /* Root container */ -.ListRendererV2-container { +.bookstack-container { font-family: var(--font-sans, system-ui), sans-serif; color: var(--foreground); line-height: 1.45; @@ -178,56 +178,53 @@ } /* Headers */ -.ListRendererV2-container h4 { +.bookstack-container h4 { margin: 0.9rem 0 0.4rem 0; font-weight: 600; line-height: 1.35; font-size: 1.05rem; color: var(--foreground); - /* PURE WHITE */ } -.ListRendererV2-container h5 { +.bookstack-container h5 { margin: 0.9rem 0 0.4rem 0; font-weight: 600; line-height: 1.35; font-size: 0.95rem; color: var(--foreground); - /* Still white (change to muted if desired) */ } /* Lists */ -.ListRendererV2-container ul { +.bookstack-container ul { list-style-type: disc; margin-left: 1.1rem; margin-bottom: 0.6rem; padding-left: 0.6rem; color: var(--muted-foreground); - /* dim text */ } /* Nested lists */ -.ListRendererV2-container ul ul { +.bookstack-container ul ul { list-style-type: circle; margin-left: 0.9rem; } /* List items */ -.ListRendererV2-container li { +.bookstack-container li { margin: 0.15rem 0; padding-left: 0.1rem; color: var(--muted-foreground); } /* Bullet color */ -.ListRendererV2-container li::marker { +.bookstack-container li::marker { color: var(--muted-foreground); } /* Inline elements */ -.ListRendererV2-container li p, -.ListRendererV2-container li span, -.ListRendererV2-container p { +.bookstack-container li p, +.bookstack-container li span, +.bookstack-container p { display: inline; margin: 0; padding: 0; @@ -235,6 +232,45 @@ } /* Top-level spacing */ -.ListRendererV2-container>ul>li { +.bookstack-container>ul>li { margin-top: 0.3rem; +} + +/* links */ +.bookstack-container a { + color: var(--color-primary); + margin-top: 0.3rem; +} + +.bookstack-container a:hover { + text-decoration: underline; +} + +/* Scrollbar stuff */ +/* Firefox */ +.scrollbar-themed { + scrollbar-width: thin; + scrollbar-color: #555 #1f1f1f; + padding-right: 6px; +} + +/* Chrome, Edge, Safari */ +.scrollbar-themed::-webkit-scrollbar { + width: 10px; + /* slightly wider to allow padding look */ +} + +.scrollbar-themed::-webkit-scrollbar-track { + background: #1f1f1f; + margin-left: 6px; + /* ❗ adds space between content + scrollbar */ +} + +.scrollbar-themed::-webkit-scrollbar-thumb { + background: #555; + border-radius: 9999px; +} + +.scrollbar-themed::-webkit-scrollbar-thumb:hover { + background: #777; } \ No newline at end of file diff --git a/ui/src/components/application/ApplicationForm.vue b/ui/src/components/application/ApplicationForm.vue index eb1a85f..5ad43b9 100644 --- a/ui/src/components/application/ApplicationForm.vue +++ b/ui/src/components/application/ApplicationForm.vue @@ -13,10 +13,18 @@ import Input from '@/components/ui/input/Input.vue'; import Textarea from '@/components/ui/textarea/Textarea.vue'; import { toTypedSchema } from '@vee-validate/zod'; import { Form } from 'vee-validate'; -import { onMounted, ref } from 'vue'; +import { nextTick, onMounted, ref, watch } from 'vue'; import * as z from 'zod'; import DateInput from '../form/DateInput.vue'; import { ApplicationData } from '@shared/types/application'; +import Dialog from '../ui/dialog/Dialog.vue'; +import DialogTrigger from '../ui/dialog/DialogTrigger.vue'; +import DialogContent from '../ui/dialog/DialogContent.vue'; +import DialogHeader from '../ui/dialog/DialogHeader.vue'; +import DialogTitle from '../ui/dialog/DialogTitle.vue'; +import DialogDescription from '../ui/dialog/DialogDescription.vue'; +import { getCoC } from '@/api/application'; +import { startBrowserTracingPageLoadSpan } from '@sentry/vue'; const regexA = /^https?:\/\/steamcommunity\.com\/id\/[A-Za-z0-9_]+\/?$/; const regexB = /^https?:\/\/steamcommunity\.com\/profiles\/\d+\/?$/; @@ -61,7 +69,7 @@ async function onSubmit(val: any) { emit('submit', val); } -onMounted(() => { +onMounted(async () => { if (props.data !== null) { const parsed = typeof props.data === "string" ? JSON.parse(props.data) @@ -71,8 +79,35 @@ onMounted(() => { } else { initialValues.value = { ...fallbackInitials }; } + + // CoCbox.value.innerHTML = await getCoC() + CoCString.value = await getCoC(); }) +const showCoC = ref(false); +const CoCbox = ref(); +const CoCString = ref(); + +async function onDialogToggle(state: boolean) { + showCoC.value = state; +} + +function enforceExternalLinks() { + if (!CoCbox.value) return; + + const links = CoCbox.value.querySelectorAll("a"); + links.forEach(a => { + a.setAttribute("target", "_blank"); + a.setAttribute("rel", "noopener noreferrer"); + }); +} + +watch(() => showCoC.value, async () => { + if (showCoC) { + await nextTick(); // wait for v-html to update + enforceExternalLinks(); + } +}); @@ -273,7 +308,8 @@ onMounted(() => {
- By checking this box, you accept the .
@@ -284,7 +320,19 @@ onMounted(() => {
- +
+ + + + + Community Code of Conduct + +
+
+
+
+
+ \ No newline at end of file diff --git a/ui/src/components/loa/loaForm.vue b/ui/src/components/loa/loaForm.vue index 3ca59a1..a7362b8 100644 --- a/ui/src/components/loa/loaForm.vue +++ b/ui/src/components/loa/loaForm.vue @@ -132,7 +132,7 @@ const maxEndDate = computed(() => {

LOA Policy

-
+
diff --git a/ui/src/pages/ManageApplications.vue b/ui/src/pages/ManageApplications.vue index a19316b..76b4737 100644 --- a/ui/src/pages/ManageApplications.vue +++ b/ui/src/pages/ManageApplications.vue @@ -1,5 +1,5 @@