first commit in js

This commit is contained in:
Ciro de Oliveira 2024-07-31 17:18:49 -03:00
parent ebfd1ccf59
commit 39aef95de1
84 changed files with 2091 additions and 2268 deletions

View File

@ -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>
)
)
}

View File

@ -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>
)
);
}

View File

@ -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}&nbsp;
</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>
)
}

View File

@ -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}&nbsp;
</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>
);
}

View File

@ -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}&nbsp;
</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}&nbsp;
</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>
)
}

View File

@ -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}&nbsp;
</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}&nbsp;
</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>
);
}

View File

@ -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}&nbsp;
</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}&nbsp;
</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>
)
}

View File

@ -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>
);
}

View File

@ -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&apos;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&apos;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&apos;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>
);
)
}

View File

@ -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

View File

@ -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>
);
)
}

View File

@ -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

View File

@ -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 []
}
}

View File

@ -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 [];
}
}

View File

@ -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>
);
)
}

View File

@ -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

287
Frontend/app/actions.js Normal file
View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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>
);
)
}

View File

@ -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>
</>
);
)
}

View File

@ -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>
</>
);
)
}

51
Frontend/app/sitemap.js Normal file
View File

@ -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
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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>
);
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>
);
)
}

View File

@ -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>
</>
)
}

View File

@ -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>
</>
);
}

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -0,0 +1,7 @@
import React from "react"
const Spacer = props => {
return <div {...props}></div>
}
export default Spacer

View File

@ -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;

View File

@ -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>
);
)
}

View File

@ -4,6 +4,7 @@ export const companySizeList = [
"30-70 employees",
"70-100 employees",
"100+ employees"
]
export const title = "FastPocket"
]
export const title = "FastPocket"

20
Frontend/lib/auth.js Normal file
View File

@ -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
}

View File

@ -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;
}

23
Frontend/middleware.js Normal file
View File

@ -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()
}

View File

@ -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();
}

View File

@ -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 havent 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;

View File

@ -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 havent 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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,8 @@
import SquaredBackgroundHero from "./SquaredBackgroundHero"
import CenterAllignedWithVideoHero from "./CenterAllignedWithVideoHero"
import WaitingListWithImageHero from "./WaitingListWithImageHero"
export {
SquaredBackgroundHero,
CenterAllignedWithVideoHero,
WaitingListWithImageHero
}

View File

@ -1,8 +0,0 @@
import SquaredBackgroundHero from "./SquaredBackgroundHero";
import CenterAllignedWithVideoHero from "./CenterAllignedWithVideoHero";
import WaitingListWithImageHero from "./WaitingListWithImageHero";
export {
SquaredBackgroundHero,
CenterAllignedWithVideoHero,
WaitingListWithImageHero
};

View File

@ -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}&nbsp;
</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&apos;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}&nbsp;
</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&apos;t regret it!
</h3>
</div>
)}
</div>
</div>
</section>
)
}
export default Newsletter

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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>
);
};
)
}

View File

@ -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

View File

@ -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

View File

@ -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

113
Frontend/tailwind.config.js Normal file
View File

@ -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

View File

@ -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;

View File

@ -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"
]

10
Frontend/types/index.js Normal file
View File

@ -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 = {}))

14
Frontend/utils/colors.js Normal file
View File

@ -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
}

View File

@ -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;
}

View File

@ -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
};
}

View File

@ -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

View File

@ -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;