Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
e116fee53a | |||
a313e09410 | |||
e2e6799f49 | |||
41598ffa32 | |||
3df1360c66 | |||
95ed928f18 | |||
9f643fae78 | |||
8262e1fa5b | |||
171d710bac | |||
|
2e1f63c5f2 | ||
|
9a227a1e47 | ||
59901b4c92 | |||
9285d46840 | |||
21faf7b043 | |||
c25d746ecb | |||
8a5a7cc732 | |||
303aa556cf | |||
|
45f6c958fe |
@ -3,6 +3,9 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
@ -10,6 +13,7 @@
|
||||
"@types/node": "^16.11.64",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"fortawesome": "^0.0.1-security",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
|
BIN
public/background.gif
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
public/background.mp4
Normal file
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 15 KiB |
@ -3,11 +3,12 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
content="Weihnachtsfeier 2024"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
@ -24,7 +25,7 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>Weihnachtsfeier 2024</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 45 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"short_name": "Weihnachtsfeier",
|
||||
"name": "Weihnachtsfeier 2023",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
@ -41,8 +41,7 @@ export const PartyContextProvider: React.FC<{ children: React.ReactNode }> = (pr
|
||||
const [partyContext, setPartyContext] = useState<PartyContextType>();
|
||||
|
||||
const apiEndpoint = useMemo<APIEndPoint>(() => {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const href = location.href;
|
||||
const href = window.location.href;
|
||||
const p = parseURI(href);
|
||||
if (!p) return { partyName: "error", token: "" }
|
||||
return p;
|
||||
|
@ -1,44 +1,110 @@
|
||||
.loading {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: black;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.App {
|
||||
color: white;
|
||||
font-size: calc(7px + 2vmin);
|
||||
text-shadow:
|
||||
-1px -1px 0.2em #000,
|
||||
1px -1px 0.2em #000,
|
||||
-1px 1px 0.2em #000,
|
||||
1px 1px 0.2em #000;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1224px;
|
||||
margin-left: 3ch;
|
||||
margin-right: 3ch;
|
||||
}
|
||||
|
||||
.fullheight {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-outer {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.hero-outer {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
margin: 1em 0 0.1em 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.3em 0 0.3em 0;
|
||||
}
|
||||
|
||||
.feedback {
|
||||
line-height: 3em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
input[type="radio"]+label {
|
||||
font-size: larger;
|
||||
cursor: pointer;
|
||||
padding: 0 1em 0 1em;
|
||||
border-right: 0.1em solid white;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
input[type="radio"]+label:hover {
|
||||
text-shadow: 0 0 1em white;
|
||||
}
|
||||
|
||||
input[type="radio"]+label:last-of-type {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
input[type="radio"]:checked+label {
|
||||
color: var(--selected-color);
|
||||
text-shadow: 0 0 1em var(--selected-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#coming-yes+label {
|
||||
--selected-color: #0f0;
|
||||
}
|
||||
|
||||
#coming-maybe+label {
|
||||
--selected-color: #fc0;
|
||||
}
|
||||
|
||||
#coming-no+label {
|
||||
--selected-color: #f00;
|
||||
}
|
||||
|
||||
.hooverdam:hover {
|
||||
text-shadow: 0 0 1em white;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +1,127 @@
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import logo from './logo.svg';
|
||||
import React, { ChangeEvent, useContext, useRef, useState } from 'react';
|
||||
import './PartyPage.css';
|
||||
import { PartyContext } from './PartyContext';
|
||||
import { APIEndPoint, PartyContext, PartyStatus } from './PartyContext';
|
||||
// import MatrixBackground from './MatrixBackground';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faAngleDown, faCalendarDays, faLocationDot } from '@fortawesome/free-solid-svg-icons';
|
||||
import { modifySelfRequest, parseURI } from './partyApi';
|
||||
|
||||
const myDear = {
|
||||
"m": "lieber",
|
||||
"f": "liebe",
|
||||
"d": "liebes",
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function getComingString(party: PartyStatus): string {
|
||||
if (party.maybe_coming === 0) {
|
||||
// exact number
|
||||
if (party.definitely_coming === 0) {
|
||||
return "Bisher hat noch niemand zugesagt."
|
||||
} else if (party.definitely_coming === 1) {
|
||||
return "Bisher hat ein Gast zugesagt."
|
||||
} else {
|
||||
return `Es haben schon ${party.definitely_coming} Gäste zugesagt.`
|
||||
}
|
||||
} else {
|
||||
// inexact
|
||||
if (party.definitely_coming === 0 && party.maybe_coming === 1) {
|
||||
return "Bisher hat ein Gast vorläufig zugesagt."
|
||||
} else if (party.definitely_coming === 0) {
|
||||
return `Bisher haben ${party.maybe_coming} Gäste vorläufig zugesagt.`
|
||||
} else {
|
||||
return `Nach den bisherigen Zusagen kommen ${party.definitely_coming} bis ${party.definitely_coming + party.maybe_coming} Gäste.`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const PartyPage: React.FC = () => {
|
||||
const partyContext = useContext(PartyContext);
|
||||
|
||||
return <div className="App" >
|
||||
<header className="App-header" >
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code> src/PartyPage.tsx </code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
<span>Hello {partyContext.self.name}</span>
|
||||
</header>
|
||||
const dear = myDear[partyContext.self.grammatical_gender];
|
||||
const name = partyContext.self.name;
|
||||
const wannUndWoRef = useRef<HTMLDivElement>(null)
|
||||
const executeScroll = () => {
|
||||
wannUndWoRef.current!.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
const [comingState, setComingState] = useState(partyContext.self.coming);
|
||||
|
||||
// SAFETY: If this is undefined, the contextProvider already fails
|
||||
const endpoint = parseURI(window.location.href) as APIEndPoint;
|
||||
|
||||
const handleSelect = async (e: ChangeEvent) => {
|
||||
const value = (e.target as HTMLInputElement).value;
|
||||
if (value !== "yes" && value !== "no" && value !== "maybe") {
|
||||
throw new Error("received invalid value?");
|
||||
}
|
||||
const status = await modifySelfRequest(endpoint, { coming: value });
|
||||
setComingState(status.coming);
|
||||
}
|
||||
|
||||
return <div className="App">
|
||||
<div className='container'>
|
||||
<div className='hero fullheight'>
|
||||
<div className='hero-outer'></div>
|
||||
<h1>Hallo {dear} {name},</h1>
|
||||
<p>
|
||||
am <strong>Freitag, den 13. Dezember</strong> wird in diesem Jahr (hoffentlich) nicht viel Unglück passieren.
|
||||
</p>
|
||||
<p>
|
||||
Wir veranstalten nämlich ab <strong> 19:00 </strong> eine Weihnachtsparty!
|
||||
</p>
|
||||
<p>
|
||||
Wir würden uns sehr freuen, wenn auch du, {dear} {name}, dabei bist :)
|
||||
</p>
|
||||
<div className='feedback'>
|
||||
<input type="radio" id="coming-yes" name="coming" value="yes" checked={comingState === "yes"} onChange={handleSelect} />
|
||||
<label htmlFor='coming-yes'>Ja</label>
|
||||
<input type="radio" id="coming-maybe" name="coming" value="maybe" checked={comingState === "maybe"} onChange={handleSelect} />
|
||||
<label htmlFor='coming-maybe'>Vielleicht</label>
|
||||
<input type="radio" id="coming-no" name="coming" value="no" checked={comingState === "no"} onChange={handleSelect} />
|
||||
<label htmlFor='coming-no'>Nein</label>
|
||||
</div>
|
||||
<div className='hero-outer' >
|
||||
<span className='hooverdam' onClick={executeScroll}>
|
||||
<p>Mehr Infos</p>
|
||||
<FontAwesomeIcon icon={faAngleDown} />
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='hero fullheight' ref={wannUndWoRef}>
|
||||
<h2>Wann und Wo?</h2>
|
||||
<p>
|
||||
<FontAwesomeIcon icon={faCalendarDays} /> <strong> 13. Dezember, ab 19:00</strong>. Bitte kommt nicht all zu spät.
|
||||
</p>
|
||||
<p>
|
||||
<FontAwesomeIcon icon={faLocationDot} /> <strong> Gebäude E1 1, Raum 407</strong>, Universität des Saarlandes
|
||||
</p>
|
||||
<h2>Was ist geplant?</h2>
|
||||
<p>
|
||||
Wir (Sebastian & Simon) wollen uns mit euch auf Weihnachten einstimmen.
|
||||
Wir planen ein weihnachtliches Programm mit Geschichten, Liedern, Essen und der einen oder anderen Überraschung. Falls du ein Instrument spielst, bring es auch gerne mit.
|
||||
</p>
|
||||
<p>
|
||||
Natürlich haben wir auch genügend Zeit, uns gemütlich bei einem Heißgetränk zu unterhalten.
|
||||
</p>
|
||||
<h2>Was gibt es zu Essen?</h2>
|
||||
<p>
|
||||
Wir planen ein Potluck-Event. Damit sollte für alle genug Essen dabei sein.
|
||||
|
||||
Getränke, insbesondere Glühwein, Kinderpunsch und Ähnliches, wird von uns organisiert.
|
||||
|
||||
</p>
|
||||
<h2>Was soll ich mitbringen?</h2>
|
||||
<p>
|
||||
Es wäre super, wenn du zum Potluck ein Gericht mitbringen kannst. Für Inspirationen kannst du uns natürlich gerne fragen, oder schon einmal <a href="https://www.tasteofhome.com/collection/vegetarian-potluck-recipes/">hier</a> vorbeischauen.
|
||||
Bitte informiere uns kurz, was du gerne mitbringen würdest, damit wir besser kalkulieren können.
|
||||
Ansonsten darfst du auch sehr gerne Plätzchen, Weihnachtsdeko oder Ähnliches mitbringen.
|
||||
</p>
|
||||
<p>
|
||||
Falls du ansonsten Wünsche oder Anregungen für einen gelungenen Abend hast, teil uns diese gerne mit!
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
};
|
BIN
src/background.gif
Normal file
After Width: | Height: | Size: 1.4 MiB |
@ -10,4 +10,15 @@ body {
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
background-image: url('./background.gif');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-attachment: fixed;
|
||||
height: 100vh;
|
||||
padding:0;
|
||||
margin:0;
|
||||
}
|
||||
|
@ -1,16 +1,21 @@
|
||||
import { APIEndPoint, PartyStatus, SelfStatus, UpdatableSelfStatus } from "./PartyContext";
|
||||
|
||||
export const parseURI = (uri: string): APIEndPoint | undefined => {
|
||||
const x = uri.match(/https?:\/\/(?<partyName>.+)\.party\.leafbla\.de\/(?<token>.+)/);
|
||||
// const x = uri.match(/https?:\/\/(?<partyName>.+)\.party\.leafbla\.de\/(?<token>.+)/);
|
||||
const x = uri.match(/https?:\/\/[^/]+\/(?<token>.+)/);
|
||||
if (x === null || x.groups === undefined) return;
|
||||
const partyName = x.groups["partyName"];
|
||||
// const partyName = x.groups["partyName"];
|
||||
const partyName = "xmas";
|
||||
const token = x.groups["token"];
|
||||
if (!partyName || !token) return;
|
||||
return { partyName, token };
|
||||
};
|
||||
|
||||
const apiUrl = (apiEndPoint : APIEndPoint): string => {
|
||||
return `https://party.leafbla.de/api/${apiEndPoint.partyName}/${apiEndPoint.token}`;
|
||||
|
||||
let a = `https://party.leafbla.de/api/${apiEndPoint.partyName}/${apiEndPoint.token}`;
|
||||
console.log(a);
|
||||
return a;
|
||||
};
|
||||
|
||||
export const getSelfStatusRequest = async (apiEndpoint: APIEndPoint): Promise<SelfStatus> => {
|
||||
|