From bd9c09471f85e21d5eeafed44ca77becfbc1e51b Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Tue, 26 Sep 2023 11:59:50 +0200 Subject: [PATCH] Adds Auth and Email Sender recipes Signed-off-by: Mihovil Ilakovac --- waspc/cli/src/Wasp/Cli/Command/UseRecipe.hs | 19 +++-- .../src/Wasp/Cli/Command/UseRecipe/Auth.hs | 22 ++++-- .../Wasp/Cli/Command/UseRecipe/Auth/Common.hs | 60 ++++++++++++++ .../Wasp/Cli/Command/UseRecipe/Auth/Email.hs | 64 +++++++++++++++ .../Wasp/Cli/Command/UseRecipe/Auth/Local.hs | 40 ++++++++++ .../Wasp/Cli/Command/UseRecipe/Auth/Social.hs | 79 +++++++++++++++++++ .../src/Wasp/Cli/Command/UseRecipe/Common.hs | 15 +++- .../Wasp/Cli/Command/UseRecipe/EmailSender.hs | 66 ++++++++++++++++ .../Wasp/Cli/Command/UseRecipe/Tailwind.hs | 16 ++-- .../recipes/auth/EmailVerificationPage.jsx | 5 ++ waspc/data/Cli/recipes/auth/LoginPage.jsx | 5 ++ .../recipes/auth/RequestPasswordResetPage.jsx | 5 ++ .../Cli/recipes/auth/ResetPasswordPage.jsx | 5 ++ waspc/data/Cli/recipes/auth/SignupPage.jsx | 5 ++ waspc/src/Wasp/Util/IO.hs | 4 + waspc/waspc.cabal | 5 ++ 16 files changed, 392 insertions(+), 23 deletions(-) create mode 100644 waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Common.hs create mode 100644 waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Email.hs create mode 100644 waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Local.hs create mode 100644 waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Social.hs create mode 100644 waspc/cli/src/Wasp/Cli/Command/UseRecipe/EmailSender.hs create mode 100644 waspc/data/Cli/recipes/auth/EmailVerificationPage.jsx create mode 100644 waspc/data/Cli/recipes/auth/LoginPage.jsx create mode 100644 waspc/data/Cli/recipes/auth/RequestPasswordResetPage.jsx create mode 100644 waspc/data/Cli/recipes/auth/ResetPasswordPage.jsx create mode 100644 waspc/data/Cli/recipes/auth/SignupPage.jsx diff --git a/waspc/cli/src/Wasp/Cli/Command/UseRecipe.hs b/waspc/cli/src/Wasp/Cli/Command/UseRecipe.hs index 682ec2a9c..fd7344327 100644 --- a/waspc/cli/src/Wasp/Cli/Command/UseRecipe.hs +++ b/waspc/cli/src/Wasp/Cli/Command/UseRecipe.hs @@ -5,11 +5,13 @@ import Data.List.NonEmpty (fromList) import Wasp.Cli.Command (Command) import Wasp.Cli.Command.Require (InWaspProject (InWaspProject), require) import Wasp.Cli.Command.UseRecipe.Auth (useAuth) +import Wasp.Cli.Command.UseRecipe.EmailSender (useEmailSender) import Wasp.Cli.Command.UseRecipe.Tailwind (useTailwind) import qualified Wasp.Cli.Interactive as Interactive data Recipe = Recipe { recipeName :: String, + recipeDescription :: String, execute :: Command () } @@ -18,20 +20,27 @@ instance Show Recipe where instance Interactive.Option Recipe where showOption = show - showOptionDescription _ = Nothing + showOptionDescription = Just . recipeDescription useRecipe :: [String] -> Command () useRecipe _args = do - InWaspProject waspProjectDir <- require + InWaspProject _ <- require let recipes = [ Recipe - { recipeName = "tailwind", - execute = useTailwind waspProjectDir + { recipeName = "Tailwind", + recipeDescription = "Add support for Tailwind CSS", + execute = useTailwind }, Recipe - { recipeName = "auth", + { recipeName = "Auth", + recipeDescription = "Add authentication to your app", execute = useAuth + }, + Recipe + { recipeName = "Email sender", + recipeDescription = "Set up an email sender", + execute = useEmailSender } ] diff --git a/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth.hs b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth.hs index bbb62372b..3b6f60228 100644 --- a/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth.hs +++ b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth.hs @@ -1,9 +1,14 @@ +{-# LANGUAGE InstanceSigs #-} + module Wasp.Cli.Command.UseRecipe.Auth where import Control.Monad.IO.Class (liftIO) import Data.List.NonEmpty (fromList) import Wasp.Cli.Command (Command) import Wasp.Cli.Command.Message (cliSendMessageC) +import Wasp.Cli.Command.UseRecipe.Auth.Email (useEmail) +import Wasp.Cli.Command.UseRecipe.Auth.Local (useLocal) +import Wasp.Cli.Command.UseRecipe.Auth.Social (useGithub, useGoogle) import qualified Wasp.Cli.Interactive as Interactive import qualified Wasp.Message as Msg @@ -16,20 +21,23 @@ instance Show AuthMethod where show Github = "Github" instance Interactive.Option AuthMethod where + showOption :: AuthMethod -> String showOption = show + showOptionDescription :: AuthMethod -> Maybe String showOptionDescription _ = Nothing useAuth :: Command () useAuth = do method <- liftIO selectMethod - cliSendMessageC $ Msg.Start "Installing authentication..." - - -- Create React pages for each method - -- Edit the Wasp file (or prompt the user to do so) - - cliSendMessageC $ Msg.Success $ "Installed " <> show method <> " authentication!" + cliSendMessageC $ Msg.Start $ "Installing " <> show method <> " authentication..." + useMethod method where + useMethod Email = useEmail + useMethod UsernameAndPassword = useLocal + useMethod Google = useGoogle + useMethod Github = useGithub + methods = [ Email, UsernameAndPassword, @@ -39,6 +47,6 @@ useAuth = do selectMethod = Interactive.askToChoose - "What authentication method do you want to use?" + "Which authentication method do you want to use?" (fromList methods) Interactive.ChooserConfig {Interactive.hasDefaultOption = False} diff --git a/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Common.hs b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Common.hs new file mode 100644 index 000000000..5457cf200 --- /dev/null +++ b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Common.hs @@ -0,0 +1,60 @@ +module Wasp.Cli.Command.UseRecipe.Auth.Common where + +import Control.Monad.IO.Class (liftIO) +import StrongPath (Abs, Dir, Path', Rel, reldir, relfile, ()) +import Wasp.Cli.Command (Command, require) +import Wasp.Cli.Command.Require (InWaspProject (InWaspProject)) +import Wasp.Cli.Command.UseRecipe.Common (RecipesDir, copyFileIfDoesNotExist) +import Wasp.Cli.Common (WaspProjectDir) + +data AuthRecipeDir + +authDirInRecipesDir :: Path' (Rel RecipesDir) (Dir AuthRecipeDir) +authDirInRecipesDir = [reldir|auth|] + +copyLoginPage :: Command () +copyLoginPage = do + authTargetDir <- getAuthTargetDir + liftIO $ copyFileIfDoesNotExist (authDirInRecipesDir loginPagePath) (authTargetDir loginPagePath) + where + loginPagePath = [relfile|LoginPage.jsx|] + +copySignupPage :: Command () +copySignupPage = do + authTargetDir <- getAuthTargetDir + liftIO $ copyFileIfDoesNotExist (authDirInRecipesDir signupPagePath) (authTargetDir signupPagePath) + where + signupPagePath = [relfile|SignupPage.jsx|] + +copyRequestPasswordResetPage :: Command () +copyRequestPasswordResetPage = do + authTargetDir <- getAuthTargetDir + liftIO $ copyFileIfDoesNotExist (authDirInRecipesDir requestPasswordResetPagePath) (authTargetDir requestPasswordResetPagePath) + where + requestPasswordResetPagePath = [relfile|RequestPasswordResetPage.jsx|] + +copyResetPasswordPage :: Command () +copyResetPasswordPage = do + authTargetDir <- getAuthTargetDir + liftIO $ copyFileIfDoesNotExist (authDirInRecipesDir resetPasswordPagePath) (authTargetDir resetPasswordPagePath) + where + resetPasswordPagePath = [relfile|ResetPasswordPage.jsx|] + +copyEmailVerificationPage :: Command () +copyEmailVerificationPage = do + authTargetDir <- getAuthTargetDir + liftIO $ copyFileIfDoesNotExist (authDirInRecipesDir emailVerificationPagePath) (authTargetDir emailVerificationPagePath) + where + emailVerificationPagePath = [relfile|EmailVerificationPage.jsx|] + +showRouteAndPageDSL :: String -> String -> String -> String +showRouteAndPageDSL routeName path pageName = unlines [route, page] + where + route = "route " <> routeName <> " { path: \"" <> path <> "\", to: " <> pageName <> " }" + page = "page " <> pageName <> " { component: import { " <> pageName <> " } from \"@client/auth/" <> pageName <> ".jsx\" }" + +getAuthTargetDir :: Command (Path' Abs (Dir WaspProjectDir)) +getAuthTargetDir = do + InWaspProject waspProjectDir <- require + let authTargetDir = waspProjectDir [reldir|src/client/auth|] + return authTargetDir diff --git a/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Email.hs b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Email.hs new file mode 100644 index 000000000..8459c9453 --- /dev/null +++ b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Email.hs @@ -0,0 +1,64 @@ +module Wasp.Cli.Command.UseRecipe.Auth.Email where + +import Wasp.Cli.Command +import Wasp.Cli.Command.Message +import Wasp.Cli.Command.UseRecipe.Auth.Common (copyEmailVerificationPage, copyLoginPage, copyRequestPasswordResetPage, copyResetPasswordPage, copySignupPage, showRouteAndPageDSL) +import Wasp.Cli.Command.UseRecipe.EmailSender (useEmailSender) +import qualified Wasp.Message as Msg +import qualified Wasp.Util.Terminal as Term + +useEmail :: Command () +useEmail = do + useEmailSender + + copyLoginPage + copySignupPage + copyRequestPasswordResetPage + copyResetPasswordPage + copyEmailVerificationPage + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following entity definition to your main.wasp file:\n" + cliSendMessageC $ + Msg.Info $ + unlines + [ "entity User {=psl", + " id Int @id @default(autoincrement())", + " email String? @unique", + " password String?", + " isEmailVerified Boolean @default(false)", + " emailVerificationSentAt DateTime?", + " passwordResetSentAt DateTime?", + "psl=}" + ] + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following auth block to the app block in your main.wasp file:\n" + cliSendMessageC $ + Msg.Info $ + unlines + [ "auth: {", + " userEntity: User,", + " methods: {", + " email: {", + " fromField: {", + " name: \"My App\",", + " email: \"myapp@domain.com\"", + " },", + " emailVerification: {", + " clientRoute: EmailVerificationRoute,", + " },", + " passwordReset: {", + " clientRoute: ResetPasswordRoute", + " },", + " allowUnverifiedLogin: false,", + " },", + " },", + " onAuthFailedRedirectTo: \"/login\"", + "}" + ] + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following routes and pages to your main.wasp file:\n" + cliSendMessageC $ Msg.Info $ showRouteAndPageDSL "LoginRoute" "/login" "LoginPage" + cliSendMessageC $ Msg.Info $ showRouteAndPageDSL "SignupRoute" "/signup" "SignupPage" + cliSendMessageC $ Msg.Info $ showRouteAndPageDSL "RequestPasswordResetRoute" "/request-password-reset" "RequestPasswordResetPage" + cliSendMessageC $ Msg.Info $ showRouteAndPageDSL "ResetPasswordRoute" "/reset-password" "ResetPasswordPage" + cliSendMessageC $ Msg.Info $ showRouteAndPageDSL "EmailVerificationRoute" "/email-verification" "EmailVerificationPage" diff --git a/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Local.hs b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Local.hs new file mode 100644 index 000000000..7259248e6 --- /dev/null +++ b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Local.hs @@ -0,0 +1,40 @@ +module Wasp.Cli.Command.UseRecipe.Auth.Local where + +import Wasp.Cli.Command (Command) +import Wasp.Cli.Command.Message (cliSendMessageC) +import Wasp.Cli.Command.UseRecipe.Auth.Common (copyLoginPage, copySignupPage, showRouteAndPageDSL) +import qualified Wasp.Message as Msg +import qualified Wasp.Util.Terminal as Term + +useLocal :: Command () +useLocal = do + copyLoginPage + copySignupPage + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following entity definition to your main.wasp file:\n" + cliSendMessageC $ + Msg.Info $ + unlines + [ "entity User {=psl", + " id Int @id @default(autoincrement())", + " username String @unique", + " password String", + "psl=}" + ] + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following auth block to the app block in your main.wasp file:\n" + cliSendMessageC $ + Msg.Info $ + unlines + [ "auth: {", + " userEntity: User,", + " methods: {", + " usernameAndPassword: {},", + " },", + " onAuthFailedRedirectTo: \"/login\"", + "}" + ] + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following routes and pages to your main.wasp file:\n" + cliSendMessageC $ Msg.Info $ showRouteAndPageDSL "LoginRoute" "/login" "LoginPage" + cliSendMessageC $ Msg.Info $ showRouteAndPageDSL "SignupRoute" "/signup" "SignupPage" diff --git a/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Social.hs b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Social.hs new file mode 100644 index 000000000..207bd04d8 --- /dev/null +++ b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Auth/Social.hs @@ -0,0 +1,79 @@ +module Wasp.Cli.Command.UseRecipe.Auth.Social where + +import Wasp.Cli.Command (Command) +import Wasp.Cli.Command.Message (cliSendMessageC) +import Wasp.Cli.Command.UseRecipe.Auth.Common (copyLoginPage, showRouteAndPageDSL) +import Wasp.Cli.Command.UseRecipe.Common (appendToServerEnv) +import qualified Wasp.Message as Msg +import qualified Wasp.Util.Terminal as Term + +useGoogle :: Command () +useGoogle = do + copyLoginPage + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following entities to your main.wasp file:\n" + printSocialLoginEntities + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following auth block to the app block in your main.wasp file:\n" + printSocialLoginAuthBlock "google" + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following routes and pages to your main.wasp file:\n" + cliSendMessageC $ Msg.Info $ showRouteAndPageDSL "LoginRoute" "/login" "LoginPage" + + appendToServerEnv $ unlines ["GOOGLE_CLIENT_ID=\"\"", "GOOGLE_CLIENT_SECRET=\"\""] + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Fill the values for GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env.server file." + +useGithub :: Command () +useGithub = do + copyLoginPage + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following entities to your main.wasp file:\n" + printSocialLoginEntities + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following auth block to the app block in your main.wasp file:\n" + printSocialLoginAuthBlock "github" + + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following routes and pages to your main.wasp file:\n" + cliSendMessageC $ Msg.Info $ showRouteAndPageDSL "LoginRoute" "/login" "LoginPage" + + appendToServerEnv $ unlines ["GITHUB_CLIENT_ID=\"\"", "GITHUB_CLIENT_SECRET=\"\""] + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Fill the values for GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET in .env.server file." + +printSocialLoginEntities :: Command () +printSocialLoginEntities = do + cliSendMessageC $ + Msg.Info $ + unlines + [ "entity User {=psl", + " id Int @id @default(autoincrement())", + " externalAuthAssociations SocialLogin[]", + "psl=}" + ] + cliSendMessageC $ + Msg.Info $ + unlines + [ "entity SocialLogin {=psl", + " id Int @id @default(autoincrement())", + " provider String", + " providerId String", + " user User @relation(fields: [userId], references: [id], onDelete: Cascade)", + " userId Int", + " createdAt DateTime @default(now())", + " @@unique([provider, providerId, userId])", + "psl=}" + ] + +printSocialLoginAuthBlock :: String -> Command () +printSocialLoginAuthBlock provider = do + cliSendMessageC $ + Msg.Info $ + unlines + [ "auth: {", + " userEntity: User,", + " externalAuthEntity: SocialLogin,", + " methods: {", + " " <> provider <> ": {},", + " },", + " onAuthFailedRedirectTo: \"/login\"", + "}" + ] diff --git a/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Common.hs b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Common.hs index fb741175c..b8daa18e3 100644 --- a/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Common.hs +++ b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Common.hs @@ -1,10 +1,14 @@ module Wasp.Cli.Command.UseRecipe.Common where import Control.Monad (unless) -import StrongPath (Abs, Dir, File, Path', Rel, reldir, toFilePath, ()) +import Control.Monad.IO.Class (liftIO) +import StrongPath (Abs, Dir, File, Path', Rel, reldir, relfile, toFilePath, ()) import System.Directory (copyFile, createDirectoryIfMissing, doesFileExist) import System.FilePath (takeDirectory) +import Wasp.Cli.Command (Command, require) +import Wasp.Cli.Command.Require (InWaspProject (InWaspProject)) import qualified Wasp.Data +import Wasp.Util.IO (appendToFile) data RecipesDir @@ -24,9 +28,14 @@ copyFileIfDoesNotExist pathInRecipesDir pathInProjectDir = do let pathInProjectDirStr = toFilePath pathInProjectDir let pathInRecipesDirStr = toFilePath $ recipesDir pathInRecipesDir - -- _ <- error $ "Copying " <> pathInRecipesDirStr <> " to " <> pathInProjectDirStr - isExistingFile <- doesFileExist pathInProjectDirStr unless isExistingFile $ do createDirectoryIfMissing True $ takeDirectory pathInProjectDirStr copyFile pathInRecipesDirStr pathInProjectDirStr + +appendToServerEnv :: String -> Command () +appendToServerEnv content = do + InWaspProject waspProjectDir <- require + + let serverEnvPath = waspProjectDir [relfile|.env.server|] + liftIO $ appendToFile serverEnvPath content diff --git a/waspc/cli/src/Wasp/Cli/Command/UseRecipe/EmailSender.hs b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/EmailSender.hs new file mode 100644 index 000000000..34d0a2ce8 --- /dev/null +++ b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/EmailSender.hs @@ -0,0 +1,66 @@ +module Wasp.Cli.Command.UseRecipe.EmailSender where + +import Control.Monad.IO.Class (liftIO) +import Data.List.NonEmpty (fromList) +import Wasp.Cli.Command (Command) +import Wasp.Cli.Command.Message (cliSendMessageC) +import Wasp.Cli.Command.UseRecipe.Common (appendToServerEnv) +import qualified Wasp.Cli.Interactive as Interactive +import qualified Wasp.Message as Msg +import qualified Wasp.Util.Terminal as Term + +data EmailProvider = SMTP | SendGrid | Mailgun + +instance Show EmailProvider where + show SMTP = "SMTP" + show SendGrid = "SendGrid" + show Mailgun = "Mailgun" + +instance Interactive.Option EmailProvider where + showOption = show + + showOptionDescription _ = Nothing + +useEmailSender :: Command () +useEmailSender = do + provider <- liftIO selectProvider + + cliSendMessageC $ Msg.Start $ "Setting up " <> show provider <> " email sender..." + + useProvider provider + where + providers = + [ SMTP, + SendGrid, + Mailgun + ] + selectProvider = + Interactive.askToChoose + "Which email sender do you want to use?" + (fromList providers) + Interactive.ChooserConfig {Interactive.hasDefaultOption = False} + + useProvider :: EmailProvider -> Command () + useProvider SMTP = useEmailProvider "SMTP" ["SMTP_HOST", "SMTP_PORT", "SMTP_USERNAME", "SMTP_PASSWORD"] + useProvider SendGrid = useEmailProvider "SendGrid" ["SENDGRID_API_KEY"] + useProvider Mailgun = useEmailProvider "Mailgun" ["MAILGUN_API_KEY", "MAILGUN_DOMAIN"] + +useEmailProvider :: String -> [String] -> Command () +useEmailProvider providerName providerEnvVariableNames = do + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] "Add the following email sender block to the app block in your main.wasp file:\n" + cliSendMessageC $ + Msg.Info $ + unlines + [ "emailSender: {", + " provider: " <> providerName, + "}" + ] + + appendToServerEnv $ unlines $ map (<> "=\"\"") providerEnvVariableNames + cliSendMessageC $ Msg.Info $ Term.applyStyles [Term.Bold] $ "Fill the values for " <> joinForSentence providerEnvVariableNames <> " in .env.server file." + +joinForSentence :: [String] -> String +joinForSentence [] = "" +joinForSentence [x] = x +joinForSentence [x, y] = x <> " and " <> y +joinForSentence (x : xs) = x <> ", " <> joinForSentence xs diff --git a/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Tailwind.hs b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Tailwind.hs index 542875da1..2dc59c0e4 100644 --- a/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Tailwind.hs +++ b/waspc/cli/src/Wasp/Cli/Command/UseRecipe/Tailwind.hs @@ -2,21 +2,20 @@ module Wasp.Cli.Command.UseRecipe.Tailwind where import Control.Monad.Cont (MonadIO (liftIO)) import StrongPath - ( Abs, - Dir, + ( Dir, Path', Rel, reldir, relfile, (), ) -import Wasp.Cli.Command (Command) +import Wasp.Cli.Command (Command, require) import Wasp.Cli.Command.Message (cliSendMessageC) +import Wasp.Cli.Command.Require (InWaspProject (InWaspProject)) import Wasp.Cli.Command.UseRecipe.Common ( RecipesDir, copyFileIfDoesNotExist, ) -import Wasp.Cli.Common (WaspProjectDir) import qualified Wasp.Message as Msg data TailwindDir @@ -24,8 +23,11 @@ data TailwindDir tailwindDirInRecipesDir :: Path' (Rel RecipesDir) (Dir TailwindDir) tailwindDirInRecipesDir = [reldir|tailwind|] -useTailwind :: Path' Abs (Dir WaspProjectDir) -> Command () -useTailwind waspProjectDir = do +useTailwind :: Command () +useTailwind = do + InWaspProject waspProjectDir <- require + let tailwindTargetDir = waspProjectDir + cliSendMessageC $ Msg.Start "Setting up Tailwind config files..." liftIO $ do @@ -44,7 +46,5 @@ useTailwind waspProjectDir = do "You can now use Tailwind classes in your CSS files." ] where - tailwindTargetDir = waspProjectDir - tailwindConfigPath = [relfile|tailwind.config.cjs|] postcssConfigPath = [relfile|postcss.config.cjs|] diff --git a/waspc/data/Cli/recipes/auth/EmailVerificationPage.jsx b/waspc/data/Cli/recipes/auth/EmailVerificationPage.jsx new file mode 100644 index 000000000..2f8ff0a90 --- /dev/null +++ b/waspc/data/Cli/recipes/auth/EmailVerificationPage.jsx @@ -0,0 +1,5 @@ +import { VerifyEmailForm } from "@wasp/auth/forms/VerifyEmail"; + +export function EmailVerificationPage() { + return ; +} diff --git a/waspc/data/Cli/recipes/auth/LoginPage.jsx b/waspc/data/Cli/recipes/auth/LoginPage.jsx new file mode 100644 index 000000000..a2f31d652 --- /dev/null +++ b/waspc/data/Cli/recipes/auth/LoginPage.jsx @@ -0,0 +1,5 @@ +import { LoginForm } from "@wasp/auth/forms/Login"; + +export function LoginPage() { + return ; +} diff --git a/waspc/data/Cli/recipes/auth/RequestPasswordResetPage.jsx b/waspc/data/Cli/recipes/auth/RequestPasswordResetPage.jsx new file mode 100644 index 000000000..685ac8242 --- /dev/null +++ b/waspc/data/Cli/recipes/auth/RequestPasswordResetPage.jsx @@ -0,0 +1,5 @@ +import { ForgotPasswordForm } from "@wasp/auth/forms/ForgotPassword"; + +export function RequestPasswordResetPage() { + return ; +} diff --git a/waspc/data/Cli/recipes/auth/ResetPasswordPage.jsx b/waspc/data/Cli/recipes/auth/ResetPasswordPage.jsx new file mode 100644 index 000000000..dc0c35b68 --- /dev/null +++ b/waspc/data/Cli/recipes/auth/ResetPasswordPage.jsx @@ -0,0 +1,5 @@ +import { ResetPasswordForm } from "@wasp/auth/forms/ResetPassword"; + +export function ResetPasswordPage() { + return ; +} diff --git a/waspc/data/Cli/recipes/auth/SignupPage.jsx b/waspc/data/Cli/recipes/auth/SignupPage.jsx new file mode 100644 index 000000000..169df1414 --- /dev/null +++ b/waspc/data/Cli/recipes/auth/SignupPage.jsx @@ -0,0 +1,5 @@ +import { SignupForm } from "@wasp/auth/forms/Signup"; + +export function SignupPage() { + return ; +} diff --git a/waspc/src/Wasp/Util/IO.hs b/waspc/src/Wasp/Util/IO.hs index 2c4b38686..1ed3e1838 100644 --- a/waspc/src/Wasp/Util/IO.hs +++ b/waspc/src/Wasp/Util/IO.hs @@ -12,6 +12,7 @@ module Wasp.Util.IO removeFile, isDirectoryEmpty, writeFileFromText, + appendToFile, ) where @@ -111,3 +112,6 @@ isDirectoryEmpty :: Path' Abs (Dir d) -> IO Bool isDirectoryEmpty dirPath = do (files, dirs) <- listDirectory dirPath return $ null files && null dirs + +appendToFile :: Path' Abs (File f) -> String -> IO () +appendToFile = P.appendFile . SP.fromAbsFile diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index f7e657934..1b5d19696 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -480,6 +480,11 @@ library cli-lib Wasp.Cli.Command.UseRecipe.Auth Wasp.Cli.Command.UseRecipe.Tailwind Wasp.Cli.Command.UseRecipe.Common + Wasp.Cli.Command.UseRecipe.Auth.Common + Wasp.Cli.Command.UseRecipe.Auth.Email + Wasp.Cli.Command.UseRecipe.Auth.Local + Wasp.Cli.Command.UseRecipe.Auth.Social + Wasp.Cli.Command.UseRecipe.EmailSender Wasp.Cli.Common Wasp.Cli.Terminal Wasp.Cli.Command.Message