refactor code to improve readability and maintainability + redesigned announcement bar
This commit is contained in:
parent
811628a952
commit
a498f3a10d
|
@ -1 +1,6 @@
|
|||
{}
|
||||
{
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
type Props = {
|
||||
onClick?: Function;
|
||||
label: string;
|
||||
loading?: boolean;
|
||||
className?: string;
|
||||
type?: "button" | "submit" | "reset" | undefined;
|
||||
"data-testid"?: string;
|
||||
};
|
||||
|
||||
export default function AccentSubmitButton({
|
||||
onClick,
|
||||
label,
|
||||
loading,
|
||||
className,
|
||||
type,
|
||||
"data-testid": dataTestId,
|
||||
}: Props) {
|
||||
return (
|
||||
<button
|
||||
type={type ? type : undefined}
|
||||
className={`border primary-btn-gradient select-none duration-200 bg-black border-[oklch(var(--p))] hover:border-[#0070b5] rounded-lg text-center px-4 py-2 text-white active:scale-95 tracking-wider w-fit flex justify-center items-center gap-2 ${
|
||||
className || ""
|
||||
}`}
|
||||
data-testid={dataTestId}
|
||||
onClick={() => {
|
||||
if (loading !== undefined && !loading && onClick) onClick();
|
||||
}}
|
||||
>
|
||||
<p className="font-bold">{label}</p>
|
||||
</button>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import Link from "next/link";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
|
||||
type Props = {
|
||||
toggleAnnouncementBar: MouseEventHandler<HTMLButtonElement>;
|
||||
};
|
||||
|
||||
export default function AnnouncementBar({ toggleAnnouncementBar }: Props) {
|
||||
const announcementId = localStorage.getItem("announcementId");
|
||||
|
||||
return (
|
||||
<div className="fixed left-0 right-0 bottom-20 sm:bottom-10 w-full p-5 z-20">
|
||||
<div className="mx-auto w-full p-2 flex justify-between gap-2 items-center border border-primary shadow-xl rounded-xl bg-base-300 backdrop-blur-sm bg-opacity-80 max-w-md">
|
||||
<i className="bi-stars text-2xl text-yellow-600 dark:text-yellow-500"></i>
|
||||
<p className="w-4/5 text-center text-sm sm:text-base">
|
||||
See what's new in{" "}
|
||||
<Link
|
||||
href={`https://blog.linkwarden.app/releases/${announcementId}`}
|
||||
target="_blank"
|
||||
className="underline"
|
||||
>
|
||||
Linkwarden {announcementId}
|
||||
</Link>
|
||||
!
|
||||
</p>
|
||||
<button
|
||||
onClick={toggleAnnouncementBar}
|
||||
className="btn btn-ghost btn-square btn-sm"
|
||||
>
|
||||
<i className="bi-x text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import Link from "next/link";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
|
||||
type Props = {
|
||||
toggleAnnouncementBar: MouseEventHandler<HTMLButtonElement>;
|
||||
};
|
||||
|
||||
export default function AnnouncementBar({ toggleAnnouncementBar }: Props) {
|
||||
return (
|
||||
<div className="fixed w-full z-20 bg-base-200">
|
||||
<div className="w-full h-10 rainbow flex items-center justify-center">
|
||||
<div className="w-fit font-semibold">
|
||||
🎉️ See what's new in{" "}
|
||||
<Link
|
||||
href="https://blog.linkwarden.app/releases/v2.5"
|
||||
target="_blank"
|
||||
className="underline hover:opacity-50 duration-100"
|
||||
>
|
||||
Linkwarden v2.5
|
||||
</Link>
|
||||
! 🥳️
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="fixed right-3 hover:opacity-50 duration-100"
|
||||
onClick={toggleAnnouncementBar}
|
||||
>
|
||||
<i className="bi-x text-neutral text-2xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import { isPWA } from "@/lib/client/utils";
|
||||
import React, { useState } from "react";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const InstallApp = (props: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
return isOpen && !isPWA() ? (
|
||||
<div className="absolute left-0 right-0 bottom-10 w-full p-5">
|
||||
<div className="mx-auto w-fit p-2 flex justify-between gap-2 items-center border border-neutral-content rounded-xl bg-base-300 backdrop-blur-md bg-opacity-80 max-w-md">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-8 h-8"
|
||||
viewBox="0 0 50 50"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M30.3 13.7L25 8.4l-5.3 5.3l-1.4-1.4L25 5.6l6.7 6.7z"
|
||||
/>
|
||||
<path fill="currentColor" d="M24 7h2v21h-2z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M35 40H15c-1.7 0-3-1.3-3-3V19c0-1.7 1.3-3 3-3h7v2h-7c-.6 0-1 .4-1 1v18c0 .6.4 1 1 1h20c.6 0 1-.4 1-1V19c0-.6-.4-1-1-1h-7v-2h7c1.7 0 3 1.3 3 3v18c0 1.7-1.3 3-3 3"
|
||||
/>
|
||||
</svg>
|
||||
<p className="w-4/5 text-[0.92rem]">
|
||||
Install Linkwarden to your home screen for a faster access and
|
||||
enhanced experience.{" "}
|
||||
<a
|
||||
className="underline"
|
||||
target="_blank"
|
||||
href="https://docs.linkwarden.app/getting-started/pwa-installation"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="btn btn-ghost btn-square btn-sm"
|
||||
>
|
||||
<i className="bi-x text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstallApp;
|
|
@ -4,7 +4,7 @@ import Sidebar from "@/components/Sidebar";
|
|||
import { useRouter } from "next/router";
|
||||
import SearchBar from "@/components/SearchBar";
|
||||
import useWindowDimensions from "@/hooks/useWindowDimensions";
|
||||
import ToggleDarkMode from "./ToggleDarkMode";
|
||||
import ToggleDarkMode from "./ui/ToggleDarkMode";
|
||||
import NewLinkModal from "./ModalContent/NewLinkModal";
|
||||
import NewCollectionModal from "./ModalContent/NewCollectionModal";
|
||||
import UploadFileModal from "./ModalContent/UploadFileModal";
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { cn } from "@/lib/client/utils";
|
||||
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"select-none relative duration-200 rounded-lg text-center w-fit flex justify-center items-center gap-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
intent: {
|
||||
accent:
|
||||
"bg-accent text-white hover:bg-accent/80 border border-violet-400",
|
||||
primary: "bg-primary text-primary-content hover:bg-primary/80",
|
||||
secondary:
|
||||
"bg-neutral-content text-secondary-foreground hover:bg-neutral-content/80 border border-neutral/30",
|
||||
destructive: "bg-error text-white hover:bg-error/80",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-content",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
small: "h-9 px-3",
|
||||
medium: "h-10 px-4 py-2",
|
||||
full: "px-4 py-2 w-full",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
loading: {
|
||||
true: "cursor-wait",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
intent: "primary",
|
||||
size: "medium",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {}
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({
|
||||
className,
|
||||
intent,
|
||||
size,
|
||||
children,
|
||||
disabled,
|
||||
loading = false,
|
||||
...props
|
||||
}) => (
|
||||
<button
|
||||
className={cn(buttonVariants({ intent, size, className }))}
|
||||
disabled={loading || disabled}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
export default Button;
|
|
@ -9,7 +9,6 @@ interface Props {
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER === "true";
|
||||
const stripeEnabled = process.env.NEXT_PUBLIC_STRIPE === "true";
|
||||
|
||||
export default function AuthRedirect({ children }: Props) {
|
||||
|
@ -26,6 +25,8 @@ export default function AuthRedirect({ children }: Props) {
|
|||
const isPublicPage = router.pathname.startsWith("/public");
|
||||
const hasInactiveSubscription =
|
||||
account.id && !account.subscription?.active && stripeEnabled;
|
||||
|
||||
// There are better ways of doing this... but this one works for now
|
||||
const routes = [
|
||||
{ path: "/login", isProtected: false },
|
||||
{ path: "/register", isProtected: false },
|
||||
|
@ -53,9 +54,7 @@ export default function AuthRedirect({ children }: Props) {
|
|||
redirectTo("/dashboard");
|
||||
} else if (
|
||||
isUnauthenticated &&
|
||||
!routes.some(
|
||||
(e) => router.pathname.startsWith(e.path) && !e.isProtected
|
||||
)
|
||||
routes.some((e) => router.pathname.startsWith(e.path) && e.isProtected)
|
||||
) {
|
||||
redirectTo("/login");
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Navbar from "@/components/Navbar";
|
||||
import AnnouncementBar from "@/components/AnnouncementBar";
|
||||
import Announcement from "@/components/Announcement";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import getLatestVersion from "@/lib/client/getLatestVersion";
|
||||
|
@ -33,27 +33,20 @@ export default function MainLayout({ children }: Props) {
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{showAnnouncement ? (
|
||||
<AnnouncementBar toggleAnnouncementBar={toggleAnnouncementBar} />
|
||||
) : undefined}
|
||||
|
||||
<div className="flex" data-testid="dashboard-wrapper">
|
||||
{showAnnouncement ? (
|
||||
<Announcement toggleAnnouncementBar={toggleAnnouncementBar} />
|
||||
) : undefined}
|
||||
<div className="hidden lg:block">
|
||||
<Sidebar
|
||||
className={`fixed ${showAnnouncement ? "top-10" : "top-0"}`}
|
||||
/>
|
||||
<Sidebar className={`fixed top-0`} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`w-full sm:pb-0 pb-20 flex flex-col min-h-${
|
||||
showAnnouncement ? "full" : "screen"
|
||||
} lg:ml-80 ${showAnnouncement ? "mt-10" : ""}`}
|
||||
className={`w-full sm:pb-0 pb-20 flex flex-col min-h-screen lg:ml-80`}
|
||||
>
|
||||
<Navbar />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -129,7 +129,8 @@ export default async function updateUserById(
|
|||
// Verify password
|
||||
if (!user.password) {
|
||||
return {
|
||||
response: "User has no password.",
|
||||
response:
|
||||
"User has no password. Please reset your password from the forgot password page.",
|
||||
status: 400,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,3 +18,7 @@ export function dropdownTriggerer(e: any) {
|
|||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
import clsx, { ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
export const cn = (...classes: ClassValue[]) => twMerge(clsx(...classes));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "linkwarden",
|
||||
"version": "v2.5.4",
|
||||
"version": "v2.6.0",
|
||||
"main": "index.js",
|
||||
"repository": "https://github.com/linkwarden/linkwarden.git",
|
||||
"author": "Daniel31X13 <daniel31x13@gmail.com>",
|
||||
|
@ -36,6 +36,8 @@
|
|||
"axios": "^1.5.1",
|
||||
"bcrypt": "^5.1.0",
|
||||
"bootstrap-icons": "^1.11.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"colorthief": "^2.4.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
|
@ -67,6 +69,7 @@
|
|||
"react-spinners": "^0.13.8",
|
||||
"socks-proxy-agent": "^8.0.2",
|
||||
"stripe": "^12.13.0",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"vaul": "^0.8.8",
|
||||
"zustand": "^4.3.8"
|
||||
},
|
||||
|
|
|
@ -608,6 +608,9 @@ if (process.env.NEXT_PUBLIC_GOOGLE_ENABLED === "true") {
|
|||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||
httpOptions: {
|
||||
timeout: 10000,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -1162,20 +1165,6 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
|
|||
},
|
||||
callbacks: {
|
||||
async signIn({ user, account, profile, email, credentials }) {
|
||||
// console.log(
|
||||
// "User sign in attempt...",
|
||||
// "User",
|
||||
// user,
|
||||
// "Account",
|
||||
// account,
|
||||
// "Profile",
|
||||
// profile,
|
||||
// "Email",
|
||||
// email,
|
||||
// "Credentials",
|
||||
// credentials
|
||||
// );
|
||||
|
||||
if (account?.provider !== "credentials") {
|
||||
// registration via SSO can be separately disabled
|
||||
const existingUser = await prisma.account.findFirst({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import AccentSubmitButton from "@/components/AccentSubmitButton";
|
||||
import AccentSubmitButton from "@/components/ui/Button";
|
||||
import TextInput from "@/components/TextInput";
|
||||
import CenteredForm from "@/layouts/CenteredForm";
|
||||
import Link from "next/link";
|
||||
|
@ -96,10 +96,13 @@ export default function ResetPassword() {
|
|||
|
||||
<AccentSubmitButton
|
||||
type="submit"
|
||||
label="Update Password"
|
||||
className="mt-2 w-full"
|
||||
intent="accent"
|
||||
className="mt-2"
|
||||
size="full"
|
||||
loading={submitLoader}
|
||||
/>
|
||||
>
|
||||
Update Password
|
||||
</AccentSubmitButton>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import AccentSubmitButton from "@/components/AccentSubmitButton";
|
||||
import AccentSubmitButton from "@/components/ui/Button";
|
||||
import TextInput from "@/components/TextInput";
|
||||
import CenteredForm from "@/layouts/CenteredForm";
|
||||
import Link from "next/link";
|
||||
|
@ -88,10 +88,13 @@ export default function Forgot() {
|
|||
|
||||
<AccentSubmitButton
|
||||
type="submit"
|
||||
label="Send Login Link"
|
||||
className="mt-2 w-full"
|
||||
intent="accent"
|
||||
className="mt-2"
|
||||
size="full"
|
||||
loading={submitLoader}
|
||||
/>
|
||||
>
|
||||
Send Login Link
|
||||
</AccentSubmitButton>
|
||||
</>
|
||||
) : (
|
||||
<p>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import AccentSubmitButton from "@/components/AccentSubmitButton";
|
||||
import AccentSubmitButton from "@/components/ui/Button";
|
||||
import TextInput from "@/components/TextInput";
|
||||
import CenteredForm from "@/layouts/CenteredForm";
|
||||
import { signIn } from "next-auth/react";
|
||||
|
@ -7,6 +7,7 @@ import React, { useState, FormEvent } from "react";
|
|||
import { toast } from "react-hot-toast";
|
||||
import { getLogins } from "./api/v1/logins";
|
||||
import { InferGetServerSidePropsType } from "next";
|
||||
import InstallApp from "@/components/InstallApp";
|
||||
|
||||
interface FormData {
|
||||
username: string;
|
||||
|
@ -118,33 +119,42 @@ export default function Login({
|
|||
</div>
|
||||
<AccentSubmitButton
|
||||
type="submit"
|
||||
label="Login"
|
||||
className=" w-full text-center"
|
||||
size="full"
|
||||
intent="accent"
|
||||
data-testid="submit-login-button"
|
||||
loading={submitLoader}
|
||||
/>
|
||||
>
|
||||
Login
|
||||
</AccentSubmitButton>
|
||||
|
||||
{availableLogins.buttonAuths.length > 0 ? (
|
||||
<div className="divider my-1">OR</div>
|
||||
<div className="divider my-1">Or continue with</div>
|
||||
) : undefined}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function displayLoginExternalButton() {
|
||||
const Buttons: any = [];
|
||||
availableLogins.buttonAuths.forEach((value, index) => {
|
||||
Buttons.push(
|
||||
<React.Fragment key={index}>
|
||||
{index !== 0 ? <div className="divider my-1">OR</div> : undefined}
|
||||
{index !== 0 ? <div className="divider my-1">Or</div> : undefined}
|
||||
|
||||
<AccentSubmitButton
|
||||
type="button"
|
||||
onClick={() => loginUserButton(value.method)}
|
||||
label={`Sign in with ${value.name}`}
|
||||
className=" w-full text-center"
|
||||
size="full"
|
||||
intent="secondary"
|
||||
loading={submitLoader}
|
||||
/>
|
||||
>
|
||||
{value.name.toLowerCase() === "google" ||
|
||||
value.name.toLowerCase() === "apple" ? (
|
||||
<i className={"bi-" + value.name.toLowerCase()}></i>
|
||||
) : undefined}
|
||||
{value.name}
|
||||
</AccentSubmitButton>
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
|
@ -178,15 +188,9 @@ export default function Login({
|
|||
{displayLoginCredential()}
|
||||
{displayLoginExternalButton()}
|
||||
{displayRegistration()}
|
||||
<Link
|
||||
href="https://docs.linkwarden.app/getting-started/pwa-installation"
|
||||
className="underline text-center"
|
||||
target="_blank"
|
||||
>
|
||||
You can install Linkwarden onto your device
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
<InstallApp />
|
||||
</CenteredForm>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import Head from "next/head";
|
|||
import useLinks from "@/hooks/useLinks";
|
||||
import useLinkStore from "@/store/links";
|
||||
import ProfilePhoto from "@/components/ProfilePhoto";
|
||||
import ToggleDarkMode from "@/components/ToggleDarkMode";
|
||||
import ToggleDarkMode from "@/components/ui/ToggleDarkMode";
|
||||
import getPublicUserData from "@/lib/client/getPublicUserData";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
@ -118,7 +118,8 @@ export default function PublicCollections() {
|
|||
<div
|
||||
className="h-96"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(${collection?.color}30 10%, ${settings.theme === "dark" ? "#262626" : "#f3f4f6"
|
||||
backgroundImage: `linear-gradient(${collection?.color}30 10%, ${
|
||||
settings.theme === "dark" ? "#262626" : "#f3f4f6"
|
||||
} 13rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import Link from "next/link";
|
||||
import { useState, FormEvent } from "react";
|
||||
import React, { useState, FormEvent } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import CenteredForm from "@/layouts/CenteredForm";
|
||||
import TextInput from "@/components/TextInput";
|
||||
import AccentSubmitButton from "@/components/AccentSubmitButton";
|
||||
import AccentSubmitButton from "@/components/ui/Button";
|
||||
import { getLogins } from "./api/v1/logins";
|
||||
import { InferGetServerSidePropsType } from "next";
|
||||
|
||||
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER === "true";
|
||||
|
||||
|
@ -17,7 +19,14 @@ type FormData = {
|
|||
passwordConfirmation: string;
|
||||
};
|
||||
|
||||
export default function Register() {
|
||||
export const getServerSideProps = () => {
|
||||
const availableLogins = getLogins();
|
||||
return { props: { availableLogins } };
|
||||
};
|
||||
|
||||
export default function Register({
|
||||
availableLogins,
|
||||
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
const [submitLoader, setSubmitLoader] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -98,6 +107,44 @@ export default function Register() {
|
|||
}
|
||||
}
|
||||
|
||||
async function loginUserButton(method: string) {
|
||||
setSubmitLoader(true);
|
||||
|
||||
const load = toast.loading("Authenticating...");
|
||||
|
||||
const res = await signIn(method, {});
|
||||
|
||||
toast.dismiss(load);
|
||||
|
||||
setSubmitLoader(false);
|
||||
}
|
||||
|
||||
function displayLoginExternalButton() {
|
||||
const Buttons: any = [];
|
||||
availableLogins.buttonAuths.forEach((value, index) => {
|
||||
Buttons.push(
|
||||
<React.Fragment key={index}>
|
||||
{index !== 0 ? <div className="divider my-1">Or</div> : undefined}
|
||||
|
||||
<AccentSubmitButton
|
||||
type="button"
|
||||
onClick={() => loginUserButton(value.method)}
|
||||
size="full"
|
||||
intent="secondary"
|
||||
loading={submitLoader}
|
||||
>
|
||||
{value.name.toLowerCase() === "google" ||
|
||||
value.name.toLowerCase() === "apple" ? (
|
||||
<i className={"bi-" + value.name.toLowerCase()}></i>
|
||||
) : undefined}
|
||||
{value.name}
|
||||
</AccentSubmitButton>
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
return Buttons;
|
||||
}
|
||||
|
||||
return (
|
||||
<CenteredForm
|
||||
text={
|
||||
|
@ -236,11 +283,19 @@ export default function Register() {
|
|||
|
||||
<AccentSubmitButton
|
||||
type="submit"
|
||||
label="Sign Up"
|
||||
className="w-full"
|
||||
loading={submitLoader}
|
||||
intent="accent"
|
||||
size="full"
|
||||
data-testid="register-button"
|
||||
/>
|
||||
>
|
||||
Sign Up
|
||||
</AccentSubmitButton>
|
||||
|
||||
{availableLogins.buttonAuths.length > 0 ? (
|
||||
<div className="divider my-1">Or continue with</div>
|
||||
) : undefined}
|
||||
|
||||
{displayLoginExternalButton()}
|
||||
<div className="flex items-baseline gap-1 justify-center">
|
||||
<p className="w-fit text-neutral">Already have an account?</p>
|
||||
<Link
|
||||
|
|
|
@ -4,7 +4,7 @@ import { toast } from "react-hot-toast";
|
|||
import { useRouter } from "next/router";
|
||||
import CenteredForm from "@/layouts/CenteredForm";
|
||||
import { Plan } from "@/types/global";
|
||||
import AccentSubmitButton from "@/components/AccentSubmitButton";
|
||||
import AccentSubmitButton from "@/components/ui/Button";
|
||||
|
||||
export default function Subscribe() {
|
||||
const [submitLoader, setSubmitLoader] = useState(false);
|
||||
|
@ -70,7 +70,7 @@ export default function Subscribe() {
|
|||
>
|
||||
<p>Yearly</p>
|
||||
</button>
|
||||
<div className="absolute -top-3 -right-4 px-1 bg-red-500 text-sm text-white rounded-md rotate-[22deg]">
|
||||
<div className="absolute -top-3 -right-4 px-1 bg-red-600 text-sm text-white rounded-md rotate-[22deg]">
|
||||
25% Off
|
||||
</div>
|
||||
</div>
|
||||
|
@ -98,11 +98,13 @@ export default function Subscribe() {
|
|||
|
||||
<AccentSubmitButton
|
||||
type="button"
|
||||
label="Complete Subscription!"
|
||||
className="w-full"
|
||||
intent="accent"
|
||||
size="full"
|
||||
onClick={submit}
|
||||
loading={submitLoader}
|
||||
/>
|
||||
>
|
||||
Complete Subscription!
|
||||
</AccentSubmitButton>
|
||||
|
||||
<div
|
||||
onClick={() => signOut()}
|
||||
|
|
|
@ -255,32 +255,6 @@
|
|||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.rainbow {
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
#ff00004b,
|
||||
#ff99004b,
|
||||
#33cc334b,
|
||||
#0099cc4b,
|
||||
#9900cc4b,
|
||||
#ff33cc4b
|
||||
);
|
||||
background-size: 400% 400%;
|
||||
animation: rainbow 30s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rainbow {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-file-input::file-selector-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
31
yarn.lock
31
yarn.lock
|
@ -666,6 +666,13 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.24.1":
|
||||
version "7.24.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c"
|
||||
integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/types@^7.18.6":
|
||||
version "7.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.0.tgz#1da00d89c2f18b226c9207d96edbeb79316a1819"
|
||||
|
@ -2551,6 +2558,13 @@ chownr@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
|
||||
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
|
||||
|
||||
class-variance-authority@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.0.tgz#1c3134d634d80271b1837452b06d821915954522"
|
||||
integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==
|
||||
dependencies:
|
||||
clsx "2.0.0"
|
||||
|
||||
client-only@0.0.1, client-only@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
|
||||
|
@ -2565,6 +2579,16 @@ cliui@^8.0.1:
|
|||
strip-ansi "^6.0.1"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
clsx@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b"
|
||||
integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==
|
||||
|
||||
clsx@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
|
@ -5751,6 +5775,13 @@ synckit@^0.8.4:
|
|||
"@pkgr/utils" "^2.3.1"
|
||||
tslib "^2.5.0"
|
||||
|
||||
tailwind-merge@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.3.0.tgz#27d2134fd00a1f77eca22bcaafdd67055917d286"
|
||||
integrity sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.24.1"
|
||||
|
||||
tailwindcss@^3.3.3:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf"
|
||||
|
|
Ŝarĝante…
Reference in New Issue