Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 29 additions & 27 deletions src/APIFunctions/SCEvents.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,57 @@
import { ApiResponse } from './ApiResponses';

const SCEVENTS_API_URL = 'http://localhost:8080';
const SCEVENTS_API_URL = 'http://localhost:8002';

export async function getAllSCEvents() {
let status = new ApiResponse();

const status = new ApiResponse();
try {
const url = new URL('/events/', SCEVENTS_API_URL);
const res = await fetch(url.href, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

if (res.ok) {
const result = await res.json();
status.responseData = result;
} else {
const res = await fetch(`${SCEVENTS_API_URL}/events/`);
const result = await res.json();
status.responseData = result;
if (!res.ok) {
status.error = true;
}
} catch (err) {
status.error = true;
status.responseData = err;
status.error = true;
Comment thread
maernest04 marked this conversation as resolved.
}

return status;
}

export async function getEventByID(id) {
let status = new ApiResponse();
const status = new ApiResponse();
try {
const res = await fetch(`${SCEVENTS_API_URL}/events/${id}`);
const result = await res.json();
status.responseData = result;
if (!res.ok) {
status.error = true;
}
} catch (err) {
status.error = true;
status.responseData = err;
}
return status;
}

export async function createSCEvent(eventBody) {
const status = new ApiResponse();
try {
const url = new URL(`/events/${id}`, SCEVENTS_API_URL);
const res = await fetch(url.href, {
method: 'GET',
const res = await fetch(`${SCEVENTS_API_URL}/events/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(eventBody),
});

if (res.ok) {
const result = await res.json();
status.responseData = result;
} else {
const body = await res.json();
status.responseData = body;
if (!res.ok) {
status.error = true;
}
} catch (err) {
status.error = true;
status.responseData = err;
}

return status;
}
27 changes: 27 additions & 0 deletions src/Components/Navbar/AdminNavbar.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import { useSCE } from '../context/SceContext';
import config from '../../config/config.json';
import { membershipState } from '../../Enums';

export default function UserNavBar(props) {
const { user, setAuthenticated } = useSCE();
Expand Down Expand Up @@ -41,6 +43,30 @@ export default function UserNavBar(props) {
},
];

const sceventsAdminNavLinks = [];
if (config.SCEvents?.ENABLED) {
sceventsAdminNavLinks.push({
title: 'SCEvents',
route: '/events',
icon: (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5a2.25 2.25 0 0 0 2.25-2.25m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5a2.25 2.25 0 0 1 2.25 2.25v7.5" />
</svg>
),
});
if (user?.accessLevel >= membershipState.OFFICER) {
sceventsAdminNavLinks.push({
title: 'Create event',
route: '/events/create',
icon: (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
),
});
}
}

const adminLinks = [
{
title: 'User Manager',
Expand Down Expand Up @@ -82,6 +108,7 @@ export default function UserNavBar(props) {
</svg>
),
},
...sceventsAdminNavLinks,
{
title: 'Card Reader',
route: '/card-reader',
Expand Down
116 changes: 116 additions & 0 deletions src/Pages/Events/CreateEventFormQuestionBlock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/* eslint-disable camelcase -- SCEvents registration question shape uses snake_case */
import React from 'react';

export default function CreateEventFormQuestionBlock({
question,
index,
onUpdateField,
onChangeType,
onRemove,
onUpdateAnswerOption,
onAddAnswerOption,
onRemoveAnswerOption,
}) {
return (
<div className="p-4 mb-3 border border-gray-200 rounded-lg shadow-sm bg-white dark:bg-gray-800 dark:border-gray-700">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start">
<div className="flex flex-wrap flex-1 gap-2 items-center">
<span className="flex justify-center items-center w-6 h-6 text-xs font-medium rounded-full bg-primary/15 text-primary">
{index + 1}
</span>
<select
value={question.type}
onChange={(e) => onChangeType(question.id, e.target.value)}
className="text-sm select select-bordered select-sm dark:bg-gray-700"
>
<option value="textbox">Text input</option>
<option value="multiple_choice">Multiple choice</option>
<option value="dropdown">Dropdown</option>
<option value="checkbox">Checkbox</option>
</select>
<label className="flex gap-2 items-center text-sm cursor-pointer label">
<input
type="checkbox"
checked={!!question.required}
onChange={(e) => onUpdateField(question.id, 'required', e.target.checked)}
className="checkbox checkbox-sm"
/>
<span className="label-text">Required</span>
</label>
</div>
<button
type="button"
className="btn btn-outline btn-sm shrink-0"
onClick={() => onRemove(question.id)}
>
Remove
</button>
</div>

<label className="w-full mt-3 form-control">
<div className="label">
<span className="label-text">Question text</span>
</div>
<input
type="text"
className="w-full text-sm input input-bordered sm:text-base"
value={question.question}
onChange={(e) => onUpdateField(question.id, 'question', e.target.value)}
placeholder="Question"
/>
</label>

{question.type === 'textbox' && (
<div className="flex gap-2 items-center mt-2 text-sm">
<span className="text-gray-500 dark:text-gray-400">Max characters</span>
<input
type="number"
min="1"
className="w-24 input input-bordered input-sm"
value={question.answer_details?.max_chars ?? ''}
onChange={(e) =>
onUpdateField(question.id, 'answer_details', {
max_chars: e.target.value ? parseInt(e.target.value, 10) : undefined,
})
}
placeholder="None"
/>
</div>
)}

{(question.type === 'multiple_choice' || question.type === 'dropdown') && (
<div className="mt-3 space-y-2">
<span className="text-sm text-gray-500 dark:text-gray-400">Answer options</span>
{(question.answer_options || []).map((option, optIndex) => (
<div key={`${question.id}-opt-${optIndex}`} className="flex gap-2 items-center">
<input
type="text"
className="flex-1 text-sm input input-bordered input-sm"
value={option}
onChange={(e) => onUpdateAnswerOption(question.id, optIndex, e.target.value)}
placeholder={`Option ${optIndex + 1}`}
/>
{(question.answer_options || []).length > 1 && (
<button
type="button"
className="btn btn-outline btn-sm btn-square"
onClick={() => onRemoveAnswerOption(question.id, optIndex)}
aria-label="Remove option"
>
×
</button>
)}
</div>
))}
<button
type="button"
className="w-full btn btn-outline btn-sm"
onClick={() => onAddAnswerOption(question.id)}
>
Add option
</button>
</div>
)}
</div>
);
}
Loading
Loading