diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..251cc86 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,21 @@ +{ + "name": "Node", + "image": "mcr.microsoft.com/devcontainers/typescript-node:0-18", + "features": { + "ghcr.io/devcontainers-contrib/features/curl-apt-get:1": {}, + "ghcr.io/devcontainers-contrib/features/neovim-apt-get:1": {} + }, + "remoteUser": "node", + "postCreateCommand": "cd /workspaces/ferienpass-anmeldung && npm install && npm install @google/clasp --global", + "customizations": { + "vscode": { + "extensions": [ + "editorconfig.editorconfig", + "xabikos.javascriptsnippets", + "ecmel.vscode-html-css", + "github.copilot", + "GitHub.vscode-github-actions" + ] + } + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..35ce3ee --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..f655c99 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,40 @@ +name: Deploy + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + name: Build GitHub Pages site + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Patch and copy artifacts + run: | + mkdir _site + cp ferienpass.webp ferienpass-transparent.webp ./_site/ + GOOGLE_APP_SCRIPS_ID=${{ secrets.GOOGLE_APP_SCRIPS_ID }} envsubst < index.html > ./_site/index.html + + - name: Upload artifacts + uses: actions/upload-pages-artifact@v1 + + deploy: + name: Deploy GitHub Pages site + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f70112 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.clasp.json +.clasprc.json +node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ad92582 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..7e1306a --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +booking.ferienpass-seeberg.ch diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ca890c4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Ferienpass Seeberg, Oliver Gut + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ef392d --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +[![Languages](https://skillicons.dev/icons?i=js,html,css,gcp,bash,githubactions,linux,vscode)](https://skillicons.dev) + +[![Deploy](https://github.com/flenny/ferienpass-seeberg/actions/workflows/deploy.yml/badge.svg)](https://github.com/flenny/ferienpass-seeberg/actions/workflows/deploy.yml) +![GitHub](https://img.shields.io/github/license/flenny/ferienpass-seeberg) + +# Ferienpass Seeberg Registration Form + +Welcome to the ferienpass-seeberg repository! This repository contains the source code and documentation for the registration form of the Ferienpass Seeberg, a holiday program for kids in the Seeberg region of Switzerland. + +## About the Ferienpass Seeberg + +The Ferienpass Seeberg is a popular program for children between the ages of 6 and 16, organized by the association Ferienpass Seeberg. The program offers a variety of activities and events during one week in the summer holidays, such as sports, crafts, excursions, and much more. Children can choose from a wide range of activities and have fun with other kids while learning new things. + +## About the Registration Form + +The registration form is primarily designed to allow parents to book courses for their children. The website is built using HTML, CSS, and JavaScript and uses Google Apps Script as a backend for processing the course bookings. The registration form includes the following features: + +- Information about the program, including dates, activities, and prices +- Detailed information about each course, including availability and age requirements +- A registration form for parents to book courses for their children +- An administration interface (Google Spreadsheet) to manage the courses, registrations and volunteers + +## Contributing + +We welcome contributions to the ferienpass-seeberg repository! If you find a bug or have a feature request, please open an issue on the repository. + +## License + +The ferienpass-seeberg repository is released under the MIT License. See the LICENSE file for details. + +## Contact + +If you have any questions or comments about the Ferienpass Seeberg or the website, please contact us at support@ferienpass-seeberg.ch. diff --git a/ferienpass-transparent.webp b/ferienpass-transparent.webp new file mode 100644 index 0000000..df7524a Binary files /dev/null and b/ferienpass-transparent.webp differ diff --git a/ferienpass.webp b/ferienpass.webp new file mode 100644 index 0000000..c197d40 Binary files /dev/null and b/ferienpass.webp differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..06b58cf --- /dev/null +++ b/index.html @@ -0,0 +1,19 @@ + + +
+Hallo ${firstName} 👋🏻
+
Vielen herzlichen Dank für deine Anmeldung beim Ferienpass Seeberg. Du kannst + hier jederzeit + den Status deiner gebuchten Kurse überprüfen. Die Rechnung, unter Berücksichtigung + der im Programmheft beschriebenen Familienpauschale, erhältst du nach Anmeldeschluss.
+Hier kannst du weitere Kurse buchen.
+Tschüss und bis bald
+Dein Ferienpass Seeberg Team
+ + + ` + + const textBody = ` + Hallo ${firstName} + + Vielen herzlichen Dank für deine Anmeldung beim Ferienpass Seeberg. Unter dem nachfolgenden + Link kannst du jederzeit den Status deiner gebuchten Kurse ueberpruefen. + + ${getStatusUrl(origin, reference)} + + Die Rechnung, unter Beruecksichtigung der im Programmheft beschriebenen Familienpauschale, + erhaeltst du nach Anmeldeschluss. + + Hier kannst du weitere Kurse buchen. + + ${getPreFilledFormUrl(origin, bookingId)} + + Tschuess und bis bald. + Dein Ferienpass Seeberg Team + ` + sendMail({ + to: emailTo, + emailFrom: PropertiesService.getScriptProperties().getProperty('EMAIL_FROM_ADDRESS'), + nameFrom: PropertiesService.getScriptProperties().getProperty('EMAIL_FROM_NAME'), + subject: "Deine Anmeldung beim Ferienpass Seeberg 🎉", + textBody: textBody, + htmlBody: htmlBody, + }) + + // return redirect url + return `${origin}?action=success&bookingId=${bookingId}` + } + catch (error) { + errorMsg = `${error}RequestData:
${formObject}
`
+ sendMail({
+ to: PropertiesService.getScriptProperties().getProperty('EMAIL_SUPPORT_REQUESTS'),
+ emailFrom: PropertiesService.getScriptProperties().getProperty('EMAIL_FROM_ADDRESS'),
+ nameFrom: PropertiesService.getScriptProperties().getProperty('EMAIL_FROM_NAME'),
+ subject: "Fehler im Ferienpass Backend 🙈",
+ textBody: errorMsg,
+ htmlBody: errorMsg,
+ })
+
+ throw error
+ }
+ finally { lock.releaseLock() }
+}
+
+const getDataFromSheet = (spreadsheet, sheetName) => {
+ const sheet = spreadsheet.getSheetByName(sheetName)
+ let numRows = sheet.getLastRow() - 1
+ if (numRows === 0) numRows = 1
+ return JSON.stringify(
+ sheet.getRange(2, 1, numRows, sheet.getLastColumn()).getValues()
+ )
+}
+
+/** Creates a custom signature for the given value and key. */
+const createSignature = (value, key, length) => {
+ const signature = Utilities.computeHmacSignature(
+ Utilities.MacAlgorithm.HMAC_MD5,
+ value,
+ key,
+ Utilities.Charset.US_ASCII);
+ return Utilities.base64EncodeWebSafe(signature)
+ .replace(/[_\-=]+/g, '')
+ .substring(0, length ?? 12)
+}
+
+const updateCache = (identifier, sheetName) => {
+ let result
+ const lock = LockService.getScriptLock()
+ const hasLock = lock.tryLock(SCRIPT_LOCK_TIMEOUT)
+ if (!hasLock) { throw new Error('Could not obtain lock after 40 seconds.') }
+ try {
+ const spreadsheet = SpreadsheetApp.openById(
+ PropertiesService.getScriptProperties().getProperty('SPREADSHEET_ID'))
+ const data = getDataFromSheet(spreadsheet, sheetName)
+ CacheService.getScriptCache().put(identifier, data, CACHE_EXPIRATION_TIMEOUT)
+ result = data
+ }
+ catch (error) { throw error }
+ finally { lock.releaseLock() }
+ return result
+}
+
+// Get the status url
+const getStatusUrl = (baseUrl, reference) => `${baseUrl}?action=status&reference=${reference}`
+
+// Get the pre-filled form url
+const getPreFilledFormUrl = (baseUrl, bookingId) => `${baseUrl}?bookingId=${bookingId}`
+
+// Send an email
+const sendMail = email => Gmail.Users.Messages.send({ raw: convertToGmailMessage(email) }, "me");
+const convertToGmailMessage = ({ to, emailFrom, nameFrom, subject, textBody, htmlBody }) => {
+ const boundary = "boundaryboundary";
+ const mailData = [
+ `MIME-Version: 1.0`,
+ `To: ${to}`,
+ nameFrom && emailFrom ? `From: "${nameFrom}" <${emailFrom}>` : "",
+ `Subject: =?UTF-8?B?${Utilities.base64Encode(
+ subject,
+ Utilities.Charset.UTF_8
+ )}?=`,
+ `Content-Type: multipart/alternative; boundary=${boundary}`,
+ ``,
+ `--${boundary}`,
+ `Content-Type: text/plain; charset=UTF-8`,
+ ``,
+ textBody,
+ ``,
+ `--${boundary}`,
+ `Content-Type: text/html; charset=UTF-8`,
+ `Content-Transfer-Encoding: base64`,
+ ``,
+ Utilities.base64Encode(htmlBody, Utilities.Charset.UTF_8),
+ ``,
+ `--${boundary}--`,
+ ].join("\r\n");
+ return Utilities.base64EncodeWebSafe(mailData);
+};
diff --git a/src/appsscript.json b/src/appsscript.json
new file mode 100644
index 0000000..723f797
--- /dev/null
+++ b/src/appsscript.json
@@ -0,0 +1,23 @@
+{
+ "timeZone": "Europe/Zurich",
+ "dependencies": {
+ "enabledAdvancedServices": [
+ {
+ "userSymbol": "Gmail",
+ "version": "v1",
+ "serviceId": "gmail"
+ }
+ ]
+ },
+ "exceptionLogging": "STACKDRIVER",
+ "runtimeVersion": "V8",
+ "webapp": {
+ "executeAs": "USER_DEPLOYING",
+ "access": "ANYONE_ANONYMOUS"
+ },
+ "oauthScopes": [
+ "https://www.googleapis.com/auth/spreadsheets.currentonly",
+ "https://www.googleapis.com/auth/spreadsheets",
+ "https://www.googleapis.com/auth/gmail.send"
+ ]
+}
diff --git a/src/css.html b/src/css.html
new file mode 100644
index 0000000..e5bd700
--- /dev/null
+++ b/src/css.html
@@ -0,0 +1,131 @@
+
diff --git a/src/head.html b/src/head.html
new file mode 100644
index 0000000..3c6c628
--- /dev/null
+++ b/src/head.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..5c3fac4
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,281 @@
+
+
+
+
+ != include('head'); ?>
+ != include('css'); ?>
+
+
+
+ Name | +Kurs | +Status | +
---|
Vielen herzlichen Dank für deine Anmeldung beim Ferienpass Seeberg. Wir freuen uns sehr, dass du + beim Ferienpass dabei bist. 🥳 Wir haben dir per E-Mail soeben einen Link geschickt. Dort kannst du jederzeit + den Status deiner gebuchten Kurse überprüfen.
+Hier kannst + du weitere Kurse buchen.
+Tschüss und bis bald 👋
+Dein Ferienpass Seeberg Team
+