Rejig everything

This commit is contained in:
Fergal Moran
2023-03-02 06:26:42 +00:00
parent 4c2e44da3e
commit b4c1235fc6
160 changed files with 2 additions and 157865 deletions

View File

@@ -1,13 +0,0 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

14
.env
View File

@@ -1,14 +0,0 @@
NEXT_PUBLIC_TITLE=RadioOtherway
QSTASH_CURRENT_SIGNING_KEY=khs3lpVBv1QtV/L9MTdXlcnoI8tTlg0aDfrFz+o8utA=
#auth
GOOGLE_APPLICATION_CREDENTIALS=serviceAccount.json
#calendar api
GOOGLE_CALENDAR_PROJECT_ID=47147490249
LIVE_GOOGLE_CALENDAR_ID=geh501qel59lf3505v2huebo18@group.calendar.google.com
GOOGLE_CALENDAR_ID=7732f7973f574db2638371394769a94aad5e38b98362d528cd985728d98cf3bd@group.calendar.google.com
GOOGLE_CALENDAR_API_KEY=AIzaSyAMvrSrwqvz9o4Y8b-0zneU-REWDIzuKR0
GOOGLE_CALENDAR_CREDENTIALS_CLIENT_EMAIL="otherway-calendar-proxy@radio-otherway.iam.gserviceaccount.com"

View File

@@ -1,97 +0,0 @@
const prettierConfig = require("./.prettierrc.js");
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
node: true,
},
extends: [
"next",
"prettier",
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"next/core-web-vitals",
],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: "module",
},
plugins: ["react"],
rules: {
// Possible errors
"no-console": "warn",
// Best practices
"dot-notation": "error",
"no-else-return": "error",
"no-floating-decimal": "error",
"no-sequences": "error",
// Stylistic
"array-bracket-spacing": "error",
"computed-property-spacing": ["error", "never"],
curly: "error",
"no-lonely-if": "error",
"no-unneeded-ternary": "error",
"one-var-declaration-per-line": "error",
quotes: [
"error",
"single",
{
allowTemplateLiterals: false,
avoidEscape: true,
},
],
// ES6
"array-callback-return": "off",
"prefer-const": "error",
// Imports
"import/prefer-default-export": "off",
"sort-imports": [
"error",
{
ignoreCase: true,
ignoreDeclarationSort: true,
},
],
"no-unused-expressions": "off",
"no-prototype-builtins": "off",
// REACT
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
"jsx-a11y/href-no-hash": [0],
"react/display-name": 0,
"react/no-deprecated": "error",
"react/no-unsafe": [
"error",
{
checkAliases: true,
},
],
"react/jsx-sort-props": [
"error",
{
ignoreCase: true,
},
],
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": 0,
// Prettier
// eslint looks for the prettier config at the top level of the package/app
// but the config lives in the `config/` directory. Passing the config here
// to get around this.
"prettier/prettier": ["error", prettierConfig],
},
settings: {
react: {
version: "detect",
},
},
};

View File

@@ -1,15 +0,0 @@
android-chrome-192x192.png,1674667592000,0875127d8f3725504a67bbebdfa564444dacf191b86845454918a9b05ed8b3a1
android-chrome-256x256.png,1674667592000,01a8a8ec16e650134b655a25f7a94b9ca497ba92879c9466990387c22ebbcaa9
apple-touch-icon.png,1674667592000,881fadedab15fe1a7c1e4174cf3ed027af6b0108a3cd4f95a7c096b7e94a5766
browserconfig.xml,1674667592000,9ce44d5f41efc3b07118b339128eeb2380985c8c72b55e77d86e286137be3b4f
favicon-16x16.png,1674667592000,45b499326c559c0a4a81367cc1d5db952a8ac90a31dea35c8f8fa6a324e4f893
favicon-32x32.png,1674667592000,9c9ea32acf0310554cc723ebd9b7fa69e66c29790961698ee7af9d211af3cb0a
favicon.ico,1674667593000,824a2c204e12d0a5d873da41724fe6e8338efab3a4d5d4fb430f92070eac930e
firebase-messaging-sw.js,1677588733520,c2c4a0d1bc2d0816016729907e8cbd52b30cbd87f944383cffae4142564ba6f7
index.html,1677696301408,fc305676e6e69d5f514b023d8a014a1b91ccc76eb8baae43b75812063ce2f39d
logo.png,1676993163860,0875127d8f3725504a67bbebdfa564444dacf191b86845454918a9b05ed8b3a1
mstile-150x150.png,1674667593000,5fd3f47afb80328e82891432c714fddf5422dae49e1bf85fa4b2e57afcd8ad21
safari-pinned-tab.svg,1674667593000,1e1c8219be1c655a9b75d68093070c7ebd0f45892f04641bece262985b8ca088
site.webmanifest,1674667593000,2021d6fe8b196a9cd9e8cc746c38ace194567ec0f58463df25de88a1b2c5cf70
theme.js,1677676883452,2412441b4ed458d32b6545b812f9cdfc39f86fa8d4290b7288c558b8c9f84e2b
img/logo.jpg,1674673878003,62fa6bb035376283e64deeaa8096ff63aabd421daeeb9b0c1cce53396a2d2a80

View File

@@ -1,5 +0,0 @@
{
"projects": {
"default": "radio-otherway"
}
}

View File

@@ -1,20 +0,0 @@
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools
name: Deploy to Firebase Hosting on merge
'on':
push:
branches:
- develop
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm ci && npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_RADIO_OTHERWAY }}'
channelId: live
projectId: radio-otherway

View File

@@ -1,17 +0,0 @@
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools
name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
build_and_preview:
if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm ci && npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_RADIO_OTHERWAY }}'
projectId: radio-otherway

View File

@@ -1,14 +0,0 @@
name:
Cache Events
on:
# Every 5 minutes
schedule:
- cron: "*/5 * * * *"
jobs:
cache:
runs-on: ubuntu-latest
steps:
- name: Cache outstanding events
uses: wei/curl@master
with:
args: https://otherway.fergl.ie/api/cron/cache

View File

@@ -1,14 +0,0 @@
name:
Send Reminders
on:
# Every 5 minutes
schedule:
- cron: "*/5 * * * *"
jobs:
reminders:
runs-on: ubuntu-latest
steps:
- name: Cache outstanding events
uses: wei/curl@master
with:
args: https://otherway.fergl.ie/api/cron/reminders

44
.gitignore vendored
View File

@@ -1,42 +1,2 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. .krud
working
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
.env*.development
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
.private/
radio-otherway-service-account.json
serviceAccount.json
/.env.production

13
.hintrc
View File

@@ -1,13 +0,0 @@
{
"extends": [
"development"
],
"hints": {
"axe/parsing": [
"default",
{
"duplicate-id-aria": "off"
}
]
}
}

5
.idea/.gitignore generated vendored
View File

@@ -1,5 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@@ -1,59 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_KEEP_WHITESPACES_INSIDE" value="" />
<option name="HTML_ENFORCE_QUOTES" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="PROJECT" libraries="{theme-change}" />
</component>
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/web.iml" filepath="$PROJECT_DIR$/.idea/web.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

16
.idea/web.iml generated
View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
<excludeFolder url="file://$MODULE_DIR$/.yarn" />
<excludeFolder url="file://$MODULE_DIR$/.private" />
<excludeFolder url="file://$MODULE_DIR$/.vercel" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="theme-change" level="application" />
</component>
</module>

View File

@@ -1,4 +0,0 @@
{
"tabWidth": 2,
"useTabs": false
}

18
.vscode/launch.json vendored
View File

@@ -1,18 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/server.js",
"outFiles": ["${workspaceFolder}/**/*.js"],
"outputCapture": "std",
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
]
}
]
}

17
.vscode/settings.json vendored
View File

@@ -1,17 +0,0 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
".vscode": true,
".next": true,
".vercel": true,
".working": true,
".yarn": true
},
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
yarn-path ".yarn/releases/yarn-1.22.19.cjs"

View File

@@ -1 +0,0 @@
yarnPath: .yarn/releases/yarn-1.22.19.cjs

201
LICENSE
View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,38 +0,0 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@@ -1,20 +0,0 @@
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}

View File

@@ -1,4 +0,0 @@
{
"indexes": [],
"fieldOverrides": []
}

View File

@@ -1,8 +0,0 @@
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}

View File

@@ -1,11 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
},
images: {
domains: ["firebasestorage.googleapis.com"],
},
};
module.exports = nextConfig;

View File

@@ -1,72 +0,0 @@
{
"name": "radio-otherway",
"version": "0.1.1",
"private": true,
"scripts": {
"turbo": "next dev --turbo",
"dev": "node ./server.js",
"debug": "node ./server.js",
"dev-nossl": "next dev",
"build": "next build && next export",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@babel/plugin-transform-react-display-name": "^7.18.6",
"@headlessui/react": "^1.7.11",
"@next/font": "13.2.0",
"@prisma/client": "^4.9.0",
"@types/feather-icons": "^4.29.1",
"@types/node": "18.14.2",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@upstash/qstash": "^0.3.6",
"axios": "^1.3.4",
"babel": "^6.23.0",
"classnames": "^2.3.2",
"daisyui": "^2.49.0",
"encoding": "^0.1.13",
"eslint": "8.35.0",
"eslint-config-next": "13.2.3",
"eslint-plugin-react-hooks": "^4.6.0",
"feather-icons": "^4.29.0",
"firebase": "^9.17.1",
"firebase-admin": "^11.5.0",
"firebase-functions": "^4.2.1",
"fireschema": "^4.0.4",
"http-status-codes": "^2.2.0",
"localforage": "^1.10.0",
"logrocket": "^3.0.1",
"next": "13.2.3",
"next-logger": "^3.0.1",
"next-seo": "^5.15.0",
"path-browserify": "^1.0.1",
"react": "18.2.0",
"react-daisyui": "^3.0.3",
"react-device-detect": "^2.2.3",
"react-dom": "18.2.0",
"react-dropzone": "^14.2.3",
"react-feather": "^2.0.10",
"react-hook-form": "^7.43.2",
"react-hot-toast": "^2.4.0",
"react-icons": "^4.7.1",
"react-phone-number-input": "^3.2.19",
"theme-change": "^2.3.0",
"twilio": "^4.8.0",
"typescript": "4.9.5",
"zod": "^3.20.6"
},
"packageManager": "yarn@1.22.19",
"devDependencies": {
"@google-cloud/local-auth": "2.1.1",
"@hookform/devtools": "^4.3.0",
"autoprefixer": "^10.4.13",
"eslint-config-prettier": "^8.6.0",
"googleapis": "111.0.0",
"postcss": "^8.4.21",
"prettier": "^2.8.4",
"prettier-plugin-tailwindcss": "^0.2.2",
"prisma": "^4.9.0",
"tailwindcss": "^3.2.4"
}
}

View File

@@ -1,7 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {
},
autoprefixer: {},
},
}

View File

@@ -1,66 +0,0 @@
-- CreateTable
CREATE TABLE "Account" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL,
"sessionToken" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"name" TEXT,
"email" TEXT,
"emailVerified" TIMESTAMP(3),
"image" TEXT,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
-- AddForeignKey
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -1,3 +0,0 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View File

@@ -1,56 +0,0 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,13 +0,0 @@
importScripts("https://www.gstatic.com/firebasejs/7.9.1/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/7.9.1/firebase-messaging.js");
firebase.initializeApp({
apiKey: "AIzaSyDtk_Ym-AZroXsHvQVcdHXYyc_TvgycAWw",
authDomain: "radio-otherway.firebaseapp.com",
projectId: "radio-otherway",
storageBucket: "radio-otherway.appspot.com",
messagingSenderId: "47147490249",
appId: "1:47147490249:web:a84515b3ce1c481826e618"
});
const messaging = firebase.messaging();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -1,89 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Welcome to Firebase Hosting</title>
<!-- update the version number as needed -->
<script defer src="/__/firebase/9.17.1/firebase-app-compat.js"></script>
<!-- include only the Firebase features as you need -->
<script defer src="/__/firebase/9.17.1/firebase-auth-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-database-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-firestore-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-functions-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-messaging-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-storage-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-analytics-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-remote-config-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-performance-compat.js"></script>
<!--
initialize the SDK after all desired features are loaded, set useEmulator to false
to avoid connecting the SDK to running emulators.
-->
<script defer src="/__/firebase/init.js?useEmulator=true"></script>
<style media="screen">
body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
#message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px; border-radius: 3px; }
#message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }
#message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
#message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }
#message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }
#message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
#load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }
@media (max-width: 600px) {
body, #message { margin-top: 0; background: white; box-shadow: none; }
body { border-top: 16px solid #ffa100; }
}
</style>
</head>
<body>
<div id="message">
<h2>Welcome</h2>
<h1>Firebase Hosting Setup Complete</h1>
<p>You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!</p>
<a target="_blank" href="https://firebase.google.com/docs/hosting/">Open Hosting Documentation</a>
</div>
<p id="load">Firebase SDK Loading&hellip;</p>
<script>
document.addEventListener('DOMContentLoaded', function() {
const loadEl = document.querySelector('#load');
// // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
// // The Firebase SDK is initialized and available here!
//
// firebase.auth().onAuthStateChanged(user => { });
// firebase.database().ref('/path/to/ref').on('value', snapshot => { });
// firebase.firestore().doc('/foo/bar').get().then(() => { });
// firebase.functions().httpsCallable('yourFunction')().then(() => { });
// firebase.messaging().requestPermission().then(() => { });
// firebase.storage().ref('/path/to/ref').getDownloadURL().then(() => { });
// firebase.analytics(); // call to activate
// firebase.analytics().logEvent('tutorial_completed');
// firebase.performance(); // call to activate
//
// // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
try {
let app = firebase.app();
let features = [
'auth',
'database',
'firestore',
'functions',
'messaging',
'storage',
'analytics',
'remoteConfig',
'performance',
].filter(feature => typeof app[feature] === 'function');
loadEl.textContent = `Firebase SDK loaded with ${features.join(', ')}`;
} catch (e) {
console.error(e);
loadEl.textContent = 'Error loading the Firebase SDK, check the console.';
}
});
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -1,79 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="320.000000pt" height="320.000000pt" viewBox="0 0 320.000000 320.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,320.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M0 1600 l0 -1600 1600 0 1600 0 0 1600 0 1600 -1600 0 -1600 0 0
-1600z m2794 952 c4 -7 -103 -197 -363 -641 l-60 -104 19 -27 c29 -41 41 -106
29 -161 -18 -84 -101 -143 -200 -142 -37 0 -40 -2 -80 -74 -17 -30 -18 -37 -6
-47 7 -6 29 -57 47 -113 19 -56 36 -100 38 -98 3 2 16 50 31 107 l27 103 38 3
c62 5 67 0 52 -50 -8 -24 -21 -65 -29 -93 -8 -27 -27 -89 -42 -137 l-27 -88
-48 0 -47 0 -33 100 c-19 55 -36 97 -39 94 -3 -3 -19 -46 -36 -97 l-30 -92
-45 -3 c-33 -3 -47 1 -57 14 -7 10 -14 16 -16 13 -4 -4 -67 -112 -106 -182
-119 -210 -193 -337 -200 -341 -4 -3 -32 39 -63 92 -30 53 -95 166 -143 250
-74 130 -90 152 -111 152 l-24 0 0 185 0 185 100 0 100 0 0 -45 0 -45 -50 0
c-47 0 -50 -2 -50 -25 0 -23 3 -25 50 -25 l50 0 0 -45 0 -45 -50 0 c-47 0 -50
-2 -50 -25 0 -23 3 -25 50 -25 l50 0 0 -45 0 -45 -60 0 c-33 0 -60 -3 -60 -6
0 -15 255 -442 261 -438 4 2 71 114 149 249 79 135 147 245 152 245 9 0 4 28
-17 95 -12 37 -51 168 -62 208 -4 16 1 18 48 15 l53 -3 13 -55 c8 -30 20 -80
26 -110 7 -30 16 -50 20 -44 3 6 20 53 36 104 17 51 40 102 51 114 12 11 36
44 55 73 l33 52 -28 6 c-32 7 -84 59 -99 98 -17 45 -13 117 10 164 43 90 169
129 261 82 23 -12 44 -18 45 -13 2 5 62 110 134 234 72 124 147 254 167 290
20 36 51 90 69 120 l33 55 -1147 3 c-630 1 -1148 1 -1150 -1 -2 -3 230 -413
351 -619 15 -27 24 -49 19 -50 -4 0 27 -4 69 -8 91 -7 128 -29 142 -82 14 -56
-11 -115 -55 -130 -10 -3 1 -24 36 -71 28 -37 54 -65 59 -64 7 2 92 214 116
290 15 47 29 57 80 57 l48 0 28 -77 c15 -43 31 -82 36 -88 4 -5 8 -15 8 -23 0
-8 6 -28 14 -45 24 -53 46 -109 46 -118 0 -5 -22 -9 -49 -9 -45 0 -50 2 -61
30 -13 30 -14 31 -74 28 -55 -3 -62 -6 -74 -30 -12 -26 -17 -28 -78 -28 -35 0
-64 -3 -64 -6 0 -4 16 -33 35 -65 30 -49 41 -58 68 -61 l32 -3 0 -180 0 -180
-47 -3 -48 -3 0 75 0 76 -65 0 -65 0 0 -75 0 -75 -50 0 -50 0 2 183 3 182 48
3 47 3 0 -70 0 -71 65 0 65 0 0 74 c0 68 -3 81 -35 135 -33 56 -39 61 -70 61
-30 0 -37 6 -67 55 -18 30 -36 55 -40 55 -5 0 -8 -25 -8 -55 l0 -55 -47 0 -48
0 2 177 c1 150 4 178 17 183 12 4 -20 66 -145 280 -234 401 -243 418 -222 423
31 8 2352 7 2357 -1z m-1836 -282 c4 0 34 27 67 60 32 33 60 58 62 56 5 -5 9
-256 5 -256 -2 0 -35 30 -73 67 l-69 66 0 -69 0 -69 -67 67 -68 68 65 65 65
66 3 -61 c2 -33 6 -60 10 -60z m387 60 c33 -33 62 -60 66 -60 3 0 4 24 1 54
-2 29 0 56 5 59 5 3 38 -23 73 -58 l64 -65 -64 -65 c-35 -36 -68 -65 -73 -65
-5 0 -8 26 -7 58 1 31 1 61 1 65 -1 4 -31 -22 -67 -58 -36 -36 -67 -65 -70
-65 -2 0 -4 59 -4 130 0 72 4 130 8 130 4 0 34 -27 67 -60z m455 -70 c0 -71
-2 -130 -4 -130 -9 0 -121 122 -121 132 0 10 106 125 118 127 4 0 7 -57 7
-129z m140 -2 l0 -133 -67 67 -68 68 65 65 c35 36 66 65 67 65 2 0 3 -60 3
-132z m310 4 c0 -17 -115 -134 -124 -125 -8 8 -8 238 0 246 9 9 124 -103 124
-121z m87 -69 l-67 -68 0 135 0 135 67 -68 68 -67 -68 -67z m-605 -372 c111
-76 114 -223 7 -300 -40 -30 -46 -31 -141 -31 l-98 0 0 181 0 181 99 -4 c83
-3 104 -7 133 -27z m238 -151 l0 -180 -50 0 -50 0 0 173 c0 96 3 177 7 180 3
4 26 7 50 7 l43 0 0 -180z m-1498 -333 c21 -11 50 -38 65 -60 36 -52 38 -145
4 -200 -35 -57 -101 -91 -175 -91 -34 0 -72 6 -86 13 -47 25 -89 74 -106 123
-14 43 -14 53 0 101 14 48 35 75 89 117 43 32 147 31 209 -3z m358 -22 c0 -45
0 -45 -35 -45 l-35 0 0 -140 0 -140 -50 0 -50 0 0 140 0 140 -35 0 c-35 0 -35
0 -35 45 l0 45 120 0 120 0 0 -45z m941 8 c24 -24 32 -40 33 -71 1 -48 -18
-86 -50 -101 l-24 -11 55 -71 c30 -39 55 -73 55 -75 0 -2 -27 -4 -59 -4 l-60
0 -38 62 -38 62 -5 -60 -5 -59 -47 -3 -48 -3 0 187 0 186 100 -4 c97 -4 102
-5 131 -35z m895 -141 c35 -96 64 -178 64 -183 0 -13 -84 -11 -98 3 -7 7 -12
20 -12 29 0 22 -20 29 -84 29 -51 0 -53 -1 -62 -32 -9 -31 -12 -33 -62 -36
-29 -2 -52 -1 -52 2 0 8 60 169 75 203 8 17 15 35 15 41 0 5 11 35 25 66 l25
57 51 -3 51 -3 64 -173z m171 133 c15 -25 33 -45 38 -45 6 0 22 20 37 45 27
45 28 45 83 45 30 0 55 -3 55 -6 0 -3 -29 -47 -65 -99 l-65 -93 0 -86 0 -87
-47 3 -48 3 -5 95 -5 95 -58 82 c-31 44 -57 84 -57 87 0 3 24 6 54 6 53 0 55
-1 83 -45z"/>
<path d="M880 1730 c0 -39 2 -41 26 -38 57 7 57 69 0 76 -24 3 -26 1 -26 -38z"/>
<path d="M2155 1728 c-23 -27 -26 -39 -22 -72 17 -121 187 -108 187 14 0 56
-36 90 -96 90 -35 0 -47 -5 -69 -32z"/>
<path d="M1257 1679 c-20 -50 -16 -59 25 -59 27 0 30 3 24 25 -8 33 -25 75
-29 75 -2 0 -11 -19 -20 -41z"/>
<path d="M1600 1668 l0 -98 29 0 c34 0 81 40 82 72 4 76 -21 113 -77 120 l-34
4 0 -98z"/>
<path d="M324 1260 c-59 -24 -72 -104 -25 -151 39 -38 87 -39 130 -3 26 21 31
33 31 69 0 36 -5 48 -31 69 -31 26 -66 32 -105 16z"/>
<path d="M1640 1239 c0 -38 1 -40 28 -37 47 5 39 78 -9 78 -16 0 -19 -8 -19
-41z"/>
<path d="M2531 1193 c-8 -21 -16 -44 -18 -50 -4 -9 6 -13 32 -13 34 0 37 2 31
25 -8 31 -25 75 -29 75 -2 0 -9 -17 -16 -37z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -1,19 +0,0 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@@ -1,4 +0,0 @@
(function initTheme() {
var theme = localStorage.getItem("theme") || "synthwave";
document.querySelector("html").setAttribute("data-theme", theme);
})();

View File

@@ -1,23 +0,0 @@
const https = require("https");
const fs = require("fs");
const next = require("next");
const port = 3000;
const dev = process.env.NODE_ENV !== "production";
const hostname = "otherway.dev.fergl.ie";
const app = next({ dev, hostname, port, dir: __dirname });
const handle = app.getRequestHandler();
const options = {
key: fs.readFileSync("/etc/letsencrypt/live/dev.fergl.ie/privkey.pem"),
cert: fs.readFileSync("/etc/letsencrypt/live/dev.fergl.ie/fullchain.pem"),
};
app.prepare().then(() => {
https
.createServer(options, (req, res) => handle(req, res))
.listen(port, (err) => {
if (err) throw err;
console.log(`> Ready on localhost:${port}`);
});
});

View File

@@ -1,13 +0,0 @@
import React from "react";
const RootLayout = ({ children }: React.PropsWithChildren) => {
return (
<div className="flex flex-wrap w-full justify-evenly">
<div className="max-w-lg p-10 mt-6 rounded-md shadow-md font-body bg-base-100 text-base-content md:flex-1">
{children}
</div>
</div>
);
};
export default RootLayout;

View File

@@ -1,8 +0,0 @@
import { LoginPage } from "@/components/pages/auth";
import React from "react";
const Login = async () => {
return <LoginPage />;
};
export default Login;

View File

@@ -1,8 +0,0 @@
import React from "react";
import { SignupPage } from "@/components/pages/auth";
const Signup = async () => {
return <SignupPage />;
};
export default Signup;

View File

@@ -1,7 +0,0 @@
import PrivacyComponent from "@/components/boilerplate/PrivacyComponent";
const PrivacyPage = () => {
return <PrivacyComponent/>
}
export default PrivacyPage;

View File

@@ -1,7 +0,0 @@
import TosComponent from "@/components/boilerplate/TosComponent";
const TosPage = () => {
return <TosComponent />;
};
export default TosPage;

View File

@@ -1,21 +0,0 @@
"use client";
import logger from "@/lib/util/logging";
import React from "react";
const ErrorPage = ({ error, reset }: { error: Error; reset: () => void }) => {
React.useEffect(() => {
// Log the error to an error reporting service
console.error(error);
logger.error(error);
}, [error]);
return (
<div>
<div>An error occurred: {error.message}</div>
<button className="btn-danger btn" onClick={() => reset()}>
Retry?
</button>
</div>
);
};
export default ErrorPage;

View File

@@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,38 +0,0 @@
import React from "react";
import Script from "next/script";
export default function Head() {
return (
<>
<title>Radio::Otherway</title>
<meta content="width=device-width, initial-scale=1" name="viewport" />
<meta
name="description"
content="Irish based radio station , broadcasting a varied selection of shows with some of the best Irish DJ talent"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
{/*need to include this here to avoid the FLUC on initial load*/}
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
</>
);
}

View File

@@ -1,51 +0,0 @@
/* eslint-disable @next/next/no-before-interactive-script-outside-document */
"use client";
import React from "react";
import "./globals.css";
import { Raleway } from "@next/font/google";
import { NavBar, PushNotificationWrapper } from "@/components/layout";
import { AuthUserProvider } from "@/lib/auth/authUserContext";
import { themeChange } from "theme-change";
import Script from "next/script";
import { Toaster } from "react-hot-toast";
import useLogRocket from "@/lib/util/logging/logRocket";
import logger from "@/lib/util/logging";
import FirestoreProvider from "@/components/providers/FirebaseProvider";
// only initialize when in the browser
const font = Raleway({
weight: ["400", "700"],
subsets: ["latin"],
variable: "--font-raleway",
});
const RootLayout = ({ children }: React.PropsWithChildren) => {
React.useEffect(() => {
logger.info("Bootstrapping application");
themeChange(false);
}, [logger]);
return (
<html lang="en">
<head>
<Script src="/theme.js" />
</head>
<body className={`${font.className}`}>
<Toaster />
<FirestoreProvider>
<AuthUserProvider>
<PushNotificationWrapper>
<div className="flex flex-col min-h-screen bg-base-100">
<NavBar />
<div className="items-end grow place-items-center bg-base-200 text-base-content">
<main className=" text-base-content">{children}</main>
</div>
</div>
</PushNotificationWrapper>
</AuthUserProvider>
</FirestoreProvider>
</body>
</html>
);
};
export default RootLayout;

View File

@@ -1,53 +0,0 @@
import React from "react";
import { useState } from "react";
const LogRocket = require("logrocket");
import packageJson from "../../package.json";
const Loading = () => {
if (process.env.LOGROCKET_ID && window !== undefined) {
LogRocket.init(process.env.LOGROCKET_ID, {
release: packageJson.version,
rootHostname: "radio-otherway.fergl.ie",
console: {
shouldAggregateConsoleErrors: true,
},
network: {
requestSanitizer: (request: any) => {
// if the url contains token 'ignore' it
if (request.url.toLowerCase().indexOf("token") !== -1) {
// ignore the request response pair
return null;
}
// remove Authorization header from logrocket
request.headers.Authorization = undefined;
// otherwise log the request normally
return request;
},
},
});
}
return (
<div role="status">
<svg
aria-hidden="true"
className="inline w-8 h-8 mr-2 text-gray-200 animate-spin fill-green-500 dark:text-gray-600"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span className="sr-only">Loading...</span>
</div>
);
};
export default Loading;

View File

@@ -1,21 +0,0 @@
import React from "react";
import HomePageComponent from "@/components/pages/home";
export const metadata = {
title: "Radio Otherway",
};
const getData = async () => {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/shows/upcoming`,
{ cache: "no-store" }
);
return await res.json();
};
const Home = async () => {
const results = await getData();
return <HomePageComponent shows={results} />;
};
export default Home;

View File

@@ -1,7 +0,0 @@
import { ProfilePageComponent } from "@/components/pages/profile";
const ProfilePage = () => {
return <ProfilePageComponent />;
};
export default ProfilePage;

View File

@@ -1,143 +0,0 @@
body {
background: #fff;
color: #000;
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 28px;
margin: 24px;
}
h1,
h2,
h3,
h4,
h5,
h6,
li,
p {
margin: 0 0 16px
}
h1 {
font-size: 40px;
line-height: 60px
}
h1,
h2 {
font-weight: 700
}
h2 {
font-size: 32px;
line-height: 48px
}
h3 {
font-size: 24px;
line-height: 36px
}
h3,
h4 {
font-weight: 700
}
h4 {
font-size: 20px;
line-height: 30px
}
h5,
h6 {
font-size: 16px;
line-height: 24px;
font-weight: 700
}
a {
text-decoration: none;
cursor: pointer;
color: #000
}
a:hover,
a[rel~=nofollow] {
text-decoration: underline
}
a[rel~=nofollow] {
color: #553df4
}
a[rel~=nofollow]:hover {
text-decoration: none
}
.visible {
display: block
}
.hidden {
display: none
}
.page {
width: 100%
}
.container {
position: relative;
width: 90%;
max-width: 1024px;
margin: 0 auto
}
.header {
padding: 16px 0
}
.header .title {
font-size: 28px;
line-height: 42px;
font-weight: 700
}
.translations-list-container {
padding-bottom: 8px;
margin: 0 0 16px
}
.translations-list-container .translations-list {
margin: 0;
padding: 0;
list-style: none
}
.translations-list-container .translations-list .translations-list-item {
display: inline-block;
padding: 0;
margin: 0 8px 8px 0;
font-weight: 700;
color: #553df4
}
.translations-list-container .translations-list .translations-list-item a {
display: inline-block;
color: #553df4;
border: 1px solid #553df4;
border-radius: 32px;
padding: 4px 16px
}
.translations-content-container {
padding-top: 16px;
border-top: 1px solid #eee;
word-break: break-all
}
.footer {
border-top: 1px solid #eee;
margin: 32px 0 0;
padding: 16px 0
}

View File

@@ -1,300 +0,0 @@
import './Boilerplate.css'
const PrivacyComponent = () => {
return (<div className="tab-content translations-content-item en visible m-5" id="en">
<h1>Privacy Policy</h1>
<p>Last updated: February 22, 2023</p>
<p>This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your
information when You use the Service and tells You about Your privacy rights and how the law protects You.</p>
<p>We use Your Personal data to provide and improve the Service. By using the Service, You agree to the collection
and use of information in accordance with this Privacy Policy. This Privacy Policy has been created with the
help of the <a href="https://www.freeprivacypolicy.com/free-privacy-policy-generator/" target="_blank"
rel="noreferrer">Free
Privacy Policy Generator</a>.</p>
<h1>Interpretation and Definitions</h1>
<h2>Interpretation</h2>m
<p>The words of which the initial letter is capitalized have eanings defined under the following conditions. The
following definitions shall have the same meaning regardless of whether they appear in singular or in
plural.</p>
<h2>Definitions</h2>
<p>For the purposes of this Privacy Policy:</p>
<ul>
<li>
<p><strong>Account</strong> means a unique account created for You to access our Service or parts of our
Service.</p>
</li>
<li>
<p><strong>Affiliate</strong> means an entity that controls, is controlled by or is under common control with
a party, where &quot;control&quot; means ownership of 50% or more of the shares, equity interest or other
securities entitled to vote for election of directors or other managing authority.</p>
</li>
<li>
<p><strong>Company</strong> (referred to as either &quot;the
Company&quot;, &quot;We&quot;, &quot;Us&quot; or &quot;Our&quot; in this Agreement) refers to Radio
Otherway.</p>
</li>
<li>
<p><strong>Cookies</strong> are small files that are placed on Your computer, mobile device or any other
device by a website, containing the details of Your browsing history on that website among its many uses.
</p>
</li>
<li>
<p><strong>Country</strong> refers to: Ireland</p>
</li>
<li>
<p><strong>Device</strong> means any device that can access the Service such as a computer, a cellphone or a
digital tablet.</p>
</li>
<li>
<p><strong>Personal Data</strong> is any information that relates to an identified or identifiable individual.
</p>
</li>
<li>
<p><strong>Service</strong> refers to the Website.</p>
</li>
<li>
<p><strong>Service Provider</strong> means any natural or legal person who processes the data on behalf of the
Company. It refers to third-party companies or individuals employed by the Company to facilitate the
Service, to provide the Service on behalf of the Company, to perform services related to the Service or to
assist the Company in analyzing how the Service is used.</p>
</li>
<li>
<p><strong>Usage Data</strong> refers to data collected automatically, either generated by the use of the
Service or from the Service infrastructure itself (for example, the duration of a page visit).</p>
</li>
<li>
<p><strong>Website</strong> refers to Radio Otherway, accessible from <a
href="https://otherway.dev.fergl.ie:3000/privacy" rel="external nofollow noopener noreferrer"
target="_blank">https://otherway.dev.fergl.ie:3000/privacy</a></p>
</li>
<li>
<p><strong>You</strong> means the individual accessing or using the Service, or the company, or other legal
entity on behalf of which such individual is accessing or using the Service, as applicable.</p>
</li>
</ul>
<h1>Collecting and Using Your Personal Data</h1>
<h2>Types of Data Collected</h2>
<h3>Personal Data</h3>
<p>While using Our Service, We may ask You to provide Us with certain personally identifiable information that can
be used to contact or identify You. Personally identifiable information may include, but is not limited to:</p>
<ul>
<li>
<p>Email address</p>
</li>
<li>
<p>First name and last name</p>
</li>
<li>
<p>Usage Data</p>
</li>
</ul>
<h3>Usage Data</h3>
<p>Usage Data is collected automatically when using the Service.</p>
<p>Usage Data may include information such as Your Device&quot;s Internet Protocol address (e.g. IP address),
browser
type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent
on those pages, unique device identifiers and other diagnostic data.</p>
<p>When You access the Service by or through a mobile device, We may collect certain information automatically,
including, but not limited to, the type of mobile device You use, Your mobile device unique ID, the IP address
of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device
identifiers and other diagnostic data.</p>
<p>We may also collect information that Your browser sends whenever You visit our Service or when You access the
Service by or through a mobile device.</p>
<h3>Tracking Technologies and Cookies</h3>
<p>We use Cookies and similar tracking technologies to track the activity on Our Service and store certain
information. Tracking technologies used are beacons, tags, and scripts to collect and track information and to
improve and analyze Our Service. The technologies We use may include:</p>
<ul>
<li><strong>Cookies or Browser Cookies.</strong> A cookie is a small file placed on Your Device. You can
instruct Your browser to refuse all Cookies or to indicate when a Cookie is being sent. However, if You do not
accept Cookies, You may not be able to use some parts of our Service. Unless you have adjusted Your browser
setting so that it will refuse Cookies, our Service may use Cookies.
</li>
<li><strong>Web Beacons.</strong> Certain sections of our Service and our emails may contain small electronic
files known as web beacons (also referred to as clear gifs, pixel tags, and single-pixel gifs) that permit the
Company, for example, to count users who have visited those pages or opened an email and for other related
website statistics (for example, recording the popularity of a certain section and verifying system and server
integrity).
</li>
</ul>
<p>Cookies can be &quot;Persistent&quot; or &quot;Session&quot; Cookies. Persistent Cookies remain on Your
personal computer or mobile device when You go offline, while Session Cookies are deleted as soon as You close
Your web browser. Learn more about cookies on the <a
href="https://www.freeprivacypolicy.com/blog/sample-privacy-policy-template/#Use_Of_Cookies_And_Tracking"
target="_blank" rel="noreferrer">Free Privacy Policy website</a> article.</p>
<p>We use both Session and Persistent Cookies for the purposes set out below:</p>
<ul>
<li>
<p><strong>Necessary / Essential Cookies</strong></p>
<p>Type: Session Cookies</p>
<p>Administered by: Us</p>
<p>Purpose: These Cookies are essential to provide You with services available through the Website and to
enable You to use some of its features. They help to authenticate users and prevent fraudulent use of user
accounts. Without these Cookies, the services that You have asked for cannot be provided, and We only use
these Cookies to provide You with those services.</p>
</li>
<li>
<p><strong>Cookies Policy / Notice Acceptance Cookies</strong></p>
<p>Type: Persistent Cookies</p>
<p>Administered by: Us</p>
<p>Purpose: These Cookies identify if users have accepted the use of cookies on the Website.</p>
</li>
<li>
<p><strong>Functionality Cookies</strong></p>
<p>Type: Persistent Cookies</p>
<p>Administered by: Us</p>
<p>Purpose: These Cookies allow us to remember choices You make when You use the Website, such as remembering
your login details or language preference. The purpose of these Cookies is to provide You with a more
personal experience and to avoid You having to re-enter your preferences every time You use the Website.</p>
</li>
</ul>
<p>For more information about the cookies we use and your choices regarding cookies, please visit our Cookies
Policy or the Cookies section of our Privacy Policy.</p>
<h2>Use of Your Personal Data</h2>
<p>The Company may use Personal Data for the following purposes:</p>
<ul>
<li>
<p><strong>To provide and maintain our Service</strong>, including to monitor the usage of our Service.</p>
</li>
<li>
<p><strong>To manage Your Account:</strong> to manage Your registration as a user of the Service. The Personal
Data You provide can give You access to different functionalities of the Service that are available to You
as a registered user.</p>
</li>
<li>
<p><strong>For the performance of a contract:</strong> the development, compliance and undertaking of the
purchase contract for the products, items or services You have purchased or of any other contract with Us
through the Service.</p>
</li>
<li>
<p><strong>To contact You:</strong> To contact You by email, telephone calls, SMS, or other equivalent forms
of electronic communication, such as a mobile application&quot;s push notifications regarding updates or
informative communications related to the functionalities, products or contracted services, including the
security updates, when necessary or reasonable for their implementation.</p>
</li>
<li>
<p><strong>To provide You</strong> with news, special offers and general information about other goods,
services and events which we offer that are similar to those that you have already purchased or enquired
about unless You have opted not to receive such information.</p>
</li>
<li>
<p><strong>To manage Your requests:</strong> To attend and manage Your requests to Us.</p>
</li>
<li>
<p><strong>For business transfers:</strong> We may use Your information to evaluate or conduct a merger,
divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Our
assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which
Personal Data held by Us about our Service users is among the assets transferred.</p>
</li>
<li>
<p><strong>For other purposes</strong>: We may use Your information for other purposes, such as data analysis,
identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and
improve our Service, products, services, marketing and your experience.</p>
</li>
</ul>
<p>We may share Your personal information in the following situations:</p>
<ul>
<li><strong>With Service Providers:</strong> We may share Your personal information with Service Providers to
monitor and analyze the use of our Service, to contact You.
</li>
<li><strong>For business transfers:</strong> We may share or transfer Your personal information in connection
with, or during negotiations of, any merger, sale of Company assets, financing, or acquisition of all or a
portion of Our business to another company.
</li>
<li><strong>With Affiliates:</strong> We may share Your information with Our affiliates, in which case we will
require those affiliates to honor this Privacy Policy. Affiliates include Our parent company and any other
subsidiaries, joint venture partners or other companies that We control or that are under common control with
Us.
</li>
<li><strong>With business partners:</strong> We may share Your information with Our business partners to offer
You certain products, services or promotions.
</li>
<li><strong>With other users:</strong> when You share personal information or otherwise interact in the public
areas with other users, such information may be viewed by all users and may be publicly distributed outside.
</li>
<li><strong>With Your consent</strong>: We may disclose Your personal information for any other purpose with
Your consent.
</li>
</ul>
<h2>Retention of Your Personal Data</h2>
<p>The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this
Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal
obligations (for example, if we are required to retain your data to comply with applicable laws), resolve
disputes, and enforce our legal agreements and policies.</p>
<p>The Company will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a
shorter period of time, except when this data is used to strengthen the security or to improve the functionality
of Our Service, or We are legally obligated to retain this data for longer time periods.</p>
<h2>Transfer of Your Personal Data</h2>
<p>Your information, including Personal Data, is processed at the Company&quot;s operating offices and in any
other
places where the parties involved in the processing are located. It means that this information may be
transferred to and maintained on computers located outside of Your state, province, country or other
governmental jurisdiction where the data protection laws may differ than those from Your jurisdiction.</p>
<p>Your consent to this Privacy Policy followed by Your submission of such information represents Your agreement
to that transfer.</p>
<p>The Company will take all steps reasonably necessary to ensure that Your data is treated securely and in
accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or
a country unless there are adequate controls in place including the security of Your data and other personal
information.</p>
<h2>Delete Your Personal Data</h2>
<p>You have the right to delete or request that We assist in deleting the Personal Data that We have collected
about You.</p>
<p>Our Service may give You the ability to delete certain information about You from within the Service.</p>
<p>You may update, amend, or delete Your information at any time by signing in to Your Account, if you have one,
and visiting the account settings section that allows you to manage Your personal information. You may also
contact Us to request access to, correct, or delete any personal information that You have provided to Us.</p>
<p>Please note, however, that We may need to retain certain information when we have a legal obligation or lawful
basis to do so.</p>
<h2>Disclosure of Your Personal Data</h2>
<h3>Business Transactions</h3>
<p>If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We
will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy
Policy.</p>
<h3>Law enforcement</h3>
<p>Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by
law or in response to valid requests by public authorities (e.g. a court or a government agency).</p>
<h3>Other legal requirements</h3>
<p>The Company may disclose Your Personal Data in the good faith belief that such action is necessary to:</p>
<ul>
<li>Comply with a legal obligation</li>
<li>Protect and defend the rights or property of the Company</li>
<li>Prevent or investigate possible wrongdoing in connection with the Service</li>
<li>Protect the personal safety of Users of the Service or the public</li>
<li>Protect against legal liability</li>
</ul>
<h2>Security of Your Personal Data</h2>
<p>The security of Your Personal Data is important to Us, but remember that no method of transmission over the
Internet, or method of electronic storage is 100% secure. While We strive to use commercially acceptable means
to protect Your Personal Data, We cannot guarantee its absolute security.</p>
<h1>Children&quot;s Privacy</h1>
<p>Our Service does not address anyone under the age of 13. We do not knowingly collect personally identifiable
information from anyone under the age of 13. If You are a parent or guardian and You are aware that Your child
has provided Us with Personal Data, please contact Us. If We become aware that We have collected Personal Data
from anyone under the age of 13 without verification of parental consent, We take steps to remove that
information from Our servers.</p>
<p>If We need to rely on consent as a legal basis for processing Your information and Your country requires
consent from a parent, We may require Your parent&quot;s consent before We collect and use that information.</p>
<h1>Links to Other Websites</h1>
<p>Our Service may contain links to other websites that are not operated by Us. If You click on a third party
link, You will be directed to that third party&quot;s site. We strongly advise You to review the Privacy Policy
of
every site You visit.</p>
<p>We have no control over and assume no responsibility for the content, privacy policies or practices of any
third party sites or services.</p>
<h1>Changes to this Privacy Policy</h1>
<p>We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new
Privacy Policy on this page.</p>
<p>We will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective
and update the &quot;Last updated&quot; date at the top of this Privacy Policy.</p>
<p>You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are
effective when they are posted on this page.</p>
<h1>Contact Us</h1>
<p>If you have any questions about this Privacy Policy, You can contact us:</p>
<ul>
<li>By email: <a href="/cdn-cgi/l/email-protection" className="__cf_email__"
data-cfemail="5d322935382f2a3c241d2d32393332302e733e3230">[email&#160;protected]</a></li>
</ul>
</div>)
}
export default PrivacyComponent;

View File

@@ -1,77 +0,0 @@
const TosComponent = () => {
// @ts-ignore
return (
<>
<h1>Website Terms and Conditions of Use</h1>
<h2>1. Terms</h2>;
<p>By accessing this Website, accessible from https://otherway.fergl.ie, you are agreeing to be bound by these Website
Terms and Conditions of Use and agree that you are responsible for the agreement with any applicable local laws. If
you disagree with any of these terms, you are prohibited from accessing this site. The materials contained in this
Website are protected by copyright and trade mark law.</p>;
<h2>2. Use License</h2>;
<p>Permission is granted to temporarily download one copy of the materials on Radio Otherway&apos;s Website for personal,
non-commercial transitory viewing only. This is the grant of a license, not a transfer of title, and under this
license you may not:</p>;
<ul>
<li>modify or copy the materials;</li>
<li>use the materials for any commercial purpose or for any public display;</li>
<li>attempt to reverse engineer any software contained on Radio Otherway&apos;s Website;</li>
<li>remove any copyright or other proprietary notations from the materials; or</li>
<li>transferring the materials to another person or `&ldquo;mirror`&ldquo; the materials on any other server.</li>
</ul>;
<p>This will let Radio Otherway to terminate upon violations of any of these restrictions. Upon termination, your
viewing right will also be terminated and you should destroy any downloaded materials in your possession whether it
is printed or electronic format.
</p>;
<h2>3. Disclaimer</h2>;
<p>All the materials on Radio Otherways Website are provided `&ldquo;as is`&ldquo;. Radio Otherway makes no warranties, may it be
expressed or implied, therefore negates all other warranties. Furthermore, Radio Otherway does not make any
representations concerning the accuracy or reliability of the use of the materials on its Website or otherwise
relating to such materials or any sites linked to this Website.</p>;
<h2>4. Limitations</h2>;
<p>Radio Otherway or its suppliers will not be hold accountable for any damages that will arise with the use or
inability to use the materials on Radio Otherways Website, even if Radio Otherway or an authorize representative of
this Website has been notified, orally or written, of the possibility of such damage. Some jurisdiction does not
allow limitations on implied warranties or limitations of liability for incidental damages, these limitations may
not apply to you.</p>;
<h2>5. Revisions and Errata</h2>;
<p>The materials appearing on Radio Otherways Website may include technical, typographical, or photographic errors.
Radio Otherway will not promise that any of the materials in this Website are accurate, complete, or current. Radio
Otherway may change the materials contained on its Website at any time without notice. Radio Otherway does not make
any commitment to update the materials.</p>;
<h2>6. Links</h2>;
<p>Radio Otherway has not reviewed all of the sites linked to its Website and is not responsible for the contents of
any such linked site. The presence of any link does not imply endorsement by Radio Otherway of the site. The use of
any linked website is at the users own risk.</p>;
<h2>7. Site Terms of Use Modifications</h2>;
<p>Radio Otherway may revise these Terms of Use for its Website at any time without prior notice. By using this
Website, you are agreeing to be bound by the current version of these Terms and Conditions of Use.</p>;
<h2>8. Your Privacy</h2>;
<p>Please read our Privacy Policy.</p>;
<h2>9. Governing Law</h2>;
<p>Any claim related to Radio Otherway&apos;s Website shall be governed by the laws of ie without regards to its conflict
of law provisions.</p>;</>
);
};
export default TosComponent;

View File

@@ -1,84 +0,0 @@
"use client";
import React from "react";
import Link from "next/link";
import { useAuthUserContext } from "@/lib/auth/authUserContext";
import Image from "next/image";
import { LogIn, LogOut, PlusSquare, Menu, User } from "react-feather";
import { ThemeSelector } from "../widgets/ui/themes";
const Navbar = () => {
const { profile, loading, logOut } = useAuthUserContext();
const NavMenu = profile ? (
<React.Fragment>
<Link
href="/profile"
id="profile"
className="gap-1 normal-case btn-ghost btn"
>
<User
size={20}
className="inline-block w-5 h-5 stroke-current md:h-6 md:w-6"
/>
Profile
</Link>
<button
tabIndex={0}
className="gap-1 normal-case btn-ghost btn"
onClick={() => logOut()}
>
<LogOut
size={20}
className="inline-block w-5 h-5 stroke-current md:h-6 md:w-6"
/>
<span className="hidden md:inline">Logout</span>
</button>
</React.Fragment>
) : (
<React.Fragment>
<Link
href="/signup"
id="signup"
className="gap-1 normal-case btn-ghost btn"
>
<PlusSquare size={20} className="inline-block w-5 h-5 stroke-current md:h-6 md:w-6" />
Register
</Link>
<Link
href="/login"
id="login"
className="gap-1 normal-case btn-ghost btn"
>
<LogIn
size={20}
className="inline-block w-5 h-5 stroke-current md:h-6 md:w-6"
/>
Login
</Link>
</React.Fragment>
);
return (
<nav className="w-full navbar bg-primary text-base-200 shadow-lg">
<Link href="/">
<Image src="/logo.png" alt="Otherway" width={42} height={42} />
</Link>
<div className="flex-col hidden ml-auto text-sm text-center font-body md:flex md:flex-row">
{!loading && NavMenu}
<ThemeSelector />
</div>
<div className="ml-auto lg:hidden">
<div className="dropdown-end dropdown" data-cy="dropdown">
<div tabIndex={0} className="m-1 cursor-pointer">
<Menu />
</div>
<div className="w-24 mt-3 space-y-3 text-center dropdown-content menu">
{NavMenu}
</div>
</div>
</div>
</nav>
);
};
export default Navbar;

View File

@@ -1,103 +0,0 @@
import * as firebase from "firebase/app";
import { useRouter } from "next/navigation";
import React, { useEffect } from "react";
import { Toaster } from "react-hot-toast";
import { getMessaging, onMessage } from "firebase/messaging";
import { app } from "@/lib/auth/firebase";
import ToastService from "../widgets/toast";
import { doc, setDoc } from "@firebase/firestore";
import { useAuthUserContext } from "@/lib/auth/authUserContext";
import { users } from "@/lib/db";
import logger from "@/lib/util/logging";
import { parseUserAgent } from "react-device-detect";
import { firebaseCloudMessaging } from "@/lib/util/notifications/firebaseMessaging";
const PushNotificationWrapper = ({ children }: React.PropsWithChildren) => {
const router = useRouter();
const { profile } = useAuthUserContext();
useEffect(() => {
const _getAndStoreRegistrationToken = async () => {
const { ua } = parseUserAgent(window.navigator.userAgent);
if (!profile) return;
await setToken();
// Event listener that listens for the push notification event in the background
if ("serviceWorker" in navigator) {
navigator.serviceWorker.addEventListener("message", (event) => {
console.log("event for the service worker", event);
});
}
// Calls the getMessage() function if the token is there
async function setToken() {
try {
if (!profile) return;
const token = await firebaseCloudMessaging.init();
if (token && profile) {
const newRegistration = {
fcmToken: token,
deviceType: ua,
lastSeen: new Date(),
};
const index = profile.deviceRegistrations?.findIndex((reg) => {
return reg.fcmToken === token;
});
if (index !== undefined && index !== -1) {
if (
profile.deviceRegistrations &&
profile.deviceRegistrations[index]
) {
profile.deviceRegistrations[index] = newRegistration;
}
} else {
profile.deviceRegistrations?.push(newRegistration);
}
}
const profileWithRegistrations = Object.assign({}, profile);
await setDoc(doc(users, profile?.id), profileWithRegistrations, {
merge: true,
});
getMessage();
} catch (error) {
console.log(error);
}
}
};
_getAndStoreRegistrationToken().catch((err) => {
logger.error(
"PushNotificationWrapper",
"_getAndStoreRegistrationToken_error",
err
);
});
}, [profile]);
function getMessage() {
const messaging = getMessaging(app);
onMessage(messaging, (message) => {
ToastService.custom(
<div
onClick={() =>
message?.data?.url &&
handleClickPushNotification(message?.data?.url)
}
>
<h5>{message?.notification?.title}</h5>
<h6>{message?.notification?.body}</h6>
</div>
);
});
}
const handleClickPushNotification = (url: string) => {
router.push(url);
};
return (
<>
<Toaster />
{children}
</>
);
};
export default PushNotificationWrapper;

View File

@@ -1,4 +0,0 @@
import NavBar from "./NavBar";
import PushNotificationWrapper from "./PushNotificationWrapper";
export { NavBar, PushNotificationWrapper };

View File

@@ -1,150 +0,0 @@
"use client";
import React from "react";
import { useRouter } from "next/navigation";
import { IoLogoFacebook, IoLogoGoogle, IoLogoTwitter } from "react-icons/io";
import { useFirebaseAuth } from "@/lib/auth";
const LoginPage = () => {
const {
signInWithGoogle,
signInWithFacebook,
signInWithTwitter,
profile,
signIn,
} = useFirebaseAuth();
const router = useRouter();
const [error, setError] = React.useState("");
const [forgot, setForgot] = React.useState(false);
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const login = async (
event: React.SyntheticEvent<HTMLButtonElement>
): Promise<void> => {
const result = await signIn(email, password);
};
return (
<div className="max-w-lg p-10 rounded-md shadow-md font-body bg-base-100 text-base-content md:flex-1">
<h3 className="my-4 text-2xl font-semibold font-title">Account Login</h3>
<form action="#" className="flex flex-col space-y-5">
<div className="flex flex-col space-y-1">
<label htmlFor="email" className="text-sm">
Email address
</label>
<input
type="email"
id="email"
autoFocus
className="input-bordered input-primary input input-sm"
value={email}
onChange={(event) => {
setEmail(event.target.value);
}}
/>
</div>
<div className="flex flex-col space-y-1">
<div className="flex items-center justify-between">
<label htmlFor="password" className="text-sm">
Password
</label>
<button
type="button"
onClick={() => {
setForgot(true);
}}
className="text-sm text-blue-600 hover:underline focus:text-blue-800"
>
Forgot Password?
</button>
</div>
<input
type="password"
id="password"
className="input-bordered input-primary input input-sm"
value={password}
onChange={(event) => {
setPassword(event.target.value);
}}
/>
</div>
<div>
<button
className="w-full btn-primary btn"
onClick={(event) => {
void login(event);
}}
>
Log in
</button>
</div>
<div className="flex flex-col space-y-5">
<span className="flex items-center justify-center space-x-2">
<span className="h-px bg-gray-400 w-14" />
<span className="font-normal text-gray-500">or login with</span>
<span className="h-px bg-gray-400 w-14" />
</span>
<div className="flex items-center justify-center gap-4">
<button
type="button"
className="w-1/3 gap-2 btn"
onClick={signInWithTwitter}
>
<div className="text-base-content">
<IoLogoTwitter />
</div>
<span className="text-sm font-medium text-base-content">
Twitter
</span>
</button>
<button
type="button"
className="w-1/3 gap-2 btn"
onClick={signInWithGoogle}
>
<div className="text-base-content">
<IoLogoGoogle />
</div>
<span className="text-sm font-medium text-base-content">
Gmail
</span>
</button>
<button
type="button"
className="w-1/3 gap-2 btn"
onClick={signInWithFacebook}
>
<div className="text-base-content">
<IoLogoFacebook />
</div>
<span className="text-sm font-medium text-base-content">
Facebook
</span>
</button>
</div>
{error && (
<div className="shadow-lg alert alert-error">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
className="flex-shrink-0 w-6 h-6 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{error}</span>
</div>
</div>
)}
</div>
</form>
</div>
);
};
export default LoginPage;

View File

@@ -1,177 +0,0 @@
"use client";
import React from "react";
import { useRouter } from "next/navigation";
import { IoLogoFacebook, IoLogoGoogle, IoLogoTwitter } from "react-icons/io";
import { AiOutlineExclamationCircle } from "react-icons/ai";
import { Info } from "react-feather";
import { useFirebaseAuth } from "@/lib/auth";
const SignupPage = () => {
const {
signInWithGoogle,
signInWithFacebook,
signInWithTwitter,
profile,
signUp,
linkAccounts
} = useFirebaseAuth();
const router = useRouter();
const [error, setError] = React.useState("");
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const [confirmPassword, setConfirmPassword] = React.useState("");
const [linking, setLinking] = React.useState(false);
const register = async (
$event: React.SyntheticEvent<HTMLButtonElement>
): Promise<void> => {
$event.preventDefault();
setError("");
setLinking(false);
const result = await signUp(email, password);
if (result === "auth/email-already-in-use") {
setLinking(true);
setError("");
// setError("This email address has already been used to create an account.");
} else if (result === "auth/invalid-email") {
setError("Please enter a correct email address");
} else {
setError("Unable to create an account for you at this time");
}
};
return (
<div className="max-w-lg p-10 rounded-md shadow-md font-body bg-base-100 text-base-content md:flex-1">
<h3 className="my-4 text-2xl font-semibold font-title">
Create New Account
</h3>
{error && (
<div className="mb-4 shadow-lg alert alert-error">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
className="flex-shrink-0 w-6 h-6 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{error}</span>
</div>
</div>
)}
<form action="#" className="flex flex-col space-y-5">
<div className="flex flex-col space-y-1">
<label htmlFor="email" className="text-sm">
Email address
</label>
<input
type="email"
id="email"
autoFocus
className="input-bordered input-primary input input-sm"
value={email}
onChange={(event) => {
setEmail(event.target.value);
}}
/>
</div>
<div className="flex flex-col space-y-1">
<div className="flex items-center justify-between">
<label htmlFor="password" className="text-sm">
Password
</label>
</div>
<input
type="password"
id="password"
className="input-bordered input-primary input input-sm"
value={password}
onChange={(event) => {
setPassword(event.target.value);
}}
/>
</div>
<div className="flex flex-col space-y-1">
<div className="flex items-center justify-between">
<label htmlFor="password" className="text-sm">
Repeat password
</label>
</div>
<input
type="password"
id="password"
className="input-bordered input-primary input input-sm"
value={confirmPassword}
onChange={(event) => {
setConfirmPassword(event.target.value);
}}
/>
</div>
<div>
<button
className="w-full btn-primary btn"
onClick={(event) => {
void register(event);
}}
>
Register for Account
</button>
</div>
<div className="flex flex-col space-y-5">
<span className="flex items-center justify-center space-x-2">
<span className="h-px bg-gray-400 w-14" />
<span className="font-normal text-gray-500">or sign up with</span>
<span className="h-px bg-gray-400 w-14" />
</span>
<div className="flex items-center justify-center gap-4">
<button
type="button"
className="w-1/3 gap-2 btn"
onClick={signInWithTwitter}
>
<div className="text-base-content">
<IoLogoTwitter />
</div>
<span className="text-sm font-medium text-base-content">
Twitter
</span>
</button>
<button
type="button"
className="w-1/3 gap-2 btn"
onClick={signInWithGoogle}
>
<div className="text-base-content">
<IoLogoGoogle />
</div>
<span className="text-sm font-medium text-base-content">
Gmail
</span>
</button>
<button
type="button"
className="w-1/3 gap-2 btn"
onClick={signInWithFacebook}
>
<div className="text-base-content">
<IoLogoFacebook />
</div>
<span className="text-sm font-medium text-base-content">
Facebook
</span>
</button>
</div>
</div>
</form>
</div>
);
};
export default SignupPage;

View File

@@ -1,4 +0,0 @@
import LoginPage from "./LoginPage";
import SignupPage from "./SignupPage";
export { LoginPage, SignupPage };

View File

@@ -1,35 +0,0 @@
import Loading from "@/app/loading";
import { RemindMeButton } from "@/components/widgets";
import { getMonthName, getTime } from "@/lib/util/dateUtils";
import { Show } from "@/models";
import React from "react";
import NoShows from "./NoShows";
import UpcomingShowsTable from "./UpcomingShowsTable";
interface IHomePageComponentProps {
shows: Show[] | undefined;
}
const HomePageComponent = ({ shows }: IHomePageComponentProps) => {
const _getLayout = () => {
if (!shows) {
return <Loading />;
}
if (shows.length === 0) {
return <NoShows />;
}
return (
<div className="h-full pt-4 overflow-hidden">
<div className="justify-center flex-1 px-2 mx-2 md:flex md:justify-start">
<span className="text-2xl font-bold">Upcoming events</span>
</div>
<div className="mt-1 divider" />
<div className="flex justify-center mx-6 mt-4" id="journal-scroll">
<UpcomingShowsTable shows={shows} />
</div>
</div>
);
};
return _getLayout();
};
export default HomePageComponent;

View File

@@ -1,15 +0,0 @@
import React from "react";
const NoShows = () => {
return (
<div className="flex items-center justify-center flex-grow -mt-32">
<div className="text-center ">
<div className="max-w-md">
<h1 className="text-5xl font-bold">No upcoming shows found</h1>
</div>
</div>
</div>
);
};
export default NoShows;

View File

@@ -1,44 +0,0 @@
import React from "react";
import { RemindMeButton } from "@/components/widgets";
import { getMonthName, getTime } from "@/lib/util/dateUtils";
import { Show } from "@/models";
interface IUpcomingShowsTableProps {
shows: Show[];
}
const UpcomingShowsTable = ({ shows }: IUpcomingShowsTableProps) => {
return (
<table className="table w-full">
{/* head */}
<thead>
<tr>
<th>Who?</th>
<th>When?</th>
<th>What?</th>
<th></th>
</tr>
</thead>
<tbody>
{/* row 1 */}
{shows &&
shows.map((show: Show) => (
<tr key={show.id}>
<th>{show.creator}</th>
<td className="pl-5 pr-3 whitespace-no-wrap">
<div className="text-gray-400">
{`${new Date(show.date).getDay()} ${getMonthName(show.date)}`}{" "}
@ {getTime(show.date)}
</div>
</td>
<td>{show.title}</td>
<th>
<RemindMeButton showId={show.id} />
</th>
</tr>
))}
</tbody>
</table>
);
};
export default UpcomingShowsTable;

View File

@@ -1,3 +0,0 @@
import HomePageComponent from "./HomePageComponent";
export default HomePageComponent;

View File

@@ -1,171 +0,0 @@
"use client";
import React, { useEffect, useMemo } from "react";
import { useRouter } from "next/navigation";
import { User, Bell } from "react-feather";
import classNames from "classnames";
import { useAuthUserContext } from "@/lib/auth/authUserContext";
import ProfilePageComponentProfile from "./ProfilePageComponentProfile";
import ProfilePageComponentNotifications from "./ProfilePageComponentNotifications";
import { SubmitHandler, useForm } from "react-hook-form";
import { users } from "@/lib/db";
import { doc, setDoc } from "firebase/firestore";
import ToastService from "@/components/widgets/toast";
import logger from "@/lib/util/logging";
import { removeUndefinedProperties } from "@/lib/util/objectUtils";
export type ProfileForm = {
displayName: string;
email: string;
about: string;
photoURL: string;
headerPhotoURL: string;
mobileNumber: string;
};
const ProfilePageComponent = () => {
const { profile, loading } = useAuthUserContext();
const router = useRouter();
React.useEffect(() => {
if (!loading && !profile) {
router.push("/");
}
}, [profile, loading, router]);
const subNavigation = [
{ name: "profile", title: "Profile", icon: User },
{
name: "notifications",
title: "Notifications",
icon: Bell,
},
];
const [selectedItem, setSelectedItem] = React.useState("profile");
const {
register,
handleSubmit,
setValue,
reset,
watch,
control,
formState: { errors },
} = useForm<ProfileForm>({
defaultValues: useMemo(() => {
return profile;
}, [profile]),
});
useEffect(() => {
if (profile) {
reset(profile);
}
}, [profile, reset]);
const onSubmit: SubmitHandler<ProfileForm> = async (data) => {
console.log(data);
try {
const newProfile = removeUndefinedProperties({
displayName: data.displayName,
email: data.email,
about: data.about,
photoURL: data.photoURL,
headerPhotoURL: data.headerPhotoURL,
mobileNumber: data.mobileNumber,
lastSeen: new Date(),
});
const result = await setDoc(
doc(users, profile?.id),
Object.assign({}, newProfile),
{
merge: true,
}
);
console.log("ProfilePageComponentProfile", "_submitProfileForm", result);
ToastService.success("Successfully updated your profile", "Success");
} catch (err) {
logger.error("ProfilePageComponentProfile", "_submitProfileForm", err);
ToastService.error("Failed to update your profile.");
}
};
useEffect(() => {}, [selectedItem]);
const _getView = () => {
if (loading) {
return <div>Loading</div>;
} else if (!loading && !profile) {
return <div>Rerouting</div>;
} else if (!loading && profile) {
return (
<div className="pt-4 overflow-hidden">
<div className="justify-center flex-1 px-2 mx-2 md:flex md:justify-start">
<span className="text-2xl font-bold">Your Profile</span>
</div>
<div className="mt-1 divider" />
<div className="divide-y lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
<aside className="py-6 lg:col-span-3">
<nav className="space-y-1">
{subNavigation.map((item) => (
<a
onClick={() => setSelectedItem(item.name)}
key={item.name}
className={classNames(
item.name === selectedItem
? "border-teal-600 bg-base-100 text-teal-700 hover:text-teal-700"
: "border-transparent hover:text-base-300",
"group flex cursor-pointer items-center border-l-4 px-3 py-2 text-sm font-medium hover:bg-accent"
)}
aria-current={item.name ? "page" : undefined}
>
<item.icon
className={classNames(
item.name === selectedItem
? "text-teal-500 group-hover:text-base-100"
: "text-base-content group-hover:text-base-100",
"-ml-1 mr-3 h-6 w-6 flex-shrink-0"
)}
aria-hidden="true"
/>
<span className="truncate">{item.title}</span>
</a>
))}
</nav>
</aside>
<div className="p-4 divide-y lg:col-span-9">
<form onSubmit={handleSubmit(onSubmit)}>
{selectedItem === "profile" ? (
<ProfilePageComponentProfile
setValue={setValue}
register={register}
profile={profile}
/>
) : (
<ProfilePageComponentNotifications
register={register}
control={control}
profile={profile}
/>
)}
<div className="pt-5">
<div className="flex justify-end space-x-2">
<button type="button" className="btn-warning btn">
Cancel
</button>
<button type="submit" className="btn-success btn">
Save
</button>
</div>
</div>
</form>
</div>
</div>
</div>
);
} else {
return (
<div>
<h1>This is weird</h1>
</div>
);
}
};
return _getView();
};
export default ProfilePageComponent;

View File

@@ -1,70 +0,0 @@
import React from "react";
import { HeadingSubComponent } from "@/components/widgets/text";
import {
PhoneNumber,
RequestPushNotifications,
} from "@/components/widgets/notifications";
import { UseFormRegister } from "react-hook-form";
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
import { Profile } from "@/models";
interface IProfilePageComponentNotificationsProps {
register: UseFormRegister<ProfileForm>;
control: any;
profile: Profile;
}
const ProfilePageComponentNotifications = ({
register,
control,
profile,
}: IProfilePageComponentNotificationsProps) => {
const [notificationPermissionsGranted, setNotificationPermissionsGranted] =
React.useState(Notification.permission !== "granted");
React.useEffect(() => {
setNotificationPermissionsGranted(Notification.permission !== "granted");
}, []);
return (
<div className="space-y-8 divide-y sm:space-y-5">
<div>
<div>
<h3 className="text-lg font-medium leading-6 ">Notifications</h3>
<p className="max-w-2xl mt-1 text-sm ">
Here you can setup various different forms of notifications
</p>
</div>
<div className="mt-6 space-y-6 sm:mt-5 sm:space-y-5">
<div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent
title="Browser Notifications"
subHeading="We'll send you an alert through (this) browser when a show is about to start."
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<div className="flex max-w-lg rounded-md shadow-sm">
{notificationPermissionsGranted ? (
<div>Permissions granted</div>
) : (
<RequestPushNotifications />
)}
</div>
</div>
</div>
<div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent
title="Phone number"
subHeading="We'll try to send you a WhatsApp message when a show is about to start"
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<div className="flex max-w-lg rounded-md shadow-sm">
<PhoneNumber name="mobileNumber" control={control} />
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ProfilePageComponentNotifications;

View File

@@ -1,124 +0,0 @@
"use client";
import { HeadingSubComponent } from "@/components/widgets/text";
import React, { useState } from "react";
import { UseFormRegister, UseFormSetValue } from "react-hook-form";
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
import { Profile } from "@/models";
import { FirebaseImageUpload, InputText } from "@/components/widgets/inputs";
import { InputTextArea } from "@/components/widgets/inputs";
interface IProfilePageComponentProfileProps {
profile: Profile;
register: UseFormRegister<ProfileForm>;
setValue: UseFormSetValue<ProfileForm>;
}
const ProfilePageComponentProfile = ({
profile,
register,
setValue,
}: IProfilePageComponentProfileProps) => {
const [photoURLFile, setPhotoURLFile] = useState("");
return (
<div className="space-y-8 divide-y sm:space-y-5">
<div>
<div>
<h3 className="text-lg font-medium leading-6 ">Profile</h3>
<p className="max-w-2xl mt-1 text-sm ">
This information will be displayed publicly so be careful what you
share.
</p>
</div>
<div className="mt-6 space-y-6 sm:mt-5 sm:space-y-5">
<div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent
title="Display Name"
subHeading="The name you would like others to see you as."
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<div className="flex max-w-lg rounded-md shadow-sm">
<InputText
id="displayName"
type="text"
label="Display name"
{...register("displayName")}
/>
</div>
</div>
</div>
<div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent
title="Email address"
subHeading="In case we need to get in touch with you"
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<div className="flex max-w-lg rounded-md shadow-sm">
<InputText
id="email"
type="email"
label="Email address"
showLabel={false}
{...register("email")}
/>
</div>
</div>
</div>
<div className="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent
title="About"
subHeading="Tell us a little bit about yourself.. but not too much"
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<InputTextArea
id="about"
type="textarea"
label="About"
showLabel={false}
{...register("about")}
/>
</div>
</div>
<div className="sm:grid sm:grid-cols-3 sm:items-center sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent
title="Photo"
subHeading="Upload a picture to distinguish you from the rest"
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<FirebaseImageUpload
forType="user"
imageType="avatar"
itemId={profile.id}
imageUrl={profile.photoURL}
controlName="photoURL"
setValue={setValue}
{...register("photoURL")}
/>
</div>
</div>
<div className="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent
title="Cover photo"
subHeading="Upload a wide photo for the top of your profile page"
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<FirebaseImageUpload
forType="user"
imageType="profile"
itemId={profile.id}
imageUrl={profile.headerPhotoURL}
controlName="headerPhotoURL"
setValue={setValue}
{...register("headerPhotoURL")}
/>
</div>
</div>
</div>
</div>
</div>
);
};
export default ProfilePageComponentProfile;

View File

@@ -1,3 +0,0 @@
import ProfilePageComponent from "./ProfilePageComponent";
export { ProfilePageComponent };

View File

@@ -1,53 +0,0 @@
import { FirestoreProvider, useFirebaseApp } from "reactfire";
import { useMemo } from "react";
import {
enableIndexedDbPersistence,
connectFirestoreEmulator,
initializeFirestore,
} from "firebase/firestore";
import { isBrowser } from "~/core/generic";
export default function FirestoreProvider({
children,
useEmulator,
}: React.PropsWithChildren<{ useEmulator?: boolean }>) {
const firestore = useFirestore();
// connect to emulator if enabled
if (useEmulator) {
const host = getFirestoreHost();
const port = Number(getFirestorePort());
try {
connectFirestoreEmulator(firestore, host, port);
} catch (e) {
// this may happen on re-renderings
}
}
const enablePersistence = isBrowser();
// We enable offline capabilities by caching Firestore in IndexedDB
// NB: if you don't want to cache results, please remove the next few lines
if (enablePersistence) {
enableIndexedDbPersistence(firestore);
}
return <FirestoreProvider sdk={firestore}>{children}</FirestoreProvider>;
}
function getFirestoreHost() {
return process.env.NEXT_PUBLIC_FIREBASE_EMULATOR_HOST ?? "localhost";
}
function getFirestorePort() {
return process.env.NEXT_PUBLIC_FIRESTORE_EMULATOR_PORT ?? 8080;
}
function useFirestore() {
const app = useFirebaseApp();
return useMemo(() => initializeFirestore(app, {}), [app]);
}

View File

@@ -1,14 +0,0 @@
import Loading from "@/app/loading";
import { defaults } from "@/lib/constants";
import React, { useEffect } from "react";
const ThemeProvider = ({ children }: React.PropsWithChildren) => {
const [theme, setTheme] = React.useState("");
useEffect(() => {
setTheme(localStorage.getItem("theme") || defaults.defaultTheme);
}, [theme]);
return theme ? <>{children}</> : <Loading />;
};
ThemeProvider.displayName = "ThemeProvider";
export default ThemeProvider;

View File

@@ -1,2 +0,0 @@
import ThemeProvider from "./ThemeProvider";
export { ThemeProvider };

View File

@@ -1,10 +0,0 @@
import React from "react";
interface IErrorTextProps extends React.PropsWithChildren {
styleClass: string;
}
const ErrorText = ({ styleClass, children }: IErrorTextProps) => {
return <p className={`text-center text-error ${styleClass}`}>{children}</p>;
};
export default ErrorText;

View File

@@ -1,43 +0,0 @@
"use client";
import { useFirebaseAuth } from "@/lib/auth";
import { Show } from "@/models";
import React from "react";
import { MdAddAlarm } from "react-icons/md";
import ToastService from "./toast/toastService";
const RemindMeButton = ({ showId }: { showId: string }) => {
const { profile } = useFirebaseAuth();
const createShowReminder = async () => {
if (profile?.id) {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/reminders`,
{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
userId: profile?.id,
showId: profile?.id
})
}
);
if (response.status === 201) {
ToastService.success("Reminder created successfully");
} else {
ToastService.error("Unable to create reminder at this time");
}
}
};
return (
<button
className="gap-2 btn"
onClick={async () => await createShowReminder()}
>
<MdAddAlarm className="w-6 h-6" />
Remind Me!
</button>
);
};
export default RemindMeButton;

View File

@@ -1,25 +0,0 @@
import React from "react";
import { ISocialButtonProps } from "./socialButtonProps";
const TwitterButton = ({ onClick }: ISocialButtonProps) => {
return (
<button
type="button"
className="btn-secondary btn mb-2 flex
w-1/3 gap-2 rounded px-6 py-2.5 text-xs font-medium uppercase leading-normal text-white shadow-md transition duration-150 ease-in-out hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg"
style={{ backgroundColor: "#1da1f2" }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="mr-2 h-3.5 w-3.5"
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z" />
</svg>
Twitter
</button>
);
};
export default TwitterButton;

View File

@@ -1,3 +0,0 @@
import TwitterButton from "./TwitterButton";
export { TwitterButton };

View File

@@ -1,3 +0,0 @@
export interface ISocialButtonProps {
onClick: () => {};
}

View File

@@ -1,4 +0,0 @@
import ErrorText from "./ErrorText";
import RemindMeButton from "./RemindMeButton";
export { ErrorText, RemindMeButton };

View File

@@ -1,232 +0,0 @@
import {
ref as storageRef,
uploadBytesResumable,
getDownloadURL,
} from "firebase/storage";
import { storage } from "@/lib/db";
import Image from "next/image";
import {
useEffect,
useRef,
useState,
forwardRef,
ChangeEventHandler,
} from "react";
var path = require("path");
import { UploadCloud } from "react-feather";
import { getFileExtension } from "@/lib/util/fileUtils";
import ITextInputProps from "@/components/widgets/inputs/props";
import {
Controller,
FieldPath,
UseFormReturn,
UseFormSetValue,
} from "react-hook-form";
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
interface IFirebaseImageUploaderProps {
forType: "user" | "show";
imageType: "avatar" | "profile";
itemId: string;
imageUrl?: string;
controlName: FieldPath<ProfileForm>;
setValue: UseFormSetValue<ProfileForm>;
onChange: ChangeEventHandler<HTMLInputElement>;
onBlur: ChangeEventHandler<HTMLInputElement>;
}
const FirebaseImageUploader = forwardRef<
HTMLInputElement,
IFirebaseImageUploaderProps
>(
(
{
controlName,
forType,
imageType,
itemId,
imageUrl,
setValue,
onChange,
onBlur,
},
ref
) => {
const [file, setFile] = useState<File | null>();
const [filePath, setFilePath] = useState();
const [isUploading, setIsUploading] = useState(false);
const [percent, setPercent] = useState(0);
const fileInput = useRef<HTMLInputElement>(null);
const [error, setError] = useState("");
const _handleChange = (event: React.FormEvent<HTMLInputElement>) => {
const e = event.target as HTMLInputElement;
if (!e.files || e.files.length === 0) {
return;
}
setFile(e.files[0]);
};
useEffect(() => {
const _cleanUp = () => {
setFile(null);
setIsUploading(false);
setPercent(0);
};
const _handleUpload = () => {
if (!file) {
setError("You must choose a file to upload first");
return;
}
console.log("FirebaseImageUploader", "Creating storage refs", storage);
const extension = getFileExtension(file);
const newFilePath = path.join(
"files",
"images",
forType,
imageType,
`${itemId}.${extension}`
);
setFilePath(newFilePath);
console.log("FirebaseImageUploader", "FilePath", newFilePath);
const remoteFileReference = storageRef(storage, newFilePath);
console.log(
"FirebaseImageUploader",
"Created storage refs",
remoteFileReference
);
console.log("FirebaseImageUploader", "Starting upload task");
setIsUploading(true);
const uploadTask = uploadBytesResumable(remoteFileReference, file);
uploadTask.on(
"state_changed",
(snapshot) => {
const percent = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
console.log(
"FirebaseImageUploader",
"uploading",
`${percent}% done`
);
setPercent(percent);
},
(err) => {
_cleanUp();
console.log(err);
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
if (controlName) {
setValue(controlName, url);
}
});
_cleanUp();
}
);
};
if (file && !isUploading) {
_handleUpload();
}
}, [controlName, file, forType, imageType, isUploading, itemId, setValue]);
return imageType === "avatar" ? (
<div className="flex flex-col">
<div className="flex items-center space-x-1">
<span className="w-12 h-12 overflow-hidden rounded-full">
{imageUrl ? (
<Image
alt="Existing image"
src={imageUrl}
className="w-full h-full "
width={48}
height={48}
/>
) : (
<svg
className="w-full h-full "
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
)}
</span>
<button
type="button"
className={`btn-outline btn-primary btn gap-2 ${
isUploading ? "loading" : ""
}`}
onClick={() => fileInput?.current?.click()}
>
<UploadCloud
className="w-6 h-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
/>
{!imageUrl ? "Upload image" : "Change image"}
</button>
<input
name={controlName}
placeholder="File input hidden"
className="invisible"
type="file"
accept="image/*"
ref={fileInput}
onBlur={onBlur}
onChange={($event) => {
_handleChange($event);
onChange($event);
}}
/>
</div>
{percent > 0 && (
<span className="block m-1 overflow-hidden text-xs italic text-gray-600 rounded-full">
{`Uploading, ${percent}% done`}
</span>
)}
</div>
) : (
<div className="flex justify-center max-w-lg px-6 pt-5 pb-6 border-2 border-dashed rounded-md">
<div className="space-y-1 text-center">
<svg
className="w-12 h-12 mx-auto "
stroke="currentColor"
fill="none"
viewBox="0 0 48 48"
aria-hidden="true"
>
<path
d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<div className="flex text-sm ">
<label
htmlFor="file-upload"
className="relative font-medium rounded-md cursor-pointer te focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2"
>
<span>Upload a file</span>
<input
id="file-upload"
name="file-upload"
type="file"
className="sr-only"
/>
</label>
<p className="pl-1">or drag and drop</p>
</div>
<p className="text-xs ">PNG, JPG, GIF up to 10MB</p>
</div>
</div>
);
}
);
FirebaseImageUploader.displayName = "FirebaseImageUploader";
export default FirebaseImageUploader;

View File

@@ -1,47 +0,0 @@
/* eslint-disable @next/next/no-img-element */
import React from "react";
import Dropzone, { DropzoneRef, useDropzone } from "react-dropzone";
const ImageUpload = () => {
const dropzoneRef = React.createRef<DropzoneRef>();
const openDialog = () => {
if (dropzoneRef.current) {
dropzoneRef.current.open();
}
};
return (
<Dropzone
accept={{
"image/png": [".png"],
"image/jpg": [".jpg", ".jpeg"],
}}
maxFiles={1}
ref={dropzoneRef}
>
{({ getRootProps, getInputProps, acceptedFiles }) => {
return (
<div className="container">
<div {...getRootProps({ className: "dropzone" })}>
{acceptedFiles?.length ? (
<div id="preview">
{acceptedFiles[0] && (
<img
alt="Uploaded image"
className="object-cover"
src={URL.createObjectURL(acceptedFiles[0])}
/>
)}
</div>
) : (
<div id="drop">
<div className="flex items-center justify-center w-full"></div>
</div>
)}
</div>
</div>
);
}}
</Dropzone>
);
};
export default ImageUpload;

View File

@@ -1,30 +0,0 @@
import React, { forwardRef } from "react";
import ITextInputProps from "./props";
const InputText = forwardRef<HTMLInputElement, ITextInputProps>(
(
{ id, type, placeholder, label, showLabel = true, onChange, onBlur },
ref
) => {
return (
<React.Fragment>
{showLabel && (
<label className="label" htmlFor={id}>
<span className="label-text">{label}</span>
</label>
)}
<input
id={id}
className="w-full input-bordered input"
type={type || "text"}
placeholder={placeholder || ""}
onChange={onChange}
onBlur={onBlur}
ref={ref}
/>
</React.Fragment>
);
}
);
InputText.displayName = "InputTextAreaComponent";
export default InputText;

View File

@@ -1,31 +0,0 @@
import React, { forwardRef, Ref } from "react";
import ITextInputProps from "./props";
const InputTextArea = forwardRef<HTMLTextAreaElement, ITextInputProps>(
({ id, placeholder, showLabel = true, label, onChange, onBlur }, ref) => {
return (
<React.Fragment>
{showLabel && (
<label className="label" htmlFor={id}>
<span className="label-text">{label}</span>
</label>
)}
<textarea
id={id}
name={id}
rows={3}
className="w-full h-24 textarea-bordered textarea"
placeholder={placeholder || ""}
onChange={onChange}
onBlur={onBlur}
ref={ref}
/>
</React.Fragment>
);
}
);
InputTextArea.displayName = "InputTextComponent";
export default InputTextArea;

View File

@@ -1,6 +0,0 @@
import ImageUpload from "./ImageUpload";
import InputText from "./InputText";
import InputTextArea from "./InputTextArea";
import FirebaseImageUpload from "./FirebaseImageUploader";
export { InputText, ImageUpload, InputTextArea, FirebaseImageUpload };

View File

@@ -1,13 +0,0 @@
import { ChangeEventHandler } from "react";
export default interface ITextInputProps {
id: string;
type: string;
placeholder?: string;
showLabel?: boolean;
label: string;
name: string;
onChange: ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onBlur: ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
}

View File

@@ -1,26 +0,0 @@
import React from "react";
import "react-phone-number-input/style.css";
import { Control, UseFormRegister } from "react-hook-form";
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
import { DefaultFormValues } from "react-phone-number-input/react-hook-form";
import PhoneInputWithCountry from "react-phone-number-input/react-hook-form";
interface IPhoneNumberNotificationControlProps {
name: string;
control: any;
}
const PhoneNumberNotificationControl = ({
name,
control,
}: IPhoneNumberNotificationControlProps) => {
return (
<PhoneInputWithCountry
name={name}
className="w-full input-bordered input"
defaultCountry="IE"
placeholder="Enter phone number"
control={control}
/>
);
};
export default PhoneNumberNotificationControl;

View File

@@ -1,26 +0,0 @@
import React from "react";
import localforage from "localforage";
import { firebaseCloudMessaging } from "@/lib/util/notifications/firebaseMessaging";
const RequestPushNotifications = () => {
const _checkNotifications = async () => {
//clear any existing tokens as we will be re-requesting
await localforage.removeItem("fcm_token");
await firebaseCloudMessaging.init();
};
return (
<button
type="button"
className="btn-primary btn"
onClick={() => {
_checkNotifications().catch((err: any) => {
console.error(err);
});
}}
>
Request Notification Permissions
</button>
);
};
export default RequestPushNotifications;

View File

@@ -1,25 +0,0 @@
import React from "react";
import { UseFormRegister } from "react-hook-form";
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
import { Profile } from "@/models";
interface ISignalNotificationProps {
register: UseFormRegister<ProfileForm>;
profile: Profile;
}
const SignalNotification = ({
register,
profile,
}: ISignalNotificationProps) => {
return (
<div>
{/* <InputText
id="signalNotification"
showLabel={false}
type="tel"
{...register('signalNotification')}
/> */}
</div>
);
};
export default SignalNotification;

Some files were not shown because too many files have changed in this diff Show More