Browse Source

Initial commit

master
Adrian Baumgart 8 months ago
commit
b1d0696c53
Signed by: exc_bad_access GPG Key ID: B6CC77BC47B56030
  1. 36
      .gitignore
  2. 21
      LICENSE
  3. 5
      README.md
  4. 10
      components/HomeCard.js
  5. 40
      components/MainPage.js
  6. 12
      components/Page.js
  7. 7
      next.config.js
  8. 8708
      package-lock.json
  9. 29
      package.json
  10. 8
      pages/_app.js
  11. 29
      pages/api/auth.js
  12. 28
      pages/api/calendar/[id].js
  13. 35
      pages/api/calendar/index.js
  14. 31
      pages/api/home.js
  15. 30
      pages/api/homework/[id].js
  16. 40
      pages/api/homework/index.js
  17. 42
      pages/api/login.js
  18. 26
      pages/api/logout.js
  19. 116
      pages/calendar/[id].js
  20. 76
      pages/calendar/index.js
  21. 78
      pages/home.js
  22. 103
      pages/homework/[id].js
  23. 69
      pages/homework/index.js
  24. 193
      pages/index.js
  25. 92
      pages/login.js
  26. 148
      pages/settings/disclosure.js
  27. 68
      pages/settings/index.js
  28. 201
      pages/settings/privacy.js
  29. BIN
      public/favicon.ico
  30. BIN
      public/icons/android-icon-192x192.png
  31. BIN
      public/icons/apple-icon-114x114.png
  32. BIN
      public/icons/apple-icon-120x120.png
  33. BIN
      public/icons/apple-icon-144x144.png
  34. BIN
      public/icons/apple-icon-152x152.png
  35. BIN
      public/icons/apple-icon-180x180.png
  36. BIN
      public/icons/apple-icon-57x57.png
  37. BIN
      public/icons/apple-icon-60x60.png
  38. BIN
      public/icons/apple-icon-72x72.png
  39. BIN
      public/icons/apple-icon-76x76.png
  40. BIN
      public/icons/favicon-16x16.png
  41. BIN
      public/icons/favicon-32x32.png
  42. BIN
      public/icons/favicon-96x96.png
  43. 79
      public/manifest.json
  44. 107
      public/sw.js
  45. 1
      public/sw.js.map
  46. 4
      public/vercel.svg
  47. 1585
      public/workbox-f88dbe3b.js
  48. 1
      public/workbox-f88dbe3b.js.map
  49. 9
      scripts/addLineBreaks.js
  50. 36
      scripts/firebase.js
  51. 9
      scripts/formatDateToString.js
  52. 19
      scripts/isValidAuth.js
  53. 6
      scripts/setDateDoubleZero.js
  54. 3
      styles/Calendar.module.css
  55. 3
      styles/Home.module.css
  56. 9
      styles/HomeCard.module.css
  57. 11
      styles/Login.module.css
  58. 134
      styles/Navigation.module.css
  59. 3
      styles/Page.module.css
  60. 11
      styles/Privacy.module.css
  61. 25
      styles/Settings.module.css
  62. 20
      styles/globals.css
  63. 8
      styles/homework/HomeworkDetail.module.css
  64. 4843
      yarn.lock

36
.gitignore

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
.env

21
LICENSE

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Lea Baumgart
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
README.md

@ -0,0 +1,5 @@
# KlassenApp
another attempt to rewrite this
If you don't know what this is, you really don't need to care. It's an app for my class with homework, tests,... Just ignore it

10
components/HomeCard.js

@ -0,0 +1,10 @@
import styles from "../styles/HomeCard.module.css";
export default function HomeCard({ title, children }) {
return (
<div className={styles.main}>
<h3>{title}</h3>
{children}
</div>
);
}

40
components/MainPage.js

@ -0,0 +1,40 @@
import Head from "next/head";
export default function MainPage({ children }) {
return (
<div>
<Head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"
/>
<meta name="description" content="Description" />
<meta name="keywords" content="Keywords" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<title>KlassenApp</title>
<link rel="manifest" href="/manifest.json" />
<link
href="/icons/favicon-16x16.png"
rel="icon"
type="image/png"
sizes="16x16"
/>
<link
href="/icons/favicon-32x32.png"
rel="icon"
type="image/png"
sizes="32x32"
/>
<link rel="apple-touch-icon" href="/apple-icon.png"></link>
<meta name="theme-color" content="#317EFB" />
</Head>
{children}
</div>
);
}

12
components/Page.js

@ -0,0 +1,12 @@
import styles from "../styles/Page.module.css";
export default function Page({ title, subtitle, children }) {
return (
<div className={styles.root}>
<h1>{title}</h1>
{subtitle != null ? <h3>{subtitle}</h3> : <div></div>}
<hr className="solid" />
{children}
</div>
);
}

7
next.config.js

@ -0,0 +1,7 @@
const withPWA = require("next-pwa");
module.exports = withPWA({
pwa: {
dest: "public",
},
});

8708
package-lock.json
File diff suppressed because it is too large
View File

29
package.json

@ -0,0 +1,29 @@
{
"name": "klassenapp",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@firebase/app": "^0.6.11",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material/elevation": "^7.0.0",
"argon2": "^0.27.0",
"axios": "^0.20.0",
"cookies": "^0.8.0",
"dotenv": "^8.2.0",
"es-cookie": "^1.3.2",
"firebase": "^7.21.1",
"firebase-admin": "^9.2.0",
"jsonwebtoken": "^8.5.1",
"next": "9.5.3",
"next-pwa": "^3.1.4",
"react": "16.13.1",
"react-dom": "16.13.1",
"uuid": "^8.3.0"
}
}

8
pages/_app.js

@ -0,0 +1,8 @@
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp;

29
pages/api/auth.js

@ -0,0 +1,29 @@
const jwt = require("jsonwebtoken");
import firebase from "../../scripts/firebase";
export default async (req, res) => {
console.log(req);
if (typeof req.body.token !== "string")
return res.status(400).json({ err: "badRequest" });
let token;
try {
token = await jwt.verify(req.body.token, process.env.SESSION_JWT);
} catch (err) {
return res.status(404).json({ err: "invalidToken" });
}
const snapshot = await firebase
.firestore()
.collection("sessions")
.doc(token.session)
.get();
if (
snapshot.data() == undefined ||
!snapshot.data().session ||
snapshot.data().session !== token.session
)
return res.status(400).json({ err: "badAuthorization" });
return res.status(200).json(snapshot.data());
};

28
pages/api/calendar/[id].js

@ -0,0 +1,28 @@
import firebase from "../../../scripts/firebase";
import formatDateToString from "../../../scripts/formatDateToString";
import isValidAuth from "../../../scripts/isValidAuth";
export default async (req, res) => {
const authorized = await isValidAuth(req.headers.authorization);
if (!authorized) return res.status(401).json({ err: "badAuthorization" });
const {
query: { id },
} = req;
const snapshot = await firebase
.firestore()
.collection("calendar")
.doc(id)
.get();
if (snapshot.data() == undefined || snapshot.data() == null)
return res.status(404).json({ err: "resourceNotFound" });
return res.status(200).json({
id: snapshot.id,
name: snapshot.data().name,
startdate: formatDateToString(snapshot.data().dates.startDate.toDate()),
enddate: snapshot.data().dates.hasOwnProperty("endDate")
? formatDateToString(snapshot.data().dates.endDate.toDate())
: null,
description: snapshot.data().description,
});
};

35
pages/api/calendar/index.js

@ -0,0 +1,35 @@
import firebase from "../../../scripts/firebase";
import formatDateToString from "../../../scripts/formatDateToString";
import isValidAuth from "../../../scripts/isValidAuth";
export default async (req, res) => {
const authorized = await isValidAuth(req.headers.authorization);
if (!authorized) return res.status(401).json({ err: "badAuthorization" });
const snapshot = await firebase.firestore().collection("calendar").get();
var events = [];
snapshot.forEach((doc) => {
if (
(doc.data().dates.hasOwnProperty("endDate") &&
doc.data().dates.endDate.toMillis() < Date.now() - 86400000) ||
doc.data().dates.startDate.toMillis() < Date.now() - 86400000
) {
return;
}
return events.push({
id: doc.id,
name: doc.data().name,
startdate: formatDateToString(doc.data().dates.startDate.toDate()),
enddate: doc.data().dates.hasOwnProperty("endDate")
? formatDateToString(doc.data().dates.endDate.toDate())
: null,
sortdate: doc.data().dates.startDate.toMillis(),
});
});
events.sort((a, b) => parseFloat(a.sortdate) - parseFloat(b.sortdate));
res.status(200).json({
events: events,
});
};

31
pages/api/home.js

@ -0,0 +1,31 @@
export default (req, res) => {
res.status(200).json({
habm: [
{
id: "0000",
lesson: "NwT",
due: "d0000",
description: "Test 00",
},
{
id: "0001",
lesson: "Deutsch",
due: "d0001",
description: "Test 01",
},
],
nextevents: [
{
id: "0000",
name: "Feiertag",
startdate: "sd0000",
enddate: "ed0000",
},
{
id: "0001",
name: "Hey",
startdate: "sd0000",
},
],
});
};

30
pages/api/homework/[id].js

@ -0,0 +1,30 @@
import firebase from "../../../scripts/firebase";
import formatDateToString from "../../../scripts/formatDateToString";
import isValidAuth from "../../../scripts/isValidAuth";
export default async (req, res) => {
const authorized = await isValidAuth(req.headers.authorization);
if (!authorized) return res.status(401).json({ err: "badAuthorization" });
const {
query: { id },
} = req;
const snapshot = await firebase
.firestore()
.collection("homework")
.doc(id)
.get();
var subhomework = "";
snapshot.data().homework.forEach((hw) => {
subhomework += `${hw.homework}\n\n\n`;
});
res.status(200).json({
id: snapshot.id,
lesson: snapshot.data().lesson,
due: formatDateToString(snapshot.data().due.toDate()),
description: subhomework,
sortdate: snapshot.data().due.toMillis(),
});
};

40
pages/api/homework/index.js

@ -0,0 +1,40 @@
import firebase from "../../../scripts/firebase";
import formatDateToString from "../../../scripts/formatDateToString";
import isValidAuth from "../../../scripts/isValidAuth";
export default async (req, res) => {
const authorized = await isValidAuth(req.headers.authorization);
if (!authorized) return res.status(401).json({ err: "badAuthorization" });
const snapshot = await firebase.firestore().collection("homework").get();
const homework = [];
snapshot.forEach((doc) => {
if (doc.data().due.toMillis() > Date.now() - 86400000) {
var subhomework = "";
doc.data().homework.forEach((hw) => {
subhomework += `${hw.homework}\n`;
});
homework.push({
id: doc.id,
lesson: doc.data().lesson,
due: formatDateToString(doc.data().due.toDate()),
description: subhomework,
sortdate: doc.data().due.toMillis(),
});
}
});
homework.sort((a, b) => parseFloat(a.sortdate) - parseFloat(b.sortdate));
res.status(200).json({
homework: homework /*[
{
id: "0001",
lesson: "Deutsch",
due: "morgen",
description: "Test 01",
},
],*/,
});
};

42
pages/api/login.js

@ -0,0 +1,42 @@
const argon2 = require("argon2");
const uuid = require("uuid").v4;
import firebase from "../../scripts/firebase";
const jwt = require("jsonwebtoken");
export default async (req, res) => {
if (
typeof req.body.password !== "string" ||
typeof req.body.clientip !== "string"
)
return res.status(400).json({ err: "badRequest" });
try {
if (
await argon2.verify(
process.env.PWD_HASH.includes("∑")
? process.env.PWD_HASH.replace(/∑/g, "$")
: process.env.PWD_HASH,
req.body.password
)
) {
const sessionUID = uuid();
await firebase.firestore().collection("sessions").doc(sessionUID).set({
session: sessionUID,
environment: process.env.NODE_ENV,
ip: req.body.clientip,
});
const token = jwt.sign(
{
session: sessionUID,
},
process.env.SESSION_JWT
);
res.status(200).json({ session: token });
} else {
res.status(401).json({ err: "wrongPassword" });
}
} catch (error) {
res.status(500).json({ err: "internalError" });
}
};

26
pages/api/logout.js

@ -0,0 +1,26 @@
import firebase from "../../scripts/firebase";
import isValidAuth from "../../scripts/isValidAuth";
const axios = require("axios").default;
export default async (req, res) => {
const authorized = await isValidAuth(req.headers.authorization);
if (!authorized) return res.status(401).json({ err: "badAuthorization" });
axios
.post(`${process.env.NEXT_URL}/api/auth`, {
token: req.headers.authorization,
})
.then(async (response) => {
firebase
.firestore()
.collection("sessions")
.doc(response.data.session)
.delete()
.then(() => {
res.status(200).json({});
});
})
.catch((err) => {
res.status(500).json({ err: "internalError" });
});
};

116
pages/calendar/[id].js

@ -0,0 +1,116 @@
import { AppBar, Toolbar, IconButton, Typography } from "@material-ui/core";
import { useState, useEffect } from "react";
import axios from "axios";
import ArrowBackIcon from "@material-ui/icons/ArrowBack";
import { makeStyles } from "@material-ui/core/styles";
import { useRouter } from "next/router";
import styles from "../../styles/homework/HomeworkDetail.module.css";
import addLineBreaks from "../../scripts/addLineBreaks";
import { get as getCookie } from "es-cookie";
import MainPage from "../../components/MainPage";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
}));
export default function CalendarDetail({ id, returnPath }) {
const classes = useStyles();
const router = useRouter();
const [calendarData, setCalendarData] = useState(null);
useEffect(() => {
const sessionToken = getCookie("token");
if (sessionToken == undefined) router.push("/login");
axios
.get(`/api/calendar/${id}`, {
headers: {
Authorization: sessionToken,
},
})
.then(({ data }) => {
setCalendarData(data);
})
.catch((error) => {
if (error.response.status === 401) {
router.push("/login");
} else {
alert(error);
}
});
}, []);
return (
<MainPage>
<div>
<AppBar position="fixed" color="inherit">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="menu"
onClick={() => {
if (returnPath != null) {
router.push(returnPath);
} else {
router.push("/");
}
}}
>
<ArrowBackIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
{calendarData != null ? (
<p>{calendarData.name}</p>
) : (
<p>Wird geladen...</p>
)}
</Typography>
</Toolbar>
</AppBar>
<Toolbar />
<div className={styles.mainFrame}>
{calendarData != null ? (
<div>
<h3>
Datum:{" "}
{calendarData.hasOwnProperty("enddate") &&
calendarData.enddate != null ? (
<b>
{calendarData.startdate} - {calendarData.enddate}
</b>
) : (
<b>{calendarData.startdate}</b>
)}
</h3>
<div className={styles.description}>
{addLineBreaks(calendarData.description)}
</div>
</div>
) : (
<p>Wird geladen...</p>
)}
</div>
</div>
</MainPage>
);
}
CalendarDetail.getInitialProps = async (ctx) => {
if (ctx.query.hasOwnProperty("return"))
return {
id: ctx.query.id,
returnPath: ctx.query.return,
};
return {
id: ctx.query.id,
returnPath: null,
};
};

76
pages/calendar/index.js

@ -0,0 +1,76 @@
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import axios from "axios";
import HomeCard from "../../components/HomeCard";
import Link from "next/link";
import styles from "../../styles/Calendar.module.css";
import { get as getCookie } from "es-cookie";
import MainPage from "../../components/MainPage";
export default function Calendar({ initFromTabbar }) {
const router = useRouter();
const [calendarData, setcalendarData] = useState(null);
useEffect(() => {
if (initFromTabbar != true) router.push("/?p=2");
const sessionToken = getCookie("token");
if (sessionToken == undefined) router.push("/login");
axios
.get("/api/calendar", {
headers: {
Authorization: sessionToken,
},
})
.then(({ data }) => {
setcalendarData(data);
})
.catch((error) => {
if (error.response.status === 401) {
router.push("/login");
} else {
alert(error);
}
});
}, []);
return (
<MainPage>
<div>
{initFromTabbar ? (
<div>
{calendarData != null ? (
calendarData.events.length > 0 ? (
calendarData.events.map((event) => (
<Link
key={event.id}
href={`/calendar/${event.id}?return=/calendar`}
>
<a>
<HomeCard key={event.id} title={event.name}>
{event.hasOwnProperty("enddate") &&
event.enddate != null ? (
<p className={styles.eventSubtitle}>
{event.startdate} - {event.enddate}
</p>
) : (
<p className={styles.eventSubtitle}>
{event.startdate}
</p>
)}
</HomeCard>
</a>
</Link>
))
) : (
<p>Keine Einträge 😕</p>
)
) : (
<p>Wird geladen...</p>
)}
</div>
) : (
<p></p>
)}
</div>
</MainPage>
);
}

78
pages/home.js

@ -0,0 +1,78 @@
import { useState, useEffect } from "react";
import axios from "axios";
import HomeCard from "../components/HomeCard";
import styles from "../styles/Home.module.css";
import { useRouter } from "next/router";
import { get as getCookie } from "es-cookie";
import MainPage from "../components/HomeCard";
export default function Home({ initFromTabbar }) {
const router = useRouter();
const [homeData, setData] = useState(null);
useEffect(() => {
if (initFromTabbar != true) router.push("/?p=0");
const sessionToken = getCookie("token");
if (sessionToken == undefined) router.push("/login");
axios
.get("/api/home")
.then(({ data }) => {
setData(data);
})
.catch((error) => {
alert(error);
});
}, []);
return (
<MainPage>
<div>
{initFromTabbar ? (
<div>
<p>Coming soon :)</p>
</div>
) : (
<p></p>
)}
</div>
</MainPage>
);
/*return (
<div>
{homeData != null ? (
<div>
<h2>Hausaufgaben bis morgen</h2>
{homeData.habm.length > 0 ? (
homeData.habm.map((habm) => (
<HomeCard key={habm.id} title={habm.lesson}>
<p>{habm.description}</p>
</HomeCard>
))
) : (
<p>Keine Hausaufgaben bis morgen 🎉</p>
)}
<hr className="solid" />
<h2>Als nächstes</h2>
{homeData.nextevents.length > 0 ? (
homeData.nextevents.map((event) => (
<HomeCard key={event.id} title={event.name}>
{event.hasOwnProperty("enddate") ? (
<p className={styles.eventSubtitle}>
{event.startdate} - {event.enddate}
</p>
) : (
<p className={styles.eventSubtitle}>{event.startdate}</p>
)}
</HomeCard>
))
) : (
<p>Keine Events</p>
)}
<hr className="solid" />
</div>
) : (
<p>Wird geladen...</p>
)}
</div>
);*/
}

103
pages/homework/[id].js

@ -0,0 +1,103 @@
import { AppBar, Toolbar, IconButton, Typography } from "@material-ui/core";
import { useState, useEffect } from "react";
import axios from "axios";
import ArrowBackIcon from "@material-ui/icons/ArrowBack";
import { makeStyles } from "@material-ui/core/styles";
import { useRouter } from "next/router";
import styles from "../../styles/homework/HomeworkDetail.module.css";
import homework from "../api/homework";
import addLineBreaks from "../../scripts/addLineBreaks";
import { get as getCookie } from "es-cookie";
import MainPage from "../../components/MainPage";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
}));
export default function HomeworkDetail({ id, returnPath }) {
const classes = useStyles();
const router = useRouter();
const [homeworkData, setHomeworkData] = useState(null);
useEffect(() => {
const sessionToken = getCookie("token");
if (sessionToken == undefined) router.push("/login");
axios
.get(`/api/homework/${id}`, {
headers: {
Authorization: sessionToken,
},
})
.then(({ data }) => {
setHomeworkData(data);
})
.catch((error) => {
if (error.response.status === 401) {
return router.push("/login");
} else {
alert(error);
}
});
}, []);
return (
<MainPage>
<div>
<AppBar position="fixed" color="inherit">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="menu"
onClick={() => {
if (returnPath != null) {
router.push(returnPath);
} else {
router.push("/");
}
}}
>
<ArrowBackIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
{homeworkData != null ? (
<p>{homeworkData.lesson}</p>
) : (
<p>Wird geladen...</p>
)}
</Typography>
</Toolbar>
</AppBar>
<Toolbar />
<div className={styles.mainFrame}>
{homeworkData != null ? (
<div>
<h3>
Zu erledigen bis: <b>{homeworkData.due}</b>
</h3>
<div className={styles.description}>
{addLineBreaks(homeworkData.description)}
</div>
</div>
) : (
<p>Wird geladen...</p>
)}
</div>
</div>
</MainPage>
);
}
HomeworkDetail.getInitialProps = async (ctx) => {
if (ctx.query.hasOwnProperty("return"))
return { id: ctx.query.id, returnPath: ctx.query.return };
return { id: ctx.query.id, returnPath: null };
};

69
pages/homework/index.js

@ -0,0 +1,69 @@
import { useState, useEffect } from "react";
import axios from "axios";
import HomeCard from "../../components/HomeCard";
import homework from "../api/homework";
import { useRouter } from "next/router";
import Link from "next/link";
import styles from "../../styles/Calendar.module.css";
import { get as getCookie } from "es-cookie";
import MainPage from "../../components/MainPage";
export default function Homework({ initFromTabbar }) {
const router = useRouter();
const [homeworkData, setHomeworkData] = useState(null);
useEffect(() => {
if (initFromTabbar != true) router.push("/?p=1");
const sessionToken = getCookie("token");
if (sessionToken == undefined) router.push("/login");
axios
.get("/api/homework", {
headers: {
Authorization: sessionToken,
},
})
.then(({ data }) => {
setHomeworkData(data);
})
.catch((error) => {
if (error.response.status === 401) {
router.push("/login");
} else {
alert(error);
}
});
}, []);
return (
<MainPage>
<div>
{initFromTabbar ? (
<div>
{homeworkData != null ? (
homeworkData.homework.length > 0 ? (
homeworkData.homework.map((homework) => (
<Link
key={homework.id}
href={`/homework/${homework.id}?return=/homework`}
>
<a>
<HomeCard key={homework.id} title={homework.lesson}>
<p className={styles.eventSubtitle}>
Zu erledigen bis: {homework.due}
</p>
</HomeCard>
</a>
</Link>
))
) : (
<p>Keine Hausaufgaben 🎉</p>
)
) : (
<p>Wird geladen...</p>
)}
</div>
) : (
<p></p>
)}
</div>
</MainPage>
);
}

193
pages/index.js

@ -0,0 +1,193 @@
import { BottomNavigation, BottomNavigationAction } from "@material-ui/core";
import Head from "next/head";
import styles from "../styles/Navigation.module.css";
import HomeIcon from "@material-ui/icons/Home";
import BookIcon from "@material-ui/icons/Book";
import TodayIcon from "@material-ui/icons/Today";
import SettingsIcon from "@material-ui/icons/Settings";
import Home from "./home";
import Homework from "./homework/index";
import Calendar from "./calendar/index";
import Settings from "./settings/index";
import Page from "../components/Page";
import { useState, useEffect } from "react";
import setDateDoubleZero from "../scripts/setDateDoubleZero";
import MainPage from "../components/MainPage";
const pages = {
0: {
name: "Home",
path: "/home",
},
1: {
name: "Hausaufgaben",
path: "/homework",
},
2: {
name: "Kalender",
path: "/calendar",
},
3: {
name: "Einstellungen",
path: "/settings",
},
};
const weekdays = {
0: "Sonntag",
1: "Montag",
2: "Dienstag",
3: "Mittwoch",
4: "Donnerstag",
5: "Freitag",
6: "Samstag",
};
export default function NavigationManager({ selection, p }) {
const [tabSelection, setTabSelection] = useState(selection);
useEffect(() => {
if (p != null) {
window.history.pushState("", pages[`${p}`].name, pages[`${p}`].path);
} else {
window.history.pushState("", pages[`0`].name, pages[`0`].path);
}
}, []);
const currentDate = new Date();
let greeting = "";
var currentHour = currentDate.getHours();
if (currentHour < 12) {
greeting = "Guten Morgen!";
} else if (currentHour < 13) {
greeting = "Guten Mittag!";
} else if (currentHour < 18) {
greeting = "Guten Nachmittag!";
} else {
greeting = "Guten Abend!";
}
const dateString = `Heute ist ${
weekdays[currentDate.getDay()]
}, der ${setDateDoubleZero(currentDate.getDate())}.${setDateDoubleZero(
currentDate.getMonth() + 1
)}.`;
return (
<MainPage>
<div>
<div className={styles.mainFrame}>
{tabSelection == 0 ? (
<Page title={greeting} subtitle={dateString}>
<Home initFromTabbar={true} />
</Page>
) : tabSelection == 1 ? (
<Page title={"Hausaufgaben"}>
<Homework initFromTabbar={true} />
</Page>
) : tabSelection == 2 ? (
<Page title={"Kalender"}>
<Calendar initFromTabbar={true} />
</Page>
) : tabSelection == 3 ? (
<Page title={"Einstellungen"}>
<Settings initFromTabbar={true} />
</Page>
) : (
<Page title={"Guten Morgen!"}>
<Home initFromTabbar={true} />
</Page>
)}
</div>
<BottomNavigation
value={tabSelection}
onChange={(event, newValue) => {
window.history.pushState(
"",
pages[`${newValue}`].name,
pages[`${newValue}`].path
);
setTabSelection(newValue);
}}
className={styles.bottomNavigation}
>
<BottomNavigationAction label="Home" icon={<HomeIcon />} />
<BottomNavigationAction label="Hausaufgaben" icon={<BookIcon />} />
<BottomNavigationAction label="Kalender" icon={<TodayIcon />} />
<BottomNavigationAction
label="Einstellungen"
icon={<SettingsIcon />}
/>
</BottomNavigation>
</div>
</MainPage>
);
/*return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>
<p className={styles.description}>
Get started by editing{' '}
<code className={styles.code}>pages/index.js</code>
</p>
<div className={styles.grid}>
<a href="https://nextjs.org/docs" className={styles.card}>
<h3>Documentation &rarr;</h3>
<p>Find in-depth information about Next.js features and API.</p>
</a>
<a href="https://nextjs.org/learn" className={styles.card}>
<h3>Learn &rarr;</h3>
<p>Learn about Next.js in an interactive course with quizzes!</p>
</a>
<a
href="https://github.com/vercel/next.js/tree/master/examples"
className={styles.card}
>
<h3>Examples &rarr;</h3>
<p>Discover and deploy boilerplate example Next.js projects.</p>
</a>
<a
href="https://vercel.com/import?filter=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}
>
<h3>Deploy &rarr;</h3>
<p>
Instantly deploy your Next.js site to a public URL with Vercel.
</p>
</a>
</div>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<img src="/vercel.svg" alt="Vercel Logo" className={styles.logo} />
</a>
</footer>
</div>
)*/
}
NavigationManager.getInitialProps = async (ctx) => {
const { query } = ctx;
if (query.hasOwnProperty("p") && pages.hasOwnProperty(query.p))
return { selection: query.p, p: query.p };
return { selection: 0, p: null };
};

92
pages/login.js

@ -0,0 +1,92 @@
import { useState, useEffect } from "react";
import { TextField, Button } from "@material-ui/core";
import styles from "../styles/Login.module.css";
import axios from "axios";
import { useRouter } from "next/router";
import { get as getCookie, set as setCookie } from "es-cookie";
import isValidAuth from "../scripts/isValidAuth";
import MainPage from "../components/MainPage";
export default function Login() {
const router = useRouter();
const [pinFieldShowError, setPinFieldShowError] = useState(false);
const [pinFieldErrorMessage, setPinFieldErrorMessage] = useState("");
const [enteredPin, setEnteredPin] = useState("");
useEffect(() => {
checkExistingToken();
}, []);
async function checkExistingToken() {
const sessionToken = getCookie("token");
if (sessionToken != undefined) {
const isValidAuthorization = await isValidAuth(sessionToken);
if (isValidAuthorization) {
router.push("/");
}
}
}
function handleSignIn() {
setPinFieldShowError(false);
setPinFieldErrorMessage("");
if (enteredPin.trim().length === 0) {
setPinFieldErrorMessage("");
return setPinFieldShowError(true);
}
axios
.get("https://api.ipify.org?format=json")
.then((ipresponse) => {
axios
.post("/api/login", {
password: enteredPin,
clientip: ipresponse.data.ip,
})
.then((response) => {
if (response.data.hasOwnProperty("session")) {
setCookie("token", response.data.session, { expires: 365 });
router.push("/");
} else {
setPinFieldErrorMessage("Der eingegebene Pin ist falsch");
return setPinFieldShowError(true);
}
})
.catch((err) => {
setPinFieldErrorMessage("Der eingegebene Pin ist falsch");
return setPinFieldShowError(true);
});
})
.catch((iperr) => {
setPinFieldErrorMessage(
"Bei der API-Anfrage ist ein Fehler vorgetreten. Bitte versuche es erneut"
);
return setPinFieldShowError(true);
});
}
return (
<MainPage>
<div className={styles.mainFrame}>
<h1>KlassenApp</h1>
<p>Bitte gib den Code ein, um fortzufahren</p>
<TextField
id="pin"
label="Pin"
value={enteredPin}
onChange={(event) => {
setEnteredPin(event.target.value);
}}
error={pinFieldShowError}
helperText={pinFieldErrorMessage}
/>
<div className={styles.signInButtonDiv}>
<Button variant="outlined" color="primary" onClick={handleSignIn}>
Anmelden
</Button>
</div>
</div>
</MainPage>
);
}

148
pages/settings/disclosure.js

@ -0,0 +1,148 @@
import MainPage from "../../components/MainPage";
import { useRouter } from "next/router";
import { AppBar, Toolbar, IconButton, Typography } from "@material-ui/core";
import Page from "../../components/Page";
import ArrowBackIcon from "@material-ui/icons/ArrowBack";
import { makeStyles } from "@material-ui/core/styles";
import styles from "../../styles/Privacy.module.css";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
}));
export default function LegalDisclosure() {
const classes = useStyles();
const router = useRouter();
return (
<MainPage>
<div>
<AppBar position="fixed" color="inherit">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="menu"
onClick={() => {
router.push("/settings");
}}
>
<ArrowBackIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
<p>Impressum</p>
</Typography>
</Toolbar>
</AppBar>
<Toolbar />
<div className={styles.mainFrame}>
<h1>Impressum</h1>
<h2>Angaben gem&auml;&szlig; &sect; 5 TMG</h2>
<p>
Adrian Baumgart
<br />
Karl-Gehrig-Stra&szlig;e 2<br />
69226 Nu&szlig;loch
</p>
<h2>Kontakt</h2>
<p>
Telefon:{" "}
<a
className={styles.link}
href="tel:+4915165909306"
target="_blank"
>
+4915165909306
</a>
<br />
E-Mail:{" "}
<a
className={styles.link}
href="mailto:adrian@abmgrt.dev"
target="_blank"
>
adrian@abmgrt.dev
</a>
<br />
Internetadresse:{" "}
<a
className={styles.link}
href="https://klassenapp.abmgrt.dev"
target="_blank"
>
https://klassenapp.abmgrt.dev
</a>
</p>
<h2>Verantwortlich f&uuml;r den Inhalt nach &sect; 55 Abs. 2 RStV</h2>
<p>Adrian Baumgart</p>
<h3>Haftung f&uuml;r Inhalte</h3>{" "}
<p>
Als Diensteanbieter sind wir gem&auml;&szlig; &sect; 7 Abs.1 TMG
f&uuml;r eigene Inhalte auf diesen Seiten nach den allgemeinen
Gesetzen verantwortlich. Nach &sect;&sect; 8 bis 10 TMG sind wir als
Diensteanbieter jedoch nicht verpflichtet, &uuml;bermittelte oder
gespeicherte fremde Informationen zu &uuml;berwachen oder nach
Umst&auml;nden zu forschen, die auf eine rechtswidrige
T&auml;tigkeit hinweisen.
</p>{" "}
<p>
Verpflichtungen zur Entfernung oder Sperrung der Nutzung von
Informationen nach den allgemeinen Gesetzen bleiben hiervon
unber&uuml;hrt. Eine diesbez&uuml;gliche Haftung ist jedoch erst ab
dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung
m&ouml;glich. Bei Bekanntwerden von entsprechenden
Rechtsverletzungen werden wir diese Inhalte umgehend entfernen.
</p>{" "}
<h3>Haftung f&uuml;r Links</h3>{" "}
<p>
Unser Angebot enth&auml;lt Links zu externen Websites Dritter, auf
deren Inhalte wir keinen Einfluss haben. Deshalb k&ouml;nnen wir
f&uuml;r diese fremden Inhalte auch keine Gew&auml;hr
&uuml;bernehmen. F&uuml;r die Inhalte der verlinkten Seiten ist
stets der jeweilige Anbieter oder Betreiber der Seiten
verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der
Verlinkung auf m&ouml;gliche Rechtsverst&ouml;&szlig;e
&uuml;berpr&uuml;ft. Rechtswidrige Inhalte waren zum Zeitpunkt der
Verlinkung nicht erkennbar.
</p>{" "}
<p>
Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist
jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht
zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir
derartige Links umgehend entfernen.
</p>{" "}
<h3>Urheberrecht</h3>{" "}
<p>
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf
diesen Seiten unterliegen dem deutschen Urheberrecht. Die
Vervielf&auml;ltigung, Bearbeitung, Verbreitung und jede Art der
Verwertung au&szlig;erhalb der Grenzen des Urheberrechtes
bed&uuml;rfen der schriftlichen Zustimmung des jeweiligen Autors
bzw. Erstellers. Downloads und Kopien dieser Seite sind nur f&uuml;r
den privaten, nicht kommerziellen Gebrauch gestattet.
</p>{" "}
<p>
Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt
wurden, werden die Urheberrechte Dritter beachtet. Insbesondere
werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie
trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten
wir um einen entsprechenden Hinweis. Bei Bekanntwerden von
Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.
</p>
<p>
Quelle:{" "}
<a href="https://www.e-recht24.de">https://www.e-recht24.de</a>
</p>
</div>
</div>
</MainPage>
);
}

68
pages/settings/index.js

@ -0,0 +1,68 @@
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { get as getCookie, remove as removeCookie } from "es-cookie";
import styles from "../../styles/Settings.module.css";
import Link from "next/link";
import axios from "axios";
import MainPage from "../../components/MainPage";
export default function Settings({ initFromTabbar }) {
const router = useRouter();
useEffect(() => {
if (initFromTabbar != true) router.push("/?p=3");
const sessionToken = getCookie("token");
if (sessionToken == undefined) router.push("/login");
}, []);
return (
<MainPage>
<div>
{initFromTabbar ? (
<div>
<p>
<i>Ziemlich leer</i>
</p>
<div className={styles.footer}>
<div
className={styles.card}
onClick={() => {
const sessionToken = getCookie("token");
axios
.get("/api/logout", {
headers: {
Authorization: sessionToken,
},
})
.then((response) => {
removeCookie("token");
router.push("/login");
})
.catch((err) => {
alert(err);
});
}}
>
<h4 style={{ color: "#ffffff" }}>Abmelden</h4>
</div>
<a
className={styles.link}
href="https://github.com/leabmgrt/klassenapp"
target="_blank"
>
Source Code
</a>
<Link href="/settings/privacy">
<a className={styles.link}>Datenschutzklärung</a>
</Link>
<Link href="/settings/disclosure">
<a className={styles.link}>Impressum</a>
</Link>
<p>© 2020, Adrian Baumgart. All rights reserved.</p>
</div>
</div>
) : (
<p></p>
)}
</div>
</MainPage>
);
}

201
pages/settings/privacy.js

@ -0,0 +1,201 @@
import MainPage from "../../components/MainPage";
import { useRouter } from "next/router";
import { AppBar, Toolbar, IconButton, Typography } from "@material-ui/core";
import Page from "../../components/Page";
import ArrowBackIcon from "@material-ui/icons/ArrowBack";
import { makeStyles } from "@material-ui/core/styles";
import styles from "../../styles/Privacy.module.css";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
}));
export default function PrivacyPolicy() {
const classes = useStyles();
const router = useRouter();
return (
<MainPage>
<div>
<AppBar position="fixed" color="inherit">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="menu"
onClick={() => {
router.push("/settings");
}}
>
<ArrowBackIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
<p>Datenschutzerklärung</p>
</Typography>
</Toolbar>
</AppBar>
<Toolbar />
<div className={styles.mainFrame}>
<h4>Datenschutzerklärung</h4>
<p></p>
<i>
Zuletzt aktualisiert am: <b>28.09.2020</b>
</i>
<p>
Hi! Danke dass du die App verwendest. Ich versuch die
Datenschutzerklärung kurz zu halten, keiner will ja tausend Seiten
durchlesen ;) (aber ich will auch rechtlich abgesichert sein...)
</p>
<p>
Ich (Adrian Baumgart) stelle diese Website (vonhieran: "App") als
kostenlosen und Open-Source Dienst so wie er ist bereit (der
Quellcode ist auf GitHub). Um die App anbieten zu können, muss ich
einige Informationen sammeln. In dieser Datenschutzerklärung liste
ich auf, welche Daten das sind.
</p>
<p>
Wenn du die App verwendest, stimmst du du zu, dass du diese
Datenschutzerklärung gelesen hast und einverstanden bist. Solltest
du diese Datenschutzerklärung nicht akzeptieren, verwende die App{" "}
<u>NICHT!</u>
</p>
<strong>Allgemeine (personenbezogene Informationen)</strong>
<p>
Um diese App zu ermöglichen und einen besseren Service anzubieten,
sammle ich einige personenbezogene Daten. Einige Daten bleiben auf
deinem Gerät, einige werden hochgeladen.
</p>
<p>
Die App verwendet Firebase als Datenbank. Es ist möglich, dass
Firebase auch personenbezogene Daten erfasst.{" "}
<a
className={styles.link}
href="https://www.google.com/policies/privacy/"
target="_blank"
rel="noopener noreferrer"
>
Hier ist ein Link zu Firebases Datenschutzerklärung
</a>