diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index f0bcf77..872a7c1 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -14,6 +14,7 @@ const config = {
"plugin:prettier/recommended"
],
"rules": {
+ "@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/array-type": "off",
"@typescript-eslint/consistent-type-definitions": "off",
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 25da3f3..edfd05e 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -12,14 +12,16 @@ jobs:
services:
postgres:
- image: postgres
+ image: postgres:latest
env:
- POSTGRES_PASSWORD: postgres
- options: >-
- --health-cmd pg_isready
- --health-interval 10s
- --health-timeout 5s
- --health-retries 5
+ POSTGRES_USER: test
+ POSTGRES_PASSWORD: test
+ POSTGRES_DB: db
+ POSTGRESQL_FSYNC: "off"
+ ports:
+ - 5432:5432
+ # needed because the postgres container does not provide a healthcheck
+ options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- name: Checkout
@@ -70,16 +72,20 @@ jobs:
run: ${{ steps.detect-package-manager.outputs.manager }} run lint
env:
SKIP_ENV_VALIDATION: true
- DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
+ DATABASE_URL: postgres://test:test@localhost:5432/db
- name: Lint Prisma
run: ${{ steps.detect-package-manager.outputs.manager }} run lint:prisma
env:
- SKIP_ENV_VALIDATION: true
- DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
+ DATABASE_URL: postgres://test:test@localhost:5432/db
+
+ - name: Can run Migrations
+ run: ${{ steps.detect-package-manager.outputs.manager }} run db:push
+ env:
+ DATABASE_URL: postgres://test:test@localhost:5432/db
- name: Build
- run: ${{ steps.detect-package-manager.outputs.manager }} run build
+ run: ${{ steps.detect-package-manager.outputs.manager }} run build -d
env:
SKIP_ENV_VALIDATION: true
- DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
\ No newline at end of file
+ DATABASE_URL: postgres://test:test@localhost:5432/db
\ No newline at end of file
diff --git a/src/app/actions/createMembership.ts b/src/app/actions/createMembership.ts
index 2b8b7bf..4372336 100644
--- a/src/app/actions/createMembership.ts
+++ b/src/app/actions/createMembership.ts
@@ -4,9 +4,9 @@ import { MembershipStatus } from "@prisma/client"
import { type ServerActionState, ServerActionStatus } from "@/app/actions/types"
import { db } from "@/services/db"
-import { stripe } from "@/services/stripe"
+import { canUseStripe, stripe } from "@/services/stripe"
-const MEMBERSHIP_PRICE_ID = "price_1P3HNlCXdJySzBrwlcoAQqS2"
+const MEMBERSHIP_PRICE_ID = "price_1P3HNlCXdJySzBrwlcoAQqS2" // TODO: Remove the hardcoded price ID
export interface FormProps {
email: string
@@ -20,6 +20,16 @@ export async function createMembership(
prevState: ServerActionState,
data: FormProps,
): Promise {
+ if (!canUseStripe()) {
+ return {
+ errors: [
+ {
+ message: "Stripe is not configured",
+ },
+ ],
+ status: ServerActionStatus.Error,
+ }
+ }
// Avoid double membership creation
if (prevState.nextStep === "providePayment") return prevState
@@ -30,6 +40,18 @@ export async function createMembership(
},
})
+ // No template, return error
+ if (!membershipTemplate) {
+ return {
+ errors: [
+ {
+ message: "Membership template not found",
+ },
+ ],
+ status: ServerActionStatus.Error,
+ }
+ }
+
// Check for user
let user = await db.user.findFirst({
where: {
@@ -52,7 +74,7 @@ export async function createMembership(
// User is not linked to Stripe, creates it
if (!user.stripeCustomerId) {
// Create customer to Stripe and link it to user
- const stripeCustomer = await stripe.customers.create({
+ const stripeCustomer = await stripe().customers.create({
email: data.email,
name: `${data.firstName} ${data.lastName}`.trim(),
})
@@ -97,7 +119,7 @@ export async function createMembership(
})
// Create Stripe subscription
- const stripeSubscription = await stripe.subscriptions.create({
+ const stripeSubscription = await stripe().subscriptions.create({
customer: user.stripeCustomerId,
expand: ["latest_invoice.payment_intent"],
items: [
@@ -123,7 +145,8 @@ export async function createMembership(
nextStep: "providePayment",
payload: {
// eslint-disable-next-line
- clientSecret: (stripeSubscription?.latest_invoice as any)?.payment_intent?.client_secret,
+ clientSecret: (stripeSubscription?.latest_invoice as any)?.payment_intent
+ ?.client_secret,
membershipId: membership.id,
},
status: ServerActionStatus.Success,
diff --git a/src/app/actions/createMembershipTemplate.ts b/src/app/actions/createMembershipTemplate.ts
index a9aef89..bb9072e 100644
--- a/src/app/actions/createMembershipTemplate.ts
+++ b/src/app/actions/createMembershipTemplate.ts
@@ -1,19 +1,16 @@
-"use server";
+"use server"
-import {
- type ServerActionState,
- ServerActionStatus,
-} from "@/app/actions/types";
-import { db } from "@/services/db";
-import { PricePeriod, type PriceUnit } from "@prisma/client";
+import { type ServerActionState, ServerActionStatus } from "@/app/actions/types"
+import { db } from "@/services/db"
+import { PricePeriod, type PriceUnit } from "@prisma/client"
export interface FormProps {
- title: string;
- description?: string;
- features?: string;
- priceAmount: number;
- priceUnit: PriceUnit;
- stripePriceId: string;
+ title: string
+ description?: string
+ features?: string
+ priceAmount: number
+ priceUnit: PriceUnit
+ stripePriceId: string
}
export async function createMembershipTemplate(
@@ -35,10 +32,10 @@ export async function createMembershipTemplate(
createdAt: new Date(),
updatedAt: new Date(),
},
- });
+ })
return {
status: ServerActionStatus.Success,
payload: membershipTemplate,
- };
+ }
}
diff --git a/src/app/actions/editMembershipTemplate.ts b/src/app/actions/editMembershipTemplate.ts
index 82cc522..34145e4 100644
--- a/src/app/actions/editMembershipTemplate.ts
+++ b/src/app/actions/editMembershipTemplate.ts
@@ -1,20 +1,17 @@
-"use server";
+"use server"
-import {
- type ServerActionState,
- ServerActionStatus,
-} from "@/app/actions/types";
-import { db } from "@/services/db";
-import { PricePeriod, type PriceUnit } from "@prisma/client";
+import { type ServerActionState, ServerActionStatus } from "@/app/actions/types"
+import { db } from "@/services/db"
+import { PricePeriod, type PriceUnit } from "@prisma/client"
export interface FormProps {
- id: string;
- title: string;
- description?: string;
- features?: string;
- priceAmount: number;
- priceUnit: PriceUnit;
- stripePriceId: string;
+ id: string
+ title: string
+ description?: string
+ features?: string
+ priceAmount: number
+ priceUnit: PriceUnit
+ stripePriceId: string
}
export async function editMembershipTemplate(
@@ -38,10 +35,10 @@ export async function editMembershipTemplate(
stripePriceId: data.stripePriceId,
updatedAt: new Date(),
},
- });
+ })
return {
status: ServerActionStatus.Success,
payload: membershipTemplate,
- };
+ }
}
diff --git a/src/app/actions/validateUserEmail.ts b/src/app/actions/validateUserEmail.ts
index b46d921..8b22ea4 100644
--- a/src/app/actions/validateUserEmail.ts
+++ b/src/app/actions/validateUserEmail.ts
@@ -1,16 +1,16 @@
-"use server";
+"use server"
-import { MembershipStatus } from "@prisma/client";
+import { MembershipStatus } from "@prisma/client"
-import { db } from "@/services/db";
+import { db } from "@/services/db"
export interface ServerActionState {
- checked: boolean;
- valid: boolean;
- email?: string;
+ checked: boolean
+ valid: boolean
+ email?: string
}
interface FormProps {
- email: string;
+ email: string
}
export async function validateUserEmail(
prevState: ServerActionState,
@@ -20,14 +20,14 @@ export async function validateUserEmail(
where: {
email: data.email,
},
- });
+ })
// No user means that is not valid
if (!user)
return {
checked: true,
valid: false,
- };
+ }
// User without membership means that is not valid
const membership = await db.membership.findFirst({
@@ -37,16 +37,16 @@ export async function validateUserEmail(
},
userId: user.id,
},
- });
+ })
if (!membership)
return {
checked: true,
valid: false,
- };
+ }
return {
checked: true,
email: data.email,
valid: true,
- };
+ }
}
diff --git a/src/app/admin/@authenticated/membership/layout.tsx b/src/app/admin/@authenticated/membership/layout.tsx
index c9d37fc..c043431 100644
--- a/src/app/admin/@authenticated/membership/layout.tsx
+++ b/src/app/admin/@authenticated/membership/layout.tsx
@@ -1,16 +1,16 @@
-import { find } from "lodash";
+import { find } from "lodash"
-import { adminMenuTreeConfig } from "@/app/admin/const";
-import { LinkWithActive } from "@/components/molecules/linkWithActive";
+import { adminMenuTreeConfig } from "@/app/admin/const"
+import { LinkWithActive } from "@/components/molecules/linkWithActive"
interface AdminAuthenticatedLayoutInterface {
- children: React.ReactNode;
+ children: React.ReactNode
}
export default async function AdminAuthenticatedLayout({
children,
}: AdminAuthenticatedLayoutInterface) {
- const settingsChildren = find(adminMenuTreeConfig, { id: "membership" })!;
+ const settingsChildren = find(adminMenuTreeConfig, { id: "membership" })!
return (
<>
@@ -28,5 +28,5 @@ export default async function AdminAuthenticatedLayout({
{children}
>
- );
+ )
}
diff --git a/src/app/admin/@authenticated/membership/manage/@modal/(.)create/client.tsx b/src/app/admin/@authenticated/membership/manage/@modal/(.)create/client.tsx
index 3ccd78e..bccd744 100644
--- a/src/app/admin/@authenticated/membership/manage/@modal/(.)create/client.tsx
+++ b/src/app/admin/@authenticated/membership/manage/@modal/(.)create/client.tsx
@@ -1,28 +1,28 @@
-"use client";
+"use client"
-import { AdminMembershipCRUDForm } from "@/app/admin/@authenticated/membership/manage/form";
-import { CRUDFormIntent } from "@/modules/crudForm/types";
-import { useToast } from "@/components/ui/use-toast";
-import { useRouter } from "next/navigation";
+import { AdminMembershipCRUDForm } from "@/app/admin/@authenticated/membership/manage/form"
+import { CRUDFormIntent } from "@/modules/crudForm/types"
+import { useToast } from "@/components/ui/use-toast"
+import { useRouter } from "next/navigation"
export function AdminMembershipCreateModalClient() {
- const { toast } = useToast();
- const router = useRouter();
+ const { toast } = useToast()
+ const router = useRouter()
const onSubmit = () => {
toast({
title: "Success",
description: "Membership Template created",
variant: "success",
- });
- router.back();
- router.refresh();
- };
+ })
+ router.back()
+ router.refresh()
+ }
return (
- );
+ )
}
diff --git a/src/app/admin/@authenticated/membership/manage/@modal/(.)create/page.tsx b/src/app/admin/@authenticated/membership/manage/@modal/(.)create/page.tsx
index 78b7392..f952a97 100644
--- a/src/app/admin/@authenticated/membership/manage/@modal/(.)create/page.tsx
+++ b/src/app/admin/@authenticated/membership/manage/@modal/(.)create/page.tsx
@@ -1,11 +1,11 @@
-import { Modal } from "@/components/molecules/modal";
-import { AdminMembershipCRUDForm } from "@/app/admin/@authenticated/membership/manage/form";
-import { CRUDFormIntent } from "@/modules/crudForm/types";
+import { Modal } from "@/components/molecules/modal"
+import { AdminMembershipCRUDForm } from "@/app/admin/@authenticated/membership/manage/form"
+import { CRUDFormIntent } from "@/modules/crudForm/types"
export default function AdminMembershipModalCreatePage() {
return (
- );
+ )
}
diff --git a/src/app/admin/@authenticated/membership/manage/@modal/(.)edit/[id]/client.tsx b/src/app/admin/@authenticated/membership/manage/@modal/(.)edit/[id]/client.tsx
index 2a9e594..6fdfaab 100644
--- a/src/app/admin/@authenticated/membership/manage/@modal/(.)edit/[id]/client.tsx
+++ b/src/app/admin/@authenticated/membership/manage/@modal/(.)edit/[id]/client.tsx
@@ -1,30 +1,30 @@
-"use client";
+"use client"
-import { AdminMembershipCRUDForm } from "@/app/admin/@authenticated/membership/manage/form";
-import { CRUDFormIntent } from "@/modules/crudForm/types";
-import { type MembershipTemplate } from "@prisma/client";
-import { useToast } from "@/components/ui/use-toast";
-import { useRouter } from "next/navigation";
+import { AdminMembershipCRUDForm } from "@/app/admin/@authenticated/membership/manage/form"
+import { CRUDFormIntent } from "@/modules/crudForm/types"
+import { type MembershipTemplate } from "@prisma/client"
+import { useToast } from "@/components/ui/use-toast"
+import { useRouter } from "next/navigation"
interface AdminMembershipEditPageClient {
- previousValues: MembershipTemplate;
+ previousValues: MembershipTemplate
}
export function AdminMembershipEditModalClient({
previousValues,
}: AdminMembershipEditPageClient) {
- const { toast } = useToast();
- const router = useRouter();
+ const { toast } = useToast()
+ const router = useRouter()
const onSubmit = () => {
toast({
title: "Success",
description: "Membership Template updated",
variant: "success",
- });
- router.back();
- router.refresh();
- };
+ })
+ router.back()
+ router.refresh()
+ }
return (
- );
+ )
}
diff --git a/src/app/admin/@authenticated/membership/manage/@modal/(.)edit/[id]/page.tsx b/src/app/admin/@authenticated/membership/manage/@modal/(.)edit/[id]/page.tsx
index c172d00..bc12e39 100644
--- a/src/app/admin/@authenticated/membership/manage/@modal/(.)edit/[id]/page.tsx
+++ b/src/app/admin/@authenticated/membership/manage/@modal/(.)edit/[id]/page.tsx
@@ -1,29 +1,29 @@
-import { Modal } from "@/components/molecules/modal";
-import { db } from "@/services/db";
-import { AdminMembershipEditModalClient } from "@/app/admin/@authenticated/membership/manage/@modal/(.)edit/[id]/client";
+import { Modal } from "@/components/molecules/modal"
+import { db } from "@/services/db"
+import { AdminMembershipEditModalClient } from "@/app/admin/@authenticated/membership/manage/@modal/(.)edit/[id]/client"
const getData = (id: string) => {
return db.membershipTemplate.findUnique({
where: {
id,
},
- });
-};
+ })
+}
interface AdminMembershipEditPageProps {
params: {
- id: string;
- };
+ id: string
+ }
}
export default async function AdminMembershipModalEditPage({
params,
}: AdminMembershipEditPageProps) {
- const membershipTemplate = await getData(params.id);
+ const membershipTemplate = await getData(params.id)
return (
- );
+ )
}
diff --git a/src/app/admin/@authenticated/membership/manage/@modal/create/client.tsx b/src/app/admin/@authenticated/membership/manage/@modal/create/client.tsx
index 5ca9722..13599fb 100644
--- a/src/app/admin/@authenticated/membership/manage/@modal/create/client.tsx
+++ b/src/app/admin/@authenticated/membership/manage/@modal/create/client.tsx
@@ -1,28 +1,28 @@
-"use client";
+"use client"
-import { AdminMembershipCRUDForm } from "@/app/admin/@authenticated/membership/manage/form";
-import { CRUDFormIntent } from "@/modules/crudForm/types";
-import { useToast } from "@/components/ui/use-toast";
-import { useRouter } from "next/navigation";
+import { AdminMembershipCRUDForm } from "@/app/admin/@authenticated/membership/manage/form"
+import { CRUDFormIntent } from "@/modules/crudForm/types"
+import { useToast } from "@/components/ui/use-toast"
+import { useRouter } from "next/navigation"
export function AdminMembershipCreatePageClient() {
- const { toast } = useToast();
- const router = useRouter();
+ const { toast } = useToast()
+ const router = useRouter()
const onSubmit = () => {
toast({
title: "Success",
description: "Membership Template created",
variant: "success",
- });
- router.replace("/admin/membership/manage");
- router.refresh();
- };
+ })
+ router.replace("/admin/membership/manage")
+ router.refresh()
+ }
return (
- );
+ )
}
diff --git a/src/app/admin/@authenticated/membership/manage/@modal/create/page.tsx b/src/app/admin/@authenticated/membership/manage/@modal/create/page.tsx
index 5b03c8a..f7ec616 100644
--- a/src/app/admin/@authenticated/membership/manage/@modal/create/page.tsx
+++ b/src/app/admin/@authenticated/membership/manage/@modal/create/page.tsx
@@ -1,9 +1,9 @@
-import { AdminMembershipCreatePageClient } from "@/app/admin/@authenticated/membership/manage/@modal/create/client";
+import { AdminMembershipCreatePageClient } from "@/app/admin/@authenticated/membership/manage/@modal/create/client"
export default function AdminMembershipCreatePage() {
return (
- );
+ )
}
diff --git a/src/app/admin/@authenticated/membership/manage/@modal/default.tsx b/src/app/admin/@authenticated/membership/manage/@modal/default.tsx
index 8ee23ba..01a4a7f 100644
--- a/src/app/admin/@authenticated/membership/manage/@modal/default.tsx
+++ b/src/app/admin/@authenticated/membership/manage/@modal/default.tsx
@@ -1,3 +1,3 @@
export default function AdminMembershipModalDefault() {
- return null;
+ return null
}
diff --git a/src/app/admin/@authenticated/membership/manage/@modal/edit/[id]/client.tsx b/src/app/admin/@authenticated/membership/manage/@modal/edit/[id]/client.tsx
index c0e5a3f..274b87d 100644
--- a/src/app/admin/@authenticated/membership/manage/@modal/edit/[id]/client.tsx
+++ b/src/app/admin/@authenticated/membership/manage/@modal/edit/[id]/client.tsx
@@ -1,30 +1,30 @@
-"use client";
+"use client"
-import { AdminMembershipCRUDForm } from "@/app/admin/@authenticated/membership/manage/form";
-import { CRUDFormIntent } from "@/modules/crudForm/types";
-import { type MembershipTemplate } from "@prisma/client";
-import { useToast } from "@/components/ui/use-toast";
-import { useRouter } from "next/navigation";
+import { AdminMembershipCRUDForm } from "@/app/admin/@authenticated/membership/manage/form"
+import { CRUDFormIntent } from "@/modules/crudForm/types"
+import { type MembershipTemplate } from "@prisma/client"
+import { useToast } from "@/components/ui/use-toast"
+import { useRouter } from "next/navigation"
interface AdminMembershipEditPageClient {
- previousValues: MembershipTemplate;
+ previousValues: MembershipTemplate
}
export function AdminMembershipEditPageClient({
previousValues,
}: AdminMembershipEditPageClient) {
- const { toast } = useToast();
- const router = useRouter();
+ const { toast } = useToast()
+ const router = useRouter()
const onSubmit = () => {
toast({
title: "Success",
description: "Membership Template updated",
variant: "success",
- });
- router.replace("/admin/membership/manage");
- router.refresh();
- };
+ })
+ router.replace("/admin/membership/manage")
+ router.refresh()
+ }
return (
- );
+ )
}
diff --git a/src/app/admin/@authenticated/membership/manage/@modal/edit/[id]/page.tsx b/src/app/admin/@authenticated/membership/manage/@modal/edit/[id]/page.tsx
index 720953e..4508194 100644
--- a/src/app/admin/@authenticated/membership/manage/@modal/edit/[id]/page.tsx
+++ b/src/app/admin/@authenticated/membership/manage/@modal/edit/[id]/page.tsx
@@ -1,28 +1,28 @@
-import { db } from "@/services/db";
-import { AdminMembershipEditPageClient } from "@/app/admin/@authenticated/membership/manage/@modal/edit/[id]/client";
+import { db } from "@/services/db"
+import { AdminMembershipEditPageClient } from "@/app/admin/@authenticated/membership/manage/@modal/edit/[id]/client"
const getData = (id: string) => {
return db.membershipTemplate.findUnique({
where: {
id,
},
- });
-};
+ })
+}
interface AdminMembershipEditPageProps {
params: {
- id: string;
- };
+ id: string
+ }
}
export default async function AdminMembershipEditPage({
params,
}: AdminMembershipEditPageProps) {
- const membershipTemplate = await getData(params.id);
+ const membershipTemplate = await getData(params.id)
return (
- );
+ )
}
diff --git a/src/app/admin/@authenticated/membership/manage/default.tsx b/src/app/admin/@authenticated/membership/manage/default.tsx
index 8ee23ba..01a4a7f 100644
--- a/src/app/admin/@authenticated/membership/manage/default.tsx
+++ b/src/app/admin/@authenticated/membership/manage/default.tsx
@@ -1,3 +1,3 @@
export default function AdminMembershipModalDefault() {
- return null;
+ return null
}
diff --git a/src/app/admin/@authenticated/membership/manage/form.tsx b/src/app/admin/@authenticated/membership/manage/form.tsx
index 973fcb7..2675c2d 100644
--- a/src/app/admin/@authenticated/membership/manage/form.tsx
+++ b/src/app/admin/@authenticated/membership/manage/form.tsx
@@ -1,6 +1,6 @@
"use client"
-import { useForm } from "react-hook-form"
+import { type SubmitHandler, useForm } from "react-hook-form"
import { z } from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
import {
@@ -71,7 +71,7 @@ export function AdminMembershipCRUDForm({
? {}
: {
...previousValues,
- features: previousValues?.features.join(","),
+ features: previousValues?.features?.join(",") || "",
priceAmount: previousValues.priceAmount / 100,
},
resolver: zodResolver(formSchema),
@@ -89,7 +89,13 @@ export function AdminMembershipCRUDForm({
}, [membershipTemplateState, onSuccess, onSuccessFired])
return (
-
>
- );
+ )
}
diff --git a/src/app/documents/legal/statute/page.tsx b/src/app/documents/legal/statute/page.tsx
index f3a0738..4721188 100644
--- a/src/app/documents/legal/statute/page.tsx
+++ b/src/app/documents/legal/statute/page.tsx
@@ -40,5 +40,5 @@ export default function StatutePage() {
at nulla.
>
- );
+ )
}
diff --git a/src/app/documents/legal/terms-of-service/page.tsx b/src/app/documents/legal/terms-of-service/page.tsx
index e8f3c1e..aab9e98 100644
--- a/src/app/documents/legal/terms-of-service/page.tsx
+++ b/src/app/documents/legal/terms-of-service/page.tsx
@@ -40,5 +40,5 @@ export default function TermsOfServicePage() {
at nulla.
>
- );
+ )
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index a858674..220b340 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,22 +1,22 @@
-import "@/styles/globals.css";
+import "@/styles/globals.css"
-import { Inter } from "next/font/google";
+import { Inter } from "next/font/google"
-import NextAuthProvider from "@/context/NextAuthProvider";
+import NextAuthProvider from "@/context/NextAuthProvider"
const inter = Inter({
subsets: ["latin"],
variable: "--font-sans",
-});
+})
export const metadata = {
description: "Generated by create-t3-app",
icons: [{ rel: "icon", url: "/favicon.ico" }],
title: "Create T3 App",
-};
+}
interface LayoutInterface {
- children: React.ReactNode;
+ children: React.ReactNode
}
export default async function RootLayout({ children }: LayoutInterface) {
@@ -26,5 +26,5 @@ export default async function RootLayout({ children }: LayoutInterface) {
{children}