Skip to content

Commit

Permalink
feat: added last_used in login page (#718)
Browse files Browse the repository at this point in the history
* added last_used

* improved last used

* Update lib/webstorage.ts

* Update lib/webstorage.ts

* Update lib/webstorage.ts

---------

Co-authored-by: Marc Seitz <4049052+mfts@users.noreply.github.com>
  • Loading branch information
RajuGangitla and mfts authored Oct 12, 2024
1 parent 214201d commit 80299d2
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 59 deletions.
145 changes: 86 additions & 59 deletions app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ import Passkey from "@/components/shared/icons/passkey";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { LastUsed, useLastUsed } from "@/components/hooks/useLastUsed";
import { Loader } from "lucide-react";

export default function Login() {
const { next } = useParams as { next?: string };

const [isLoginWithEmail, setIsLoginWithEmail] = useState<boolean>(false);
const [isLoginWithGoogle, setIsLoginWithGoogle] = useState<boolean>(false);
const [isLoginWithLinkedIn, setIsLoginWithLinkedIn] =
useState<boolean>(false);
const [lastUsed, setLastUsed] = useLastUsed();
const authMethods = ["google", "email", "linkedin", "passkey"] as const;
type AuthMethod = (typeof authMethods)[number];
const [clickedMethod, setClickedMethod] = useState<AuthMethod | undefined>(
Expand All @@ -28,6 +36,7 @@ export default function Login() {
"Continue with Email",
);


return (
<div className="flex h-screen w-full flex-wrap">
{/* Left part */}
Expand Down Expand Up @@ -59,6 +68,7 @@ export default function Login() {
}).then((res) => {
if (res?.ok && !res?.error) {
setEmail("");
setLastUsed("credentials")
setEmailButtonText("Email sent - check your inbox!");
toast.success("Email sent - check your inbox!");
} else {
Expand Down Expand Up @@ -97,69 +107,86 @@ export default function Login() {
)}
Continue with Email
</Button> */}
<Button
type="submit"
loading={clickedMethod === "email"}
className={`${
clickedMethod === "email"
<div className="relative">
<Button
type="submit"
loading={clickedMethod === "email"}
className={`${clickedMethod === "email"
? "bg-black"
: "bg-gray-800 hover:bg-gray-900"
} focus:shadow-outline transform rounded px-4 py-2 text-white transition-colors duration-300 ease-in-out focus:outline-none`}
>
{emailButtonText}
</Button>
} w-full focus:shadow-outline transform rounded px-4 py-2 text-white transition-colors duration-300 ease-in-out focus:outline-none`}
>
{emailButtonText}
{lastUsed === "credentials" && <LastUsed />}
</Button>
</div>
</form>
<p className="py-4 text-center">or</p>
<div className="flex flex-col space-y-2 px-4 sm:px-16">
<Button
onClick={() => {
setClickedMethod("google");
signIn("google", {
...(next && next.length > 0 ? { callbackUrl: next } : {}),
}).then((res) => {
if (res?.status) {
setClickedMethod(undefined);
}
});
}}
loading={clickedMethod === "google"}
disabled={clickedMethod && clickedMethod !== "google"}
className="flex items-center justify-center space-x-2 border border-gray-200 bg-gray-100 font-normal text-gray-900 hover:bg-gray-200"
>
<Google className="h-5 w-5" />
<span>Continue with Google</span>
</Button>
<Button
onClick={() => {
setClickedMethod("linkedin");
signIn("linkedin", {
...(next && next.length > 0 ? { callbackUrl: next } : {}),
}).then((res) => {
if (res?.status) {
setClickedMethod(undefined);
}
});
}}
loading={clickedMethod === "linkedin"}
disabled={clickedMethod && clickedMethod !== "linkedin"}
className="flex items-center justify-center space-x-2 border border-gray-200 bg-gray-100 font-normal text-gray-900 hover:bg-gray-200"
>
<LinkedIn />
<span>Continue with LinkedIn</span>
</Button>
<Button
onClick={() => {
signInWithPasskey({
tenantId: process.env.NEXT_PUBLIC_HANKO_TENANT_ID as string,
});
}}
disabled={clickedMethod && clickedMethod !== "passkey"}
variant="outline"
className="flex items-center justify-center space-x-2 border border-gray-200 bg-gray-100 font-normal text-gray-900 hover:bg-gray-200 hover:text-gray-900"
>
<Passkey className="h-4 w-4" />
<span>Continue with a passkey</span>
</Button>
<div className="relative">
<Button
onClick={() => {
setLastUsed("google")
setIsLoginWithGoogle(true);
signIn("google", {
...(next && next.length > 0 ? { callbackUrl: next } : {}),
}).then((res) => {
if (res?.status) {
setIsLoginWithGoogle(false);
}
});
}}
disabled={isLoginWithGoogle}
className="w-full flex items-center justify-center space-x-2 border border-gray-200 bg-gray-100 font-normal text-gray-900 hover:bg-gray-200 "
>
{isLoginWithGoogle ? (
<Loader className="mr-2 h-5 w-5 animate-spin" />
) : (
<Google className="h-5 w-5" />
)}
<span>Continue with Google</span>
{lastUsed === "google" && <LastUsed />}
</Button>
</div>
<div className="relative">
<Button
onClick={() => {
setClickedMethod("linkedin");
signIn("linkedin", {
...(next && next.length > 0 ? { callbackUrl: next } : {}),
}).then((res) => {
if (res?.status) {
setClickedMethod(undefined);
}
});
}}
loading={clickedMethod === "linkedin"}
disabled={clickedMethod && clickedMethod !== "linkedin"}
className="w-full flex items-center justify-center space-x-2 border border-gray-200 bg-gray-100 font-normal text-gray-900 hover:bg-gray-200"
>
<LinkedIn />
<span>Continue with LinkedIn</span>
{lastUsed === "linkedin" && <LastUsed />}
</Button>
</div>
<div className="relative">
<Button
onClick={() => {
setLastUsed("saml")
signInWithPasskey({
tenantId: process.env.NEXT_PUBLIC_HANKO_TENANT_ID as string,
})
}
}
variant="outline"
disabled={clickedMethod && clickedMethod !== "passkey"}
className="w-full flex items-center justify-center space-x-2 border border-gray-200 bg-gray-100 font-normal text-gray-900 hover:bg-gray-200 hover:text-gray-900"
>
<Passkey className="h-4 w-4" />
<span>Continue with a passkey</span>
{lastUsed === "saml" && <LastUsed />}
</Button>
</div>
</div>
<p className="mt-10 w-full max-w-md px-4 text-xs text-muted-foreground sm:px-16">
By clicking continue, you acknowledge that you have read and agree
Expand Down Expand Up @@ -212,6 +239,6 @@ export default function Login() {
</div>
</div>
</div>
</div>
</div >
);
}
42 changes: 42 additions & 0 deletions components/hooks/useLastUsed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { classNames } from "@/lib/utils";
import { localStorage } from "@/lib/webstorage";
import { useState, useEffect } from "react";




type LoginType = "saml" | "google" | "credentials" | "linkedin";

export function useLastUsed() {
const [lastUsed, setLastUsed] = useState<LoginType>();

useEffect(() => {
const storedValue = localStorage.getItem("last_papermark_login");
if (storedValue) {
setLastUsed(storedValue as LoginType);
}
}, []);

useEffect(() => {
if (lastUsed) {
localStorage.setItem("last_papermark_login", lastUsed);
} else {
localStorage.removeItem("last_papermark_login");
}
}, [lastUsed]);

return [lastUsed, setLastUsed] as const;
}

export const LastUsed = ({ className }: { className?: string | undefined }) => {
return (
<>
<div className="absolute left-11 sm:left-1 top-1/2 -translate-y-1/2 -translate-x-full">
<div className={`bg-[#333333] text-white text-xs py-1 px-2 rounded-md relative ${className} z-[999]`}>
Last used
<div className={`absolute -right-1 top-1/2 -translate-y-1/2 w-0 h-0 border-t-4 border-b-4 border-l-4 border-r-0 border-transparent border-l-[#333333] `}></div>
</div>
</div>
</>
);
};
36 changes: 36 additions & 0 deletions lib/webstorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Provides a wrapper around localStorage(and sessionStorage(TODO when needed)) to avoid errors in case of restricted storage access.
*
* TODO: In case of an embed if localStorage is not available(third party), use localStorage of parent(first party) that contains the iframe.
*/
export const localStorage = {
getItem(key: string) {
try {
// eslint-disable-next-line
return window.localStorage.getItem(key);
} catch (e) {
// In case storage is restricted. Possible reasons
// 1. Third Party Context in Chrome Incognito mode.
return null;
}
},
setItem(key: string, value: string) {
try {
// eslint-disable-next-line
window.localStorage.setItem(key, value);
} catch (e) {
// In case storage is restricted. Possible reasons
// 1. Third Party Context in Chrome Incognito mode.
// 2. Storage limit reached
return;
}
},
removeItem: (key: string) => {
try {
// eslint-disable-next-line
window.localStorage.removeItem(key);
} catch (e) {
return;
}
},
};

0 comments on commit 80299d2

Please sign in to comment.