Rejig everything
@@ -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
@@ -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"
|
||||
97
.eslintrc.js
@@ -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",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"projects": {
|
||||
"default": "radio-otherway"
|
||||
}
|
||||
}
|
||||
20
.github/workflows/firebase-hosting-merge.yml
vendored
@@ -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
|
||||
@@ -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
|
||||
14
.github/workflows/scheduler-cache-events.yml
vendored
@@ -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
|
||||
14
.github/workflows/scheduler-check-reminders.yml
vendored
@@ -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
@@ -1,42 +1,2 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# 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
|
||||
.krud
|
||||
working
|
||||
|
||||
13
.hintrc
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"development"
|
||||
],
|
||||
"hints": {
|
||||
"axe/parsing": [
|
||||
"default",
|
||||
{
|
||||
"duplicate-id-aria": "off"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
5
.idea/.gitignore
generated
vendored
@@ -1,5 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
59
.idea/codeStyles/Project.xml
generated
@@ -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>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -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>
|
||||
6
.idea/jsLibraryMappings.xml
generated
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
18
.vscode/launch.json
vendored
@@ -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
@@ -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
|
||||
}
|
||||
147529
.yarn/releases/yarn-1.22.19.cjs
vendored
5
.yarnrc
@@ -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"
|
||||
@@ -1 +0,0 @@
|
||||
yarnPath: .yarn/releases/yarn-1.22.19.cjs
|
||||
201
LICENSE
@@ -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.
|
||||
38
README.md
@@ -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.
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"indexes": [],
|
||||
"fieldOverrides": []
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
rules_version = '2';
|
||||
service cloud.firestore {
|
||||
match /databases/{database}/documents {
|
||||
match /{document=**} {
|
||||
allow read, write: if true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
appDir: true,
|
||||
},
|
||||
images: {
|
||||
domains: ["firebasestorage.googleapis.com"],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
72
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
@@ -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])
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
@@ -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>
|
||||
|
Before Width: | Height: | Size: 622 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -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();
|
||||
|
Before Width: | Height: | Size: 76 KiB |
@@ -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…</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>
|
||||
BIN
public/logo.png
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
@@ -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 |
@@ -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"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
(function initTheme() {
|
||||
var theme = localStorage.getItem("theme") || "synthwave";
|
||||
document.querySelector("html").setAttribute("data-theme", theme);
|
||||
})();
|
||||
23
server.js
@@ -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}`);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
@@ -1,8 +0,0 @@
|
||||
import { LoginPage } from "@/components/pages/auth";
|
||||
import React from "react";
|
||||
|
||||
const Login = async () => {
|
||||
return <LoginPage />;
|
||||
};
|
||||
|
||||
export default Login;
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from "react";
|
||||
import { SignupPage } from "@/components/pages/auth";
|
||||
|
||||
const Signup = async () => {
|
||||
return <SignupPage />;
|
||||
};
|
||||
|
||||
export default Signup;
|
||||
@@ -1,7 +0,0 @@
|
||||
import PrivacyComponent from "@/components/boilerplate/PrivacyComponent";
|
||||
|
||||
const PrivacyPage = () => {
|
||||
return <PrivacyComponent/>
|
||||
}
|
||||
|
||||
export default PrivacyPage;
|
||||
@@ -1,7 +0,0 @@
|
||||
import TosComponent from "@/components/boilerplate/TosComponent";
|
||||
|
||||
const TosPage = () => {
|
||||
return <TosComponent />;
|
||||
};
|
||||
|
||||
export default TosPage;
|
||||
@@ -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;
|
||||
@@ -1,3 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -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" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,7 +0,0 @@
|
||||
import { ProfilePageComponent } from "@/components/pages/profile";
|
||||
|
||||
const ProfilePage = () => {
|
||||
return <ProfilePageComponent />;
|
||||
};
|
||||
|
||||
export default ProfilePage;
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 "control" 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 "the
|
||||
Company", "We", "Us" or "Our" 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"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 "Persistent" or "Session" 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"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"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"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"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"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 "Last updated" 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 protected]</a></li>
|
||||
</ul>
|
||||
</div>)
|
||||
}
|
||||
|
||||
export default PrivacyComponent;
|
||||
@@ -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'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's Website;</li>
|
||||
<li>remove any copyright or other proprietary notations from the materials; or</li>
|
||||
<li>transferring the materials to another person or `“mirror`“ 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 Otherway’s Website are provided `“as is`“. 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 Otherway’s 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 Otherway’s 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 user’s 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's Website shall be governed by the laws of ie without regards to its conflict
|
||||
of law provisions.</p>;</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TosComponent;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,4 +0,0 @@
|
||||
import NavBar from "./NavBar";
|
||||
import PushNotificationWrapper from "./PushNotificationWrapper";
|
||||
|
||||
export { NavBar, PushNotificationWrapper };
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,4 +0,0 @@
|
||||
import LoginPage from "./LoginPage";
|
||||
import SignupPage from "./SignupPage";
|
||||
|
||||
export { LoginPage, SignupPage };
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,3 +0,0 @@
|
||||
import HomePageComponent from "./HomePageComponent";
|
||||
|
||||
export default HomePageComponent;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,3 +0,0 @@
|
||||
import ProfilePageComponent from "./ProfilePageComponent";
|
||||
|
||||
export { ProfilePageComponent };
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -1,2 +0,0 @@
|
||||
import ThemeProvider from "./ThemeProvider";
|
||||
export { ThemeProvider };
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,3 +0,0 @@
|
||||
import TwitterButton from "./TwitterButton";
|
||||
|
||||
export { TwitterButton };
|
||||
@@ -1,3 +0,0 @@
|
||||
export interface ISocialButtonProps {
|
||||
onClick: () => {};
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import ErrorText from "./ErrorText";
|
||||
import RemindMeButton from "./RemindMeButton";
|
||||
|
||||
export { ErrorText, RemindMeButton };
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 };
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||