Compare commits
2 Commits
768fa39cf2
...
3481adfcab
Author | SHA1 | Date | |
---|---|---|---|
3481adfcab | |||
18365af5dc |
@ -1 +1,7 @@
|
||||
NEXT_PUBLIC_SITE_URL=https://example.com
|
||||
NEXT_PUBLIC_SITE_URL=https://westsoundhall.org
|
||||
STRIPE_SECRET_KEY=sk_XXXX
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_XXXX
|
||||
POSTMARK_SERVER_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
|
||||
FROM_ADDRESS=support@westsoundhall.org
|
||||
ADMIN_ADDRESS=board@westsoundhall.org
|
||||
CALENDAR_ADDR=https://calendar.google.com/calendar/ical/westsoundcommunityclub%40gmail.com/private-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/basic.ics
|
||||
|
19
README.md
19
README.md
@ -6,10 +6,7 @@ https://westsoundhall.org
|
||||
|
||||
## Running
|
||||
|
||||
Pre-build containers are created whenever a version is tagged in this
|
||||
repository. Pull the [latest
|
||||
version](https://git.grosinger.net/tgrosinger/-/packages/container/west-sound-hall/)
|
||||
and run on a server with Docker available.
|
||||
Pre-build containers are created whenever a version is tagged in this repository. Pull the [latest version](https://git.grosinger.net/tgrosinger/-/packages/container/west-sound-hall/) and run on a server with Docker available.
|
||||
|
||||
```sh
|
||||
docker run -p 3000:3000 git.grosinger.net/tgrosinger/west-sound-hall:0.0.14
|
||||
@ -17,11 +14,11 @@ docker run -p 3000:3000 git.grosinger.net/tgrosinger/west-sound-hall:0.0.14
|
||||
|
||||
## Updating
|
||||
|
||||
### Events on the Homepage
|
||||
### Events on the Homepage and the Calendar
|
||||
|
||||
The homepage has a list of upcoming events. This list is created from [`src/app/upcoming-events.json`](https://git.grosinger.net/tgrosinger/west-sound-hall/src/branch/main/src/app/upcoming-events.json). To update the events listed, modify that file, tag a new version, and then update the running container to the latest version.
|
||||
The events on the calendar are loaded from the westsoundcommunityclub@gmail.com Google Calendar.
|
||||
|
||||
Events in the past will be automatically hidden from view.
|
||||
Please note that all events on the calendar will be displayed. If an event should not reveal the title to the public, add the word "Private" to the event description (not the title).
|
||||
|
||||
### News Posts
|
||||
|
||||
@ -41,11 +38,7 @@ To get started, first install the npm dependencies:
|
||||
npm install
|
||||
```
|
||||
|
||||
Next, create a `.env.local` file in the root of your project and set the `NEXT_PUBLIC_SITE_URL` variable to your site's public URL:
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_SITE_URL=https://example.com
|
||||
```
|
||||
Next, copy the `.env.example` file from this directory and call it `.env.local`. Fill in the values that have been redacted with their actual secrets. Be sure to use the test environment key from Stripe unless you are setting up production.
|
||||
|
||||
Next, run the development server:
|
||||
|
||||
@ -59,4 +52,4 @@ Finally, open [http://localhost:3000](http://localhost:3000) in your browser to
|
||||
|
||||
This site is based off of the Spotlight template from Tailwind, and licensed under the [Tailwind UI license](https://tailwindui.com/license).
|
||||
|
||||
It was purchased by Tony Grosinger.
|
||||
It was purchased by [Tony Grosinger](mailto:tony@grosinger.net).
|
||||
|
186
package-lock.json
generated
186
package-lock.json
generated
@ -18,15 +18,13 @@
|
||||
"@stripe/stripe-js": "2.2.1",
|
||||
"@tailwindcss/forms": "0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/node": "20.4.7",
|
||||
"@types/react": "18.2.18",
|
||||
"@types/react-dom": "18.2.7",
|
||||
"@types/webpack-env": "^1.18.1",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"clsx": "^1.2.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"fast-glob": "^3.2.11",
|
||||
"feed": "^4.2.2",
|
||||
"ical": "^0.8.0",
|
||||
"next": "13.4.16",
|
||||
"next-themes": "^0.2.1",
|
||||
"postmark": "4.0.2",
|
||||
@ -39,6 +37,11 @@
|
||||
"typescript": "5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ical": "^0.8.3",
|
||||
"@types/node": "20.4.7",
|
||||
"@types/react": "18.2.18",
|
||||
"@types/react-dom": "18.2.7",
|
||||
"@types/webpack-env": "^1.18.1",
|
||||
"eslint": "8.45.0",
|
||||
"eslint-config-next": "13.4.16",
|
||||
"prettier": "^3.0.1",
|
||||
@ -701,6 +704,33 @@
|
||||
"@types/unist": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ical": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ical/-/ical-0.8.3.tgz",
|
||||
"integrity": "sha512-qPejGORaXOstmqyKzp0Qw9nXDPiWiahiJJcx4zMB0zJVg0rLfJ6bDip/naqagEqYTjKl/LI91399hR8zFwRJ5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"rrule": "2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ical/node_modules/rrule": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.4.tgz",
|
||||
"integrity": "sha512-sLdnh4lmjUqq8liFiOUXD5kWp/FcnbDLPwq5YAc/RrN6120XOPb86Ae5zxF7ttBVq8O3LxjjORMEit1baluahA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^1.10.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"luxon": "^1.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ical/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
|
||||
@ -755,6 +785,7 @@
|
||||
"version": "18.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz",
|
||||
"integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
@ -772,7 +803,8 @@
|
||||
"node_modules/@types/webpack-env": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.1.tgz",
|
||||
"integrity": "sha512-D0HJET2/UY6k9L6y3f5BL+IDxZmPkYmPT4+qBrRdmRLYRuV0qNKizMgTvYxXZYn+36zjPeoDZAEYBCM6XB+gww=="
|
||||
"integrity": "sha512-D0HJET2/UY6k9L6y3f5BL+IDxZmPkYmPT4+qBrRdmRLYRuV0qNKizMgTvYxXZYn+36zjPeoDZAEYBCM6XB+gww==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "5.62.0",
|
||||
@ -2243,6 +2275,11 @@
|
||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.10",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
||||
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
@ -3472,9 +3509,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@ -4110,6 +4147,14 @@
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ical": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ical/-/ical-0.8.0.tgz",
|
||||
"integrity": "sha512-/viUSb/RGLLnlgm0lWRlPBtVeQguQRErSPYl3ugnUaKUnzQswKqOG3M8/P1v1AB5NJwlHTuvTq1cs4mpeG2rCg==",
|
||||
"dependencies": {
|
||||
"rrule": "2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@ -5596,6 +5641,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "1.28.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
|
||||
"integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
@ -6978,9 +7032,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -7644,9 +7698,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.27",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
|
||||
"integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
|
||||
"version": "8.4.38",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -7662,9 +7716,9 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.6",
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
@ -8388,6 +8442,14 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rrule": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.4.1.tgz",
|
||||
"integrity": "sha512-+NcvhETefswZq13T8nkuEnnQ6YgUeZaqMqVbp+ZiFDPCbp3AVgQIwUvNVDdMNrP05bKZG9ddDULFp0qZZYDrxg==",
|
||||
"optionalDependencies": {
|
||||
"luxon": "^1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/run-applescript": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
|
||||
@ -8859,9 +8921,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -10911,6 +10973,33 @@
|
||||
"@types/unist": "^2"
|
||||
}
|
||||
},
|
||||
"@types/ical": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ical/-/ical-0.8.3.tgz",
|
||||
"integrity": "sha512-qPejGORaXOstmqyKzp0Qw9nXDPiWiahiJJcx4zMB0zJVg0rLfJ6bDip/naqagEqYTjKl/LI91399hR8zFwRJ5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"rrule": "2.6.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"rrule": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.4.tgz",
|
||||
"integrity": "sha512-sLdnh4lmjUqq8liFiOUXD5kWp/FcnbDLPwq5YAc/RrN6120XOPb86Ae5zxF7ttBVq8O3LxjjORMEit1baluahA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"luxon": "^1.21.3",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
|
||||
@ -10965,6 +11054,7 @@
|
||||
"version": "18.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz",
|
||||
"integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
@ -10982,7 +11072,8 @@
|
||||
"@types/webpack-env": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.1.tgz",
|
||||
"integrity": "sha512-D0HJET2/UY6k9L6y3f5BL+IDxZmPkYmPT4+qBrRdmRLYRuV0qNKizMgTvYxXZYn+36zjPeoDZAEYBCM6XB+gww=="
|
||||
"integrity": "sha512-D0HJET2/UY6k9L6y3f5BL+IDxZmPkYmPT4+qBrRdmRLYRuV0qNKizMgTvYxXZYn+36zjPeoDZAEYBCM6XB+gww==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/parser": {
|
||||
"version": "5.62.0",
|
||||
@ -12041,6 +12132,11 @@
|
||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
||||
"dev": true
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.11.10",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
||||
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
@ -12956,9 +13052,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q=="
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
|
||||
},
|
||||
"for-each": {
|
||||
"version": "0.3.3",
|
||||
@ -13400,6 +13496,14 @@
|
||||
"integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ical": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ical/-/ical-0.8.0.tgz",
|
||||
"integrity": "sha512-/viUSb/RGLLnlgm0lWRlPBtVeQguQRErSPYl3ugnUaKUnzQswKqOG3M8/P1v1AB5NJwlHTuvTq1cs4mpeG2rCg==",
|
||||
"requires": {
|
||||
"rrule": "2.4.1"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@ -14463,6 +14567,12 @@
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"luxon": {
|
||||
"version": "1.28.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
|
||||
"integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==",
|
||||
"optional": true
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
@ -15382,9 +15492,9 @@
|
||||
}
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
|
||||
},
|
||||
"napi-build-utils": {
|
||||
"version": "1.0.2",
|
||||
@ -15840,13 +15950,13 @@
|
||||
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg=="
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.27",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
|
||||
"integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
|
||||
"version": "8.4.38",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||
"requires": {
|
||||
"nanoid": "^3.3.6",
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"postcss-import": {
|
||||
@ -16310,6 +16420,14 @@
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"rrule": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.4.1.tgz",
|
||||
"integrity": "sha512-+NcvhETefswZq13T8nkuEnnQ6YgUeZaqMqVbp+ZiFDPCbp3AVgQIwUvNVDdMNrP05bKZG9ddDULFp0qZZYDrxg==",
|
||||
"requires": {
|
||||
"luxon": "^1.3.3"
|
||||
}
|
||||
},
|
||||
"run-applescript": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
|
||||
@ -16623,9 +16741,9 @@
|
||||
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="
|
||||
},
|
||||
"source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg=="
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.21",
|
||||
|
11
package.json
11
package.json
@ -20,15 +20,13 @@
|
||||
"@stripe/stripe-js": "2.2.1",
|
||||
"@tailwindcss/forms": "0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/node": "20.4.7",
|
||||
"@types/react": "18.2.18",
|
||||
"@types/react-dom": "18.2.7",
|
||||
"@types/webpack-env": "^1.18.1",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"clsx": "^1.2.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"fast-glob": "^3.2.11",
|
||||
"feed": "^4.2.2",
|
||||
"ical": "^0.8.0",
|
||||
"next": "13.4.16",
|
||||
"next-themes": "^0.2.1",
|
||||
"postmark": "4.0.2",
|
||||
@ -41,6 +39,11 @@
|
||||
"typescript": "5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ical": "^0.8.3",
|
||||
"@types/node": "20.4.7",
|
||||
"@types/react": "18.2.18",
|
||||
"@types/react-dom": "18.2.7",
|
||||
"@types/webpack-env": "^1.18.1",
|
||||
"eslint": "8.45.0",
|
||||
"eslint-config-next": "13.4.16",
|
||||
"prettier": "^3.0.1",
|
||||
|
@ -22,7 +22,7 @@ function SocialLink({
|
||||
<li className={clsx(className, 'flex')}>
|
||||
<Link
|
||||
href={href}
|
||||
className="group flex text-sm font-medium text-zinc-800 transition hover:text-teal-500 dark:text-zinc-200 dark:hover:text-teal-500"
|
||||
className="group flex text-sm font-medium text-zinc-800 transition hover:text-teal-500"
|
||||
>
|
||||
<Icon className="h-6 w-6 flex-none fill-zinc-500 transition group-hover:fill-teal-500" />
|
||||
<span className="ml-4">{children}</span>
|
||||
@ -43,10 +43,10 @@ export default function Club() {
|
||||
<Container className="mt-16 sm:mt-32">
|
||||
<div className="grid grid-cols-1 gap-y-16 lg:grid-cols-2 lg:grid-rows-[auto_1fr] lg:gap-y-12">
|
||||
<div className="lg:order-first lg:row-span-2">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 sm:text-5xl">
|
||||
The Board of Directors
|
||||
</h1>
|
||||
<div className="mt-6 space-y-7 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<div className="mt-6 space-y-7 text-base text-zinc-600">
|
||||
<p>
|
||||
Elections for the Board of Directors are held annually at the October member meeting and potluck.
|
||||
</p>
|
||||
@ -105,14 +105,14 @@ export default function Club() {
|
||||
<SocialLink
|
||||
href="mailto:contact@westsoundhall.org"
|
||||
icon={EnvelopeIcon}
|
||||
className="mt-4 border-zinc-100 dark:border-zinc-700/40"
|
||||
className="mt-4 border-zinc-100"
|
||||
>
|
||||
contact@westsoundhall.org
|
||||
</SocialLink>
|
||||
<SocialLink
|
||||
href="mailto:contact@westsoundhall.org"
|
||||
icon={EnvelopeIcon}
|
||||
className="mt-4 border-zinc-100 dark:border-zinc-700/40"
|
||||
className="mt-4 border-zinc-100"
|
||||
>
|
||||
board@westsoundhall.org
|
||||
</SocialLink>
|
||||
|
239
src/app/calendar/calendar.tsx
Normal file
239
src/app/calendar/calendar.tsx
Normal file
@ -0,0 +1,239 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
ClockIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import React from 'react';
|
||||
import { Calendar, Event } from './page';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface day {
|
||||
date: dayjs.Dayjs;
|
||||
isCurrentMonth: boolean;
|
||||
isToday: boolean;
|
||||
}
|
||||
|
||||
|
||||
function classNames(...classes: string[]): string {
|
||||
return classes.filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
const getEvents = (cal: Calendar, d: dayjs.Dayjs): Event[] => {
|
||||
const year = cal[d.year()];
|
||||
if (!year) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const month = year[d.month()];
|
||||
if (!month) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return month[d.date()] || [];
|
||||
}
|
||||
|
||||
export const CalendarComponent: React.FC<{ calendar: Calendar }> = ({ calendar }) => {
|
||||
const [selectedDay, setSelectedDay] = React.useState(dayjs().startOf('day'));
|
||||
const [selectedMonth, setSelectedMonth] = React.useState(dayjs().startOf('month'));
|
||||
|
||||
const days = React.useMemo(() => {
|
||||
// Number of greyed days shown before the beginning of the current month.
|
||||
const renderedDaysBeforeMonth = selectedMonth.day();
|
||||
const firstDay = selectedMonth.subtract(renderedDaysBeforeMonth, 'day');
|
||||
const month = selectedMonth.month();
|
||||
const today = dayjs().startOf('day');
|
||||
|
||||
let current = firstDay;
|
||||
const days: day[] = [];
|
||||
|
||||
// Fill out the first week with days from the previous month.
|
||||
for (let i = 0; i < renderedDaysBeforeMonth; i++) {
|
||||
days.push({
|
||||
date: current,
|
||||
isCurrentMonth: false,
|
||||
isToday: current.isSame(today),
|
||||
});
|
||||
current = current.add(1, 'day');
|
||||
}
|
||||
|
||||
// Add the days for the selected month.
|
||||
while (current.month() === month) {
|
||||
days.push({
|
||||
date: current,
|
||||
isCurrentMonth: true,
|
||||
isToday: current.isSame(today),
|
||||
});
|
||||
current = current.add(1, 'day');
|
||||
}
|
||||
|
||||
// Finish out the week with days from the following month.
|
||||
while (current.day() > 0) {
|
||||
days.push({
|
||||
date: current,
|
||||
isCurrentMonth: false,
|
||||
isToday: current.isSame(today),
|
||||
});
|
||||
current = current.add(1, 'day');
|
||||
}
|
||||
|
||||
return days;
|
||||
}, [selectedMonth]);
|
||||
|
||||
const isSelectedDay = (d: day): boolean => {
|
||||
return d.date.isSame(selectedDay);
|
||||
}
|
||||
|
||||
const selectedDayEvents = React.useMemo(() => {
|
||||
return getEvents(calendar, selectedDay);
|
||||
}, [calendar, selectedDay]);
|
||||
|
||||
return (
|
||||
<div className="lg:flex lg:h-full lg:flex-col">
|
||||
<header className="flex items-center justify-between border-b border-zinc-100 py-4 lg:flex-none">
|
||||
<h1 className="text-base font-semibold leading-6 text-zinc-800">
|
||||
<time dateTime={selectedMonth.format('YYYY-MM')}>{selectedMonth.format('MMMM YYYY')}</time>
|
||||
</h1>
|
||||
<div className="flex items-center">
|
||||
<div className="relative flex items-center rounded-md shadow-sm md:items-stretch">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setSelectedMonth(selectedMonth.subtract(1, 'month')) }}
|
||||
className="flex h-9 w-12 items-center justify-center rounded-l-md border-y border-l border-zinc-100 pr-1 text-gray-400 hover:text-gray-500 focus:relative md:w-9 md:pr-0 md:hover:bg-gray-50"
|
||||
>
|
||||
<span className="sr-only">Previous month</span>
|
||||
<ChevronLeftIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedDay(dayjs().startOf('day'))
|
||||
setSelectedMonth(dayjs().startOf('month'));
|
||||
}}
|
||||
className="hidden border-y border-zinc-100 px-3.5 text-sm font-semibold text-zinc-800 hover:bg-gray-50 focus:relative md:block"
|
||||
>
|
||||
Today
|
||||
</button>
|
||||
<span className="relative -mx-px h-5 w-px bg-gray-300 md:hidden" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setSelectedMonth(selectedMonth.add(1, 'month')) }}
|
||||
className="flex h-9 w-12 items-center justify-center rounded-r-md border-y border-r border-zinc-100 pl-1 text-gray-400 hover:text-gray-500 focus:relative md:w-9 md:pl-0 md:hover:bg-gray-50"
|
||||
>
|
||||
<span className="sr-only">Next month</span>
|
||||
<ChevronRightIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div className="shadow ring-1 ring-black ring-opacity-5 lg:flex lg:flex-auto lg:flex-col">
|
||||
<div className="grid grid-cols-7 gap-px border-b border-zinc-100 bg-gray-200 text-center text-xs font-semibold leading-6 text-gray-700 lg:flex-none">
|
||||
{[['M', 'on'], ['T', 'ue'], ['W', 'ed'], ['T', 'hu'], ['F', 'ri'], ['S', 'at'], ['S', 'un']].map((d) => (
|
||||
<div key={d[0] + d[1]} className="bg-white py-2">
|
||||
{d[0]}<span className="sr-only sm:not-sr-only">{d[1]}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex bg-gray-200 text-xs leading-6 text-gray-700 lg:flex-auto">
|
||||
<div className="isolate grid w-full grid-cols-7 gap-px">
|
||||
{days.map((day) => {
|
||||
const events = getEvents(calendar, day.date);
|
||||
const isSelected = isSelectedDay(day);
|
||||
|
||||
return (
|
||||
<button
|
||||
key={day.date.format('YYYY-MM-DD')}
|
||||
type="button"
|
||||
onClick={() => { setSelectedDay(day.date) }}
|
||||
className={classNames(
|
||||
day.isCurrentMonth ? 'bg-white' : 'bg-gray-50',
|
||||
isSelected || day.isToday ? 'font-semibold' : '',
|
||||
isSelected ? 'text-white' : '',
|
||||
!isSelected && day.isToday ? 'text-indigo-600' : '',
|
||||
!isSelected && day.isCurrentMonth && !day.isToday ? 'text-gray-900' : '',
|
||||
!isSelected && !day.isCurrentMonth && !day.isToday ? 'text-gray-500' : '',
|
||||
'flex h-12 sm:h-14 md:h-16 lg:h-24 flex-col px-3 py-2 hover:bg-gray-100 focus:z-10'
|
||||
)}
|
||||
>
|
||||
<time
|
||||
dateTime={day.date.format('YYYY-MM-DD')}
|
||||
className={classNames(
|
||||
isSelected ? 'flex h-6 w-6 items-center justify-center rounded-full' : '',
|
||||
isSelected && day.isToday ? 'bg-indigo-600' : '',
|
||||
isSelected && !day.isToday ? 'bg-gray-900' : '',
|
||||
'ml-auto'
|
||||
)}
|
||||
>
|
||||
{day.date.date()}
|
||||
</time>
|
||||
<span className="sr-only">{events.length} events</span>
|
||||
{events.length > 0 && (
|
||||
<>
|
||||
<span className="-mx-0.5 mt-auto flex flex-wrap-reverse lg:hidden">
|
||||
{events.map((event) => (
|
||||
<span key={event.id} className="mx-0.5 mb-1 h-1.5 w-1.5 rounded-full bg-gray-400" />
|
||||
))}
|
||||
</span>
|
||||
<ol className="mt-2 hidden lg:block">
|
||||
{events.slice(0, 2).map((event) => (
|
||||
<li key={event.id}>
|
||||
<p className="flex-auto truncate font-medium text-gray-900 group-hover:text-indigo-600">
|
||||
{event.name}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
{events.length > 2 && <li className="text-gray-500">+ {events.length - 2} more</li>}
|
||||
</ol>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-10">
|
||||
<h2 className="text-base font-semibold leading-6 text-zinc-800">
|
||||
Events on <time dateTime={selectedDay.format('YYYY-MM-DD')}>{selectedDay.format('MMMM D')}</time>
|
||||
</h2>
|
||||
{selectedDayEvents.length > 0 && (
|
||||
<div className="py-5">
|
||||
<ol className="divide-y divide-gray-100 overflow-hidden rounded-lg bg-white text-sm shadow ring-1 ring-black ring-opacity-5">
|
||||
{selectedDayEvents.map((event) => {
|
||||
const start = dayjs(event.start);
|
||||
return (
|
||||
<li key={event.id} className="group flex p-4 pr-6 focus-within:bg-gray-50 hover:bg-gray-50">
|
||||
<div className="flex-auto">
|
||||
<p className="font-semibold text-gray-900">{event.name}</p>
|
||||
<time dateTime={start.format('YYYY-MM-DD HH:mm')} className="mt-2 flex items-center text-gray-700">
|
||||
<ClockIcon className="mr-2 h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
{event.allDay
|
||||
? "All day"
|
||||
: start.format("h:mm a")
|
||||
}
|
||||
</time>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</div>
|
||||
)}
|
||||
{selectedDayEvents.length === 0 && (
|
||||
<p className="py-6 text-sm font-semibold text-gray-600">No events on this day</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
If you are interested in reserving the hall, please see the
|
||||
<Link href="/rental" className="pl-1 text-blue-600 hover:underline"
|
||||
>hall rental page
|
||||
</Link>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
119
src/app/calendar/page.tsx
Normal file
119
src/app/calendar/page.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
import * as ical from "ical";
|
||||
import dayjs from "dayjs";
|
||||
import React from 'react';
|
||||
import { CalendarComponent } from "./calendar";
|
||||
import { Container } from "@/components/Container";
|
||||
|
||||
const icalAddr = process.env.CALENDAR_ADDR || '';
|
||||
|
||||
export type Calendar = Record<number, MonthEvents>;
|
||||
type MonthEvents = Record<number, DayEvents>;
|
||||
type DayEvents = Record<number, Event[]>;
|
||||
|
||||
let calendar: Calendar;
|
||||
let upcomingEvents: Event[] = [];
|
||||
let calendarLastRefresh: dayjs.Dayjs;
|
||||
|
||||
export async function getCalendar(): Promise<Calendar> {
|
||||
const now = dayjs();
|
||||
if (!calendar || !calendarLastRefresh || now.diff(calendarLastRefresh, 'hours') >= 1) {
|
||||
await loadCalendar();
|
||||
calendarLastRefresh = now;
|
||||
}
|
||||
|
||||
return calendar;
|
||||
}
|
||||
|
||||
export async function getUpcomingEvents(): Promise<Event[]> {
|
||||
await getCalendar();
|
||||
return upcomingEvents;
|
||||
}
|
||||
|
||||
/** Event must be a "plain object" so that it can be passed to the Calendar client component. */
|
||||
export interface Event {
|
||||
id: string;
|
||||
name: string;
|
||||
allDay: boolean;
|
||||
start: Date;
|
||||
end: Date;
|
||||
}
|
||||
|
||||
async function loadCalendar(): Promise<void> {
|
||||
if (icalAddr === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
const icalContents = await (await fetch(icalAddr)).text();
|
||||
const events = ical.parseICS(icalContents);
|
||||
const thisMonth = dayjs().startOf('month');
|
||||
const yesterday = dayjs().startOf('day').subtract(1, 'day');
|
||||
const upcoming: Event[] = []
|
||||
|
||||
const cal: Calendar = {};
|
||||
for (const id in events) {
|
||||
if (!events.hasOwnProperty(id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const event = events[id];
|
||||
const start = dayjs(event.start);
|
||||
const end = dayjs(event.end);
|
||||
const allDay = start.hour() === 0 && start.minute() === 0 && end.diff(start, 'hours') === 24;
|
||||
const privateEvent = event.description?.toLowerCase().includes("private");
|
||||
|
||||
const converted = {
|
||||
id: event.uid || "",
|
||||
name: privateEvent ? "Private event" : (event.summary || ""),
|
||||
allDay: allDay,
|
||||
start: start.toDate(),
|
||||
end: end.toDate(),
|
||||
}
|
||||
|
||||
// Don't spend any more time on events that aren't going to be displayed.
|
||||
if (start.isBefore(thisMonth)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (upcoming.length < 4 && start.isAfter(yesterday) && !privateEvent) {
|
||||
upcoming.push(converted);
|
||||
}
|
||||
|
||||
const year = start.year();
|
||||
const month = start.month();
|
||||
const day = start.date();
|
||||
|
||||
if (!(year in cal)) {
|
||||
cal[year] = {};
|
||||
}
|
||||
const yearMap = cal[year];
|
||||
|
||||
if (!(month in yearMap)) {
|
||||
yearMap[month] = {};
|
||||
}
|
||||
const monthMap = yearMap[month];
|
||||
|
||||
if (day in monthMap) {
|
||||
monthMap[day].push(converted);
|
||||
} else {
|
||||
monthMap[day] = [converted];
|
||||
}
|
||||
}
|
||||
|
||||
calendar = cal;
|
||||
upcomingEvents = upcoming;
|
||||
}
|
||||
|
||||
export default async function Calendar() {
|
||||
const calendar = await getCalendar();
|
||||
|
||||
return (
|
||||
<Container className="mt-16 sm:mt-32">
|
||||
<header className="max-w-2xl">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 sm:text-5xl">
|
||||
Hall Calendar
|
||||
</h1>
|
||||
</header>
|
||||
<CalendarComponent calendar={calendar} />
|
||||
</Container>
|
||||
);
|
||||
}
|
@ -6,7 +6,7 @@ import dynamic from "next/dynamic";
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { Container } from '@/components/Container'
|
||||
import { UserPlusIcon, GiftIcon, EnvelopeIcon, UserGroupIcon } from '@heroicons/react/24/solid'
|
||||
import { UserPlusIcon, AtSymbolIcon, EnvelopeIcon, UserGroupIcon } from '@heroicons/react/24/solid'
|
||||
import interiorEmptyImage from '@/images/photos/interior-empty.jpg'
|
||||
|
||||
function SocialLink({
|
||||
@ -24,7 +24,7 @@ function SocialLink({
|
||||
<li className={clsx(className, 'flex')}>
|
||||
<Link
|
||||
href={href}
|
||||
className="group flex text-sm font-medium text-zinc-800 transition hover:text-teal-500 dark:text-zinc-200 dark:hover:text-teal-500"
|
||||
className="group flex text-sm font-medium text-zinc-800 transition hover:text-teal-500"
|
||||
>
|
||||
<Icon className="h-6 w-6 flex-none fill-zinc-500 transition group-hover:fill-teal-500" />
|
||||
<span className="ml-4">{children}</span>
|
||||
@ -61,15 +61,15 @@ export default function Club() {
|
||||
src={interiorEmptyImage}
|
||||
alt=""
|
||||
sizes="(min-width: 1024px) 32rem, 20rem"
|
||||
className="aspect-square rounded-2xl bg-zinc-100 object-cover dark:bg-zinc-800"
|
||||
className="aspect-square rounded-2xl bg-zinc-100 object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:order-first lg:row-span-2">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 sm:text-5xl">
|
||||
West Sound Community Club
|
||||
</h1>
|
||||
<div className="mt-6 space-y-7 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<div className="mt-6 space-y-7 text-base text-zinc-600">
|
||||
<p>
|
||||
The West Sound Community Club is a group of neighbors and friends
|
||||
living in the West Sound area. Together we steward the West Sound
|
||||
@ -97,13 +97,16 @@ export default function Club() {
|
||||
<SocialLink href="/WSCC-Membership-Form.pdf" icon={UserPlusIcon}>
|
||||
Membership Form
|
||||
</SocialLink>
|
||||
<SocialLink href="#" icon={GiftIcon} className="mt-4">
|
||||
Donations (Coming soon)
|
||||
<SocialLink href="https://groups.orcashub.org/invites/YkP79vjDtz" icon={AtSymbolIcon} className="mt-4">
|
||||
Club Member Mailing List
|
||||
</SocialLink>
|
||||
<SocialLink href="https://groups.orcashub.org/invites/Lg87tP3G2V" icon={AtSymbolIcon} className="mt-4">
|
||||
West Sound Neighbor to Neighbor Mailing List
|
||||
</SocialLink>
|
||||
<SocialLink
|
||||
href="mailto:contact@westsoundhall.org"
|
||||
icon={EnvelopeIcon}
|
||||
className="mt-8 border-t border-zinc-100 pt-8 dark:border-zinc-700/40"
|
||||
className="mt-8 border-t border-zinc-100 pt-8"
|
||||
>
|
||||
contact@westsoundhall.org
|
||||
</SocialLink>
|
||||
|
@ -166,7 +166,7 @@ function CheckoutForm({
|
||||
|
||||
{/* Membership Type */}
|
||||
<RadioGroup value={selectedMembershipLevel} onChange={setSelectedMembershipLevel} className="space-y-3">
|
||||
<RadioGroup.Label className="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
<RadioGroup.Label className="text-base font-semibold leading-6 text-gray-900">
|
||||
Select a membership type
|
||||
</RadioGroup.Label>
|
||||
|
||||
@ -177,8 +177,8 @@ function CheckoutForm({
|
||||
value={membership}
|
||||
className={({ active }) =>
|
||||
classNames(
|
||||
active ? 'border-indigo-600 ring-2 ring-indigo-600' : 'border-gray-200 dark:border-gray-500',
|
||||
'relative flex cursor-pointer rounded-lg border bg-white hover:bg-gray-50 hover:dark:bg-zinc-600 dark:bg-zinc-700 p-4 shadow-sm focus:outline-none'
|
||||
active ? 'border-indigo-600 ring-2 ring-indigo-600' : 'border-gray-200',
|
||||
'relative flex cursor-pointer rounded-lg border bg-white hover:bg-gray-50 p-4 shadow-sm focus:outline-none'
|
||||
)
|
||||
}
|
||||
>
|
||||
@ -186,7 +186,7 @@ function CheckoutForm({
|
||||
<>
|
||||
<span className="flex flex-1 items-center justify-between ">
|
||||
<span className="flex flex-col">
|
||||
<RadioGroup.Label as="span" className="block text-sm font-medium text-gray-900 dark:text-white">
|
||||
<RadioGroup.Label as="span" className="block text-sm font-medium text-gray-900">
|
||||
{membership.title}
|
||||
</RadioGroup.Label>
|
||||
<RadioGroup.Description as="span" className="mt-1 flex items-center text-sm text-gray-500">
|
||||
@ -194,7 +194,7 @@ function CheckoutForm({
|
||||
</RadioGroup.Description>
|
||||
</span>
|
||||
<RadioGroup.Description as="span" className="ml-8 text-sm font-medium">
|
||||
<span className="text-gray-900 dark:text-white">${membership.price}</span>
|
||||
<span className="text-gray-900">${membership.price}</span>
|
||||
<span className="text-gray-500">/yr</span>
|
||||
</RadioGroup.Description>
|
||||
</span>
|
||||
@ -215,7 +215,7 @@ function CheckoutForm({
|
||||
|
||||
{/* Additional donation */}
|
||||
<RadioGroup value={selectedAdditionalDonation} onChange={setSelectedAdditionalDonation} className="space-y-3">
|
||||
<RadioGroup.Label className="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
<RadioGroup.Label className="text-base font-semibold leading-6 text-gray-900">
|
||||
Additional donation
|
||||
</RadioGroup.Label>
|
||||
<div className="grid grid-cols-3 gap-3 sm:grid-cols-5">
|
||||
@ -229,8 +229,8 @@ function CheckoutForm({
|
||||
option === -1 ? 'col-span-2' : '',
|
||||
checked
|
||||
? 'ring-2 ring-indigo-600'
|
||||
: 'ring-1 ring-inset ring-gray-200 dark:ring-gray-500 text-gray-900 dark:text-white hover:bg-gray-50 hover:dark:bg-zinc-600',
|
||||
'flex items-center justify-center rounded-md py-3 px-3 text-sm font-semibold sm:flex-1 bg-white dark:bg-zinc-700'
|
||||
: 'ring-1 ring-inset ring-gray-200 text-gray-900 hover:bg-gray-50',
|
||||
'flex items-center justify-center rounded-md py-3 px-3 text-sm font-semibold sm:flex-1 bg-white'
|
||||
)
|
||||
}
|
||||
>
|
||||
@ -250,7 +250,7 @@ function CheckoutForm({
|
||||
min="0"
|
||||
step="1"
|
||||
onChange={(e) => setCustomAmount(e.target.value)}
|
||||
className="block w-full rounded-md border-0 py-1.5 pl-7 pr-12 text-gray-900 dark:text-white dark:bg-zinc-700 ring-1 ring-inset ring-gray-200 dark:ring-gray-500 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-gray-500 sm:text-sm sm:leading-6"
|
||||
className="block w-full rounded-md border-0 py-1.5 pl-7 pr-12 text-gray-900 ring-1 ring-inset ring-gray-200 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-gray-500 sm:text-sm sm:leading-6"
|
||||
placeholder="Custom"
|
||||
aria-describedby="price-currency"
|
||||
/>
|
||||
@ -274,12 +274,12 @@ function CheckoutForm({
|
||||
</RadioGroup>
|
||||
|
||||
<div className="space-y-3">
|
||||
<h2 className="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
<h2 className="text-base font-semibold leading-6 text-gray-900">
|
||||
About you
|
||||
</h2>
|
||||
|
||||
<div className="rounded-md mb-3 px-3 pb-1.5 pt-2.5 shadow-sm ring-1 ring-inset ring-gray-200 dark:ring-gray-500 focus-within:ring-2 focus-within:ring-indigo-600 dark:bg-zinc-700 dark:text-white">
|
||||
<label htmlFor="name" className="block text-xs font-medium text-gray-900 dark:text-zinc-400">
|
||||
<div className="rounded-md mb-3 px-3 pb-1.5 pt-2.5 shadow-sm ring-1 ring-inset ring-gray-200 focus-within:ring-2 focus-within:ring-indigo-600">
|
||||
<label htmlFor="name" className="block text-xs font-medium text-gray-900">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
@ -288,7 +288,7 @@ function CheckoutForm({
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="block w-full border-0 p-0 dark:bg-zinc-700 text-gray-900 dark:text-white placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
||||
className="block w-full border-0 p-0 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
||||
placeholder="you@example.com"
|
||||
/>
|
||||
</div>
|
||||
@ -296,7 +296,7 @@ function CheckoutForm({
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<h2 className="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
<h2 className="text-base font-semibold leading-6 text-gray-900">
|
||||
Payment
|
||||
</h2>
|
||||
<div className="mt-1 text-sm text-gray-500">
|
||||
@ -358,7 +358,6 @@ export default function ClubPayment() {
|
||||
const [paymentIntent, setPaymentIntent] = useState('');
|
||||
|
||||
const htmlEl = document.getElementsByTagName('html')[0];
|
||||
const darkTheme = htmlEl.classList.contains("dark");
|
||||
|
||||
useEffect(() => {
|
||||
// Create PaymentIntent as soon as the page loads using our local API
|
||||
@ -388,20 +387,6 @@ export default function ClubPayment() {
|
||||
labels: 'floating',
|
||||
};
|
||||
|
||||
const cb = useCallback(
|
||||
() => {
|
||||
const updatedHtmlEl = document.getElementsByTagName('html')[0];
|
||||
const updatedDarkTheme = updatedHtmlEl.classList.contains("dark");
|
||||
|
||||
if (updatedDarkTheme !== darkTheme) {
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
[darkTheme]
|
||||
)
|
||||
|
||||
useMutationObservable(htmlEl, cb);
|
||||
|
||||
const options: StripeElementsOptions = {
|
||||
clientSecret,
|
||||
appearance,
|
||||
|
@ -23,7 +23,7 @@ function SocialLink({
|
||||
<li className={clsx(className, 'flex')}>
|
||||
<Link
|
||||
href={href}
|
||||
className="group flex text-sm font-medium text-zinc-800 transition hover:text-teal-500 dark:text-zinc-200 dark:hover:text-teal-500"
|
||||
className="group flex text-sm font-medium text-zinc-800 transition hover:text-teal-500"
|
||||
>
|
||||
<Icon className="h-6 w-6 flex-none fill-zinc-500 transition group-hover:fill-teal-500" />
|
||||
<span className="ml-4">{children}</span>
|
||||
@ -94,18 +94,18 @@ export default function About() {
|
||||
src={originalDeedImage}
|
||||
alt=""
|
||||
sizes="(min-width: 1024px) 32rem, 20rem"
|
||||
className="rotate-3 rounded-2xl bg-zinc-100 object-cover dark:bg-zinc-800"
|
||||
className="rotate-3 rounded-2xl bg-zinc-100 object-cover"
|
||||
/>
|
||||
<p className="text-sm text-zinc-400 dark:text-zinc-500">
|
||||
<p className="text-sm text-zinc-400">
|
||||
Original property deed from Alexander Chalmers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:order-first lg:row-span-2">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 sm:text-5xl">
|
||||
Hall History
|
||||
</h1>
|
||||
<div className="mt-6 space-y-7 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<div className="mt-6 space-y-7 text-base text-zinc-600">
|
||||
<p>
|
||||
The West Sound Community Hall represents the history and character
|
||||
of Orcas Island. Members of the nonprofit West Sound Community Club,
|
||||
@ -130,7 +130,7 @@ export default function About() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-8 space-y-8">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 sm:text-5xl">
|
||||
Detailed History
|
||||
</h2>
|
||||
|
||||
@ -154,7 +154,7 @@ export default function About() {
|
||||
title='1903 - 1935'
|
||||
description='During this period the following organizations were regular users of the Hall:'
|
||||
>
|
||||
<div className="relative z-10 ml-4 mt-2 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<div className="relative z-10 ml-4 mt-2 text-sm text-zinc-600">
|
||||
<DateListItem
|
||||
year='1903'
|
||||
value='Women's Christian Temperance Union Woodmen Lodge'
|
||||
|
@ -27,7 +27,7 @@ export default function RootLayout({
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className="h-full antialiased" suppressHydrationWarning>
|
||||
<body className="flex h-full bg-zinc-50 dark:bg-black">
|
||||
<body className="flex h-full bg-zinc-50">
|
||||
<Providers>
|
||||
<div className="flex w-full">
|
||||
<Layout>{children}</Layout>
|
||||
|
@ -25,7 +25,7 @@ all over Orcas Island, many of which dated back to the early 1900's.
|
||||
|
||||
<div className="not-prose flex flex-col items-center">
|
||||
<Image src={boddingtonStore} alt="Boddington's Store at the end of Crow Valley Road" />
|
||||
<span className="mt-2 text-sm text-zinc-400 dark:text-zinc-500">
|
||||
<span className="mt-2 text-sm text-zinc-400">
|
||||
Boddington's Store at the end of Crow Valley Road.
|
||||
</span>
|
||||
</div>
|
||||
@ -36,7 +36,7 @@ passed down by John's family.
|
||||
|
||||
<div className="not-prose flex flex-col items-center">
|
||||
<Image src={westSoundMainStreet} alt="West Sound Main Street" />
|
||||
<span className="mt-2 text-sm text-zinc-400 dark:text-zinc-500">
|
||||
<span className="mt-2 text-sm text-zinc-400">
|
||||
West Sound Main Street
|
||||
</span>
|
||||
</div>
|
||||
@ -46,7 +46,7 @@ Sound, and how few trees there were on the island!
|
||||
|
||||
<div className="not-prose flex flex-col items-center">
|
||||
<Image src={westSound} alt="West Sound and the south end of Turtleback" />
|
||||
<span className="mt-2 text-sm text-zinc-400 dark:text-zinc-500">
|
||||
<span className="mt-2 text-sm text-zinc-400">
|
||||
West Sound and the south end of Turtleback
|
||||
</span>
|
||||
</div>
|
||||
|
@ -23,7 +23,7 @@ can be prioritized and planned in the future.
|
||||
|
||||
<div className="not-prose flex flex-col items-center">
|
||||
<Image src={townHallMeeting} alt="Town Hall Meeting" />
|
||||
<span className="mt-2 text-sm text-zinc-400 dark:text-zinc-500">
|
||||
<span className="mt-2 text-sm text-zinc-400">
|
||||
Town Hall Meeting
|
||||
</span>
|
||||
</div>
|
||||
|
@ -47,7 +47,7 @@ export default async function ArticlesIndex() {
|
||||
title="West Sound Hall News"
|
||||
intro="History, Announcements, and more from the West Sound Hall and Community Club."
|
||||
>
|
||||
<div className="md:border-l md:border-zinc-100 md:pl-6 md:dark:border-zinc-700/40">
|
||||
<div className="md:border-l md:border-zinc-100 md:pl-6">
|
||||
<div className="flex max-w-3xl flex-col space-y-16">
|
||||
{articles.map((article) => (
|
||||
<Article key={article.slug} article={article} />
|
||||
|
@ -31,7 +31,7 @@ years.
|
||||
|
||||
<div className="not-prose flex flex-col items-center">
|
||||
<Image src={originalWebsite} alt="Screenshot of the original West Sound Hall website" />
|
||||
<span className="mt-2 text-sm text-zinc-400 dark:text-zinc-500">
|
||||
<span className="mt-2 text-sm text-zinc-400">
|
||||
Screenshot of the original West Sound Hall website.
|
||||
</span>
|
||||
</div>
|
||||
@ -42,3 +42,7 @@ WA Heritage Register. There's so much more to the hall than just potlucks,
|
||||
and this website hopes to share this with the West Sound Community.
|
||||
|
||||
Thanks for being a part of the West Sound Community!
|
||||
|
||||
For posterity, the source code for this website along with instructions for
|
||||
developing and updating it can be found
|
||||
[here](https://git.grosinger.net/tgrosinger/west-sound-hall).
|
||||
|
@ -5,13 +5,13 @@ export default function NotFound() {
|
||||
return (
|
||||
<Container className="flex h-full items-center pt-16 sm:pt-32">
|
||||
<div className="flex flex-col items-center">
|
||||
<p className="text-base font-semibold text-zinc-400 dark:text-zinc-500">
|
||||
<p className="text-base font-semibold text-zinc-400">
|
||||
404
|
||||
</p>
|
||||
<h1 className="mt-4 text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
|
||||
<h1 className="mt-4 text-4xl font-bold tracking-tight text-zinc-800 sm:text-5xl">
|
||||
Page not found
|
||||
</h1>
|
||||
<p className="mt-4 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<p className="mt-4 text-base text-zinc-600">
|
||||
Sorry, we couldn’t find the page you’re looking for.
|
||||
</p>
|
||||
<Button href="/" variant="secondary" className="mt-4">
|
||||
|
103
src/app/page.tsx
103
src/app/page.tsx
@ -7,7 +7,8 @@ import { CalendarDaysIcon } from '@heroicons/react/24/solid'
|
||||
import exteriorFrontImage from '@/images/photos/exterior-front.png'
|
||||
import { type BlogPostWithSlug, getAllBlogPosts } from '@/lib/articles'
|
||||
import { formatDate } from '@/lib/formatDate'
|
||||
import { promises as fs } from 'fs';
|
||||
import { getUpcomingEvents, Event } from './calendar/page'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
|
||||
function LinkButton({
|
||||
@ -20,7 +21,7 @@ function LinkButton({
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className="rounded-md px-3 py-2 font-semibold text-center transition dark:hover:text-teal-400 bg-sky-300 hover:bg-sky-400 dark:bg-zinc-700/40 dark:hover:bg-zinc-600/40"
|
||||
className="rounded-md px-3 py-2 font-semibold text-center transition bg-sky-300 hover:bg-sky-400"
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
@ -42,42 +43,50 @@ function Article({ article }: { article: BlogPostWithSlug }) {
|
||||
)
|
||||
}
|
||||
|
||||
interface Meeting {
|
||||
title: string
|
||||
date: string
|
||||
startTime: string
|
||||
endTime?: string
|
||||
notes?: string
|
||||
}
|
||||
function EventListItem({ event }: { event: Event }) {
|
||||
const start = dayjs(event.start);
|
||||
const end = dayjs(event.end);
|
||||
|
||||
const date = start.format('YYYY-MM-DD');
|
||||
|
||||
function MeetingListItem({ meeting }: { meeting: Meeting }) {
|
||||
return (
|
||||
<li className="flex gap-4">
|
||||
<dl className="flex flex-auto flex-wrap gap-x-2">
|
||||
<dt className="sr-only">Title</dt>
|
||||
<dd className="w-full flex-none text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{meeting.title}
|
||||
<dd className="w-full flex-none text-sm font-medium text-zinc-900">
|
||||
{event.name}
|
||||
</dd>
|
||||
<dt className="sr-only">Date</dt>
|
||||
<dd className="text-xs text-zinc-500 dark:text-zinc-400">
|
||||
{meeting.date}
|
||||
<dd className="text-xs text-zinc-500">
|
||||
{date}
|
||||
</dd>
|
||||
<dt className="sr-only">Time</dt>
|
||||
{meeting.endTime
|
||||
{event.allDay
|
||||
? <dd
|
||||
className="ml-auto text-xs text-zinc-400 dark:text-zinc-500"
|
||||
aria-label={`${meeting.startTime} until ${meeting.endTime}`}
|
||||
className="ml-auto text-xs text-zinc-400"
|
||||
aria-label="All day"
|
||||
>
|
||||
<time dateTime={meeting.date + ' ' + meeting.startTime}>{meeting.startTime}</time>{' '}
|
||||
All day
|
||||
</dd>
|
||||
: (event.end
|
||||
? (
|
||||
<dd
|
||||
className="ml-auto text-xs text-zinc-400"
|
||||
aria-label={`${start.format('YYYY-MM-DD HH:mm')} until ${end.format('YYYY-MM-DD HH-mm')}`}
|
||||
>
|
||||
<time dateTime={start.format('YYYY-MM-DD HH:mm')}>{start.format('h:mm a')}</time>{' '}
|
||||
<span aria-hidden="true">—</span>{' '}
|
||||
<time dateTime={meeting.date + ' ' + meeting.endTime}>{meeting.endTime}</time>{' '}
|
||||
<time dateTime={end.format('YYYY-MM-DD HH-mm')}>{end.format('h:mm a')}</time>{' '}
|
||||
</dd>
|
||||
: <dd
|
||||
className="ml-auto text-xs text-zinc-400 dark:text-zinc-500"
|
||||
aria-label={`${meeting.startTime}`}
|
||||
) : (
|
||||
<dd
|
||||
className="ml-auto text-xs text-zinc-400"
|
||||
aria-label={start.format('YYYY-MM-DD HH-mm')}
|
||||
>
|
||||
<time dateTime={meeting.date + ' ' + meeting.startTime}>{meeting.startTime}</time>{' '}
|
||||
<time dateTime={start.format('YYYY-MM-DD HH-mm')}>{start.format('h:mm a')}</time>{' '}
|
||||
</dd>
|
||||
)
|
||||
)
|
||||
}
|
||||
</dl>
|
||||
</li>
|
||||
@ -85,45 +94,17 @@ function MeetingListItem({ meeting }: { meeting: Meeting }) {
|
||||
}
|
||||
|
||||
async function Events() {
|
||||
const now = new Date();
|
||||
const nowYear = now.getFullYear();
|
||||
const nowMonth = now.getMonth() + 1;
|
||||
const nowDay = now.getDate();
|
||||
|
||||
const file = await fs.readFile(process.cwd() + '/src/app/upcoming-events.json', 'utf8');
|
||||
const allEvents: Array<Meeting> = JSON.parse(file);
|
||||
|
||||
// Remove any events in the past.
|
||||
const events = allEvents.filter((e) => {
|
||||
const [year, month, day] = e.date.split('-');
|
||||
|
||||
const parsedYear = parseInt(year)
|
||||
if (parsedYear > nowYear) {
|
||||
return true
|
||||
} else if (parsedYear < nowYear) {
|
||||
return false
|
||||
}
|
||||
|
||||
const parsedMonth = parseInt(month)
|
||||
if (parsedMonth > nowMonth) {
|
||||
return true
|
||||
} else if (parsedMonth < nowMonth) {
|
||||
return false
|
||||
}
|
||||
|
||||
const parsedDay = parseInt(day)
|
||||
return parsedDay >= nowDay
|
||||
});
|
||||
const events = await getUpcomingEvents();
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-zinc-100 p-6 dark:border-zinc-700/40">
|
||||
<h2 className="flex text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
<CalendarDaysIcon className="h-6 w-6 flex-none fill-zinc-100 stroke-zinc-400 dark:fill-zinc-100/10 dark:stroke-zinc-500" />
|
||||
<div className="rounded-2xl border border-zinc-100 p-6">
|
||||
<h2 className="flex text-sm font-semibold text-zinc-900">
|
||||
<CalendarDaysIcon className="h-6 w-6 flex-none fill-zinc-100 stroke-zinc-400" />
|
||||
<span className="ml-3">Upcoming Events</span>
|
||||
</h2>
|
||||
<ol className="mt-6 space-y-4">
|
||||
{events.map((meeting, idx) => (
|
||||
<MeetingListItem key={idx} meeting={meeting} />
|
||||
{events.map((event, idx) => (
|
||||
<EventListItem key={idx} event={event} />
|
||||
))}
|
||||
</ol>
|
||||
{/*
|
||||
@ -143,17 +124,17 @@ export default async function Home() {
|
||||
<div className="mx-auto max-w-7xl px-6 mt-24 lg:px-8">
|
||||
<div className="relative px-4 sm:px-8 lg:px-12">
|
||||
<div className="mx-auto max-w-2xl lg:mx-0 grid lg:max-w-none grid-cols-1 lg:grid-cols-2 lg:gap-x-8 gap-y-10">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-6xl lg:col-span-2">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 sm:text-6xl lg:col-span-2">
|
||||
West Sound Community Hall
|
||||
</h1>
|
||||
<div className="max-w-xl">
|
||||
<p className="mt-6 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<p className="mt-6 text-base text-zinc-600">
|
||||
The West Sound Community Hall is located in the hamlet of West Sound
|
||||
on Orcas Island, about 10 minutes from the ferry landing and
|
||||
Eastsound. It has served as a public assembly hall since it was
|
||||
built by volunteers in 1902.
|
||||
</p>
|
||||
<p className="mt-6 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<p className="mt-6 text-base text-zinc-600">
|
||||
Facing West Sound, the Hall is at the heart of the West Sound community.
|
||||
</p>
|
||||
</div>
|
||||
@ -162,7 +143,7 @@ export default async function Home() {
|
||||
alt="Exterior front of the West Sound Hall"
|
||||
className="lg:row-span-2 w-full max-w-xl rounded-2xl object-cover lg:max-w-none"
|
||||
/>
|
||||
<div className="h-fit max-w-xl rounded-2xl border border-zinc-100 p-6 dark:border-zinc-700/40">
|
||||
<div className="h-fit max-w-xl rounded-2xl border border-zinc-100 p-6">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<LinkButton href='/club'>Join or Renew your Membership</LinkButton>
|
||||
<LinkButton href='/rental'>Rent the Hall</LinkButton>
|
||||
|
@ -75,7 +75,7 @@ export default function Projects() {
|
||||
>
|
||||
{projects.map((project) => (
|
||||
<Card as="li" key={project.name}>
|
||||
<div className="relative z-10 flex h-12 w-12 items-center justify-center rounded-full bg-white shadow-md shadow-zinc-800/5 ring-1 ring-zinc-900/5 dark:border dark:border-zinc-700/50 dark:bg-zinc-800 dark:ring-0">
|
||||
<div className="relative z-10 flex h-12 w-12 items-center justify-center rounded-full bg-white shadow-md shadow-zinc-800/5 ring-1 ring-zinc-900/5">
|
||||
<Image
|
||||
src={project.logo}
|
||||
alt=""
|
||||
@ -83,11 +83,11 @@ export default function Projects() {
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
<h2 className="mt-6 text-base font-semibold text-zinc-800 dark:text-zinc-100">
|
||||
<h2 className="mt-6 text-base font-semibold text-zinc-800">
|
||||
<Card.Link href={project.link.href}>{project.name}</Card.Link>
|
||||
</h2>
|
||||
<Card.Description>{project.description}</Card.Description>
|
||||
<p className="relative z-10 mt-6 flex text-sm font-medium text-zinc-400 transition group-hover:text-teal-500 dark:text-zinc-200">
|
||||
<p className="relative z-10 mt-6 flex text-sm font-medium text-zinc-400 transition group-hover:text-teal-500">
|
||||
<LinkIcon className="h-6 w-6 flex-none" />
|
||||
<span className="ml-2">{project.link.label}</span>
|
||||
</p>
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
import { createContext, useEffect, useRef } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { ThemeProvider, useTheme } from 'next-themes'
|
||||
|
||||
function usePrevious<T>(value: T) {
|
||||
let ref = useRef<T>()
|
||||
@ -14,30 +13,6 @@ function usePrevious<T>(value: T) {
|
||||
return ref.current
|
||||
}
|
||||
|
||||
function ThemeWatcher() {
|
||||
let { resolvedTheme, setTheme } = useTheme()
|
||||
|
||||
useEffect(() => {
|
||||
let media = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
|
||||
function onMediaChange() {
|
||||
let systemTheme = media.matches ? 'dark' : 'light'
|
||||
if (resolvedTheme === systemTheme) {
|
||||
setTheme('system')
|
||||
}
|
||||
}
|
||||
|
||||
onMediaChange()
|
||||
media.addEventListener('change', onMediaChange)
|
||||
|
||||
return () => {
|
||||
media.removeEventListener('change', onMediaChange)
|
||||
}
|
||||
}, [resolvedTheme, setTheme])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const AppContext = createContext<{ previousPathname?: string }>({})
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
@ -46,10 +21,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={{ previousPathname }}>
|
||||
<ThemeProvider attribute="class" disableTransitionOnChange>
|
||||
<ThemeWatcher />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</AppContext.Provider>
|
||||
)
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ function SocialLink({
|
||||
<li className={clsx(className, 'flex')}>
|
||||
<Link
|
||||
href={href}
|
||||
className="group flex text-sm font-medium text-zinc-800 transition hover:text-teal-500 dark:text-zinc-200 dark:hover:text-teal-500"
|
||||
className="group flex text-sm font-medium text-zinc-800 transition hover:text-teal-500"
|
||||
>
|
||||
<Icon className="h-6 w-6 flex-none fill-zinc-500 transition group-hover:fill-teal-500" />
|
||||
<span className="ml-4">{children}</span>
|
||||
@ -49,15 +49,15 @@ export default function Rental() {
|
||||
src={exteriorFront}
|
||||
alt=""
|
||||
sizes="(min-width: 1024px) 32rem, 20rem"
|
||||
className="aspect-square rounded-2xl bg-zinc-100 object-cover dark:bg-zinc-800"
|
||||
className="aspect-square rounded-2xl bg-zinc-100 object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:order-first lg:row-span-2">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 sm:text-5xl">
|
||||
Hall Rental
|
||||
</h1>
|
||||
<div className="mt-6 space-y-7 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<div className="mt-6 space-y-7 text-base text-zinc-600">
|
||||
<p>
|
||||
The West Sound Community Hall is a public assembly hall, which has
|
||||
been in continuous operation since it was built in 1902. In 1999,
|
||||
@ -80,6 +80,12 @@ export default function Rental() {
|
||||
damage to the Hall or cleaning is necessary, deductions will be
|
||||
made at the discretion of the Board.
|
||||
</p>
|
||||
<p>
|
||||
Please check the
|
||||
<Link href="/calendar" className="pl-1 text-blue-600 hover:underline"
|
||||
>calendar
|
||||
</Link> for availability.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="order-last sm:order-none lg:pl-20">
|
||||
@ -93,14 +99,14 @@ export default function Rental() {
|
||||
<SocialLink
|
||||
href="mailto:contact@westsoundhall.org"
|
||||
icon={EnvelopeIcon}
|
||||
className="mt-8 border-t border-zinc-100 pt-8 dark:border-zinc-700/40"
|
||||
className="mt-8 border-t border-zinc-100 pt-8"
|
||||
>
|
||||
contact@westsoundhall.org
|
||||
</SocialLink>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="lg:col-span-2">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-2xl">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 sm:text-2xl">
|
||||
Rental Rates
|
||||
</h2>
|
||||
|
||||
@ -151,8 +157,8 @@ export default function Rental() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 space-y-5 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-2xl">
|
||||
<div className="mt-6 space-y-5 text-base text-zinc-600">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 sm:text-2xl">
|
||||
Use Restrictions
|
||||
</h2>
|
||||
|
||||
@ -216,8 +222,8 @@ export default function Rental() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 space-y-5 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-2xl">
|
||||
<div className="mt-6 space-y-5 text-base text-zinc-600">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 sm:text-2xl">
|
||||
Capacity
|
||||
</h2>
|
||||
|
||||
@ -227,8 +233,8 @@ export default function Rental() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 space-y-5 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-2xl">
|
||||
<div className="mt-6 space-y-5 text-base text-zinc-600">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 sm:text-2xl">
|
||||
Parking
|
||||
</h2>
|
||||
|
||||
@ -249,8 +255,8 @@ export default function Rental() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 space-y-5 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-2xl">
|
||||
<div className="mt-6 space-y-5 text-base text-zinc-600">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-800 sm:text-2xl">
|
||||
Accessibility
|
||||
</h2>
|
||||
|
||||
|
@ -23,7 +23,7 @@ export default async function ThankYou({
|
||||
title="Thanks for becoming a member."
|
||||
intro="Thank you for joining the West Sound Community Club."
|
||||
>
|
||||
<p className="mt-6 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<p className="mt-6 text-base text-zinc-600">
|
||||
Your financial support helps us perserve this historic building and to host events for the community. We'll add you to our member mailing list so you receive announcement emails about upcoming events.
|
||||
</p>
|
||||
</SimpleLayout>
|
||||
|
@ -1,24 +0,0 @@
|
||||
[
|
||||
{
|
||||
"title": "February Potluck",
|
||||
"date": "2024-02-17",
|
||||
"startTime": "6:00pm",
|
||||
"notes": "With historic West Sound slide show."
|
||||
},
|
||||
{
|
||||
"title": "Town Hall Meeting",
|
||||
"date": "2024-03-06",
|
||||
"startTime": "5:00pm",
|
||||
"endTime": "7:00pm"
|
||||
},
|
||||
{
|
||||
"title": "April Potluck",
|
||||
"date": "2024-04-20",
|
||||
"startTime": "6:00pm"
|
||||
},
|
||||
{
|
||||
"title": "May Potluck",
|
||||
"date": "2024-05-18",
|
||||
"startTime": "6:00pm"
|
||||
}
|
||||
]
|
@ -41,24 +41,24 @@ export function ArticleLayout({
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
aria-label="Go back to articles"
|
||||
className="group mb-8 flex h-10 w-10 items-center justify-center rounded-full bg-white shadow-md shadow-zinc-800/5 ring-1 ring-zinc-900/5 transition dark:border dark:border-zinc-700/50 dark:bg-zinc-800 dark:ring-0 dark:ring-white/10 dark:hover:border-zinc-700 dark:hover:ring-white/20 lg:absolute lg:-left-5 lg:-mt-2 lg:mb-0 xl:-top-1.5 xl:left-0 xl:mt-0"
|
||||
className="group mb-8 flex h-10 w-10 items-center justify-center rounded-full bg-white shadow-md shadow-zinc-800/5 ring-1 ring-zinc-900/5 transition lg:absolute lg:-left-5 lg:-mt-2 lg:mb-0 xl:-top-1.5 xl:left-0 xl:mt-0"
|
||||
>
|
||||
<ArrowLeftIcon className="h-4 w-4 stroke-zinc-500 transition group-hover:stroke-zinc-700 dark:stroke-zinc-500 dark:group-hover:stroke-zinc-400" />
|
||||
<ArrowLeftIcon className="h-4 w-4 stroke-zinc-500 transition group-hover:stroke-zinc-700" />
|
||||
</button>
|
||||
)}
|
||||
<article>
|
||||
<header className="flex flex-col">
|
||||
<h1 className="mt-6 text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
|
||||
<h1 className="mt-6 text-4xl font-bold tracking-tight text-zinc-800 sm:text-5xl">
|
||||
{article.title}
|
||||
</h1>
|
||||
<time
|
||||
dateTime={article.date}
|
||||
className="order-first flex items-center text-base text-zinc-400 dark:text-zinc-500"
|
||||
className="order-first flex items-center text-base text-zinc-400"
|
||||
>
|
||||
<span className="h-4 w-0.5 rounded-full bg-zinc-200 dark:bg-zinc-500" />
|
||||
<span className="h-4 w-0.5 rounded-full bg-zinc-200" />
|
||||
<span className="ml-3">{formatDate(article.date)}</span>
|
||||
</time>
|
||||
<span id="byline" className='mt-4 text-base text-zinc-400 dark:text-zinc-500'>by {article.author}</span>
|
||||
<span id="byline" className="mt-4 text-base text-zinc-400">by {article.author}</span>
|
||||
</header>
|
||||
<Prose className="mt-8" data-mdx-content>
|
||||
{children}
|
||||
|
@ -3,9 +3,9 @@ import clsx from 'clsx'
|
||||
|
||||
const variantStyles = {
|
||||
primary:
|
||||
'bg-zinc-800 font-semibold text-zinc-100 hover:bg-zinc-700 active:bg-zinc-800 active:text-zinc-100/70 dark:bg-zinc-700 dark:hover:bg-zinc-600 dark:active:bg-zinc-700 dark:active:text-zinc-100/70',
|
||||
'bg-zinc-800 font-semibold text-zinc-100 hover:bg-zinc-700 active:bg-zinc-800 active:text-zinc-100/70',
|
||||
secondary:
|
||||
'bg-zinc-50 font-medium text-zinc-900 hover:bg-zinc-100 active:bg-zinc-100 active:text-zinc-900/60 dark:bg-zinc-800/50 dark:text-zinc-300 dark:hover:bg-zinc-800 dark:hover:text-zinc-50 dark:active:bg-zinc-800/50 dark:active:text-zinc-50/70',
|
||||
'bg-zinc-50 font-medium text-zinc-900 hover:bg-zinc-100 active:bg-zinc-100 active:text-zinc-900/60',
|
||||
}
|
||||
|
||||
type ButtonProps = {
|
||||
|
@ -39,7 +39,7 @@ Card.Link = function CardLink({
|
||||
}: React.ComponentPropsWithoutRef<typeof Link>) {
|
||||
return (
|
||||
<>
|
||||
<div className="absolute -inset-x-4 -inset-y-6 z-0 scale-95 bg-zinc-50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 dark:bg-zinc-800/50 sm:-inset-x-6 sm:rounded-2xl" />
|
||||
<div className="absolute -inset-x-4 -inset-y-6 z-0 scale-95 bg-zinc-50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 sm:-inset-x-6 sm:rounded-2xl" />
|
||||
<Link {...props}>
|
||||
<span className="absolute -inset-x-4 -inset-y-6 z-20 sm:-inset-x-6 sm:rounded-2xl" />
|
||||
<span className="relative z-10">{children}</span>
|
||||
@ -59,7 +59,7 @@ Card.Title = function CardTitle<T extends React.ElementType = 'h2'>({
|
||||
let Component = as ?? 'h2'
|
||||
|
||||
return (
|
||||
<Component className="text-base font-semibold tracking-tight text-zinc-800 dark:text-zinc-100">
|
||||
<Component className="text-base font-semibold tracking-tight text-zinc-800">
|
||||
{href ? <Card.Link href={href}>{children}</Card.Link> : children}
|
||||
</Component>
|
||||
)
|
||||
@ -71,7 +71,7 @@ Card.Description = function CardDescription({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<p className="relative z-10 mt-2 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<p className="relative z-10 mt-2 text-sm text-zinc-600">
|
||||
{children}
|
||||
</p>
|
||||
)
|
||||
@ -105,7 +105,7 @@ Card.Eyebrow = function CardEyebrow<T extends React.ElementType = 'p'>({
|
||||
<Component
|
||||
className={clsx(
|
||||
className,
|
||||
'relative z-10 order-first mb-3 flex items-center text-sm text-zinc-400 dark:text-zinc-500',
|
||||
"relative z-10 order-first mb-3 flex items-center text-sm text-zinc-400",
|
||||
decorate && 'pl-3.5',
|
||||
)}
|
||||
{...props}
|
||||
@ -115,7 +115,7 @@ Card.Eyebrow = function CardEyebrow<T extends React.ElementType = 'p'>({
|
||||
className="absolute inset-y-0 left-0 flex items-center"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span className="h-4 w-0.5 rounded-full bg-zinc-200 dark:bg-zinc-500" />
|
||||
<span className="h-4 w-0.5 rounded-full bg-zinc-200" />
|
||||
</span>
|
||||
)}
|
||||
{children}
|
||||
|
@ -12,7 +12,7 @@ function NavLink({
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className="transition hover:text-teal-500 dark:hover:text-teal-400"
|
||||
className="transition hover:text-teal-500"
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
@ -23,20 +23,21 @@ export function Footer() {
|
||||
return (
|
||||
<footer className="mt-32 flex-none">
|
||||
<ContainerOuter>
|
||||
<div className="border-t border-zinc-100 pb-16 pt-10 dark:border-zinc-700/40">
|
||||
<div className="border-t border-zinc-100 pb-16 pt-10">
|
||||
<ContainerInner>
|
||||
<div className="flex flex-col items-center justify-between gap-6 sm:flex-row">
|
||||
<div className="flex flex-wrap justify-center gap-x-6 gap-y-1 text-sm font-medium text-zinc-800 dark:text-zinc-200">
|
||||
<div className="flex flex-wrap justify-center gap-x-6 gap-y-1 text-sm font-medium text-zinc-800">
|
||||
<NavLink href="/hall-history">History</NavLink>
|
||||
<NavLink href="/news">News</NavLink>
|
||||
<NavLink href="/calendar">Calendar</NavLink>
|
||||
<NavLink href="/rental">Rental</NavLink>
|
||||
<NavLink href="/club">Club</NavLink>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-zinc-400 dark:text-zinc-500">
|
||||
<p className="text-sm text-zinc-400">
|
||||
© {new Date().getFullYear()} West Sound Community Club. All rights reserved.
|
||||
</p>
|
||||
<p className="text-sm text-zinc-400 dark:text-zinc-500">
|
||||
<p className="text-sm text-zinc-400">
|
||||
WSCC is a 501c3 nonprofit organization - 91-1283768
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,47 +1,14 @@
|
||||
'use client'
|
||||
|
||||
import { Fragment, useEffect, useRef, useState } from 'react'
|
||||
import { Fragment, useEffect, useRef } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
import { XMarkIcon, ChevronDownIcon } from '@heroicons/react/24/solid'
|
||||
|
||||
import { Container } from '@/components/Container'
|
||||
|
||||
function SunIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M8 12.25A4.25 4.25 0 0 1 12.25 8v0a4.25 4.25 0 0 1 4.25 4.25v0a4.25 4.25 0 0 1-4.25 4.25v0A4.25 4.25 0 0 1 8 12.25v0Z" />
|
||||
<path
|
||||
d="M12.25 3v1.5M21.5 12.25H20M18.791 18.791l-1.06-1.06M18.791 5.709l-1.06 1.06M12.25 20v1.5M4.5 12.25H3M6.77 6.77 5.709 5.709M6.77 17.73l-1.061 1.061"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MoonIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" {...props}>
|
||||
<path
|
||||
d="M17.25 16.22a6.937 6.937 0 0 1-9.47-9.47 7.451 7.451 0 1 0 9.47 9.47ZM12.75 7C17 7 17 2.75 17 2.75S17 7 21.25 7C17 7 17 11.25 17 11.25S17 7 12.75 7Z"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MobileNavItem({
|
||||
href,
|
||||
children,
|
||||
@ -63,9 +30,9 @@ function MobileNavigation(
|
||||
) {
|
||||
return (
|
||||
<Popover {...props}>
|
||||
<Popover.Button className="group flex items-center rounded-full bg-white/90 px-4 py-2 text-sm font-medium text-zinc-800 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur dark:bg-zinc-800/90 dark:text-zinc-200 dark:ring-white/10 dark:hover:ring-white/20">
|
||||
<Popover.Button className="group flex items-center rounded-full bg-white/90 px-4 py-2 text-sm font-medium text-zinc-800 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur">
|
||||
Menu
|
||||
<ChevronDownIcon className="ml-3 h-auto w-2 stroke-zinc-500 group-hover:stroke-zinc-700 dark:group-hover:stroke-zinc-400" />
|
||||
<ChevronDownIcon className="ml-3 h-auto w-2 stroke-zinc-500 group-hover:stroke-zinc-700" />
|
||||
</Popover.Button>
|
||||
<Transition.Root>
|
||||
<Transition.Child
|
||||
@ -77,7 +44,7 @@ function MobileNavigation(
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Popover.Overlay className="fixed inset-0 z-50 bg-zinc-800/40 backdrop-blur-sm dark:bg-black/80" />
|
||||
<Popover.Overlay className="fixed inset-0 z-50 bg-zinc-800/40 backdrop-blur-sm" />
|
||||
</Transition.Child>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
@ -90,21 +57,22 @@ function MobileNavigation(
|
||||
>
|
||||
<Popover.Panel
|
||||
focus
|
||||
className="fixed inset-x-4 top-8 z-50 origin-top rounded-3xl bg-white p-8 ring-1 ring-zinc-900/5 dark:bg-zinc-900 dark:ring-zinc-800"
|
||||
className="fixed inset-x-4 top-8 z-50 origin-top rounded-3xl bg-white p-8 ring-1 ring-zinc-900/5"
|
||||
>
|
||||
<div className="flex flex-row-reverse items-center justify-between">
|
||||
<Popover.Button aria-label="Close menu" className="-m-1 p-1">
|
||||
<XMarkIcon className="h-6 w-6 text-zinc-500 dark:text-zinc-400" />
|
||||
<XMarkIcon className="h-6 w-6 text-zinc-500" />
|
||||
</Popover.Button>
|
||||
<h2 className="text-sm font-medium text-zinc-600 dark:text-zinc-400">
|
||||
<h2 className="text-sm font-medium text-zinc-600">
|
||||
Navigation
|
||||
</h2>
|
||||
</div>
|
||||
<nav className="mt-6">
|
||||
<ul className="-my-2 divide-y divide-zinc-100 text-base text-zinc-800 dark:divide-zinc-100/5 dark:text-zinc-300">
|
||||
<ul className="-my-2 divide-y divide-zinc-100 text-base text-zinc-800">
|
||||
<MobileNavItem href="/">Home</MobileNavItem>
|
||||
<MobileNavItem href="/hall-history">Hall History</MobileNavItem>
|
||||
<MobileNavItem href="/news">News</MobileNavItem>
|
||||
<MobileNavItem href="/calendar">Calendar</MobileNavItem>
|
||||
<MobileNavItem href="/rental">Rental</MobileNavItem>
|
||||
<MobileNavItem href="/club">Club</MobileNavItem>
|
||||
</ul>
|
||||
@ -132,13 +100,13 @@ function NavItem({
|
||||
className={clsx(
|
||||
'relative block px-3 py-2 transition',
|
||||
isActive
|
||||
? 'text-teal-500 dark:text-teal-400'
|
||||
: 'hover:text-teal-500 dark:hover:text-teal-400',
|
||||
? "text-teal-500"
|
||||
: "hover:text-teal-500",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
{isActive && (
|
||||
<span className="absolute inset-x-1 -bottom-px h-px bg-gradient-to-r from-teal-500/0 via-teal-500/40 to-teal-500/0 dark:from-teal-400/0 dark:via-teal-400/40 dark:to-teal-400/0" />
|
||||
<span className="absolute inset-x-1 -bottom-px h-px bg-gradient-to-r from-teal-500/0 via-teal-500/40 to-teal-500/0" />
|
||||
)}
|
||||
</Link>
|
||||
</li>
|
||||
@ -148,10 +116,11 @@ function NavItem({
|
||||
function DesktopNavigation(props: React.ComponentPropsWithoutRef<'nav'>) {
|
||||
return (
|
||||
<nav {...props}>
|
||||
<ul className="flex rounded-full bg-white/90 px-3 text-sm font-medium text-zinc-800 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur dark:bg-zinc-800/90 dark:text-zinc-200 dark:ring-white/10">
|
||||
<ul className="flex rounded-full bg-white/90 px-3 text-sm font-medium text-zinc-800 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur">
|
||||
<NavItem href="/">Home</NavItem>
|
||||
<NavItem href="/hall-history">History</NavItem>
|
||||
<NavItem href="/news">News</NavItem>
|
||||
<NavItem href="/calendar">Calendar</NavItem>
|
||||
<NavItem href="/rental">Rental</NavItem>
|
||||
<NavItem href="/club">Club</NavItem>
|
||||
</ul>
|
||||
@ -159,28 +128,6 @@ function DesktopNavigation(props: React.ComponentPropsWithoutRef<'nav'>) {
|
||||
)
|
||||
}
|
||||
|
||||
function ThemeToggle() {
|
||||
let { resolvedTheme, setTheme } = useTheme()
|
||||
let otherTheme = resolvedTheme === 'dark' ? 'light' : 'dark'
|
||||
let [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={mounted ? `Switch to ${otherTheme} theme` : 'Toggle theme'}
|
||||
className="group rounded-full bg-white/90 px-3 py-2 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur transition dark:bg-zinc-800/90 dark:ring-white/10 dark:hover:ring-white/20"
|
||||
onClick={() => setTheme(otherTheme)}
|
||||
>
|
||||
<SunIcon className="h-6 w-6 fill-zinc-100 stroke-zinc-500 transition group-hover:fill-zinc-200 group-hover:stroke-zinc-700 dark:hidden [@media(prefers-color-scheme:dark)]:fill-teal-50 [@media(prefers-color-scheme:dark)]:stroke-teal-500 [@media(prefers-color-scheme:dark)]:group-hover:fill-teal-50 [@media(prefers-color-scheme:dark)]:group-hover:stroke-teal-600" />
|
||||
<MoonIcon className="hidden h-6 w-6 fill-zinc-700 stroke-zinc-500 transition dark:block [@media(prefers-color-scheme:dark)]:group-hover:stroke-zinc-400 [@media_not_(prefers-color-scheme:dark)]:fill-teal-400/10 [@media_not_(prefers-color-scheme:dark)]:stroke-teal-500" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function clamp(number: number, a: number, b: number) {
|
||||
let min = Math.min(a, b)
|
||||
let max = Math.max(a, b)
|
||||
@ -283,18 +230,10 @@ export function Header() {
|
||||
'var(--header-inner-position)' as React.CSSProperties['position'],
|
||||
}}
|
||||
>
|
||||
<div className="relative flex gap-4">
|
||||
<div className="flex flex-1"></div>
|
||||
<div className="flex flex-1 justify-end md:justify-center">
|
||||
<MobileNavigation className="pointer-events-auto md:hidden" />
|
||||
<DesktopNavigation className="pointer-events-auto hidden md:block" />
|
||||
</div>
|
||||
<div className="flex justify-end md:flex-1">
|
||||
<div className="pointer-events-auto">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -6,7 +6,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||
<>
|
||||
<div className="fixed inset-0 flex justify-center sm:px-8">
|
||||
<div className="flex w-full max-w-7xl lg:px-8">
|
||||
<div className="w-full bg-white ring-1 ring-zinc-100 dark:bg-zinc-900 dark:ring-zinc-300/20" />
|
||||
<div className="w-full bg-white ring-1 ring-zinc-100" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex w-full flex-col">
|
||||
|
@ -5,6 +5,6 @@ export function Prose({
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'div'>) {
|
||||
return (
|
||||
<div className={clsx(className, 'prose dark:prose-invert')} {...props} />
|
||||
<div className={clsx(className, 'prose')} {...props} />
|
||||
)
|
||||
}
|
||||
|
@ -12,12 +12,12 @@ export function Section({
|
||||
return (
|
||||
<section
|
||||
aria-labelledby={id}
|
||||
className="md:border-l md:border-zinc-100 md:pl-6 md:dark:border-zinc-700/40"
|
||||
className="md:border-l md:border-zinc-100 md:pl-6"
|
||||
>
|
||||
<div className="grid max-w-3xl grid-cols-1 items-baseline gap-y-8 md:grid-cols-4">
|
||||
<h2
|
||||
id={id}
|
||||
className="text-sm font-semibold text-zinc-800 dark:text-zinc-100"
|
||||
className="text-sm font-semibold text-zinc-800"
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
|
@ -12,10 +12,10 @@ export function SimpleLayout({
|
||||
return (
|
||||
<Container className="mt-16 sm:mt-32">
|
||||
<header className="max-w-2xl">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 sm:text-5xl">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="mt-6 text-base text-zinc-600 dark:text-zinc-400">
|
||||
<p className="mt-6 text-base text-zinc-600">
|
||||
{intro}
|
||||
</p>
|
||||
</header>
|
||||
|
@ -4,7 +4,7 @@ export function TableHeading({
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-zinc-100">{children}</th>
|
||||
return <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">{children}</th>
|
||||
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ export function TableLeftHeading({
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return <td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 dark:text-zinc-100 sm:pl-0">{children}</td>
|
||||
return <td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0">{children}</td>
|
||||
}
|
||||
|
||||
export function TableCell({
|
||||
@ -21,5 +21,5 @@ export function TableCell({
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return <td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-zinc-400">{children}</td>
|
||||
return <td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{children}</td>
|
||||
}
|
||||
|
@ -9,8 +9,3 @@
|
||||
--stripe-background: #FFFFFF;
|
||||
--stripe-foreground: #000000;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--stripe-background: #3f3f46;
|
||||
--stripe-foreground: #E4E4E7;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user