newFeature/rebuild-in-js #15
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,36 @@
|
|||
import React from "react"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import { cookies } from "next/headers"
|
||||
import { getUserFromCookie } from "@/lib/auth"
|
||||
import AccountContent from "@/sections/AccountContent"
|
||||
import { redirect } from "next/navigation"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Footer from "@/components/Footer"
|
||||
import Spacer from "@/components/Utilities/Spacer"
|
||||
|
||||
export default async function AccountPage() {
|
||||
const user = await getUserFromCookie(cookies())
|
||||
const cookie = cookies().get("pb_auth")
|
||||
//server side
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
!pb.authStore.isValid && redirect("/")
|
||||
return (
|
||||
user && (
|
||||
<main
|
||||
id="content"
|
||||
role="main"
|
||||
className="h-full flex flex-col min-h-screen mx-auto w-screen overflow-hidden bg-base-100"
|
||||
>
|
||||
<Background className="min-h-screen flex">
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<PageHeader title="Account" subtitle={<></>} />
|
||||
{/* <AccountContent user={user} /> */}
|
||||
<Spacer className="mt-auto mb-auto" />
|
||||
<Footer />
|
||||
</div>
|
||||
</Background>
|
||||
</main>
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
import React from "react";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
import { cookies } from "next/headers";
|
||||
import { getUserFromCookie } from "@/lib/auth";
|
||||
import { User } from "@/types";
|
||||
import AccountContent from "@/sections/AccountContent";
|
||||
import { redirect } from "next/navigation";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import Footer from "@/components/Footer";
|
||||
import Spacer from "@/components/Utilities/Spacer";
|
||||
|
||||
export default async function AccountPage() {
|
||||
const user = (await getUserFromCookie(cookies())) as User;
|
||||
const cookie = cookies().get("pb_auth");
|
||||
//server side
|
||||
pb.authStore.loadFromCookie(cookie?.value || "");
|
||||
!pb.authStore.isValid && redirect("/");
|
||||
return (
|
||||
user && (
|
||||
<main
|
||||
id="content"
|
||||
role="main"
|
||||
className="h-full flex flex-col min-h-screen mx-auto w-screen overflow-hidden bg-base-100"
|
||||
>
|
||||
<Background className="min-h-screen flex">
|
||||
<div className="min-h-screen flex flex-col">
|
||||
|
||||
<PageHeader title="Account" subtitle={<></>} />
|
||||
<AccountContent user={user} />
|
||||
<Spacer className="mt-auto mb-auto" />
|
||||
<Footer />
|
||||
</div>
|
||||
</Background>
|
||||
</main>
|
||||
)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { changeEmailValidationSchema } from "@/utils/form"
|
||||
import { toast } from "react-toastify"
|
||||
import PocketBase from "pocketbase"
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper"
|
||||
import { usePathname } from "next/navigation"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL)
|
||||
|
||||
export default function ConfirmEmailChangePage() {
|
||||
const pathName = usePathname()
|
||||
const token = pathName.split("/").at(-1)
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
reset
|
||||
} = useForm({
|
||||
resolver: yupResolver(changeEmailValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async data => {
|
||||
try {
|
||||
await pb.collection("user").confirmEmailChange(token ?? "", data.password)
|
||||
reset()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error("There was a problem. Please try change your email again", {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader
|
||||
title={"Enter Your Password To Change Your Email"}
|
||||
subtitle={<></>}
|
||||
/>
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full max-w-xl px-4"
|
||||
>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Password…"
|
||||
aria-label="Password…"
|
||||
autoComplete="on"
|
||||
{...register("password")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.password?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-full justify-between">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray" : "btn btn-primary"}
|
||||
>
|
||||
Change Email
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { changeEmailValidationSchema } from "@/utils/form";
|
||||
import { toast } from "react-toastify";
|
||||
import PocketBase from 'pocketbase';
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper";
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL as string);
|
||||
|
||||
export default function ConfirmEmailChangePage() {
|
||||
const pathName = usePathname();
|
||||
const token = pathName.split('/').at(-1);
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
reset
|
||||
} = useForm({
|
||||
resolver: yupResolver(changeEmailValidationSchema),
|
||||
});
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
await pb.collection('user').confirmEmailChange(
|
||||
token ?? "",
|
||||
data.password,
|
||||
);
|
||||
reset();
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error("There was a problem. Please try change your email again", {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader
|
||||
title={"Enter Your Password To Change Your Email"}
|
||||
subtitle={<></>}
|
||||
/>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="w-full max-w-xl px-4">
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Password…"
|
||||
aria-label="Password…"
|
||||
autoComplete="on"
|
||||
{...register("password")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.password?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-full justify-between">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray": "btn btn-primary"}
|
||||
>
|
||||
Change Email
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { passwordValidationSchema } from "@/utils/form"
|
||||
import { toast } from "react-toastify"
|
||||
import PocketBase from "pocketbase"
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper"
|
||||
import { usePathname } from "next/navigation"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL)
|
||||
|
||||
export default function ConfirmPasswordResetPage() {
|
||||
const pathName = usePathname()
|
||||
const token = pathName.split("/").at(-1)
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
reset
|
||||
} = useForm({
|
||||
resolver: yupResolver(passwordValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async data => {
|
||||
try {
|
||||
await pb
|
||||
.collection("user")
|
||||
.confirmPasswordReset(
|
||||
token ?? "",
|
||||
data.newPassword,
|
||||
data.newPasswordConfirm
|
||||
)
|
||||
reset()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(
|
||||
"There was a problem. Please try reset your password again",
|
||||
{
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader title={"Enter Your New Password"} subtitle={<></>} />
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full max-w-xl px-4"
|
||||
>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="New password…"
|
||||
aria-label="New password…"
|
||||
autoComplete="on"
|
||||
{...register("newPassword")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPassword?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Confirm new password…"
|
||||
aria-label="Confirm new password…"
|
||||
autoComplete="on"
|
||||
{...register("newPasswordConfirm")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPasswordConfirm?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-full justify-between">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray" : "btn btn-primary"}
|
||||
>
|
||||
Reset Password
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { passwordValidationSchema } from "@/utils/form";
|
||||
import { toast } from "react-toastify";
|
||||
import PocketBase from 'pocketbase';
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper";
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL as string);
|
||||
|
||||
export default function ConfirmPasswordResetPage() {
|
||||
const pathName = usePathname();
|
||||
const token = pathName.split('/').at(-1);
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
reset
|
||||
} = useForm({
|
||||
resolver: yupResolver(passwordValidationSchema),
|
||||
});
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
await pb.collection('user').confirmPasswordReset(
|
||||
token ?? "",
|
||||
data.newPassword,
|
||||
data.newPasswordConfirm,
|
||||
);
|
||||
reset()
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error("There was a problem. Please try reset your password again", {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader
|
||||
title={"Enter Your New Password"}
|
||||
subtitle={<></>}
|
||||
/>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="w-full max-w-xl px-4">
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="New password…"
|
||||
aria-label="New password…"
|
||||
autoComplete="on"
|
||||
{...register("newPassword")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPassword?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Confirm new password…"
|
||||
aria-label="Confirm new password…"
|
||||
autoComplete="on"
|
||||
{...register("newPasswordConfirm")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPasswordConfirm?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-full justify-between">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray": "btn btn-primary"}
|
||||
>
|
||||
Reset Password
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { passwordValidationSchema } from "@/utils/form"
|
||||
import { toast } from "react-toastify"
|
||||
import PocketBase from "pocketbase"
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper"
|
||||
import { usePathname } from "next/navigation"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL)
|
||||
|
||||
export default function ConfirmPasswordResetPage() {
|
||||
const pathName = usePathname()
|
||||
const token = pathName.split("/").at(-1)
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
reset
|
||||
} = useForm({
|
||||
resolver: yupResolver(passwordValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async data => {
|
||||
try {
|
||||
await pb
|
||||
.collection("user")
|
||||
.confirmPasswordReset(
|
||||
token ?? "",
|
||||
data.newPassword,
|
||||
data.newPasswordConfirm
|
||||
)
|
||||
reset()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(
|
||||
"There was a problem. Please try reset your password again",
|
||||
{
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader title={"Enter Your New Password"} subtitle={<></>} />
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full max-w-xl px-4"
|
||||
>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="New password…"
|
||||
aria-label="New password…"
|
||||
autoComplete="on"
|
||||
{...register("newPassword")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPassword?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="password"
|
||||
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
|
||||
placeholder="Confirm new password…"
|
||||
aria-label="Confirm new password…"
|
||||
autoComplete="on"
|
||||
{...register("newPasswordConfirm")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.newPasswordConfirm?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-full justify-between">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray" : "btn btn-primary"}
|
||||
>
|
||||
Reset Password
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import PocketBase from 'pocketbase';
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper";
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
|
||||
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL as string);
|
||||
|
||||
export default function ConfirmVerification() {
|
||||
const pathName = usePathname();
|
||||
const router = useRouter();
|
||||
const token = pathName.split('/').at(-1);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
await pb.collection('user').confirmVerification(
|
||||
token ?? ""
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error("There was a problem. Please try confirm your email again", {
|
||||
position: "bottom-left",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
}
|
||||
}
|
||||
})()
|
||||
}, [])
|
||||
|
||||
const handleSignInRedirect = () => {
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<Background>
|
||||
<div className="h-screen w-screen flex items-center flex-col">
|
||||
<PageHeader
|
||||
title={"Your Email Was Verified"}
|
||||
subtitle={<></>}
|
||||
/>
|
||||
<button onClick={handleSignInRedirect} className="btn btn-primary mt-4">
|
||||
Go to Sign In
|
||||
</button>
|
||||
</div>
|
||||
</Background>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import PageHeader from "@/sections/PageHeader";
|
||||
import React from "react";
|
||||
import Footer from "@/components/Footer";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import React from "react"
|
||||
import Footer from "@/components/Footer"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
export default async function About() {
|
||||
return (
|
||||
|
@ -15,20 +15,45 @@ export default async function About() {
|
|||
<PageHeader title="Helping Developers Build" />
|
||||
<div className="max-w-4xl mx-auto mb-auto pb-24 h-full w-full py-12 px-8 flex flex-col gap-y-12 text-center items-center">
|
||||
<p className="text-lg text-base-content font-thin">
|
||||
In 2023 I built <Link href={"https://sign365.com.au"} className="font-bold text-secondary">Sign365</Link> using pocketbase
|
||||
and setup an open source library to help people get setup with <Link href={"https://github.com/mrwyndham/pocketbase-stripe"} className="font-bold text-secondary">Stripe
|
||||
+ Pocketbase</Link>. As 2024 has come around I have had more and more
|
||||
requests for applications and features on the existing code. I built a
|
||||
codebase that would save me 20 hours + in the bootstrapping time to
|
||||
get my applications ready. That is why I had to build <Link href={"https://fastpocket.dev"} className="font-bold text-secondary">FastPocket</Link> an
|
||||
easy solution to give everyone a head start.
|
||||
In 2023 I built{" "}
|
||||
<Link
|
||||
href={"https://sign365.com.au"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
Sign365
|
||||
</Link>{" "}
|
||||
using pocketbase and setup an open source library to help people get
|
||||
setup with{" "}
|
||||
<Link
|
||||
href={"https://github.com/mrwyndham/pocketbase-stripe"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
Stripe + Pocketbase
|
||||
</Link>
|
||||
. As 2024 has come around I have had more and more requests for
|
||||
applications and features on the existing code. I built a codebase
|
||||
that would save me 20 hours + in the bootstrapping time to get my
|
||||
applications ready. That is why I had to build{" "}
|
||||
<Link
|
||||
href={"https://fastpocket.dev"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
FastPocket
|
||||
</Link>{" "}
|
||||
an easy solution to give everyone a head start.
|
||||
</p>
|
||||
<p className="text-lg text-base-content font-thin">
|
||||
After reflecting on the challenges that developers face building their
|
||||
applications, I've seen that there will never be one codebase
|
||||
that suits all developers. But I am sure for those who are
|
||||
opensourcing, self-hosting and want to spin up an application quickly
|
||||
they will be able to do it using <Link href={"https://fastpocket.dev"} className="font-bold text-secondary">FastPocket</Link>
|
||||
they will be able to do it using{" "}
|
||||
<Link
|
||||
href={"https://fastpocket.dev"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
FastPocket
|
||||
</Link>
|
||||
</p>
|
||||
<p className="text-lg text-base-content font-thin">
|
||||
I am taking all of the knowledge that I have gained across 20+
|
||||
|
@ -38,11 +63,23 @@ export default async function About() {
|
|||
that have been battle tested in enterprise code
|
||||
</p>
|
||||
<p className="text-lg text-base-content font-thin">
|
||||
So I've committed to building <Link href={"https://fastpocket.dev"} className="font-bold text-secondary">FastPocket</Link> to help you build your
|
||||
projects faster to get paid.
|
||||
So I've committed to building{" "}
|
||||
<Link
|
||||
href={"https://fastpocket.dev"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
FastPocket
|
||||
</Link>{" "}
|
||||
to help you build your projects faster to get paid.
|
||||
</p>
|
||||
<p className="text-xl text-base-content font-bold">
|
||||
<Link href={"https://fastpocket.dev"} className="font-bold text-secondary">FastPocket</Link> will get you producing more with less development.
|
||||
<Link
|
||||
href={"https://fastpocket.dev"}
|
||||
className="font-bold text-secondary"
|
||||
>
|
||||
FastPocket
|
||||
</Link>{" "}
|
||||
will get you producing more with less development.
|
||||
</p>
|
||||
<p className="text-lg text-base-content font-thin">
|
||||
I know that your next project will grow exponentially because of the
|
||||
|
@ -69,5 +106,5 @@ export default async function About() {
|
|||
</div>
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -1,32 +1,28 @@
|
|||
import BlogContent from "@/sections/BlogContent";
|
||||
import getPostMetadata from "@/utils/getPostMetaData";
|
||||
import { headers } from "next/headers";
|
||||
import React from "react";
|
||||
import Spacer from "@/components/Utilities/Spacer";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import Footer from "@/components/Footer";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import BlogContent from "@/sections/BlogContent"
|
||||
import getPostMetadata from "@/utils/getPostMetaData"
|
||||
import { headers } from "next/headers"
|
||||
import React from "react"
|
||||
import Spacer from "@/components/Utilities/Spacer"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import Footer from "@/components/Footer"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string };
|
||||
}) {
|
||||
const { slug } = params;
|
||||
const headersList = headers();
|
||||
const siteURL = headersList.get("host");
|
||||
pb.autoCancellation(false);
|
||||
export async function generateMetadata({ params }) {
|
||||
const { slug } = params
|
||||
const headersList = headers()
|
||||
const siteURL = headersList.get("host")
|
||||
pb.autoCancellation(false)
|
||||
const post = await pb.collection("blog").getFirstListItem(`slug="${slug}"`, {
|
||||
requestKey: "metaData",
|
||||
});
|
||||
pb.autoCancellation(true);
|
||||
requestKey: "metaData"
|
||||
})
|
||||
pb.autoCancellation(true)
|
||||
|
||||
return {
|
||||
title: `${post.title} | FastPocket`,
|
||||
authors: [
|
||||
{
|
||||
name: post.author || "Samuel Wyndham",
|
||||
},
|
||||
name: post.author || "Samuel Wyndham"
|
||||
}
|
||||
],
|
||||
description: post.description,
|
||||
keywords: post.keywords,
|
||||
|
@ -44,9 +40,9 @@ export async function generateMetadata({
|
|||
width: 1024,
|
||||
height: 576,
|
||||
alt: post.title,
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
type: "image/png"
|
||||
}
|
||||
]
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
|
@ -59,32 +55,32 @@ export async function generateMetadata({
|
|||
url: `${post.imageUrl}`,
|
||||
width: 1024,
|
||||
height: 576,
|
||||
alt: post.title,
|
||||
},
|
||||
],
|
||||
alt: post.title
|
||||
}
|
||||
]
|
||||
},
|
||||
alternates: {
|
||||
canonical: `https://${siteURL}/blogs/${post.slug}`,
|
||||
},
|
||||
};
|
||||
canonical: `https://${siteURL}/blogs/${post.slug}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const generateStaticParams = async () => {
|
||||
const posts = await getPostMetadata();
|
||||
console.log("static posts", posts.length);
|
||||
const mappedPosts = posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}));
|
||||
return mappedPosts;
|
||||
};
|
||||
const posts = await getPostMetadata()
|
||||
console.log("static posts", posts.length)
|
||||
const mappedPosts = posts.map(post => ({
|
||||
slug: post.slug
|
||||
}))
|
||||
return mappedPosts
|
||||
}
|
||||
|
||||
const PostPage = async (props: any) => {
|
||||
console.log("params", props.params);
|
||||
const PostPage = async props => {
|
||||
console.log("params", props.params)
|
||||
const post = await pb
|
||||
.collection("blog")
|
||||
.getFirstListItem(`slug="` + props.params.slug + `"`, {
|
||||
requestKey: "post",
|
||||
});
|
||||
requestKey: "post"
|
||||
})
|
||||
return (
|
||||
<main
|
||||
id="content"
|
||||
|
@ -99,7 +95,7 @@ const PostPage = async (props: any) => {
|
|||
<Footer />
|
||||
</Background>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default PostPage;
|
||||
export default PostPage
|
|
@ -1,17 +1,17 @@
|
|||
import PageHeader from "@/sections/PageHeader";
|
||||
import BlogCard from "@/components/BlogCard";
|
||||
import getPostMetadata from "@/utils/getPostMetaData";
|
||||
import React from "react";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import Footer from "@/components/Footer";
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import BlogCard from "@/components/BlogCard"
|
||||
import getPostMetadata from "@/utils/getPostMetaData"
|
||||
import React from "react"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Footer from "@/components/Footer"
|
||||
|
||||
export default async function BlogsPage() {
|
||||
const postMetadata = await getPostMetadata();
|
||||
const postMetadata = await getPostMetadata()
|
||||
|
||||
console.log("blogs", postMetadata.length);
|
||||
console.log("blogs", postMetadata.length)
|
||||
const postPreviews = postMetadata
|
||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||||
.map((post) => <BlogCard key={post.slug} {...(post as any)} />);
|
||||
.map(post => <BlogCard key={post.slug} {...post} />)
|
||||
return (
|
||||
<main
|
||||
id="content"
|
||||
|
@ -40,5 +40,5 @@ export default async function BlogsPage() {
|
|||
<Footer />
|
||||
</Background>
|
||||
</main>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import Footer from "@/components/Footer";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import Spacer from "@/components/Utilities/Spacer";
|
||||
import FormLeftDescriptionRightContactUs from "@/sections/ContactUs/FormLeftDescriptionRightContactUs";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
import React from "react";
|
||||
import Footer from "@/components/Footer"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Spacer from "@/components/Utilities/Spacer"
|
||||
import FormLeftDescriptionRightContactUs from "@/sections/ContactUs/FormLeftDescriptionRightContactUs"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import React from "react"
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
|
@ -25,7 +25,7 @@ const page = () => {
|
|||
<Footer />
|
||||
</Background>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default page;
|
||||
export default page
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
function layout({ children }: { children: React.ReactNode }) {
|
||||
function layout({ children }) {
|
||||
return (
|
||||
<>
|
||||
{children}
|
|
@ -0,0 +1,51 @@
|
|||
"use server"
|
||||
import pb from "@/lib/pocketbase"
|
||||
|
||||
export async function apiPrices() {
|
||||
try {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error("Connection Timeout")
|
||||
}
|
||||
const productRequest = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/product/records?filter=(active=true)`,
|
||||
{
|
||||
cache: "no-cache",
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
)
|
||||
const productResponse = await productRequest.json()
|
||||
console.log("app/pricing/action", "productResponse", productResponse)
|
||||
const unsortedProducts = productResponse.items
|
||||
console.log("app/pricing/action", "unsortedProducts", unsortedProducts)
|
||||
const prices = await pb.collection("price").getFullList()
|
||||
console.log("app/pricing/action", "prices", prices)
|
||||
for (const product of unsortedProducts) {
|
||||
product.metadata.benefits = product?.metadata?.benefits
|
||||
? JSON.parse(product.metadata.benefits)
|
||||
: []
|
||||
const pricesOfProduct = prices.filter(
|
||||
price => price.product_id === product.product_id
|
||||
)
|
||||
for (const priceOfProduct of pricesOfProduct) {
|
||||
product.type = priceOfProduct.type
|
||||
if (priceOfProduct.interval === "year") {
|
||||
product.yearlyPrice = priceOfProduct
|
||||
} else {
|
||||
product.monthlyPrice = priceOfProduct
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sortedProducts = unsortedProducts.sort(
|
||||
(a, b) => a.product_order - b.product_order
|
||||
)
|
||||
return sortedProducts
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return []
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
"use server";
|
||||
|
||||
import { Product, Price } from "@/types";
|
||||
import pb from "@/lib/pocketbase";
|
||||
|
||||
export async function apiPrices() {
|
||||
try {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL;
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error('Connection Timeout');
|
||||
}
|
||||
const productRequest = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/product/records?filter=(active=true)`,
|
||||
{
|
||||
cache: 'no-cache',
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
const productResponse = await productRequest.json();
|
||||
console.log("app/pricing/actions","productResponse",productResponse)
|
||||
const unsortedProducts: Product[] = productResponse.items;
|
||||
console.log("app/pricing/actions","unsortedProducts",unsortedProducts)
|
||||
const prices = await pb.collection("price").getFullList<Price>();
|
||||
console.log("app/pricing/actions","prices",prices)
|
||||
for (const product of unsortedProducts) {
|
||||
product.metadata.benefits = product?.metadata?.benefits ? JSON.parse(product.metadata.benefits as any) : [];
|
||||
const pricesOfProduct = prices.filter(price => price.product_id === product.product_id);
|
||||
for (const priceOfProduct of pricesOfProduct){
|
||||
product.type = priceOfProduct.type;
|
||||
if (priceOfProduct.interval === "year"){
|
||||
product.yearlyPrice = priceOfProduct;
|
||||
} else {
|
||||
product.monthlyPrice = priceOfProduct;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sortedProducts = unsortedProducts.sort((a: Product, b: Product) => a.product_order - b.product_order)
|
||||
return sortedProducts;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
"use client"
|
||||
|
||||
import React from "react";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
import Newsletter from "@/sections/Newsletter/Newsletter";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import Footer from "@/components/Footer";
|
||||
import Payment from "@/sections/Payment";
|
||||
import Spacer from "@/components/Utilities/Spacer";
|
||||
import SolidBackgrondIconFeature from "@/sections/Features/SolidBackgrondIconFeature";
|
||||
import React from "react"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import Payment from "@/sections/Payment"
|
||||
import Newsletter from "@/sections/Newsletter/Newsletter"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Footer from "@/components/Footer"
|
||||
import Spacer from "@/components/Utilities/Spacer"
|
||||
import SolidBackgrondIconFeature from "@/sections/Features/SolidBackgrondIconFeature"
|
||||
|
||||
export default function PricingPage() {
|
||||
return (
|
||||
|
@ -39,10 +39,10 @@ export default function PricingPage() {
|
|||
|
||||
<Payment type="one_time" />
|
||||
<Spacer className="mt-auto" />
|
||||
<Newsletter />
|
||||
<Newsletter/>
|
||||
<Spacer className="mb-8" />
|
||||
<Footer />
|
||||
</Background>
|
||||
</main>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
"use client"
|
||||
|
||||
import Footer from "@/components/Footer";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import Spacer from "@/components/Utilities/Spacer";
|
||||
import SolidBackgrondIconFeature from "@/sections/Features/SolidBackgrondIconFeature";
|
||||
import PageHeader from "@/sections/PageHeader";
|
||||
import React from "react";
|
||||
import Footer from "@/components/Footer"
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Spacer from "@/components/Utilities/Spacer"
|
||||
import SolidBackgrondIconFeature from "@/sections/Features/SolidBackgrondIconFeature"
|
||||
import PageHeader from "@/sections/PageHeader"
|
||||
import React from "react"
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
|
@ -28,7 +28,7 @@ const page = () => {
|
|||
<Footer />
|
||||
</Background>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default page;
|
||||
export default page
|
|
@ -0,0 +1,287 @@
|
|||
"use server"
|
||||
import { redirect } from "next/navigation"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { cookies } from "next/headers"
|
||||
import CryptoJS from "crypto-js"
|
||||
|
||||
import { apiPrices } from "./(public)/pricing/action"
|
||||
|
||||
export async function mailchimp(formData) {
|
||||
const email = formData.email
|
||||
const mailchimpBaseUrl = process.env.NEXT_PUBLIC_MAILCHIMP_BASE_URL
|
||||
const mailchimpApiKey = process.env.NEXT_PUBLIC_MAILCHIMP_BASE64_API_KEY
|
||||
const mailchimpList = process.env.NEXT_PUBLIC_MAILCHIMP_LIST_ID
|
||||
if (!mailchimpApiKey) return
|
||||
try {
|
||||
const subscriberHash = CryptoJS.MD5(email.toLocaleLowerCase())
|
||||
const mailchimpResponse = await fetch(
|
||||
`${mailchimpBaseUrl}/3.0/lists/${mailchimpList}/members/${subscriberHash}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: mailchimpApiKey
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email_address: email,
|
||||
status: "subscribed",
|
||||
merge_fields: {
|
||||
EMAIL: formData.email,
|
||||
FNAME: formData.first_name,
|
||||
LNAME: formData.last_name,
|
||||
PHONE: !formData.phone_number ? "" : formData.phone_number,
|
||||
CSIZE: formData.company_size,
|
||||
SOURCE: formData.source
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
if (mailchimpResponse.status !== 200) {
|
||||
throw new Error("couldn't complete the request")
|
||||
}
|
||||
return mailchimpResponse.json()
|
||||
} catch (err) {
|
||||
throw new Error("couldn't complete the request")
|
||||
}
|
||||
}
|
||||
|
||||
export async function signup(formData) {
|
||||
const email = formData.email
|
||||
const password = formData.password
|
||||
const organisation = formData.organisation
|
||||
console.log("app/(authenticated)/actions", "organisation", organisation)
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL
|
||||
const adminToken = process.env.NEXT_PUBLIC_POCKETBASE_ADMIN_TOKEN
|
||||
try {
|
||||
const orgRes = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/organisation/records`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: adminToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: organisation
|
||||
})
|
||||
}
|
||||
)
|
||||
console.log("orgRes.status: ", orgRes.status)
|
||||
if (orgRes.status !== 200) {
|
||||
throw new Error("Failed to create organisation")
|
||||
}
|
||||
const orgData = await orgRes.json()
|
||||
console.log(orgData)
|
||||
const userRes = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/user/records`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: adminToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
firstName: formData.first_name,
|
||||
lastName: formData.last_name,
|
||||
displayName: formData.first_name + " " + formData.last_name,
|
||||
email: email,
|
||||
password: password,
|
||||
passwordConfirm: password,
|
||||
organisation: orgData?.id,
|
||||
role: "Admin",
|
||||
lastSeen: new Date()
|
||||
})
|
||||
}
|
||||
)
|
||||
console.log("userRes.status: ", userRes.status)
|
||||
if (userRes.status !== 200) {
|
||||
console.log(userRes)
|
||||
throw new Error("Failed to create user")
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw new Error(err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function login(formData) {
|
||||
console.log("login")
|
||||
const email = formData.email
|
||||
const password = formData.password
|
||||
try {
|
||||
const { token, record: data } = await pb
|
||||
.collection("user")
|
||||
.authWithPassword(email, password)
|
||||
if (pb.authStore.isValid) {
|
||||
const cookie = pb.authStore.exportToCookie()
|
||||
|
||||
cookies().set("pb_auth", cookie, {
|
||||
secure: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
httpOnly: true
|
||||
})
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
error: "Failed to log in",
|
||||
token: token,
|
||||
data: data
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
if (error.status == 403) {
|
||||
await pb.collection("user").requestVerification(email)
|
||||
}
|
||||
return JSON.parse(JSON.stringify(error))
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAuthCookie() {
|
||||
try {
|
||||
const cookie = cookies().get("pb_auth")
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
return pb.authStore.token
|
||||
} catch (error) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export async function isAuthenticated() {
|
||||
try {
|
||||
const cookie = cookies().get("pb_auth")
|
||||
if (!cookie) return false
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
return pb.authStore.isValid || false
|
||||
} catch (error) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
export async function getUser() {
|
||||
try {
|
||||
const cookie = cookies().get("pb_auth")
|
||||
if (!cookie) return false
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
return pb.authStore.model
|
||||
} catch (error) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
cookies().delete("pb_auth")
|
||||
redirect("/")
|
||||
}
|
||||
|
||||
export async function createCheckoutSession(price_id, type) {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error("Connection Timeout")
|
||||
}
|
||||
if (!price_id) {
|
||||
throw Error("There was an error during the payment processing")
|
||||
}
|
||||
const token = await getAuthCookie()
|
||||
if (!token) {
|
||||
throw Error("Could not authenticate")
|
||||
}
|
||||
console.log("token", token)
|
||||
console.log("url ", `${pocketbaseUrl}/create-checkout-session`)
|
||||
|
||||
const body = JSON.stringify({
|
||||
price: {
|
||||
id: price_id,
|
||||
type: type
|
||||
},
|
||||
quantity: 1
|
||||
})
|
||||
console.log("body", body)
|
||||
|
||||
try {
|
||||
const createCheckoutSessionResponse = await fetch(
|
||||
`${pocketbaseUrl}/create-checkout-session`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: token
|
||||
},
|
||||
body: body
|
||||
}
|
||||
)
|
||||
if (createCheckoutSessionResponse.status !== 200) {
|
||||
throw new Error("Failed to process Request")
|
||||
}
|
||||
|
||||
const createCheckoutSessionData = await createCheckoutSessionResponse.json()
|
||||
|
||||
if (createCheckoutSessionData.url === "") {
|
||||
throw new Error("Failed to process request an invalid URL was served")
|
||||
}
|
||||
return createCheckoutSessionData
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSubscriptions() {
|
||||
const cookie = cookies().get("pb_auth")
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
const userId = pb.authStore.model.id
|
||||
const subscriptions = await pb
|
||||
.collection("subscription")
|
||||
.getFullList({ filter: `user_id="${userId}" && status="active"` })
|
||||
console.log("app/(authenticated)/actions", "subscriptions", subscriptions)
|
||||
if (subscriptions.length === 0) {
|
||||
return []
|
||||
}
|
||||
const products = await apiPrices()
|
||||
const subscriptionWithProducts = subscriptions.map(subscription => {
|
||||
return {
|
||||
...subscription,
|
||||
product: products.find(
|
||||
product =>
|
||||
product?.yearlyPrice?.price_id === subscription.price_id ||
|
||||
product?.monthlyPrice?.price_id === subscription.price_id
|
||||
)
|
||||
}
|
||||
})
|
||||
return subscriptionWithProducts
|
||||
}
|
||||
|
||||
export async function createManagementSubscriptionSession() {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error("Connection Timeout")
|
||||
}
|
||||
const token = await getAuthCookie()
|
||||
if (!token) {
|
||||
throw Error("Could not authenticate")
|
||||
}
|
||||
console.log("token", token)
|
||||
console.log("url ", `${pocketbaseUrl}/create-checkout-session`)
|
||||
try {
|
||||
const createManagementSessionResponse = await fetch(
|
||||
`${pocketbaseUrl}/create-portal-link`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: token
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
}
|
||||
)
|
||||
if (createManagementSessionResponse.status !== 200) {
|
||||
console.log(createManagementSessionResponse.status)
|
||||
throw new Error(
|
||||
JSON.stringify(await createManagementSessionResponse.json())
|
||||
)
|
||||
}
|
||||
const createManagementSessionData = await createManagementSessionResponse.json()
|
||||
return createManagementSessionData
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
|
@ -1,278 +0,0 @@
|
|||
"use server";
|
||||
|
||||
import { redirect } from "next/navigation";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { cookies } from "next/headers";
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
import { CheckoutSession, SignUpForm, SourceModal, Subscription, SubscriptionSession, User } from "@/types";
|
||||
import { apiPrices } from "./(public)/pricing/actions";
|
||||
|
||||
export async function mailchimp(formData: {
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
phone_number?: string;
|
||||
company_size: string;
|
||||
source?: SourceModal;
|
||||
}) {
|
||||
const email = formData.email;
|
||||
const mailchimpBaseUrl = process.env.NEXT_PUBLIC_MAILCHIMP_BASE_URL;
|
||||
const mailchimpApiKey = process.env.NEXT_PUBLIC_MAILCHIMP_BASE64_API_KEY;
|
||||
const mailchimpList = process.env.NEXT_PUBLIC_MAILCHIMP_LIST_ID;
|
||||
if (!mailchimpApiKey) return;
|
||||
try {
|
||||
const subscriberHash = CryptoJS.MD5(email.toLocaleLowerCase());
|
||||
const mailchimpResponse = await fetch(`${mailchimpBaseUrl}/3.0/lists/${mailchimpList}/members/${subscriberHash}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: mailchimpApiKey,
|
||||
},
|
||||
body: JSON.stringify(
|
||||
{
|
||||
"email_address": email,
|
||||
status: "subscribed",
|
||||
merge_fields: {
|
||||
EMAIL: formData.email,
|
||||
FNAME: formData.first_name,
|
||||
LNAME: formData.last_name,
|
||||
PHONE: !formData.phone_number ? '' : formData.phone_number,
|
||||
CSIZE: formData.company_size,
|
||||
SOURCE: formData.source
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
if (mailchimpResponse.status !== 200) {
|
||||
throw new Error("couldn't complete the request");
|
||||
}
|
||||
return mailchimpResponse.json();
|
||||
} catch (err) {
|
||||
throw new Error("couldn't complete the request");
|
||||
}
|
||||
}
|
||||
|
||||
export async function signup(formData: SignUpForm) {
|
||||
const email = formData.email;
|
||||
const password = formData.password;
|
||||
const organisation = formData.organisation;
|
||||
console.log('app/(authenticated)/actions', 'organisation', organisation)
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL as string;
|
||||
const adminToken = process.env.NEXT_PUBLIC_POCKETBASE_ADMIN_TOKEN as string;
|
||||
try {
|
||||
const orgRes = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/organisation/records`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: adminToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: organisation,
|
||||
}),
|
||||
}
|
||||
);
|
||||
console.log("orgRes.status: ", orgRes.status);
|
||||
if (orgRes.status !== 200) {
|
||||
throw new Error("Failed to create organisation");
|
||||
}
|
||||
const orgData = await orgRes.json();
|
||||
console.log(orgData);
|
||||
const userRes = await fetch(
|
||||
`${pocketbaseUrl}/api/collections/user/records`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: adminToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
firstName: formData.first_name,
|
||||
lastName: formData.last_name,
|
||||
displayName: formData.first_name + ' ' + formData.last_name,
|
||||
email: email,
|
||||
password: password,
|
||||
passwordConfirm: password,
|
||||
organisation: orgData?.id,
|
||||
role: "Admin",
|
||||
lastSeen: new Date(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
console.log("userRes.status: ", userRes.status);
|
||||
if (userRes.status !== 200) {
|
||||
console.log(userRes);
|
||||
throw new Error("Failed to create user");
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw new Error(err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function login(formData: { email: string; password: string }) {
|
||||
console.log('login')
|
||||
const email = formData.email as string;
|
||||
const password = formData.password as string;
|
||||
try {
|
||||
const { token, record: data } = await pb
|
||||
.collection("user")
|
||||
.authWithPassword(email, password);
|
||||
if (pb.authStore.isValid) {
|
||||
const cookie = pb.authStore.exportToCookie();
|
||||
|
||||
cookies().set("pb_auth", cookie, {
|
||||
secure: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
httpOnly: true,
|
||||
});
|
||||
}
|
||||
return { success: true, error: "Failed to log in", token: token, data: data };
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
if ((error as any).status == 403) {
|
||||
await pb.collection('user').requestVerification(email);
|
||||
}
|
||||
return JSON.parse(JSON.stringify(error))
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAuthCookie() {
|
||||
try {
|
||||
const cookie = cookies().get('pb_auth');
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
return pb.authStore.token;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function isAuthenticated() {
|
||||
try {
|
||||
const cookie = cookies().get('pb_auth');
|
||||
if(!cookie) return false;
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
return pb.authStore.isValid || false;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
export async function getUser() {
|
||||
try {
|
||||
const cookie = cookies().get('pb_auth');
|
||||
if(!cookie) return false;
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
return pb.authStore.model;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
cookies().delete("pb_auth");
|
||||
redirect('/');
|
||||
}
|
||||
|
||||
export async function createCheckoutSession(price_id: string, type: string) {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL;
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error('Connection Timeout');
|
||||
}
|
||||
if (!price_id) {
|
||||
throw Error('There was an error during the payment processing');
|
||||
}
|
||||
const token = await getAuthCookie();
|
||||
if (!token) {
|
||||
throw Error('Could not authenticate');
|
||||
}
|
||||
console.log('token', token);
|
||||
console.log('url ', `${pocketbaseUrl}/create-checkout-session`);
|
||||
|
||||
const body = JSON.stringify({
|
||||
price: {
|
||||
id: price_id,
|
||||
type: type
|
||||
},
|
||||
quantity: 1
|
||||
});
|
||||
console.log('body',body);
|
||||
|
||||
try{
|
||||
const createCheckoutSessionResponse = await fetch(
|
||||
`${pocketbaseUrl}/create-checkout-session`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: token,
|
||||
},
|
||||
body: body,
|
||||
}
|
||||
);
|
||||
if (createCheckoutSessionResponse.status !== 200) {
|
||||
throw new Error("Failed to process Request");
|
||||
}
|
||||
|
||||
const createCheckoutSessionData: CheckoutSession = await createCheckoutSessionResponse.json();
|
||||
|
||||
if (createCheckoutSessionData.url === "") {
|
||||
throw new Error("Failed to process request an invalid URL was served");
|
||||
}
|
||||
return createCheckoutSessionData;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSubscriptions() {
|
||||
const cookie = cookies().get('pb_auth');
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
const userId = (pb.authStore.model as User).id
|
||||
const subscriptions = await pb.collection("subscription").getFullList<Subscription>({filter: `user_id="${userId}" && status="active"`});
|
||||
console.log('app/(authenticated)/actions', 'subscriptions', subscriptions)
|
||||
if (subscriptions.length === 0){
|
||||
return [];
|
||||
}
|
||||
const products = await apiPrices();
|
||||
const subscriptionWithProducts = subscriptions.map(subscription => {return {...subscription, product: products.find(product => product?.yearlyPrice?.price_id === subscription.price_id || product?.monthlyPrice?.price_id === subscription.price_id)}}) as Subscription[]
|
||||
return subscriptionWithProducts;
|
||||
}
|
||||
|
||||
export async function createManagementSubscriptionSession() {
|
||||
const pocketbaseUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL;
|
||||
if (!pocketbaseUrl) {
|
||||
throw Error('Connection Timeout');
|
||||
}
|
||||
const token = await getAuthCookie();
|
||||
if (!token) {
|
||||
throw Error('Could not authenticate');
|
||||
}
|
||||
console.log('token', token);
|
||||
console.log('url ', `${pocketbaseUrl}/create-checkout-session`);
|
||||
try{
|
||||
const createManagementSessionResponse = await fetch(
|
||||
`${pocketbaseUrl}/create-portal-link`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: token,
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
}
|
||||
);
|
||||
if (createManagementSessionResponse.status !== 200) {
|
||||
console.log(createManagementSessionResponse.status)
|
||||
throw new Error(JSON.stringify( await createManagementSessionResponse.json()));
|
||||
}
|
||||
const createManagementSessionData: SubscriptionSession = await createManagementSessionResponse.json();
|
||||
return createManagementSessionData;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
|
@ -1,40 +1,35 @@
|
|||
/* 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 { 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 +41,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 +74,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 +82,7 @@ export default async function RootLayout({
|
|||
id="promotekit"
|
||||
async
|
||||
defer
|
||||
strategy="afterInteractive"
|
||||
strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
$(document).ready(function(){
|
||||
|
@ -103,11 +101,11 @@ export default async function RootLayout({
|
|||
}, 2000);
|
||||
});
|
||||
|
||||
`,
|
||||
`
|
||||
}}
|
||||
></Script>
|
||||
</body>
|
||||
</PHProvider>
|
||||
</html>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
import Footer from "@/components/Footer";
|
||||
import Background from "@/components/Utilities/Background";
|
||||
import PageWrapper from "@/components/Utilities/PageWrapper";
|
||||
import Link from "next/link";
|
||||
import Background from "@/components/Utilities/Background"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
|
@ -70,5 +68,5 @@ export default function NotFound() {
|
|||
</Background>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -1,22 +1,17 @@
|
|||
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 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";
|
||||
|
||||
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 +21,7 @@ export const metadata: Metadata = {
|
|||
"next.js",
|
||||
"boilerplate",
|
||||
"template",
|
||||
"codebase",
|
||||
"codebase"
|
||||
],
|
||||
openGraph: {
|
||||
url: "https://fastpocket.dev",
|
||||
|
@ -39,9 +34,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,24 +50,19 @@ 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 (
|
||||
<>
|
||||
<PageWrapper>
|
||||
<SquaredBackgroundHero />
|
||||
|
||||
<Teste/>
|
||||
|
||||
|
||||
<SquaredBackgroundHero />
|
||||
<VerticalTabsFeature />
|
||||
<div className="bg-primary/2">
|
||||
|
@ -105,5 +95,5 @@ export default function Home() {
|
|||
<Footer />
|
||||
</PageWrapper>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
"use client";
|
||||
import posthog from 'posthog-js'
|
||||
import { PostHogProvider } from 'posthog-js/react'
|
||||
import GoogleAnalytics from "@/components/GoogleAnalytics";
|
||||
import { ThemeProvider as NTThemeProvider } from 'next-themes'
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import getPostMetadata from "@/utils/getPostMetaData"
|
||||
|
||||
export default async function sitemap() {
|
||||
const defaultPages = [
|
||||
{
|
||||
url: "https://fastpocket.dev",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "daily",
|
||||
priority: 1
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/about",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/pricing",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/blogs",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/whatsnew",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
}
|
||||
// other pages
|
||||
]
|
||||
|
||||
const postSlugs = await getPostMetadata()
|
||||
|
||||
const sitemap = [
|
||||
...defaultPages,
|
||||
...postSlugs.map(e => ({
|
||||
url: `https://fastpocket.dev/blogs/${e.slug}`,
|
||||
lastModified: e.modified,
|
||||
changeFrequency: "daily",
|
||||
priority: 0.8
|
||||
}))
|
||||
]
|
||||
|
||||
return sitemap
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
import getPostMetadata from "@/utils/getPostMetaData";
|
||||
import { MetadataRoute } from "next";
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const defaultPages = [
|
||||
{
|
||||
url: "https://fastpocket.dev",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "daily",
|
||||
priority: 1
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/about",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/pricing",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/blogs",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
},
|
||||
{
|
||||
url: "https://fastpocket.dev/whatsnew",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.9
|
||||
}
|
||||
// other pages
|
||||
];
|
||||
|
||||
const postSlugs = await getPostMetadata();
|
||||
|
||||
const sitemap = [
|
||||
...defaultPages,
|
||||
...postSlugs.map((e: any) => ({
|
||||
url: `https://fastpocket.dev/blogs/${e.slug}`,
|
||||
lastModified: e.modified,
|
||||
changeFrequency: "daily",
|
||||
priority: 0.8
|
||||
} as any))
|
||||
];
|
||||
|
||||
return sitemap;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
function Page() {
|
||||
return (
|
||||
<>
|
||||
Qualquercoisa
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
|
@ -1,20 +1,9 @@
|
|||
import React from "react";
|
||||
import React from "react"
|
||||
|
||||
import { PostMetadata } from "@/types";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
function BlogCard({
|
||||
title,
|
||||
slug,
|
||||
subtitle,
|
||||
imageUrl,
|
||||
}: {
|
||||
title: string;
|
||||
slug: string;
|
||||
subtitle: string;
|
||||
imageUrl: string;
|
||||
}) {
|
||||
function BlogCard({ title, slug, subtitle, imageUrl }) {
|
||||
return (
|
||||
<>
|
||||
<Link
|
||||
|
@ -37,7 +26,7 @@ function BlogCard({
|
|||
</div>
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogCard;
|
||||
export default BlogCard
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from "react";
|
||||
const SvgComponent = (props: any) => (
|
||||
import * as React from "react"
|
||||
const SvgComponent = props => (
|
||||
<a href="https://fastpocket.dev">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -43,5 +43,5 @@ const SvgComponent = (props: any) => (
|
|||
/>
|
||||
</svg>
|
||||
</a>
|
||||
);
|
||||
export default SvgComponent;
|
||||
)
|
||||
export default SvgComponent
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import { title } from "@/constants";
|
||||
import Logo from "@/components/Logo";
|
||||
import Link from "next/link";
|
||||
import React from "react"
|
||||
import { title } from "@/constants"
|
||||
import Logo from "@/components/Logo"
|
||||
import Link from "next/link"
|
||||
import FastPocketBadge from "@/components/FastPocketBadge"
|
||||
|
||||
function Footer() {
|
||||
|
@ -253,7 +253,7 @@ function Footer() {
|
|||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
export default Footer
|
|
@ -1,18 +1,13 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Subscription, User } from "@/types";
|
||||
import { getSubscriptions } from "@/app/actions";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface GetStartedSectionButtonProps {
|
||||
user: User;
|
||||
}
|
||||
export const GetStartedSectionButton = ({
|
||||
user,
|
||||
}: GetStartedSectionButtonProps) => {
|
||||
export const GetStartedSectionButton = ({ user }) => {
|
||||
const router = useRouter();
|
||||
const [subscription, setSubscription] = useState<Subscription>();
|
||||
const [subscription, setSubscription] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!user) {
|
||||
|
@ -37,4 +32,4 @@ export const GetStartedSectionButton = ({
|
|||
);
|
||||
};
|
||||
|
||||
export default GetStartedSectionButton;
|
||||
export default GetStartedSectionButton;
|
|
@ -1,6 +1,6 @@
|
|||
import Script from "next/script";
|
||||
import Script from "next/script"
|
||||
|
||||
const GoogleAnalytics = ({ ga_id }: { ga_id: string }) => (
|
||||
const GoogleAnalytics = ({ ga_id }) => (
|
||||
<>
|
||||
<Script
|
||||
async
|
||||
|
@ -20,9 +20,9 @@ const GoogleAnalytics = ({ ga_id }: { ga_id: string }) => (
|
|||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '${ga_id}');
|
||||
`,
|
||||
`
|
||||
}}
|
||||
></Script>
|
||||
</>
|
||||
);
|
||||
export default GoogleAnalytics;
|
||||
)
|
||||
export default GoogleAnalytics
|
|
@ -0,0 +1,28 @@
|
|||
import Script from "next/script"
|
||||
|
||||
const GoogleAnalytics = ({ ga_id }) => (
|
||||
<>
|
||||
<Script
|
||||
async
|
||||
defer
|
||||
strategy="afterInteractive"
|
||||
src={`https://www.googletagmanager.com/gtag/js?
|
||||
id=${ga_id}`}
|
||||
></Script>
|
||||
<Script
|
||||
id="google-analytics"
|
||||
defer
|
||||
strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '${ga_id}');
|
||||
`
|
||||
}}
|
||||
></Script>
|
||||
</>
|
||||
)
|
||||
export default GoogleAnalytics
|
|
@ -1,162 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import Navigation from "@/components/Navigation";
|
||||
import ModalSignUp from "@/components/Modals/ModalSignUp";
|
||||
import ModalSignIn from "@/components/Modals/ModalSignIn";
|
||||
import { getAuthCookie, getUser, logout } from "@/app/actions";
|
||||
import { User } from "@/types";
|
||||
import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { useTheme } from "next-themes";
|
||||
import Link from "next/link";
|
||||
import ModalPasswordReset from "./Modals/ModalPasswordReset";
|
||||
import {Person24Filled, SignOut24Filled} from '@fluentui/react-icons'
|
||||
import ModalEmailChange from "./Modals/ModalEmailChange";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
interface HeaderProps {
|
||||
isUserLoggedIn: boolean;
|
||||
authString: string;
|
||||
}
|
||||
|
||||
export default function Header({ isUserLoggedIn, authString }: HeaderProps) {
|
||||
//Advice: Remove this and replace it with proper state management like Redux, Recoil or Zustand
|
||||
const [user, setUser] = useState<User | undefined>();
|
||||
const [statefulUser, setStatefulUser] = useState<User | undefined>();
|
||||
const pathName = usePathname();
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const user = await getUser();
|
||||
setUser(user as User);
|
||||
})();
|
||||
}, [statefulUser]);
|
||||
|
||||
|
||||
const { theme, setTheme } = useTheme();
|
||||
return (
|
||||
<header className="absolute w-full z-30">
|
||||
<div className="max-w-5xl mx-auto px-4 sm:px-6 md:pt-6">
|
||||
<div className="flex items-center justify-between h-20">
|
||||
<Navigation isUserLoggedIn={isUserLoggedIn} />
|
||||
|
||||
{/* Desktop navigation */}
|
||||
<nav className=" md:flex">
|
||||
{/* Desktop sign in links */}
|
||||
<div className="flex flex-row grow sm:grow-0 items-center pr-2 gap-x-2">
|
||||
{/* <button
|
||||
className="btn btn-primary"
|
||||
onClick={() => setTheme("dark")}
|
||||
>
|
||||
dark
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => setTheme("light")}
|
||||
>
|
||||
light
|
||||
</button> */}
|
||||
<label className="swap swap-rotate" aria-label="theme-toggle">
|
||||
{/* this hidden checkbox controls the state */}
|
||||
<input
|
||||
type="checkbox"
|
||||
className="opacity-0"
|
||||
aria-label="theme-toggle-input"
|
||||
onClick={() =>
|
||||
theme === "light" ? setTheme("dark") : setTheme("light")
|
||||
}
|
||||
/>
|
||||
|
||||
{/* sun icon */}
|
||||
<svg
|
||||
className="swap-on fill-base-content w-6 h-6 z-10"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
|
||||
</svg>
|
||||
|
||||
{/* moon icon */}
|
||||
<svg
|
||||
className="swap-off fill-base-content w-6 h-6 z-1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
|
||||
</svg>
|
||||
</label>
|
||||
|
||||
{!isUserLoggedIn ? (
|
||||
<>
|
||||
<button
|
||||
onClick={() =>
|
||||
(async (router?: AppRouterInstance) => {
|
||||
console.log("firing");
|
||||
const authCookie = await getAuthCookie();
|
||||
pb.authStore.loadFromCookie(authCookie || "");
|
||||
if (!authCookie || !pb.authStore.isValid) {
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
} else {
|
||||
router?.push("/account");
|
||||
}
|
||||
})()
|
||||
}
|
||||
className={`btn btn-sm btn-ghost text-primary mr-1`}
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
localStorage.getItem("price") &&
|
||||
localStorage.removeItem("price");
|
||||
document.getElementById("sign-up-modal")?.click();
|
||||
}}
|
||||
className={`btn btn-primary btn-sm text-primary-content `}
|
||||
id="sign-up-modal-button"
|
||||
>
|
||||
Get First Access
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div className="dropdown dropdown-end">
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
className="btn btn-ghost btn-circle avatar"
|
||||
>
|
||||
<div className="w-6 rounded-full !flex justify-center items-center bg-base-300">
|
||||
<Person24Filled />
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"
|
||||
>
|
||||
<li>
|
||||
<Link href="/account" aria-label="profile">
|
||||
<div className=" capitalize flex flex-row gap-x-2">
|
||||
<Person24Filled />
|
||||
{user?.displayName}
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
<li onClick={() => logout()}>
|
||||
<div className="capitalize flex flex-row gap-x-2">
|
||||
<SignOut24Filled />
|
||||
Logout
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<ModalSignIn />
|
||||
<ModalPasswordReset />
|
||||
<ModalEmailChange authString={authString} />
|
||||
<ModalSignUp setUser={setStatefulUser} />
|
||||
</header>
|
||||
);
|
||||
}
|
|
@ -1,14 +1,8 @@
|
|||
import React from "react";
|
||||
import React from "react"
|
||||
|
||||
import Image, { ImageProps } from "next/image";
|
||||
import Image from "next/image"
|
||||
|
||||
interface LogoProps {
|
||||
label?: string;
|
||||
className?: string;
|
||||
imageProps?: ImageProps;
|
||||
}
|
||||
|
||||
function Logo({ imageProps, ...props }: LogoProps) {
|
||||
function Logo({ imageProps, ...props }) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
|
@ -26,7 +20,7 @@ function Logo({ imageProps, ...props }: LogoProps) {
|
|||
{...imageProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Logo;
|
||||
export default Logo
|
|
@ -1,31 +1,31 @@
|
|||
import React, { useRef } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { emailValidationSchema } from "@/utils/form";
|
||||
import { toast } from "react-toastify";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import {Dismiss20Filled} from "@fluentui/react-icons"
|
||||
import { redirect } from "next/navigation";
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { emailValidationSchema } from "@/utils/form"
|
||||
import { toast } from "react-toastify"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { Dismiss20Filled } from "@fluentui/react-icons"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
function ModalEmailChange({authString}:{authString:string}) {
|
||||
function ModalEmailChange({ authString }) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm({
|
||||
resolver: yupResolver(emailValidationSchema),
|
||||
});
|
||||
resolver: yupResolver(emailValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
console.log(data);
|
||||
pb.authStore.loadFromCookie(authString);
|
||||
!pb.authStore.isValid && redirect("/");
|
||||
const onSubmit = async data => {
|
||||
console.log(data)
|
||||
pb.authStore.loadFromCookie(authString)
|
||||
!pb.authStore.isValid && redirect("/")
|
||||
try {
|
||||
if (await pb.collection("user").requestEmailChange(data.email)) {
|
||||
reset();
|
||||
document.getElementById("email-change-modal")?.click();
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
reset()
|
||||
document.getElementById("email-change-modal")?.click()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
|
@ -37,11 +37,11 @@ function ModalEmailChange({authString}:{authString:string}) {
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
|
@ -49,7 +49,7 @@ function ModalEmailChange({authString}:{authString:string}) {
|
|||
id="email-change-modal"
|
||||
className="modal-toggle"
|
||||
onClick={() => {
|
||||
reset();
|
||||
reset()
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="email-change-modal" className="modal cursor-pointer">
|
||||
|
@ -88,7 +88,7 @@ function ModalEmailChange({authString}:{authString:string}) {
|
|||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className={isSubmitting ? "btn btn-gray": "btn btn-primary"}
|
||||
className={isSubmitting ? "btn btn-gray" : "btn btn-primary"}
|
||||
>
|
||||
Change Email
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
|
@ -99,7 +99,7 @@ function ModalEmailChange({authString}:{authString:string}) {
|
|||
</label>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalEmailChange;
|
||||
export default ModalEmailChange
|
|
@ -1,30 +1,29 @@
|
|||
import React, { useRef } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { emailValidationSchema } from "@/utils/form";
|
||||
import { toast } from "react-toastify";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import {Dismiss20Filled} from '@fluentui/react-icons'
|
||||
import { redirect } from "next/navigation";
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { emailValidationSchema } from "@/utils/form"
|
||||
import { toast } from "react-toastify"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { Dismiss20Filled } from "@fluentui/react-icons"
|
||||
|
||||
function ModalPasswordReset() {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm({
|
||||
resolver: yupResolver(emailValidationSchema),
|
||||
});
|
||||
resolver: yupResolver(emailValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
console.log(data);
|
||||
const onSubmit = async data => {
|
||||
console.log(data)
|
||||
try {
|
||||
//login user
|
||||
if (await pb.collection("user").requestPasswordReset(data.email)) {
|
||||
reset();
|
||||
document.getElementById("password-reset-modal")?.click();
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
reset()
|
||||
document.getElementById("password-reset-modal")?.click()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
|
@ -36,11 +35,11 @@ function ModalPasswordReset() {
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
|
@ -48,7 +47,7 @@ function ModalPasswordReset() {
|
|||
id="password-reset-modal"
|
||||
className="modal-toggle"
|
||||
onClick={() => {
|
||||
reset();
|
||||
reset()
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="password-reset-modal" className="modal cursor-pointer">
|
||||
|
@ -98,7 +97,7 @@ function ModalPasswordReset() {
|
|||
</label>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalPasswordReset;
|
||||
export default ModalPasswordReset
|
|
@ -1,36 +1,34 @@
|
|||
"use client";
|
||||
"use client"
|
||||
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { signInValidationSchema } from "@/utils/form";
|
||||
import { login } from "@/app/actions";
|
||||
import { toast } from "react-toastify";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {Dismiss20Filled} from '@fluentui/react-icons'
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { signInValidationSchema } from "@/utils/form"
|
||||
import { login } from "@/app/actions"
|
||||
import { toast } from "react-toastify"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Dismiss20Filled } from "@fluentui/react-icons"
|
||||
|
||||
function ModalSignIn() {
|
||||
const router = useRouter();
|
||||
const router = useRouter()
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm({
|
||||
resolver: yupResolver(signInValidationSchema),
|
||||
});
|
||||
resolver: yupResolver(signInValidationSchema)
|
||||
})
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
const onSubmit = async data => {
|
||||
try {
|
||||
//login user
|
||||
const auth = (await login({ email: data.email, password: data.password }))
|
||||
if (
|
||||
auth.success
|
||||
) {
|
||||
reset();
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
router.push("/account");
|
||||
const auth = await login({ email: data.email, password: data.password })
|
||||
if (auth.success) {
|
||||
reset()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
router.push("/account")
|
||||
} else {
|
||||
throw new Error(auth.response.message)
|
||||
}
|
||||
|
@ -44,11 +42,11 @@ function ModalSignIn() {
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
|
@ -56,7 +54,7 @@ function ModalSignIn() {
|
|||
id="sign-in-modal"
|
||||
className="modal-toggle"
|
||||
onClick={() => {
|
||||
reset();
|
||||
reset()
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="sign-in-modal" className="modal cursor-pointer">
|
||||
|
@ -120,8 +118,8 @@ function ModalSignIn() {
|
|||
|
||||
<span
|
||||
onClick={() => {
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
document.getElementById("sign-up-modal")?.click();
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
document.getElementById("sign-up-modal")?.click()
|
||||
}}
|
||||
className="text-primary hover:text-primary/60 cursor-pointer "
|
||||
>
|
||||
|
@ -143,7 +141,7 @@ function ModalSignIn() {
|
|||
</label>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalSignIn;
|
||||
export default ModalSignIn
|
|
@ -0,0 +1,147 @@
|
|||
"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"
|
||||
|
||||
function ModalSignIn() {
|
||||
const router = useRouter()
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm({
|
||||
resolver: yupResolver(signInValidationSchema)
|
||||
})
|
||||
|
||||
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")
|
||||
} else {
|
||||
throw new Error(auth.response.message)
|
||||
}
|
||||
} 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 (
|
||||
<>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="sign-in-modal"
|
||||
className="modal-toggle"
|
||||
onClick={() => {
|
||||
reset()
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="sign-in-modal" className="modal cursor-pointer">
|
||||
<label className="modal-box relative bg-base-100 max-w-full md:max-w-[550px] py-4 px-3 md:p-6">
|
||||
<div className="flex justify-end pb-2 select-none">
|
||||
<label
|
||||
htmlFor="sign-in-modal"
|
||||
className="cursor-pointer text-base-content"
|
||||
>
|
||||
<Dismiss20Filled />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div className="w-[100%] bg-gradient-to-r from-primary to-secondary px-6 pt-2 mt-3 pb-6 rounded-lg text-primary-content">
|
||||
<h3 className="pb-1 text-3xl font-bold md:text-3xl pt-6">
|
||||
Welcome back!
|
||||
</h3>
|
||||
<p className="text-sm md:text-base">Lets kick some more ass?</p>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="w-full">
|
||||
<div className="relative mt-6">
|
||||
<input
|
||||
type="text"
|
||||
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="Your email…"
|
||||
aria-label="Your email…"
|
||||
autoComplete="on"
|
||||
{...register("email")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.email?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-1 mb-2 ">
|
||||
<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="Your password..."
|
||||
aria-label="Your password..."
|
||||
{...register("password")}
|
||||
/>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
{errors.password?.message}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row w-full justify-between">
|
||||
<div className="flex flex-row gap-x-4">
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
>
|
||||
Sign In
|
||||
{isSubmitting && <div className="loading"></div>}
|
||||
</button>
|
||||
<div className=" text-xs w-28 block">
|
||||
<span className="whitespace-normal">
|
||||
Dont have an account?{" "}
|
||||
</span>
|
||||
|
||||
<span
|
||||
onClick={() => {
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
document.getElementById("sign-up-modal")?.click()
|
||||
}}
|
||||
className="text-primary hover:text-primary/60 cursor-pointer "
|
||||
>
|
||||
Sign up
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() =>
|
||||
document.getElementById("password-reset-modal")?.click()
|
||||
}
|
||||
className="btn btn-ghost text-primary capitalize border-none"
|
||||
>
|
||||
Reset Password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</label>
|
||||
</label>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalSignIn
|
|
@ -1,39 +1,33 @@
|
|||
"use client";
|
||||
"use client"
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import { signUpValidationSchema } from "@/utils/form"
|
||||
import { companySizeList } from "@/constants"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { createCheckoutSession, login } from "@/app/actions"
|
||||
import { toast } from "react-toastify"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Dismiss20Filled } from "@fluentui/react-icons"
|
||||
|
||||
import React, { Dispatch, SetStateAction } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { signUpValidationSchema } from "@/utils/form";
|
||||
import { companySizeList } from "@/constants";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { createCheckoutSession, login } from "@/app/actions";
|
||||
import { toast } from "react-toastify";
|
||||
import { Price, User } from "@/types";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {Dismiss20Filled} from '@fluentui/react-icons'
|
||||
|
||||
function ModalSignUp({
|
||||
setUser,
|
||||
}: {
|
||||
setUser: Dispatch<SetStateAction<User | undefined>>;
|
||||
}) {
|
||||
function ModalSignUp({ setUser }) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm({
|
||||
resolver: yupResolver(signUpValidationSchema),
|
||||
});
|
||||
const router = useRouter();
|
||||
const generateCheckoutPage = async (price: Price, type: string) => {
|
||||
resolver: yupResolver(signUpValidationSchema)
|
||||
})
|
||||
const router = useRouter()
|
||||
const generateCheckoutPage = async (price, type) => {
|
||||
try {
|
||||
const checkoutSessionResponse = await createCheckoutSession(
|
||||
price.price_id,
|
||||
type
|
||||
);
|
||||
console.log(checkoutSessionResponse);
|
||||
router.push(checkoutSessionResponse.url);
|
||||
)
|
||||
console.log(checkoutSessionResponse)
|
||||
router.push(checkoutSessionResponse.url)
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message, {
|
||||
|
@ -44,53 +38,51 @@ function ModalSignUp({
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
const onSubmit = async (data: any) => {
|
||||
}
|
||||
const onSubmit = async data => {
|
||||
data = {
|
||||
emailVisibility: false,
|
||||
lastSeen: new Date(),
|
||||
role: "Admin",
|
||||
displayName: `${data.firstName} ${data.lastName}`,
|
||||
passwordConfirm: data.password,
|
||||
...data,
|
||||
};
|
||||
...data
|
||||
}
|
||||
try {
|
||||
//create organisation
|
||||
const organisation = await pb.collection("organisation").create({
|
||||
name: data.organisation,
|
||||
organisationSize: data.organisationSize,
|
||||
});
|
||||
organisationSize: data.organisationSize
|
||||
})
|
||||
//create user
|
||||
const user = await pb
|
||||
.collection("user")
|
||||
.create({ ...data, organisation: organisation.id });
|
||||
.create({ ...data, organisation: organisation.id })
|
||||
//login user
|
||||
const auth = (await login({ email: data.email, password: data.password }))
|
||||
if (
|
||||
auth.success
|
||||
) {
|
||||
reset();
|
||||
document.getElementById("sign-up-modal")?.click();
|
||||
const price = localStorage.getItem("price");
|
||||
const type = localStorage.getItem("type");
|
||||
setUser(user as User);
|
||||
console.log("price", price);
|
||||
console.log("type", type);
|
||||
const auth = await login({ email: data.email, password: data.password })
|
||||
if (auth.success) {
|
||||
reset()
|
||||
document.getElementById("sign-up-modal")?.click()
|
||||
const price = localStorage.getItem("price")
|
||||
const type = localStorage.getItem("type")
|
||||
setUser(user)
|
||||
console.log("price", price)
|
||||
console.log("type", type)
|
||||
price
|
||||
? generateCheckoutPage(JSON.parse(price), type ?? "")
|
||||
: router.push("/account");
|
||||
: router.push("/account")
|
||||
} else {
|
||||
throw new Error(auth.response.message)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(
|
||||
Object.values((error as any).data.data)
|
||||
.map((x: any) => x.message)
|
||||
Object.values(error.data.data)
|
||||
.map(x => x.message)
|
||||
.join(),
|
||||
{
|
||||
position: "bottom-left",
|
||||
|
@ -100,12 +92,12 @@ function ModalSignUp({
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
theme: "colored"
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
|
@ -114,7 +106,7 @@ function ModalSignUp({
|
|||
className="modal-toggle"
|
||||
name=""
|
||||
onClick={() => {
|
||||
reset();
|
||||
reset()
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="sign-up-modal" className="modal cursor-pointer">
|
||||
|
@ -124,7 +116,7 @@ function ModalSignUp({
|
|||
htmlFor="sign-up-modal"
|
||||
className="cursor-pointer text-base-content"
|
||||
onClick={() => {
|
||||
reset();
|
||||
reset()
|
||||
}}
|
||||
>
|
||||
<Dismiss20Filled />
|
||||
|
@ -225,7 +217,7 @@ function ModalSignUp({
|
|||
>
|
||||
{companySizeOption}
|
||||
</option>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
<div className="text-start text-sm italic text-error-content">
|
||||
|
@ -265,8 +257,8 @@ function ModalSignUp({
|
|||
|
||||
<span
|
||||
onClick={() => {
|
||||
document.getElementById("sign-up-modal")?.click();
|
||||
document.getElementById("sign-in-modal")?.click();
|
||||
document.getElementById("sign-up-modal")?.click()
|
||||
document.getElementById("sign-in-modal")?.click()
|
||||
}}
|
||||
className="text-primary hover:text-primary/60 cursor-pointer "
|
||||
>
|
||||
|
@ -279,7 +271,7 @@ function ModalSignUp({
|
|||
</label>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalSignUp;
|
||||
export default ModalSignUp
|
|
@ -1,15 +1,15 @@
|
|||
"use client";
|
||||
"use client"
|
||||
|
||||
import Logo from "@/components/Logo";
|
||||
import React, { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import {Navigation24Filled} from "@fluentui/react-icons"
|
||||
import Logo from "@/components/Logo"
|
||||
import React, { useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { Navigation24Filled } from "@fluentui/react-icons"
|
||||
|
||||
function Navigation({ isUserLoggedIn }: { isUserLoggedIn: boolean }) {
|
||||
const [checked, setChecked] = useState<boolean>();
|
||||
function Navigation({ isUserLoggedIn }) {
|
||||
const [checked, setChecked] = useState()
|
||||
const handleClick = () => {
|
||||
checked ? setChecked(!checked) : setChecked(checked);
|
||||
};
|
||||
checked ? setChecked(!checked) : setChecked(checked)
|
||||
}
|
||||
return (
|
||||
<div className="drawer max-w-fit">
|
||||
<input
|
||||
|
@ -23,10 +23,7 @@ function Navigation({ isUserLoggedIn }: { isUserLoggedIn: boolean }) {
|
|||
{/* Navbar */}
|
||||
<div className="flex-grow navbar ">
|
||||
<div className="flex-none lg:hidden">
|
||||
<label
|
||||
htmlFor="my-drawer-3"
|
||||
className="items-center"
|
||||
>
|
||||
<label htmlFor="my-drawer-3" className="items-center">
|
||||
<Navigation24Filled />
|
||||
</label>
|
||||
</div>
|
||||
|
@ -107,7 +104,7 @@ function Navigation({ isUserLoggedIn }: { isUserLoggedIn: boolean }) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Navigation;
|
||||
export default Navigation
|
|
@ -1,34 +1,24 @@
|
|||
"use client";
|
||||
"use client"
|
||||
import { createCheckoutSession, isAuthenticated } from "@/app/actions"
|
||||
import { toast } from "react-toastify"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { CheckmarkCircle24Filled } from "@fluentui/react-icons"
|
||||
|
||||
import { Price, Product, SourceModal } from "@/types";
|
||||
import { createCheckoutSession, isAuthenticated } from "@/app/actions";
|
||||
import { toast } from "react-toastify";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {CheckmarkCircle24Filled } from "@fluentui/react-icons"
|
||||
|
||||
export default function PriceCard({
|
||||
product,
|
||||
isAnnual,
|
||||
loading,
|
||||
}: {
|
||||
product?: Product;
|
||||
isAnnual?: boolean;
|
||||
loading?: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const openSignUpModalOnPriceClick = (price: Price, type: string) => {
|
||||
const signUpModal = document.getElementById("sign-up-modal");
|
||||
if (!signUpModal) return;
|
||||
signUpModal.click();
|
||||
};
|
||||
const generateCheckoutPage = async (price: Price, type: string) => {
|
||||
export default function PriceCard({ product, isAnnual, loading }) {
|
||||
const router = useRouter()
|
||||
const openSignUpModalOnPriceClick = (price, type) => {
|
||||
const signUpModal = document.getElementById("sign-up-modal")
|
||||
if (!signUpModal) return
|
||||
signUpModal.click()
|
||||
}
|
||||
const generateCheckoutPage = async (price, type) => {
|
||||
try {
|
||||
const checkoutSessionResponse = await createCheckoutSession(
|
||||
price.price_id,
|
||||
type
|
||||
);
|
||||
console.log(checkoutSessionResponse);
|
||||
router.push(checkoutSessionResponse.url);
|
||||
)
|
||||
console.log(checkoutSessionResponse)
|
||||
router.push(checkoutSessionResponse.url)
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message, {
|
||||
|
@ -39,25 +29,25 @@ export default function PriceCard({
|
|||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "colored",
|
||||
});
|
||||
theme: "colored"
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
const submitForm = async (product: Product) => {
|
||||
const userIsAuthenticated = await isAuthenticated();
|
||||
console.log("userIsAuthenticated", userIsAuthenticated);
|
||||
const price = isAnnual ? product.yearlyPrice : product.monthlyPrice;
|
||||
console.log("price", price);
|
||||
console.log("product.type", product.type);
|
||||
localStorage.setItem("price", JSON.stringify(price));
|
||||
localStorage.setItem("type", product.type);
|
||||
}
|
||||
const submitForm = async product => {
|
||||
const userIsAuthenticated = await isAuthenticated()
|
||||
console.log("userIsAuthenticated", userIsAuthenticated)
|
||||
const price = isAnnual ? product.yearlyPrice : product.monthlyPrice
|
||||
console.log("price", price)
|
||||
console.log("product.type", product.type)
|
||||
localStorage.setItem("price", JSON.stringify(price))
|
||||
localStorage.setItem("type", product.type)
|
||||
if (userIsAuthenticated) {
|
||||
await generateCheckoutPage(price, product.type);
|
||||
await generateCheckoutPage(price, product.type)
|
||||
} else {
|
||||
openSignUpModalOnPriceClick(price, product.type);
|
||||
openSignUpModalOnPriceClick(price, product.type)
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={`relative w-64 sm:w-80 bg-base-200 rounded-lg p-6 shadow-lg border border-primary/40 ${
|
||||
|
@ -124,5 +114,5 @@ export default function PriceCard({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
export default function PriceToggle({ isAnnual, onChange }) {
|
||||
return (
|
||||
<>
|
||||
<label className="shadow-card relative inline-flex cursor-pointer select-none items-center justify-center rounded-lg bg-base-100 p-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only"
|
||||
checked={isAnnual}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<span
|
||||
className={`flex items-center space-x-[6px] rounded py-2 px-[18px] text-sm font-medium ${
|
||||
!isAnnual ? " bg-secondary" : "text-secondary-content"
|
||||
}`}
|
||||
>
|
||||
Monthly Billing
|
||||
</span>
|
||||
<span
|
||||
className={`flex items-center space-x-[6px] rounded py-2 px-[18px] text-sm font-medium ${
|
||||
isAnnual ? " bg-secondary" : "text-secondary-content"
|
||||
}`}
|
||||
>
|
||||
Yearly Billing
|
||||
</span>
|
||||
</label>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import { ChangeEventHandler } from "react";
|
||||
|
||||
export default function PriceToggle({
|
||||
isAnnual,
|
||||
onChange,
|
||||
}: {
|
||||
isAnnual: boolean;
|
||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<label className="shadow-card relative inline-flex cursor-pointer select-none items-center justify-center rounded-lg bg-base-100 p-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only"
|
||||
checked={isAnnual}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<span
|
||||
className={`flex items-center space-x-[6px] rounded py-2 px-[18px] text-sm font-medium ${
|
||||
!isAnnual ? " bg-secondary" : "text-secondary-content"
|
||||
}`}
|
||||
>
|
||||
Monthly Billing
|
||||
</span>
|
||||
<span
|
||||
className={`flex items-center space-x-[6px] rounded py-2 px-[18px] text-sm font-medium ${
|
||||
isAnnual ? " bg-secondary" : "text-secondary-content"
|
||||
}`}
|
||||
>
|
||||
Yearly Billing
|
||||
</span>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
"use client"
|
||||
|
||||
import { useTheme } from "next-themes"
|
||||
import React from "react"
|
||||
import Image from "next/image"
|
||||
|
||||
const Background = ({ children, className }) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"h-full relative w-full bg-center bg-no-repeat bg-cover bg-fixed flex flex-col " +
|
||||
className
|
||||
}
|
||||
>
|
||||
<Image
|
||||
src={
|
||||
theme == "dark"
|
||||
? "/images/dark-gradient.webp"
|
||||
: "/images/gradient.webp"
|
||||
}
|
||||
alt="background"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
objectPosition="center"
|
||||
priority
|
||||
/>
|
||||
<div className="z-10">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Background
|
|
@ -1,33 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import React, { ReactNode } from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
const Background = ({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={"h-full relative w-full bg-center bg-no-repeat bg-cover bg-fixed flex flex-col " + className}
|
||||
>
|
||||
<Image
|
||||
src={theme == "dark" ? "/images/dark-gradient.webp": "/images/gradient.webp"}
|
||||
alt="background"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
objectPosition="center"
|
||||
priority
|
||||
/>
|
||||
<div className="z-10">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Background;
|
|
@ -0,0 +1,11 @@
|
|||
import React from "react"
|
||||
|
||||
function PageWrapper({ children }) {
|
||||
return (
|
||||
<main className="h-full flex flex-col my-auto mx-auto size-full flex-grow">
|
||||
{children}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageWrapper
|
|
@ -1,12 +0,0 @@
|
|||
import { WaitingListWithImageHero } from "@/sections/Hero";
|
||||
import React from "react";
|
||||
|
||||
function PageWrapper({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<main className="h-full flex flex-col my-auto mx-auto size-full flex-grow">
|
||||
{children}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageWrapper;
|
|
@ -0,0 +1,7 @@
|
|||
import React from "react"
|
||||
|
||||
const Spacer = props => {
|
||||
return <div {...props}></div>
|
||||
}
|
||||
|
||||
export default Spacer
|
|
@ -1,12 +0,0 @@
|
|||
import React, { ReactHTMLElement } from "react";
|
||||
|
||||
const Spacer = (
|
||||
props: React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>
|
||||
) => {
|
||||
return <div {...props}></div>;
|
||||
};
|
||||
|
||||
export default Spacer;
|
|
@ -1,20 +1,14 @@
|
|||
import React, { useState } from "react";
|
||||
import React from "react"
|
||||
|
||||
import { MediaPlayer, MediaProvider } from "@vidstack/react";
|
||||
import { MediaPlayer, MediaProvider } from "@vidstack/react"
|
||||
import {
|
||||
DefaultAudioLayout,
|
||||
defaultLayoutIcons,
|
||||
DefaultVideoLayout,
|
||||
} from "@vidstack/react/player/layouts/default";
|
||||
import "@vidstack/react/player/styles/base.css";
|
||||
export type Props = {
|
||||
videotitle: string;
|
||||
video: string;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
DefaultVideoLayout
|
||||
} from "@vidstack/react/player/layouts/default"
|
||||
import "@vidstack/react/player/styles/base.css"
|
||||
|
||||
export default function YouTubeFrame({ video, width, height }: Props) {
|
||||
export default function YouTubeFrame({ video, width, height }) {
|
||||
return (
|
||||
<MediaPlayer
|
||||
className="w-full aspect-video bg-slate-900 bg-base-content font-sans overflow-hidden rounded-xl ring-media-focus data-[focus]:ring-4"
|
||||
|
@ -29,5 +23,5 @@ export default function YouTubeFrame({ video, width, height }: Props) {
|
|||
thumbnails="https://media-files.vidstack.io/thumbnails.vtt"
|
||||
/>
|
||||
</MediaPlayer>
|
||||
);
|
||||
)
|
||||
}
|
|
@ -4,6 +4,7 @@ export const companySizeList = [
|
|||
"30-70 employees",
|
||||
"70-100 employees",
|
||||
"100+ employees"
|
||||
]
|
||||
|
||||
export const title = "FastPocket"
|
||||
]
|
||||
|
||||
export const title = "FastPocket"
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import pb from "@/lib/pocketbase"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
export const getUserFromCookie = async cookies => {
|
||||
const cookie = cookies.get("pb_auth")
|
||||
if (!cookie) {
|
||||
redirect("/")
|
||||
//throw new Error("No authenticated user");
|
||||
} else {
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
return pb.authStore.model
|
||||
}
|
||||
}
|
||||
|
||||
export const isAuthenticated = async cookieStore => {
|
||||
const cookie = cookieStore.get("pb_auth")
|
||||
if (!cookie) return false
|
||||
pb.authStore.loadFromCookie(cookie?.value || "")
|
||||
return pb.authStore.isValid || false
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const getUserFromCookie = async (cookies: ReadonlyRequestCookies) => {
|
||||
const cookie = cookies.get('pb_auth');
|
||||
if (!cookie) {
|
||||
redirect('/');
|
||||
//throw new Error("No authenticated user");
|
||||
} else {
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
return pb.authStore.model;
|
||||
}
|
||||
}
|
||||
|
||||
export const isAuthenticated = async (cookieStore: ReadonlyRequestCookies) => {
|
||||
const cookie = cookieStore.get('pb_auth');
|
||||
if(!cookie) return false;
|
||||
pb.authStore.loadFromCookie(cookie?.value || '');
|
||||
return pb.authStore.isValid || false;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue