From 2eb0c20bb05c666c82d24d69b11f1506f60a7e9b Mon Sep 17 00:00:00 2001 From: Ciro de Oliveira Date: Wed, 18 Sep 2024 17:14:26 -0300 Subject: [PATCH] all changes --- app/(authenticated)/account/page.jsx | 36 + .../confirm-email-change/[token]/page.jsx | 89 + .../confirm-password-reset/[token]/page.jsx | 108 + .../confirm-verification/[token]/page.jsx | 108 + app/(public)/about/page.jsx | 110 + app/(public)/blogs/[slug]/page.jsx | 101 + app/(public)/blogs/page.jsx | 44 + app/(public)/contact/page.jsx | 31 + app/(public)/layout.jsx | 11 + app/(public)/pricing/action.js | 51 + app/(public)/pricing/page.jsx | 48 + app/(public)/whatsnew/page.jsx | 34 + app/actions.js | 287 ++ app/fastpocket-diagram_352x316.webp | Bin 0 -> 13570 bytes app/favicon.ico | Bin 25931 -> 15406 bytes app/globals.css | 34 +- app/layout.js | 17 - app/layout.jsx | 106 + app/not-found.jsx | 72 + app/page.js | 113 - app/page.jsx | 100 + app/providers.js | 26 + app/teste/page.js | 9 + components/BlogCard.jsx | 32 + components/FastPocketBadge.jsx | 47 + components/Footer.jsx | 259 ++ components/GetStartedSectionButton.jsx | 35 + components/GoogleAnalytics.jsx | 28 + components/Header.jsx | 141 + components/Logo.jsx | 26 + components/Modals/ModalEmailChange.jsx | 105 + components/Modals/ModalPasswordReset.jsx | 103 + components/Modals/ModalSign.jsx | 147 + components/Modals/ModalSignIn.jsx | 147 + components/Modals/ModalSignUp.jsx | 277 ++ components/Navigation.jsx | 110 + components/PriceCard.jsx | 118 + components/PriceToggle.jsx | 29 + components/Utilities/Background.jsx | 34 + components/Utilities/PageWrapper.jsx | 11 + components/Utilities/Spacer.jsx | 7 + components/Utilities/YoutubeEmbed.jsx | 27 + constants/index.js | 10 + lib/auth.js | 20 + lib/pocketbase.js | 8 + next.config.js | 33 + next.config.mjs | 4 - package.json | 56 +- public/assets/training-form.pdf | Bin 0 -> 289590 bytes public/icons/combination-logo.svg | 5 + public/icons/logo.svg | 4 + public/images/combination-icon.webp | Bin 0 -> 4018 bytes public/images/daisyui-icon.webp | Bin 0 -> 2018 bytes public/images/dark-gradient.webp | Bin 0 -> 2780 bytes public/images/fastpocket-diagram.webp | Bin 0 -> 55098 bytes public/images/fastpocket-diagram_352x316.webp | Bin 0 -> 13570 bytes public/images/fastpocket-diagram_713x640.webp | Bin 0 -> 32616 bytes public/images/gradient.webp | Bin 0 -> 10438 bytes public/images/icon.webp | Bin 0 -> 1172 bytes public/images/nextjs-icon.webp | Bin 0 -> 1244 bytes public/images/pocketbase-icon.webp | Bin 0 -> 880 bytes public/images/sam.webp | Bin 0 -> 79612 bytes public/images/stripe-icon.webp | Bin 0 -> 1816 bytes public/images/tailwind-icon.webp | Bin 0 -> 2352 bytes public/images/vertical-tabs-feature-1.webp | Bin 0 -> 40232 bytes .../vertical-tabs-feature-1_520x643.webp | Bin 0 -> 32814 bytes public/images/vertical-tabs-feature-2.webp | Bin 0 -> 42848 bytes public/images/vertical-tabs-feature-3.webp | Bin 0 -> 48562 bytes public/next.svg | 1 - public/robots.txt | 9 + public/vercel.svg | 1 - sections/AccountContent.jsx | 123 + sections/BlogContent.jsx | 46 + .../FormLeftDescriptionRightContactUs.jsx | 250 ++ sections/FAQ/RightAlignedBorderBottomFAQ.jsx | 161 + sections/Features/CardsFeature.jsx | 218 + .../ContainerImageIconBlocksFeature.jsx | 170 + .../Features/SolidBackgrondIconFeature.jsx | 155 + sections/Features/VerticalTabsFeature.jsx | 245 ++ sections/Hero/CenterAllignedWithVideoHero.jsx | 36 + sections/Hero/SimpleHero.jsx | 41 + sections/Hero/SquaredBackgroundHero.jsx | 78 + sections/Hero/WaitingListWithImageHero.jsx | 209 + sections/Hero/index.js | 8 + sections/Newsletter/Newsletter.jsx | 96 + sections/PageHeader.jsx | 14 + sections/Payment.jsx | 58 + sections/Testemonial/CardTestemonial.jsx | 67 + sections/Testimonial.jsx | 40 + sections/Video.jsx | 41 + tailwind.config.js | 112 +- utils/colors.js | 14 + utils/form.js | 111 + utils/getPostMetaData.js | 11 + yarn.lock | 3748 +++++++++++++++++ 95 files changed, 9236 insertions(+), 185 deletions(-) create mode 100644 app/(authenticated)/account/page.jsx create mode 100644 app/(public)/[ignore]/auth/confirm-email-change/[token]/page.jsx create mode 100644 app/(public)/[ignore]/auth/confirm-password-reset/[token]/page.jsx create mode 100644 app/(public)/[ignore]/auth/confirm-verification/[token]/page.jsx create mode 100644 app/(public)/about/page.jsx create mode 100644 app/(public)/blogs/[slug]/page.jsx create mode 100644 app/(public)/blogs/page.jsx create mode 100644 app/(public)/contact/page.jsx create mode 100644 app/(public)/layout.jsx create mode 100644 app/(public)/pricing/action.js create mode 100644 app/(public)/pricing/page.jsx create mode 100644 app/(public)/whatsnew/page.jsx create mode 100644 app/actions.js create mode 100644 app/fastpocket-diagram_352x316.webp delete mode 100644 app/layout.js create mode 100644 app/layout.jsx create mode 100644 app/not-found.jsx delete mode 100644 app/page.js create mode 100644 app/page.jsx create mode 100644 app/providers.js create mode 100644 app/teste/page.js create mode 100644 components/BlogCard.jsx create mode 100644 components/FastPocketBadge.jsx create mode 100644 components/Footer.jsx create mode 100644 components/GetStartedSectionButton.jsx create mode 100644 components/GoogleAnalytics.jsx create mode 100644 components/Header.jsx create mode 100644 components/Logo.jsx create mode 100644 components/Modals/ModalEmailChange.jsx create mode 100644 components/Modals/ModalPasswordReset.jsx create mode 100644 components/Modals/ModalSign.jsx create mode 100644 components/Modals/ModalSignIn.jsx create mode 100644 components/Modals/ModalSignUp.jsx create mode 100644 components/Navigation.jsx create mode 100644 components/PriceCard.jsx create mode 100644 components/PriceToggle.jsx create mode 100644 components/Utilities/Background.jsx create mode 100644 components/Utilities/PageWrapper.jsx create mode 100644 components/Utilities/Spacer.jsx create mode 100644 components/Utilities/YoutubeEmbed.jsx create mode 100644 constants/index.js create mode 100644 lib/auth.js create mode 100644 lib/pocketbase.js create mode 100644 next.config.js delete mode 100644 next.config.mjs create mode 100644 public/assets/training-form.pdf create mode 100644 public/icons/combination-logo.svg create mode 100644 public/icons/logo.svg create mode 100644 public/images/combination-icon.webp create mode 100644 public/images/daisyui-icon.webp create mode 100644 public/images/dark-gradient.webp create mode 100644 public/images/fastpocket-diagram.webp create mode 100644 public/images/fastpocket-diagram_352x316.webp create mode 100644 public/images/fastpocket-diagram_713x640.webp create mode 100644 public/images/gradient.webp create mode 100644 public/images/icon.webp create mode 100644 public/images/nextjs-icon.webp create mode 100644 public/images/pocketbase-icon.webp create mode 100644 public/images/sam.webp create mode 100644 public/images/stripe-icon.webp create mode 100644 public/images/tailwind-icon.webp create mode 100644 public/images/vertical-tabs-feature-1.webp create mode 100644 public/images/vertical-tabs-feature-1_520x643.webp create mode 100644 public/images/vertical-tabs-feature-2.webp create mode 100644 public/images/vertical-tabs-feature-3.webp delete mode 100644 public/next.svg create mode 100644 public/robots.txt delete mode 100644 public/vercel.svg create mode 100644 sections/AccountContent.jsx create mode 100644 sections/BlogContent.jsx create mode 100644 sections/ContactUs/FormLeftDescriptionRightContactUs.jsx create mode 100644 sections/FAQ/RightAlignedBorderBottomFAQ.jsx create mode 100644 sections/Features/CardsFeature.jsx create mode 100644 sections/Features/ContainerImageIconBlocksFeature.jsx create mode 100644 sections/Features/SolidBackgrondIconFeature.jsx create mode 100644 sections/Features/VerticalTabsFeature.jsx create mode 100644 sections/Hero/CenterAllignedWithVideoHero.jsx create mode 100644 sections/Hero/SimpleHero.jsx create mode 100644 sections/Hero/SquaredBackgroundHero.jsx create mode 100644 sections/Hero/WaitingListWithImageHero.jsx create mode 100644 sections/Hero/index.js create mode 100644 sections/Newsletter/Newsletter.jsx create mode 100644 sections/PageHeader.jsx create mode 100644 sections/Payment.jsx create mode 100644 sections/Testemonial/CardTestemonial.jsx create mode 100644 sections/Testimonial.jsx create mode 100644 sections/Video.jsx create mode 100644 utils/colors.js create mode 100644 utils/form.js create mode 100644 utils/getPostMetaData.js create mode 100644 yarn.lock diff --git a/app/(authenticated)/account/page.jsx b/app/(authenticated)/account/page.jsx new file mode 100644 index 0000000..2fab39f --- /dev/null +++ b/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/app/(public)/[ignore]/auth/confirm-email-change/[token]/page.jsx b/app/(public)/[ignore]/auth/confirm-email-change/[token]/page.jsx new file mode 100644 index 0000000..4c58fd6 --- /dev/null +++ b/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 ( + + +
+ } + /> +
+
+ +
+ {errors.password?.message}  +
+
+
+ +
+
+
+
+
+ ) +} diff --git a/app/(public)/[ignore]/auth/confirm-password-reset/[token]/page.jsx b/app/(public)/[ignore]/auth/confirm-password-reset/[token]/page.jsx new file mode 100644 index 0000000..7399fb4 --- /dev/null +++ b/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 ( + + +
+ } /> +
+
+ +
+ {errors.newPassword?.message}  +
+
+
+ +
+ {errors.newPasswordConfirm?.message}  +
+
+
+ +
+
+
+
+
+ ) +} diff --git a/app/(public)/[ignore]/auth/confirm-verification/[token]/page.jsx b/app/(public)/[ignore]/auth/confirm-verification/[token]/page.jsx new file mode 100644 index 0000000..7399fb4 --- /dev/null +++ b/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 ( + + +
+ } /> +
+
+ +
+ {errors.newPassword?.message}  +
+
+
+ +
+ {errors.newPasswordConfirm?.message}  +
+
+
+ +
+
+
+
+
+ ) +} diff --git a/app/(public)/about/page.jsx b/app/(public)/about/page.jsx new file mode 100644 index 0000000..268d73c --- /dev/null +++ b/app/(public)/about/page.jsx @@ -0,0 +1,110 @@ +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 ( +
+ {/* Page sections */} + +
+

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

+

+ I am taking all of the knowledge that I have gained across 20+ + different React projects and combining it into 1 single codebase. It + includes personal philosophies on styling, theming and building and + will help indie-hackers, startups make complex technical decisions + 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. +

+

+ + FastPocket + {" "} + will get you producing more with less development. +

+

+ I know that your next project will grow exponentially because of the + work I have already done. +

+

+ Ready to launch your next product? +

+ + {"samuel + + + Let's go! + +
+
+
+ ) +} diff --git a/app/(public)/blogs/[slug]/page.jsx b/app/(public)/blogs/[slug]/page.jsx new file mode 100644 index 0000000..7159902 --- /dev/null +++ b/app/(public)/blogs/[slug]/page.jsx @@ -0,0 +1,101 @@ +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 }) { + 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) + + return { + title: `${post.title} | FastPocket`, + authors: [ + { + name: post.author || "Samuel Wyndham" + } + ], + description: post.description, + keywords: post.keywords, + openGraph: { + title: `${post.title} | FastPocket`, + description: post.description, + type: "article", + url: `https://${siteURL}/blogs/${post.slug}`, + publishedTime: post.created, + modifiedTime: post.updated, + authors: [`https://${siteURL}/about`], + images: [ + { + url: `${post.imageUrl}`, + width: 1024, + height: 576, + alt: post.title, + type: "image/png" + } + ] + }, + twitter: { + card: "summary_large_image", + site: "@meinbiz", + creator: "@meinbiz", + title: `${post.title} | FastPocket`, + description: post.description, + images: [ + { + url: `${post.imageUrl}`, + width: 1024, + height: 576, + alt: post.title + } + ] + }, + alternates: { + 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 PostPage = async props => { + console.log("params", props.params) + const post = await pb + .collection("blog") + .getFirstListItem(`slug="` + props.params.slug + `"`, { + requestKey: "post" + }) + return ( +
+ +
+ + +
+
+ +
+ ) +} + +export default PostPage diff --git a/app/(public)/blogs/page.jsx b/app/(public)/blogs/page.jsx new file mode 100644 index 0000000..20845c9 --- /dev/null +++ b/app/(public)/blogs/page.jsx @@ -0,0 +1,44 @@ +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() + + console.log("blogs", postMetadata.length) + const postPreviews = postMetadata + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) + .map(post => ) + return ( +
+ + {/* Page sections */} + + {" "} +

+ Take a look as we show you how to make a great PocketBase + + React App +

+ + } + /> +
+
+ {postPreviews} +
+
+
+ +
+ ) +} diff --git a/app/(public)/contact/page.jsx b/app/(public)/contact/page.jsx new file mode 100644 index 0000000..a57ddda --- /dev/null +++ b/app/(public)/contact/page.jsx @@ -0,0 +1,31 @@ +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 ( +
+ + + {" "} +

+ We'd love to talk about how we can help you. +

+ + } + /> + + +
+ +
+ ) +} + +export default page diff --git a/app/(public)/layout.jsx b/app/(public)/layout.jsx new file mode 100644 index 0000000..6b2d8d4 --- /dev/null +++ b/app/(public)/layout.jsx @@ -0,0 +1,11 @@ +import React from "react"; + +function layout({ children }) { + return ( + <> + {children} + + ); +} + +export default layout; diff --git a/app/(public)/pricing/action.js b/app/(public)/pricing/action.js new file mode 100644 index 0000000..e7bdc7d --- /dev/null +++ b/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/app/(public)/pricing/page.jsx b/app/(public)/pricing/page.jsx new file mode 100644 index 0000000..b45a68c --- /dev/null +++ b/app/(public)/pricing/page.jsx @@ -0,0 +1,48 @@ +"use client" + +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 ( +
+ + + {" "} +

+ Apply now to join us as we build apps fast, together! Be among + the first to start using FastPocket and lock in your lifetime + discount. +

+ + } + /> + +

+ What you will get! +

+ + + + + + + +
+ +
+ ) +} diff --git a/app/(public)/whatsnew/page.jsx b/app/(public)/whatsnew/page.jsx new file mode 100644 index 0000000..09c3914 --- /dev/null +++ b/app/(public)/whatsnew/page.jsx @@ -0,0 +1,34 @@ +"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" + +const page = () => { + return ( +
+ + + {" "} +

+ Fastpocket gives you boilerplate to help you build. +

+ + } + /> + + + +
+ +
+ ) +} + +export default page diff --git a/app/actions.js b/app/actions.js new file mode 100644 index 0000000..8de57c1 --- /dev/null +++ b/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/app/fastpocket-diagram_352x316.webp b/app/fastpocket-diagram_352x316.webp new file mode 100644 index 0000000000000000000000000000000000000000..d104c3028bf1927c1013829aed75ff9ee3b2fa48 GIT binary patch literal 13570 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 literal 0 HcmV?d00001 diff --git a/app/favicon.ico b/app/favicon.ico index 718d6fea4835ec2d246af9800eddb7ffb276240c..80d7e9f0fa87e758698479861373c8671f005490 100644 GIT binary patch literal 15406 zcmeI232YQ)6vwB6cx&+hjm9(@1T@}?w-SvYatPh|won8r=A;#%fHACMGJE zlBfaHXf&b{6;KitL<1U)LQqlTjiOZyRNLK|`TV`zu3ffWm(uN4aVB~BX1?qFzc=rD z-|?Pdlo@@D@^XW;!m#=p#xTP$Dk{?7L;4%WW8|H7T3h)EhOzbt!x%yxn$RMhPx8Nc zW=_%rZBvi3l^*Sy-WWHkubidyp}QggYoe~OO@AAtv&m8ZH`B-Ig9`OwE3Z1cUG{ri zCC@9=-ZAx5Y2%oF(pA3gME%B0AC5|1Sg5@n^`_Cb#WB4~+w?}&Mw1sf5q}na1dh@L z+gr+;Lc3LUqx3%4BFBdDzm?ZO^tl0H+w_K%0f6kJ8^VM|pGH%H+j` z+FPbK*-^nO$Z;|K%#n7qU*wp<>z0`uUZ{Otr9OuCFVQ~6nCo2AZ?L1zF8mdFD3@otDExi%99_k$2hZC z7?AbSJbd!X!;1LzMdNRJE4s=bnawU1fByYzx%}A!?10A&=-XV%f)3^UvtseLRj_L3 z{sZ=b?Ry@|8gem!@8%xjztvWWlx|5{U#$lTANGfFF!&jqJ;9$ozjvzi(9F1y%Qoiz z+RV4?QQ40r+c4ac$?tBOkw5*9$nHz_gl=@_y`#NHQu!ZHw`ly)^QW*Umx!!+{}P>7&P08_J5is*e)NJ`_P>n#MtC+;zL35C z)$DJJCV%$GGXJ*8xrz73d2fP`{AZ8xdg(OSLV7EG{Uf)qGj|>GNr1>ZjIW$Vj*Py1oRa7&r;_jWW#yVzpbl03fI?VoFOlx?|a}k z4?chIJ<{*~Jo4{wD-&lF?klVjN+6U#D1qLWfZUUCMQhYR;XMgL2%k^_p#(w+gc1lP zu*XS2m&KGmBsNJOZcWewW=`5MPze9ik+I6=qjITkvOf6VAcDq5y_fh4Iom(SIqy@> zTbntLZnh)-=P6R8D(FC*$2M^ee%P&Q!Ta1hPb{YQ=A3#xn0G*(pW6%NbFO!)5@+|u z2sqnMMvg7JjXg6q<$q{R(F0032GL*i<^67BZySRXNz5uGez|8t_TLr@KlfP6N{OGl z%3;h?xi{NAz3%9i5YT5kRTiggv@r5+8KN_7WV@~wL4>KNrSaW6!%=7h`@=~PP_`$KhGv8G_ zk~jS8a@ODks-EJ?^1k(*do>rV%+>z`uLXj$I-8Kxnu0fZ;2~pU%xb7-7*La;#+Ix z`)51iU7b3!aE=kXlOcRkN65LRKB5N^gYgLU6V(3(J&b~V96DH1`!D#@>u>Nj=a-R= znY@E`)T(EM1WbqM~*v4S1a;D5iA_{Bd7R>6{h ze+#nzUTl(_f9=t_{|%%s?RFJ=4Ek zl{go^TaYb&&D0$JgZJOSv6A!|&b}!>DDR78ehN{45tyGupH`>sX7Cl?_mRJqdK+Y3 zXZr8yl=bvf_=+!lW4+SHvVP70JMjaZ>}@*c$vjyS;|wJ8oq7B_ZQm@V{$OJcgy&jt zNL<70*jT;)aQ?r6tw( z^W6TiIU`aw)AWB~918@u9nq)6v5~Uh$xuAfGDz-8h_M*#Mp{p*Q;A_RF4`_ zJ9u`tZ5*9p9QT4r?jzE7SDoHlrSG(;_bKC8BQf&5A@_Xal=zSK`$Ii2_s&qx0)_Ylt3tfPy(R@LJ5Qt2qmy*OW-e|ceuv@ literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m diff --git a/app/globals.css b/app/globals.css index 875c01e..3026545 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,33 +1,9 @@ + + @tailwind base; @tailwind components; @tailwind utilities; -:root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; -} - -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - } -} - -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); -} - -@layer utilities { - .text-balance { - text-wrap: balance; - } -} +h1, h2, h3, h4, h5, h6 { + font-family: var(--heading-font); +} \ No newline at end of file diff --git a/app/layout.js b/app/layout.js deleted file mode 100644 index 9aef1df..0000000 --- a/app/layout.js +++ /dev/null @@ -1,17 +0,0 @@ -import { Inter } from "next/font/google"; -import "./globals.css"; - -const inter = Inter({ subsets: ["latin"] }); - -export const metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default function RootLayout({ children }) { - return ( - - {children} - - ); -} diff --git a/app/layout.jsx b/app/layout.jsx new file mode 100644 index 0000000..f64ee4e --- /dev/null +++ b/app/layout.jsx @@ -0,0 +1,106 @@ +/* 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"; + +const raleway = Arimo({ + variable: "--body-font", + subsets: ["latin"], + display: "swap", +}); + +const arimo = Arimo({ + variable: "--body-font", + subsets: ["latin"], + display: "swap", +}); + +const indieFlower = Indie_Flower({ + variable: "--accent-font", + weight: "400", + subsets: ["latin"], + display: "swap", +}); + +export default async function RootLayout({ + children, +}) { + const isUserLoggedIn = await isAuthenticated(cookies()); + + return ( + + + + + +
+ {children} + + + + + + + ); +} + diff --git a/app/not-found.jsx b/app/not-found.jsx new file mode 100644 index 0000000..e5f7f28 --- /dev/null +++ b/app/not-found.jsx @@ -0,0 +1,72 @@ +import Background from "@/components/Utilities/Background" +import Link from "next/link" + +export default function NotFound() { + return ( + <> +
+ +
+
+
+

+ 404 +

+

+

+ Oops, something went wrong. +

+

+ Sorry, we couldn't find your page. +

+
+ + + + + Get the source code + + + + + + Back to home + +
+

+
+
+
+
+ + ) +} diff --git a/app/page.js b/app/page.js deleted file mode 100644 index a7c2036..0000000 --- a/app/page.js +++ /dev/null @@ -1,113 +0,0 @@ -import Image from "next/image"; - -export default function Home() { - return ( -
-
-

- Get started by editing  - app/page.js -

- -
- -
- Next.js Logo -
- - -
- ); -} diff --git a/app/page.jsx b/app/page.jsx new file mode 100644 index 0000000..8cff01b --- /dev/null +++ b/app/page.jsx @@ -0,0 +1,100 @@ +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 Payment from "@/sections/Payment" + +export const metadata = { + title: "Pocketbase, Stripe, Next.js, Boilerplate | FastPocket", + description: + "FastPocket - Is a boilerplate codebase for everyone to build a product quickly.", + keywords: [ + "pocketbase", + "stripe", + "next.js", + "boilerplate", + "template", + "codebase" + ], + openGraph: { + url: "https://fastpocket.dev", + type: "website", + title: "Pocketbase, Stripe, Next.js, Boilerplate | FastPocket", + description: + "FastPocket - Is a boilerplate codebase for everyone to build a product quickly.", + images: [ + { + url: "https://fastpocket.dev/images/home/thumbnail.png", + width: 1200, + height: 630, + alt: "fastpocket.degv" + } + ] + }, + twitter: { + card: "summary_large_image", + title: "Pocketbase, Stripe, Next.js, Boilerplate | FastPocket", + description: + "fastpocket.dev - Programming blog for everyone to learn Elastic Stack, Next.js, Python, JavaScript, React, Machine Learning, Data Science, and more.", + creator: "@meinbiz", + site: "@meinbiz", + images: [ + { + url: "https://fastpocket.dev/images/home/thumbnail.png", + width: 1200, + height: 630, + alt: "fastpocket.degv" + } + ] + }, + alternates: { + canonical: "https://fastpocket.dev" + } +} + +export default function Home() { + return ( + <> + + + +
+ +
+ } + /> + + + + + + {" "} +

+ Purchase now to get early access at a discounted price and start + building with fly.io and docker compose templates immediately! +

+ + } + /> + + +