diff --git a/README.md b/README.md index 35c0022..4a00822 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,10 @@ ## Running it locally -Before you being, install [Wasp](https://wasp-lang.dev) by running `curl -sSL https://get.wasp-lang.dev/installer.sh | sh` in your terminal. -1. First clone this repo. -2. Create a `.env.server` file in the root of the project -3. Copy the `env.server.example` file contents to `.env.server` and fill in your API keys +1. Make sure you have the latest version of [Wasp](https://wasp-lang.dev) installed by running `curl -sSL https://get.wasp-lang.dev/installer.sh | sh` in your terminal. +2. Run `wasp new -t saas` to create a new app using this template. +3. Rename the `env.server.example` file to `.env.server` and fill in your API keys 4. Make sure you have a Database connected and running. Here are two quick options: - run `wasp start db` if you have Docker installed (if not, on MacOS run `brew install docker-machine docker`). This will start a Postgres database for you. No need to do anything else! 🤯 - or provision a Postgres database on [Railway](https://railway.app), go to settings and copy the `connection url`. Past it as `DATABASE_URL=` into your `env.server` file. diff --git a/main.wasp b/main.wasp index 8ced9a0..bb5ec71 100644 --- a/main.wasp +++ b/main.wasp @@ -15,10 +15,26 @@ app SaaSTemplate { userEntity: User, externalAuthEntity: SocialLogin, methods: { + email: { + fromField: { + name: "SaaS App", + // make sure this address is the same you registered your SendGrid or MailGun account with! + email: "vince@wasp-lang.dev" + }, + emailVerification: { + clientRoute: EmailVerificationRoute, + getEmailContentFn: import { getVerificationEmailContent } from "@server/auth/email.js", + }, + passwordReset: { + clientRoute: PasswordResetRoute, + getEmailContentFn: import { getPasswordResetEmailContent } from "@server/auth/email.js", + }, + }, google: { // Guide for setting up Auth via Google https://wasp-lang.dev/docs/integrations/google getUserFieldsFn: import { getUserFields } from "@server/auth/google.js", configFn: import { config } from "@server/auth/google.js", }, + // gitHub: {} // Guide for setting up Auth via Github https://wasp-lang.dev/docs/integrations/github }, onAuthFailedRedirectTo: "/", }, @@ -31,10 +47,15 @@ app SaaSTemplate { emailSender: { provider: SendGrid, defaultFrom: { - name: "MySaaSApp", - // make sure this address is the same you registered your SendGrid account with! - email: "email@mysaasapp.com" + name: "SaaS App", + // make sure this address is the same you registered your SendGrid or MailGun account with! + email: "vince@wasp-lang.dev" }, + // defaultFrom: { + // name: "MySaaSApp", + // // make sure this address is the same you registered your SendGrid or MailGun account with! + // email: "email@mysaasapp.com" + // }, }, dependencies: [ ("@headlessui/react", "1.7.13"), @@ -55,15 +76,19 @@ app SaaSTemplate { */ entity User {=psl - id Int @id @default(autoincrement()) - email String @unique - stripeId String? - checkoutSessionId String? - hasPaid Boolean @default(false) - sendEmail Boolean @default(false) - datePaid DateTime? - credits Int @default(3) - relatedObject RelatedObject[] + id Int @id @default(autoincrement()) + email String? @unique + password String? + isEmailVerified Boolean @default(false) + emailVerificationSentAt DateTime? + passwordResetSentAt DateTime? + stripeId String? + checkoutSessionId String? + hasPaid Boolean @default(false) + sendEmail Boolean @default(false) + datePaid DateTime? + credits Int @default(3) + relatedObject RelatedObject[] externalAuthAssociations SocialLogin[] psl=} @@ -99,7 +124,27 @@ page MainPage { route LoginRoute { path: "/login", to: LoginPage } page LoginPage { - component: import Login from "@client/Login" + component: import Login from "@client/auth/LoginPage" +} + +route SignupRoute { path: "/signup", to: SignupPage } +page SignupPage { + component: import { Signup } from "@client/auth/SignupPage" +} + +route RequestPasswordResetRoute { path: "/request-password-reset", to: RequestPasswordResetPage } +page RequestPasswordResetPage { + component: import { RequestPasswordReset } from "@client/auth/RequestPasswordReset", +} + +route PasswordResetRoute { path: "/password-reset", to: PasswordResetPage } +page PasswordResetPage { + component: import { PasswordReset } from "@client/auth/PasswordReset", +} + +route EmailVerificationRoute { path: "/email-verification", to: EmailVerificationPage } +page EmailVerificationPage { + component: import { EmailVerification } from "@client/auth/EmailVerification", } route GptRoute { path: "/gpt", to: GptPage } @@ -159,6 +204,7 @@ query getRelatedObjects { /* * 📡 These are custom Wasp API Endpoints. Use them for callbacks, webhooks, etc. + * https://wasp-lang.dev/docs/language/features#apis */ api stripeWebhook { diff --git a/migrations/20230417122144_email_password/migration.sql b/migrations/20230417122144_email_password/migration.sql new file mode 100644 index 0000000..983a5dd --- /dev/null +++ b/migrations/20230417122144_email_password/migration.sql @@ -0,0 +1,6 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "emailVerificationSentAt" TIMESTAMP(3), +ADD COLUMN "isEmailVerified" BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN "password" TEXT, +ADD COLUMN "passwordResetSentAt" TIMESTAMP(3), +ALTER COLUMN "email" DROP NOT NULL; diff --git a/src/client/AccountPage.tsx b/src/client/AccountPage.tsx index 4fa705e..1c4cd7f 100644 --- a/src/client/AccountPage.tsx +++ b/src/client/AccountPage.tsx @@ -72,7 +72,7 @@ function BuyMoreButton({ isLoading, setIsLoading }: { isLoading: boolean, setIsL const handleClick = async () => { setIsLoading(true); const stripeResults = await stripePayment(); - if (stripeResults) { + if (stripeResults?.sessionUrl) { window.open(stripeResults.sessionUrl, '_self'); } setIsLoading(false); diff --git a/src/client/CheckoutPage.tsx b/src/client/CheckoutPage.tsx index 2c9738b..bf43baa 100644 --- a/src/client/CheckoutPage.tsx +++ b/src/client/CheckoutPage.tsx @@ -8,7 +8,6 @@ export default function CheckoutPage({ user }: { user: User }) { const history = useHistory(); useEffect(() => { - function delayedRedirect() { return setTimeout(() => { history.push('/account'); @@ -33,19 +32,23 @@ export default function CheckoutPage({ user }: { user: User }) { }, []); return ( - <> -

- {hasPaid === 'paid' - ? '🥳 Payment Successful!' - : hasPaid === 'canceled' - ? '😢 Payment Canceled' - : hasPaid === 'error' && '🙄 Payment Error'} -

- {hasPaid !== 'loading' && ( - - You are being redirected to your account page...
-
- )} - +
+
+
+

+ {hasPaid === 'paid' + ? '🥳 Payment Successful!' + : hasPaid === 'canceled' + ? '😢 Payment Canceled' + : hasPaid === 'error' && '🙄 Payment Error'} +

+ {hasPaid !== 'loading' && ( + + You are being redirected to your account page...
+
+ )} +
+
+
); } diff --git a/src/client/GptPage.tsx b/src/client/GptPage.tsx index fc9e107..ddfabbc 100644 --- a/src/client/GptPage.tsx +++ b/src/client/GptPage.tsx @@ -17,7 +17,7 @@ export default function GptPage() { const { data: user } = useAuth(); const onSubmit = async ({ instructions, command, temperature }: any) => { - console.log('user, ', !!user) + console.log('user, ', !!user); if (!user) { alert('You must be logged in to use this feature.'); return; @@ -25,7 +25,7 @@ export default function GptPage() { try { const response = (await generateGptResponse({ instructions, command, temperature })) as RelatedObject; if (response) { - setResponse(response.content) + setResponse(response.content); } } catch (e) { alert('Something went wrong. Please try again.'); @@ -41,97 +41,103 @@ export default function GptPage() { } = useForm(); return ( -
-
-
-
- -
-