Skip to content
16 changes: 16 additions & 0 deletions backend/src/controllers/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
EmailTemplateSize,
FrontendSettings,
Settings,
ProjectSettings,
} from "../entities/settings";
import { User } from "../entities/user";
import { UserRole } from "../entities/user-role";
Expand Down Expand Up @@ -91,6 +92,17 @@ export class ApplicationSettingsDTO implements DTO<ApplicationSettings> {
@IsNumber()
@Expose()
public hoursToConfirm!: number;
Comment thread
sezanzeb marked this conversation as resolved.
@Type(() => Date)
@IsDate()
@Expose()
public acceptanceDeadline!: Date;
@Type(() => Date)
@IsDate()
@Expose()
public confirmSpotUntil!: Date;
}

export class ProjectSettingsDTO implements DTO<ProjectSettings> {
@IsBoolean()
@Expose()
public allowRatingProjects!: boolean;
Expand Down Expand Up @@ -166,6 +178,10 @@ export class SettingsDTO implements DTO<Omit<Settings, "updatedAt">> {
@ValidateNested()
@Expose()
public email!: EmailSettingsDTO;
@Type(() => ProjectSettingsDTO)
@ValidateNested()
@Expose()
public project!: ProjectSettingsDTO;
}

export abstract class QuestionConfigurationDTOBase {
Expand Down
9 changes: 7 additions & 2 deletions backend/src/entities/application-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { FormSettings } from "./form-settings";
// TODO all other settings are part of the settings table, whereas ApplicationSettings
// is a separate table. Move into settings table just like EmailSettings.

/**
* Application as in "Peoples application for the event"
*/
@Entity()
export class ApplicationSettings {
@PrimaryGeneratedColumn()
Expand All @@ -29,6 +32,8 @@ export class ApplicationSettings {
public allowProfileFormUntil!: Date;
@Column()
public hoursToConfirm!: number;
@Column({ default: false })
public allowRatingProjects!: boolean;
@Column()
public acceptanceDeadline!: Date;
@Column()
public confirmSpotUntil!: Date;
Comment thread
sezanzeb marked this conversation as resolved.
Outdated
}
8 changes: 8 additions & 0 deletions backend/src/entities/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export class EmailSettings {
public forgotPasswordEmail!: EmailTemplate;
}

export class ProjectSettings {
@Column({ default: false })
public allowRatingProjects!: boolean;
}

@Entity()
export class Settings {
@PrimaryGeneratedColumn()
Expand All @@ -64,6 +69,9 @@ export class Settings {
@Type(() => EmailSettings)
@Column(() => EmailSettings)
public email!: EmailSettings;
@Type(() => ProjectSettings)
@Column(() => ProjectSettings)
public project!: ProjectSettings;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion backend/src/services/project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class ProjectService implements IProjectService {
.map((team) => team.id);

const [settings] = await this._settings.find();
const allowRatingProjects = settings.application.allowRatingProjects;
const allowRatingProjects = settings.project.allowRatingProjects;
Comment thread
sezanzeb marked this conversation as resolved.
const isAdmin = user.role === UserRole.Root;

const projects = await this._projects.find();
Expand Down
6 changes: 2 additions & 4 deletions backend/src/services/rating-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,8 @@ export class RatingService implements IRatingService {
}

const settings = await this._settings.getSettings();
if (!settings.application.allowRatingProjects) {
throw new ForbiddenError(
"Rating is not allowed due to application settings",
);
if (!settings.project.allowRatingProjects) {
Comment thread
sezanzeb marked this conversation as resolved.
Comment thread
sezanzeb marked this conversation as resolved.
throw new ForbiddenError("Rating is not allowed due to settings");
}

const project = await this._projects.findOneBy({ id: rating.project.id });
Expand Down
14 changes: 13 additions & 1 deletion backend/src/services/settings-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
EmailTemplate,
FrontendSettings,
Settings,
ProjectSettings,
} from "../entities/settings";
import {
ConfigurationServiceToken,
Expand Down Expand Up @@ -102,6 +103,7 @@ export class SettingsService implements ISettingsService {
settings.application = this.getDefaultApplicationSettings();
settings.frontend = this.getDefaultFrontendSettings();
settings.email = this.getDefaultEmailSettings();
settings.project = this.getDefaultProjectSettings();
return settings;
}

Expand All @@ -115,10 +117,20 @@ export class SettingsService implements ISettingsService {
applicationSettings.allowProfileFormFrom = new Date();
applicationSettings.allowProfileFormUntil = new Date();
applicationSettings.hoursToConfirm = 24;
applicationSettings.allowRatingProjects = false;
applicationSettings.acceptanceDeadline = new Date();
applicationSettings.confirmSpotUntil = new Date();
return applicationSettings;
}

/**
* Creates an application settings object with default values.
*/
private getDefaultProjectSettings(): ProjectSettings {
const projectSettings = new ProjectSettings();
projectSettings.allowRatingProjects = false;
return projectSettings;
}
Comment thread
sezanzeb marked this conversation as resolved.
Comment thread
sezanzeb marked this conversation as resolved.

/**
* Creates a form settings object with default values.
*/
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ export class ApiClient {
allowProfileFormUntil: this.reviveDate(
settings.application.allowProfileFormUntil,
),
acceptanceDeadline: this.reviveDate(
settings.application.acceptanceDeadline,
),
confirmSpotUntil: this.reviveDate(
settings.application.confirmSpotUntil,
),
},
};
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/pages/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Heading } from "../base/headings";
import { ApplicationSettings } from "../settings/application/application-settings";
import { EmailSettings } from "../settings/mail-settings/email-settings";
import { FrontendSettings } from "../settings/appearance/frontend-settings";
import { ProjectRatingSettings } from "../settings/project-rating/rating-criteria-settings";
import { ProjectProjectSettings } from "../settings/project-rating/rating-criteria-settings";
import { SettingsSaveButton } from "../settings/save-button";
Comment thread
sezanzeb marked this conversation as resolved.
import { Page } from "./page";
import { SimpleCard } from "../base/simple-card";
Expand Down Expand Up @@ -40,7 +40,7 @@ export const Settings = () => (
<EmailSettings />
</SimpleCard>
<SimpleCard>
<ProjectRatingSettings />
<ProjectProjectSettings />
</SimpleCard>
Comment thread
sezanzeb marked this conversation as resolved.
</Page>
);
21 changes: 18 additions & 3 deletions frontend/src/components/pages/status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ export const Status = () => {
const { user, updateUser } = useLoginContext();

const confirmationDays = Math.floor(settings.application.hoursToConfirm / 24);

const allowProfileFormFrom = dateToString(
settings.application.allowProfileFormFrom,
);
const allowProfileFormUntil = dateToString(
settings.application.allowProfileFormUntil,
);
const acceptanceDeadline = dateToString(
settings.application.acceptanceDeadline,
);
const confirmSpotUntil = dateToString(settings.application.confirmSpotUntil);

const isExpired = user == null ? false : isConfirmationExpired(user);
const isNotAttending = isExpired || user?.declined;
const deadline = user?.confirmationExpiresAt;
Comment thread
sezanzeb marked this conversation as resolved.
Expand Down Expand Up @@ -98,7 +110,10 @@ export const Status = () => {
<InternalLink to={Routes.ProfileForm}>
profile form
</InternalLink>
, any time between <b>01.03.2026 - 31.04.2026</b>
, any time between{" "}
<b>
{allowProfileFormFrom} - {allowProfileFormUntil}
</b>
</Text>
Comment thread
sezanzeb marked this conversation as resolved.
</>
)}
Expand Down Expand Up @@ -145,7 +160,7 @@ export const Status = () => {
<>
<Text style={{ fontSize: "1.15rem" }}>
We will come back to you and send you a acceptance mail until{" "}
<b>01.05.2026</b>.
<b>{acceptanceDeadline}</b>.
</Text>
</>
)}
Expand Down Expand Up @@ -173,7 +188,7 @@ export const Status = () => {
<>
<Text style={{ fontSize: "1.15rem" }}>
If you got accepted, you need to confirm your spot until{" "}
<b>08.05.2026</b>
<b>{confirmSpotUntil}</b>
{user?.admitted && (
<>
{" "}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Dispatch, SetStateAction } from "react";
import * as React from "react";
import { useCallback } from "react";
import type { ApplicationSettingsDTO } from "../../../api/types/dto";
Expand Down Expand Up @@ -40,38 +41,32 @@ export const ApplicationSettings = () => {
[settings],
);

const handleAllowProfileFormFromChange = useCallback(
(value: string) => {
setAllowProfileFormFrom(value);
const date = new Date(value);

if (!isValidDate(date)) {
return;
}
const [allowProfileFormUntil, setAllowProfileFormUntil] = useDerivedState(
() => settings.application.allowProfileFormUntil.toISOString(),
[settings],
);

updateApplicationSettings({
allowProfileFormFrom: date,
});
},
[updateApplicationSettings],
const [acceptanceDeadline, setAcceptanceDeadline] = useDerivedState(
() => settings.application.acceptanceDeadline.toISOString(),
[settings],
);

const [allowProfileFormUntil, setAllowProfileFormUntil] = useDerivedState(
() => settings.application.allowProfileFormUntil.toISOString(),
const [confirmSpotUntil, setConfirmSpotUntil] = useDerivedState(
() => settings.application.confirmSpotUntil.toISOString(),
[settings],
);

const handleAllowProfileFormUntilChange = useCallback(
(value: string) => {
setAllowProfileFormUntil(value);
const handleDateChange = useCallback(
(value: string, setter: Dispatch<SetStateAction<string>>, key: string) => {
setter(value);
const date = new Date(value);

if (!isValidDate(date)) {
return;
}

updateApplicationSettings({
allowProfileFormUntil: date,
[key]: date,
});
Comment thread
sezanzeb marked this conversation as resolved.
},
[updateApplicationSettings],
Expand Down Expand Up @@ -112,11 +107,18 @@ export const ApplicationSettings = () => {
placeholder="keep it fair, e.g. 240 for 10 days"
/>
</FlexRowColumnContainer>
<Spacer />
</FlexRowContainer>
<FlexRowContainer>
<FlexRowColumnContainer>
<TextInput
value={allowProfileFormFrom}
onChange={handleAllowProfileFormFromChange}
onChange={(value) =>
handleDateChange(
value,
setAllowProfileFormFrom,
"allowProfileFormFrom",
)
}
title="Open registration on"
placeholder="1970-01-01 00:00:00"
/>
Expand All @@ -125,12 +127,45 @@ export const ApplicationSettings = () => {
<FlexRowColumnContainer>
<TextInput
value={allowProfileFormUntil}
onChange={handleAllowProfileFormUntilChange}
onChange={(value) =>
handleDateChange(
value,
setAllowProfileFormUntil,
"allowProfileFormUntil",
)
}
title="Close registration on"
placeholder="1970-01-01 00:00:00"
/>
</FlexRowColumnContainer>
</FlexRowContainer>
<FlexRowContainer>
<FlexRowColumnContainer>
<TextInput
value={acceptanceDeadline}
onChange={(value) =>
handleDateChange(
value,
setAcceptanceDeadline,
"acceptanceDeadline",
)
}
title="When we will accept people"
placeholder="1970-01-01 00:00:00"
/>
</FlexRowColumnContainer>
<Spacer />
<FlexRowColumnContainer>
<TextInput
value={confirmSpotUntil}
onChange={(value) =>
handleDateChange(value, setConfirmSpotUntil, "confirmSpotUntil")
}
title="Until when accepted people need to confirm their spot"
placeholder="1970-01-01 00:00:00"
/>
</FlexRowColumnContainer>
</FlexRowContainer>

<Text>
Use the add button to add new questions and the edit button in the top
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const CriterionEditor = React.memo(
/**
* A component to edit criteria for rating projects.
*/
export const ProjectRatingSettings = () => {
export const ProjectProjectSettings = () => {
Comment thread
sezanzeb marked this conversation as resolved.
Outdated
Comment thread
sezanzeb marked this conversation as resolved.
Outdated
// Load all criteria and render them
const [allCriteria, setAllCriteria] = useState<CriterionDTO[]>([]);
const [settings, setSettings] = useState<Partial<SettingsDTO>>({});
Expand All @@ -91,7 +91,7 @@ export const ProjectRatingSettings = () => {

useEffect(() => {
// Only update if settings are loaded
if (settings.application) {
if (settings.project) {
api.updateSettings(settings as SettingsDTO);
}
}, [settings]);
Comment thread
sezanzeb marked this conversation as resolved.
Expand Down Expand Up @@ -138,8 +138,8 @@ export const ProjectRatingSettings = () => {
setSettings((prev) => {
const changedSettings = {
...prev,
application: {
...prev.application,
project: {
...prev.project,
allowRatingProjects: value,
},
Comment thread
sezanzeb marked this conversation as resolved.
};
Expand All @@ -155,7 +155,7 @@ export const ProjectRatingSettings = () => {
<FormControlLabel
control={
<Switch
checked={settings?.application?.allowRatingProjects}
checked={settings?.project?.allowRatingProjects}
Comment thread
sezanzeb marked this conversation as resolved.
Outdated
Comment thread
sezanzeb marked this conversation as resolved.
Outdated
onChange={onSwitchChange}
/>
}
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ export const sleep = (ms: number) =>
* Formats a date "YYYY-MM-DD on HH:mm:ss" style.
* @param date The date to format
Comment thread
sezanzeb marked this conversation as resolved.
*/
export const dateToString = (date: Date) => {
export const dateToString = (date: Date | null) => {
if (date == null) {
return "?";
}
Comment thread
sezanzeb marked this conversation as resolved.
Outdated

return date.toLocaleString("default", {
year: "numeric",
month: "long",
Expand Down
Loading