mirror of
https://github.com/fergalmoran/radio-otherway.git
synced 2025-12-22 09:50:29 +00:00
Changes to login flow and API
This commit is contained in:
2
.github/workflows/scheduler-cache-events.yml
vendored
2
.github/workflows/scheduler-cache-events.yml
vendored
@@ -9,4 +9,4 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cache outstanding events
|
||||
run: curl -X GET https://external.fergl.ie/api/cron/cache
|
||||
run: curl -X GET https://otherway.fergl.ie/api/cron/cache
|
||||
|
||||
@@ -9,4 +9,4 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cache outstanding events
|
||||
run: curl -X GET https://external.fergl.ie/api/cron/reminders
|
||||
run: curl -X GET https://otherway.fergl.ie/api/cron/reminders
|
||||
|
||||
@@ -8,9 +8,10 @@ const LoginPage = () => {
|
||||
const { signInWithGoogle, signInWithFacebook, signInWithTwitter, profile, signIn } =
|
||||
useFirebaseAuth();
|
||||
const router = useRouter();
|
||||
const [error, setError] = React.useState("");
|
||||
const [email, setEmail] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [forgot, setForgot] = React.useState(false);
|
||||
|
||||
|
||||
const login = async (
|
||||
event: React.SyntheticEvent<HTMLButtonElement>
|
||||
@@ -19,144 +20,118 @@ const LoginPage = () => {
|
||||
};
|
||||
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
|
||||
<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>
|
||||
<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);
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setForgot(true);
|
||||
}}
|
||||
className="text-sm text-blue-600 hover:underline focus:text-blue-800"
|
||||
>
|
||||
Log in
|
||||
Forgot Password?
|
||||
</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>
|
||||
{/* <div className="flex flex-row space-x-1">
|
||||
<button
|
||||
className="flex items-center justify-center px-4 py-2 space-x-2 transition-colors duration-300 border rounded-md group border-base-200 hover:bg-base-300 focus:outline-none"
|
||||
onClick={signInWithTwitter}
|
||||
>
|
||||
<div className="text-base-content">
|
||||
<IoLogoTwitter />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-base-content">
|
||||
Twitter
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center justify-center px-4 py-2 space-x-2 transition-colors duration-300 border rounded-md group border-base-200 hover:bg-base-300 focus:outline-none"
|
||||
onClick={signInWithGoogle}
|
||||
>
|
||||
<div className="text-base-content">
|
||||
<IoLogoGoogle />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-base-content">
|
||||
Gmail
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center justify-center px-4 py-2 space-x-2 transition-colors duration-300 border rounded-md group border-base-200 hover:bg-base-300 focus:outline-none"
|
||||
onClick={signInWithFacebook}
|
||||
>
|
||||
<div className="text-base-content">
|
||||
<IoLogoFacebook />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-base-content">
|
||||
Facebook
|
||||
</span>
|
||||
</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>
|
||||
</form>
|
||||
</>
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import "./Boilerplate.css";
|
||||
|
||||
const TosComponent = () => {
|
||||
// @ts-ignore
|
||||
return (
|
||||
@@ -15,26 +13,26 @@ const TosComponent = () => {
|
||||
|
||||
<h2>2. Use License</h2>;
|
||||
|
||||
<p>Permission is granted to temporarily download one copy of the materials on Radio Otherway's Website for personal,
|
||||
<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>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>
|
||||
<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. These Terms of Service has been created with the help of the <a
|
||||
href="https://www.termsofservicegenerator.net">Terms Of Service Generator</a>.</p>;
|
||||
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
|
||||
<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>;
|
||||
@@ -71,7 +69,7 @@ const TosComponent = () => {
|
||||
|
||||
<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
|
||||
<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>;</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,13 +5,14 @@ import { Profile } from "@/models";
|
||||
interface IAuthUserContext {
|
||||
loading: boolean;
|
||||
profile: Profile | undefined,
|
||||
logOut: () => Promise<boolean>;
|
||||
logOut: () => void
|
||||
}
|
||||
|
||||
const authUserContext = createContext<IAuthUserContext>({
|
||||
loading: true,
|
||||
profile: undefined,
|
||||
logOut: () => Promise.resolve(false)
|
||||
logOut: () => {
|
||||
}
|
||||
});
|
||||
|
||||
export function AuthUserProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
const LoginFunctions = {
|
||||
// PROVIDER_ID of auth provider, the credential object, and the email of the user
|
||||
signInOrLink: async function(provider, credential, email) {
|
||||
this.saveCredential(provider, credential);
|
||||
await auth().signInWithCredential(credential).catch(
|
||||
async (error) => {
|
||||
try {
|
||||
if (error.code != "auth/account-exists-with-different-credential") {
|
||||
throw error;
|
||||
}
|
||||
let methods = await auth().fetchSignInMethodsForEmail(email);
|
||||
let oldCred = await this.getCredential(methods[0]);
|
||||
let prevUser = await auth().signInWithCredential(oldCred);
|
||||
auth().currentUser.linkWithCredential(credential);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
getCredential: async function(provider) {
|
||||
try {
|
||||
let value = await AsyncStorage.getItem(provider);
|
||||
if (value !== null) {
|
||||
let [token, secret] = JSON.parse(value);
|
||||
return this.getProvider(provider).credential(token, secret);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
saveCredential: async function(provider, credential) {
|
||||
try {
|
||||
let saveData = JSON.stringify([credential.token, credential.secret]);
|
||||
await AsyncStorage.setItem(
|
||||
provider,
|
||||
saveData
|
||||
);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
getProvider: function(providerId) {
|
||||
switch (providerId) {
|
||||
case auth.GoogleAuthProvider.PROVIDER_ID:
|
||||
return auth.GoogleAuthProvider;
|
||||
case auth.FacebookAuthProvider.PROVIDER_ID:
|
||||
return auth.FacebookAuthProvider;
|
||||
case auth.TwitterAuthProvider.PROVIDER_ID:
|
||||
return auth.TwitterAuthProvider;
|
||||
default:
|
||||
throw new Error(`No provider implemented for ${providerId}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
export default LoginFunctions;
|
||||
@@ -3,19 +3,18 @@ import {
|
||||
createUserWithEmailAndPassword,
|
||||
FacebookAuthProvider,
|
||||
getAuth,
|
||||
GoogleAuthProvider, linkWithPopup,
|
||||
GoogleAuthProvider, linkWithPopup, OAuthProvider,
|
||||
onAuthStateChanged,
|
||||
signInWithEmailAndPassword,
|
||||
signInWithPopup, signInWithRedirect,
|
||||
signOut,
|
||||
TwitterAuthProvider
|
||||
TwitterAuthProvider, UserCredential
|
||||
} from "firebase/auth";
|
||||
import { app } from "./firebase";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Profile } from "@/models";
|
||||
import { FirebaseAuth } from "@firebase/auth-types";
|
||||
import firebase from "firebase/app";
|
||||
import LoginFunctions from "@/lib/auth/loginFunctions";
|
||||
|
||||
|
||||
export default function useFirebaseAuth() {
|
||||
@@ -54,7 +53,7 @@ export default function useFirebaseAuth() {
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
const signIn = (email: string, password: string) =>
|
||||
const signIn = (email: string, password: string): Promise<> =>
|
||||
signInWithEmailAndPassword(auth, email, password);
|
||||
|
||||
const signUp = (email: string, password: string) =>
|
||||
@@ -63,45 +62,49 @@ export default function useFirebaseAuth() {
|
||||
const logOut = () =>
|
||||
signOut(auth).then(clear);
|
||||
|
||||
const _processSignIn = async (provider: any) => {
|
||||
const _processSignIn = async (provider: any): Promise<UserCredential | undefined> => {
|
||||
try {
|
||||
try {
|
||||
const result = await signInWithPopup(auth, provider);
|
||||
} catch (error: any) {
|
||||
if (error.code === "auth/account-exists-with-different-credential") {
|
||||
linkWithPopup(result.currentUser, provider).then((result) => {
|
||||
// Accounts successfully linked.
|
||||
const result = await signInWithPopup(auth, provider);
|
||||
return result;
|
||||
} catch (err: any) {
|
||||
console.log("useFirebaseAuth", "_processSignIn", err);
|
||||
if (err.code === "auth/account-exists-with-different-credential") {
|
||||
const credential = OAuthProvider.credentialFromError(err);
|
||||
console.log("useFirebaseAuth", "_processSignIn_duplicateAccount", err);
|
||||
const auth = getAuth();
|
||||
if (auth?.currentUser) {
|
||||
linkWithPopup(auth.currentUser, provider).then((result) => {
|
||||
const credential = GoogleAuthProvider.credentialFromResult(result);
|
||||
return credential;
|
||||
}).catch((error) => {
|
||||
console.log("useFirebaseAuth", "_processSignIn", "Failure in _processSignIn", err);
|
||||
// Handle Errors here.
|
||||
// ...
|
||||
});
|
||||
}
|
||||
}
|
||||
if (result.user) {
|
||||
const profile = getUserProfile();
|
||||
setProfile(profile);
|
||||
router.push("/");
|
||||
}
|
||||
} catch (error: any) {
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message;
|
||||
const email = error.email;
|
||||
const credential = GoogleAuthProvider.credentialFromError(error);
|
||||
}
|
||||
};
|
||||
const signInWithGoogle = async () => {
|
||||
const provider = new GoogleAuthProvider();
|
||||
provider.setCustomParameters({ prompt: "select_account" });
|
||||
provider.addScope("https://www.googleapis.com/auth/userinfo.profile");
|
||||
const credential = await signInWithPopup(auth, provider);
|
||||
return LoginFunctions.signInOrLink(GoogleAuthProvider.PROVIDER_ID, credential, credential.user.email);
|
||||
// await _processSignIn(provider);
|
||||
|
||||
const result = await _processSignIn(provider);
|
||||
if (result) {
|
||||
const credential = GoogleAuthProvider.credentialFromResult(result);
|
||||
const profile = getUserProfile();
|
||||
setProfile(profile);
|
||||
router.push("/");
|
||||
}
|
||||
};
|
||||
const signInWithTwitter = async () => {
|
||||
const credential = new TwitterAuthProvider();
|
||||
return LoginFunctions.signInOrLink(GoogleAuthProvider.PROVIDER_ID, credential, credential.user.email);
|
||||
const provider = new TwitterAuthProvider();
|
||||
const result = await _processSignIn(provider);
|
||||
if (result) {
|
||||
const credential = TwitterAuthProvider.credentialFromResult(result);
|
||||
const profile = getUserProfile();
|
||||
setProfile(profile);
|
||||
router.push("/");
|
||||
}
|
||||
};
|
||||
const signInWithFacebook = async () => {
|
||||
const provider = new FacebookAuthProvider();
|
||||
|
||||
@@ -19,10 +19,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
}, { merge: true });
|
||||
}
|
||||
logger.debug("Stored show", res);
|
||||
res.status(200);
|
||||
res.status(200).json({ status: "OK" });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
res.status(500);
|
||||
res.status(500).json({status: "Error"});
|
||||
}
|
||||
res.end();
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
res.status(200);
|
||||
res.status(200).json({status: "OK"});
|
||||
res.end();
|
||||
};
|
||||
export default handler;
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@@ -463,9 +463,9 @@
|
||||
yargs "^16.2.0"
|
||||
|
||||
"@headlessui/react@^1.7.11":
|
||||
version "1.7.11"
|
||||
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.11.tgz#1cc5750226abe5af2c94f72e975c0c8d2f5cc5a6"
|
||||
integrity sha512-EaDbVgcyiylhtskZZf4Qb/JiiByY7cYbd0qgZ9xm2pm2X7hKojG0P4TaQYKgPOV3vojPhd/pZyQh3nmRkkcSyw==
|
||||
version "1.7.12"
|
||||
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.12.tgz#9ab2baa3c4f632782631e00937f9531a34033619"
|
||||
integrity sha512-FhSx5V+Qp0GvbTpaxyS+ymGDDNntCacClWsk/d8Upbr19g3AsPbjfPk4+m2CgJGcuCB5Dz7LpUIOAbvQTyjL2g==
|
||||
dependencies:
|
||||
client-only "^0.0.1"
|
||||
|
||||
@@ -1568,9 +1568,9 @@ ee-first@1.1.1:
|
||||
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
|
||||
|
||||
electron-to-chromium@^1.4.284:
|
||||
version "1.4.309"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.309.tgz#82158606da135ffffc16a2e1eb853fa0a10955e7"
|
||||
integrity sha512-U7DTiKe4h+irqBG6h4EZ0XXaZuJj4md3xIXXaGSYhwiumPZ4BSc6rgf9UD0hVUMaeP/jB0q5pKWCPxvhO8fvZA==
|
||||
version "1.4.311"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.311.tgz#953bc9a4767f5ce8ec125f9a1ad8e00e8f67e479"
|
||||
integrity sha512-RoDlZufvrtr2Nx3Yx5MB8jX3aHIxm8nRWPJm3yVvyHmyKaRvn90RjzB6hNnt0AkhS3IInJdyRfQb4mWhPvUjVw==
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
|
||||
Reference in New Issue
Block a user