diff --git a/Frontend/.gitignore b/Frontend/.gitignore
index 6fb7981..fd3dbb5 100644
--- a/Frontend/.gitignore
+++ b/Frontend/.gitignore
@@ -4,6 +4,7 @@
/node_modules
/.pnp
.pnp.js
+.yarn/install-state.gz
# testing
/coverage
@@ -26,7 +27,6 @@ yarn-error.log*
# local env files
.env*.local
-.env
# vercel
.vercel
@@ -34,6 +34,3 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
-
-# Local Netlify folder
-.netlify
diff --git a/Frontend/.nvmrc b/Frontend/.nvmrc
deleted file mode 100644
index 016efd8..0000000
--- a/Frontend/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-v20.10.0
\ No newline at end of file
diff --git a/Frontend/.vercelignore b/Frontend/.vercelignore
deleted file mode 100644
index 5c457d7..0000000
--- a/Frontend/.vercelignore
+++ /dev/null
@@ -1 +0,0 @@
-docs
\ No newline at end of file
diff --git a/Frontend/README.md b/Frontend/README.md
index f4da3c4..0dc9ea2 100644
--- a/Frontend/README.md
+++ b/Frontend/README.md
@@ -10,11 +10,13 @@ npm run dev
yarn dev
# or
pnpm dev
+# or
+bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
diff --git a/Frontend/app/(authenticated)/account/page.jsx b/Frontend/app/(authenticated)/account/page.jsx
new file mode 100644
index 0000000..2fab39f
--- /dev/null
+++ b/Frontend/app/(authenticated)/account/page.jsx
@@ -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 && (
+
+
+
+
+
+ )
+ )
+}
diff --git a/Frontend/app/(authenticated)/account/page.tsx b/Frontend/app/(authenticated)/account/page.tsx
deleted file mode 100644
index 7f572f1..0000000
--- a/Frontend/app/(authenticated)/account/page.tsx
+++ /dev/null
@@ -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 && (
-
-
-
-
-
- )
- );
-}
diff --git a/Frontend/app/(public)/[ignore]/auth/confirm-email-change/[token]/page.jsx b/Frontend/app/(public)/[ignore]/auth/confirm-email-change/[token]/page.jsx
new file mode 100644
index 0000000..4c58fd6
--- /dev/null
+++ b/Frontend/app/(public)/[ignore]/auth/confirm-email-change/[token]/page.jsx
@@ -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 (
+
+
+
+
+
+ )
+}
diff --git a/Frontend/app/(public)/[ignore]/auth/confirm-email-change/[token]/page.tsx b/Frontend/app/(public)/[ignore]/auth/confirm-email-change/[token]/page.tsx
deleted file mode 100644
index 9270ca9..0000000
--- a/Frontend/app/(public)/[ignore]/auth/confirm-email-change/[token]/page.tsx
+++ /dev/null
@@ -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 (
-
-
-
-
-
- );
-}
diff --git a/Frontend/app/(public)/[ignore]/auth/confirm-password-reset/[token]/page.jsx b/Frontend/app/(public)/[ignore]/auth/confirm-password-reset/[token]/page.jsx
new file mode 100644
index 0000000..7399fb4
--- /dev/null
+++ b/Frontend/app/(public)/[ignore]/auth/confirm-password-reset/[token]/page.jsx
@@ -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 (
+
+
+
+
+
+ )
+}
diff --git a/Frontend/app/(public)/[ignore]/auth/confirm-password-reset/[token]/page.tsx b/Frontend/app/(public)/[ignore]/auth/confirm-password-reset/[token]/page.tsx
deleted file mode 100644
index ef3f365..0000000
--- a/Frontend/app/(public)/[ignore]/auth/confirm-password-reset/[token]/page.tsx
+++ /dev/null
@@ -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 (
-
-
-
-
-
- );
-}
diff --git a/Frontend/app/(public)/[ignore]/auth/confirm-verification/[token]/page.jsx b/Frontend/app/(public)/[ignore]/auth/confirm-verification/[token]/page.jsx
new file mode 100644
index 0000000..7399fb4
--- /dev/null
+++ b/Frontend/app/(public)/[ignore]/auth/confirm-verification/[token]/page.jsx
@@ -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 (
+
+
+
+
+
+ )
+}
diff --git a/Frontend/app/(public)/[ignore]/auth/confirm-verification/[token]/page.tsx b/Frontend/app/(public)/[ignore]/auth/confirm-verification/[token]/page.tsx
deleted file mode 100644
index 15382d1..0000000
--- a/Frontend/app/(public)/[ignore]/auth/confirm-verification/[token]/page.tsx
+++ /dev/null
@@ -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 (
-
-
-
-
>}
- />
-
- Go to Sign In
-
-
-
-
- );
-}
diff --git a/Frontend/app/(public)/about/page.tsx b/Frontend/app/(public)/about/page.jsx
similarity index 53%
rename from Frontend/app/(public)/about/page.tsx
rename to Frontend/app/(public)/about/page.jsx
index aeae38f..268d73c 100644
--- a/Frontend/app/(public)/about/page.tsx
+++ b/Frontend/app/(public)/about/page.jsx
@@ -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() {
- In 2023 I built Sign365 using pocketbase
- and setup an open source library to help people get setup with Stripe
- + Pocketbase. 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 FastPocket an
- easy solution to give everyone a head start.
+ In 2023 I built{" "}
+
+ Sign365
+ {" "}
+ using pocketbase and setup an open source library to help people get
+ setup with{" "}
+
+ Stripe + Pocketbase
+
+ . 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{" "}
+
+ FastPocket
+ {" "}
+ an easy solution to give everyone a head start.
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 FastPocket
+ they will be able to do it using{" "}
+
+ FastPocket
+
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
- So I've committed to building FastPocket to help you build your
- projects faster to get paid.
+ So I've committed to building{" "}
+
+ FastPocket
+ {" "}
+ to help you build your projects faster to get paid.
- FastPocket will get you producing more with less development.
+
+ FastPocket
+ {" "}
+ will get you producing more with less development.
I know that your next project will grow exponentially because of the
@@ -69,5 +106,5 @@ export default async function About() {
- );
+ )
}
diff --git a/Frontend/app/(public)/blogs/[slug]/page.tsx b/Frontend/app/(public)/blogs/[slug]/page.jsx
similarity index 56%
rename from Frontend/app/(public)/blogs/[slug]/page.tsx
rename to Frontend/app/(public)/blogs/[slug]/page.jsx
index 41ffa96..7159902 100644
--- a/Frontend/app/(public)/blogs/[slug]/page.tsx
+++ b/Frontend/app/(public)/blogs/[slug]/page.jsx
@@ -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 (
{
- );
-};
+ )
+}
-export default PostPage;
+export default PostPage
diff --git a/Frontend/app/(public)/blogs/page.tsx b/Frontend/app/(public)/blogs/page.jsx
similarity index 69%
rename from Frontend/app/(public)/blogs/page.tsx
rename to Frontend/app/(public)/blogs/page.jsx
index b4ef42f..20845c9 100644
--- a/Frontend/app/(public)/blogs/page.tsx
+++ b/Frontend/app/(public)/blogs/page.jsx
@@ -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) => );
+ .map(post => )
return (
- );
+ )
}
diff --git a/Frontend/app/(public)/contact/page.tsx b/Frontend/app/(public)/contact/page.jsx
similarity index 68%
rename from Frontend/app/(public)/contact/page.tsx
rename to Frontend/app/(public)/contact/page.jsx
index ba8e525..a57ddda 100644
--- a/Frontend/app/(public)/contact/page.tsx
+++ b/Frontend/app/(public)/contact/page.jsx
@@ -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 = () => {
- );
-};
+ )
+}
-export default page;
+export default page
diff --git a/Frontend/app/(public)/layout.tsx b/Frontend/app/(public)/layout.jsx
similarity index 61%
rename from Frontend/app/(public)/layout.tsx
rename to Frontend/app/(public)/layout.jsx
index b64798e..6b2d8d4 100644
--- a/Frontend/app/(public)/layout.tsx
+++ b/Frontend/app/(public)/layout.jsx
@@ -1,6 +1,6 @@
import React from "react";
-function layout({ children }: { children: React.ReactNode }) {
+function layout({ children }) {
return (
<>
{children}
diff --git a/Frontend/app/(public)/pricing/action.js b/Frontend/app/(public)/pricing/action.js
new file mode 100644
index 0000000..e7bdc7d
--- /dev/null
+++ b/Frontend/app/(public)/pricing/action.js
@@ -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 []
+ }
+}
diff --git a/Frontend/app/(public)/pricing/actions.ts b/Frontend/app/(public)/pricing/actions.ts
deleted file mode 100644
index 1baea0b..0000000
--- a/Frontend/app/(public)/pricing/actions.ts
+++ /dev/null
@@ -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();
- 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 [];
- }
-}
\ No newline at end of file
diff --git a/Frontend/app/(public)/pricing/page.tsx b/Frontend/app/(public)/pricing/page.jsx
similarity index 74%
rename from Frontend/app/(public)/pricing/page.tsx
rename to Frontend/app/(public)/pricing/page.jsx
index 8ad1c66..b45a68c 100644
--- a/Frontend/app/(public)/pricing/page.tsx
+++ b/Frontend/app/(public)/pricing/page.jsx
@@ -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() {
-
+
- );
+ )
}
diff --git a/Frontend/app/(public)/whatsnew/page.tsx b/Frontend/app/(public)/whatsnew/page.jsx
similarity index 70%
rename from Frontend/app/(public)/whatsnew/page.tsx
rename to Frontend/app/(public)/whatsnew/page.jsx
index 865bcba..09c3914 100644
--- a/Frontend/app/(public)/whatsnew/page.tsx
+++ b/Frontend/app/(public)/whatsnew/page.jsx
@@ -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 = () => {
- );
-};
+ )
+}
-export default page;
+export default page
diff --git a/Frontend/app/actions.js b/Frontend/app/actions.js
new file mode 100644
index 0000000..8de57c1
--- /dev/null
+++ b/Frontend/app/actions.js
@@ -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
+ }
+}
diff --git a/Frontend/app/actions.ts b/Frontend/app/actions.ts
deleted file mode 100644
index 790a65a..0000000
--- a/Frontend/app/actions.ts
+++ /dev/null
@@ -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({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;
- }
-}
\ No newline at end of file
diff --git a/Frontend/app/layout.tsx b/Frontend/app/layout.jsx
similarity index 91%
rename from Frontend/app/layout.tsx
rename to Frontend/app/layout.jsx
index de5e2b4..f64ee4e 100644
--- a/Frontend/app/layout.tsx
+++ b/Frontend/app/layout.jsx
@@ -31,8 +31,6 @@ const indieFlower = Indie_Flower({
export default async function RootLayout({
children,
-}: {
- children: React.ReactNode;
}) {
const isUserLoggedIn = await isAuthenticated(cookies());
@@ -61,12 +59,6 @@ export default async function RootLayout({
theme="colored"
/>
- {/* */}
@@ -84,7 +76,7 @@ export default async function RootLayout({
id="promotekit"
async
defer
- strategy="afterInteractive"
+ strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
$(document).ready(function(){
@@ -111,3 +103,4 @@ export default async function RootLayout({