first commit in js
This commit is contained in:
parent
ebfd1ccf59
commit
39aef95de1
|
@ -0,0 +1,36 @@
|
|||
import React from "react"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import { cookies } from "next/headers"
|
||||
import { getUserFromCookie } from "@/lib/auth"
|
||||
import AccountContent from "@/sections/AccountContent"
|
||||
import { redirect } from "next/navigation"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Footer from "@/components/Footer"
|
||||
import Spacer from "@/components/Utilities/Spacer"
|
||||
|
||||
export default async function AccountPage() {
|
||||
const user = await getUserFromCookie(cookies())
|
||||
const cookie = cookies().get("pb_auth")
|
||||
//server side
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
!pb.authStore.isValid && redirect("/")
|
||||
return (
|
||||
user && (
|
||||
<main
|
||||
id="content"
|
||||
role="main"
|
||||
className="h-full flex flex-col min-h-screen mx-auto w-screen overflow-hidden bg-base-100"
|
||||
>
|
||||
<Background className="min-h-screen flex">
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<PageHeader title="Account" subtitle={<></>} />
|
||||
{/* <AccountContent user={user} /> */}
|
||||
<Spacer className="mt-auto mb-auto" />
|
||||
<Footer />
|
||||
</div>
|
||||
</Background>
|
||||
</main>
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
import React from "react";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
import { cookies } from "next/headers";
|
||||
import { getUserFromCookie } from "@/lib/auth";
|
||||
import { User } from "@/types";
|
||||
import AccountContent from "@/sections/AccountContent";
|
||||
import { redirect } from "next/navigation";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import Footer from "@/components/Footer";
|
||||
import Spacer from "@/components/Utilities/Spacer";
|
||||
|
||||
export default async function AccountPage() {
|
||||
const user = (await getUserFromCookie(cookies())) as User;
|
||||
const cookie = cookies().get("pb_auth");
|
||||
//server side
|
||||
pb.authStore.loadFromCookie(cookie?.value || "");
|
||||
!pb.authStore.isValid && redirect("/");
|
||||
return (
|
||||
user && (
|
||||
<main
|
||||
id="content"
|
||||
role="main"
|
||||
className="h-full flex flex-col min-h-screen mx-auto w-screen overflow-hidden bg-base-100"
|
||||
>
|
||||
<Background className="min-h-screen flex">
|
||||
<div className="min-h-screen flex flex-col">
|
||||
|
||||
<PageHeader title="Account" subtitle={<></>} />
|
||||
<AccountContent user={user} />
|
||||
<Spacer className="mt-auto mb-auto" />
|
||||
<Footer />
|
||||
</div>
|
||||
</Background>
|
||||
</main>
|
||||
)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { changeEmailValidationSchema } from "@/utils/form"
|
||||
import { toast } from "react-toastify"
|
||||
import PocketBase from "pocketbase"
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper"
|
||||
import { usePathname } from "next/navigation"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL)
|
||||
|
||||
export default function ConfirmEmailChangePage() {
|
||||
const pathName = usePathname()
|
||||
const token = pathName.split("/").at(-1)
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
reset
|
||||
} = useForm({
|
||||
resolver: yupResolver(changeEmailValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async data => {
|
||||
try {
|
||||
await pb.collection("user").confirmEmailChange(token ?? "", data.password)
|
||||
reset()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error("There was a problem. Please try change your email again", {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader
|
||||
title={"Enter Your Password To Change Your Email"}
|
||||
subtitle={<></>}
|
||||
/>
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full max-w-xl px-4"
|
||||
>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Password…"
|
||||
aria-label="Password…"
|
||||
autoComplete="on"
|
||||
{...register("password")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.password?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-full justify-between">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray" : "btn btn-primary"}
|
||||
>
|
||||
Change Email
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { changeEmailValidationSchema } from "@/utils/form";
|
||||
import { toast } from "react-toastify";
|
||||
import PocketBase from 'pocketbase';
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper";
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL as string);
|
||||
|
||||
export default function ConfirmEmailChangePage() {
|
||||
const pathName = usePathname();
|
||||
const token = pathName.split('/').at(-1);
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
reset
|
||||
} = useForm({
|
||||
resolver: yupResolver(changeEmailValidationSchema),
|
||||
});
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
await pb.collection('user').confirmEmailChange(
|
||||
token ?? "",
|
||||
data.password,
|
||||
);
|
||||
reset();
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error("There was a problem. Please try change your email again", {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader
|
||||
title={"Enter Your Password To Change Your Email"}
|
||||
subtitle={<></>}
|
||||
/>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="w-full max-w-xl px-4">
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Password…"
|
||||
aria-label="Password…"
|
||||
autoComplete="on"
|
||||
{...register("password")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.password?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-full justify-between">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray": "btn btn-primary"}
|
||||
>
|
||||
Change Email
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { passwordValidationSchema } from "@/utils/form"
|
||||
import { toast } from "react-toastify"
|
||||
import PocketBase from "pocketbase"
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper"
|
||||
import { usePathname } from "next/navigation"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL)
|
||||
|
||||
export default function ConfirmPasswordResetPage() {
|
||||
const pathName = usePathname()
|
||||
const token = pathName.split("/").at(-1)
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
reset
|
||||
} = useForm({
|
||||
resolver: yupResolver(passwordValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async data => {
|
||||
try {
|
||||
await pb
|
||||
.collection("user")
|
||||
.confirmPasswordReset(
|
||||
token ?? "",
|
||||
data.newPassword,
|
||||
data.newPasswordConfirm
|
||||
)
|
||||
reset()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(
|
||||
"There was a problem. Please try reset your password again",
|
||||
{
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader title={"Enter Your New Password"} subtitle={<></>} />
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full max-w-xl px-4"
|
||||
>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="New password…"
|
||||
aria-label="New password…"
|
||||
autoComplete="on"
|
||||
{...register("newPassword")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPassword?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Confirm new password…"
|
||||
aria-label="Confirm new password…"
|
||||
autoComplete="on"
|
||||
{...register("newPasswordConfirm")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPasswordConfirm?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-full justify-between">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray" : "btn btn-primary"}
|
||||
>
|
||||
Reset Password
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { passwordValidationSchema } from "@/utils/form";
|
||||
import { toast } from "react-toastify";
|
||||
import PocketBase from 'pocketbase';
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper";
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL as string);
|
||||
|
||||
export default function ConfirmPasswordResetPage() {
|
||||
const pathName = usePathname();
|
||||
const token = pathName.split('/').at(-1);
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
reset
|
||||
} = useForm({
|
||||
resolver: yupResolver(passwordValidationSchema),
|
||||
});
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
await pb.collection('user').confirmPasswordReset(
|
||||
token ?? "",
|
||||
data.newPassword,
|
||||
data.newPasswordConfirm,
|
||||
);
|
||||
reset()
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error("There was a problem. Please try reset your password again", {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader
|
||||
title={"Enter Your New Password"}
|
||||
subtitle={<></>}
|
||||
/>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="w-full max-w-xl px-4">
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="New password…"
|
||||
aria-label="New password…"
|
||||
autoComplete="on"
|
||||
{...register("newPassword")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPassword?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Confirm new password…"
|
||||
aria-label="Confirm new password…"
|
||||
autoComplete="on"
|
||||
{...register("newPasswordConfirm")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPasswordConfirm?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-full justify-between">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray": "btn btn-primary"}
|
||||
>
|
||||
Reset Password
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { passwordValidationSchema } from "@/utils/form"
|
||||
import { toast } from "react-toastify"
|
||||
import PocketBase from "pocketbase"
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper"
|
||||
import { usePathname } from "next/navigation"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL)
|
||||
|
||||
export default function ConfirmPasswordResetPage() {
|
||||
const pathName = usePathname()
|
||||
const token = pathName.split("/").at(-1)
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
reset
|
||||
} = useForm({
|
||||
resolver: yupResolver(passwordValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async data => {
|
||||
try {
|
||||
await pb
|
||||
.collection("user")
|
||||
.confirmPasswordReset(
|
||||
token ?? "",
|
||||
data.newPassword,
|
||||
data.newPasswordConfirm
|
||||
)
|
||||
reset()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(
|
||||
"There was a problem. Please try reset your password again",
|
||||
{
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader title={"Enter Your New Password"} subtitle={<></>} />
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full max-w-xl px-4"
|
||||
>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="New password…"
|
||||
aria-label="New password…"
|
||||
autoComplete="on"
|
||||
{...register("newPassword")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPassword?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Confirm new password…"
|
||||
aria-label="Confirm new password…"
|
||||
autoComplete="on"
|
||||
{...register("newPasswordConfirm")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPasswordConfirm?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-full justify-between">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray" : "btn btn-primary"}
|
||||
>
|
||||
Reset Password
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import PocketBase from 'pocketbase';
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper";
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL as string);
|
||||
|
||||
export default function ConfirmVerification() {
|
||||
const pathName = usePathname();
|
||||
const router = useRouter();
|
||||
const token = pathName.split('/').at(-1);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
await pb.collection('user').confirmVerification(
|
||||
token ?? ""
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error("There was a problem. Please try confirm your email again", {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
}
|
||||
}
|
||||
})()
|
||||
}, [])
|
||||
|
||||
const handleSignInRedirect = () => {
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader
|
||||
title={"Your Email Was Verified"}
|
||||
subtitle={<></>}
|
||||
/>
|
||||
<button onClick={handleSignInRedirect} className="btn btn-primary mt-4">
|
||||
Go to Sign In
|
||||
</button>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import PageHeader from "@/sections/PageHeader";
|
||||
import React from "react";
|
||||
import Footer from "@/components/Footer";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import React from "react"
|
||||
import Footer from "@/components/Footer"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
export default async function About() {
|
||||
return (
|
||||
|
@ -15,20 +15,45 @@ export default async function About() {
|
|||
<PageHeader title="Helping Developers Build" />
|
||||
<div className="max-w-4xl mx-auto mb-auto pb-24 h-full w-full py-12 px-8 flex flex-col gap-y-12 text-center items-center">
|
||||
<p className="text-lg text-base-content font-thin">
|
||||
In 2023 I built <Link href={"https://sign365.com.au"} className="font-bold text-secondary">Sign365</Link> using pocketbase
|
||||
and setup an open source library to help people get setup with <Link href={"https://github.com/mrwyndham/pocketbase-stripe"} className="font-bold text-secondary">Stripe
|
||||
+ Pocketbase</Link>. As 2024 has come around I have had more and more
|
||||
requests for applications and features on the existing code. I built a
|
||||
codebase that would save me 20 hours + in the bootstrapping time to
|
||||
get my applications ready. That is why I had to build <Link href={"https://fastpocket.dev"} className="font-bold text-secondary">FastPocket</Link> an
|
||||
easy solution to give everyone a head start.
|
||||
In 2023 I built{" "}
|
||||
<Link
|
||||
href={"https://sign365.com.au"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
Sign365
|
||||
</Link>{" "}
|
||||
using pocketbase and setup an open source library to help people get
|
||||
setup with{" "}
|
||||
<Link
|
||||
href={"https://github.com/mrwyndham/pocketbase-stripe"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
Stripe + Pocketbase
|
||||
</Link>
|
||||
. As 2024 has come around I have had more and more requests for
|
||||
applications and features on the existing code. I built a codebase
|
||||
that would save me 20 hours + in the bootstrapping time to get my
|
||||
applications ready. That is why I had to build{" "}
|
||||
<Link
|
||||
href={"https://fastpocket.dev"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
FastPocket
|
||||
</Link>{" "}
|
||||
an easy solution to give everyone a head start.
|
||||
</p>
|
||||
<p className="text-lg text-base-content font-thin">
|
||||
After reflecting on the challenges that developers face building their
|
||||
applications, I've seen that there will never be one codebase
|
||||
that suits all developers. But I am sure for those who are
|
||||
opensourcing, self-hosting and want to spin up an application quickly
|
||||
they will be able to do it using <Link href={"https://fastpocket.dev"} className="font-bold text-secondary">FastPocket</Link>
|
||||
they will be able to do it using{" "}
|
||||
<Link
|
||||
href={"https://fastpocket.dev"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
FastPocket
|
||||
</Link>
|
||||
</p>
|
||||
<p className="text-lg text-base-content font-thin">
|
||||
I am taking all of the knowledge that I have gained across 20+
|
||||
|
@ -38,11 +63,23 @@ export default async function About() {
|
|||
that have been battle tested in enterprise code
|
||||
</p>
|
||||
<p className="text-lg text-base-content font-thin">
|
||||
So I've committed to building <Link href={"https://fastpocket.dev"} className="font-bold text-secondary">FastPocket</Link> to help you build your
|
||||
projects faster to get paid.
|
||||
So I've committed to building{" "}
|
||||
<Link
|
||||
href={"https://fastpocket.dev"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
FastPocket
|
||||
</Link>{" "}
|
||||
to help you build your projects faster to get paid.
|
||||
</p>
|
||||
<p className="text-xl text-base-content font-bold">
|
||||
<Link href={"https://fastpocket.dev"} className="font-bold text-secondary">FastPocket</Link> will get you producing more with less development.
|
||||
<Link
|
||||
href={"https://fastpocket.dev"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
FastPocket
|
||||
</Link>{" "}
|
||||
will get you producing more with less development.
|
||||
</p>
|
||||
<p className="text-lg text-base-content font-thin">
|
||||
I know that your next project will grow exponentially because of the
|
||||
|
@ -69,5 +106,5 @@ export default async function About() {
|
|||
</div>
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -1,32 +1,28 @@
|
|||
import BlogContent from "@/sections/BlogContent";
|
||||
import getPostMetadata from "@/utils/getPostMetaData";
|
||||
import { headers } from "next/headers";
|
||||
import React from "react";
|
||||
import Spacer from "@/components/Utilities/Spacer";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import Footer from "@/components/Footer";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import BlogContent from "@/sections/BlogContent"
|
||||
import getPostMetadata from "@/utils/getPostMetaData"
|
||||
import { headers } from "next/headers"
|
||||
import React from "react"
|
||||
import Spacer from "@/components/Utilities/Spacer"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import Footer from "@/components/Footer"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string };
|
||||
}) {
|
||||
const { slug } = params;
|
||||
const headersList = headers();
|
||||
const siteURL = headersList.get("host");
|
||||
pb.autoCancellation(false);
|
||||
export async function generateMetadata({ params }) {
|
||||
const { slug } = params
|
||||
const headersList = headers()
|
||||
const siteURL = headersList.get("host")
|
||||
pb.autoCancellation(false)
|
||||
const post = await pb.collection("blog").getFirstListItem(`slug="${slug}"`, {
|
||||
requestKey: "metaData",
|
||||
});
|
||||
pb.autoCancellation(true);
|
||||
requestKey: "metaData"
|
||||
})
|
||||
pb.autoCancellation(true)
|
||||
|
||||
return {
|
||||
title: `${post.title} | FastPocket`,
|
||||
authors: [
|
||||
{
|
||||
name: post.author || "Samuel Wyndham",
|
||||
},
|
||||
name: post.author || "Samuel Wyndham"
|
||||
}
|
||||
],
|
||||
description: post.description,
|
||||
keywords: post.keywords,
|
||||
|
@ -44,9 +40,9 @@ export async function generateMetadata({
|
|||
width: 1024,
|
||||
height: 576,
|
||||
alt: post.title,
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
type: "image/png"
|
||||
}
|
||||
]
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
|
@ -59,32 +55,32 @@ export async function generateMetadata({
|
|||
url: `${post.imageUrl}`,
|
||||
width: 1024,
|
||||
height: 576,
|
||||
alt: post.title,
|
||||
},
|
||||
],
|
||||
alt: post.title
|
||||
}
|
||||
]
|
||||
},
|
||||
alternates: {
|
||||
canonical: `https://${siteURL}/blogs/${post.slug}`,
|
||||
},
|
||||
};
|
||||
canonical: `https://${siteURL}/blogs/${post.slug}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const generateStaticParams = async () => {
|
||||
const posts = await getPostMetadata();
|
||||
console.log("static posts", posts.length);
|
||||
const mappedPosts = posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}));
|
||||
return mappedPosts;
|
||||
};
|
||||
const posts = await getPostMetadata()
|
||||
console.log("static posts", posts.length)
|
||||
const mappedPosts = posts.map(post => ({
|
||||
slug: post.slug
|
||||
}))
|
||||
return mappedPosts
|
||||
}
|
||||
|
||||
const PostPage = async (props: any) => {
|
||||
console.log("params", props.params);
|
||||
const PostPage = async props => {
|
||||
console.log("params", props.params)
|
||||
const post = await pb
|
||||
.collection("blog")
|
||||
.getFirstListItem(`slug="` + props.params.slug + `"`, {
|
||||
requestKey: "post",
|
||||
});
|
||||
requestKey: "post"
|
||||
})
|
||||
return (
|
||||
<main
|
||||
id="content"
|
||||
|
@ -99,7 +95,7 @@ const PostPage = async (props: any) => {
|
|||
<Footer />
|
||||
</Background>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default PostPage;
|
||||
export default PostPage
|
|
@ -1,17 +1,17 @@
|
|||
import PageHeader from "@/sections/PageHeader";
|
||||
import BlogCard from "@/components/BlogCard";
|
||||
import getPostMetadata from "@/utils/getPostMetaData";
|
||||
import React from "react";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import Footer from "@/components/Footer";
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import BlogCard from "@/components/BlogCard"
|
||||
import getPostMetadata from "@/utils/getPostMetaData"
|
||||
import React from "react"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Footer from "@/components/Footer"
|
||||
|
||||
export default async function BlogsPage() {
|
||||
const postMetadata = await getPostMetadata();
|
||||
const postMetadata = await getPostMetadata()
|
||||
|
||||
console.log("blogs", postMetadata.length);
|
||||
console.log("blogs", postMetadata.length)
|
||||
const postPreviews = postMetadata
|
||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||||
.map((post) => <BlogCard key={post.slug} {...(post as any)} />);
|
||||
.map(post => <BlogCard key={post.slug} {...post} />)
|
||||
return (
|
||||
<main
|
||||
id="content"
|
||||
|
@ -40,5 +40,5 @@ export default async function BlogsPage() {
|
|||
<Footer />
|
||||
</Background>
|
||||
</main>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import Footer from "@/components/Footer";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import Spacer from "@/components/Utilities/Spacer";
|
||||
import FormLeftDescriptionRightContactUs from "@/sections/ContactUs/FormLeftDescriptionRightContactUs";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
import React from "react";
|
||||
import Footer from "@/components/Footer"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Spacer from "@/components/Utilities/Spacer"
|
||||
import FormLeftDescriptionRightContactUs from "@/sections/ContactUs/FormLeftDescriptionRightContactUs"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import React from "react"
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
|
@ -25,7 +25,7 @@ const page = () => {
|
|||
<Footer />
|
||||
</Background>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default page;
|
||||
export default page
|
|
@ -0,0 +1,51 @@
|
|||
"use server"
|
||||
import pb from "@/lib/pocketbase"
|
||||
|
||||
export async function apiPrices() {
|
||||
try {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error("Connection Timeout")
|
||||
}
|
||||
const productRequest = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/product/records?filter=(active=true)`,
|
||||
{
|
||||
cache: "no-cache",
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
)
|
||||
const productResponse = await productRequest.json()
|
||||
console.log("app/pricing/action", "productResponse", productResponse)
|
||||
const unsortedProducts = productResponse.items
|
||||
console.log("app/pricing/action", "unsortedProducts", unsortedProducts)
|
||||
const prices = await pb.collection("price").getFullList()
|
||||
console.log("app/pricing/action", "prices", prices)
|
||||
for (const product of unsortedProducts) {
|
||||
product.metadata.benefits = product?.metadata?.benefits
|
||||
? JSON.parse(product.metadata.benefits)
|
||||
: []
|
||||
const pricesOfProduct = prices.filter(
|
||||
price => price.product_id === product.product_id
|
||||
)
|
||||
for (const priceOfProduct of pricesOfProduct) {
|
||||
product.type = priceOfProduct.type
|
||||
if (priceOfProduct.interval === "year") {
|
||||
product.yearlyPrice = priceOfProduct
|
||||
} else {
|
||||
product.monthlyPrice = priceOfProduct
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sortedProducts = unsortedProducts.sort(
|
||||
(a, b) => a.product_order - b.product_order
|
||||
)
|
||||
return sortedProducts
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return []
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
"use server";
|
||||
|
||||
import { Product, Price } from "@/types";
|
||||
import pb from "@/lib/pocketbase";
|
||||
|
||||
export async function apiPrices() {
|
||||
try {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL;
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error('Connection Timeout');
|
||||
}
|
||||
const productRequest = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/product/records?filter=(active=true)`,
|
||||
{
|
||||
cache: 'no-cache',
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
const productResponse = await productRequest.json();
|
||||
console.log("app/pricing/actions","productResponse",productResponse)
|
||||
const unsortedProducts: Product[] = productResponse.items;
|
||||
console.log("app/pricing/actions","unsortedProducts",unsortedProducts)
|
||||
const prices = await pb.collection("price").getFullList<Price>();
|
||||
console.log("app/pricing/actions","prices",prices)
|
||||
for (const product of unsortedProducts) {
|
||||
product.metadata.benefits = product?.metadata?.benefits ? JSON.parse(product.metadata.benefits as any) : [];
|
||||
const pricesOfProduct = prices.filter(price => price.product_id === product.product_id);
|
||||
for (const priceOfProduct of pricesOfProduct){
|
||||
product.type = priceOfProduct.type;
|
||||
if (priceOfProduct.interval === "year"){
|
||||
product.yearlyPrice = priceOfProduct;
|
||||
} else {
|
||||
product.monthlyPrice = priceOfProduct;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sortedProducts = unsortedProducts.sort((a: Product, b: Product) => a.product_order - b.product_order)
|
||||
return sortedProducts;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
"use client"
|
||||
|
||||
import React from "react";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
import Newsletter from "@/sections/Newsletter/Newsletter";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import Footer from "@/components/Footer";
|
||||
import Payment from "@/sections/Payment";
|
||||
import Spacer from "@/components/Utilities/Spacer";
|
||||
import SolidBackgrondIconFeature from "@/sections/Features/SolidBackgrondIconFeature";
|
||||
import React from "react"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import Newsletter from "@/sections/Newsletter/Newsletter"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Footer from "@/components/Footer"
|
||||
import Payment from "@/sections/Payment"
|
||||
import Spacer from "@/components/Utilities/Spacer"
|
||||
import SolidBackgrondIconFeature from "@/sections/Features/SolidBackgrondIconFeature"
|
||||
|
||||
export default function PricingPage() {
|
||||
return (
|
||||
|
@ -44,5 +44,5 @@ export default function PricingPage() {
|
|||
<Footer />
|
||||
</Background>
|
||||
</main>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
"use client"
|
||||
|
||||
import Footer from "@/components/Footer";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import Spacer from "@/components/Utilities/Spacer";
|
||||
import SolidBackgrondIconFeature from "@/sections/Features/SolidBackgrondIconFeature";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
import React from "react";
|
||||
import Footer from "@/components/Footer"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Spacer from "@/components/Utilities/Spacer"
|
||||
import SolidBackgrondIconFeature from "@/sections/Features/SolidBackgrondIconFeature"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import React from "react"
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
|
@ -28,7 +28,7 @@ const page = () => {
|
|||
<Footer />
|
||||
</Background>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default page;
|
||||
export default page
|
|
@ -0,0 +1,287 @@
|
|||
"use server"
|
||||
import { redirect } from "next/navigation"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { cookies } from "next/headers"
|
||||
import CryptoJS from "crypto-js"
|
||||
|
||||
import { apiPrices } from "./(public)/pricing/action"
|
||||
|
||||
export async function mailchimp(formData) {
|
||||
const email = formData.email
|
||||
const mailchimpBaseUrl = process.env.NEXT_PUBLIC_MAILCHIMP_BASE_URL
|
||||
const mailchimpApiKey = process.env.NEXT_PUBLIC_MAILCHIMP_BASE64_API_KEY
|
||||
const mailchimpList = process.env.NEXT_PUBLIC_MAILCHIMP_LIST_ID
|
||||
if (!mailchimpApiKey) return
|
||||
try {
|
||||
const subscriberHash = CryptoJS.MD5(email.toLocaleLowerCase())
|
||||
const mailchimpResponse = await fetch(
|
||||
`${mailchimpBaseUrl}/3.0/lists/${mailchimpList}/members/${subscriberHash}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: mailchimpApiKey
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email_address: email,
|
||||
status: "subscribed",
|
||||
merge_fields: {
|
||||
EMAIL: formData.email,
|
||||
FNAME: formData.first_name,
|
||||
LNAME: formData.last_name,
|
||||
PHONE: !formData.phone_number ? "" : formData.phone_number,
|
||||
CSIZE: formData.company_size,
|
||||
SOURCE: formData.source
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
if (mailchimpResponse.status !== 200) {
|
||||
throw new Error("couldn't complete the request")
|
||||
}
|
||||
return mailchimpResponse.json()
|
||||
} catch (err) {
|
||||
throw new Error("couldn't complete the request")
|
||||
}
|
||||
}
|
||||
|
||||
export async function signup(formData) {
|
||||
const email = formData.email
|
||||
const password = formData.password
|
||||
const organisation = formData.organisation
|
||||
console.log("app/(authenticated)/actions", "organisation", organisation)
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL
|
||||
const adminToken = process.env.NEXT_PUBLIC_POCKETBASE_ADMIN_TOKEN
|
||||
try {
|
||||
const orgRes = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/organisation/records`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: adminToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: organisation
|
||||
})
|
||||
}
|
||||
)
|
||||
console.log("orgRes.status: ", orgRes.status)
|
||||
if (orgRes.status !== 200) {
|
||||
throw new Error("Failed to create organisation")
|
||||
}
|
||||
const orgData = await orgRes.json()
|
||||
console.log(orgData)
|
||||
const userRes = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/user/records`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: adminToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
firstName: formData.first_name,
|
||||
lastName: formData.last_name,
|
||||
displayName: formData.first_name + " " + formData.last_name,
|
||||
email: email,
|
||||
password: password,
|
||||
passwordConfirm: password,
|
||||
organisation: orgData?.id,
|
||||
role: "Admin",
|
||||
lastSeen: new Date()
|
||||
})
|
||||
}
|
||||
)
|
||||
console.log("userRes.status: ", userRes.status)
|
||||
if (userRes.status !== 200) {
|
||||
console.log(userRes)
|
||||
throw new Error("Failed to create user")
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw new Error(err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function login(formData) {
|
||||
console.log("login")
|
||||
const email = formData.email
|
||||
const password = formData.password
|
||||
try {
|
||||
const { token, record: data } = await pb
|
||||
.collection("user")
|
||||
.authWithPassword(email, password)
|
||||
if (pb.authStore.isValid) {
|
||||
const cookie = pb.authStore.exportToCookie()
|
||||
|
||||
cookies().set("pb_auth", cookie, {
|
||||
secure: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
httpOnly: true
|
||||
})
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
error: "Failed to log in",
|
||||
token: token,
|
||||
data: data
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
if (error.status == 403) {
|
||||
await pb.collection("user").requestVerification(email)
|
||||
}
|
||||
return JSON.parse(JSON.stringify(error))
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAuthCookie() {
|
||||
try {
|
||||
const cookie = cookies().get("pb_auth")
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
return pb.authStore.token
|
||||
} catch (error) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export async function isAuthenticated() {
|
||||
try {
|
||||
const cookie = cookies().get("pb_auth")
|
||||
if (!cookie) return false
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
return pb.authStore.isValid || false
|
||||
} catch (error) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
export async function getUser() {
|
||||
try {
|
||||
const cookie = cookies().get("pb_auth")
|
||||
if (!cookie) return false
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
return pb.authStore.model
|
||||
} catch (error) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
cookies().delete("pb_auth")
|
||||
redirect("/")
|
||||
}
|
||||
|
||||
export async function createCheckoutSession(price_id, type) {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error("Connection Timeout")
|
||||
}
|
||||
if (!price_id) {
|
||||
throw Error("There was an error during the payment processing")
|
||||
}
|
||||
const token = await getAuthCookie()
|
||||
if (!token) {
|
||||
throw Error("Could not authenticate")
|
||||
}
|
||||
console.log("token", token)
|
||||
console.log("url ", `${pocketbaseUrl}/create-checkout-session`)
|
||||
|
||||
const body = JSON.stringify({
|
||||
price: {
|
||||
id: price_id,
|
||||
type: type
|
||||
},
|
||||
quantity: 1
|
||||
})
|
||||
console.log("body", body)
|
||||
|
||||
try {
|
||||
const createCheckoutSessionResponse = await fetch(
|
||||
`${pocketbaseUrl}/create-checkout-session`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: token
|
||||
},
|
||||
body: body
|
||||
}
|
||||
)
|
||||
if (createCheckoutSessionResponse.status !== 200) {
|
||||
throw new Error("Failed to process Request")
|
||||
}
|
||||
|
||||
const createCheckoutSessionData = await createCheckoutSessionResponse.json()
|
||||
|
||||
if (createCheckoutSessionData.url === "") {
|
||||
throw new Error("Failed to process request an invalid URL was served")
|
||||
}
|
||||
return createCheckoutSessionData
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSubscriptions() {
|
||||
const cookie = cookies().get("pb_auth")
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
const userId = pb.authStore.model.id
|
||||
const subscriptions = await pb
|
||||
.collection("subscription")
|
||||
.getFullList({ filter: `user_id="${userId}" && status="active"` })
|
||||
console.log("app/(authenticated)/actions", "subscriptions", subscriptions)
|
||||
if (subscriptions.length === 0) {
|
||||
return []
|
||||
}
|
||||
const products = await apiPrices()
|
||||
const subscriptionWithProducts = subscriptions.map(subscription => {
|
||||
return {
|
||||
...subscription,
|
||||
product: products.find(
|
||||
product =>
|
||||
product?.yearlyPrice?.price_id === subscription.price_id ||
|
||||
product?.monthlyPrice?.price_id === subscription.price_id
|
||||
)
|
||||
}
|
||||
})
|
||||
return subscriptionWithProducts
|
||||
}
|
||||
|
||||
export async function createManagementSubscriptionSession() {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error("Connection Timeout")
|
||||
}
|
||||
const token = await getAuthCookie()
|
||||
if (!token) {
|
||||
throw Error("Could not authenticate")
|
||||
}
|
||||
console.log("token", token)
|
||||
console.log("url ", `${pocketbaseUrl}/create-checkout-session`)
|
||||
try {
|
||||
const createManagementSessionResponse = await fetch(
|
||||
`${pocketbaseUrl}/create-portal-link`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: token
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
}
|
||||
)
|
||||
if (createManagementSessionResponse.status !== 200) {
|
||||
console.log(createManagementSessionResponse.status)
|
||||
throw new Error(
|
||||
JSON.stringify(await createManagementSessionResponse.json())
|
||||
)
|
||||
}
|
||||
const createManagementSessionData = await createManagementSessionResponse.json()
|
||||
return createManagementSessionData
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
|
@ -1,278 +0,0 @@
|
|||
"use server";
|
||||
|
||||
import { redirect } from "next/navigation";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { cookies } from "next/headers";
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
import { CheckoutSession, SignUpForm, SourceModal, Subscription, SubscriptionSession, User } from "@/types";
|
||||
import { apiPrices } from "./(public)/pricing/actions";
|
||||
|
||||
export async function mailchimp(formData: {
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
phone_number?: string;
|
||||
company_size: string;
|
||||
source?: SourceModal;
|
||||
}) {
|
||||
const email = formData.email;
|
||||
const mailchimpBaseUrl = process.env.NEXT_PUBLIC_MAILCHIMP_BASE_URL;
|
||||
const mailchimpApiKey = process.env.NEXT_PUBLIC_MAILCHIMP_BASE64_API_KEY;
|
||||
const mailchimpList = process.env.NEXT_PUBLIC_MAILCHIMP_LIST_ID;
|
||||
if (!mailchimpApiKey) return;
|
||||
try {
|
||||
const subscriberHash = CryptoJS.MD5(email.toLocaleLowerCase());
|
||||
const mailchimpResponse = await fetch(`${mailchimpBaseUrl}/3.0/lists/${mailchimpList}/members/${subscriberHash}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: mailchimpApiKey,
|
||||
},
|
||||
body: JSON.stringify(
|
||||
{
|
||||
"email_address": email,
|
||||
status: "subscribed",
|
||||
merge_fields: {
|
||||
EMAIL: formData.email,
|
||||
FNAME: formData.first_name,
|
||||
LNAME: formData.last_name,
|
||||
PHONE: !formData.phone_number ? '' : formData.phone_number,
|
||||
CSIZE: formData.company_size,
|
||||
SOURCE: formData.source
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
if (mailchimpResponse.status !== 200) {
|
||||
throw new Error("couldn't complete the request");
|
||||
}
|
||||
return mailchimpResponse.json();
|
||||
} catch (err) {
|
||||
throw new Error("couldn't complete the request");
|
||||
}
|
||||
}
|
||||
|
||||
export async function signup(formData: SignUpForm) {
|
||||
const email = formData.email;
|
||||
const password = formData.password;
|
||||
const organisation = formData.organisation;
|
||||
console.log('app/(authenticated)/actions', 'organisation', organisation)
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL as string;
|
||||
const adminToken = process.env.NEXT_PUBLIC_POCKETBASE_ADMIN_TOKEN as string;
|
||||
try {
|
||||
const orgRes = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/organisation/records`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: adminToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: organisation,
|
||||
}),
|
||||
}
|
||||
);
|
||||
console.log("orgRes.status: ", orgRes.status);
|
||||
if (orgRes.status !== 200) {
|
||||
throw new Error("Failed to create organisation");
|
||||
}
|
||||
const orgData = await orgRes.json();
|
||||
console.log(orgData);
|
||||
const userRes = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/user/records`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: adminToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
firstName: formData.first_name,
|
||||
lastName: formData.last_name,
|
||||
displayName: formData.first_name + ' ' + formData.last_name,
|
||||
email: email,
|
||||
password: password,
|
||||
passwordConfirm: password,
|
||||
organisation: orgData?.id,
|
||||
role: "Admin",
|
||||
lastSeen: new Date(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
console.log("userRes.status: ", userRes.status);
|
||||
if (userRes.status !== 200) {
|
||||
console.log(userRes);
|
||||
throw new Error("Failed to create user");
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw new Error(err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function login(formData: { email: string; password: string }) {
|
||||
console.log('login')
|
||||
const email = formData.email as string;
|
||||
const password = formData.password as string;
|
||||
try {
|
||||
const { token, record: data } = await pb
|
||||
.collection("user")
|
||||
.authWithPassword(email, password);
|
||||
if (pb.authStore.isValid) {
|
||||
const cookie = pb.authStore.exportToCookie();
|
||||
|
||||
cookies().set("pb_auth", cookie, {
|
||||
secure: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
httpOnly: true,
|
||||
});
|
||||
}
|
||||
return { success: true, error: "Failed to log in", token: token, data: data };
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
if ((error as any).status == 403) {
|
||||
await pb.collection('user').requestVerification(email);
|
||||
}
|
||||
return JSON.parse(JSON.stringify(error))
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAuthCookie() {
|
||||
try {
|
||||
const cookie = cookies().get('pb_auth');
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
return pb.authStore.token;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function isAuthenticated() {
|
||||
try {
|
||||
const cookie = cookies().get('pb_auth');
|
||||
if(!cookie) return false;
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
return pb.authStore.isValid || false;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
export async function getUser() {
|
||||
try {
|
||||
const cookie = cookies().get('pb_auth');
|
||||
if(!cookie) return false;
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
return pb.authStore.model;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
cookies().delete("pb_auth");
|
||||
redirect('/');
|
||||
}
|
||||
|
||||
export async function createCheckoutSession(price_id: string, type: string) {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL;
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error('Connection Timeout');
|
||||
}
|
||||
if (!price_id) {
|
||||
throw Error('There was an error during the payment processing');
|
||||
}
|
||||
const token = await getAuthCookie();
|
||||
if (!token) {
|
||||
throw Error('Could not authenticate');
|
||||
}
|
||||
console.log('token', token);
|
||||
console.log('url ', `${pocketbaseUrl}/create-checkout-session`);
|
||||
|
||||
const body = JSON.stringify({
|
||||
price: {
|
||||
id: price_id,
|
||||
type: type
|
||||
},
|
||||
quantity: 1
|
||||
});
|
||||
console.log('body',body);
|
||||
|
||||
try{
|
||||
const createCheckoutSessionResponse = await fetch(
|
||||
`${pocketbaseUrl}/create-checkout-session`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: token,
|
||||
},
|
||||
body: body,
|
||||
}
|
||||
);
|
||||
if (createCheckoutSessionResponse.status !== 200) {
|
||||
throw new Error("Failed to process Request");
|
||||
}
|
||||
|
||||
const createCheckoutSessionData: CheckoutSession = await createCheckoutSessionResponse.json();
|
||||
|
||||
if (createCheckoutSessionData.url === "") {
|
||||
throw new Error("Failed to process request an invalid URL was served");
|
||||
}
|
||||
return createCheckoutSessionData;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSubscriptions() {
|
||||
const cookie = cookies().get('pb_auth');
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
const userId = (pb.authStore.model as User).id
|
||||
const subscriptions = await pb.collection("subscription").getFullList<Subscription>({filter: `user_id="${userId}" && status="active"`});
|
||||
console.log('app/(authenticated)/actions', 'subscriptions', subscriptions)
|
||||
if (subscriptions.length === 0){
|
||||
return [];
|
||||
}
|
||||
const products = await apiPrices();
|
||||
const subscriptionWithProducts = subscriptions.map(subscription => {return {...subscription, product: products.find(product => product?.yearlyPrice?.price_id === subscription.price_id || product?.monthlyPrice?.price_id === subscription.price_id)}}) as Subscription[]
|
||||
return subscriptionWithProducts;
|
||||
}
|
||||
|
||||
export async function createManagementSubscriptionSession() {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL;
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error('Connection Timeout');
|
||||
}
|
||||
const token = await getAuthCookie();
|
||||
if (!token) {
|
||||
throw Error('Could not authenticate');
|
||||
}
|
||||
console.log('token', token);
|
||||
console.log('url ', `${pocketbaseUrl}/create-checkout-session`);
|
||||
try{
|
||||
const createManagementSessionResponse = await fetch(
|
||||
`${pocketbaseUrl}/create-portal-link`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: token,
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
}
|
||||
);
|
||||
if (createManagementSessionResponse.status !== 200) {
|
||||
console.log(createManagementSessionResponse.status)
|
||||
throw new Error(JSON.stringify( await createManagementSessionResponse.json()));
|
||||
}
|
||||
const createManagementSessionData: SubscriptionSession = await createManagementSessionResponse.json();
|
||||
return createManagementSessionData;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
|
@ -1,40 +1,36 @@
|
|||
/* eslint-disable @next/next/no-before-interactive-script-outside-document */
|
||||
import "./globals.css";
|
||||
import "../app/globals.css";
|
||||
import { Arimo, Indie_Flower } from "next/font/google";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import Header from "@/components/Header";
|
||||
import { cookies } from "next/headers";
|
||||
import { isAuthenticated } from "@/lib/auth";
|
||||
import { GTagProvider, PHProvider, ThemeProvider } from "./providers";
|
||||
import Script from "next/script";
|
||||
import "./globals.css"
|
||||
import "../app/globals.css"
|
||||
import { Arimo, Indie_Flower } from "next/font/google"
|
||||
import { ToastContainer } from "react-toastify"
|
||||
import "react-toastify/dist/ReactToastify.css"
|
||||
import Header from "@/components/Header"
|
||||
import { cookies } from "next/headers"
|
||||
import { isAuthenticated } from "@/lib/auth"
|
||||
import { GTagProvider, PHProvider, ThemeProvider } from "./providers"
|
||||
import Script from "next/script"
|
||||
|
||||
const raleway = Arimo({
|
||||
variable: "--body-font",
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
display: "swap"
|
||||
})
|
||||
|
||||
const arimo = Arimo({
|
||||
variable: "--body-font",
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
display: "swap"
|
||||
})
|
||||
|
||||
const indieFlower = Indie_Flower({
|
||||
variable: "--accent-font",
|
||||
weight: "400",
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
display: "swap"
|
||||
})
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const isUserLoggedIn = await isAuthenticated(cookies());
|
||||
export default async function RootLayout({ children }) {
|
||||
const isUserLoggedIn = await isAuthenticated(cookies())
|
||||
|
||||
return (
|
||||
<html
|
||||
|
@ -46,7 +42,10 @@ export default async function RootLayout({
|
|||
<GTagProvider />
|
||||
<body className={`${arimo.className} bg-base-100 flex`}>
|
||||
<ThemeProvider>
|
||||
<Header isUserLoggedIn={isUserLoggedIn} authString={cookies().get("pb_auth")?.value || ""} />
|
||||
<Header
|
||||
isUserLoggedIn={isUserLoggedIn}
|
||||
authString={cookies().get("pb_auth")?.value || ""}
|
||||
/>
|
||||
{children}
|
||||
<ToastContainer
|
||||
position="bottom-left"
|
||||
|
@ -76,7 +75,7 @@ export default async function RootLayout({
|
|||
id="promotekit-js"
|
||||
async
|
||||
defer
|
||||
strategy="afterInteractive"
|
||||
strategy="afterInteractive"
|
||||
src="https://cdn.promotekit.com/promotekit.js"
|
||||
data-promotekit="41c8e339-d2aa-414b-88b8-31b6d6346e2b"
|
||||
/>
|
||||
|
@ -84,7 +83,7 @@ export default async function RootLayout({
|
|||
id="promotekit"
|
||||
async
|
||||
defer
|
||||
strategy="afterInteractive"
|
||||
strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
$(document).ready(function(){
|
||||
|
@ -103,11 +102,11 @@ export default async function RootLayout({
|
|||
}, 2000);
|
||||
});
|
||||
|
||||
`,
|
||||
`
|
||||
}}
|
||||
></Script>
|
||||
</body>
|
||||
</PHProvider>
|
||||
</html>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
import Footer from "@/components/Footer";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper";
|
||||
import Link from "next/link";
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
|
@ -70,5 +68,5 @@ export default function NotFound() {
|
|||
</Background>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -1,22 +1,20 @@
|
|||
import "aos/dist/aos.css";
|
||||
import React from "react";
|
||||
import { SquaredBackgroundHero } from "@/sections/Hero";
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper";
|
||||
import Footer from "@/components/Footer";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
import ContainerImageIconBlocksFeature from "@/sections/Features/ContainerImageIconBlocksFeature";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import CardTestemonial from "@/sections/Testemonial/CardTestemonial";
|
||||
import Payment from "@/sections/Payment";
|
||||
import CardsFeature from "@/sections/Features/CardsFeature";
|
||||
import FAQ from "@/sections/FAQ/RightAlignedBorderBottomFAQ";
|
||||
import VerticalTabsFeature from "@/sections/Features/VerticalTabsFeature";
|
||||
import "aos/dist/aos.css"
|
||||
import React from "react"
|
||||
import { SquaredBackgroundHero } from "@/sections/Hero"
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper"
|
||||
import Footer from "@/components/Footer"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import ContainerImageIconBlocksFeature from "@/sections/Features/ContainerImageIconBlocksFeature"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import CardTestemonial from "@/sections/Testemonial/CardTestemonial"
|
||||
import Payment from "@/sections/Payment"
|
||||
import CardsFeature from "@/sections/Features/CardsFeature"
|
||||
import FAQ from "@/sections/FAQ/RightAlignedBorderBottomFAQ"
|
||||
import VerticalTabsFeature from "@/sections/Features/VerticalTabsFeature"
|
||||
|
||||
import { Metadata } from "next";
|
||||
import Head from "next/head";
|
||||
import Teste from "@/sections/Testing/Teste";
|
||||
import Teste from "@/sections/Testing/Teste"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
export const metadata = {
|
||||
title: "Pocketbase, Stripe, Next.js, Boilerplate | FastPocket",
|
||||
description:
|
||||
"FastPocket - Is a boilerplate codebase for everyone to build a product quickly.",
|
||||
|
@ -26,7 +24,7 @@ export const metadata: Metadata = {
|
|||
"next.js",
|
||||
"boilerplate",
|
||||
"template",
|
||||
"codebase",
|
||||
"codebase"
|
||||
],
|
||||
openGraph: {
|
||||
url: "https://fastpocket.dev",
|
||||
|
@ -39,9 +37,9 @@ export const metadata: Metadata = {
|
|||
url: "https://fastpocket.dev/images/home/thumbnail.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "fastpocket.degv",
|
||||
},
|
||||
],
|
||||
alt: "fastpocket.degv"
|
||||
}
|
||||
]
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
|
@ -55,14 +53,14 @@ export const metadata: Metadata = {
|
|||
url: "https://fastpocket.dev/images/home/thumbnail.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "fastpocket.degv",
|
||||
},
|
||||
],
|
||||
alt: "fastpocket.degv"
|
||||
}
|
||||
]
|
||||
},
|
||||
alternates: {
|
||||
canonical: "https://fastpocket.dev",
|
||||
},
|
||||
};
|
||||
canonical: "https://fastpocket.dev"
|
||||
}
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
|
@ -70,8 +68,7 @@ export default function Home() {
|
|||
<PageWrapper>
|
||||
<SquaredBackgroundHero />
|
||||
|
||||
<Teste/>
|
||||
|
||||
<Teste />
|
||||
|
||||
<SquaredBackgroundHero />
|
||||
<VerticalTabsFeature />
|
||||
|
@ -105,5 +102,5 @@ export default function Home() {
|
|||
<Footer />
|
||||
</PageWrapper>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import getPostMetadata from "@/utils/getPostMetaData"
|
||||
|
||||
export default async function sitemap() {
|
||||
const defaultPages = [
|
||||
{
|
||||
url: "https://fastpocket.dev",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "daily",
|
||||
priority: 1
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/about",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/pricing",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/blogs",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/whatsnew",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
}
|
||||
// other pages
|
||||
]
|
||||
|
||||
const postSlugs = await getPostMetadata()
|
||||
|
||||
const sitemap = [
|
||||
...defaultPages,
|
||||
...postSlugs.map(e => ({
|
||||
url: `https://fastpocket.dev/blogs/${e.slug}`,
|
||||
lastModified: e.modified,
|
||||
changeFrequency: "daily",
|
||||
priority: 0.8
|
||||
}))
|
||||
]
|
||||
|
||||
return sitemap
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
import getPostMetadata from "@/utils/getPostMetaData";
|
||||
import { MetadataRoute } from "next";
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const defaultPages = [
|
||||
{
|
||||
url: "https://fastpocket.dev",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "daily",
|
||||
priority: 1
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/about",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/pricing",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/blogs",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/whatsnew",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
}
|
||||
// other pages
|
||||
];
|
||||
|
||||
const postSlugs = await getPostMetadata();
|
||||
|
||||
const sitemap = [
|
||||
...defaultPages,
|
||||
...postSlugs.map((e: any) => ({
|
||||
url: `https://fastpocket.dev/blogs/${e.slug}`,
|
||||
lastModified: e.modified,
|
||||
changeFrequency: "daily",
|
||||
priority: 0.8
|
||||
} as any))
|
||||
];
|
||||
|
||||
return sitemap;
|
||||
}
|
|
@ -1,20 +1,9 @@
|
|||
import React from "react";
|
||||
import React from "react"
|
||||
|
||||
import { PostMetadata } from "@/types";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
function BlogCard({
|
||||
title,
|
||||
slug,
|
||||
subtitle,
|
||||
imageUrl,
|
||||
}: {
|
||||
title: string;
|
||||
slug: string;
|
||||
subtitle: string;
|
||||
imageUrl: string;
|
||||
}) {
|
||||
function BlogCard({ title, slug, subtitle, imageUrl }) {
|
||||
return (
|
||||
<>
|
||||
<Link
|
||||
|
@ -37,7 +26,7 @@ function BlogCard({
|
|||
</div>
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogCard;
|
||||
export default BlogCard
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from "react";
|
||||
const SvgComponent = (props: any) => (
|
||||
import * as React from "react"
|
||||
const SvgComponent = props => (
|
||||
<a href="https://fastpocket.dev">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -43,5 +43,5 @@ const SvgComponent = (props: any) => (
|
|||
/>
|
||||
</svg>
|
||||
</a>
|
||||
);
|
||||
export default SvgComponent;
|
||||
)
|
||||
export default SvgComponent
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import { title } from "@/constants";
|
||||
import Logo from "@/components/Logo";
|
||||
import Link from "next/link";
|
||||
import React from "react"
|
||||
import { title } from "@/constants"
|
||||
import Logo from "@/components/Logo"
|
||||
import Link from "next/link"
|
||||
import FastPocketBadge from "@/components/FastPocketBadge"
|
||||
|
||||
function Footer() {
|
||||
|
@ -253,7 +253,7 @@ function Footer() {
|
|||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
export default Footer
|
|
@ -1,18 +1,13 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Subscription, User } from "@/types";
|
||||
import { getSubscriptions } from "@/app/actions";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface GetStartedSectionButtonProps {
|
||||
user: User;
|
||||
}
|
||||
export const GetStartedSectionButton = ({
|
||||
user,
|
||||
}: GetStartedSectionButtonProps) => {
|
||||
export const GetStartedSectionButton = ({ user }) => {
|
||||
const router = useRouter();
|
||||
const [subscription, setSubscription] = useState<Subscription>();
|
||||
const [subscription, setSubscription] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!user) {
|
||||
|
@ -37,4 +32,4 @@ export const GetStartedSectionButton = ({
|
|||
);
|
||||
};
|
||||
|
||||
export default GetStartedSectionButton;
|
||||
export default GetStartedSectionButton;
|
|
@ -1,6 +1,6 @@
|
|||
import Script from "next/script";
|
||||
import Script from "next/script"
|
||||
|
||||
const GoogleAnalytics = ({ ga_id }: { ga_id: string }) => (
|
||||
const GoogleAnalytics = ({ ga_id }) => (
|
||||
<>
|
||||
<Script
|
||||
async
|
||||
|
@ -20,9 +20,9 @@ const GoogleAnalytics = ({ ga_id }: { ga_id: string }) => (
|
|||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '${ga_id}');
|
||||
`,
|
||||
`
|
||||
}}
|
||||
></Script>
|
||||
</>
|
||||
);
|
||||
export default GoogleAnalytics;
|
||||
)
|
||||
export default GoogleAnalytics
|
|
@ -0,0 +1,28 @@
|
|||
import Script from "next/script"
|
||||
|
||||
const GoogleAnalytics = ({ ga_id }) => (
|
||||
<>
|
||||
<Script
|
||||
async
|
||||
defer
|
||||
strategy="afterInteractive"
|
||||
src={`https://www.googletagmanager.com/gtag/js?
|
||||
id=${ga_id}`}
|
||||
></Script>
|
||||
<Script
|
||||
id="google-analytics"
|
||||
defer
|
||||
strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '${ga_id}');
|
||||
`
|
||||
}}
|
||||
></Script>
|
||||
</>
|
||||
)
|
||||
export default GoogleAnalytics
|
|
@ -1,162 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import Navigation from "@/components/Navigation";
|
||||
import ModalSignUp from "@/components/Modals/ModalSignUp";
|
||||
import ModalSignIn from "@/components/Modals/ModalSignIn";
|
||||
import { getAuthCookie, getUser, logout } from "@/app/actions";
|
||||
import { User } from "@/types";
|
||||
import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { useTheme } from "next-themes";
|
||||
import Link from "next/link";
|
||||
import ModalPasswordReset from "./Modals/ModalPasswordReset";
|
||||
import {Person24Filled, SignOut24Filled} from '@fluentui/react-icons'
|
||||
import ModalEmailChange from "./Modals/ModalEmailChange";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
interface HeaderProps {
|
||||
isUserLoggedIn: boolean;
|
||||
authString: string;
|
||||
}
|
||||
|
||||
export default function Header({ isUserLoggedIn, authString }: HeaderProps) {
|
||||
//Advice: Remove this and replace it with proper state management like Redux, Recoil or Zustand
|
||||
const [user, setUser] = useState<User | undefined>();
|
||||
const [statefulUser, setStatefulUser] = useState<User | undefined>();
|
||||
const pathName = usePathname();
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const user = await getUser();
|
||||
setUser(user as User);
|
||||
})();
|
||||
}, [statefulUser]);
|
||||
|
||||
|
||||
const { theme, setTheme } = useTheme();
|
||||
return (
|
||||
<header className="absolute w-full z-30">
|
||||
<div className="max-w-5xl mx-auto px-4 sm:px-6 md:pt-6">
|
||||
<div className="flex items-center justify-between h-20">
|
||||
<Navigation isUserLoggedIn={isUserLoggedIn} />
|
||||
|
||||
{/* Desktop navigation */}
|
||||
<nav className=" md:flex">
|
||||
{/* Desktop sign in links */}
|
||||
<div className="flex flex-row grow sm:grow-0 items-center pr-2 gap-x-2">
|
||||
{/* <button
|
||||
className="btn btn-primary"
|
||||
onClick={() => setTheme("dark")}
|
||||
>
|
||||
dark
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => setTheme("light")}
|
||||
>
|
||||
light
|
||||
</button> */}
|
||||
<label className="swap swap-rotate" aria-label="theme-toggle">
|
||||
{/* this hidden checkbox controls the state */}
|
||||
<input
|
||||
type="checkbox"
|
||||
className="opacity-0"
|
||||
aria-label="theme-toggle-input"
|
||||
onClick={() =>
|
||||
theme === "light" ? setTheme("dark") : setTheme("light")
|
||||
}
|
||||
/>
|
||||
|
||||
{/* sun icon */}
|
||||
<svg
|
||||
className="swap-on fill-base-content w-6 h-6 z-10"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
|
||||
</svg>
|
||||
|
||||
{/* moon icon */}
|
||||
<svg
|
||||
className="swap-off fill-base-content w-6 h-6 z-1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
|
||||
</svg>
|
||||
</label>
|
||||
|
||||
{!isUserLoggedIn ? (
|
||||
<>
|
||||
<button
|
||||
onClick={() =>
|
||||
(async (router?: AppRouterInstance) => {
|
||||
console.log("firing");
|
||||
const authCookie = await getAuthCookie();
|
||||
pb.authStore.loadFromCookie(authCookie || "");
|
||||
if (!authCookie || !pb.authStore.isValid) {
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
} else {
|
||||
router?.push("/account");
|
||||
}
|
||||
})()
|
||||
}
|
||||
className={`btn btn-sm btn-ghost text-primary mr-1`}
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
localStorage.getItem("price") &&
|
||||
localStorage.removeItem("price");
|
||||
document.getElementById("sign-up-modal")?.click();
|
||||
}}
|
||||
className={`btn btn-primary btn-sm text-primary-content `}
|
||||
id="sign-up-modal-button"
|
||||
>
|
||||
Get First Access
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div className="dropdown dropdown-end">
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
className="btn btn-ghost btn-circle avatar"
|
||||
>
|
||||
<div className="w-6 rounded-full !flex justify-center items-center bg-base-300">
|
||||
<Person24Filled />
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"
|
||||
>
|
||||
<li>
|
||||
<Link href="/account" aria-label="profile">
|
||||
<div className=" capitalize flex flex-row gap-x-2">
|
||||
<Person24Filled />
|
||||
{user?.displayName}
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
<li onClick={() => logout()}>
|
||||
<div className="capitalize flex flex-row gap-x-2">
|
||||
<SignOut24Filled />
|
||||
Logout
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<ModalSignIn />
|
||||
<ModalPasswordReset />
|
||||
<ModalEmailChange authString={authString} />
|
||||
<ModalSignUp setUser={setStatefulUser} />
|
||||
</header>
|
||||
);
|
||||
}
|
|
@ -1,14 +1,8 @@
|
|||
import React from "react";
|
||||
import React from "react"
|
||||
|
||||
import Image, { ImageProps } from "next/image";
|
||||
import Image from "next/image"
|
||||
|
||||
interface LogoProps {
|
||||
label?: string;
|
||||
className?: string;
|
||||
imageProps?: ImageProps;
|
||||
}
|
||||
|
||||
function Logo({ imageProps, ...props }: LogoProps) {
|
||||
function Logo({ imageProps, ...props }) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
|
@ -26,7 +20,7 @@ function Logo({ imageProps, ...props }: LogoProps) {
|
|||
{...imageProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Logo;
|
||||
export default Logo
|
|
@ -1,31 +1,31 @@
|
|||
import React, { useRef } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { emailValidationSchema } from "@/utils/form";
|
||||
import { toast } from "react-toastify";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import {Dismiss20Filled} from "@fluentui/react-icons"
|
||||
import { redirect } from "next/navigation";
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { emailValidationSchema } from "@/utils/form"
|
||||
import { toast } from "react-toastify"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { Dismiss20Filled } from "@fluentui/react-icons"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
function ModalEmailChange({authString}:{authString:string}) {
|
||||
function ModalEmailChange({ authString }) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm({
|
||||
resolver: yupResolver(emailValidationSchema),
|
||||
});
|
||||
resolver: yupResolver(emailValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
console.log(data);
|
||||
pb.authStore.loadFromCookie(authString);
|
||||
!pb.authStore.isValid && redirect("/");
|
||||
const onSubmit = async data => {
|
||||
console.log(data)
|
||||
pb.authStore.loadFromCookie(authString)
|
||||
!pb.authStore.isValid && redirect("/")
|
||||
try {
|
||||
if (await pb.collection("user").requestEmailChange(data.email)) {
|
||||
reset();
|
||||
document.getElementById("email-change-modal")?.click();
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
reset()
|
||||
document.getElementById("email-change-modal")?.click()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
|
@ -37,11 +37,11 @@ function ModalEmailChange({authString}:{authString:string}) {
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
|
@ -49,7 +49,7 @@ function ModalEmailChange({authString}:{authString:string}) {
|
|||
id="email-change-modal"
|
||||
className="modal-toggle"
|
||||
onClick={() => {
|
||||
reset();
|
||||
reset()
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="email-change-modal" className="modal cursor-pointer">
|
||||
|
@ -88,7 +88,7 @@ function ModalEmailChange({authString}:{authString:string}) {
|
|||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray": "btn btn-primary"}
|
||||
className={isSubmitting ? "btn btn-gray" : "btn btn-primary"}
|
||||
>
|
||||
Change Email
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
|
@ -99,7 +99,7 @@ function ModalEmailChange({authString}:{authString:string}) {
|
|||
</label>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalEmailChange;
|
||||
export default ModalEmailChange
|
|
@ -1,30 +1,29 @@
|
|||
import React, { useRef } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { emailValidationSchema } from "@/utils/form";
|
||||
import { toast } from "react-toastify";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import {Dismiss20Filled} from '@fluentui/react-icons'
|
||||
import { redirect } from "next/navigation";
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { emailValidationSchema } from "@/utils/form"
|
||||
import { toast } from "react-toastify"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { Dismiss20Filled } from "@fluentui/react-icons"
|
||||
|
||||
function ModalPasswordReset() {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm({
|
||||
resolver: yupResolver(emailValidationSchema),
|
||||
});
|
||||
resolver: yupResolver(emailValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
console.log(data);
|
||||
const onSubmit = async data => {
|
||||
console.log(data)
|
||||
try {
|
||||
//login user
|
||||
if (await pb.collection("user").requestPasswordReset(data.email)) {
|
||||
reset();
|
||||
document.getElementById("password-reset-modal")?.click();
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
reset()
|
||||
document.getElementById("password-reset-modal")?.click()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
|
@ -36,11 +35,11 @@ function ModalPasswordReset() {
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
|
@ -48,7 +47,7 @@ function ModalPasswordReset() {
|
|||
id="password-reset-modal"
|
||||
className="modal-toggle"
|
||||
onClick={() => {
|
||||
reset();
|
||||
reset()
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="password-reset-modal" className="modal cursor-pointer">
|
||||
|
@ -98,7 +97,7 @@ function ModalPasswordReset() {
|
|||
</label>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalPasswordReset;
|
||||
export default ModalPasswordReset
|
|
@ -1,36 +1,34 @@
|
|||
"use client";
|
||||
"use client"
|
||||
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { signInValidationSchema } from "@/utils/form";
|
||||
import { login } from "@/app/actions";
|
||||
import { toast } from "react-toastify";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {Dismiss20Filled} from '@fluentui/react-icons'
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { signInValidationSchema } from "@/utils/form"
|
||||
import { login } from "@/app/actions"
|
||||
import { toast } from "react-toastify"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Dismiss20Filled } from "@fluentui/react-icons"
|
||||
|
||||
function ModalSignIn() {
|
||||
const router = useRouter();
|
||||
const router = useRouter()
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm({
|
||||
resolver: yupResolver(signInValidationSchema),
|
||||
});
|
||||
resolver: yupResolver(signInValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
const onSubmit = async data => {
|
||||
try {
|
||||
//login user
|
||||
const auth = (await login({ email: data.email, password: data.password }))
|
||||
if (
|
||||
auth.success
|
||||
) {
|
||||
reset();
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
router.push("/account");
|
||||
const auth = await login({ email: data.email, password: data.password })
|
||||
if (auth.success) {
|
||||
reset()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
router.push("/account")
|
||||
} else {
|
||||
throw new Error(auth.response.message)
|
||||
}
|
||||
|
@ -44,11 +42,11 @@ function ModalSignIn() {
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
|
@ -56,7 +54,7 @@ function ModalSignIn() {
|
|||
id="sign-in-modal"
|
||||
className="modal-toggle"
|
||||
onClick={() => {
|
||||
reset();
|
||||
reset()
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="sign-in-modal" className="modal cursor-pointer">
|
||||
|
@ -120,8 +118,8 @@ function ModalSignIn() {
|
|||
|
||||
<span
|
||||
onClick={() => {
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
document.getElementById("sign-up-modal")?.click();
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
document.getElementById("sign-up-modal")?.click()
|
||||
}}
|
||||
className="text-primary hover:text-primary/60 cursor-pointer "
|
||||
>
|
||||
|
@ -143,7 +141,7 @@ function ModalSignIn() {
|
|||
</label>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalSignIn;
|
||||
export default ModalSignIn
|
|
@ -1,39 +1,33 @@
|
|||
"use client";
|
||||
"use client"
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { signUpValidationSchema } from "@/utils/form"
|
||||
import { companySizeList } from "@/constants"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { createCheckoutSession, login } from "@/app/actions"
|
||||
import { toast } from "react-toastify"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Dismiss20Filled } from "@fluentui/react-icons"
|
||||
|
||||
import React, { Dispatch, SetStateAction } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { signUpValidationSchema } from "@/utils/form";
|
||||
import { companySizeList } from "@/constants";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { createCheckoutSession, login } from "@/app/actions";
|
||||
import { toast } from "react-toastify";
|
||||
import { Price, User } from "@/types";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {Dismiss20Filled} from '@fluentui/react-icons'
|
||||
|
||||
function ModalSignUp({
|
||||
setUser,
|
||||
}: {
|
||||
setUser: Dispatch<SetStateAction<User | undefined>>;
|
||||
}) {
|
||||
function ModalSignUp({ setUser }) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm({
|
||||
resolver: yupResolver(signUpValidationSchema),
|
||||
});
|
||||
const router = useRouter();
|
||||
const generateCheckoutPage = async (price: Price, type: string) => {
|
||||
resolver: yupResolver(signUpValidationSchema)
|
||||
})
|
||||
const router = useRouter()
|
||||
const generateCheckoutPage = async (price, type) => {
|
||||
try {
|
||||
const checkoutSessionResponse = await createCheckoutSession(
|
||||
price.price_id,
|
||||
type
|
||||
);
|
||||
console.log(checkoutSessionResponse);
|
||||
router.push(checkoutSessionResponse.url);
|
||||
)
|
||||
console.log(checkoutSessionResponse)
|
||||
router.push(checkoutSessionResponse.url)
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message, {
|
||||
|
@ -44,53 +38,51 @@ function ModalSignUp({
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
const onSubmit = async (data: any) => {
|
||||
}
|
||||
const onSubmit = async data => {
|
||||
data = {
|
||||
emailVisibility: false,
|
||||
lastSeen: new Date(),
|
||||
role: "Admin",
|
||||
displayName: `${data.firstName} ${data.lastName}`,
|
||||
passwordConfirm: data.password,
|
||||
...data,
|
||||
};
|
||||
...data
|
||||
}
|
||||
try {
|
||||
//create organisation
|
||||
const organisation = await pb.collection("organisation").create({
|
||||
name: data.organisation,
|
||||
organisationSize: data.organisationSize,
|
||||
});
|
||||
organisationSize: data.organisationSize
|
||||
})
|
||||
//create user
|
||||
const user = await pb
|
||||
.collection("user")
|
||||
.create({ ...data, organisation: organisation.id });
|
||||
.create({ ...data, organisation: organisation.id })
|
||||
//login user
|
||||
const auth = (await login({ email: data.email, password: data.password }))
|
||||
if (
|
||||
auth.success
|
||||
) {
|
||||
reset();
|
||||
document.getElementById("sign-up-modal")?.click();
|
||||
const price = localStorage.getItem("price");
|
||||
const type = localStorage.getItem("type");
|
||||
setUser(user as User);
|
||||
console.log("price", price);
|
||||
console.log("type", type);
|
||||
const auth = await login({ email: data.email, password: data.password })
|
||||
if (auth.success) {
|
||||
reset()
|
||||
document.getElementById("sign-up-modal")?.click()
|
||||
const price = localStorage.getItem("price")
|
||||
const type = localStorage.getItem("type")
|
||||
setUser(user)
|
||||
console.log("price", price)
|
||||
console.log("type", type)
|
||||
price
|
||||
? generateCheckoutPage(JSON.parse(price), type ?? "")
|
||||
: router.push("/account");
|
||||
: router.push("/account")
|
||||
} else {
|
||||
throw new Error(auth.response.message)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(
|
||||
Object.values((error as any).data.data)
|
||||
.map((x: any) => x.message)
|
||||
Object.values(error.data.data)
|
||||
.map(x => x.message)
|
||||
.join(),
|
||||
{
|
||||
position: "bottom-left",
|
||||
|
@ -100,12 +92,12 @@ function ModalSignUp({
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
theme: "colored"
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
|
@ -114,7 +106,7 @@ function ModalSignUp({
|
|||
className="modal-toggle"
|
||||
name=""
|
||||
onClick={() => {
|
||||
reset();
|
||||
reset()
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="sign-up-modal" className="modal cursor-pointer">
|
||||
|
@ -124,7 +116,7 @@ function ModalSignUp({
|
|||
htmlFor="sign-up-modal"
|
||||
className="cursor-pointer text-base-content"
|
||||
onClick={() => {
|
||||
reset();
|
||||
reset()
|
||||
}}
|
||||
>
|
||||
<Dismiss20Filled />
|
||||
|
@ -225,7 +217,7 @@ function ModalSignUp({
|
|||
>
|
||||
{companySizeOption}
|
||||
</option>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
|
@ -265,8 +257,8 @@ function ModalSignUp({
|
|||
|
||||
<span
|
||||
onClick={() => {
|
||||
document.getElementById("sign-up-modal")?.click();
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
document.getElementById("sign-up-modal")?.click()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
}}
|
||||
className="text-primary hover:text-primary/60 cursor-pointer "
|
||||
>
|
||||
|
@ -279,7 +271,7 @@ function ModalSignUp({
|
|||
</label>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalSignUp;
|
||||
export default ModalSignUp
|
|
@ -1,15 +1,15 @@
|
|||
"use client";
|
||||
"use client"
|
||||
|
||||
import Logo from "@/components/Logo";
|
||||
import React, { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import {Navigation24Filled} from "@fluentui/react-icons"
|
||||
import Logo from "@/components/Logo"
|
||||
import React, { useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { Navigation24Filled } from "@fluentui/react-icons"
|
||||
|
||||
function Navigation({ isUserLoggedIn }: { isUserLoggedIn: boolean }) {
|
||||
const [checked, setChecked] = useState<boolean>();
|
||||
function Navigation({ isUserLoggedIn }) {
|
||||
const [checked, setChecked] = useState()
|
||||
const handleClick = () => {
|
||||
checked ? setChecked(!checked) : setChecked(checked);
|
||||
};
|
||||
checked ? setChecked(!checked) : setChecked(checked)
|
||||
}
|
||||
return (
|
||||
<div className="drawer max-w-fit">
|
||||
<input
|
||||
|
@ -23,10 +23,7 @@ function Navigation({ isUserLoggedIn }: { isUserLoggedIn: boolean }) {
|
|||
{/* Navbar */}
|
||||
<div className="flex-grow navbar ">
|
||||
<div className="flex-none lg:hidden">
|
||||
<label
|
||||
htmlFor="my-drawer-3"
|
||||
className="items-center"
|
||||
>
|
||||
<label htmlFor="my-drawer-3" className="items-center">
|
||||
<Navigation24Filled />
|
||||
</label>
|
||||
</div>
|
||||
|
@ -107,7 +104,7 @@ function Navigation({ isUserLoggedIn }: { isUserLoggedIn: boolean }) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Navigation;
|
||||
export default Navigation
|
|
@ -1,34 +1,24 @@
|
|||
"use client";
|
||||
"use client"
|
||||
import { createCheckoutSession, isAuthenticated } from "@/app/actions"
|
||||
import { toast } from "react-toastify"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { CheckmarkCircle24Filled } from "@fluentui/react-icons"
|
||||
|
||||
import { Price, Product, SourceModal } from "@/types";
|
||||
import { createCheckoutSession, isAuthenticated } from "@/app/actions";
|
||||
import { toast } from "react-toastify";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {CheckmarkCircle24Filled } from "@fluentui/react-icons"
|
||||
|
||||
export default function PriceCard({
|
||||
product,
|
||||
isAnnual,
|
||||
loading,
|
||||
}: {
|
||||
product?: Product;
|
||||
isAnnual?: boolean;
|
||||
loading?: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const openSignUpModalOnPriceClick = (price: Price, type: string) => {
|
||||
const signUpModal = document.getElementById("sign-up-modal");
|
||||
if (!signUpModal) return;
|
||||
signUpModal.click();
|
||||
};
|
||||
const generateCheckoutPage = async (price: Price, type: string) => {
|
||||
export default function PriceCard({ product, isAnnual, loading }) {
|
||||
const router = useRouter()
|
||||
const openSignUpModalOnPriceClick = (price, type) => {
|
||||
const signUpModal = document.getElementById("sign-up-modal")
|
||||
if (!signUpModal) return
|
||||
signUpModal.click()
|
||||
}
|
||||
const generateCheckoutPage = async (price, type) => {
|
||||
try {
|
||||
const checkoutSessionResponse = await createCheckoutSession(
|
||||
price.price_id,
|
||||
type
|
||||
);
|
||||
console.log(checkoutSessionResponse);
|
||||
router.push(checkoutSessionResponse.url);
|
||||
)
|
||||
console.log(checkoutSessionResponse)
|
||||
router.push(checkoutSessionResponse.url)
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message, {
|
||||
|
@ -39,25 +29,25 @@ export default function PriceCard({
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
const submitForm = async (product: Product) => {
|
||||
const userIsAuthenticated = await isAuthenticated();
|
||||
console.log("userIsAuthenticated", userIsAuthenticated);
|
||||
const price = isAnnual ? product.yearlyPrice : product.monthlyPrice;
|
||||
console.log("price", price);
|
||||
console.log("product.type", product.type);
|
||||
localStorage.setItem("price", JSON.stringify(price));
|
||||
localStorage.setItem("type", product.type);
|
||||
}
|
||||
const submitForm = async product => {
|
||||
const userIsAuthenticated = await isAuthenticated()
|
||||
console.log("userIsAuthenticated", userIsAuthenticated)
|
||||
const price = isAnnual ? product.yearlyPrice : product.monthlyPrice
|
||||
console.log("price", price)
|
||||
console.log("product.type", product.type)
|
||||
localStorage.setItem("price", JSON.stringify(price))
|
||||
localStorage.setItem("type", product.type)
|
||||
if (userIsAuthenticated) {
|
||||
await generateCheckoutPage(price, product.type);
|
||||
await generateCheckoutPage(price, product.type)
|
||||
} else {
|
||||
openSignUpModalOnPriceClick(price, product.type);
|
||||
openSignUpModalOnPriceClick(price, product.type)
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={`relative w-64 sm:w-80 bg-base-200 rounded-lg p-6 shadow-lg border border-primary/40 ${
|
||||
|
@ -124,5 +114,5 @@ export default function PriceCard({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
export default function PriceToggle({ isAnnual, onChange }) {
|
||||
return (
|
||||
<>
|
||||
<label className="shadow-card relative inline-flex cursor-pointer select-none items-center justify-center rounded-lg bg-base-100 p-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only"
|
||||
checked={isAnnual}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<span
|
||||
className={`flex items-center space-x-[6px] rounded py-2 px-[18px] text-sm font-medium ${
|
||||
!isAnnual ? " bg-secondary" : "text-secondary-content"
|
||||
}`}
|
||||
>
|
||||
Monthly Billing
|
||||
</span>
|
||||
<span
|
||||
className={`flex items-center space-x-[6px] rounded py-2 px-[18px] text-sm font-medium ${
|
||||
isAnnual ? " bg-secondary" : "text-secondary-content"
|
||||
}`}
|
||||
>
|
||||
Yearly Billing
|
||||
</span>
|
||||
</label>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import { ChangeEventHandler } from "react";
|
||||
|
||||
export default function PriceToggle({
|
||||
isAnnual,
|
||||
onChange,
|
||||
}: {
|
||||
isAnnual: boolean;
|
||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<label className="shadow-card relative inline-flex cursor-pointer select-none items-center justify-center rounded-lg bg-base-100 p-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only"
|
||||
checked={isAnnual}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<span
|
||||
className={`flex items-center space-x-[6px] rounded py-2 px-[18px] text-sm font-medium ${
|
||||
!isAnnual ? " bg-secondary" : "text-secondary-content"
|
||||
}`}
|
||||
>
|
||||
Monthly Billing
|
||||
</span>
|
||||
<span
|
||||
className={`flex items-center space-x-[6px] rounded py-2 px-[18px] text-sm font-medium ${
|
||||
isAnnual ? " bg-secondary" : "text-secondary-content"
|
||||
}`}
|
||||
>
|
||||
Yearly Billing
|
||||
</span>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
"use client"
|
||||
|
||||
import { useTheme } from "next-themes"
|
||||
import React from "react"
|
||||
import Image from "next/image"
|
||||
|
||||
const Background = ({ children, className }) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"h-full relative w-full bg-center bg-no-repeat bg-cover bg-fixed flex flex-col " +
|
||||
className
|
||||
}
|
||||
>
|
||||
<Image
|
||||
src={
|
||||
theme == "dark"
|
||||
? "/images/dark-gradient.webp"
|
||||
: "/images/gradient.webp"
|
||||
}
|
||||
alt="background"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
objectPosition="center"
|
||||
priority
|
||||
/>
|
||||
<div className="z-10">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Background
|
|
@ -1,33 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import React, { ReactNode } from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
const Background = ({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={"h-full relative w-full bg-center bg-no-repeat bg-cover bg-fixed flex flex-col " + className}
|
||||
>
|
||||
<Image
|
||||
src={theme == "dark" ? "/images/dark-gradient.webp": "/images/gradient.webp"}
|
||||
alt="background"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
objectPosition="center"
|
||||
priority
|
||||
/>
|
||||
<div className="z-10">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Background;
|
|
@ -0,0 +1,11 @@
|
|||
import React from "react"
|
||||
|
||||
function PageWrapper({ children }) {
|
||||
return (
|
||||
<main className="h-full flex flex-col my-auto mx-auto size-full flex-grow">
|
||||
{children}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageWrapper
|
|
@ -1,12 +0,0 @@
|
|||
import { WaitingListWithImageHero } from "@/sections/Hero";
|
||||
import React from "react";
|
||||
|
||||
function PageWrapper({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<main className="h-full flex flex-col my-auto mx-auto size-full flex-grow">
|
||||
{children}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageWrapper;
|
|
@ -0,0 +1,7 @@
|
|||
import React from "react"
|
||||
|
||||
const Spacer = props => {
|
||||
return <div {...props}></div>
|
||||
}
|
||||
|
||||
export default Spacer
|
|
@ -1,12 +0,0 @@
|
|||
import React, { ReactHTMLElement } from "react";
|
||||
|
||||
const Spacer = (
|
||||
props: React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>
|
||||
) => {
|
||||
return <div {...props}></div>;
|
||||
};
|
||||
|
||||
export default Spacer;
|
|
@ -1,20 +1,14 @@
|
|||
import React, { useState } from "react";
|
||||
import React from "react"
|
||||
|
||||
import { MediaPlayer, MediaProvider } from "@vidstack/react";
|
||||
import { MediaPlayer, MediaProvider } from "@vidstack/react"
|
||||
import {
|
||||
DefaultAudioLayout,
|
||||
defaultLayoutIcons,
|
||||
DefaultVideoLayout,
|
||||
} from "@vidstack/react/player/layouts/default";
|
||||
import "@vidstack/react/player/styles/base.css";
|
||||
export type Props = {
|
||||
videotitle: string;
|
||||
video: string;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
DefaultVideoLayout
|
||||
} from "@vidstack/react/player/layouts/default"
|
||||
import "@vidstack/react/player/styles/base.css"
|
||||
|
||||
export default function YouTubeFrame({ video, width, height }: Props) {
|
||||
export default function YouTubeFrame({ video, width, height }) {
|
||||
return (
|
||||
<MediaPlayer
|
||||
className="w-full aspect-video bg-slate-900 bg-base-content font-sans overflow-hidden rounded-xl ring-media-focus data-[focus]:ring-4"
|
||||
|
@ -29,5 +23,5 @@ export default function YouTubeFrame({ video, width, height }: Props) {
|
|||
thumbnails="https://media-files.vidstack.io/thumbnails.vtt"
|
||||
/>
|
||||
</MediaPlayer>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -4,6 +4,7 @@ export const companySizeList = [
|
|||
"30-70 employees",
|
||||
"70-100 employees",
|
||||
"100+ employees"
|
||||
]
|
||||
|
||||
export const title = "FastPocket"
|
||||
]
|
||||
|
||||
export const title = "FastPocket"
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import pb from "@/lib/pocketbase"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
export const getUserFromCookie = async cookies => {
|
||||
const cookie = cookies.get("pb_auth")
|
||||
if (!cookie) {
|
||||
redirect("/")
|
||||
//throw new Error("No authenticated user");
|
||||
} else {
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
return pb.authStore.model
|
||||
}
|
||||
}
|
||||
|
||||
export const isAuthenticated = async cookieStore => {
|
||||
const cookie = cookieStore.get("pb_auth")
|
||||
if (!cookie) return false
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
return pb.authStore.isValid || false
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const getUserFromCookie = async (cookies: ReadonlyRequestCookies) => {
|
||||
const cookie = cookies.get('pb_auth');
|
||||
if (!cookie) {
|
||||
redirect('/');
|
||||
//throw new Error("No authenticated user");
|
||||
} else {
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
return pb.authStore.model;
|
||||
}
|
||||
}
|
||||
|
||||
export const isAuthenticated = async (cookieStore: ReadonlyRequestCookies) => {
|
||||
const cookie = cookieStore.get('pb_auth');
|
||||
if(!cookie) return false;
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
return pb.authStore.isValid || false;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { NextResponse } from "next/server"
|
||||
import { isAuthenticated } from "./lib/auth"
|
||||
|
||||
export async function middleware(request) {
|
||||
const { pathname } = request.nextUrl
|
||||
if (
|
||||
pathname.startsWith("/_next") ||
|
||||
pathname.startsWith("/api") ||
|
||||
pathname.startsWith("/static")
|
||||
) {
|
||||
return NextResponse.next()
|
||||
}
|
||||
const isLoggedIn = await isAuthenticated(request.cookies)
|
||||
if (pathname.startsWith("/account")) {
|
||||
if (isLoggedIn) {
|
||||
return NextResponse.next()
|
||||
} else {
|
||||
request.nextUrl.pathname = "/"
|
||||
}
|
||||
return NextResponse.redirect(request.nextUrl)
|
||||
}
|
||||
return NextResponse.next()
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { isAuthenticated } from "./lib/auth";
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
if (
|
||||
pathname.startsWith("/_next") ||
|
||||
pathname.startsWith("/api") ||
|
||||
pathname.startsWith("/static")
|
||||
) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
const isLoggedIn = await isAuthenticated(request.cookies as any);
|
||||
if (pathname.startsWith("/account")) {
|
||||
if (isLoggedIn) {
|
||||
return NextResponse.next();
|
||||
} else {
|
||||
request.nextUrl.pathname = "/"
|
||||
}
|
||||
return NextResponse.redirect(request.nextUrl);
|
||||
}
|
||||
return NextResponse.next();
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useQRCode } from "next-qrcode";
|
||||
import { createManagementSubscriptionSession, getSubscriptions } from "@/app/actions";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { Subscription, User } from "@/types";
|
||||
|
||||
function AccountContent({ user }) {
|
||||
const router = useRouter();
|
||||
const { Canvas } = useQRCode();
|
||||
|
||||
const [subscription, setSubscription] = useState();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!user) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setLoading(true);
|
||||
const subscriptions = await getSubscriptions();
|
||||
if (subscriptions.length > 0) {
|
||||
setSubscription(subscriptions[0]);
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [user]);
|
||||
|
||||
const manageSubscription = async () => {
|
||||
try {
|
||||
const managementSession = await createManagementSubscriptionSession();
|
||||
router.push(managementSession.url);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message, {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return loading ? (
|
||||
<div className="flex flex-col"></div>
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
<div className="max-w-6xl mx-auto mb-12 h-full w-full px-6">
|
||||
<div className="w-full bg-base-200 p-8 rounded text-base-content">
|
||||
<h3 className="font-accent text-xl text-secondary mb-2">
|
||||
Your Subscription
|
||||
</h3>
|
||||
{subscription && subscription.status !== "canceled" ? (
|
||||
<>
|
||||
<h2 className="text-3xl mb-3 font-heading font-bold">
|
||||
{subscription?.product?.name}
|
||||
</h2>
|
||||
<p className="text-lg mb-4">
|
||||
{subscription.product?.description}
|
||||
</p>
|
||||
<button
|
||||
onClick={manageSubscription}
|
||||
className="btn btn-sm text-base-content capitalize !rounded-md bg-secondary hover:bg-secondary/60 w-full sm:w-auto mt-8"
|
||||
>
|
||||
Manage Subscription
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-lg text-base-content mb-4">
|
||||
{"You haven’t upgraded your workflow yet"}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button
|
||||
onClick={() => router.push("/pricing")}
|
||||
className="btn btn-sm btn-neutral text-primary-content capitalize bg-gradient-to-r from-primary to-secondary border-none"
|
||||
>
|
||||
Upgrade
|
||||
</button>
|
||||
<button
|
||||
onClick={manageSubscription}
|
||||
className="btn btn-sm btn-secondary text-primary-content capitalize border-none"
|
||||
>
|
||||
Manage Purchases
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
document.getElementById("password-reset-modal")?.click()
|
||||
}
|
||||
className="btn btn-sm btn-secondary md:ml-auto text-primary-content capitalize border-none"
|
||||
>
|
||||
Reset Password
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
document.getElementById("email-change-modal")?.click()
|
||||
}
|
||||
className="btn btn-sm btn-secondary text-primary-content capitalize border-none"
|
||||
>
|
||||
Change Email
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccountContent;
|
|
@ -1,199 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
createManagementSubscriptionSession,
|
||||
getSubscriptions,
|
||||
} from "@/app/actions";
|
||||
import { Subscription, User } from "@/types";
|
||||
import React from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useQRCode } from "next-qrcode";
|
||||
import pb from "@/lib/pocketbase";
|
||||
|
||||
interface ManageSubscriptionProps {
|
||||
user: User;
|
||||
}
|
||||
|
||||
function AccountContent({ user }: ManageSubscriptionProps) {
|
||||
const router = useRouter();
|
||||
const { Canvas } = useQRCode();
|
||||
|
||||
const [subscription, setSubscription] = useState<Subscription>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!user) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setLoading(true);
|
||||
const subscriptions = await getSubscriptions();
|
||||
if (subscriptions.length > 0) {
|
||||
setSubscription(subscriptions[0]);
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [user]);
|
||||
const manageSubscription = async () => {
|
||||
try {
|
||||
const managementSession = await createManagementSubscriptionSession();
|
||||
router.push(managementSession.url);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message, {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return loading ? (
|
||||
<div className="flex flex-col"></div>
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
{/* //TODO: Create Application Component */}
|
||||
{/* {subscription ? (
|
||||
<div className="max-w-6xl mx-auto h-full w-full py-8 px-6 md:pt-12">
|
||||
<div className="flex flex-row justify-between w-full bg-gray-100 dark:bg-gray-700 p-8 rounded">
|
||||
<div>
|
||||
<h3 className="font-architects-daughter text-xl text-secondary mb-2">
|
||||
Get Started
|
||||
</h3>
|
||||
<ol className="list-decimal ml-6">
|
||||
<li>
|
||||
<p className="text-lg bg-base-content">
|
||||
<a
|
||||
href={downloadApplicationLink}
|
||||
className="text-secondary"
|
||||
>
|
||||
Download the application
|
||||
</a>{" "}
|
||||
by scanning the QR code
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p className="text-lg bg-base-content">
|
||||
<a
|
||||
href={portalWebsiteTemplatesLink}
|
||||
className="text-secondary"
|
||||
>
|
||||
Upload some PDF
|
||||
</a>{" "}
|
||||
Forms you want to fill
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p className="text-lg bg-base-content">
|
||||
Setup some{" "}
|
||||
<a
|
||||
href={portalWebsiteIntegrationsLink}
|
||||
className="text-secondary"
|
||||
>
|
||||
Integrations
|
||||
</a>
|
||||
</p>
|
||||
</li>
|
||||
</ol>
|
||||
<a href={portalWebsite}>
|
||||
<button className="btn btn-sm text-base capitalize !rounded-md bg-base-content bg-bg-gray-925 hover:bg-gray-900 w-full sm:w-auto mt-8">
|
||||
Management Portal
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<Canvas
|
||||
text={"https://github.com/bunlong/next-qrcode"}
|
||||
options={{
|
||||
errorCorrectionLevel: "M",
|
||||
margin: 3,
|
||||
scale: 4,
|
||||
width: 200,
|
||||
color: {
|
||||
dark: "#000000",
|
||||
light: "#ffffff",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)} */}
|
||||
<div className="max-w-6xl mx-auto mb-12 h-full w-full px-6">
|
||||
<div className="w-full bg-base-200 p-8 rounded text-base-content">
|
||||
<h3 className="font-accent text-xl text-secondary mb-2">
|
||||
Your Subscription
|
||||
</h3>
|
||||
{subscription && subscription.status != "canceled" ? (
|
||||
<>
|
||||
<h2 className="text-3xl mb-3 font-heading font-bold">
|
||||
{subscription?.product?.name}
|
||||
</h2>
|
||||
<p className="text-lg mb-4">
|
||||
{subscription.product?.description}
|
||||
</p>
|
||||
<button
|
||||
onClick={manageSubscription}
|
||||
className="btn btn-sm text-base-content capitalize !rounded-md bg-secondary hover:bg-secondary/60 w-full sm:w-auto mt-8"
|
||||
>
|
||||
Manage Subscription
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-lg text-base-content mb-4">
|
||||
{"You haven’t upgraded your workflow yet"}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button
|
||||
onClick={() => router.push("/pricing")}
|
||||
className="btn btn-sm btn-neutral text-primary-content capitalize bg-gradient-to-r from-primary to-secondary border-none"
|
||||
>
|
||||
Upgrade
|
||||
</button>
|
||||
<button
|
||||
onClick={manageSubscription}
|
||||
className="btn btn-sm btn-secondary text-primary-content capitalize border-none"
|
||||
>
|
||||
Manage Purchases
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
document.getElementById("password-reset-modal")?.click()
|
||||
}
|
||||
className="btn btn-sm btn-secondary md:ml-auto text-primary-content capitalize border-none"
|
||||
>
|
||||
Reset Password
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
document.getElementById("email-change-modal")?.click()
|
||||
}
|
||||
className="btn btn-sm btn-secondary text-primary-content capitalize border-none"
|
||||
>
|
||||
Change Email
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccountContent;
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import React from "react"
|
||||
import Image from "next/image"
|
||||
|
||||
function BlogContent({ post }: { post: any }) {
|
||||
function BlogContent({ post }) {
|
||||
return (
|
||||
<div className="lg:mx-0 px-4 lg:px-8 py-12 lg:rounded-lg lg:shadow-lg lg:bg-base-200">
|
||||
<h1 className="font-heading text-4xl font-bold text-base-content">
|
||||
|
@ -30,7 +30,7 @@ function BlogContent({ post }: { post: any }) {
|
|||
display: "block",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
objectFit: "cover"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -40,7 +40,7 @@ function BlogContent({ post }: { post: any }) {
|
|||
></div>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogContent;
|
||||
export default BlogContent
|
|
@ -1,31 +1,25 @@
|
|||
"use client";
|
||||
"use client"
|
||||
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { contactUsValidationSchema } from "@/utils/form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "react-toastify";
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { contactUsValidationSchema } from "@/utils/form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { toast } from "react-toastify"
|
||||
|
||||
const FormLeftDescriptionRightContactUs = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
formState: { errors }
|
||||
} = useForm({
|
||||
resolver: yupResolver(contactUsValidationSchema),
|
||||
});
|
||||
resolver: yupResolver(contactUsValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async (data: {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phoneNumber?: string;
|
||||
note?: string;
|
||||
}) => {
|
||||
const onSubmit = async data => {
|
||||
try {
|
||||
await pb.collection("contact").create({ source: "contactus", ...data });
|
||||
localStorage.setItem("contactus", JSON.stringify(data));
|
||||
await pb.collection("contact").create({ source: "contactus", ...data })
|
||||
localStorage.setItem("contactus", JSON.stringify(data))
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message, {
|
||||
|
@ -36,11 +30,11 @@ const FormLeftDescriptionRightContactUs = () => {
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{/* Contact Us */}
|
||||
|
@ -250,7 +244,7 @@ const FormLeftDescriptionRightContactUs = () => {
|
|||
</div>
|
||||
{/* End Contact Us */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default FormLeftDescriptionRightContactUs;
|
||||
export default FormLeftDescriptionRightContactUs
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React from "react"
|
||||
|
||||
function RightAlignedBorderBottomFAQ() {
|
||||
return (
|
||||
|
@ -31,7 +31,10 @@ function RightAlignedBorderBottomFAQ() {
|
|||
className="w-auto h-auto"
|
||||
defaultChecked
|
||||
/>
|
||||
<label htmlFor="my-tech-stack-is" className="collapse-title text-xl font-medium">
|
||||
<label
|
||||
htmlFor="my-tech-stack-is"
|
||||
className="collapse-title text-xl font-medium"
|
||||
>
|
||||
My tech stack is different can I still use it?
|
||||
</label>
|
||||
<div className="collapse-content">
|
||||
|
@ -51,7 +54,10 @@ function RightAlignedBorderBottomFAQ() {
|
|||
name="my-accordion-3"
|
||||
className="w-auto h-auto"
|
||||
/>
|
||||
<label htmlFor="what-do-i-get" className="collapse-title text-xl font-medium">
|
||||
<label
|
||||
htmlFor="what-do-i-get"
|
||||
className="collapse-title text-xl font-medium"
|
||||
>
|
||||
What do I get exactly?
|
||||
</label>
|
||||
<div className="collapse-content">
|
||||
|
@ -77,7 +83,10 @@ function RightAlignedBorderBottomFAQ() {
|
|||
name="my-accordion-3"
|
||||
className="w-auto h-auto"
|
||||
/>
|
||||
<label htmlFor="is-it-a-website" className="collapse-title text-xl font-medium">
|
||||
<label
|
||||
htmlFor="is-it-a-website"
|
||||
className="collapse-title text-xl font-medium"
|
||||
>
|
||||
Is it a website template?
|
||||
</label>
|
||||
<div className="collapse-content">
|
||||
|
@ -99,7 +108,10 @@ function RightAlignedBorderBottomFAQ() {
|
|||
name="my-accordion-3"
|
||||
className="w-auto h-auto"
|
||||
/>
|
||||
<label htmlFor="are-there-any-other" className="collapse-title text-xl font-medium">
|
||||
<label
|
||||
htmlFor="are-there-any-other"
|
||||
className="collapse-title text-xl font-medium"
|
||||
>
|
||||
Are there any other costs associated
|
||||
</label>
|
||||
<div className="collapse-content">
|
||||
|
@ -119,7 +131,10 @@ function RightAlignedBorderBottomFAQ() {
|
|||
name="my-accordion-3"
|
||||
className="w-auto h-auto"
|
||||
/>
|
||||
<label htmlFor="how-is-fastpocket-different" className="collapse-title text-xl font-medium">
|
||||
<label
|
||||
htmlFor="how-is-fastpocket-different"
|
||||
className="collapse-title text-xl font-medium"
|
||||
>
|
||||
How is FastPocket different from other boilerplates
|
||||
</label>
|
||||
<div className="collapse-content">
|
||||
|
@ -140,7 +155,7 @@ function RightAlignedBorderBottomFAQ() {
|
|||
</div>
|
||||
\{/* End FAQ */}
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default RightAlignedBorderBottomFAQ;
|
||||
export default RightAlignedBorderBottomFAQ
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React from "react"
|
||||
|
||||
function CardsFeature() {
|
||||
return (
|
||||
|
@ -212,7 +212,7 @@ function CardsFeature() {
|
|||
</div>
|
||||
{/* End Icon Blocks */}
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default CardsFeature;
|
||||
export default CardsFeature
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import React from "react"
|
||||
import Image from "next/image"
|
||||
|
||||
function ContainerImageIconBlocksFeature() {
|
||||
return (
|
||||
|
@ -164,7 +164,7 @@ function ContainerImageIconBlocksFeature() {
|
|||
</div>
|
||||
{/* End Features */}
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default ContainerImageIconBlocksFeature;
|
||||
export default ContainerImageIconBlocksFeature
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import { Library32Filled } from "@fluentui/react-icons";
|
||||
import React from "react"
|
||||
import Image from "next/image"
|
||||
import { Library32Filled } from "@fluentui/react-icons"
|
||||
|
||||
function SolidBackgrondIconFeature() {
|
||||
return (
|
||||
|
@ -149,7 +149,7 @@ function SolidBackgrondIconFeature() {
|
|||
</div>
|
||||
{/* End Icon Blocks */}
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default SolidBackgrondIconFeature;
|
||||
export default SolidBackgrondIconFeature
|
|
@ -1,9 +1,9 @@
|
|||
"use client";
|
||||
import Image from "next/image";
|
||||
import React, { useState } from "react";
|
||||
"use client"
|
||||
import Image from "next/image"
|
||||
import React, { useState } from "react"
|
||||
|
||||
function VerticalTabsFeature() {
|
||||
const [tab, setTab] = useState("1");
|
||||
const [tab, setTab] = useState("1")
|
||||
return (
|
||||
<>
|
||||
{/* Features */}
|
||||
|
@ -23,9 +23,8 @@ function VerticalTabsFeature() {
|
|||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`hover:bg-base-content/5 ${
|
||||
tab === "1" && "bg-base-300 shadow-lg hover:bg-base-300"
|
||||
} text-start p-4 md:p-5 rounded-xl`}
|
||||
className={`hover:bg-base-content/5 ${tab === "1" &&
|
||||
"bg-base-300 shadow-lg hover:bg-base-300"} text-start p-4 md:p-5 rounded-xl`}
|
||||
id="tabs-with-card-item-1"
|
||||
aria-controls="tabs-with-card-item-1"
|
||||
role="tab"
|
||||
|
@ -64,9 +63,8 @@ function VerticalTabsFeature() {
|
|||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`hover:bg-base-content/5 ${
|
||||
tab === "2" && "bg-base-300 shadow-lg hover:bg-base-300"
|
||||
} text-start p-4 md:p-5 rounded-xl `}
|
||||
className={`hover:bg-base-content/5 ${tab === "2" &&
|
||||
"bg-base-300 shadow-lg hover:bg-base-300"} text-start p-4 md:p-5 rounded-xl `}
|
||||
id="tabs-with-card-item-2"
|
||||
aria-controls="tabs-with-card-item-2"
|
||||
role="tab"
|
||||
|
@ -102,9 +100,8 @@ function VerticalTabsFeature() {
|
|||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`hover:bg-base-content/5 ${
|
||||
tab === "3" && "bg-base-300 shadow-lg hover:bg-base-300"
|
||||
} text-start p-4 md:p-5 rounded-xl `}
|
||||
className={`hover:bg-base-content/5 ${tab === "3" &&
|
||||
"bg-base-300 shadow-lg hover:bg-base-300"} text-start p-4 md:p-5 rounded-xl `}
|
||||
id="tabs-with-card-item-3"
|
||||
aria-controls="tabs-with-card-item-3"
|
||||
role="tab"
|
||||
|
@ -242,7 +239,7 @@ function VerticalTabsFeature() {
|
|||
</div>
|
||||
{/* End Features */}
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default VerticalTabsFeature;
|
||||
export default VerticalTabsFeature
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import Video from "../Video";
|
||||
import React from "react"
|
||||
import Video from "../Video"
|
||||
|
||||
const CenterAllignedWithVideoHero = () => {
|
||||
return (
|
||||
|
@ -30,7 +30,7 @@ const CenterAllignedWithVideoHero = () => {
|
|||
</div>
|
||||
{/* End Hero */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default CenterAllignedWithVideoHero;
|
||||
export default CenterAllignedWithVideoHero
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React from "react"
|
||||
|
||||
const SimpleHero = () => {
|
||||
return (
|
||||
|
@ -35,7 +35,7 @@ const SimpleHero = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SimpleHero;
|
||||
export default SimpleHero
|
|
@ -1,7 +1,7 @@
|
|||
import Background from "@/components/Utilities/Background";
|
||||
import { title } from "@/constants";
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import { title } from "@/constants"
|
||||
import Image from "next/image"
|
||||
import React from "react"
|
||||
|
||||
const SquaredBackgroundHero = () => {
|
||||
return (
|
||||
|
@ -72,7 +72,7 @@ const SquaredBackgroundHero = () => {
|
|||
</Background>
|
||||
{/* End Hero */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SquaredBackgroundHero;
|
||||
export default SquaredBackgroundHero
|
|
@ -1,34 +1,26 @@
|
|||
"use client";
|
||||
"use client"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { waitinglistValidationSchema } from "@/utils/form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { waitinglistValidationSchema } from "@/utils/form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import Footer from "@/components/Footer";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import { toast } from "react-toastify";
|
||||
import { title } from "@/constants";
|
||||
import Logo from "@/components/Logo";
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import { toast } from "react-toastify"
|
||||
|
||||
const WaitingListWithImageHero = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm({
|
||||
resolver: yupResolver(waitinglistValidationSchema),
|
||||
});
|
||||
resolver: yupResolver(waitinglistValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async (data: {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
}) => {
|
||||
const onSubmit = async data => {
|
||||
try {
|
||||
await pb.collection("contact").create({ source: "waitinglist", ...data });
|
||||
localStorage.setItem("waitinglist", JSON.stringify(data));
|
||||
await pb.collection("contact").create({ source: "waitinglist", ...data })
|
||||
localStorage.setItem("waitinglist", JSON.stringify(data))
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message, {
|
||||
|
@ -39,11 +31,11 @@ const WaitingListWithImageHero = () => {
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<Background>
|
||||
<div className="h-screen w-full bg-clip flex items-center justify-center pt-20">
|
||||
|
@ -211,7 +203,7 @@ const WaitingListWithImageHero = () => {
|
|||
</div>
|
||||
</div>
|
||||
</Background>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default WaitingListWithImageHero;
|
||||
export default WaitingListWithImageHero
|
|
@ -0,0 +1,8 @@
|
|||
import SquaredBackgroundHero from "./SquaredBackgroundHero"
|
||||
import CenterAllignedWithVideoHero from "./CenterAllignedWithVideoHero"
|
||||
import WaitingListWithImageHero from "./WaitingListWithImageHero"
|
||||
export {
|
||||
SquaredBackgroundHero,
|
||||
CenterAllignedWithVideoHero,
|
||||
WaitingListWithImageHero
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import SquaredBackgroundHero from "./SquaredBackgroundHero";
|
||||
import CenterAllignedWithVideoHero from "./CenterAllignedWithVideoHero";
|
||||
import WaitingListWithImageHero from "./WaitingListWithImageHero";
|
||||
export {
|
||||
SquaredBackgroundHero,
|
||||
CenterAllignedWithVideoHero,
|
||||
WaitingListWithImageHero
|
||||
};
|
|
@ -1,96 +1,96 @@
|
|||
"use client";
|
||||
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { emailValidationSchema } from "@/utils/form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
function Newsletter() {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: yupResolver(emailValidationSchema),
|
||||
});
|
||||
|
||||
const onSubmit = async (data: { email: string }) => {
|
||||
try {
|
||||
await pb.collection("contact").create({ source: "newsletter", ...data });
|
||||
localStorage.setItem("newsletter", JSON.stringify(data));
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message, {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
{/* CTA box */}
|
||||
<div
|
||||
className="relative bg-gradient-to-r from-primary to-secondary py-10 px-8 md:py-16 md:px-12"
|
||||
data-aos="fade-up"
|
||||
>
|
||||
{typeof window !== "undefined" &&
|
||||
!localStorage.getItem("newsletter") ? (
|
||||
<div className="relative flex flex-col lg:flex-row justify-between items-center">
|
||||
<div className="mb-6 lg:mr-16 lg:mb-0 text-center lg:text-left lg:w-1/2 text-primary-content">
|
||||
<h3 className=" mb-2 text-3xl font-black">
|
||||
Stay Ahead of the Curve
|
||||
</h3>
|
||||
<p className=" text-lg">
|
||||
Join our newsletter to get top news before anyone else.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-1/2">
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="flex flex-col sm:flex-row justify-center max-w-xs mx-auto sm:max-w-md lg:max-w-none gap-x-2"
|
||||
>
|
||||
<div className="w-full">
|
||||
<input
|
||||
id="NewsletterEmail"
|
||||
type="text"
|
||||
className="py-3 px-4 block w-full text-base-content border-white rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Email…"
|
||||
aria-label="Email…"
|
||||
{...register("email")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.email?.message}
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn text-primary-content btn-neutral">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative flex flex-col lg:flex-row justify-between items-center text-primary-content">
|
||||
<h3 className=" mb-2 text-3xl font-black">
|
||||
Thanks for subscribing. You won't regret it!
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default Newsletter;
|
||||
"use client"
|
||||
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { emailValidationSchema } from "@/utils/form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { toast } from "react-toastify"
|
||||
|
||||
function Newsletter() {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors }
|
||||
} = useForm({
|
||||
resolver: yupResolver(emailValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async data => {
|
||||
try {
|
||||
await pb.collection("contact").create({ source: "newsletter", ...data })
|
||||
localStorage.setItem("newsletter", JSON.stringify(data))
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message, {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
{/* CTA box */}
|
||||
<div
|
||||
className="relative bg-gradient-to-r from-primary to-secondary py-10 px-8 md:py-16 md:px-12"
|
||||
data-aos="fade-up"
|
||||
>
|
||||
{typeof window !== "undefined" &&
|
||||
!localStorage.getItem("newsletter") ? (
|
||||
<div className="relative flex flex-col lg:flex-row justify-between items-center">
|
||||
<div className="mb-6 lg:mr-16 lg:mb-0 text-center lg:text-left lg:w-1/2 text-primary-content">
|
||||
<h3 className=" mb-2 text-3xl font-black">
|
||||
Stay Ahead of the Curve
|
||||
</h3>
|
||||
<p className=" text-lg">
|
||||
Join our newsletter to get top news before anyone else.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-1/2">
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="flex flex-col sm:flex-row justify-center max-w-xs mx-auto sm:max-w-md lg:max-w-none gap-x-2"
|
||||
>
|
||||
<div className="w-full">
|
||||
<input
|
||||
id="NewsletterEmail"
|
||||
type="text"
|
||||
className="py-3 px-4 block w-full text-base-content border-white rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Email…"
|
||||
aria-label="Email…"
|
||||
{...register("email")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.email?.message}
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn text-primary-content btn-neutral">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative flex flex-col lg:flex-row justify-between items-center text-primary-content">
|
||||
<h3 className=" mb-2 text-3xl font-black">
|
||||
Thanks for subscribing. You won't regret it!
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default Newsletter
|
|
@ -1,11 +1,6 @@
|
|||
import React, { ReactNode } from "react";
|
||||
import React from "react"
|
||||
|
||||
interface PageHeaderProps {
|
||||
title: string;
|
||||
subtitle?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
function PageHeader({ title, subtitle, className }: PageHeaderProps) {
|
||||
function PageHeader({ title, subtitle, className }) {
|
||||
return (
|
||||
<div className={` pt-48 max-w-screen flex flex-col ${className}`}>
|
||||
<h1 className="text-4xl md:text-5xl text-center font-bold text-base-content mb-6 mx-auto px-4">
|
||||
|
@ -13,7 +8,7 @@ function PageHeader({ title, subtitle, className }: PageHeaderProps) {
|
|||
</h1>
|
||||
{subtitle}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default PageHeader;
|
||||
export default PageHeader
|
|
@ -1,19 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import { Product } from "@/types";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import PriceCard from "@/components/PriceCard";
|
||||
import { useState } from "react";
|
||||
import PriceToggle from "@/components/PriceToggle";
|
||||
import { apiPrices } from "../app/(public)/pricing/actions";
|
||||
import { apiPrices } from "../app/(public)/pricing/action";
|
||||
|
||||
const Payment = ({
|
||||
type = "one_time",
|
||||
}: {
|
||||
type?: "one_time" | "reoccuring";
|
||||
}) => {
|
||||
const [isAnnual, setIsAnnual] = useState(false);
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
const [products, setProducts] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const handleToggle = () => {
|
||||
setIsAnnual((prev) => !prev);
|
||||
|
@ -21,7 +17,7 @@ const Payment = ({
|
|||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
(async () => {
|
||||
const resposeProducts: Product[] = await apiPrices();
|
||||
const resposeProducts = await apiPrices();
|
||||
setProducts(resposeProducts);
|
||||
setIsLoading(false);
|
||||
})();
|
||||
|
@ -39,17 +35,17 @@ const Payment = ({
|
|||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<PriceCard loading={isLoading} />
|
||||
<PriceCard loading={isLoading} />
|
||||
<PriceCard loading={isLoading} />
|
||||
<PriceCard loading={isLoading} product={undefined} isAnnual={undefined} />
|
||||
<PriceCard loading={isLoading} product={undefined} isAnnual={undefined} />
|
||||
<PriceCard loading={isLoading} product={undefined} isAnnual={undefined} />
|
||||
</>
|
||||
) : (
|
||||
(products ?? [])
|
||||
(products || [])
|
||||
.filter((x) =>
|
||||
type == "one_time" ? x.type == "one_time" : x.type != "one_time"
|
||||
)
|
||||
.map((x, i) => (
|
||||
<PriceCard key={i} product={x} isAnnual={isAnnual} />
|
||||
<PriceCard key={i} product={x} isAnnual={isAnnual} loading={undefined} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
@ -59,3 +55,4 @@ const Payment = ({
|
|||
};
|
||||
|
||||
export default Payment;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import React from "react"
|
||||
import Image from "next/image"
|
||||
|
||||
function CardTestemonial() {
|
||||
return (
|
||||
|
@ -61,7 +61,7 @@ function CardTestemonial() {
|
|||
</div>
|
||||
{/* End Testimonials */}
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default CardTestemonial;
|
||||
export default CardTestemonial
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React from "react"
|
||||
|
||||
export const Testimonial = () => {
|
||||
return (
|
||||
|
@ -36,5 +36,5 @@ export const Testimonial = () => {
|
|||
</footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
function Teste() {
|
||||
return (
|
||||
<>
|
||||
{/* <!-- Card Blog --> */}
|
||||
<div className="max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto">
|
||||
{/* <!-- Grid --> */}
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{/* <!-- Card --> */}
|
||||
<div className="group flex flex-col h-full bg-base-100 border border-base-200 shadow-sm rounded-xl">
|
||||
<div className="h-52 flex flex-col justify-center items-center bg-primary rounded-t-xl">
|
||||
<svg
|
||||
className="size-28"
|
||||
width="56"
|
||||
height="56"
|
||||
viewBox="0 0 56 56"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="56" height="56" rx="10" fill="base-100" />
|
||||
<path
|
||||
d="M20.2819 26.7478C20.1304 26.5495 19.9068 26.4194 19.6599 26.386C19.4131 26.3527 19.1631 26.4188 18.9647 26.5698C18.848 26.6622 18.7538 26.78 18.6894 26.9144L10.6019 43.1439C10.4874 43.3739 10.4686 43.6401 10.5496 43.884C10.6307 44.1279 10.805 44.3295 11.0342 44.4446C11.1681 44.5126 11.3163 44.5478 11.4664 44.5473H22.7343C22.9148 44.5519 23.0927 44.5037 23.2462 44.4084C23.3998 44.3132 23.5223 44.1751 23.5988 44.011C26.0307 38.9724 24.5566 31.3118 20.2819 26.7478Z"
|
||||
fill="url(#paint0_linear_2204_541)"
|
||||
/>
|
||||
<path
|
||||
d="M28.2171 11.9791C26.201 15.0912 25.026 18.6755 24.8074 22.3805C24.5889 26.0854 25.3342 29.7837 26.9704 33.1126L32.403 44.0113C32.4833 44.1724 32.6067 44.3079 32.7593 44.4026C32.912 44.4973 33.088 44.5475 33.2675 44.5476H44.5331C44.6602 44.5479 44.7861 44.523 44.9035 44.4743C45.0209 44.4257 45.1276 44.3543 45.2175 44.2642C45.3073 44.1741 45.3785 44.067 45.427 43.9492C45.4755 43.8314 45.5003 43.7052 45.5 43.5777C45.5001 43.4274 45.4659 43.2791 45.3999 43.1441L29.8619 11.9746C29.7881 11.8184 29.6717 11.6864 29.5261 11.594C29.3805 11.5016 29.2118 11.4525 29.0395 11.4525C28.8672 11.4525 28.6984 11.5016 28.5529 11.594C28.4073 11.6864 28.2908 11.8184 28.2171 11.9746V11.9791Z"
|
||||
fill="#2684FF"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_2204_541"
|
||||
x1="24.734"
|
||||
y1="29.2284"
|
||||
x2="16.1543"
|
||||
y2="44.0429"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#0052CC" />
|
||||
<stop offset="0.92" stop-color="#2684FF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="p-4 md:p-6">
|
||||
<span className="block mb-1 text-xs font-semibold uppercase text-primary dark:text-primary">
|
||||
Atlassian API
|
||||
</span>
|
||||
<h3 className="text-xl font-semibold text-secondary-content">
|
||||
Atlassian
|
||||
</h3>
|
||||
<p className="mt-3 text-secondary-content">
|
||||
A software that develops products for software developers and
|
||||
developments.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-auto flex border-t border-base-200 divide-x divide-base-200">
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-es-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View sample
|
||||
</a>
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-ee-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View API
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/* <!-- End Card --> */}
|
||||
|
||||
{/* <!-- Card --> */}
|
||||
<div className="group flex flex-col h-full bg-base-100 border border-base-200 shadow-sm rounded-xl">
|
||||
<div className="h-52 flex flex-col justify-center items-center bg-rose-500 rounded-t-xl">
|
||||
<svg
|
||||
className="size-28"
|
||||
width="56"
|
||||
height="56"
|
||||
viewBox="0 0 56 56"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="56" height="56" rx="10" fill="base-100" />
|
||||
<g clip-path="url(#clip0_2204_541)">
|
||||
<path
|
||||
d="M37.0409 28.8697C33.1968 28.8697 30.0811 31.9854 30.0811 35.8288C30.0811 39.6726 33.1968 42.789 37.0409 42.789C40.8843 42.789 44 39.6726 44 35.8288C44 31.9854 40.8843 28.8697 37.0409 28.8697ZM18.9594 28.8701C15.116 28.8704 12 31.9854 12 35.8292C12 39.6726 15.116 42.7886 18.9594 42.7886C22.8032 42.7886 25.9192 39.6726 25.9192 35.8292C25.9192 31.9854 22.8032 28.8701 18.9591 28.8701H18.9594ZM34.9595 20.1704C34.9595 24.0138 31.8438 27.1305 28.0004 27.1305C24.1563 27.1305 21.0406 24.0138 21.0406 20.1704C21.0406 16.3269 24.1563 13.2109 28.0003 13.2109C31.8438 13.2109 34.9591 16.3269 34.9591 20.1704H34.9595Z"
|
||||
fill="url(#paint0_radial_2204_541)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient
|
||||
id="paint0_radial_2204_541"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(28.0043 29.3944) scale(21.216 19.6102)"
|
||||
>
|
||||
<stop stop-color="#FFB900" />
|
||||
<stop offset="0.6" stop-color="#F95D8F" />
|
||||
<stop offset="0.999" stop-color="#F95353" />
|
||||
</radialGradient>
|
||||
<clipPath id="clip0_2204_541">
|
||||
<rect
|
||||
width="32"
|
||||
height="29.5808"
|
||||
fill="base-100"
|
||||
transform="translate(12 13.2096)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="p-4 md:p-6">
|
||||
<span className="block mb-1 text-xs font-semibold uppercase text-rose-600 dark:text-rose-500">
|
||||
Asana API
|
||||
</span>
|
||||
<h3 className="text-xl font-semibold text-secondary-content">
|
||||
Asana
|
||||
</h3>
|
||||
<p className="mt-3 text-secondary-content">
|
||||
Track tasks and projects, use agile boards, measure progress.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-auto flex border-t border-base-200 divide-x divide-base-200">
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-es-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View sample
|
||||
</a>
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-ee-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View API
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/* <!-- End Card --> */}
|
||||
|
||||
{/* <!-- Card --> */}
|
||||
<div className="group flex flex-col h-full bg-base-100 border border-base-200 shadow-sm rounded-xl">
|
||||
<div className="h-52 flex flex-col justify-center items-center bg-secondary rounded-t-xl">
|
||||
<svg
|
||||
className="size-28"
|
||||
width="56"
|
||||
height="56"
|
||||
viewBox="0 0 56 56"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="56" height="56" rx="10" fill="base-100" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M23.7326 11.968C21.9637 11.9693 20.5321 13.4049 20.5334 15.1738C20.5321 16.9427 21.965 18.3782 23.7339 18.3795H26.9345V15.1751C26.9358 13.4062 25.5029 11.9706 23.7326 11.968C23.7339 11.968 23.7339 11.968 23.7326 11.968M23.7326 20.5184H15.2005C13.4316 20.5197 11.9987 21.9553 12 23.7242C11.9974 25.4931 13.4303 26.9286 15.1992 26.9312H23.7326C25.5016 26.9299 26.9345 25.4944 26.9332 23.7255C26.9345 21.9553 25.5016 20.5197 23.7326 20.5184V20.5184Z"
|
||||
fill="#36C5F0"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M44.0001 23.7242C44.0014 21.9553 42.5684 20.5197 40.7995 20.5184C39.0306 20.5197 37.5977 21.9553 37.599 23.7242V26.9312H40.7995C42.5684 26.9299 44.0014 25.4944 44.0001 23.7242ZM35.4666 23.7242V15.1738C35.4679 13.4062 34.0363 11.9706 32.2674 11.968C30.4985 11.9693 29.0656 13.4049 29.0669 15.1738V23.7242C29.0643 25.4931 30.4972 26.9286 32.2661 26.9312C34.035 26.9299 35.4679 25.4944 35.4666 23.7242Z"
|
||||
fill="#2EB67D"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M32.2661 44.0322C34.035 44.0309 35.4679 42.5953 35.4666 40.8264C35.4679 39.0575 34.035 37.622 32.2661 37.6207H29.0656V40.8264C29.0642 42.594 30.4972 44.0295 32.2661 44.0322ZM32.2661 35.4804H40.7995C42.5684 35.4791 44.0013 34.0436 44 32.2747C44.0026 30.5058 42.5697 29.0702 40.8008 29.0676H32.2674C30.4985 29.0689 29.0656 30.5045 29.0669 32.2734C29.0656 34.0436 30.4972 35.4791 32.2661 35.4804V35.4804Z"
|
||||
fill="#ECB22E"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M12 32.2746C11.9987 34.0435 13.4316 35.479 15.2005 35.4804C16.9694 35.479 18.4024 34.0435 18.401 32.2746V29.0688H15.2005C13.4316 29.0702 11.9987 30.5057 12 32.2746ZM20.5334 32.2746V40.825C20.5308 42.5939 21.9637 44.0295 23.7326 44.0321C25.5016 44.0308 26.9345 42.5952 26.9332 40.8263V32.2772C26.9358 30.5083 25.5029 29.0728 23.7339 29.0702C21.9637 29.0702 20.5321 30.5057 20.5334 32.2746C20.5334 32.2759 20.5334 32.2746 20.5334 32.2746Z"
|
||||
fill="#E01E5A"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="p-4 md:p-6">
|
||||
<span className="block mb-1 text-xs font-semibold uppercase text-secondary">
|
||||
Slack API
|
||||
</span>
|
||||
<h3 className="text-xl font-semibold text-secondary-content">
|
||||
Slack
|
||||
</h3>
|
||||
<p className="mt-3 text-secondary-content">
|
||||
Email collaboration and email service desk made easy.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-auto flex border-t border-base-200 divide-x divide-base-200">
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-es-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View sample
|
||||
</a>
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-ee-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View API
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/* <!-- End Card --> */}
|
||||
</div>
|
||||
{/* <!-- End Grid --> */}
|
||||
</div>
|
||||
{/* <!-- End Card Blog --> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Teste
|
||||
|
|
@ -1,216 +0,0 @@
|
|||
function Teste() {
|
||||
return (
|
||||
|
||||
<>
|
||||
{/* <!-- Card Blog --> */}
|
||||
<div className="max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto">
|
||||
{/* <!-- Grid --> */}
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{/* <!-- Card --> */}
|
||||
<div className="group flex flex-col h-full bg-base-100 border border-base-200 shadow-sm rounded-xl">
|
||||
<div className="h-52 flex flex-col justify-center items-center bg-primary rounded-t-xl">
|
||||
<svg
|
||||
className="size-28"
|
||||
width="56"
|
||||
height="56"
|
||||
viewBox="0 0 56 56"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="56" height="56" rx="10" fill="base-100" />
|
||||
<path
|
||||
d="M20.2819 26.7478C20.1304 26.5495 19.9068 26.4194 19.6599 26.386C19.4131 26.3527 19.1631 26.4188 18.9647 26.5698C18.848 26.6622 18.7538 26.78 18.6894 26.9144L10.6019 43.1439C10.4874 43.3739 10.4686 43.6401 10.5496 43.884C10.6307 44.1279 10.805 44.3295 11.0342 44.4446C11.1681 44.5126 11.3163 44.5478 11.4664 44.5473H22.7343C22.9148 44.5519 23.0927 44.5037 23.2462 44.4084C23.3998 44.3132 23.5223 44.1751 23.5988 44.011C26.0307 38.9724 24.5566 31.3118 20.2819 26.7478Z"
|
||||
fill="url(#paint0_linear_2204_541)"
|
||||
/>
|
||||
<path
|
||||
d="M28.2171 11.9791C26.201 15.0912 25.026 18.6755 24.8074 22.3805C24.5889 26.0854 25.3342 29.7837 26.9704 33.1126L32.403 44.0113C32.4833 44.1724 32.6067 44.3079 32.7593 44.4026C32.912 44.4973 33.088 44.5475 33.2675 44.5476H44.5331C44.6602 44.5479 44.7861 44.523 44.9035 44.4743C45.0209 44.4257 45.1276 44.3543 45.2175 44.2642C45.3073 44.1741 45.3785 44.067 45.427 43.9492C45.4755 43.8314 45.5003 43.7052 45.5 43.5777C45.5001 43.4274 45.4659 43.2791 45.3999 43.1441L29.8619 11.9746C29.7881 11.8184 29.6717 11.6864 29.5261 11.594C29.3805 11.5016 29.2118 11.4525 29.0395 11.4525C28.8672 11.4525 28.6984 11.5016 28.5529 11.594C28.4073 11.6864 28.2908 11.8184 28.2171 11.9746V11.9791Z"
|
||||
fill="#2684FF"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_2204_541"
|
||||
x1="24.734"
|
||||
y1="29.2284"
|
||||
x2="16.1543"
|
||||
y2="44.0429"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#0052CC" />
|
||||
<stop offset="0.92" stop-color="#2684FF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="p-4 md:p-6">
|
||||
<span className="block mb-1 text-xs font-semibold uppercase text-primary dark:text-primary">
|
||||
Atlassian API
|
||||
</span>
|
||||
<h3 className="text-xl font-semibold text-secondary-content">
|
||||
Atlassian
|
||||
</h3>
|
||||
<p className="mt-3 text-secondary-content">
|
||||
A software that develops products for software developers and
|
||||
developments.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-auto flex border-t border-base-200 divide-x divide-base-200">
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-es-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View sample
|
||||
</a>
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-ee-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View API
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/* <!-- End Card --> */}
|
||||
|
||||
{/* <!-- Card --> */}
|
||||
<div className="group flex flex-col h-full bg-base-100 border border-base-200 shadow-sm rounded-xl">
|
||||
<div className="h-52 flex flex-col justify-center items-center bg-rose-500 rounded-t-xl">
|
||||
<svg
|
||||
className="size-28"
|
||||
width="56"
|
||||
height="56"
|
||||
viewBox="0 0 56 56"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="56" height="56" rx="10" fill="base-100" />
|
||||
<g clip-path="url(#clip0_2204_541)">
|
||||
<path
|
||||
d="M37.0409 28.8697C33.1968 28.8697 30.0811 31.9854 30.0811 35.8288C30.0811 39.6726 33.1968 42.789 37.0409 42.789C40.8843 42.789 44 39.6726 44 35.8288C44 31.9854 40.8843 28.8697 37.0409 28.8697ZM18.9594 28.8701C15.116 28.8704 12 31.9854 12 35.8292C12 39.6726 15.116 42.7886 18.9594 42.7886C22.8032 42.7886 25.9192 39.6726 25.9192 35.8292C25.9192 31.9854 22.8032 28.8701 18.9591 28.8701H18.9594ZM34.9595 20.1704C34.9595 24.0138 31.8438 27.1305 28.0004 27.1305C24.1563 27.1305 21.0406 24.0138 21.0406 20.1704C21.0406 16.3269 24.1563 13.2109 28.0003 13.2109C31.8438 13.2109 34.9591 16.3269 34.9591 20.1704H34.9595Z"
|
||||
fill="url(#paint0_radial_2204_541)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient
|
||||
id="paint0_radial_2204_541"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(28.0043 29.3944) scale(21.216 19.6102)"
|
||||
>
|
||||
<stop stop-color="#FFB900" />
|
||||
<stop offset="0.6" stop-color="#F95D8F" />
|
||||
<stop offset="0.999" stop-color="#F95353" />
|
||||
</radialGradient>
|
||||
<clipPath id="clip0_2204_541">
|
||||
<rect
|
||||
width="32"
|
||||
height="29.5808"
|
||||
fill="base-100"
|
||||
transform="translate(12 13.2096)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="p-4 md:p-6">
|
||||
<span className="block mb-1 text-xs font-semibold uppercase text-rose-600 dark:text-rose-500">
|
||||
Asana API
|
||||
</span>
|
||||
<h3 className="text-xl font-semibold text-secondary-content">
|
||||
Asana
|
||||
</h3>
|
||||
<p className="mt-3 text-secondary-content">
|
||||
Track tasks and projects, use agile boards, measure progress.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-auto flex border-t border-base-200 divide-x divide-base-200">
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-es-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View sample
|
||||
</a>
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-ee-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View API
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/* <!-- End Card --> */}
|
||||
|
||||
{/* <!-- Card --> */}
|
||||
<div className="group flex flex-col h-full bg-base-100 border border-base-200 shadow-sm rounded-xl">
|
||||
<div className="h-52 flex flex-col justify-center items-center bg-secondary rounded-t-xl">
|
||||
<svg
|
||||
className="size-28"
|
||||
width="56"
|
||||
height="56"
|
||||
viewBox="0 0 56 56"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="56" height="56" rx="10" fill="base-100" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M23.7326 11.968C21.9637 11.9693 20.5321 13.4049 20.5334 15.1738C20.5321 16.9427 21.965 18.3782 23.7339 18.3795H26.9345V15.1751C26.9358 13.4062 25.5029 11.9706 23.7326 11.968C23.7339 11.968 23.7339 11.968 23.7326 11.968M23.7326 20.5184H15.2005C13.4316 20.5197 11.9987 21.9553 12 23.7242C11.9974 25.4931 13.4303 26.9286 15.1992 26.9312H23.7326C25.5016 26.9299 26.9345 25.4944 26.9332 23.7255C26.9345 21.9553 25.5016 20.5197 23.7326 20.5184V20.5184Z"
|
||||
fill="#36C5F0"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M44.0001 23.7242C44.0014 21.9553 42.5684 20.5197 40.7995 20.5184C39.0306 20.5197 37.5977 21.9553 37.599 23.7242V26.9312H40.7995C42.5684 26.9299 44.0014 25.4944 44.0001 23.7242ZM35.4666 23.7242V15.1738C35.4679 13.4062 34.0363 11.9706 32.2674 11.968C30.4985 11.9693 29.0656 13.4049 29.0669 15.1738V23.7242C29.0643 25.4931 30.4972 26.9286 32.2661 26.9312C34.035 26.9299 35.4679 25.4944 35.4666 23.7242Z"
|
||||
fill="#2EB67D"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M32.2661 44.0322C34.035 44.0309 35.4679 42.5953 35.4666 40.8264C35.4679 39.0575 34.035 37.622 32.2661 37.6207H29.0656V40.8264C29.0642 42.594 30.4972 44.0295 32.2661 44.0322ZM32.2661 35.4804H40.7995C42.5684 35.4791 44.0013 34.0436 44 32.2747C44.0026 30.5058 42.5697 29.0702 40.8008 29.0676H32.2674C30.4985 29.0689 29.0656 30.5045 29.0669 32.2734C29.0656 34.0436 30.4972 35.4791 32.2661 35.4804V35.4804Z"
|
||||
fill="#ECB22E"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M12 32.2746C11.9987 34.0435 13.4316 35.479 15.2005 35.4804C16.9694 35.479 18.4024 34.0435 18.401 32.2746V29.0688H15.2005C13.4316 29.0702 11.9987 30.5057 12 32.2746ZM20.5334 32.2746V40.825C20.5308 42.5939 21.9637 44.0295 23.7326 44.0321C25.5016 44.0308 26.9345 42.5952 26.9332 40.8263V32.2772C26.9358 30.5083 25.5029 29.0728 23.7339 29.0702C21.9637 29.0702 20.5321 30.5057 20.5334 32.2746C20.5334 32.2759 20.5334 32.2746 20.5334 32.2746Z"
|
||||
fill="#E01E5A"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="p-4 md:p-6">
|
||||
<span className="block mb-1 text-xs font-semibold uppercase text-secondary">
|
||||
Slack API
|
||||
</span>
|
||||
<h3 className="text-xl font-semibold text-secondary-content">
|
||||
Slack
|
||||
</h3>
|
||||
<p className="mt-3 text-secondary-content">
|
||||
Email collaboration and email service desk made easy.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-auto flex border-t border-base-200 divide-x divide-base-200">
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-es-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View sample
|
||||
</a>
|
||||
<a
|
||||
className="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-ee-xl bg-base-100 text-secondary-content shadow-sm hover:bg-base-300 disabled:opacity-50 disabled:pointer-events-none dark:focus:outline-none dark:focus:ring-1"
|
||||
href="#"
|
||||
>
|
||||
View API
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/* <!-- End Card --> */}
|
||||
</div>
|
||||
{/* <!-- End Grid --> */}
|
||||
</div>
|
||||
{/* <!-- End Card Blog --> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Teste
|
|
@ -1,41 +1,41 @@
|
|||
"use client";
|
||||
|
||||
import YouTubeFrame from "@/components/Utilities/YoutubeEmbed";
|
||||
import React from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
function Video() {
|
||||
const [windowWidth, setWindowWidth] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
setWindowWidth(window.innerWidth);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 relative">
|
||||
{/* Hero image */}
|
||||
<div
|
||||
className="relative flex justify-center items-center"
|
||||
data-aos="fade-up"
|
||||
data-aos-delay="200"
|
||||
>
|
||||
<YouTubeFrame
|
||||
video="KCHnP62DWpg"
|
||||
videotitle="Sign365 - Fill and Sign Once"
|
||||
width={
|
||||
!!windowWidth && windowWidth > 425
|
||||
? windowWidth > 800
|
||||
? 800
|
||||
: 400
|
||||
: 280
|
||||
}
|
||||
height={!!windowWidth && windowWidth > 425 ? 500 : 240}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default Video;
|
||||
"use client"
|
||||
|
||||
import YouTubeFrame from "@/components/Utilities/YoutubeEmbed"
|
||||
import React from "react"
|
||||
import { useState, useEffect } from "react"
|
||||
|
||||
function Video() {
|
||||
const [windowWidth, setWindowWidth] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
setWindowWidth(window.innerWidth)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 relative">
|
||||
{/* Hero image */}
|
||||
<div
|
||||
className="relative flex justify-center items-center"
|
||||
data-aos="fade-up"
|
||||
data-aos-delay="200"
|
||||
>
|
||||
<YouTubeFrame
|
||||
video="KCHnP62DWpg"
|
||||
videotitle="Sign365 - Fill and Sign Once"
|
||||
width={
|
||||
!!windowWidth && windowWidth > 425
|
||||
? windowWidth > 800
|
||||
? 800
|
||||
: 400
|
||||
: 280
|
||||
}
|
||||
height={!!windowWidth && windowWidth > 425 ? 500 : 240}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default Video
|
|
@ -0,0 +1,113 @@
|
|||
// const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
|
||||
const config = {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./sections/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./node_modules/preline/preline.js"
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
heading: "var(--heading-font)",
|
||||
body: "var(--heading-font)",
|
||||
accent: "var(--accent-font)"
|
||||
},
|
||||
fontSize: {
|
||||
xs: "0.75rem",
|
||||
sm: "0.875rem",
|
||||
base: "1rem",
|
||||
lg: "1.125rem",
|
||||
xl: "1.25rem",
|
||||
"2xl": "1.5rem",
|
||||
"3xl": "2rem",
|
||||
"4xl": "2.5rem",
|
||||
"5xl": "3.25rem",
|
||||
"6xl": "4rem"
|
||||
},
|
||||
letterSpacing: {
|
||||
tighter: "-0.02em",
|
||||
tight: "-0.01em",
|
||||
normal: "0",
|
||||
wide: "0.01em",
|
||||
wider: "0.02em",
|
||||
widest: "0.4em"
|
||||
},
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
p: {
|
||||
fontFamily: "var(--body-font)"
|
||||
},
|
||||
h1: {
|
||||
fontFamily: "var(--heading-font)"
|
||||
},
|
||||
h2: {
|
||||
fontFamily: "var(--heading-font)"
|
||||
},
|
||||
h3: {
|
||||
fontFamily: "var(--heading-font)"
|
||||
},
|
||||
h4: {
|
||||
fontFamily: "var(--heading-font)"
|
||||
},
|
||||
h5: {
|
||||
fontFamily: "var(--heading-font)"
|
||||
},
|
||||
h6: {
|
||||
fontFamily: "var(--heading-font)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
daisyui: {
|
||||
themes: [
|
||||
{
|
||||
light: {
|
||||
primary: "#FD5469",
|
||||
"primary-content": "#F5F5F5",
|
||||
secondary: "#7082FF",
|
||||
"secondary-content": "#000",
|
||||
accent: "#fd0000",
|
||||
neutral: "#28282e",
|
||||
"base-100": "#FBFAFA",
|
||||
"base-200": "#F9F8F8",
|
||||
"base-300": "#F4F3F4",
|
||||
"base-content": "#000",
|
||||
info: "#00f5ff",
|
||||
success: "#00ff8b",
|
||||
warning: "#ff5100",
|
||||
error: "#ff0051"
|
||||
},
|
||||
dark: {
|
||||
primary: "#FD5469",
|
||||
"primary-content": "#F5F5F5",
|
||||
secondary: "#7082FF",
|
||||
"secondary-content": "#fff",
|
||||
accent: "#006cff",
|
||||
neutral: "#060206",
|
||||
"base-100": "#2a3130",
|
||||
"base-200": "#2a2d2a",
|
||||
"base-300": "#292924",
|
||||
"base-content": "#fff",
|
||||
info: "#009ae0",
|
||||
success: "#76e200",
|
||||
warning: "#eb9400",
|
||||
error: "#be2133"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
require("@tailwindcss/forms"),
|
||||
require("@tailwindcss/typography"),
|
||||
require("daisyui"),
|
||||
require("preline/plugin")
|
||||
]
|
||||
}
|
||||
export default config
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
import type { Config } from "tailwindcss";
|
||||
// const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./sections/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./node_modules/preline/preline.js",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
heading: 'var(--heading-font)',
|
||||
body: 'var(--heading-font)',
|
||||
accent: 'var(--accent-font)',
|
||||
},
|
||||
fontSize: {
|
||||
xs: "0.75rem",
|
||||
sm: "0.875rem",
|
||||
base: "1rem",
|
||||
lg: "1.125rem",
|
||||
xl: "1.25rem",
|
||||
"2xl": "1.5rem",
|
||||
"3xl": "2rem",
|
||||
"4xl": "2.5rem",
|
||||
"5xl": "3.25rem",
|
||||
"6xl": "4rem",
|
||||
},
|
||||
letterSpacing: {
|
||||
tighter: "-0.02em",
|
||||
tight: "-0.01em",
|
||||
normal: "0",
|
||||
wide: "0.01em",
|
||||
wider: "0.02em",
|
||||
widest: "0.4em",
|
||||
},
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
p: {
|
||||
fontFamily: 'var(--body-font)'
|
||||
},
|
||||
h1: {
|
||||
fontFamily: 'var(--heading-font)'
|
||||
},
|
||||
h2: {
|
||||
fontFamily: 'var(--heading-font)'
|
||||
},
|
||||
h3: {
|
||||
fontFamily: 'var(--heading-font)'
|
||||
},
|
||||
h4: {
|
||||
fontFamily: 'var(--heading-font)'
|
||||
},
|
||||
h5: {
|
||||
fontFamily: 'var(--heading-font)'
|
||||
},
|
||||
h6: {
|
||||
fontFamily: 'var(--heading-font)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
daisyui: {
|
||||
themes: [
|
||||
{
|
||||
light: {
|
||||
primary: "#FD5469",
|
||||
"primary-content": "#F5F5F5",
|
||||
secondary: "#7082FF",
|
||||
"secondary-content": "#000",
|
||||
accent: "#fd0000",
|
||||
neutral: "#28282e",
|
||||
"base-100": "#FBFAFA",
|
||||
"base-200": "#F9F8F8",
|
||||
"base-300": "#F4F3F4",
|
||||
"base-content": "#000",
|
||||
info: "#00f5ff",
|
||||
success: "#00ff8b",
|
||||
warning: "#ff5100",
|
||||
error: "#ff0051",
|
||||
},
|
||||
dark: {
|
||||
primary: "#FD5469",
|
||||
"primary-content": "#F5F5F5",
|
||||
secondary: "#7082FF",
|
||||
"secondary-content": "#fff",
|
||||
accent: "#006cff",
|
||||
neutral: "#060206",
|
||||
"base-100": "#2a3130",
|
||||
"base-200": "#2a2d2a",
|
||||
"base-300": "#292924",
|
||||
"base-content": "#fff",
|
||||
info: "#009ae0",
|
||||
success: "#76e200",
|
||||
warning: "#eb9400",
|
||||
error: "#be2133",
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
require("@tailwindcss/forms"),
|
||||
require("@tailwindcss/typography"),
|
||||
require("daisyui"),
|
||||
require("preline/plugin"),
|
||||
],
|
||||
};
|
||||
export default config;
|
|
@ -35,7 +35,7 @@
|
|||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
, "app/providers.js" ],
|
||||
, "app/providers.js", "app/actions.js", "sections/Payment.jsx", "sections/AccountContent.jsx" ],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
export let SourceModal
|
||||
|
||||
;(function(SourceModal) {
|
||||
SourceModal["SignUp"] = "SignUp"
|
||||
SourceModal["SignUpViaPurchase"] = "SignUpViaPurchase"
|
||||
SourceModal["BookDemo"] = "BookDemo"
|
||||
SourceModal["LearnMore"] = "LearnMore"
|
||||
SourceModal["TryIt"] = "TryIt"
|
||||
SourceModal["Newsletter"] = "Newsletter"
|
||||
})(SourceModal || (SourceModal = {}))
|
|
@ -0,0 +1,14 @@
|
|||
import tw from "../tailwind.config"
|
||||
|
||||
export default tw.daisyui.themes[0]
|
||||
|
||||
export function hexToRgb(hex) {
|
||||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
return result
|
||||
? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
}
|
||||
: null
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import tw from '../tailwind.config'
|
||||
|
||||
export default tw.daisyui.themes[0];
|
||||
|
||||
export function hexToRgb(hex: string) {
|
||||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
} : null;
|
||||
}
|
|
@ -1,46 +1,42 @@
|
|||
import * as Yup from "yup";
|
||||
import YupPassword from "yup-password";
|
||||
YupPassword(Yup);
|
||||
import * as Yup from "yup"
|
||||
import YupPassword from "yup-password"
|
||||
YupPassword(Yup)
|
||||
|
||||
declare module "yup" {
|
||||
interface StringSchema {
|
||||
mobileNumberValidation(errorMessage: string): StringSchema;
|
||||
}
|
||||
}
|
||||
|
||||
const isValidMobileNumber = (mobileNumber: string | any[]) => {
|
||||
const isValidMobileNumber = mobileNumber => {
|
||||
if (mobileNumber.length < 8 || mobileNumber.length > 12) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Mobile Number should of 9 to 11 length",
|
||||
};
|
||||
message: "Mobile Number should of 9 to 11 length"
|
||||
}
|
||||
} else if (isNaN(Number(mobileNumber))) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Mobile Number should only contain numbers",
|
||||
};
|
||||
}
|
||||
return { success: true, message: "Valid Mobile Number" };
|
||||
};
|
||||
|
||||
Yup.addMethod(Yup.string, "mobileNumberValidation", function (errorMessage) {
|
||||
return this.test(`test-mobile-number`, errorMessage, function (value) {
|
||||
const { path, createError } = this;
|
||||
if (!value) {
|
||||
return createError({ path, message: errorMessage });
|
||||
message: "Mobile Number should only contain numbers"
|
||||
}
|
||||
const validation = isValidMobileNumber(value);
|
||||
}
|
||||
return { success: true, message: "Valid Mobile Number" }
|
||||
}
|
||||
|
||||
Yup.addMethod(Yup.string, "mobileNumberValidation", function(errorMessage) {
|
||||
return this.test(`test-mobile-number`, errorMessage, function(value) {
|
||||
const { path, createError } = this
|
||||
if (!value) {
|
||||
return createError({ path, message: errorMessage })
|
||||
}
|
||||
const validation = isValidMobileNumber(value)
|
||||
return (
|
||||
(value && validation.success) ||
|
||||
createError({ path, message: validation.message })
|
||||
);
|
||||
});
|
||||
});
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
const signUpValidationSchema = Yup.object().shape({
|
||||
firstName: Yup.string().required("First Name is required"),
|
||||
lastName: Yup.string().required("Last Name is required"),
|
||||
email: Yup.string().email().required("E-mail is required"),
|
||||
email: Yup.string()
|
||||
.email()
|
||||
.required("E-mail is required"),
|
||||
phoneNumber: Yup.string()
|
||||
.required("Phone Number is required")
|
||||
.mobileNumberValidation("Phone Number is not valid"),
|
||||
|
@ -50,51 +46,59 @@ const signUpValidationSchema = Yup.object().shape({
|
|||
.password()
|
||||
.required("Password is required.")
|
||||
.min(8, "Password is too short - should be 8 characters minimum.")
|
||||
});
|
||||
})
|
||||
|
||||
const passwordValidationSchema = Yup.object().shape({
|
||||
newPassword: Yup.string()
|
||||
.password()
|
||||
.required("Password is required.")
|
||||
.min(8, "Password is too short - should be 8 characters minimum."),
|
||||
newPasswordConfirm: Yup.string()
|
||||
newPasswordConfirm: Yup.string()
|
||||
.password()
|
||||
.required("Password is required.")
|
||||
.min(8, "Password is too short - should be 8 characters minimum.")
|
||||
});
|
||||
})
|
||||
const changeEmailValidationSchema = Yup.object().shape({
|
||||
password: Yup.string()
|
||||
.password()
|
||||
.required("Password is required.")
|
||||
.min(8, "Password is too short - should be 8 characters minimum."),
|
||||
});
|
||||
.min(8, "Password is too short - should be 8 characters minimum.")
|
||||
})
|
||||
const waitinglistValidationSchema = Yup.object().shape({
|
||||
firstName: Yup.string().required("First Name is required"),
|
||||
lastName: Yup.string().required("Last Name is required"),
|
||||
email: Yup.string().email().required("E-mail is required"),
|
||||
});
|
||||
email: Yup.string()
|
||||
.email()
|
||||
.required("E-mail is required")
|
||||
})
|
||||
const contactUsValidationSchema = Yup.object().shape({
|
||||
firstName: Yup.string().required("First Name is required"),
|
||||
lastName: Yup.string().required("Last Name is required"),
|
||||
note: Yup.string(),
|
||||
phoneNumber: Yup.string()
|
||||
.required("Phone Number is required")
|
||||
.mobileNumberValidation("Phone Number is not valid"),
|
||||
email: Yup.string().email().required("E-mail is required"),
|
||||
});
|
||||
.required("Phone Number is required")
|
||||
.mobileNumberValidation("Phone Number is not valid"),
|
||||
email: Yup.string()
|
||||
.email()
|
||||
.required("E-mail is required")
|
||||
})
|
||||
|
||||
const emailValidationSchema = Yup.object().shape({
|
||||
email: Yup.string().email().required("E-mail is required"),
|
||||
});
|
||||
email: Yup.string()
|
||||
.email()
|
||||
.required("E-mail is required")
|
||||
})
|
||||
|
||||
const signInValidationSchema = Yup.object().shape({
|
||||
email: Yup.string().email().required("E-mail is required"),
|
||||
email: Yup.string()
|
||||
.email()
|
||||
.required("E-mail is required"),
|
||||
password: Yup.string()
|
||||
.password()
|
||||
.required("Password is required.")
|
||||
.min(8, "Password is too short - should be 8 characters minimum.")
|
||||
.minUppercase(1, "password must contain at least 1 upper case letter"),
|
||||
});
|
||||
.minUppercase(1, "password must contain at least 1 upper case letter")
|
||||
})
|
||||
|
||||
export {
|
||||
emailValidationSchema,
|
||||
|
@ -104,4 +108,4 @@ export {
|
|||
signInValidationSchema,
|
||||
waitinglistValidationSchema,
|
||||
contactUsValidationSchema
|
||||
};
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import pb from "@/lib/pocketbase"
|
||||
|
||||
const getPostMetadata = async () => {
|
||||
try {
|
||||
return await pb.collection("blog").getFullList({ requestKey: "blogs" })
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export default getPostMetadata
|
|
@ -1,11 +0,0 @@
|
|||
import pb from '@/lib/pocketbase';
|
||||
|
||||
const getPostMetadata = async () => {
|
||||
try {
|
||||
return (await (pb.collection("blog").getFullList({requestKey:'blogs'})))
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
};
|
||||
|
||||
export default getPostMetadata;
|
Loading…
Reference in New Issue