├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── config.yml
│ └── ---01-bug-report.yml
├── PULL_REQUEST_TEMPLATE.md
└── DISCUSSION_TEMPLATE
│ └── ideas.yml
├── public
├── robots.txt
└── firebase-messaging-sw.js
├── .gitignore
├── src
├── index.html
├── workbox-sw.js
├── components
│ ├── FAB.jsx
│ ├── FAB.module.scss
│ ├── Button.jsx
│ ├── Banner.jsx
│ ├── Banner.module.scss
│ ├── Checkbox.jsx
│ ├── AssignmentForm.module.scss
│ ├── Modal.jsx
│ ├── Button.module.scss
│ ├── Modal.module.scss
│ ├── Checkbox.module.scss
│ └── AssignmentForm.jsx
├── routes
│ ├── api
│ │ ├── verify-password
│ │ │ └── +get.js
│ │ ├── subscribe
│ │ │ └── +post.js
│ │ └── assignments
│ │ │ ├── [id]
│ │ │ ├── +delete.js
│ │ │ └── +patch.js
│ │ │ └── +post.js
│ ├── styles.module.scss
│ └── +pages.jsx
├── index.css
├── util
│ ├── pendingAssignments.js
│ ├── finishedAssignments.js
│ ├── dateUtils.js
│ └── firebase.js
└── scss
│ └── colors.scss
├── tsconfig.json
├── README.md
├── LICENSE
├── package.json
└── CONTRIBUTING.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | ko_fi: geeekyboy
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | dist
4 | lib
5 | .cache
6 | .env
7 | .env.local
8 | .idea
9 | .parcel-cache
10 | .vscode/settings.json
11 | .netlify
12 | /patches
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: 💡 Feature Request
4 | url: https://github.com/GeeekyBoy/csed-2024-assignments/discussions/categories/ideas
5 | about: Have an idea for a new feature? Let us know!
6 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "checkJs": false,
5 | "jsx": "preserve",
6 | "baseUrl": ".",
7 | "types": ["@mango-js/types"],
8 | "outDir": "dist",
9 | },
10 | "exclude": ["node_modules", "dist"]
11 | }
12 |
--------------------------------------------------------------------------------
/src/workbox-sw.js:
--------------------------------------------------------------------------------
1 | import { clientsClaim } from "workbox-core";
2 | import { registerRoute } from "workbox-routing";
3 | import { NetworkFirst } from "workbox-strategies";
4 |
5 | self.skipWaiting();
6 | clientsClaim();
7 |
8 | registerRoute(
9 | ({ request }) => request.destination !== "",
10 | new NetworkFirst({
11 | cacheName: "offlineCache",
12 | })
13 | );
14 |
--------------------------------------------------------------------------------
/src/components/FAB.jsx:
--------------------------------------------------------------------------------
1 | import * as styles from "./FAB.module.scss";
2 |
3 | function FAB({ children, label, onClick }) {
4 | const handleClick = (e) => {
5 | if (onClick) {
6 | onClick(e);
7 | }
8 | }
9 | return (
10 |
13 | );
14 | }
15 |
16 | export default FAB;
17 |
--------------------------------------------------------------------------------
/src/routes/api/verify-password/+get.js:
--------------------------------------------------------------------------------
1 | import * as dotenv from "dotenv";
2 |
3 | dotenv.config()
4 |
5 | export default async ({ headers }) => {
6 | const token = headers.authorization?.split(" ")[1];
7 | if (token !== process.env["ADMIN_TOKEN"]) {
8 | return {
9 | statusCode: 401,
10 | data: {
11 | message: "Unauthorized",
12 | },
13 | };
14 | }
15 | return {
16 | data: {
17 | message: "Password verified",
18 | },
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | font-size: 19.2px;
4 | font-family: Arial, Helvetica, sans-serif;
5 | }
6 |
7 | body {
8 | margin: 0;
9 | overflow: hidden;
10 | width: 100%;
11 | height: 100%;
12 | }
13 |
14 | *::-webkit-scrollbar {
15 | width: 8px;
16 |
17 | }
18 |
19 | *::-webkit-scrollbar-track {
20 | background: transparent;
21 | }
22 |
23 | *::-webkit-scrollbar-thumb {
24 | background-color: #dadce0;
25 | border-radius: 10px;
26 | }
27 |
28 | *::-webkit-scrollbar-thumb:hover {
29 | background-color: #80868b;
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/FAB.module.scss:
--------------------------------------------------------------------------------
1 | @import "../scss/colors";
2 |
3 | .root {
4 | position: fixed;
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | bottom: 18px;
9 | right: 18px;
10 | border-radius: 100%;
11 | font-size: 24px;
12 | color: $color-fill-color-text-on-accent-primary;
13 | background-color: $color-fill-color-accent-primary;
14 | outline: none;
15 | border: none;
16 | width: 56px;
17 | height: 56px;
18 | box-shadow: $effect-style-shadow-flyout;
19 | cursor: pointer;
20 | -webkit-tap-highlight-color: transparent;
21 | }
22 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Motivation
2 |
3 | This section should describe the motivation behind the changes, and reference the related GitHub issues. E.g. "Fixes #123".
4 |
5 | ## Description
6 |
7 | This section should describe the changes proposed in the pull request. Explain the rationale for the changes, and comment on your code where necessary.
8 |
9 | ## Testing
10 |
11 | This section should describe the testing that has been done on the changes. Include instructions for reproducing the tests/verification, and list any relevant details for your test configuration. If there are no tests to be run, please add a quick note explaining why.
12 |
--------------------------------------------------------------------------------
/src/components/Button.jsx:
--------------------------------------------------------------------------------
1 | import * as styles from "./Button.module.scss";
2 |
3 | function Button({
4 | children,
5 | label,
6 | fullWidth = false,
7 | secondary = false,
8 | disabled = false,
9 | onClick,
10 | }) {
11 | const handleClick = (e) => {
12 | if (!disabled && onClick) {
13 | onClick(e);
14 | }
15 | }
16 | return (
17 |
27 | );
28 | }
29 |
30 | export default Button;
31 |
--------------------------------------------------------------------------------
/src/util/pendingAssignments.js:
--------------------------------------------------------------------------------
1 | export const load = () => {
2 | return (localStorage.getItem("pending") || "").split(",");
3 | };
4 |
5 | export const toggle = (id) => {
6 | const pending = load();
7 | const index = pending.indexOf(id);
8 | if (index == -1) {
9 | pending.push(id);
10 | } else {
11 | pending.splice(index, 1);
12 | }
13 | localStorage.setItem("pending", pending.join(","));
14 | return pending;
15 | };
16 |
17 | export const sync = (assignments) => {
18 | const ids = assignments.map((assignment) => assignment[0]);
19 | const pending = load().filter((id) => ids.includes(id));
20 | localStorage.setItem("pending", pending.join(","));
21 | return pending;
22 | };
23 |
--------------------------------------------------------------------------------
/src/util/finishedAssignments.js:
--------------------------------------------------------------------------------
1 | export const load = () => {
2 | return (localStorage.getItem("finished") || "").split(",");
3 | };
4 |
5 | export const toggle = (id) => {
6 | const finished = load();
7 | const index = finished.indexOf(id);
8 | if (index == -1) {
9 | finished.push(id);
10 | } else {
11 | finished.splice(index, 1);
12 | }
13 | localStorage.setItem("finished", finished.join(","));
14 | return finished;
15 | };
16 |
17 | export const sync = (assignments) => {
18 | const ids = assignments.map((assignment) => assignment[0]);
19 | const finished = load().filter((id) => ids.includes(id));
20 | localStorage.setItem("finished", finished.join(","));
21 | return finished;
22 | };
23 |
--------------------------------------------------------------------------------
/src/components/Banner.jsx:
--------------------------------------------------------------------------------
1 | import * as styles from "./Banner.module.scss";
2 |
3 | function Banner({ children, variant = "attention", onClick }) {
4 | const handleClick = () => {
5 | if (onClick) {
6 | onClick();
7 | }
8 | };
9 | return (
10 |
19 | {children}
20 |
21 | );
22 | }
23 |
24 | export default Banner;
25 |
--------------------------------------------------------------------------------
/src/routes/api/subscribe/+post.js:
--------------------------------------------------------------------------------
1 | import * as dotenv from "dotenv";
2 | import { getApps, initializeApp, cert } from "firebase-admin/app";
3 | import { getMessaging } from "firebase-admin/messaging";
4 |
5 | dotenv.config()
6 |
7 | const app = getApps()[0] || initializeApp({
8 | credential: cert({
9 | projectId: process.env["FIREBASE_ADMIN_PROJECT_ID"],
10 | clientEmail: process.env["FIREBASE_ADMIN_CLIENT_EMAIL"],
11 | privateKey: process.env["FIREBASE_ADMIN_PRIVATE_KEY"].replace(/\\n/g, "\n"),
12 | }),
13 | databaseURL: `https://${process.env["FIREBASE_ADMIN_PROJECT_ID"]}.firebaseio.com`,
14 | });
15 |
16 | const messaging = getMessaging(app);
17 |
18 | export default async ({ body }) => {
19 | const token = body["token"];
20 | await messaging.subscribeToTopic(token, "assignments");
21 | return {
22 | data: {
23 | message: "Subscribed",
24 | },
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/public/firebase-messaging-sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener("notificationclick", function (event) {
2 | event.stopImmediatePropagation();
3 | event.notification.close();
4 | clients.openWindow("https://csed2024assignments.geeekyboy.com/");
5 | });
6 |
7 | importScripts("https://www.gstatic.com/firebasejs/9.18.0/firebase-app-compat.js");
8 | importScripts("https://www.gstatic.com/firebasejs/9.18.0/firebase-messaging-compat.js");
9 |
10 | firebase.initializeApp({
11 | apiKey: "AIzaSyBRTHLN9HHQjevO4yHIqQeokv3VCzaZHXw",
12 | authDomain: "csed-assignments-4deb5.firebaseapp.com",
13 | databaseURL: "https://csed-assignments-4deb5-default-rtdb.firebaseio.com",
14 | projectId: "csed-assignments-4deb5",
15 | storageBucket: "csed-assignments-4deb5.appspot.com",
16 | messagingSenderId: "435235328157",
17 | appId: "1:435235328157:web:51647b0916d880972b8474",
18 | measurementId: "G-3HB4Z9SEGM",
19 | });
20 |
21 | const messaging = firebase.messaging();
22 |
--------------------------------------------------------------------------------
/src/components/Banner.module.scss:
--------------------------------------------------------------------------------
1 | @import "../scss/colors";
2 |
3 | .root {
4 | width: calc(100% - 40px);
5 | margin-top: -10px;
6 | padding: 8px 20px;
7 | background-color: $color-fill-color-accent-primary;
8 | color: $color-fill-color-text-on-accent-primary;
9 | font-size: 0.875rem;
10 | text-align: center;
11 | &.clickable {
12 | cursor: pointer;
13 | }
14 | &.attention {
15 | background-color: $color-fill-color-accent-primary;
16 | color: $color-fill-color-text-on-accent-primary;
17 | }
18 | &.success {
19 | background-color: $color-fill-color-system-success-background;
20 | color: $color-fill-color-system-success;
21 | }
22 | &.caution {
23 | background-color: $color-fill-color-system-caution-background;
24 | color: $color-fill-color-system-caution;
25 | }
26 | &.critical {
27 | background-color: $color-fill-color-system-critical-background;
28 | color: $color-fill-color-system-critical;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/Checkbox.jsx:
--------------------------------------------------------------------------------
1 | import CheckmarkIcon from "jsx:@fluentui/svg-icons/icons/checkmark_12_regular.svg";
2 | import * as styles from "./Checkbox.module.scss";
3 |
4 | function Checkbox({
5 | name,
6 | label,
7 | $value,
8 | readOnly,
9 | disabled,
10 | }) {
11 | const handleToggle = () => {
12 | if (disabled || readOnly) return;
13 | $value = !$value;
14 | }
15 | return (
16 |
21 |
29 | {label && (
30 |
33 | )}
34 |
35 | );
36 | }
37 |
38 | export default Checkbox;
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CSED 2024 Assignments
2 |
3 |
4 |
5 | [](https://github.com/GeeekyBoy/csed-2024-assignments/blob/main/LICENSE)
6 | [](https://github.com/GeeekyBoy/csed-2024-assignments)
7 |
8 |
9 |
10 | ## Overview
11 |
12 | CSED 2024 Assignments is a simple assignments tracker for CSED 2024 students. Through this app, students can mark their assignments as pending or done. They can also be notified when any assignment is added or updated.
13 |
14 | ## Contributing
15 |
16 | Read our [contributing guide](CONTRIBUTING.md) to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to CSED 2024 Assignments.
17 |
18 | ## License
19 |
20 | CSED 2024 Assignments is licensed under the [MIT License](LICENSE).
21 |
22 | 
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023-present GeeekyBoy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "csed-2024-assignments",
3 | "description": "All the assignments for CSED 2024 in one place.",
4 | "keywords": [
5 | "csed",
6 | "assignments",
7 | "mango"
8 | ],
9 | "license": "MIT",
10 | "scripts": {
11 | "start": "mango-scripts start",
12 | "build": "mango-scripts build",
13 | "serve": "mango-scripts serve"
14 | },
15 | "devDependencies": {
16 | "@mango-js/scripts": "^1.0.0-alpha.29",
17 | "@mango-js/types": "^1.0.0-alpha.29",
18 | "process": "^0.11.10"
19 | },
20 | "dependencies": {
21 | "firebase": "^10.7.2",
22 | "firebase-admin": "^12.0.0",
23 | "@fluentui/svg-icons": "^1.1.225",
24 | "canvas-confetti": "^1.9.2",
25 | "workbox-core": "^7.0.0",
26 | "workbox-routing": "^7.0.0",
27 | "workbox-strategies": "^7.0.0",
28 | "dotenv": "^16.3.2"
29 | },
30 | "alias": {
31 | "process": "process/browser.js"
32 | },
33 | "config": {
34 | "publicUrl": "/",
35 | "browsers": "defaults",
36 | "cdn": "self",
37 | "devServer": {
38 | "port": 4000
39 | },
40 | "prodServer": {
41 | "port": 3000
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/routes/api/assignments/[id]/+delete.js:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { getApps, initializeApp, cert } from "firebase-admin/app";
3 | import { getFirestore } from "firebase-admin/firestore";
4 |
5 | dotenv.config()
6 |
7 | const app = getApps()[0] || initializeApp({
8 | credential: cert({
9 | projectId: process.env["FIREBASE_ADMIN_PROJECT_ID"],
10 | clientEmail: process.env["FIREBASE_ADMIN_CLIENT_EMAIL"],
11 | privateKey: process.env["FIREBASE_ADMIN_PRIVATE_KEY"].replace(/\\n/g, "\n"),
12 | }),
13 | databaseURL: `https://${process.env["FIREBASE_ADMIN_PROJECT_ID"]}.firebaseio.com`,
14 | });
15 |
16 | const db = getFirestore(app);
17 |
18 | export default async ({ body, route, headers }) => {
19 | const token = headers.authorization?.split(" ")[1];
20 | if (token !== process.env["ADMIN_TOKEN"]) {
21 | return {
22 | statusCode: 401,
23 | data: {
24 | message: "Unauthorized",
25 | },
26 | };
27 | }
28 | const assignmentId = route.params.id;
29 | await db.doc(`assignments/${assignmentId}`).delete();
30 | return {
31 | data: {
32 | message: "Assignment updated",
33 | },
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/AssignmentForm.module.scss:
--------------------------------------------------------------------------------
1 | @import "../scss/colors";
2 |
3 | .modalForm {
4 | position: relative;
5 | display: grid;
6 | grid-template-columns: repeat(2, 1fr);
7 | grid-template-rows: repeat(3, auto);
8 | grid-gap: 10px;
9 | label {
10 | display: block;
11 | font-size: 0.875rem;
12 | margin-bottom: 5px;
13 | }
14 | input {
15 | width: 100%;
16 | height: 32px;
17 | padding: 6px 10px;
18 | border: 1px solid transparent;
19 | border-radius: 4px;
20 | border-color: $color-stroke-color-card-stroke-default;
21 | border-bottom-color: $color-stroke-color-control-strong-stroke-default;
22 | background-color: $color-fill-color-control-default;
23 | outline: none;
24 | box-sizing: border-box;
25 | -moz-box-sizing: border-box;
26 | -webkit-box-sizing: border-box;
27 | margin-bottom: 10px;
28 | &:disabled {
29 | opacity: 0.6;
30 | cursor: not-allowed;
31 | }
32 | &:hover {
33 | background-color: $color-fill-color-control-secondary;
34 | }
35 | &:focus {
36 | border-radius: 4px 4px 0 0;
37 | background-color: $color-fill-color-control-input-active;
38 | border-bottom-color: $color-fill-color-accent-primary;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Modal.jsx:
--------------------------------------------------------------------------------
1 | import Button from "./Button";
2 | import * as styles from "./Modal.module.scss";
3 |
4 | function Modal({
5 | title,
6 | children,
7 | submitLabel,
8 | cancelLabel,
9 | onSubmit,
10 | onCancel,
11 | disabled,
12 | $active,
13 | }) {
14 | const handleCancel = () => {
15 | if (onCancel) {
16 | onCancel();
17 | }
18 | $active = false;
19 | }
20 | const handleSubmit = () => {
21 | if (onSubmit) {
22 | onSubmit();
23 | }
24 | }
25 | return (
26 |
27 |
28 |
29 |
33 |
41 |
42 |
43 | );
44 | }
45 |
46 | export default Modal;
47 |
--------------------------------------------------------------------------------
/src/routes/api/assignments/+post.js:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { getApps, initializeApp, cert } from "firebase-admin/app";
3 | import { getFirestore } from "firebase-admin/firestore";
4 | import { getMessaging } from "firebase-admin/messaging";
5 |
6 | dotenv.config()
7 |
8 | const app = getApps()[0] || initializeApp({
9 | credential: cert({
10 | projectId: process.env["FIREBASE_ADMIN_PROJECT_ID"],
11 | clientEmail: process.env["FIREBASE_ADMIN_CLIENT_EMAIL"],
12 | privateKey: process.env["FIREBASE_ADMIN_PRIVATE_KEY"].replace(/\\n/g, "\n"),
13 | }),
14 | databaseURL: `https://${process.env["FIREBASE_ADMIN_PROJECT_ID"]}.firebaseio.com`,
15 | });
16 |
17 | const db = getFirestore(app);
18 | const messaging = getMessaging(app);
19 |
20 | export default async ({ body, headers }) => {
21 | const token = headers.authorization?.split(" ")[1];
22 | if (token !== process.env["ADMIN_TOKEN"]) {
23 | return {
24 | statusCode: 401,
25 | data: {
26 | message: "Unauthorized",
27 | },
28 | };
29 | }
30 | await db.collection("assignments").add(body);
31 | await messaging.sendToTopic("assignments", {
32 | notification: {
33 | title: "New Assignment",
34 | body: `${body["subject"]} - ${body["assignment"]}`,
35 | },
36 | });
37 | return {
38 | data: {
39 | message: "Assignment added",
40 | },
41 | };
42 | };
43 |
--------------------------------------------------------------------------------
/src/routes/api/assignments/[id]/+patch.js:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { getApps, initializeApp, cert } from "firebase-admin/app";
3 | import { getFirestore } from "firebase-admin/firestore";
4 | import { getMessaging } from "firebase-admin/messaging";
5 |
6 | dotenv.config()
7 |
8 | const app = getApps()[0] || initializeApp({
9 | credential: cert({
10 | projectId: process.env["FIREBASE_ADMIN_PROJECT_ID"],
11 | clientEmail: process.env["FIREBASE_ADMIN_CLIENT_EMAIL"],
12 | privateKey: process.env["FIREBASE_ADMIN_PRIVATE_KEY"].replace(/\\n/g, "\n"),
13 | }),
14 | databaseURL: `https://${process.env["FIREBASE_ADMIN_PROJECT_ID"]}.firebaseio.com`,
15 | });
16 |
17 | const db = getFirestore(app);
18 | const messaging = getMessaging(app);
19 |
20 | export default async ({ body, route, headers }) => {
21 | const token = headers.authorization?.split(" ")[1];
22 | if (token !== process.env["ADMIN_TOKEN"]) {
23 | return {
24 | statusCode: 401,
25 | data: {
26 | message: "Unauthorized",
27 | },
28 | };
29 | }
30 | const assignmentId = route.params.id;
31 | await db.doc(`assignments/${assignmentId}`).update(body);
32 | await messaging.sendToTopic("assignments", {
33 | notification: {
34 | title: "Assignment Updated",
35 | body: `${body["subject"]} - ${body["assignment"]}`,
36 | },
37 | });
38 | return {
39 | data: {
40 | message: "Assignment updated",
41 | },
42 | };
43 | };
44 |
--------------------------------------------------------------------------------
/src/util/dateUtils.js:
--------------------------------------------------------------------------------
1 | export const timestampToDate = (timestamp) => {
2 | const date = timestamp ? new Date(timestamp) : new Date();
3 | let month = date.getMonth() + 1;
4 | let dayOfMonth = date.getDate();
5 | const year = date.getFullYear();
6 | month = month < 10 ? "0" + month : month;
7 | dayOfMonth = dayOfMonth < 10 ? "0" + dayOfMonth : dayOfMonth;
8 | return year + "-" + month + "-" + dayOfMonth;
9 | }
10 |
11 | export const timestampToTime = (timestamp) => {
12 | const date = timestamp ? new Date(timestamp) : new Date();
13 | let hour = date.getHours();
14 | let minute = date.getMinutes();
15 | hour = hour < 10 ? "0" + hour : hour;
16 | minute = minute < 10 ? "0" + minute : minute;
17 | return hour + ":" + minute;
18 | }
19 |
20 | export const dateToString = (date, showTime, beforeSection) => {
21 | const day = date.getDay();
22 | const month = date.getMonth() + 1;
23 | const dayOfMonth = date.getDate();
24 | let hour = date.getHours();
25 | let minute = date.getMinutes();
26 | const ampm = hour >= 12 ? "PM" : "AM";
27 | hour = hour % 12;
28 | hour = hour ? hour : 12;
29 | minute = minute < 10 ? "0" + minute : minute;
30 | const strTime = hour + ":" + minute + " " + ampm;
31 | const days = [
32 | "Sunday",
33 | "Monday",
34 | "Tuesday",
35 | "Wednesday",
36 | "Thursday",
37 | "Friday",
38 | "Saturday",
39 | ];
40 | return (
41 | days[day] +
42 | " " +
43 | dayOfMonth +
44 | "/" +
45 | month +
46 | (showTime ? " @ " + strTime : "") +
47 | (beforeSection ? " before section" : "")
48 | );
49 | }
--------------------------------------------------------------------------------
/.github/DISCUSSION_TEMPLATE/ideas.yml:
--------------------------------------------------------------------------------
1 | title: "Sample title"
2 | labels: ["enhancement"]
3 | body:
4 | - type: markdown
5 | attributes:
6 | value: |
7 | Thanks for taking the time to propose a new feature! Please make sure to include as much information as possible.
8 | - type: checkboxes
9 | attributes:
10 | label: Is this feature proposed before?
11 | description: Please search to see if your idea is already proposed.
12 | options:
13 | - label: I have searched the existing proposals and have not found a similar one
14 | required: true
15 | - type: textarea
16 | attributes:
17 | label: Motivation
18 | description: Describe the motivation behind this idea, and why you think it would be a good addition. Feel free to support your argument with real-world examples. This section should be high-level and not include technical details or implementation ideas.
19 | validations:
20 | required: true
21 | - type: textarea
22 | attributes:
23 | label: Proposed Solution
24 | description: Describe the solution you'd like. Try to be as specific as possible. Feel free to include code snippets, diagrams, or photos to help explain your idea.
25 | validations:
26 | required: true
27 | - type: textarea
28 | attributes:
29 | label: Alternative Solutions
30 | description: Describe any alternative solutions you've considered. This is the place to discuss trade-offs you might have thought of.
31 | validations:
32 | required: true
33 | - type: textarea
34 | attributes:
35 | label: Competitors
36 | description: What other products have this feature? What do you like about how they implement it? What do you dislike about how they implement it?
37 | validations:
38 | required: false
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---01-bug-report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: File a bug report
3 | title: "Sample title"
4 | labels: ["bug"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to fill out this bug report! Please make sure to include as much information as possible.
10 | - type: checkboxes
11 | attributes:
12 | label: Is there an existing issue for this?
13 | description: Please search to see if an issue already exists for the bug you encountered.
14 | options:
15 | - label: I have searched the existing issues and have not found a similar one
16 | required: true
17 | - type: textarea
18 | attributes:
19 | label: Current Behavior
20 | description: A concise description of what you're experiencing.
21 | validations:
22 | required: true
23 | - type: textarea
24 | attributes:
25 | label: Expected Behavior
26 | description: A concise description of what you expected to happen.
27 | validations:
28 | required: true
29 | - type: textarea
30 | attributes:
31 | label: Steps To Reproduce
32 | description: Steps to reproduce the behavior.
33 | placeholder: |
34 | 1. In this environment...
35 | 2. Do '...'
36 | 3. See error...
37 | validations:
38 | required: true
39 | - type: textarea
40 | attributes:
41 | label: Environment
42 | description: |
43 | examples:
44 | - **OS**: Ubuntu 20.04
45 | - **Browser**: Chrome 90
46 | value: |
47 | - **OS**:
48 | - **Browser**:
49 | validations:
50 | required: true
51 | - type: checkboxes
52 | id: willing-to-pr
53 | attributes:
54 | label: I would be interested in opening a PR for this issue
55 | description: |
56 | If you have the time and interest, we always appreciate pull requests!
57 | options:
58 | - label: I would be interested in opening a PR for this issue
59 | required: false
60 |
--------------------------------------------------------------------------------
/src/components/Button.module.scss:
--------------------------------------------------------------------------------
1 | @import "../scss/colors";
2 |
3 | .root {
4 | min-width: 86px;
5 | padding: 7px 14px;
6 | border: 1px solid transparent;
7 | border-radius: 4px;
8 | border-color: $color-stroke-color-control-stroke-default;
9 | border-bottom-color: $color-stroke-color-control-stroke-secondary;
10 | outline: none;
11 | background-color: $color-fill-color-accent-primary;
12 | color: $color-fill-color-text-on-accent-primary;
13 | font-size: 0.875rem;
14 | cursor: pointer;
15 | transition: background-color 0.3s, color 0.2s;
16 | -webkit-tap-highlight-color: transparent;
17 | &.fullWidth {
18 | width: 100%;
19 | }
20 | &.secondary {
21 | background-color: $color-fill-color-control-default;
22 | color: $color-fill-color-text-primary;
23 | }
24 | &:hover {
25 | background-color: $color-fill-color-accent-secondary;
26 | &.secondary {
27 | background-color: $color-fill-color-control-secondary;
28 | }
29 | }
30 | &:active {
31 | border-color: $color-stroke-color-control-stroke-on-accent-default;
32 | border-bottom-color: $color-stroke-color-control-stroke-on-accent-default;
33 | background-color: $color-fill-color-accent-tertiary;
34 | color: $color-fill-color-text-on-accent-secondary;
35 | &.secondary {
36 | border-color: $color-stroke-color-control-stroke-default;
37 | border-bottom-color: $color-stroke-color-control-stroke-default;
38 | color: $color-fill-color-text-secondary;
39 | }
40 | }
41 | &:disabled {
42 | border-color: transparent !important;
43 | border-bottom-color: transparent !important;
44 | background-color: $color-fill-color-accent-disabled !important;
45 | color: $color-fill-color-text-on-accent-disabled !important;
46 | cursor: not-allowed;
47 | &.secondary {
48 | border-color: $color-stroke-color-control-stroke-default !important;
49 | border-bottom-color: $color-stroke-color-control-stroke-default !important;
50 | background-color: $color-fill-color-control-disabled !important;
51 | color: $color-fill-color-text-disabled !important;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/Modal.module.scss:
--------------------------------------------------------------------------------
1 | @import "../scss/colors";
2 |
3 | .root {
4 | display: none;
5 | position: absolute;
6 | z-index: 10;
7 |
8 | &.active {
9 | display: block;
10 | }
11 | .modalOverlay {
12 | position: fixed;
13 | top: 0;
14 | left: 0;
15 | width: 100%;
16 | height: 100%;
17 | background-color: #000000;
18 | opacity: 0.5;
19 | }
20 | .modalContent {
21 | position: fixed;
22 | margin: 0 auto;
23 | width: 100%;
24 | bottom: 0;
25 | background-color: #ffffff;
26 | color: #000000;
27 | line-height: 1.3;
28 | overflow: hidden;
29 | border-radius: 8px;
30 | background-color: $color-background-fill-color-solid-background-base;
31 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
32 |
33 | @media (min-width: 768px) {
34 | position: relative;
35 | max-width: 600px;
36 | }
37 |
38 | .modalHeader {
39 | position: relative;
40 | text-align: center;
41 | margin-bottom: 15px;
42 | color: $color-fill-color-text-primary;
43 | font-weight: 600;
44 | .modalClose {
45 | position: absolute;
46 | top: 0;
47 | right: 0;
48 | padding: 1rem;
49 | cursor: pointer;
50 | }
51 | }
52 | .modalHeaderFooter {
53 | padding: 20px;
54 | overflow: hidden;
55 | background-color: $color-background-fill-color-layer-alt;
56 | box-sizing: border-box;
57 | -moz-box-sizing: border-box;
58 | -webkit-box-sizing: border-box;
59 | }
60 | .modalFooter {
61 | display: flex;
62 | flex-direction: row;
63 | align-items: center;
64 | justify-content: flex-end;
65 | width: 100%;
66 | padding: 15px 20px;
67 | gap: 10px;
68 | box-sizing: border-box;
69 | -moz-box-sizing: border-box;
70 | -webkit-box-sizing: border-box;
71 | }
72 | }
73 | @media (min-width: 768px) {
74 | position: fixed;
75 | width: 100%;
76 | height: 100%;
77 | top: 0;
78 | left: 0;
79 | align-items: center;
80 | justify-content: center;
81 | &.active {
82 | display: flex;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/components/Checkbox.module.scss:
--------------------------------------------------------------------------------
1 | @import "../scss/colors";
2 |
3 | .CheckboxShell {
4 | display: flex;
5 | position: relative;
6 | flex-direction: row;
7 | align-items: flex-start;
8 | justify-content: center;
9 | width: fit-content;
10 | padding: 6px 4px;
11 | background-color: transparent;
12 | gap: 8px;
13 | & > label {
14 | width: max-content;
15 | margin-bottom: 0;
16 | color: $color-fill-color-text-primary;
17 | font-size: 0.875rem;
18 | cursor: pointer;
19 | }
20 | &.disabled {
21 | & > label {
22 | color: $color-fill-color-text-disabled;
23 | }
24 | .CheckToggle {
25 | border-color: $color-stroke-color-control-strong-stroke-disabled;
26 | background-color: $color-fill-color-control-alt-disabled;
27 | &.checked {
28 | border-color: transparent;
29 | background-color: $color-fill-color-accent-disabled;
30 | color: $color-fill-color-text-on-accent-disabled;
31 | }
32 | }
33 | & * {
34 | cursor: not-allowed;
35 | }
36 | }
37 | &.readOnly * {
38 | cursor: default;
39 | }
40 | }
41 |
42 | .CheckToggle {
43 | display: flex;
44 | align-items: center;
45 | justify-content: center;
46 | width: 20px;
47 | height: 20px;
48 | padding: 0;
49 | border: 1px solid;
50 | border-radius: 3px;
51 | border-color: $color-stroke-color-control-strong-stroke-default;
52 | outline: none;
53 | background-color: transparent;
54 | background-color: $color-fill-color-control-alt-secondary;
55 | cursor: pointer;
56 | transition: border-color 0.2s, background-color 0.2s;
57 | &:hover {
58 | background-color: $color-fill-color-control-alt-tertiary;
59 | }
60 | &:active {
61 | border-color: $color-stroke-color-control-strong-stroke-disabled;
62 | background-color: $color-fill-color-control-alt-quarternary;
63 | }
64 | &.checked {
65 | border-color: transparent;
66 | background-color: $color-fill-color-accent-primary;
67 | color: $color-fill-color-text-on-accent-primary;
68 | &:hover {
69 | background-color: $color-fill-color-accent-secondary;
70 | }
71 | &:active {
72 | background-color: $color-fill-color-accent-tertiary;
73 | color: $color-fill-color-text-on-accent-secondary;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/util/firebase.js:
--------------------------------------------------------------------------------
1 | // @mango
2 | import { initializeApp } from "firebase/app";
3 | import { getAnalytics } from "firebase/analytics";
4 | import { getMessaging, onMessage, getToken, isSupported } from "firebase/messaging";
5 | import { initializeFirestore, enableMultiTabIndexedDbPersistence } from "firebase/firestore";
6 |
7 | const firebaseConfig = {
8 | apiKey: process.env["FIREBASE_CLIENT_API_KEY"],
9 | authDomain: process.env["FIREBASE_CLIENT_AUTH_DOMAIN"],
10 | databaseURL: process.env["FIREBASE_CLIENT_DATABASE_URL"],
11 | projectId: process.env["FIREBASE_CLIENT_PROJECT_ID"],
12 | storageBucket: process.env["FIREBASE_CLIENT_STORAGE_BUCKET"],
13 | messagingSenderId: process.env["FIREBASE_CLIENT_MESSAGING_SENDER_ID"],
14 | appId: process.env["FIREBASE_CLIENT_APP_ID"],
15 | measurementId: process.env["FIREBASE_CLIENT_MEASUREMENT_ID"],
16 | };
17 |
18 | const app = initializeApp(firebaseConfig);
19 | getAnalytics(app);
20 |
21 | const db = initializeFirestore(app, {
22 | cacheSizeBytes: 5e6,
23 | });
24 |
25 | enableMultiTabIndexedDbPersistence(db).catch((err) => {
26 | if (err.code === "failed-precondition") {
27 | console.error(
28 | "Multiple tabs open, persistence can only be enabled in one tab at a a time."
29 | );
30 | } else if (err.code === "unimplemented") {
31 | console.error(
32 | "The current browser does not support all of the features required to enable persistence"
33 | );
34 | }
35 | });
36 |
37 | let $notificationsState =
38 | (typeof navigator !== 'undefined') &&
39 | (navigator.cookieEnabled) &&
40 | ('serviceWorker' in navigator) &&
41 | ('Notification' in window) &&
42 | ('PushManager' in window)
43 | ? Notification.permission
44 | : 'unsupported';
45 |
46 | isSupported().then((supported) => {
47 | if (!supported) {
48 | $notificationsState = 'unsupported';
49 | }
50 | });
51 |
52 | const enableNotifications = async () => {
53 | if (await isSupported()) {
54 | Notification.requestPermission().then((permission) => {
55 | $notificationsState = permission;
56 | if (permission === 'granted') {
57 | const messaging = getMessaging(app);
58 | getToken(messaging, {
59 | vapidKey: process.env["FIREBASE_CLIENT_VAPID_KEY"],
60 | }).then(async (currentToken) => {
61 | if (currentToken) {
62 | try {
63 | await fetch("/api/subscribe", {
64 | method: "POST",
65 | body: JSON.stringify({
66 | token: currentToken,
67 | }),
68 | headers: {
69 | "Content-Type": "application/json",
70 | },
71 | });
72 | onMessage(
73 | messaging,
74 | (payload) => {
75 | console.log("payload", payload);
76 | },
77 | (error) => {
78 | console.error("Error while subscribing to notifications", error);
79 | }
80 | );
81 | } catch {
82 | console.error(`Can't subscribe to notifications`);
83 | }
84 | }
85 | });
86 | }
87 | });
88 | }
89 | }
90 |
91 | if (('Notification' in window) && Notification.permission === "granted") {
92 | enableNotifications();
93 | }
94 |
95 | export {
96 | $notificationsState,
97 | enableNotifications,
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/src/routes/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import "../scss/colors";
2 |
3 | @keyframes pulse {
4 | 0% {
5 | transform: scale(0.95);
6 | opacity: 0.5;
7 | }
8 | 70% {
9 | transform: scale(1);
10 | opacity: 1;
11 | }
12 | 100% {
13 | transform: scale(0.95);
14 | opacity: 0.5;
15 | }
16 | }
17 |
18 | .root {
19 | position: fixed;
20 | width: 100%;
21 | height: 100%;
22 | background-color: #f9fbfd;
23 | background-position: center;
24 | color: #000000;
25 | line-height: 1.3;
26 | overflow: auto;
27 |
28 | table {
29 | display: table;
30 | width: 100%;
31 | max-width: 600px;
32 | border-collapse: collapse;
33 | border-spacing: 0;
34 | margin: 0;
35 | padding: 0;
36 | border: 0;
37 | font-size: 100%;
38 | font: inherit;
39 | background: transparent;
40 | border-collapse: collapse;
41 | border-spacing: 0;
42 | caption-side: top;
43 | cursor: auto;
44 | direction: ltr;
45 | empty-cells: show;
46 | font-family: inherit;
47 | font-size: inherit;
48 | font-style: inherit;
49 | font-variant: inherit;
50 | font-weight: inherit;
51 | letter-spacing: inherit;
52 | line-height: inherit;
53 | list-style: none;
54 | text-align: left;
55 | text-indent: 0;
56 | text-transform: none;
57 | visibility: visible;
58 | white-space: normal;
59 | word-spacing: normal;
60 | th {
61 | background-color: #999999;
62 | border: 1px solid #000000;
63 | padding: 0.3rem 0.5rem;
64 | font-size: 1.2rem;
65 | }
66 | td {
67 | position: relative;
68 | border: 1px solid #000000;
69 | padding: 0.3rem 0.5rem;
70 | font-family: 'Times New Roman', Times, serif;
71 | &.pending {
72 | color: $color-fill-color-system-caution;
73 | animation: pulse 2s infinite;
74 | }
75 | &.done {
76 | text-decoration: line-through;
77 | color: $color-fill-color-system-success;
78 | }
79 | }
80 | }
81 |
82 | a {
83 | color: #000000;
84 | }
85 |
86 | footer {
87 | font-size: 0.8rem;
88 | }
89 | }
90 |
91 | .filters {
92 | display: flex;
93 | flex-direction: row;
94 | justify-content: space-between;
95 | align-items: center;
96 | width: 100%;
97 | max-width: 600px;
98 | margin-bottom: 12px;
99 | }
100 |
101 | .StatusToggle {
102 | position: absolute;
103 | top: 50%;
104 | margin-top: -10px;
105 | left: -28px;
106 | display: flex;
107 | align-items: center;
108 | justify-content: center;
109 | width: 20px;
110 | min-width: 20px;
111 | height: 20px;
112 | min-height: 20px;
113 | padding: 2.5px;
114 | border: 1px solid;
115 | border-radius: 100%;
116 | border-color: $color-stroke-color-control-strong-stroke-default;
117 | outline: none;
118 | background-color: $color-fill-color-control-alt-secondary;
119 | cursor: pointer;
120 | transition: border-color 0.2s;
121 | &.done {
122 | border-color: $color-fill-color-accent-primary;
123 | background-color: $color-fill-color-accent-primary;
124 | color: $color-fill-color-text-on-accent-primary;
125 | & > svg * {
126 | stroke-width: 64px;
127 | }
128 | }
129 | }
130 |
131 | .DeleteButton {
132 | position: absolute;
133 | top: 50%;
134 | margin-top: -10px;
135 | left: -28px;
136 | display: flex;
137 | align-items: center;
138 | justify-content: center;
139 | width: 20px;
140 | min-width: 20px;
141 | height: 20px;
142 | min-height: 20px;
143 | padding: 2.5px;
144 | border: none;
145 | outline: none;
146 | background-color: transparent;
147 | cursor: pointer;
148 | transition: border-color 0.2s;
149 | }
150 |
151 | .viewport {
152 | background-color: #ffffff;
153 | color: #000000;
154 | width: 100%;
155 | min-height: 100vh;
156 | max-width: 800px;
157 | border-width: 1px;
158 | border-style: solid;
159 | padding: 50px 30px;
160 | margin-bottom: 12px;
161 | box-sizing: border-box;
162 | -moz-box-sizing: border-box;
163 | -webkit-box-sizing: border-box;
164 | border-color: $color-stroke-color-card-stroke-default;
165 |
166 | a {
167 | color: #1155cc;
168 | text-decoration: underline;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/components/AssignmentForm.jsx:
--------------------------------------------------------------------------------
1 | import Modal from "./Modal";
2 | import Checkbox from "./Checkbox";
3 | import * as dateUtils from "../util/dateUtils";
4 | import * as styles from "./AssignmentForm.module.scss";
5 |
6 | function AssignmentForm({ $active, currData = [] }) {
7 | let $isBusy = false;
8 | let $id,
9 | $subject,
10 | $assignment,
11 | $dueDate,
12 | $dueTime,
13 | $link,
14 | $showTime,
15 | $beforeSection;
16 | $createIEffect(() => {
17 | $id = currData[0] !== undefined ? currData[0] : null;
18 | $subject = currData[1] || "";
19 | $assignment = currData[2] || "";
20 | $dueDate = dateUtils.timestampToDate(currData[3]);
21 | $dueTime = dateUtils.timestampToTime(currData[3]);
22 | $link = currData[4] || "";
23 | $showTime = currData[5] || false;
24 | $beforeSection = currData[6] || false;
25 | }, [currData]);
26 | async function handleSubmit() {
27 | $isBusy = true;
28 | if (!localStorage.getItem("token")) {
29 | localStorage.setItem("token", prompt("Please enter your password"));
30 | }
31 | const id = $id;
32 | const subject = $subject;
33 | const assignment = $assignment;
34 | const dueDate = new Date($dueDate + " " + $dueTime).getTime();
35 | const link = $link;
36 | const showTime = $showTime;
37 | const beforeSection = $beforeSection;
38 | if ($id !== null) {
39 | const res = await fetch(`/api/assignments/${id}`, {
40 | method: "PATCH",
41 | headers: {
42 | "Content-Type": "application/json",
43 | "Authorization": "Bearer " + localStorage.getItem("token"),
44 | },
45 | body: JSON.stringify({
46 | subject,
47 | assignment,
48 | dueDate,
49 | link,
50 | showTime,
51 | beforeSection,
52 | })
53 | });
54 | if (res.status === 401) {
55 | localStorage.removeItem("token");
56 | alert("Invalid password");
57 | } else {
58 | $active = false;
59 | }
60 | } else {
61 | const res = await fetch("/api/assignments", {
62 | method: "POST",
63 | headers: {
64 | "Content-Type": "application/json",
65 | "Authorization": "Bearer " + localStorage.getItem("token"),
66 | },
67 | body: JSON.stringify({
68 | subject,
69 | assignment,
70 | dueDate,
71 | link,
72 | showTime,
73 | beforeSection,
74 | })
75 | });
76 | if (res.status === 401) {
77 | localStorage.removeItem("token");
78 | alert("Invalid password");
79 | } else {
80 | $active = false;
81 | }
82 | }
83 | $isBusy = false;
84 | }
85 | return (
86 |
94 |
149 |
150 | );
151 | }
152 |
153 | export default AssignmentForm;
154 |
--------------------------------------------------------------------------------
/src/scss/colors.scss:
--------------------------------------------------------------------------------
1 | $color-fill-color-text-primary: rgba(0,0,0,0.896);
2 | $color-fill-color-text-secondary: rgba(0,0,0,0.606);
3 | $color-fill-color-text-tertiary: rgba(0,0,0,0.446);
4 | $color-fill-color-text-disabled: rgba(0,0,0,0.361);
5 | $color-fill-color-accent-text-disabled: rgba(0,0,0,0.361);
6 | $color-fill-color-text-on-accent-primary: rgba(255,255,255,1);
7 | $color-fill-color-text-on-accent-secondary: rgba(255,255,255,0.700);
8 | $color-fill-color-text-on-accent-disabled: rgba(255,255,255,1);
9 | $color-fill-color-text-on-accent-selected-text: rgba(255,255,255,1);
10 | $color-fill-color-control-transparent: rgba(255,255,255,0.000);
11 | $color-fill-color-control-default: rgba(255,255,255,0.700);
12 | $color-fill-color-control-secondary: rgba(249,249,249,0.500);
13 | $color-fill-color-control-tertiary: rgba(249,249,249,0.300);
14 | $color-fill-color-control-input-active: rgba(255,255,255,1);
15 | $color-fill-color-control-disabled: rgba(249,249,249,0.300);
16 | $color-fill-color-control-strong-default: rgba(0,0,0,0.446);
17 | $color-fill-color-control-strong-disabled: rgba(0,0,0,0.317);
18 | $color-fill-color-subtle-transparent: rgba(255,255,255,0.000);
19 | $color-fill-color-subtle-secondary: rgba(0,0,0,0.037);
20 | $color-fill-color-subtle-tertiary: rgba(0,0,0,0.024);
21 | $color-fill-color-subtle-disabled: rgba(0,0,0,0.000);
22 | $color-fill-color-control-solid-default: rgba(255,255,255,1);
23 | $color-fill-color-control-alt-transparent: rgba(255,255,255,0.000);
24 | $color-fill-color-control-alt-secondary: rgba(0,0,0,0.024);
25 | $color-fill-color-control-alt-tertiary: rgba(0,0,0,0.058);
26 | $color-fill-color-control-alt-quarternary: rgba(0,0,0,0.092);
27 | $color-fill-color-control-alt-disabled: rgba(255,255,255,0.000);
28 | $color-fill-color-accent-primary: rgba(0, 103, 192, 1);
29 | $color-fill-color-accent-secondary: rgba(0, 103, 192, 0.90);
30 | $color-fill-color-accent-tertiary: rgba(0, 103, 192, 0.80);
31 | $color-fill-color-accent-disabled: rgba(0,0,0,0.217);
32 | $color-fill-color-system-critical: rgba(196,43,28,1);
33 | $color-fill-color-system-success: rgba(15,123,15,1);
34 | $color-fill-color-system-attention: rgba(0,95,183,1);
35 | $color-fill-color-system-caution: rgba(157,93,0,1);
36 | $color-fill-color-system-attention-background: rgba(246,246,246,0.500);
37 | $color-fill-color-system-success-background: rgba(223,246,221,1);
38 | $color-fill-color-system-caution-background: rgba(255,244,206,1);
39 | $color-fill-color-system-critical-background: rgba(253,231,233,1);
40 | $color-fill-color-system-neutral: rgba(0,0,0,0.446);
41 | $color-fill-color-system-neutral-background: rgba(0,0,0,0.024);
42 | $color-fill-color-system-solid-neutral: rgba(138,138,138,1);
43 | $color-fill-color-system-solid-attention-background: rgba(247,247,247,1);
44 | $color-fill-color-system-solid-neutral-background: rgba(243,243,243,1);
45 | $color-fill-color-control-on-image-default: rgba(255,255,255,0.790);
46 | $color-fill-color-control-on-image-secondary: rgba(243,243,243,1);
47 | $color-fill-color-control-on-image-tertiary: rgba(235,235,235,1);
48 | $color-fill-color-control-on-image-disabled: rgba(255,255,255,0.000);
49 | $color-stroke-color-control-stroke-default: rgba(0,0,0,0.058);
50 | $color-stroke-color-control-stroke-secondary: rgba(0,0,0,0.162);
51 | $color-stroke-color-control-stroke-on-accent-default: rgba(255,255,255,0.080);
52 | $color-stroke-color-control-stroke-on-accent-secondary: rgba(0,0,0,0.400);
53 | $color-stroke-color-control-stroke-on-accent-tertiary: rgba(0,0,0,0.217);
54 | $color-stroke-color-control-stroke-on-accent-disabled: rgba(0,0,0,0.058);
55 | $color-stroke-color-control-stroke-for-strong-fill-when-on-image: rgba(255,255,255,0.350);
56 | $color-stroke-color-control-strong-stroke-default: rgba(0,0,0,0.446);
57 | $color-stroke-color-control-strong-stroke-disabled: rgba(0,0,0,0.217);
58 | $color-stroke-color-card-stroke-default: rgba(0,0,0,0.15);
59 | $color-stroke-color-card-stroke-default-solid: rgba(235,235,235,1);
60 | $color-stroke-color-divider-stroke-default: rgba(0,0,0,0.080);
61 | $color-stroke-color-surface-stroke-default: rgba(117,117,117,0.400);
62 | $color-stroke-color-surface-stroke-flyout: rgba(0,0,0,0.058);
63 | $color-stroke-color-focus-stroke-outer: rgba(0,0,0,0.896);
64 | $color-stroke-color-focus-stroke-inner: rgba(255,255,255,1);
65 | $color-background-fill-color-card-background-default: rgba(255,255,255,0.700);
66 | $color-background-fill-color-card-background-secondary: rgba(246,246,246,0.500);
67 | $color-background-fill-color-smoke-default: rgba(0,0,0,0.300);
68 | $color-background-fill-color-layer-default: rgba(255,255,255,0.500);
69 | $color-background-fill-color-layer-alt: rgba(255,255,255,1);
70 | $color-background-fill-color-solid-background-base: rgba(243,243,243,1);
71 | $color-background-fill-color-solid-background-secondary: rgba(238,238,238,1);
72 | $color-background-fill-color-solid-background-tertiary: rgba(249,249,249,1);
73 | $color-background-fill-color-solid-background-quarternary: rgba(255,255,255,1);
74 | $effect-style-shadow-card-rest: 0.00px 2.00px 4.00px rgba(0,0,0,0.040);
75 | $effect-style-shadow-card-hover: 0.00px 2.00px 4.00px rgba(0,0,0,0.100);
76 | $effect-style-shadow-tooltip: 0.00px 4.00px 8.00px rgba(0,0,0,0.140);
77 | $effect-style-shadow-flyout: 0.00px 8.00px 16.00px rgba(0,0,0,0.140);
78 | $effect-style-shadow-dialog: 0.00px 2.00px 21.00px rgba(0,0,0,0.147);
79 | $effect-style-shell-shadows-inactive-window: 0.00px 2.00px 10.67px rgba(0,0,0,0.147);
80 | $effect-style-shell-shadows-active-window: 0.00px 2.00px 21.00px rgba(0,0,0,0.220);
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributor Handbook
2 |
3 | Thanks for your interest in contributing to CSED 2024 Assignments! We're excited to have you on board. In this document, we'll outline the guidelines for contributing to CSED 2024 Assignments. These guidelines are designed to make it as easy as possible to get involved.
4 |
5 | ## Table of Contents
6 |
7 | - [Reporting Bugs](#reporting-bugs)
8 | - [Feature Requests](#feature-requests)
9 | - [Code Contribution Guidelines](#code-contribution-guidelines)
10 | - [Development Setup](#development-setup)
11 | - [Commit Message Style](#commit-message-style)
12 | - [Pull Requests Rules](#pull-requests-rules)
13 |
14 | ## Reporting Bugs
15 |
16 | This section guides you through submitting a bug report for CSED 2024 Assignments. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
17 |
18 | ### Before Submitting A Bug Report
19 |
20 | - **Check the [issue tracker](https://github.com/GeeekyBoy/csed-2024-assignments/issues?q=is%3Aissue)**. Someone might have already reported the same problem. If it's already reported **and the issue is still open**, add a comment to the existing issue instead of opening a new one.
21 | - **Perform a cursory search on the internet**. This is a good way to see if the problem is a known issue with a known solution.
22 |
23 | ### How Do I Submit A (Good) Bug Report?
24 |
25 | To make sure your bug report gets the attention it deserves, please follow these guidelines:
26 |
27 | - **Use a clear and descriptive title** for the issue to identify the problem.
28 | - **Describe the exact steps which reproduce the problem** in as many details as possible.
29 | - **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
30 | - **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
31 | - **Explain which behavior you expected to see instead and why.**
32 |
33 | ## Feature Requests
34 |
35 | This section guides you through submitting a feature request for CSED 2024 Assignments. Following these guidelines helps maintainers and the community understand your suggestion and why it would be useful.
36 |
37 | ### Before Submitting An Enhancement Suggestion
38 |
39 | - **Have a look at the [proposed features](https://github.com/GeeekyBoy/csed-2024-assignments/discussions/categories/ideas)**. Someone might have already suggested the same thing.
40 |
41 | ### How Do I Submit A (Good) Feature Request?
42 |
43 | To make sure your feature request gets the attention it deserves, please follow these guidelines:
44 |
45 | - **Use a clear and descriptive title** for the discussion to identify the suggestion.
46 | - **Explain why this feature would be useful** to most CSED 2024 students.
47 | - **Describe the solution you'd like**. Be specific and include details of your proposed implementation.
48 | - **List some other projects where this feature exists**. This is a good way to get a feel for how it might work. This is not required, but it can help to get a better picture of what you're suggesting.
49 | - **Describe alternatives you've considered**. This is important so that others can understand the trade-offs you might have thought of.
50 | - **Include UML diagrams** if you think they would help to understand your suggestion.
51 |
52 | ## Code Contribution Guidelines
53 |
54 | ### Development Setup
55 |
56 | Before you start working on CSED 2024 Assignments, you'll need to setup your development environment. Follow the steps below to get started:
57 |
58 | 1. Fork the CSED 2024 Assignments repository on GitHub.
59 | 2. Clone your fork locally:
60 | ```bash
61 | git clone
62 | cd csed-2024-assignments
63 | ```
64 | 3. Install the dependencies:
65 | ```bash
66 | npm install
67 | ```
68 | 4. Run CSED 2024 Assignments website locally to test your changes:
69 | ```bash
70 | npm run start
71 | ```
72 |
73 | No need to work on a separate branch. Once you're done, create a pull request to the `main` branch. It's advisable to pull the latest changes from the `main` branch periodically to avoid merge conflicts.
74 |
75 | ### Commit Message Style
76 |
77 | CSED 2024 Assignments commit message guidelines are derived from the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification. Before proceeding, make sure you understand the specification. We add a few extra rules on top of the specification:
78 |
79 | #### Format
80 |
81 | The commit message must be in the following format. Please note that each line cannot be longer than 72 characters.
82 |
83 | ```
84 | [(optional scope)]:
85 |
86 | [optional body]
87 |
88 | [optional footer(s)]
89 | ```
90 |
91 | #### Type & Emoji
92 |
93 | | Type | Description | Emoji |
94 | | ---- | ----------- | ----- |
95 | | `build` | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm). This includes changes to the build scripts and package.json files. | 👷 |
96 | | `ci` | Changes to our CI configuration files and scripts (examples: CircleCi, SauceLabs) | 👷 |
97 | | `docs` | Documentation only changes. This includes changes to the documentation, website, README, etc. | 📝 |
98 | | `feat` | A new feature or enhancement that is not a bug fix. | ✨ |
99 | | `fix` | A bug fix. | 🐛 |
100 | | `perf` | A code change that improves performance | ⚡️ |
101 | | `refactor` | A code change that neither fixes a bug nor adds a feature | ♻️ |
102 | | `test` | Adding missing tests or correcting existing tests | 🧪 |
103 | | `revert` | Reverting a previous commit. | ⏪ |
104 | | `chore` | Changes that don't fit any of the above categories | [choose](https://gitmoji.dev/) |
105 |
106 | #### Scope
107 |
108 | Scope is optional. It can be anything specifying the place of the commit change. A common use case is when committing a change in the README file where the commit message would be `docs(readme): 📝`.
109 |
110 | #### Subject
111 |
112 | - **Use the imperative, present tense**: "change" not "changed" nor "changes".
113 | - **Capitalize the first letter**.
114 | - **Don't end the subject with a period or any other punctuation**.
115 | - **Initials must be uppercased**: `IE rejects CSS classes starting with _` not `ie rejects css classes starting with _`.
116 |
117 | #### Body
118 |
119 | - **Use the imperative, present tense**: "change" not "changed" nor "changes".
120 | - **Capitalize the first letter of each sentence**.
121 |
122 | ### Pull Requests Rules
123 |
124 | Here's some rules to follow when submitting a pull request. These rules are strictly enforced to ensure a high quality of the codebase.
125 |
126 | - **One pull request per feature**. If you want to add multiple features, create multiple pull requests.
127 | - **One pull request per bug fix**. If you want to fix multiple bugs, create multiple pull requests.
128 | - **Don't commit changes to files that are irrelevant to your feature or bug fix**. This will make it easier for the reviewer to focus on the changes that matter.
129 |
--------------------------------------------------------------------------------
/src/routes/+pages.jsx:
--------------------------------------------------------------------------------
1 | import { enableNotifications, $notificationsState } from "../util/firebase";
2 | import confetti from 'canvas-confetti';
3 | import AssignmentForm from "../components/AssignmentForm";
4 | import Banner from "../components/Banner";
5 | import FAB from "../components/FAB";
6 | import Checkbox from "../components/Checkbox";
7 | import CheckmarkIcon from "jsx:@fluentui/svg-icons/icons/checkmark_24_regular.svg";
8 | import DeleteIcon from "jsx:@fluentui/svg-icons/icons/delete_24_regular.svg";
9 | import AddIcon from "jsx:@fluentui/svg-icons/icons/add_24_regular.svg";
10 | import EditIcon from "jsx:@fluentui/svg-icons/icons/edit_24_regular.svg";
11 | import { getFirestore, onSnapshot, collection } from "firebase/firestore";
12 | import * as dateUtils from "../util/dateUtils.js";
13 | import * as pendingAssignments from "../util/pendingAssignments.js";
14 | import * as finishedAssignments from "../util/finishedAssignments.js";
15 | import * as styles from "./styles.module.scss";
16 |
17 | if ("serviceWorker" in navigator) {
18 | window.addEventListener("load", () => {
19 | navigator.serviceWorker.register(new URL("../workbox-sw.js", import.meta.url), { type: "module" });
20 | });
21 | }
22 |
23 | function App() {
24 | let $isModalActive = false;
25 | let $activeAssignment = [];
26 | let $pending = pendingAssignments.load();
27 | let $finished = finishedAssignments.load();
28 | let $isEditMode = false;
29 | let $showTodo = true;
30 | let $showPending = true;
31 | let $showFinished = true;
32 | let $loading = true;
33 | let $data = [];
34 | const teamsCodes = [
35 | ["Data-Intensive", "g9kqa14"],
36 | ["Security", "64daw8i"],
37 | ["Multimedia", "hfe12ez"],
38 | ];
39 | const driveLink = "https://drive.google.com/drive/folders/1QrC56oFyDboBWRCpjoFI7PedFU3ffilf";
40 | const db = getFirestore();
41 | onSnapshot(collection(db, "assignments"), (snapshot) => {
42 | $data = snapshot.docs.map((item) => {
43 | const data = item.data();
44 | return [
45 | item.id,
46 | data.subject,
47 | data.assignment,
48 | data.dueDate,
49 | data.link,
50 | data.showTime,
51 | data.beforeSection
52 | ];
53 | }).sort((a, b) => a[3] - b[3]);
54 | $loading = false;
55 | });
56 | const handleFabClick = async () => {
57 | if (!$isEditMode) {
58 | if (!localStorage.getItem("token")) {
59 | localStorage.setItem("token", prompt("Please enter your password"));
60 | }
61 | const res = await fetch(`/api/verify-password`, {
62 | method: "GET",
63 | headers: {
64 | "Authorization": "Bearer " + localStorage.getItem("token"),
65 | }
66 | });
67 | if (res.status === 401) {
68 | localStorage.removeItem("token");
69 | alert("Invalid password");
70 | } else {
71 | $isEditMode = true;
72 | }
73 | } else {
74 | $activeAssignment = [];
75 | $isModalActive = true;
76 | }
77 | }
78 | const handleExitEditMode = () => {
79 | $isEditMode = false;
80 | }
81 | const handleChangeStatus = (id) => {
82 | if ($pending.includes(id)) {
83 | $pending = pendingAssignments.toggle(id);
84 | $finished = finishedAssignments.toggle(id);
85 | } else if ($finished.includes(id)) {
86 | $finished = finishedAssignments.toggle(id);
87 | } else {
88 | $pending = pendingAssignments.toggle(id);
89 | }
90 | }
91 | const handleDelete = async (id, subject, assignment) => {
92 | if (!confirm(`Are you sure you want to delete the assignment "${subject} - ${assignment}"?`)) return;
93 | if (!localStorage.getItem("token")) {
94 | localStorage.setItem("token", prompt("Please enter your password"));
95 | }
96 | const res = await fetch(`/api/assignments/${id}`, {
97 | method: "DELETE",
98 | headers: {
99 | "Authorization": "Bearer " + localStorage.getItem("token"),
100 | }
101 | });
102 | if (res.status === 401) {
103 | localStorage.removeItem("token");
104 | alert("Invalid password");
105 | }
106 | }
107 | const handleEditAssignment = (assignment) => {
108 | if ($isEditMode) {
109 | $activeAssignment = assignment;
110 | $isModalActive = true;
111 | }
112 | }
113 | confetti({
114 | particleCount: 400,
115 | spread: 100,
116 | resize: true,
117 | origin: { y: 0.6 }
118 | });
119 | return (
120 |
121 |
122 |
CSED 2024 Assignments
123 |
127 |
128 |
129 |
130 |
131 | CSED 2024 Assignments
132 |
133 |
139 |
140 |
141 |
142 | A service powered by
143 | Mango!
144 |
145 |
146 |
147 |
148 | {$isEditMode ? (
149 |
157 | Tap to exit edit mode!
158 |
159 | ) : $notificationsState === "default" ? (
160 |
168 | Tap to enable notifications to get notified when an assignment is due!
169 |
170 | ) : $notificationsState === "denied" ? (
171 |
178 | Notifications are disabled. Please enable them in your browser settings.
179 |
180 | ) : null}
181 | {$loading ? (
182 |
Loading...
183 | ) : (
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | |
193 | Assignment
194 | |
195 |
196 | Date
197 | |
198 |
199 | {$data.map((item) => (
200 | (($showTodo && !$pending.includes(item[0]) && !$finished.includes(item[0])) ||
201 | ($showPending && $pending.includes(item[0])) ||
202 | ($showFinished && $finished.includes(item[0]))) && (
203 | handleEditAssignment(item)}>
204 | |
208 | {item[1]} → {item[4] ? (
209 | e.stopPropagation()}>
210 | {item[2]}
211 |
212 | ) : (
213 | item[2]
214 | )}
215 | {$isEditMode ? (
216 |
226 | ) : (
227 |
241 | )}
242 | |
243 |
247 | {dateUtils.dateToString(new Date(item[3]), item[5], item[6])}
248 | |
249 |
250 | )
251 | ))}
252 |
253 |
254 |
255 |
256 |
257 | Teams Codes :
258 |
259 |
260 | {teamsCodes.map((item) => (
261 | -
262 | {item[0]} → {item[1]}
263 |
264 | ))}
265 |
266 |
267 |
268 | Drive :
269 |
270 |
271 | CSED 2024
272 |
273 |
274 |
275 | )}
276 |
277 |
284 |
285 |
286 | {$isEditMode ? (
287 |
288 | ) : (
289 |
290 | )}
291 |
292 |
293 |
294 | );
295 | }
296 |
297 | export default App;
298 |
--------------------------------------------------------------------------------