Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
aa5c857
Add linkText and linkUrl to annoucements schema
SierraTran Oct 16, 2025
976ba51
Add linkText and linkUrl to createAnnouncement and updateAnnouncement…
SierraTran Oct 16, 2025
d28fc9d
Add linkText and linkUrl to createAnnouncement and updateAnnouncement…
SierraTran Oct 16, 2025
04d3b82
Add the announcement card as a preview and fields for link button tex…
SierraTran Oct 16, 2025
7f4f135
Add link button in card actions if the announcement has one
SierraTran Oct 16, 2025
4bd0a4b
Migration for new linkText and linkUrl columns in Announcements table
SierraTran Oct 16, 2025
c4ed1af
Add new hasLInk column to announcements table and mutations
SierraTran Oct 23, 2025
e688a0b
Add link columns to announcement queries
SierraTran Oct 23, 2025
af71e7b
Utilize hasLInk column for displaying the link button
SierraTran Oct 23, 2025
feb0cc1
Add link columns to variables for mutation
SierraTran Oct 23, 2025
7110fe5
Enhance announcement forms with link functionality and validation
SierraTran Oct 24, 2025
586c68b
Merge branch 'main' into SierraTran/announcement-preview-and-link-button
SierraTran Oct 24, 2025
01b86fd
Merge branch 'main' into SierraTran/announcement-preview-and-link-button
SierraTran Oct 28, 2025
adcd6ce
Remove hasLink column and use local boolean instead
SierraTran Oct 28, 2025
64e5ce4
Add useEffect to set showLinkFields accurately
SierraTran Nov 3, 2025
a3bea2f
Merge branch 'main' into SierraTran/announcement-preview-and-link-button
SierraTran Nov 10, 2025
218de7e
Merge branch 'main' into SierraTran/announcement-preview-and-link-button
SierraTran Nov 10, 2025
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
42 changes: 30 additions & 12 deletions client/src/pages/both/homepage/AnnouncementCard.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
import { Card, CardContent, Typography, useTheme } from "@mui/material";
import { Button, Card, CardActions, CardContent, Typography, useTheme } from "@mui/material";
import { Announcement } from "../../../queries/announcementsQueries";
import ThemedMarkdown from "../../../common/ThemedMarkdown";

interface AnnouncementCardProps {
announcement: Announcement;
announcement: Announcement;
}

export default function AnnouncementCard(props: AnnouncementCardProps) {
const theme = useTheme();
return (
<Card sx={{ height: "100%" }}>
<CardContent>
<Typography color={theme.palette.primary.main} variant="h5">{props.announcement.title}</Typography>
<Typography variant="body1"><ThemedMarkdown>{props.announcement.description}</ThemedMarkdown></Typography>
</CardContent>
</Card>
);
}
const theme = useTheme();
return (
<Card sx={{ height: "100%" }}>
<CardContent>
<Typography color={theme.palette.primary.main} variant="h5">
{props.announcement.title}
</Typography>
<Typography variant="body1">
<ThemedMarkdown>{props.announcement.description}</ThemedMarkdown>
</Typography>
</CardContent>
<CardActions sx={{ justifyContent: 'flex-end' }}>
{props.announcement.linkUrl ? (
<Button
variant="contained"
color="info"
size="small"
href={props.announcement.linkUrl}
target="_blank"
rel="noopener noreferrer"
>
{props.announcement.linkText}
</Button>
) : null}
</CardActions>
</Card>
);
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { ChangeEvent, useState } from "react";
import {
Button,
Stack,
TextField,
Typography,
} from "@mui/material";
import React, { ChangeEvent, useState } from "react";
import { Button, FormControlLabel, Grid, Stack, Switch, TextField, Typography } from "@mui/material";
import SaveIcon from "@mui/icons-material/Save";
import { Announcement } from "../../../queries/announcementsQueries";
import { useNavigate } from "react-router-dom";
import DeleteAnnouncementButton from "./button/DeleteAnnouncementButton";
import AnnouncementCard from "../../both/homepage/AnnouncementCard.js";
import { toast } from "react-toastify";

interface InputErrors {
title?: boolean;
description?: boolean;
linkText?: boolean;
linkUrl?: boolean;
}

interface AnnouncementPageProps {
Expand All @@ -32,44 +31,73 @@ export default function AnnouncementModalContents({
onDelete,
loading,
}: AnnouncementPageProps) {

const navigate = useNavigate();


const [showLinkFields, setShowLinkFields] = useState(!!announcementDraft.linkUrl || !!announcementDraft.linkText);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When testing this, it seems like the default value of this isnt getting calculated just right. If i add a link and link text to an announcement, then save and close it, then reopen it, the Link Button switch is set to off. If i turn it on manually it shows the saved fields just fine though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recently pushed a commit (64e5ce4) to remedy this. The useEffect looks clunky to me but I could be overthinking it.

const [inputErrors, setInputErrors] = useState<InputErrors>({});

const handleStringChange =
(property: keyof Announcement) =>
(e: ChangeEvent<HTMLInputElement>) =>
setAnnouncementDraft({ ...announcementDraft, [property]: e.target.value });
const handleStringChange = (property: keyof Announcement) => (e: ChangeEvent<HTMLInputElement>) =>
setAnnouncementDraft({ ...announcementDraft, [property]: e.target.value });

const handleHasLinkSwitch = (e: ChangeEvent<HTMLInputElement>) => {
const checked = e.target.checked;
setShowLinkFields(checked);
if (!checked) {
setAnnouncementDraft({
...announcementDraft,
linkText: "",
linkUrl: "",
});
}
};

const normalizeUrl = (url: string) => {
if (!url) return "";
return /^https?:\/\//.test(url) ? url : "https://" + url;
};

const handleUrlBlur = (e: React.FocusEvent<HTMLInputElement>) => {
const raw = e.target.value.trim();
const normalized = normalizeUrl(raw);

setAnnouncementDraft({ ...announcementDraft, linkUrl: normalized });
};

const handleSaveClick = async () => {
const updatedInputErrors: InputErrors = {
title: !announcementDraft.title,
description: !announcementDraft.description,
linkText: showLinkFields && !announcementDraft.linkText,
linkUrl: showLinkFields && !announcementDraft.linkUrl,
};

setInputErrors(updatedInputErrors);

const hasInputErrors = Object.values(updatedInputErrors).some((e) => e);
if (hasInputErrors) return;

await onSave();

navigate("/admin/announcements");
try {
await onSave();
navigate("/admin/announcements");
} catch (error) {
toast.error("Failed to save announcement. " + (error instanceof Error ? error.message : ""));
}
};

const handleDeleteClick = async () => {
await onDelete()
navigate("/admin/announcements");
}
try {
await onDelete();
navigate("/admin/announcements");
} catch (error) {
toast.error("Failed to delete announcement." + (error instanceof Error ? error.message : ""));
}
};

const title = `${isNewAnnouncement ? "New" : "Edit"} Announcement`;

return (
<Stack padding="25px" spacing={2}>
<Typography variant="h5">
{title}
</Typography>
<Typography variant="h5">{title}</Typography>
<Stack direction="row" spacing={2}>
<Stack spacing={2} flexGrow={1}>
<TextField
Expand All @@ -90,15 +118,40 @@ export default function AnnouncementModalContents({
minRows={3}
/>
</Stack>
<FormControlLabel
control={<Switch checked={showLinkFields} onChange={handleHasLinkSwitch} />}
label={<b>Link Button</b>}
labelPlacement="top"
/>
{showLinkFields && (
<>
<TextField
label="Link Text"
type="string"
value={announcementDraft.linkText ?? ""}
error={inputErrors.linkText}
onChange={handleStringChange("linkText")}
required
/>
<TextField
helperText="Please enter a valid URL, including 'http://' or 'https://'. If you leave it out, we'll assume 'https://'."
label="Link URL"
type="url"
value={announcementDraft.linkUrl ?? ""}
error={inputErrors.linkUrl}
onChange={handleStringChange("linkUrl")}
onBlur={handleUrlBlur}
required
/>
</>
)}
</Stack>
</Stack>

<Stack direction="row" justifyContent="flex-end" spacing={2}>
{!isNewAnnouncement && (
<Stack direction="row" spacing={2}>
<DeleteAnnouncementButton
onDelete={handleDeleteClick}
/>
<DeleteAnnouncementButton onDelete={handleDeleteClick} />
</Stack>
)}

Expand All @@ -114,6 +167,11 @@ export default function AnnouncementModalContents({
Save
</Button>
</Stack>
<Grid margin="10px" display="flex" justifyContent="center" alignItems="center">
<Grid width="400px">
<AnnouncementCard announcement={announcementDraft} />
</Grid>
</Grid>
</Stack>
);
}
21 changes: 10 additions & 11 deletions client/src/pages/lab_management/announcements/EditAnnouncement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { useParams } from "react-router-dom";


export default function EditAnnouncement() {

const { id } = useParams<{ id: string }>();

const [announcementDraft, setAnnouncementDraft] = useState<Partial<Announcement>>({});
Expand All @@ -17,19 +16,20 @@ export default function EditAnnouncement() {
});

const [updateAnnouncement, mutation] = useMutation(UPDATE_ANNOUNCEMENT, {
variables: { id: id, title: announcementDraft.title, description: announcementDraft.description },
refetchQueries: [
{ query: GET_ANNOUNCEMENTS },
{ query: GET_ANNOUNCEMENT, variables: { id } },
],
variables: {
id: id,
title: announcementDraft.title,
description: announcementDraft.description,
linkText: announcementDraft.linkText,
linkUrl: announcementDraft.linkUrl,
},
refetchQueries: [{ query: GET_ANNOUNCEMENTS }, { query: GET_ANNOUNCEMENT, variables: { id } }],
});

const [deleteAnnouncement, del] = useMutation(DELETE_ANNOUNCEMENT, {
variables: { id: id },
refetchQueries: [
{ query: GET_ANNOUNCEMENTS }
],
})
refetchQueries: [{ query: GET_ANNOUNCEMENTS }],
});

// After the item has been fetched, fill in the draft
useEffect(() => {
Expand All @@ -40,7 +40,6 @@ export default function EditAnnouncement() {
});
}, [query.data, setAnnouncementDraft]);


return (
<RequestWrapper loading={query.loading || del.loading} error={query.error || del.error} minHeight={322}>
<>
Expand Down
Loading
Loading