Skip to content
Snippets Groups Projects
Commit 253c2dde authored by Alexander Schoch's avatar Alexander Schoch
Browse files

Merge branch 'dev' into 'main'

Version 1.0

See merge request vseth/0500-kom/0522-thealt/website!1
parents d2260237 d5ebc53c
No related branches found
No related tags found
No related merge requests found
Showing
with 4780 additions and 47 deletions
SIP_AUTH_PAPPERLAWEB_CLIENT_ID=pub_staging_website_website
SIP_AUTH_PAPPERLAWEB_CLIENT_SECRET=MzrytATFFEZkTHmNjoMGFNbor
SIP_AUTH_OIDC_ISSUER=https://auth.vseth.ethz.ch/auth/realms/VSETH
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=asdf
{ {
"extends": "next/core-web-vitals" "extends": "next/core-web-vitals",
"rules": {
"react/no-unescaped-entities": "off"
}
} }
...@@ -26,6 +26,7 @@ yarn-error.log* ...@@ -26,6 +26,7 @@ yarn-error.log*
# local env files # local env files
.env*.local .env*.local
.env
# vercel # vercel
.vercel .vercel
......
...@@ -16,6 +16,10 @@ programs: ...@@ -16,6 +16,10 @@ programs:
- SIP_AUTH_OIDC_ISSUER: - SIP_AUTH_OIDC_ISSUER:
- SIP_AUTH_WEBSITE_CLIENT_ID: - SIP_AUTH_WEBSITE_CLIENT_ID:
- SIP_AUTH_WEBSITE_CLIENT_SECRET: - SIP_AUTH_WEBSITE_CLIENT_SECRET:
- MAILER_USERNAME:
- MAILER_PASSWORD:
- MAILER_HOST:
- MAILER_NAME:
- name: 'Update DB Schema' - name: 'Update DB Schema'
workdir: '/website' workdir: '/website'
path: 'bash' path: 'bash'
...@@ -30,4 +34,4 @@ programs: ...@@ -30,4 +34,4 @@ programs:
- SIP_MYSQL_ALT_SERVER: - SIP_MYSQL_ALT_SERVER:
- SIP_MYSQL_ALT_PORT: - SIP_MYSQL_ALT_PORT:
- NEXTAUTH_URL: - NEXTAUTH_URL:
- NEXTAUTH_SECRET: - NEXTAUTH_SECRET:
\ No newline at end of file
File deleted
import dynamic from "next/dynamic";
const OfficeMap = dynamic(() => import("../components/map"), {
ssr: false,
});
import Image from "next/image";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import parse from "html-react-parser";
import { Alert, Button, Card, Grid, Space } from "@mantine/core";
import { Icon, ICONS } from "vseth-canine-ui";
import ContactForm from "../components/contactForm";
import BoardMemberCard from "../components/boardMemberCard";
import board from "../content/board.json";
import about from "../content/about.json";
export default function About() {
const { t } = useTranslation("common");
const { locale } = useRouter();
return (
<>
<h1>{t("about")}</h1>
<Grid style={{ justifyContent: "center" }}>
{board.map((entry, i) => (
<Grid.Col
key={i}
md={3}
sm={4}
xs={6}
style={{ display: "flex", flexDirection: "column" }}
>
<BoardMemberCard entry={entry} />
</Grid.Col>
))}
</Grid>
<Space h="xl" />
<Space h="xl" />
<section id="address">
<Grid>
<Grid.Col md={4} sm={6} xs={12}>
<Alert title={t("aboutUs")}>
{about.description[locale || "en"]}
</Alert>
<Space h="md" />
<b>TheAlternative</b>
{about.address.map((line, i) => (
<p key={i} style={{ marginBottom: 0 }}>
{line}
</p>
))}
<Space h="xs" />
{/* stolen from here: https://stackoverflow.com/questions/33577448/is-there-a-way-to-do-array-join-in-react */}
{about.links
.map((link, i) => (
<a target="_blank" href={link.url} key={i}>
{link.text}
</a>
))
.reduce(
(acc, x) =>
acc === null ? (
x
) : (
<>
{acc} | {x}
</>
),
null
)}
</Grid.Col>
<Grid.Col md={4} sm={6} xs={12}>
<OfficeMap />
</Grid.Col>
<Grid.Col md={4} sm={6} xs={12}>
<ContactForm />
</Grid.Col>
</Grid>
</section>
</>
);
}
This diff is collapsed.
import { useRouter } from "next/router";
import { Avatar, Button, Paper, Text, useMantineTheme } from "@mantine/core";
import { Icon, ICONS } from "vseth-canine-ui";
import { getAccentColor } from "../utilities/colors";
export default function BoardMemberCard({ entry }) {
const theme = useMantineTheme();
const { locale, push } = useRouter();
const sendMail = (address) => {
push("mailto:" + address);
};
return (
<Paper
radius="md"
withBorder
p="lg"
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
flexGrow: 1,
}}
shadow="sm"
>
<div>
<Avatar src={entry.image} size={120} radius={20} mx="auto" />
<Text ta="center" fz="lg" weight={500} mt="md">
{entry.name}
</Text>
<Text ta="center" c="dimmed" fz="sm">
{entry.mail ? entry.mail.replace("@", " [ät] ") + "" : ""}
{entry.role[locale || "en"]}
</Text>
</div>
{entry.mail && (
<div>
<Button
leftIcon={<Icon icon={ICONS.EMAIL} color={getAccentColor(theme)} />}
variant="default"
fullWidth
mt="md"
onClick={() => sendMail(entry.mail)}
>
Send message
</Button>
</div>
)}
</Paper>
);
}
import { useState } from "react";
import { useTranslation } from "next-i18next";
import axios from "axios";
import {
Alert,
Button,
Checkbox,
Grid,
Space,
Textarea,
TextInput,
} from "@mantine/core";
import { showNotification } from "@mantine/notifications";
export default function ContactForm() {
const { t } = useTranslation("common");
const [name, setName] = useState("");
const [mail, setMail] = useState("");
const [message, setMessage] = useState("");
const [human, setHuman] = useState(false);
const [robot, setRobot] = useState(false);
const handleSubmit = async () => {
if (!validate()) {
showNotification({
title: t("formIncomplete"),
message: t("formIncompleteText"),
color: "red",
});
return;
}
const response = await axios.post("/api/contact", {
name: name,
mail: mail,
message: message,
human: human,
robot: robot,
});
if (response.data.message == "mailSuccess") {
showNotification({
title: t("mailSuccess"),
message: t("mailSuccessText"),
color: "green",
});
} else {
showNotification({
title: t("mailFailure"),
message: t("mailFailureText"),
color: "red",
});
}
};
const validate = () => {
if (name == "") return false;
if (mail == "") return false;
if (message == "") return false;
return true;
};
return (
<>
<Grid>
<Grid.Col xs={12} sm={6}>
<TextInput
value={name}
onChange={(e) => setName(e.target.value)}
label={t("name")}
withAsterisk
placeholder="Maxime Musterfrau"
/>
</Grid.Col>
<Grid.Col xs={12} sm={6}>
<TextInput
value={mail}
onChange={(e) => setMail(e.target.value)}
label={t("mail")}
withAsterisk
placeholder="maxmu@student.ethz.ch"
/>
</Grid.Col>
<Grid.Col xs={12}>
<Textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
label={t("message")}
withAsterisk
minRows={5}
/>
</Grid.Col>
<Grid.Col xs={12}>
<Checkbox
value={human}
onChange={() => setHuman(!human)}
label={t("human")}
/>
<Space h="xs" />
<Checkbox
value={robot}
onChange={() => setRobot(!robot)}
label={t("robot")}
/>
</Grid.Col>
<Grid.Col xs={12}>
<Button onClick={handleSubmit} variant="contained">
{t("submit")}
</Button>
</Grid.Col>
</Grid>
</>
);
}
import { RichTextEditor } from "@mantine/tiptap";
export default function Editor({ editor }) {
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Code />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
<RichTextEditor.H4 />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Blockquote />
<RichTextEditor.Hr />
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
import { useRouter } from "next/router";
import { useSession, signIn } from "next-auth/react";
import { useTranslation } from "next-i18next";
import {
Alert,
Button,
Card,
Group,
List,
Space,
useMantineTheme,
} from "@mantine/core";
import { Icon, ICONS } from "vseth-canine-ui";
import parse from "html-react-parser";
import hasAccess from "../utilities/hasAccess";
import { getAccentColor } from "../utilities/colors";
import { formatDateFromDB, formatTimeFromDB } from "../utilities/dates";
import { isRegistered } from "../utilities/signUp";
import { gql, useMutation } from "@apollo/client";
const addSignUpMutation = gql`
mutation addSignUp($id: Int) {
addSignUp(id: $id)
}
`;
const removeSignUpMutation = gql`
mutation removeSignUp($id: Int) {
removeSignUp(id: $id)
}
`;
export default function EventCard({
event,
setEvent,
setOpen,
refetch,
setSignUpOpen,
}) {
const [addSignUp] = useMutation(addSignUpMutation);
const [removeSignUp] = useMutation(removeSignUpMutation);
const { data: session } = useSession();
const { t } = useTranslation("common");
const theme = useMantineTheme();
const { locale } = useRouter();
const accentColor = getAccentColor(theme);
const signedUp = isRegistered(session, event);
const signUp = async (id) => {
if (!session) signIn();
await addSignUp({
variables: {
id: event.id,
},
});
refetch();
};
const signOff = async (id) => {
await removeSignUp({
variables: {
id: event.id,
},
});
refetch();
};
const editEvent = (event) => {
setEvent(event);
setOpen(true);
};
const viewSignUps = (event) => {
setEvent(event);
setSignUpOpen(true);
};
return (
<Card
shadow="md"
radius="md"
style={{
display: "flex",
justifyContent: "space-between",
flexDirection: "column",
width: "100%",
color: event.isStammtisch ? "gray" : "",
}}
withBorder
>
<div>
<Group>
<h2 style={{ margin: 0 }}>{event.title}</h2>
</Group>
{signedUp && (
<>
<Space h="xs" />
<Alert
color="green"
variant="light"
icon={<Icon icon={ICONS.CHECK} color="green" />}
title={t("signedUpTitle")}
>
{t("signedUpText")}
</Alert>
</>
)}
<Space h="lg" />
<List
spacing="md"
size="md"
center
style={{ color: event.isStammtisch ? "gray" : "" }}
>
{event.speaker && (
<List.Item
icon={<Icon icon={ICONS.USER} color="#f28a20" size={16} />}
>
<p>{event.speaker}</p>
</List.Item>
)}
<List.Item
icon={<Icon icon={ICONS.LOCATION} color="#f28a20" size={16} />}
>
<p>{event.place}</p>
</List.Item>
<List.Item
icon={<Icon icon={ICONS.CALENDAR} color="#f28a20" size={16} />}
>
<p>{formatDateFromDB(event.date, locale)}</p>
</List.Item>
<List.Item
icon={<Icon icon={ICONS.CLOCK} color="#f28a20" size={16} />}
>
<p>{formatTimeFromDB(event.startTime, event.endTime)}</p>
</List.Item>
</List>
<Space h="lg" />
<p>{parse(event.description)}</p>
</div>
<div>
{!event.isStammtisch && (
<Group style={{ verticalAlign: "middle", marginTop: "20px" }}>
{event.signUp ? (
<Button
href={event.signUp}
target="_blank"
rightIcon={<Icon icon={ICONS.RIGHT} color="#f28a20" />}
component="a"
>
{t("externalSignUp")}
</Button>
) : (
<>
{signedUp ? (
<Button
onClick={() => signOff(event.id)}
leftIcon={<Icon icon={ICONS.USER_MINUS} color="white" />}
>
{t("signOff")}
</Button>
) : (
<Button
onClick={() => signUp(event.id)}
leftIcon={<Icon icon={ICONS.USER_PLUS} color="#f28a20" />}
style={{ color: "#f28a20" }}
>
{t("signUp")}
</Button>
)}
</>
)}
</Group>
)}
<Group grow mt="md" style={{ verticalAlign: "middle" }}>
{hasAccess(session, true) && (
<>
<Button
leftIcon={<Icon icon={ICONS.EDIT} color={accentColor} />}
variant="light"
onClick={() => editEvent(event)}
>
{t("editEvent")}
</Button>
{!event.isStammtisch && (
<Button
leftIcon={<Icon icon={ICONS.LIST} color={accentColor} />}
variant="light"
onClick={() => viewSignUps(event)}
>
{t("viewParticipants")}
</Button>
)}
</>
)}
</Group>
</div>
</Card>
);
}
import { useEffect } from "react";
import { useTranslation } from "next-i18next";
import {
Button,
Checkbox,
Divider,
Grid,
Modal,
Select,
Space,
Text,
Textarea,
TextInput,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { DatePicker, TimeRangeInput } from "@mantine/dates";
import { useEditor } from "@tiptap/react";
import { Link } from "@mantine/tiptap";
import StarterKit from "@tiptap/starter-kit";
import Underline from "@tiptap/extension-underline";
import Editor from "../components/editor";
import templates from "../content/eventTemplates.json";
import { gql, useMutation } from "@apollo/client";
const addEventMutation = gql`
mutation addEvent(
$title: String
$speaker: String
$description: String
$date: DateTime
$time: [DateTime]
$place: String
$signUp: String
$isStammtisch: Boolean
) {
addEvent(
title: $title
speaker: $speaker
description: $description
date: $date
time: $time
place: $place
signUp: $signUp
isStammtisch: $isStammtisch
)
}
`;
const editEventMutation = gql`
mutation editEvent(
$id: Int
$title: String
$speaker: String
$description: String
$date: DateTime
$time: [DateTime]
$place: String
$signUp: String
$isStammtisch: Boolean
) {
editEvent(
id: $id
title: $title
speaker: $speaker
description: $description
date: $date
time: $time
place: $place
signUp: $signUp
isStammtisch: $isStammtisch
)
}
`;
export default function EventModal({ open, close, event, refetch }) {
const [addEvent] = useMutation(addEventMutation);
const [editEvent] = useMutation(editEventMutation);
const { t } = useTranslation("common");
const initialValues = {
title: "",
speaker: "",
date: null,
time: null,
place: "",
signUp: "",
isStammtisch: false,
};
const form = useForm({
initialValues: initialValues,
validate: {
title: (value) => (value ? null : t("ENotEmpty")),
date: (value) => (value ? null : t("ENotEmpty")),
time: (value) => (value ? null : t("ENotEmpty")),
place: (value) => (value ? null : t("ENotEmpty")),
},
});
const content = "";
const editor = useEditor({
extensions: [StarterKit, Underline, Link],
content,
});
const templatesFmt = templates.map((template) => ({
label: template.title,
value: template.title,
}));
const setTemplate = (t) => {
const template = templates.filter((temp) => temp.title === t)[0];
let timeArr = [new Date(), new Date()];
if (template.time) {
timeArr[0].setHours(
Number(template.time[0].split(":")[0]),
Number(template.time[0].split(":")[1]),
0
);
timeArr[1].setHours(
Number(template.time[1].split(":")[0]),
Number(template.time[1].split(":")[1]),
0
);
}
form.setValues({
...template,
time: template.time ? [...timeArr] : null,
});
editor.commands.setContent(template.description);
};
const setEvent = () => {
if (event) {
form.setValues({
...event,
date: new Date(event.date),
time: [new Date(event.startTime), new Date(event.endTime)],
});
if (editor) editor.commands.setContent(event.description);
} else {
form.setValues(initialValues);
if (editor) editor.commands.setContent(content);
}
};
useEffect(() => {
setEvent();
}, [open]);
const submit = async (values) => {
if (event) {
const res = await editEvent({
variables: {
id: event.id,
...values,
description: editor.getHTML(),
},
});
if (res.data.editEvent) {
refetch();
close();
}
} else {
const res = await addEvent({
variables: {
...values,
description: editor.getHTML(),
},
});
if (res.data.addEvent) {
refetch();
close();
}
}
};
return (
<Modal opened={open} onClose={close} fz="xl" size="xl">
<Text variant="h2" fw={700}>
{event ? t("editEvent") : t("addEvent")}
</Text>
<Space h="xl" />
<Select
data={templatesFmt}
placeholder={t("loadTemplate")}
onChange={(e) => setTemplate(e)}
/>
<Space h="xl" />
<Divider />
<Space h="xs" />
<form onSubmit={form.onSubmit((values) => submit(values))}>
<Grid>
<Grid.Col sm={12} md={6}>
<TextInput
withAsterisk
label={t("title")}
placeholder="Introduction to Free Software"
{...form.getInputProps("title")}
/>
</Grid.Col>
<Grid.Col sm={12} md={6}>
<TextInput
withAsterisk
label={t("speaker")}
placeholder="Maxime Musterfrau"
{...form.getInputProps("speaker")}
/>
</Grid.Col>
<Grid.Col sm={12}>
<Text fz="sm">{t("description")}</Text>
{editor && <Editor editor={editor} />}
</Grid.Col>
<Grid.Col sm={12} md={6}>
<DatePicker
placeholder="January 1, 1970"
label={t("date")}
withAsterisk
{...form.getInputProps("date")}
/>
</Grid.Col>
<Grid.Col sm={12} md={6}>
<TimeRangeInput
label={t("time")}
withAsterisk
clearable
{...form.getInputProps("time")}
/>
</Grid.Col>
<Grid.Col sm={12} md={6}>
<TextInput
withAsterisk
label={t("place")}
placeholder="ETH HG F 7"
{...form.getInputProps("place")}
/>
</Grid.Col>
<Grid.Col sm={12} md={6}>
<TextInput
label={t("signUp")}
placeholder="https://..."
{...form.getInputProps("signUp")}
/>
</Grid.Col>
<Grid.Col sm={12} md={3}>
<Checkbox
mt="xl"
label={t("stammtisch")}
{...form.getInputProps("isStammtisch", { type: "checkbox" })}
/>
</Grid.Col>
<Grid.Col sm={12}>
<Button type="submit">{t("submit")}</Button>
</Grid.Col>
</Grid>
</form>
</Modal>
);
}
import { useState } from "react";
import { useTranslation } from "next-i18next";
import { Button, Grid, Space, useMantineTheme } from "@mantine/core";
import { Icon, ICONS } from "vseth-canine-ui";
import EventModal from "../components/eventModal";
import SignUpsModal from "../components/signUpsModal";
import EventCard from "../components/eventCard";
import { getAccentColor } from "../utilities/colors";
import { gql, useQuery } from "@apollo/client";
const getFutureEventsQuery = gql`
query getFutureEvents {
getFutureEvents {
id
title
speaker
description
date
startTime
endTime
place
signUp
isStammtisch
signUps {
sub
firstName
lastName
email
}
}
}
`;
export default function Events() {
const { data: events, refetch } = useQuery(getFutureEventsQuery);
const [open, setOpen] = useState(false);
const [signUpOpen, setSignUpOpen] = useState(false);
const [event, setEvent] = useState(null);
const theme = useMantineTheme();
const { t } = useTranslation("common");
const addEvent = () => {
setEvent(null);
setOpen(true);
};
return (
<>
<h1>{t("events")}</h1>
<Button
leftIcon={<Icon icon={ICONS.PLUS} color={getAccentColor(theme)} />}
onClick={addEvent}
variant="default"
>
{t("addEvent")}
</Button>
<EventModal
open={open}
event={event}
close={() => setOpen(false)}
refetch={refetch}
/>
<SignUpsModal
open={signUpOpen}
event={event}
close={() => setSignUpOpen(false)}
/>
<Space h="xl" />
<Grid>
{events &&
events.getFutureEvents.map((e) => (
<Grid.Col
md={4}
sm={6}
xs={12}
key={e.id}
style={{ display: "flex" }}
>
<EventCard
event={e}
setEvent={setEvent}
setOpen={setOpen}
refetch={refetch}
setSignUpOpen={setSignUpOpen}
/>
</Grid.Col>
))}
</Grid>
</>
);
}
import { useRouter } from "next/router";
import Image from "next/image";
import dynamic from "next/dynamic";
import {
ActionIcon,
Grid,
MediaQuery,
Space,
useMantineTheme,
} from "@mantine/core";
import { Icon, ICONS } from "vseth-canine-ui";
import parse from "html-react-parser";
const NoAdblockBanner = dynamic(() => import("../components/noAdblockBanner"), {
ssr: false,
});
import { getAccentColor } from "../utilities/colors";
import content from "../content/texts.json";
import tux from "../public/images/tux.png";
export default function Header() {
const { locale } = useRouter();
const theme = useMantineTheme();
return (
<div
style={{
height: "calc(100vh - 112px)",
display: "flex",
flexDirection: "column",
justifyContent: "space-evenly",
}}
>
<NoAdblockBanner />
<Grid style={{ display: "flex", alignItems: "center" }}>
<Grid.Col xs={12} md={6}>
<div>
<Image
src={
theme.colorScheme === "dark"
? "https://static.vseth.ethz.ch/assets/vseth-0522-thealt/logo-inv.svg"
: "/images/logo.svg"
}
width={0}
height={0}
sizes="100vw"
style={{ width: "30rem", maxWidth: "100%", height: "auto" }}
alt="TheAlternative Logo "
/>
<Space h="xl" />
<Space h="xl" />
{content.topSection.map((entry, i) => (
<p key={i}>{parse(entry[locale || "en"])}</p>
))}
</div>
</Grid.Col>
<MediaQuery smallerThan="md" styles={{ display: "none" }}>
<Grid.Col md={6}>
<Image
src={tux}
width={0}
height={0}
sizes="100vw"
style={{ width: "100%", height: "auto" }}
alt="Tux, the Linux Mascot"
/>
</Grid.Col>
</MediaQuery>
</Grid>
<a href="#events">
<ActionIcon
style={{ marginLeft: "auto", marginRight: "auto" }}
size="3rem"
>
<Icon icon={ICONS.DOWN} color={getAccentColor(theme)} size="2rem" />
</ActionIcon>
</a>
</div>
);
}
This diff is collapsed.
...@@ -4,6 +4,8 @@ import { Button, useMantineTheme } from "@mantine/core"; ...@@ -4,6 +4,8 @@ import { Button, useMantineTheme } from "@mantine/core";
import { Icon, ICONS } from "vseth-canine-ui"; import { Icon, ICONS } from "vseth-canine-ui";
import { getAccentColor } from "../utilities/colors";
const LoginButton = () => { const LoginButton = () => {
const { data: session } = useSession(); const { data: session } = useSession();
const theme = useMantineTheme(); const theme = useMantineTheme();
...@@ -11,7 +13,7 @@ const LoginButton = () => { ...@@ -11,7 +13,7 @@ const LoginButton = () => {
if (session) { if (session) {
return ( return (
<Button <Button
leftIcon={<Icon icon={ICONS.LEAVE} color="#4f2a17" />} leftIcon={<Icon icon={ICONS.LEAVE} color={getAccentColor(theme)} />}
variant="light" variant="light"
onClick={() => signOut()} onClick={() => signOut()}
> >
...@@ -21,7 +23,7 @@ const LoginButton = () => { ...@@ -21,7 +23,7 @@ const LoginButton = () => {
} }
return ( return (
<Button <Button
leftIcon={<Icon icon={ICONS.ENTER} color="#4f2a17" />} leftIcon={<Icon icon={ICONS.ENTER} color={getAccentColor(theme)} />}
variant="light" variant="light"
onClick={() => signIn("keycloak")} onClick={() => signIn("keycloak")}
> >
......
import React, { useState, useEffect, useRef } from "react";
import { Box, Card } from "@mantine/core";
import about from "../content/about.json";
import { Feature, Map, View, Overlay } from "ol";
import TileLayer from "ol/layer/Tile";
import OSM from "ol/source/OSM";
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import Style from "ol/style/Style";
import Point from "ol/geom/Point";
import { fromLonLat } from "ol/proj";
export default function OfficeMap() {
const [map, setMap] = useState();
const mapElement = useRef();
const overlayElement = useRef();
const lat = 47.378532;
const lon = 8.5488535;
const zoomLevel = 17;
const osmLayer = new TileLayer({
preload: Infinity,
source: new OSM(),
});
const iconFeature = new Feature({
geometry: new Point(fromLonLat([lon, lat])),
name: "TheAlternative Office",
});
const vectorSource = new VectorSource({
features: [iconFeature],
});
const vectorLayer = new VectorLayer({
source: vectorSource,
});
const initialMap = new Map({
layers: [osmLayer, vectorLayer],
view: new View({
center: fromLonLat([lon, lat]),
zoom: zoomLevel,
}),
});
useEffect(() => {
initialMap.setTarget(mapElement.current);
const overlay = new Overlay({
element: overlayElement.current,
position: fromLonLat([lon, lat]),
positioning: "bottom-center",
stopEvent: false,
});
initialMap.addOverlay(overlay);
setMap(initialMap);
}, []);
return (
<Card
shadow="md"
style={{
width: "100%",
paddingTop: "100%",
position: "relative",
}}
withBorder
>
<Box
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
alignItems: "stretch",
flexDirection: "column",
}}
>
<div
style={{ height: "100vh", width: "100%" }}
ref={mapElement}
className="map-container"
/>
<div
ref={overlayElement}
id="marker"
style={{
border: "1px solid #000",
borderRadius: ".2rem",
backgroundColor: "#fff",
opacity: "0.9",
padding: "10px",
marginBottom: "10px",
}}
>
<b style={{ color: "black" }}>TheAlternative</b>
{about.address.map((line, i) => (
<p key={i} style={{ color: "black", marginBottom: 0 }}>
{line}
</p>
))}
</div>
</Box>
</Card>
);
}
import React, { ReactNode, useState } from "react"; import React, { ReactNode, useState, useEffect } from "react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { ColorSchemeProvider, Group, MediaQuery } from "@mantine/core";
import { NotificationsProvider } from "@mantine/notifications"; import { NotificationsProvider } from "@mantine/notifications";
import { ApolloProvider } from "@apollo/client"; import { ApolloProvider } from "@apollo/client";
...@@ -20,6 +21,7 @@ import { ...@@ -20,6 +21,7 @@ import {
import hasAccess from "../utilities/hasAccess"; import hasAccess from "../utilities/hasAccess";
import LoginButton from "../components/loginButton"; import LoginButton from "../components/loginButton";
import ThemeSwitcher from "../components/themeSwitcher";
export default function Navbar(props) { export default function Navbar(props) {
const router = useRouter(); const router = useRouter();
...@@ -30,22 +32,63 @@ export default function Navbar(props) { ...@@ -30,22 +32,63 @@ export default function Navbar(props) {
const theme = makeVsethTheme(); const theme = makeVsethTheme();
const [selectedLanguage, setSelectedLanguage] = React.useState(locale); const [selectedLanguage, setSelectedLanguage] = React.useState(locale);
const [colorScheme, setColorScheme] = useState("dark");
const toggleColorScheme = (value) =>
setColorScheme(value || (colorScheme === "dark" ? "light" : "dark"));
theme.colorScheme = colorScheme;
/* /*
* VSETH Colors: See https://s.aschoch.ch/canine-colors * VSETH Colors: See https://s.aschoch.ch/canine-colors
* theme.colors.vsethMain[7] is #009fe3 (cyan) * theme.colors.vsethMain[7] is #009fe3 (cyan)
*/ */
theme.colors.vsethMain[7] = "#244471"; theme.colors.vsethMain[7] = "#244471";
theme.colors.vsethMain[0] = "#b7cae5"; // light color theme.colors.vsethMain[0] = "#dee5ef"; // light color
theme.colors.vsethMain[1] = "#b7cae5"; // light color theme.colors.vsethMain[1] = "#b7cae5"; // light color
theme.colors.vsethMain[8] = "#4f6d96"; // light color theme.colors.vsethMain[8] = "#4f6d96"; // light color
theme.colors.orange = [
"#f28a20",
"#f28a20",
"#f28a20",
"#f28a20",
"#f28a20",
"#f28a20",
"#f28a20",
"#f28a20",
"#f28a20",
"#f28a20",
];
// NOTE: This is a hack, as colors are hardcoded in vseth-canine
useEffect(() => {
const footerDiv = document.querySelector("footer>div");
const footerLogo = document.querySelector("footer img");
const selectItems = document.querySelector("header a");
if (colorScheme == "dark") {
footerDiv.classList.add("vseth-footer-dark");
footerLogo.classList.add("vseth-logo-dark");
if (selectItems) selectItems.classList.add("vseth-text-dark");
} else {
footerDiv.classList.remove("vseth-footer-dark");
footerLogo.classList.remove("vseth-logo-dark");
if (selectItems) selectItems.classList.remove("vseth-text-dark");
}
}, [colorScheme]);
const { data } = useConfig( const { data } = useConfig(
"https://static.vseth.ethz.ch/assets/vseth-0522-thealt/config.json" "https://static.vseth.ethz.ch/assets/vseth-0522-thealt/config.json"
); );
const customDemoNav = [ const customDemoNav = [
{ title: t("main"), href: "/" }, { title: t("main"), href: "/" },
] { title: t("bashGuide"), href: "/bash" },
{ title: t("installGuide"), href: "/install" },
{
title: t("courses"),
href: "https://gitlab.ethz.ch/thealternative/courses",
},
];
// if (hasAccess(session, true)) { // if (hasAccess(session, true)) {
// customDemoNav.push({ title: t("menu"), href: "/menu" }); // customDemoNav.push({ title: t("menu"), href: "/menu" });
...@@ -54,40 +97,73 @@ export default function Navbar(props) { ...@@ -54,40 +97,73 @@ export default function Navbar(props) {
return ( return (
<React.StrictMode> <React.StrictMode>
<VSETHThemeProvider theme={theme}> <ColorSchemeProvider
<VSETHExternalApp colorScheme={colorScheme}
selectedLanguage={selectedLanguage} toggleColorScheme={toggleColorScheme}
onLanguageSelect={(lang) => { >
setSelectedLanguage(lang); <VSETHThemeProvider theme={theme}>
router.push({ pathname, query }, asPath, { locale: lang }); <VSETHExternalApp
}} selectedLanguage={selectedLanguage}
languages={data?.languages} onLanguageSelect={(lang) => {
title={"TheAlternative"} setSelectedLanguage(lang);
appNav={customDemoNav} router.push({ pathname, query }, asPath, { locale: lang });
organizationNav={data.externalNav} }}
makeWrapper={(url, child) => ( languages={data?.languages}
<Link title={"TheAlternative"}
href={url} appNav={customDemoNav}
style={{ textDecoration: "none", color: "inherit" }} organizationNav={data.externalNav}
> makeWrapper={(url, child) => (
{child} <>
</Link> <MediaQuery smallerThan="md" styles={{ display: "none" }}>
)} <Link
privacyPolicy={data?.privacy} href={url}
disclaimer={data?.copyright} style={{
//activeHref is the current active url path. Required to style the active page in the Nav textDecoration: "none",
activeHref={"/"} color:
socialMedia={data?.socialMedia} theme.colorScheme == "light" ? "#333333b3" : "#C1C2C5",
logo={"https://static.vseth.ethz.ch/assets/vseth-0522-thealt/logo-inv.svg"} }}
loginButton={<LoginButton />} >
signet={"https://static.vseth.ethz.ch/assets/vseth-0522-thealt/signet-inv.svg"} {child}
size="xl" </Link>
> </MediaQuery>
<ApolloProvider client={apolloClient}> <MediaQuery largerThan="md" styles={{ display: "none" }}>
<NotificationsProvider>{props.children}</NotificationsProvider> <Link
</ApolloProvider> href={url}
</VSETHExternalApp> style={{
</VSETHThemeProvider> textDecoration: "none",
color: "#C1C2C5",
}}
>
{child}
</Link>
</MediaQuery>
</>
)}
privacyPolicy={data?.privacy}
disclaimer={data?.copyright}
//activeHref is the current active url path. Required to style the active page in the Nav
activeHref={"/"}
socialMedia={data?.socialMedia}
logo={
"https://static.vseth.ethz.ch/assets/vseth-0522-thealt/logo-inv.svg"
}
loginButton={
<Group>
<ThemeSwitcher />
<LoginButton />
</Group>
}
signet={
"https://static.vseth.ethz.ch/assets/vseth-0522-thealt/signet-inv.svg"
}
size="xl"
>
<ApolloProvider client={apolloClient}>
<NotificationsProvider>{props.children}</NotificationsProvider>
</ApolloProvider>
</VSETHExternalApp>
</VSETHThemeProvider>
</ColorSchemeProvider>
</React.StrictMode> </React.StrictMode>
); );
} }
import { useState, useEffect } from "react";
import { Alert } from "@mantine/core";
import { useTranslation } from "next-i18next";
import parse from "html-react-parser";
import { isAdblocking } from "adblock-hunter";
export default function NoAdblockBanner() {
const { t } = useTranslation("common");
const [hasAdblock, setHasAdblock] = useState(true);
useEffect(() => {
isAdblocking().then((isAdblocking) => {
setHasAdblock(isAdblocking);
});
}, []);
return (
<>
{!hasAdblock && (
<Alert title={t("noAdblockTitle")} color="red">
{parse(t("noAdblockText"))}
</Alert>
)}
</>
);
}
import { useRouter } from "next/router";
import { Blockquote, Space } from "@mantine/core";
import parse from "html-react-parser";
import philosophy from "../content/philosophy";
export default function Philosophy() {
const { locale } = useRouter();
return (
<>
{philosophy.map((entry, i) => (
<div key={i}>
<h2 style={{ margin: 0 }}>{entry.title[locale || "en"]}</h2>
<Blockquote cite={"" + entry.source} color="orange">
{entry.summary[locale || "en"]}
</Blockquote>
{entry.definition[locale || "en"].map((def, i) => (
<p key={i}>{parse(def)}</p>
))}
<Space h="md" />
<p>{parse(entry.examples[locale || "en"])}</p>
<Space h="xl" />
<Space h="xl" />
</div>
))}
</>
);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment