Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
56a2553
schema and shared changes, still need to change faq to boolean pattern
getheobald May 22, 2026
ef8946f
editing migration for safety
getheobald Jun 12, 2026
b91e4cf
transformers and services
getheobald Jun 16, 2026
3c7252b
final orgId fixes
getheobald Jun 18, 2026
012423a
one more dashboard boolean default change
getheobald Jun 18, 2026
13c00e8
lint
getheobald Jun 18, 2026
3b922f8
Merge remote-tracking branch 'origin/develop' into onboarding/schema-…
getheobald Jun 18, 2026
e36fb37
merge migrations
getheobald Jun 19, 2026
f98e3cc
add routes, update migration to add reviewer, pass org to query args
getheobald Jun 26, 2026
0f8d70b
Merge pull request #4285 from Northeastern-Electric-Racing/onboarding…
wavehassman Jun 26, 2026
1d46f0c
validation endpoint
getheobald Jun 26, 2026
bacb7b1
api and hook, validate in SetUserPreferences
getheobald Jun 29, 2026
ae7510d
unit tests
getheobald Jun 29, 2026
8e83829
prettier
getheobald Jun 29, 2026
978c190
different test pattern
getheobald Jun 29, 2026
20aeed2
Merge branch 'develop' into feature/onboarding-improvement
wavehassman Jul 7, 2026
c7b64d1
#4122 schema changes + widget
wavehassman Jul 7, 2026
34286e9
#4122 fix error handling
wavehassman Jul 7, 2026
b862b82
remove endpoint and validate in updateusersettings instead
getheobald Jul 10, 2026
3b1aac0
remove tests
getheobald Jul 10, 2026
776bab2
tests
getheobald Jul 10, 2026
1bd86bc
remove endpoint from urls
getheobald Jul 10, 2026
06e670d
prettier
getheobald Jul 10, 2026
ab65e7c
typo
getheobald Jul 10, 2026
ef6c1fe
#4121 frontend and backend changes
wavehassman Jul 10, 2026
0c70545
#4121 make it two tables
wavehassman Jul 11, 2026
ee3f0eb
#4121 fix look of table in admin tools
wavehassman Jul 11, 2026
0921585
#4121 silly mistakes + refactor backend
wavehassman Jul 11, 2026
2bf91ec
#4121 frontend code adjustments
wavehassman Jul 11, 2026
745f251
Abstracted out faq service, added recruitment / onboarding separation
chpy04 Jul 16, 2026
e5f3d43
Merge pull request #4312 from Northeastern-Electric-Racing/#4122-cale…
wavehassman Jul 26, 2026
6fa6a7e
Merge branch '#4121-milestone-widget' into feature/onboarding-improve…
wavehassman Jul 26, 2026
b85e46e
Merge pull request #4301 from Northeastern-Electric-Racing/onboarding…
wavehassman Jul 26, 2026
e2d2be2
code fixes
wavehassman Jul 26, 2026
7cc79de
fix flow from potential new member to onboarding to new member
wavehassman Jul 27, 2026
f671b75
Merge branch 'develop' into feature/onboarding-improvement
wavehassman Jul 27, 2026
343ba16
#4120 slack feed widget
wavehassman Jul 27, 2026
2cd849b
#4134 and #4133 usefule links
wavehassman Jul 27, 2026
a45dbf3
#4135 readonly checklist
wavehassman Jul 27, 2026
cb31a24
#4123 + #4124 team join requests
wavehassman Jul 28, 2026
8c63b46
remove slack id from guest so page is hit
wavehassman Jul 28, 2026
c1b1c29
rename file names, update contact widget to have slack
wavehassman Jul 28, 2026
426c10f
#4126 slack integration
wavehassman Jul 29, 2026
e7b400a
more seed data
wavehassman Jul 30, 2026
813a969
UI fixes
wavehassman Jul 30, 2026
2447e70
approving a request makes guest a member
wavehassman Jul 30, 2026
2ec5651
requested changes from meeting
wavehassman Aug 2, 2026
3594fcd
more UI fixes
wavehassman Aug 5, 2026
2c9a702
make timeline scrollable
wavehassman Aug 6, 2026
3583d54
revert manual.ts
wavehassman Aug 6, 2026
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
15 changes: 13 additions & 2 deletions src/backend/src/controllers/calendar.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,14 @@ export default class CalendarController {

static async createCalendar(req: Request, res: Response, next: NextFunction) {
try {
const { name, description, colorHexCode } = req.body;
const { name, description, colorHexCode, isNewMemberCalendar } = req.body;

const calendar = await CalendarService.createCalendar(
req.currentUser,
name,
description,
colorHexCode,
isNewMemberCalendar,
req.organization
);

Expand All @@ -158,14 +159,15 @@ export default class CalendarController {
static async editCalendar(req: Request, res: Response, next: NextFunction) {
try {
const { calendarId } = req.params as Record<string, string>;
const { name, colorHexCode, description } = req.body;
const { name, colorHexCode, description, isNewMemberCalendar } = req.body;

const updatedCalendar = await CalendarService.editCalendar(
req.currentUser,
calendarId,
name,
description,
colorHexCode,
isNewMemberCalendar,
req.organization
);

Expand All @@ -175,6 +177,15 @@ export default class CalendarController {
}
}

static async getNewMemberEvents(req: Request, res: Response, next: NextFunction) {
try {
const events = await CalendarService.getNewMemberEvents(req.organization);
res.status(200).json(events);
} catch (error: unknown) {
next(error);
}
}

static async deleteCalendar(req: Request, res: Response, next: NextFunction) {
try {
const { calendarId } = req.params as Record<string, string>;
Expand Down
25 changes: 0 additions & 25 deletions src/backend/src/controllers/organizations.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,31 +125,6 @@ export default class OrganizationsController {
}
}

static async setNewMemberImage(req: Request, res: Response, next: NextFunction) {
try {
if (!req.file) {
throw new HttpException(400, 'Invalid or undefined image data');
}

const updatedOrg = await OrganizationsService.setNewMemberImage(req.file, req.currentUser, req.organization);

res.status(200).json(updatedOrg);
} catch (error: unknown) {
next(error);
}
}

static async getOrganizationNewMemberImage(req: Request, res: Response, next: NextFunction) {
try {
const { organization } = req;

const newMemberImageId = await OrganizationsService.getNewMemberImage(organization.organizationId);
res.status(200).json(newMemberImageId);
} catch (error: unknown) {
next(error);
}
}

static async setOrganizationDescription(req: Request, res: Response, next: NextFunction) {
try {
const updatedOrg = await OrganizationsService.setOrganizationDescription(
Expand Down
11 changes: 10 additions & 1 deletion src/backend/src/controllers/part-review.controllers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextFunction, Request, Response } from 'express';
import PartReviewService from '../services/part-review.services.js';
import RecruitmentServices from '../services/recruitment.services.js';
import { WbsNumber, validateWBS } from 'shared';
import { HttpException } from '../utils/errors.utils.js';

Expand Down Expand Up @@ -255,7 +256,15 @@ export default class PartReviewController {
static async createFaq(req: Request, res: Response, next: NextFunction) {
try {
const { question, answer } = req.body;
const faq = await PartReviewService.createFaq(question, answer, req.currentUser, req.organization.organizationId);
const faq = await RecruitmentServices.createOrganizationFaq(
req.currentUser,
question,
answer,
req.organization,
false,
false,
true
);
res.status(200).json(faq);
} catch (error: unknown) {
next(error);
Expand Down
17 changes: 14 additions & 3 deletions src/backend/src/controllers/projects.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,17 @@ export default class ProjectsController {

static async createLinkType(req: Request, res: Response, next: NextFunction) {
try {
const { name, iconName, required, isOnGuestHomePage } = req.body;
const { name, iconName, required, isOnGuestHomePage, isOnNewMemberDashboard, isOnOnboardingDashboard } = req.body;

const newLinkType = await ProjectsService.createLinkType(
req.currentUser,
name,
iconName,
required,
req.organization,
isOnGuestHomePage
isOnGuestHomePage,
isOnNewMemberDashboard,
isOnOnboardingDashboard
);
res.status(200).json(newLinkType);
} catch (error: unknown) {
Expand Down Expand Up @@ -469,14 +471,23 @@ export default class ProjectsController {
static async editLinkType(req: Request, res: Response, next: NextFunction) {
try {
const { linkTypeName } = req.params as Record<string, string>;
const { name: newName, iconName, required, isOnGuestHomePage } = req.body;
const {
name: newName,
iconName,
required,
isOnGuestHomePage,
isOnNewMemberDashboard,
isOnOnboardingDashboard
} = req.body;
const linkTypeUpdated = await ProjectsService.editLinkType(
linkTypeName,
iconName,
required,
req.currentUser,
req.organization,
isOnGuestHomePage,
isOnNewMemberDashboard,
isOnOnboardingDashboard,
newName
);
res.status(200).json(linkTypeUpdated);
Expand Down
74 changes: 70 additions & 4 deletions src/backend/src/controllers/recruitment.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,34 @@ export default class RecruitmentController {
}
}

static async getNewMemberMilestones(req: Request, res: Response, next: NextFunction) {
try {
const newMemberMilestones = await RecruitmentServices.getNewMemberMilestones(req.organization);
res.status(200).json(newMemberMilestones);
} catch (error: unknown) {
next(error);
}
}

static async getRecruitingMilestones(req: Request, res: Response, next: NextFunction) {
try {
const recruitingMilestones = await RecruitmentServices.getRecruitingMilestones(req.organization);
res.status(200).json(recruitingMilestones);
} catch (error: unknown) {
next(error);
}
}

static async createMilestone(req: Request, res: Response, next: NextFunction) {
try {
const { name, description, dateOfEvent } = req.body;
const { name, description, dateOfEvent, isOnNewMemberDashboard, isOnRecruitingDashboard } = req.body;

const milestone = await RecruitmentServices.createMilestone(
req.currentUser,
name,
description,
dateOfEvent,
{ isOnNewMemberDashboard, isOnRecruitingDashboard },
req.organization
);
res.status(200).json(milestone);
Expand Down Expand Up @@ -57,19 +76,66 @@ export default class RecruitmentController {
}
}

// TODO rename this method throughout stack
// Changed scope of getAllOrganizationFaqs to include part review, so what this call really wants is
// recruiting FAQs, but I'll change this as part of actual work not schema changes
static async getAllOrganizationFaqs(req: Request, res: Response, next: NextFunction) {
try {
const allFaqs = await RecruitmentServices.getAllOrganizationFaqs(req.organization);
const allFaqs = await RecruitmentServices.getRecruitingFaqs(req.organization);
res.status(200).json(allFaqs);
} catch (error: unknown) {
next(error);
}
}

static async createOrganizationFaq(req: Request, res: Response, next: NextFunction) {
static async getRecruitingFaqs(req: Request, res: Response, next: NextFunction) {
try {
const faqs = await RecruitmentServices.getRecruitingFaqs(req.organization);
res.status(200).json(faqs);
} catch (error: unknown) {
next(error);
}
}

static async getNewMemberFaqs(req: Request, res: Response, next: NextFunction) {
try {
const faqs = await RecruitmentServices.getNewMemberFaqs(req.organization);
res.status(200).json(faqs);
} catch (error: unknown) {
next(error);
}
}

static async createRecruitingFaq(req: Request, res: Response, next: NextFunction) {
try {
const { question, answer } = req.body;
const faq = await RecruitmentServices.createOrganizationFaq(req.currentUser, question, answer, req.organization);
const faq = await RecruitmentServices.createOrganizationFaq(
req.currentUser,
question,
answer,
req.organization,
true,
false,
false
);
res.status(200).json(faq);
} catch (error: unknown) {
next(error);
}
}

static async createNewMemberFaq(req: Request, res: Response, next: NextFunction) {
try {
const { question, answer } = req.body;
const faq = await RecruitmentServices.createOrganizationFaq(
req.currentUser,
question,
answer,
req.organization,
false,
true,
false
);
res.status(200).json(faq);
} catch (error: unknown) {
next(error);
Expand Down
48 changes: 47 additions & 1 deletion src/backend/src/controllers/slack.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import OrganizationsService from '../services/organizations.services.js';
import SlackServices, {
SlackBlockActionBody,
SaboSubmissionActionValue,
CrApprovalActionValue
CrApprovalActionValue,
TeamJoinRequestApprovalActionValue
} from '../services/slack.services.js';
import { tryParseJson } from '../utils/slack.utils.js';

Expand Down Expand Up @@ -129,4 +130,49 @@ export default class SlackController {
throw error;
}
}

/**
* Handles the Slack block action for approving a team join request.
* Unlike handleApproveCRAction, all error reporting goes through respond() rather than
* replyToMessageInThread -- team join request notifications are sent as fresh (non-threaded)
* ephemerals, so there's no reliable message thread to reply into.
*
* @param body The validated Slack block action body (general structure validated in routes)
* @param respond Bolt response callback bound to this interaction's response_url
*/
static async handleApproveTeamJoinRequestAction(
body: SlackBlockActionBody,
respond: (msg: {
response_type?: 'ephemeral';
text?: string;
replace_original?: boolean;
delete_original?: boolean;
}) => Promise<unknown>
) {
const { user, actions } = body;
const [firstAction] = actions;

const parsed = tryParseJson<TeamJoinRequestApprovalActionValue>(firstAction.value);
if (!parsed.ok) {
await respond({
response_type: 'ephemeral',
text: `❌ An error occurred: Invalid action data format.\n\n*Error:* ${parsed.error}`
});
return;
}
const actionValue = parsed.data;

if (!actionValue.teamJoinRequestId || typeof actionValue.teamJoinRequestId !== 'string') {
await respond({
response_type: 'ephemeral',
text: `❌ An error occurred: Missing or invalid team join request ID.`
});
return;
}

const userSlackId = user.id;
const { teamJoinRequestId } = actionValue;

await SlackServices.handleApproveTeamJoinRequestAction(userSlackId, teamJoinRequestId, respond);
}
}
49 changes: 49 additions & 0 deletions src/backend/src/controllers/teams.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,55 @@ export default class TeamsController {
}
}

static async createTeamJoinRequest(req: Request, res: Response, next: NextFunction) {
try {
const { teamId } = req.params as Record<string, string>;

const request = await TeamsService.createTeamJoinRequest(req.currentUser, teamId, req.organization);
res.status(200).json(request);
} catch (error: unknown) {
next(error);
}
}

static async getMyTeamJoinRequests(req: Request, res: Response, next: NextFunction) {
try {
const requests = await TeamsService.getMyTeamJoinRequests(req.currentUser, req.organization);
res.status(200).json(requests);
} catch (error: unknown) {
next(error);
}
}

static async getPendingTeamJoinRequests(req: Request, res: Response, next: NextFunction) {
try {
const { teamId } = req.params as Record<string, string>;

const requests = await TeamsService.getPendingTeamJoinRequests(teamId, req.currentUser, req.organization);
res.status(200).json(requests);
} catch (error: unknown) {
next(error);
}
}

static async reviewTeamJoinRequest(req: Request, res: Response, next: NextFunction) {
try {
const { teamJoinRequestId } = req.params as Record<string, string>;
const { approved, denialReason } = req.body;

const request = await TeamsService.reviewTeamJoinRequest(
req.currentUser,
teamJoinRequestId,
approved,
denialReason,
req.organization
);
res.status(200).json(request);
} catch (error: unknown) {
next(error);
}
}

static async deleteTeam(req: Request, res: Response, next: NextFunction) {
try {
const { teamId } = req.params as Record<string, string>;
Expand Down
Loading
Loading